Skip to content
This repository was archived by the owner on Jul 16, 2026. It is now read-only.

harden(task-bridge): PR #982 field-ordering + #1035 fallthrough guard - #70

Open
jgluck96 wants to merge 1 commit into
mainfrom
josh/phase-5.1-task-bridge-hardening
Open

jgluck96 wants to merge 1 commit into
mainfrom
josh/phase-5.1-task-bridge-hardening

Conversation

@jgluck96

@jgluck96 jgluck96 commented Jun 1, 2026

Copy link
Copy Markdown

Summary

Phase 5.1 of the OSS sync roadmap — a selective merge of task-bridge.ts security hardening that OSS sutando has but stando was missing. This is not a wholesale port: stando's deliberate divergences (state-paths.js layout, watch-tasks-stream.mjs watcher, cloud-client telemetry) are left untouched.

Branches off main (independent of the UI-extraction PRs #68/#69).

What changed (3 hardening items)

  1. Field ordering (PR fix(agent-api): close task-file field injection via /task body sonichi/sutando#982) — all task writers (writeChatTask, the voice workTool writer, context-drop) now emit task: last and add a priority: field (normal for chat/context-drop, urgent for voice). A multi-line user/Gemini body can no longer forge header fields below it.
    • 🔑 This aligns task-bridge.ts with stando's own agent-api.py:834, which already orders task: last. task-bridge.ts was the lone inconsistent writer, and task_priority.py already exists to consume the priority: field.
  2. _isVoiceTask stops at the first task: line — the residual half of fix(agent-api): close task-file field injection via /task body sonichi/sutando#982. Without it, a body line channel_id: local-voice would forge a voice-task classification (affects the offline DM-forwarding gate). Added a FORGED_VOICE_BODY regression test; updated existing fixtures to the new task:-last format.
  3. _shouldFallthrough allowlist guard (issue task-bridge: belt-suspenders guard for non-task .txt files (narrow-scope-followup from #1033) sonichi/sutando#1035) — the result-watcher's unconditional onResult() fallthrough is now gated to filenames task-bridge legitimately delivers (task-/voice-/proactive-), so a future per-channel pull namespace won't be injected into voice.

Deliberately NOT included

Test plan

  • tests/task-bridge-isvoice.test.ts — 8/8 incl. new forge-guard regression
  • tests/task-bridge-format.test.ts — 5/5 (field matches are line-anchored, order-independent)
  • Full TS suite: 241/241 pass
  • tsc --noEmit clean

Related

Made with Cursor

…fallthrough guard

Selective merge of OSS task-bridge.ts security hardening that stando was
missing. Does NOT touch stando's deliberate divergences (state-paths.js
layout, watch-tasks-stream.mjs watcher, cloud-client telemetry).

1. Field ordering (PR sonichi#982): all task writers (writeChatTask, the voice
   workTool writer, context-drop) now emit `task:` LAST and include a
   `priority:` field (normal for chat/context-drop, urgent for voice).
   This matches what stando's own agent-api.py already does (it ordered
   `task:` last at agent-api.py:834) — task-bridge.ts was the lone
   inconsistent writer. A multi-line body can no longer forge header
   fields below it.

2. _isVoiceTask stops scanning headers at the first `task:` line — the
   residual half of sonichi#982. Without it, a body line `channel_id:
   local-voice` would forge a voice-task classification. Added a
   regression test (FORGED_VOICE_BODY) proving the guard; updated the
   existing fixtures to the new `task:`-last format.

3. _shouldFallthrough allowlist guard (issue sonichi#1035): the result-watcher's
   unconditional onResult() fallthrough is now gated to filenames
   task-bridge legitimately delivers (task-/voice-/proactive-), so a
   future per-channel pull namespace won't be injected into voice.

Verified: full TS suite green (241/241), incl. task-bridge-format and
task-bridge-isvoice; tsc clean.

Co-authored-by: Cursor <cursoragent@cursor.com>

@bassilkhilo-ag2 bassilkhilo-ag2 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Josh — one voice-delivery regression to flag before merge.

PR #70's _shouldFallthrough allowlist is:

return file.startsWith('task-') || file.startsWith('voice-') || file.startsWith('proactive-');

This misses four result prefixes that are explicitly written to be spoken aloud when the voice client is connected:

  • question- — written by src/check-pending-questions.py:115
  • insight- — written by src/daily-insight.py
  • friction- — written by src/friction-detector.py
  • briefing- — written by skills/morning-briefing/SKILL.md:49

The discord bridge already codifies these in FALLBACK_PREFIXES at discord-bridge.py:3145:

FALLBACK_PREFIXES = ("task-", "question-", "briefing-", "insight-", "friction-")

As written, this PR would silently stop voice from speaking pending-question pings, daily insights, friction reports, and morning briefings. Suggested one-liner:

export function _shouldFallthrough(file: string): boolean {
  return ['task-','voice-','proactive-','question-','briefing-','insight-','friction-']
    .some(p => file.startsWith(p));
}

PR #94 (unit tests for this guard) also only covers the existing three prefixes — would be good to extend those tests to cover the four new ones too.

Happy to prepare a follow-up commit if helpful.

@bassilkhilo-ag2

Copy link
Copy Markdown

Supporting Bassil's review — here's the exact change needed for _shouldFallthrough:

export function _shouldFallthrough(file: string): boolean {
    return (
        file.startsWith('task-') ||
        file.startsWith('voice-') ||
        file.startsWith('proactive-') ||
        file.startsWith('question-') ||   // check-pending-questions.py:132
        file.startsWith('insight-') ||    // daily-insight.py
        file.startsWith('friction-') ||   // friction-detector.py
        file.startsWith('briefing-')      // morning-briefing skill
    );
}

Source verification:

  • question-: src/check-pending-questions.py:132 → path = RESULTS_DIR / f"question-{ts}.txt"
  • insight-: src/daily-insight.py docstring → "Output: results/insight-{date}.txt (voice agent can speak it)"
  • friction-: src/friction-detector.py → "Output: results/friction-{date}.txt"
  • briefing-: skills/morning-briefing/SKILL.md:49 → "Write to results/briefing-{date}.txt so the voice agent can speak it"

Also note: tests/task-bridge-fallthrough-guard.test.ts line 59 currently asserts question- → false (in the "rejects unknown" it() block) — that will need to move to an "allows voice-delivery result types" block alongside the other three new prefixes.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants