chore(workspace): drop stale root plans

This commit is contained in:
YeonGyu-Kim
2026-05-18 16:01:14 +09:00
parent 7187db6ede
commit cf901d4b8e
4 changed files with 1 additions and 1097 deletions
+1
View File
@@ -48,3 +48,4 @@ oauth-success.html
# Debugging / session artifacts (skill workspace residue)
.debug-journal*.md
session-ses_*.md
plans/
@@ -1,405 +0,0 @@
# Fix Delegate-Task Subagent Permissions
## TL;DR
> Summary: Fix OMO `task` so delegated subagents can use repo exploration tools in their child sessions without permission prompts or denials. Match OpenCode native Task semantics by deriving child-session permissions and prompt tools from the delegated agent instead of only denying `question`.
> Deliverables:
> - Shared subagent permission/tool builder with unit coverage
> - Sync delegate-task child-session permission fix
> - Background and unstable delegate-task permission fix
> - Regression coverage for `read`/`bash`/search access and write/tool delegation denial
> - Module QA plus real tmux/non-interactive QA evidence
> Effort: Medium
> Risk: Medium — permission rules affect delegated child-session execution and can accidentally over-allow tools if not scoped.
## Scope
### Must have
- Subagents launched through OMO `task` can call repo exploration tools such as `read`, `bash`, `grep`, and `glob` in child sessions.
- Sync child sessions are created with `parentID`, OpenCode-compatible title, model fields, directory route, and a derived permission ruleset.
- Background child sessions receive the same derived permission ruleset through `LaunchInput.sessionPermission`.
- Prompt bodies for sync, background launch, background resume, and fallback-agent retry use the same derived prompt tool map so `session.prompt` does not overwrite useful child-session permissions with deny-only rules.
- `question` remains denied for delegated child sessions.
- Read-only subagents still cannot write or recursively delegate: `write`, `edit`, `apply_patch`, `task`, and `call_omo_agent` remain denied where the delegated agent restrictions deny them.
- Existing task metadata contract remains intact: `sessionId` is published and visible task metadata still includes `session_id`.
- Regression tests prove both sync and background paths include explicit `allow` rules for exploration tools and explicit `deny` rules for restricted tools.
- Real tmux/manual QA proves an `explore` subagent launched by `task` can inspect files and run a harmless shell command without `"Permission required"` output.
### Must NOT have (guardrails, anti-slop, scope boundaries)
- Do not disable OpenCode permission checks globally.
- Do not add `permission: "*", action: "allow"` or any broad wildcard allow.
- Do not grant write/edit/apply_patch to `explore`, `librarian`, or `oracle`.
- Do not refactor background polling, concurrency, wake gating, model fallback, or tmux layout.
- Do not change agent prompts, category model selection, metadata formatting, or task output text except where tests require permission metadata.
- Do not remove `getAgentToolRestrictions`; centralize the new derived session permission behavior around it or a closely related shared helper.
- Do not change the native OpenCode source under `../opencode`.
## Verification strategy
> Zero human intervention — all verification is agent-executed.
- Test decision: TDD + Bun test
- QA policy: every task has agent-executed scenarios
- Evidence: `evidence/task-<N>-<slug>.<ext>`
## Execution strategy
### Parallel execution waves
> Target 5-8 tasks per wave. <3 per wave (except final) = under-splitting.
> Extract shared dependencies as Wave-1 tasks to maximize parallelism.
Wave 1 (no dependencies):
- Task 1: Add shared subagent permission/tool builder and focused unit tests
- Task 2: Add native-reference contract tests around existing permission fixtures
Wave 2 (after Wave 1):
- Task 3: depends [1, 2] - Wire sync delegate-task session creation and prompt body
- Task 4: depends [1, 2] - Wire background and unstable delegate-task launch path
- Task 5: depends [1] - Wire background resume and fallback-agent prompt bodies
Wave 3 (after Wave 2):
- Task 6: depends [3, 4, 5] - Run module QA and lock regression output
- Task 7: depends [3, 4, 5] - Run real tmux/manual QA for delegated exploration
Critical path: Task 1 -> Task 3 -> Task 6
### Dependency matrix
| Task | Depends on | Blocks | Can parallelize with |
|------|------------|--------|----------------------|
| 1 | none | 3, 4, 5, 6, 7 | 2 |
| 2 | none | 3, 4, 6, 7 | 1 |
| 3 | 1, 2 | 6, 7 | 4, 5 |
| 4 | 1, 2 | 6, 7 | 3, 5 |
| 5 | 1 | 6, 7 | 3, 4 |
| 6 | 3, 4, 5 | none | 7 |
| 7 | 3, 4, 5 | none | 6 |
## Todos
> Implementation + Test = ONE task. Never separate.
> Every task MUST have: References + Acceptance Criteria + QA Scenarios + Commit.
- [ ] 1. Add shared subagent permission/tool builder
What to do: Create one focused helper, preferably `src/shared/subagent-session-permission.ts`, plus `src/shared/subagent-session-permission.test.ts`. The helper must produce both:
- `buildSubagentSessionPermission(agentName, options): SessionPermissionRule[]`
- `buildSubagentPromptTools(agentName, options): Record<string, boolean>`
Required behavior:
- Convert OMO permission-map values (`"allow" | "deny" | "ask"`) into OpenCode session rules (`{ permission, action, pattern: "*" }`).
- Always include `{ permission: "question", action: "deny", pattern: "*" }`.
- Add explicit `allow` rules for repo exploration tools when not denied by the agent: `read`, `bash`, `grep`, `glob`, `lsp_symbols`, `lsp_goto_definition`, `lsp_find_references`, `lsp_diagnostics`, `ast_grep_search`.
- Apply agent/tool deny rules last so read-only restrictions win over defaults.
- Preserve team-tool denylist behavior when `includeTeamToolDenylist` is true.
- Do not add wildcard allow rules.
Must NOT do: Do not change any call sites yet. Do not change agent definitions. Do not add a generic `utils.ts` or `helpers.ts`.
Parallelization: Can parallel: YES | Wave 1 | Blocks: [3, 4, 5, 6, 7] | Blocked by: []
References (executor has NO interview context — be exhaustive):
- Pattern: `src/shared/agent-tool-restrictions.ts:24` — current read-only denylist omits explicit `read`/`bash` allows, which leaves child sessions in ask/permission-required state.
- Pattern: `src/shared/agent-tool-restrictions.ts:66` — current `getAgentToolRestrictions()` is the source of prompt-body deny rules and team-tool deny rules.
- Pattern: `src/shared/question-denied-session-permission.ts:1` — current ruleset shape and `QUESTION_DENIED_SESSION_PERMISSION`.
- Pattern: `src/shared/permission-compat.ts:6` — OMO agent permission map value type.
- Pattern: `src/agents/explore.ts:27``explore` denies write/edit/apply_patch/task/call_omo_agent and explicitly allows some LSP/AST tools.
- Pattern: `src/agents/librarian.ts:24``librarian` denies write/edit/apply_patch/task/call_omo_agent.
- External: `../opencode/packages/opencode/src/agent/subagent-permissions.ts:17` — native Task derives child-session permissions from parent and subagent rules.
- External: `../opencode/packages/opencode/src/permission/evaluate.ts:9` — missing rule defaults to `ask`, which is the source of permission prompts/denials.
- Test: `src/agents/tool-restrictions.test.ts` — existing assertions around agent permission maps.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/shared/subagent-session-permission.test.ts --bail` passes.
- [ ] Test asserts `buildSubagentSessionPermission("explore")` contains `allow` for `read`, `bash`, `grep`, and `glob`.
- [ ] Test asserts `buildSubagentSessionPermission("explore")` contains `deny` for `write`, `edit`, `apply_patch`, `task`, `call_omo_agent`, and `question`.
- [ ] Test asserts no generated rule is `{ permission: "*", action: "allow", pattern: "*" }`.
- [ ] Test asserts prompt tools mirror the permission intent: exploration tools `true`, restricted tools `false`.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: explore permission builder allows read-only repo exploration
Tool: bash
Steps: mkdir -p evidence && bun test src/shared/subagent-session-permission.test.ts --bail > evidence/task-1-permission-builder.txt
Expected: Exit 0, and test names mention read/bash exploration allow plus write/edit denial.
Evidence: evidence/task-1-permission-builder.txt
Scenario: builder rejects overbroad wildcard allow
Tool: bash
Steps: rg -n 'permission: "\\*", action: "allow"|\\["\\*", true\\]' src/shared/subagent-session-permission.ts src/shared/subagent-session-permission.test.ts > evidence/task-1-wildcard-scan.txt; test ! -s evidence/task-1-wildcard-scan.txt
Expected: Exit 0 and evidence file is empty.
Evidence: evidence/task-1-wildcard-scan.txt
```
Commit: YES | Message: `fix(task): derive subagent exploration permissions` | Files: [`src/shared/subagent-session-permission.ts`, `src/shared/subagent-session-permission.test.ts`]
- [ ] 2. Add native-reference contract tests around current fixtures
What to do: Update current permission tests so they describe the native-compatible contract before wiring code:
- `src/tools/delegate-task/sync-session-creator.test.ts`
- `src/tools/delegate-task/background-task.test.ts`
- `src/features/background-agent/manager-session-permission.test.ts`
The tests must assert that child session creation receives a permission ruleset with explicit exploration allows and restricted-tool denies. Keep existing title, `parentID`, and directory assertions.
Must NOT do: Do not make broad behavior changes in this task except the minimal helper import needed if Task 1 already exists. Do not delete the old `question` denial assertion; update it into the larger ruleset.
Parallelization: Can parallel: YES | Wave 1 | Blocks: [3, 4, 6, 7] | Blocked by: []
References (executor has NO interview context — be exhaustive):
- Pattern: `src/tools/delegate-task/sync-session-creator.test.ts:5` — current sync child-session test only expects `question` deny.
- Pattern: `src/tools/delegate-task/background-task.test.ts:209` — current delegate background launch test only expects `question` deny in `sessionPermission`.
- Pattern: `src/features/background-agent/manager-session-permission.test.ts:84` — manager-level test asserts explicit session permission rules are passed into `session.create`.
- External: `../opencode/packages/opencode/src/tool/task.ts:152` — native Task creates child session with `parentID`, title, and derived permission.
- External: `../opencode/packages/opencode/src/tool/task.ts:178` — native Task metadata includes child `sessionId`.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/background-task.test.ts src/features/background-agent/manager-session-permission.test.ts --bail` initially fails before Tasks 3 and 4 if run after only test edits.
- [ ] Tests assert `parentID` and title stay unchanged.
- [ ] Tests assert permission arrays contain `allow` for `read` and `bash`.
- [ ] Tests assert permission arrays contain `deny` for `question`, `write`, `edit`, `apply_patch`, and `task`.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: contract tests capture the regression
Tool: bash
Steps: mkdir -p evidence && bun test src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/background-task.test.ts src/features/background-agent/manager-session-permission.test.ts --bail > evidence/task-2-contract-tests.txt || true
Expected: Evidence shows the new expectations before implementation, or passes if Task 3/4 are already applied by parallel execution.
Evidence: evidence/task-2-contract-tests.txt
Scenario: existing metadata contract remains present
Tool: bash
Steps: rg -n 'session_id|sessionId|parentID|@\\$\\{.*subagent|@explore subagent' src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/background-task.test.ts src/features/background-agent/manager-session-permission.test.ts > evidence/task-2-metadata-contract.txt
Expected: Evidence includes session lineage/title assertions, not only permission assertions.
Evidence: evidence/task-2-metadata-contract.txt
```
Commit: YES | Message: `test(task): pin subagent session permission contract` | Files: [`src/tools/delegate-task/sync-session-creator.test.ts`, `src/tools/delegate-task/background-task.test.ts`, `src/features/background-agent/manager-session-permission.test.ts`]
- [ ] 3. Wire sync delegate-task session creation and prompt body
What to do:
- Update `createSyncSession()` to accept an optional `sessionPermission` argument and use it instead of hardcoded `QUESTION_DENIED_SESSION_PERMISSION`.
- Update `executeSyncTask()` to pass `buildSubagentSessionPermission(agentToUse, ...)` on initial child session creation and retry child session creation.
- Update `sendSyncPrompt()` to use `buildSubagentPromptTools(agentToUse, { allowTask })` rather than assembling a deny-only map inline.
- Keep `setSessionTools()` and `applySessionPromptParams()` behavior.
- Keep `routePromptRetry()` and `routePromptSyncRetry()` behavior unchanged.
Must NOT do: Do not alter polling, fetch result, fallback selection, metadata formatting, or `promptAsync` gate behavior.
Parallelization: Can parallel: YES | Wave 2 | Blocks: [6, 7] | Blocked by: [1, 2]
References (executor has NO interview context — be exhaustive):
- Pattern: `src/tools/delegate-task/sync-session-creator.ts:20` — current sync child session creation hardcodes `QUESTION_DENIED_SESSION_PERMISSION`.
- Pattern: `src/tools/delegate-task/sync-task.ts:88` — initial sync child session is created here.
- Pattern: `src/tools/delegate-task/sync-task.ts:264` — fallback retry creates another sync child session and must receive the same derived permission rules.
- Pattern: `src/tools/delegate-task/sync-prompt-sender.ts:70` — prompt body currently builds tools from `getAgentToolRestrictions()` only.
- Pattern: `src/tools/delegate-task/sync-prompt-route.test.ts:16` — existing route tests must keep passing.
- External: `../opencode/packages/opencode/src/tool/task.ts:194` — native Task prompts `nextSession.id`, not parent session.
- External: `../opencode/packages/opencode/src/session/prompt.ts:1622` — prompt `tools` are converted into session permission rules, so prompt body must not erase exploration allows.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/sync-prompt-sender.test.ts src/tools/delegate-task/sync-prompt-route.test.ts src/tools/delegate-task/sync-task.test.ts --bail` passes.
- [ ] `createSyncSession()` tests prove sync child session body includes `allow` rules for `read` and `bash`.
- [ ] `sendSyncPrompt()` tests prove prompt body tools include `read: true`, `bash: true`, `grep: true`, `glob: true`, `question: false`, and `write/edit/apply_patch/task` false for `explore`.
- [ ] Sync route tests still prove prompts are routed to the child session directory.
- [ ] Retry path tests prove fallback child sessions also receive derived permission rules.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: sync task creates an exploration-capable child session
Tool: bash
Steps: mkdir -p evidence && bun test src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/sync-prompt-sender.test.ts src/tools/delegate-task/sync-prompt-route.test.ts src/tools/delegate-task/sync-task.test.ts --bail > evidence/task-3-sync-permissions.txt
Expected: Exit 0; evidence contains passing sync permission and prompt-body tests.
Evidence: evidence/task-3-sync-permissions.txt
Scenario: sync prompt still denies recursive delegation and writes
Tool: bash
Steps: bun test src/tools/delegate-task/sync-prompt-sender.test.ts --bail > evidence/task-3-sync-denies.txt
Expected: Exit 0; tests assert `task`, `call_omo_agent`, `write`, `edit`, and `apply_patch` are false for read-only subagents.
Evidence: evidence/task-3-sync-denies.txt
```
Commit: YES | Message: `fix(task): pass derived permissions to sync subagents` | Files: [`src/tools/delegate-task/sync-session-creator.ts`, `src/tools/delegate-task/sync-task.ts`, `src/tools/delegate-task/sync-prompt-sender.ts`, related tests]
- [ ] 4. Wire background and unstable delegate-task launch path
What to do:
- Update `executeBackgroundTask()` to pass `buildSubagentSessionPermission(normalizedAgent, ...)` into `manager.launch()`.
- Update `executeUnstableAgentTask()` to pass the same derived permission builder into `manager.launch()`.
- Update `BackgroundManager`/`startTask()` permission tests to expect derived session permissions.
- Keep `LaunchInput.sessionPermission` optional for other callers.
- Keep background metadata and `buildTaskMetadataBlock()` output unchanged.
Must NOT do: Do not change background task concurrency, polling, idle detection, parent wake notification, or cancellation behavior.
Parallelization: Can parallel: YES | Wave 2 | Blocks: [6, 7] | Blocked by: [1, 2]
References (executor has NO interview context — be exhaustive):
- Pattern: `src/tools/delegate-task/background-task.ts:116` — background delegate-task launch currently passes only `QUESTION_DENIED_SESSION_PERMISSION`.
- Pattern: `src/tools/delegate-task/unstable-agent-task.ts:32` — unstable-agent path also passes only `QUESTION_DENIED_SESSION_PERMISSION`.
- Pattern: `src/features/background-agent/types.ts:102` — `LaunchInput.sessionPermission` type is already the correct extension point.
- Pattern: `src/features/background-agent/spawner.ts:100` — `startTask()` passes `input.sessionPermission` into `client.session.create`.
- Test: `src/tools/delegate-task/background-task.test.ts:209` — update this existing background permission assertion.
- Test: `src/tools/delegate-task/unstable-agent-permission.test.ts` — update unstable path assertion.
- Test: `src/features/background-agent/manager-session-permission.test.ts:84` — manager-level permission passthrough coverage.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/tools/delegate-task/background-task.test.ts src/tools/delegate-task/unstable-agent-permission.test.ts src/features/background-agent/manager-session-permission.test.ts --bail` passes.
- [ ] Background launch tests assert `manager.launch().sessionPermission` includes `read`/`bash` allows for `explore`.
- [ ] Background launch tests assert write/edit/apply_patch/task/call_omo_agent/question denies remain.
- [ ] Manager passthrough test proves `client.session.create().body.permission` receives the derived ruleset exactly.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: background launch passes derived permissions through manager
Tool: bash
Steps: mkdir -p evidence && bun test src/tools/delegate-task/background-task.test.ts src/tools/delegate-task/unstable-agent-permission.test.ts src/features/background-agent/manager-session-permission.test.ts --bail > evidence/task-4-background-permissions.txt
Expected: Exit 0; evidence shows background and unstable permission tests pass.
Evidence: evidence/task-4-background-permissions.txt
Scenario: background metadata remains OpenCode-compatible
Tool: bash
Steps: bun test src/tools/delegate-task/background-task.test.ts --bail > evidence/task-4-background-metadata.txt
Expected: Exit 0; existing metadata tests still pass and visible output contains `session_id`.
Evidence: evidence/task-4-background-metadata.txt
```
Commit: YES | Message: `fix(task): pass derived permissions to background subagents` | Files: [`src/tools/delegate-task/background-task.ts`, `src/tools/delegate-task/unstable-agent-task.ts`, `src/features/background-agent/manager-session-permission.test.ts`, related tests]
- [ ] 5. Wire background resume and fallback prompt bodies
What to do:
- Update `src/features/background-agent/spawner.ts` initial prompt body, resume prompt body, and fallback-agent prompt body to use `buildSubagentPromptTools()`.
- Keep `includeTeamToolDenylist: input.teamRunId === undefined` behavior.
- Keep fallback agent behavior, `releasePromptAsyncReservation()`, and `task.agent = FALLBACK_AGENT` unchanged.
- Add tests that `promptAsync` body for launch and resume includes `read: true`, `bash: true`, and restricted-tool denies for `explore`.
Must NOT do: Do not change session creation, concurrency release, tmux callback timing, or fallback-agent selection.
Parallelization: Can parallel: YES | Wave 2 | Blocks: [6, 7] | Blocked by: [1]
References (executor has NO interview context — be exhaustive):
- Pattern: `src/features/background-agent/spawner.ts:158` — initial background prompt body currently builds tool map inline.
- Pattern: `src/features/background-agent/spawner.ts:29` — fallback prompt body currently builds a second inline tool map.
- Pattern: `src/features/background-agent/spawner.ts:299` — resume prompt body currently builds another inline tool map.
- Test: `src/features/background-agent/manager-session-permission.test.ts:9` — already captures prompt route and can be extended or paired with a new focused test.
- External: `../opencode/packages/opencode/src/session/prompt.ts:1622` — prompt body tools become session permission rules.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/features/background-agent/manager-session-permission.test.ts src/features/background-agent/manager.test.ts src/features/background-agent/spawner.test.ts --bail` passes, or if `manager.test.ts` is too broad/slow, record the narrower replacement command in evidence.
- [ ] A launch prompt test asserts exploration tools are explicitly true and restricted tools false.
- [ ] A resume prompt test asserts the same tool map contract.
- [ ] Existing fallback-agent retry tests still pass.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: background launch prompt keeps exploration tools enabled
Tool: bash
Steps: mkdir -p evidence && bun test src/features/background-agent/manager-session-permission.test.ts --bail > evidence/task-5-background-prompt-tools.txt
Expected: Exit 0; evidence includes prompt body assertions for read/bash and restricted-tool denies.
Evidence: evidence/task-5-background-prompt-tools.txt
Scenario: no prompt gate or fallback routing regression
Tool: bash
Steps: bun test src/features/background-agent/manager.test.ts src/features/background-agent/spawner.test.ts --bail > evidence/task-5-background-manager.txt
Expected: Exit 0, or if pre-existing unrelated failures occur, evidence includes exact failing test names and a narrower passing command that covers spawner prompt behavior.
Evidence: evidence/task-5-background-manager.txt
```
Commit: YES | Message: `fix(background-agent): keep exploration tools enabled in subagent prompts` | Files: [`src/features/background-agent/spawner.ts`, `src/features/background-agent/manager-session-permission.test.ts`, related tests]
- [ ] 6. Run module QA and lock regression evidence
What to do:
- Run focused delegate-task/background-agent tests.
- Run typecheck.
- Run the full root test suite if focused tests and typecheck pass.
- Capture evidence files and summarize failures only if unrelated/pre-existing.
Must NOT do: Do not fix unrelated failures. Do not weaken tests to pass. Do not skip typecheck.
Parallelization: Can parallel: YES | Wave 3 | Blocks: [] | Blocked by: [3, 4, 5]
References (executor has NO interview context — be exhaustive):
- Command: `bun test src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/sync-prompt-sender.test.ts src/tools/delegate-task/sync-prompt-route.test.ts src/tools/delegate-task/sync-task.test.ts src/tools/delegate-task/background-task.test.ts src/tools/delegate-task/unstable-agent-permission.test.ts src/features/background-agent/manager-session-permission.test.ts --bail`
- Command: `bun run typecheck`
- Command: `bun test`
- Pattern: `package.json` — scripts use Bun only; no npm/yarn/pnpm.
Acceptance criteria (agent-executable only):
- [ ] Focused delegate-task/background-agent test command exits 0.
- [ ] `bun run typecheck` exits 0.
- [ ] `bun test` exits 0, or evidence documents exact unrelated pre-existing failures with focused command still green.
- [ ] No `as any`, `@ts-ignore`, or `@ts-expect-error` introduced.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: focused module regression suite
Tool: bash
Steps: mkdir -p evidence && bun test src/tools/delegate-task/sync-session-creator.test.ts src/tools/delegate-task/sync-prompt-sender.test.ts src/tools/delegate-task/sync-prompt-route.test.ts src/tools/delegate-task/sync-task.test.ts src/tools/delegate-task/background-task.test.ts src/tools/delegate-task/unstable-agent-permission.test.ts src/features/background-agent/manager-session-permission.test.ts --bail > evidence/task-6-focused-tests.txt
Expected: Exit 0.
Evidence: evidence/task-6-focused-tests.txt
Scenario: typecheck and anti-suppression scan
Tool: bash
Steps: bun run typecheck > evidence/task-6-typecheck.txt && rg -n 'as any|@ts-ignore|@ts-expect-error' src/shared/subagent-session-permission.ts src/tools/delegate-task src/features/background-agent > evidence/task-6-suppression-scan.txt || true
Expected: Typecheck exits 0; suppression scan contains no new suppressions in changed files.
Evidence: evidence/task-6-typecheck.txt
```
Commit: NO | Message: `test(task): verify delegate-task permission regression` | Files: [`evidence/task-6-focused-tests.txt`, `evidence/task-6-typecheck.txt`, `evidence/task-6-suppression-scan.txt`]
- [ ] 7. Run real tmux/manual QA for delegated exploration
What to do:
- Build the local plugin.
- Run a real OMO non-interactive session inside tmux from this repo that forces `task(subagent_type="explore", run_in_background=false or true)` to inspect a known file and run a harmless shell command.
- Confirm output contains the expected file fact and does not contain `"Permission required"`, `"missing permission"`, or `"permission denied"` for `read`/`bash`.
- Repeat with background mode and collect `background_output` after system completion.
- Capture tmux pane output and `/tmp/oh-my-opencode.log` excerpts.
Must NOT do: Do not run destructive shell commands. Do not use `sleep`; use tmux capture/polling loops with bounded attempts.
Parallelization: Can parallel: YES | Wave 3 | Blocks: [] | Blocked by: [3, 4, 5]
References (executor has NO interview context — be exhaustive):
- Command: `bun run build` — local plugin build.
- Command: `bun src/cli/index.ts run --agent Sisyphus --directory /Users/yeongyu/local-workspaces/omo --json "<message>"`
- Pattern: `src/cli/cli-program.ts:74` — local CLI supports `run <message>`.
- Pattern: `src/cli/run/AGENTS.md` — run command waits for todos/background tasks.
- Pattern: `src/tools/delegate-task/tools.ts:21` — `run_in_background` controls sync/background delegation.
- Pattern: `/tmp/oh-my-opencode.log` — project logger destination.
Acceptance criteria (agent-executable only):
- [ ] `bun run build` exits 0.
- [ ] tmux sync QA output includes an `explore` result referencing `src/tools/delegate-task/sync-session-creator.ts`.
- [ ] tmux sync QA output has no case-insensitive match for `Permission required|missing permission|permission denied`.
- [ ] tmux background QA output includes a background task ID and collected result.
- [ ] `/tmp/oh-my-opencode.log` has no child-session permission rejection for the QA session.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: sync delegated explore can read and run harmless bash
Tool: tmux
Steps: mkdir -p evidence && bun run build > evidence/task-7-build.txt && tmux new-session -d -s omo-delegate-sync-qa 'cd /Users/yeongyu/local-workspaces/omo && bun src/cli/index.ts run --agent Sisyphus --directory /Users/yeongyu/local-workspaces/omo --json "Use task with subagent_type=explore, run_in_background=false, load_skills=[] to inspect src/tools/delegate-task/sync-session-creator.ts and run pwd. Report the permission field behavior and the cwd."' ; poll `tmux capture-pane -pt omo-delegate-sync-qa` until the command exits or the pane shows JSON; save final capture.
Expected: Capture includes a useful exploration result and no permission-required text.
Evidence: evidence/task-7-sync-tmux.txt
Scenario: background delegated explore can read and run harmless bash
Tool: tmux
Steps: tmux new-session -d -s omo-delegate-bg-qa 'cd /Users/yeongyu/local-workspaces/omo && bun src/cli/index.ts run --agent Sisyphus --directory /Users/yeongyu/local-workspaces/omo --json "Launch task subagent_type=explore with run_in_background=true and load_skills=[] to inspect src/shared/agent-tool-restrictions.ts. Wait for completion notification, collect background_output, and report whether read/bash were usable."' ; poll `tmux capture-pane -pt omo-delegate-bg-qa` until JSON or completion; save final capture and relevant log excerpt.
Expected: Capture includes background result and no permission-required text.
Evidence: evidence/task-7-background-tmux.txt
```
Commit: NO | Message: `test(task): capture real delegate-task permission QA` | Files: [`evidence/task-7-build.txt`, `evidence/task-7-sync-tmux.txt`, `evidence/task-7-background-tmux.txt`]
## Final verification wave (MANDATORY — after all implementation tasks)
> Runs in PARALLEL. ALL must APPROVE. Surface results to the caller and wait for an explicit "okay" before declaring complete.
- [ ] F1. Plan compliance audit — every task done, every acceptance criterion met
- [ ] F2. Code quality review — diagnostics clean, idioms match, no dead code
- [ ] F3. Real manual QA — every QA scenario executed with evidence captured
- [ ] F4. Scope fidelity — nothing extra shipped beyond Must-Have, nothing Must-NOT-Have introduced
## Commit strategy
- One logical change per commit. Conventional Commits (`<type>(<scope>): <subject>` body + footer).
- Atomic: every commit builds and passes tests on its own.
- No "WIP" / "fix typo squash later" commits on the final branch — clean up before merge.
- Reference the plan file path in the final commit footer: `Plan: plans/fix-delegate-task-subagent-permissions.md`.
## Success criteria
- All Must-Have shipped; all QA scenarios pass with captured evidence; F1F4 approved; commit history clean.
-632
View File
@@ -1,632 +0,0 @@
# Fix Prompt Hang Race
## TL;DR
> Summary: Fix OMO's internal prompt gate so failed or timed-out prompt dispatches do not leave a stale reservation that blocks sibling recovery prompts after OpenCode `promptAsync` returns before durable prompt completion. Keep behavior unchanged except the bug fix, prove it with failing-first Bun tests, actual tmux-backed QA, CI, review-work, and Cubic before merge.
> Deliverables:
> - Shared prompt gate regression tests and minimal gate cleanup fix
> - Route-level regressions for model fallback, runtime fallback, background wakes, and team live delivery
> - Updated `.debugging` journal with root-cause evidence and cleanup ledger
> - PR against `dev`, green CI, review-work pass, Cubic pass, merged branch, removed worktree
> Effort: Medium
> Risk: High — async prompt acceptance is fire-and-forget upstream, so duplicate suppression and recovery retry timing are easy to regress.
## Scope
### Must have
- Work only in `/Users/yeongyu/local-workspaces/gpt 5.5 xhigh` on branch `code-yeongyu/fix-prompt-hang-race`.
- Preserve the sibling OpenCode repo at `/Users/yeongyu/local-workspaces/opencode`; read and run it only as evidence unless an explicit later request changes scope.
- Keep `.debugging` current with hypotheses, red/green evidence, manual QA evidence, artifacts, and cleanup status.
- Add failing-first tests before the fix for the stale prompt reservation behavior.
- Fix the smallest mechanism that makes failed or timed-out prompt dispatches release their reservation promptly while successful dispatches still dedupe immediate duplicate prompts.
- Cover main-session internal prompt routes: model fallback, runtime fallback, session recovery, background-agent parent wakes, and team-mode live delivery/wake hints.
- Run actual manual QA through local commands and tmux sessions owned by this task.
- Commit atomically, create a PR, iterate until CI, review-work, and Cubic are all passing, merge, then remove the worktree.
### Must NOT have (guardrails, anti-slop, scope boundaries)
- Do not modify sibling OpenCode product code.
- Do not kill the tmux server; only kill tmux sessions created by this task.
- Do not add compatibility layers, config switches, broad retry frameworks, or unrelated refactors.
- Do not bypass `src/shared/prompt-async-gate.ts` for any production internal prompt route.
- Do not delete failing tests, suppress type errors, use `as any`, `@ts-ignore`, or `@ts-expect-error`.
- Do not use `git reset --hard`, `git checkout --`, `rm -rf`, `--no-verify`, or direct `bun publish`.
- Do not treat a passing unit suite as enough; actual QA must exercise the local OpenCode/OMO prompt path.
## Verification strategy
> Zero human intervention — all verification is agent-executed.
- Test decision: TDD + Bun test (`bun:test`)
- QA policy: every task has agent-executed scenarios
- Evidence: `evidence/task-<N>-<slug>.<ext>`
## Execution strategy
### Parallel execution waves
> Target 58 tasks per wave. <3 per wave (except final) = under-splitting.
> Extract shared dependencies as Wave-1 tasks to maximize parallelism.
Wave 1 (no dependencies):
- Task 1: Confirm root cause and journal evidence
- Task 2: Fix shared prompt gate reservation semantics
- Task 3: Strengthen static prompt-route audit
Wave 2 (after Wave 1):
- Task 4: depends [2, 3]
- Task 5: depends [2, 3]
- Task 6: depends [2, 3]
- Task 7: depends [2, 3]
Wave 3 (after Wave 2):
- Task 8: depends [4, 5]
- Task 9: depends [4, 5, 6]
- Task 10: depends [6, 7]
- Task 11: depends [1, 2, 3, 4, 5, 6, 7]
Wave 4 (after Wave 3):
- Task 12: depends [8, 9, 10, 11]
Critical path: Task 2 → Task 4 → Task 8 → Task 11 → Task 12
### Dependency matrix
| Task | Depends on | Blocks | Can parallelize with |
|------|------------|--------|----------------------|
| 1 | none | 11 | 2, 3 |
| 2 | none | 4, 5, 6, 7, 11 | 1, 3 |
| 3 | none | 4, 5, 6, 7, 11 | 1, 2 |
| 4 | 2, 3 | 8, 9, 11 | 5, 6, 7 |
| 5 | 2, 3 | 8, 9, 11 | 4, 6, 7 |
| 6 | 2, 3 | 9, 10, 11 | 4, 5, 7 |
| 7 | 2, 3 | 10, 11 | 4, 5, 6 |
| 8 | 4, 5 | 12 | 9, 10, 11 |
| 9 | 4, 5, 6 | 12 | 8, 10, 11 |
| 10 | 6, 7 | 12 | 8, 9, 11 |
| 11 | 1, 2, 3, 4, 5, 6, 7 | 12 | 8, 9, 10 |
| 12 | 8, 9, 10, 11 | none | none |
## Todos
> Implementation + Test = ONE task. Never separate.
> Every task MUST have: References + Acceptance Criteria + QA Scenarios + Commit.
- [ ] 1. Confirm root cause and journal evidence
What to do: Update `.debugging` with the confirmed causal chain and the exact evidence already found: OpenCode `promptAsync` returns 204 after forking `SessionPrompt.prompt`, the fork later publishes `session.error` on failure, and OMO's gate can leave a short-lived reservation that overlaps recovery routes. Add the red/green/QA evidence ledger sections before creating more artifacts.
Must NOT do: Do not alter source code in this task. Do not remove existing `.debugging` entries.
Parallelization: Can parallel: YES | Wave 1 | Blocks: [11] | Blocked by: []
References (executor has NO interview context — be exhaustive):
- Pattern: `.debugging:58` — existing 2026-05-17 journal run starts here and must be preserved.
- Pattern: `.debugging:81` — existing hypothesis says OpenCode `promptAsync` can resolve before durable acceptance.
- Pattern: `.debugging:86` — existing artifact ledger includes the worktree and branch cleanup obligations.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:295``promptAsync` handler starts.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:300` — handler calls `promptSvc.prompt`.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:306` — fork failure publishes `Session.Event.Error`.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:312` — prompt effect is forked with `startImmediately: true`.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:314` — handler returns `NoContent`.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/session/prompt.ts:1092``createUserMessage` starts.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/session/prompt.ts:1098` — invalid agent creates `NamedError.Unknown`.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/session/prompt.ts:1099` — invalid agent publishes `Session.Event.Error`.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/session/prompt.ts:1614``SessionPrompt.prompt` returns a completed `MessageV2.WithParts` effect.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/session/prompt.ts:1631``noReply` returns after user message creation.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/sdk/js/src/gen/sdk.gen.ts:637` — SDK says `promptAsync` returns immediately.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/sdk/js/src/gen/types.gen.ts:2723``SessionPromptAsyncResponses` type starts.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/sdk/js/src/gen/types.gen.ts:2727``promptAsync` success is `204: void`.
- External: `https://github.com/anomalyco/opencode/issues/11616` — public docs issue describes `/prompt_async` as returning immediately and lists `session.error`.
- External: `https://github.com/anomalyco/opencode/issues/12860` — public issue reports `/prompt_async` status can stay unknown after submission.
Acceptance criteria (agent-executable only):
- [ ] `rg -n "Root cause|promptAsync|204|session.error|NoContent|fork" .debugging` prints the updated root-cause section.
- [ ] `rg -n "Artifacts To Revert|worktree|code-yeongyu/fix-prompt-hang-race|tmux" .debugging` confirms the cleanup ledger mentions the branch, worktree, and tmux constraints.
- [ ] `git diff -- .debugging > evidence/task-1-journal.diff` captures only journal changes for this task.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: root-cause journal is complete
Tool: bash
Steps: mkdir -p evidence; rg -n "Root cause|promptAsync|204|session.error|forkIn|NoContent" .debugging | tee evidence/task-1-journal.txt
Expected: output contains .debugging lines for OpenCode 204, forked prompt completion, later session.error, and OMO reservation overlap.
Evidence: evidence/task-1-journal.txt
Scenario: cleanup ledger is preserved
Tool: bash
Steps: rg -n "remove after merged PR|Never kill tmux server|worktree|branch" .debugging | tee evidence/task-1-cleanup-ledger.txt
Expected: output contains cleanup instructions for the PR branch/worktree and tmux server guardrail.
Evidence: evidence/task-1-cleanup-ledger.txt
```
Commit: YES | Message: `test(debugging): document prompt async race evidence` | Files: [`.debugging`]
- [ ] 2. Fix shared prompt gate reservation semantics
What to do: Add failing-first tests in the existing gate test file proving that timed-out and rejected dispatches release their reservation even when `postDispatchHoldMs` is the default, while successful dispatches keep the short post-dispatch hold. Then minimally change `src/shared/prompt-async-gate.ts` so post-dispatch hold is applied only after a real `dispatched` result, not merely after `dispatchAttempted = true`. If the executor confirms that OpenCode's 204 is still too early for recovery routes, keep the gate change minimal and leave route-triggered release to Tasks 4 and 5.
Must NOT do: Do not remove the successful-dispatch hold. Do not add a queue, debounce framework, global lock, or new config option.
Parallelization: Can parallel: YES | Wave 1 | Blocks: [4, 5, 6, 7, 11] | Blocked by: []
References (executor has NO interview context — be exhaustive):
- Pattern: `src/shared/prompt-async-gate.ts:97` — timeout wrapper starts.
- Pattern: `src/shared/prompt-async-gate.ts:122` — `dispatchAfterSessionIdle` starts.
- Pattern: `src/shared/prompt-async-gate.ts:158` — reservation object is created.
- Pattern: `src/shared/prompt-async-gate.ts:163` — reservation is stored before dispatch.
- Pattern: `src/shared/prompt-async-gate.ts:164` — current `dispatchAttempted` flag starts the risky state.
- Pattern: `src/shared/prompt-async-gate.ts:190` — dispatch is marked attempted before awaiting the SDK call.
- Pattern: `src/shared/prompt-async-gate.ts:191` — dispatch is wrapped with timeout.
- Pattern: `src/shared/prompt-async-gate.ts:197` — only this path returns `dispatched`.
- Pattern: `src/shared/prompt-async-gate.ts:198` — failure path returns `failed`.
- Pattern: `src/shared/prompt-async-gate.ts:201` — cleanup starts.
- Pattern: `src/shared/prompt-async-gate.ts:204` — current hold applies after any attempted dispatch.
- Pattern: `src/hooks/shared/prompt-async-gate.test.ts:60` — existing successful-dispatch hold test.
- Pattern: `src/hooks/shared/prompt-async-gate.test.ts:282` — existing timeout test uses `postDispatchHoldMs: 0`; add the default-hold regression beside it.
- Pattern: `src/hooks/shared/prompt-async-gate.test.ts:321` — existing rejected-dispatch test currently expects duplicate blocking; update or supersede with the corrected failing-first behavior.
- Test: `src/hooks/shared/prompt-async-gate.test.ts` — co-located Bun tests import from the shared gate re-export.
Acceptance criteria (agent-executable only):
- [ ] Before changing `src/shared/prompt-async-gate.ts`, `bun test src/hooks/shared/prompt-async-gate.test.ts --bail` fails on the new timeout/rejection reservation test; save output to `evidence/task-2-red.txt`.
- [ ] After the minimal fix, `bun test src/hooks/shared/prompt-async-gate.test.ts --bail` passes; save output to `evidence/task-2-green.txt`.
- [ ] `bun test src/shared/prompt-async-route-audit.test.ts src/hooks/shared/prompt-async-gate.test.ts --bail` passes.
- [ ] `git diff -- src/shared/prompt-async-gate.ts src/hooks/shared/prompt-async-gate.test.ts` shows no unrelated refactor.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: failed promptAsync releases reservation
Tool: bash
Steps: mkdir -p evidence; bun test src/hooks/shared/prompt-async-gate.test.ts --bail 2>&1 | tee evidence/task-2-green.txt
Expected: test output exits 0 and includes the new case where an immediate rejected dispatch lets the next caller attempt dispatch instead of returning reserved.
Evidence: evidence/task-2-green.txt
Scenario: successful promptAsync still dedupes immediate duplicate
Tool: bash
Steps: bun test src/hooks/shared/prompt-async-gate.test.ts --bail 2>&1 | tee evidence/task-2-success-hold.txt
Expected: existing successful hold tests still pass and assert prompt call count remains 1 for immediate duplicate after dispatch.
Evidence: evidence/task-2-success-hold.txt
```
Commit: YES | Message: `fix(prompt-gate): release failed dispatch reservations` | Files: [`src/shared/prompt-async-gate.ts`, `src/hooks/shared/prompt-async-gate.test.ts`]
- [ ] 3. Strengthen static prompt-route audit
What to do: Extend `src/shared/prompt-async-route-audit.test.ts` only if needed so the production invariant remains pinned: raw `session.prompt`/`session.promptAsync` calls stay inside the shared gate or documented wrappers, production callers cannot set `postDispatchHoldMs: 0`, and new route wrappers must throw or requeue on `failed` instead of silently dropping prompt failures. Keep the allowlist small and documented.
Must NOT do: Do not add a broad allowlist for convenience. Do not weaken the existing raw-prompt scanner.
Parallelization: Can parallel: YES | Wave 1 | Blocks: [4, 5, 6, 7, 11] | Blocked by: []
References (executor has NO interview context — be exhaustive):
- Pattern: `src/shared/prompt-async-route-audit.test.ts:6` — `SOURCE_ROOT` points at production `src`.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:8` — current raw prompt allowlist begins.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:48` — AST helper extracts property names.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:106` — raw prompt property detection starts.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:139` — destructured prompt binding detection starts.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:249` — production raw prompt audit test starts.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:270` — production `postDispatchHoldMs: 0` audit starts.
- Pattern: `src/plugin/unstable-agent-babysitter.ts:29` — wrapper currently ignores non-failed statuses and should be assessed by audit or route tests.
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:153` — failed result is thrown and requeued in catch.
- Pattern: `src/features/team-mode/tools/messaging.ts:212` — non-dispatched live delivery falls back to inbox.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/shared/prompt-async-route-audit.test.ts --bail` passes.
- [ ] If the audit is changed, first save a red run in `evidence/task-3-red.txt` proving the audit catches the intended bad pattern.
- [ ] `rg -n "postDispatchHoldMs\\s*:\\s*0" src --glob '*.ts' --glob '!*.test.ts'` returns no production offenders.
- [ ] `bun test src/shared/prompt-async-route-audit.test.ts src/hooks/shared/prompt-async-gate.test.ts --bail` passes.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: raw prompt audit remains strict
Tool: bash
Steps: mkdir -p evidence; bun test src/shared/prompt-async-route-audit.test.ts --bail 2>&1 | tee evidence/task-3-audit.txt
Expected: command exits 0 and reports the production prompt route audit passing with the existing narrow allowlist.
Evidence: evidence/task-3-audit.txt
Scenario: production callers do not disable the hold
Tool: bash
Steps: rg -n "postDispatchHoldMs\\s*:\\s*0" src --glob '*.ts' --glob '!*.test.ts' 2>&1 | tee evidence/task-3-hold-audit.txt; test "${PIPESTATUS[0]}" -eq 1
Expected: no production TypeScript file sets postDispatchHoldMs to 0.
Evidence: evidence/task-3-hold-audit.txt
```
Commit: YES | Message: `test(prompt-gate): audit internal prompt routes` | Files: [`src/shared/prompt-async-route-audit.test.ts`]
- [ ] 4. Cover model-fallback promptAsync overlap
What to do: Add or update model-fallback tests so an OpenCode-style sequence is pinned: first internal `promptAsync` returns/appears accepted, then a `session.error` arrives before the post-dispatch hold expires. Same-model duplicate events must remain deduped, but a legitimate next fallback/recovery route must not be skipped solely because of a stale reservation. Make the smallest route fix in `src/plugin/event.ts` only if the shared gate fix does not satisfy the tests.
Must NOT do: Do not merge model-fallback and runtime-fallback state machines. Do not broaden fallback eligibility.
Parallelization: Can parallel: YES | Wave 2 | Blocks: [8, 9, 11] | Blocked by: [2, 3]
References (executor has NO interview context — be exhaustive):
- Pattern: `src/plugin/event.ts:451` — `autoContinueAfterFallback` starts.
- Pattern: `src/plugin/event.ts:462` — `modelFallbackContinuationsInFlight` is set.
- Pattern: `src/plugin/event.ts:465` — route aborts the active session before retry.
- Pattern: `src/plugin/event.ts:468` — route releases model-fallback reservations by exact source/prefix.
- Pattern: `src/plugin/event.ts:499` — async prompt path starts.
- Pattern: `src/plugin/event.ts:500` — route dispatches through `promptAsyncAfterSessionIdle`.
- Pattern: `src/plugin/event.ts:506` — `dispatched` sets `dispatched = true`.
- Pattern: `src/plugin/event.ts:508` — failed prompt is logged.
- Pattern: `src/plugin/event.ts:530` — cleanup/fallback dedupe state starts.
- Pattern: `src/plugin/event.model-fallback.test.ts:169` — existing overlapping error-events regression.
- Pattern: `src/plugin/event.model-fallback.test.ts:248` — existing providerless duplicate regression.
- Pattern: `src/plugin/event.model-fallback.test.ts:308` — existing distinct-provider regression expects two retries.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:295` — upstream `promptAsync` handler returns before prompt completion.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:306` — upstream later failure publishes `session.error`.
Acceptance criteria (agent-executable only):
- [ ] Before route or shared-gate fix is applied, save a red run for the new model-fallback overlap test in `evidence/task-4-red.txt`.
- [ ] `bun test src/plugin/event.model-fallback.test.ts --bail` passes after the fix.
- [ ] Existing duplicate suppression tests still assert one dispatch for same failed model and two dispatches for distinct providers.
- [ ] `git diff -- src/plugin/event.ts src/plugin/event.model-fallback.test.ts` shows only fallback prompt reservation/recovery changes and tests.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: duplicate same-model fallback remains suppressed
Tool: bash
Steps: mkdir -p evidence; bun test src/plugin/event.model-fallback.test.ts --bail 2>&1 | tee evidence/task-4-model-fallback.txt
Expected: command exits 0; assertions around overlapping same-model events still show one prompt dispatch.
Evidence: evidence/task-4-model-fallback.txt
Scenario: distinct provider fallback is not blocked by stale reservation
Tool: bash
Steps: bun test src/plugin/event.model-fallback.test.ts --bail 2>&1 | tee evidence/task-4-distinct-provider.txt
Expected: command exits 0; distinct-provider regression asserts two prompt dispatches rather than a reserved skip.
Evidence: evidence/task-4-distinct-provider.txt
```
Commit: YES | Message: `test(model-fallback): cover prompt async error overlap` | Files: [`src/plugin/event.ts`, `src/plugin/event.model-fallback.test.ts`]
- [ ] 5. Cover runtime-fallback retry cleanup
What to do: Add runtime-fallback tests proving a failed, timed-out, or OpenCode-style async error retry clears `sessionRetryInFlight`, `sessionAwaitingFallbackResult`, fallback timeout, and pending model state so the next eligible fallback attempt can dispatch. Make the smallest fix in `src/hooks/runtime-fallback/auto-retry.ts` if state cleanup or reservation release is incomplete.
Must NOT do: Do not change fallback model selection order, cooldown policy, or visible-response detection.
Parallelization: Can parallel: YES | Wave 2 | Blocks: [8, 9, 11] | Blocked by: [2, 3]
References (executor has NO interview context — be exhaustive):
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:38` — `abortSessionRequest` starts.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:50` — abort call is issued.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:51` — runtime-fallback reservation release happens after abort.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:72` — fallback timeout scheduling starts.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:113` — `autoRetryWithFallback` starts.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:140` — retry-in-flight is set.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:157` — awaiting fallback result is set.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:160` — route dispatches through `promptAsyncAfterSessionIdle`.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:177` — failed gate result throws.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:193` — cleanup begins.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:195` — non-dispatched retries clear awaiting state and timeout.
- Pattern: `src/hooks/runtime-fallback/index.test.ts:1274` — existing in-flight race test starts.
- Pattern: `src/hooks/runtime-fallback/index.test.ts:1352` — existing force-advance retry signal test starts.
- Test: `src/hooks/runtime-fallback/index.test.ts` — broad runtime fallback integration tests.
- Test: `src/hooks/runtime-fallback/success-retry-key-cleanup.test.ts` — cleanup-specific test style.
Acceptance criteria (agent-executable only):
- [ ] Red evidence saved to `evidence/task-5-red.txt` for the new retry cleanup regression before implementation.
- [ ] `bun test src/hooks/runtime-fallback/index.test.ts src/hooks/runtime-fallback/success-retry-key-cleanup.test.ts --bail` passes.
- [ ] New assertions prove a second fallback attempt reaches `promptAsync` after the first failed or timed out attempt.
- [ ] No existing runtime-fallback duplicate suppression test starts dispatching duplicate same-source retries.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: failed runtime fallback retry clears state
Tool: bash
Steps: mkdir -p evidence; bun test src/hooks/runtime-fallback/index.test.ts src/hooks/runtime-fallback/success-retry-key-cleanup.test.ts --bail 2>&1 | tee evidence/task-5-runtime-fallback.txt
Expected: command exits 0 and includes the new cleanup regression.
Evidence: evidence/task-5-runtime-fallback.txt
Scenario: in-flight duplicate suppression still works
Tool: bash
Steps: bun test src/hooks/runtime-fallback/index.test.ts --bail 2>&1 | tee evidence/task-5-inflight.txt
Expected: command exits 0 and the existing in-flight race test still expects a single fallback preparation while the retry is pending.
Evidence: evidence/task-5-inflight.txt
```
Commit: YES | Message: `fix(runtime-fallback): clear failed retry prompt state` | Files: [`src/hooks/runtime-fallback/auto-retry.ts`, `src/hooks/runtime-fallback/index.test.ts`, `src/hooks/runtime-fallback/success-retry-key-cleanup.test.ts`]
- [ ] 6. Cover background-agent prompt wake and resume paths
What to do: Add tests around background-agent launch/resume/parent-wake prompt failures so a failed or timed-out gated prompt does not leave the task in a hanging in-between state and does not block the retry/restore path behind a stale reservation. Fix only the affected background-agent path if the shared gate is not enough.
Must NOT do: Do not change background concurrency limits, polling stability thresholds, task state schema, or tmux behavior.
Parallelization: Can parallel: YES | Wave 2 | Blocks: [9, 10, 11] | Blocked by: [2, 3]
References (executor has NO interview context — be exhaustive):
- Pattern: `src/features/background-agent/manager.ts:831` — task history records launch state before prompt dispatch.
- Pattern: `src/features/background-agent/manager.ts:892` — launch prompt uses `promptWithRetryInDirectory`.
- Pattern: `src/features/background-agent/manager.ts:895` — launch prompt failure is caught.
- Pattern: `src/features/background-agent/manager.ts:947` — launch prompt failure can try fallback retry.
- Pattern: `src/features/background-agent/manager.ts:1279` — resume path comments fire-and-forget prompt.
- Pattern: `src/features/background-agent/manager.ts:1293` — resume dispatches through `promptAsyncAfterSessionIdle`.
- Pattern: `src/features/background-agent/manager.ts:1321` — failed resume prompt throws into catch.
- Pattern: `src/features/background-agent/manager.ts:1339` — resume failure can try fallback retry.
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:137` — parent wake dispatches through `promptAsyncAfterSessionIdle`.
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:153` — failed parent wake throws and requeues.
- Pattern: `src/features/background-agent/manager.test.ts:7548` — existing stale launch prompt error regression.
- Pattern: `src/features/background-agent/manager.test.ts:7556` — existing launch prompt can remain pending until rejected.
- Test: `src/features/background-agent/manager.test.ts` — large integration-style manager test file.
- Test: `src/features/background-agent/parent-wake-notifier.test.ts` — use if present; otherwise add focused coverage next to the notifier.
Acceptance criteria (agent-executable only):
- [ ] Red evidence saved to `evidence/task-6-red.txt` before background-agent fix.
- [ ] `bun test src/features/background-agent/manager.test.ts --bail` passes.
- [ ] If a notifier test file exists or is added, `bun test src/features/background-agent/parent-wake-notifier.test.ts --bail` passes.
- [ ] Assertions prove failed parent wake is requeued and failed resume restores or transitions task state instead of leaving an in-flight prompt hang.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: launch/resume failure does not hang task state
Tool: bash
Steps: mkdir -p evidence; bun test src/features/background-agent/manager.test.ts --bail 2>&1 | tee evidence/task-6-background-manager.txt
Expected: command exits 0 and includes the new failed prompt launch/resume regression.
Evidence: evidence/task-6-background-manager.txt
Scenario: parent wake is requeued after failed prompt
Tool: bash
Steps: if [ -f src/features/background-agent/parent-wake-notifier.test.ts ]; then bun test src/features/background-agent/parent-wake-notifier.test.ts --bail; else bun test src/features/background-agent/manager.test.ts --bail; fi 2>&1 | tee evidence/task-6-parent-wake.txt
Expected: command exits 0 and the tested path asserts a wake is requeued after a failed gated prompt.
Evidence: evidence/task-6-parent-wake.txt
```
Commit: YES | Message: `test(background-agent): cover failed prompt wake recovery` | Files: [`src/features/background-agent/manager.ts`, `src/features/background-agent/manager.test.ts`, `src/features/background-agent/parent-wake-notifier.ts`, `src/features/background-agent/parent-wake-notifier.test.ts`]
- [ ] 7. Cover team-mode live delivery and wake hint paths
What to do: Add tests proving team live delivery and idle wake hints recover from failed or gated prompt dispatch by leaving mailbox fallback paths available and not permanently reserving the recipient session. Fix only the affected team route if the shared gate is not enough.
Must NOT do: Do not change team storage schema, member eligibility, worktree creation, or tmux layout behavior.
Parallelization: Can parallel: YES | Wave 2 | Blocks: [10, 11] | Blocked by: [2, 3]
References (executor has NO interview context — be exhaustive):
- Pattern: `src/features/team-mode/tools/messaging.ts:155` — `deliverLive` starts.
- Pattern: `src/features/team-mode/tools/messaging.ts:168` — mailbox reservation is taken before live delivery.
- Pattern: `src/features/team-mode/tools/messaging.ts:202` — live delivery dispatches through `promptAsyncAfterSessionIdle`.
- Pattern: `src/features/team-mode/tools/messaging.ts:212` — non-dispatched live delivery falls back to inbox injection.
- Pattern: `src/features/team-mode/tools/messaging.ts:234` — live delivery catch releases mailbox reservation.
- Pattern: `src/hooks/team-session-events/team-idle-wake-hint.ts:102` — wake hint skips if promptAsync unavailable.
- Pattern: `src/hooks/team-session-events/team-idle-wake-hint.ts:114` — wake hint dispatches through `promptAsyncAfterSessionIdle`.
- Pattern: `src/hooks/team-session-events/team-idle-wake-hint.ts:125` — non-dispatched wake hint is logged.
- Test: `src/features/team-mode/tools/messaging.test.ts` — live delivery tests include network-down cases around line 615.
- Test: `src/hooks/team-session-events/team-idle-wake-hint.test.ts` — wake hint tests cover dispatch and skip behavior.
Acceptance criteria (agent-executable only):
- [ ] Red evidence saved to `evidence/task-7-red.txt` before team-mode fix.
- [ ] `bun test src/features/team-mode/tools/messaging.test.ts src/hooks/team-session-events/team-idle-wake-hint.test.ts --bail` passes.
- [ ] Live delivery failure releases mailbox reservation and leaves the message available for inbox fallback.
- [ ] Wake hint failure does not leave recipient session reserved for the next legitimate prompt.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: team live delivery fallback remains available
Tool: bash
Steps: mkdir -p evidence; bun test src/features/team-mode/tools/messaging.test.ts --bail 2>&1 | tee evidence/task-7-team-messaging.txt
Expected: command exits 0 and includes failed live-delivery fallback coverage.
Evidence: evidence/task-7-team-messaging.txt
Scenario: team wake hint does not poison prompt gate
Tool: bash
Steps: bun test src/hooks/team-session-events/team-idle-wake-hint.test.ts --bail 2>&1 | tee evidence/task-7-team-wake.txt
Expected: command exits 0 and includes the wake-hint failure or gated retry regression.
Evidence: evidence/task-7-team-wake.txt
```
Commit: YES | Message: `test(team-mode): cover failed live prompt delivery` | Files: [`src/features/team-mode/tools/messaging.ts`, `src/features/team-mode/tools/messaging.test.ts`, `src/hooks/team-session-events/team-idle-wake-hint.ts`, `src/hooks/team-session-events/team-idle-wake-hint.test.ts`]
- [ ] 8. Manual QA direct upstream promptAsync failure path
What to do: Exercise the sibling OpenCode promptAsync behavior against a local session so the evidence shows `prompt_async` returns before the later failure event. Use a task-owned tmux session or direct command with bounded polling. Capture request, status, emitted error, and cleanup commands.
Must NOT do: Do not modify sibling OpenCode code. Do not kill the tmux server. Do not rely on a simulated unit test for this task.
Parallelization: Can parallel: YES | Wave 3 | Blocks: [12] | Blocked by: [4, 5]
References (executor has NO interview context — be exhaustive):
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:295` — promptAsync handler.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:306` — later failure event publication.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts:314` — NoContent return.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/opencode/src/session/prompt.ts:1098` — invalid agent error.
- API/Type: `/Users/yeongyu/local-workspaces/opencode/packages/sdk/js/src/gen/types.gen.ts:2727` — SDK success response is 204 void.
- Pattern: `.debugging:86` — cleanup ledger must track tmux sessions and worktree cleanup.
- Test: `packages/opencode/test/server/httpapi-promptasync-context.test.ts` in sibling repo — upstream already has promptAsync context coverage and can be used as a reference pattern.
Acceptance criteria (agent-executable only):
- [ ] `evidence/task-8-opencode-promptasync.txt` contains a 204/NoContent observation and a later `session.error` observation for the same session.
- [ ] `.debugging` records the manual QA command, evidence path, and owned tmux session name if tmux is used.
- [ ] Any tmux session created by this task is killed by name after evidence capture; `tmux ls` still works and no `kill-server` command is used.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: upstream promptAsync returns before durable completion
Tool: tmux
Steps: mkdir -p evidence; create a task-owned tmux session named omo-promptasync-upstream-qa that starts or connects to local OpenCode, creates a session, posts /prompt_async with an invalid agent, subscribes to events, writes the 204 response and subsequent session.error to evidence/task-8-opencode-promptasync.txt, signals completion with tmux wait-for; then kill only tmux session omo-promptasync-upstream-qa.
Expected: evidence file contains the same session id, HTTP 204 or NoContent for prompt_async, and later session.error with Agent not found or equivalent prompt failure.
Evidence: evidence/task-8-opencode-promptasync.txt
Scenario: tmux server is preserved
Tool: bash
Steps: tmux ls 2>&1 | tee evidence/task-8-tmux-ls.txt
Expected: command succeeds or reports no sessions; no task command used tmux kill-server.
Evidence: evidence/task-8-tmux-ls.txt
```
Commit: NO | Message: `n/a` | Files: [`evidence/task-8-opencode-promptasync.txt`, `.debugging`]
- [ ] 9. Manual QA OMO main-session no-hang path
What to do: Run an actual OMO/OpenCode session in a task-owned tmux session and reproduce the original main-session internal prompt race as closely as possible: trigger an internal fallback/recovery prompt, force a prompt failure or timeout, and verify the next recovery/fallback prompt is dispatched or requeued instead of hanging behind `reserved`. Capture `/tmp/oh-my-opencode.log`, session output, and final status.
Must NOT do: Do not declare success from tests alone. Do not kill global tmux server. Do not leave an OpenCode server or task-owned tmux session running.
Parallelization: Can parallel: YES | Wave 3 | Blocks: [12] | Blocked by: [4, 5, 6]
References (executor has NO interview context — be exhaustive):
- Pattern: `package.json:24` — scripts section starts.
- Pattern: `package.json:25` — `bun run build` command.
- Pattern: `package.json:36` — `bun run typecheck` command.
- Pattern: `package.json:38` — `bun test` command.
- Pattern: `src/plugin/event.ts:499` — model-fallback async prompt path.
- Pattern: `src/hooks/runtime-fallback/auto-retry.ts:160` — runtime-fallback async retry path.
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:137` — background parent wake prompt path.
- Pattern: `src/shared/prompt-async-gate.ts:189` — prompt gate logs dispatching.
- Pattern: `src/shared/prompt-async-gate.ts:199` — prompt gate logs failed.
- Pattern: `/tmp/oh-my-opencode.log` — project logger target from AGENTS.md.
Acceptance criteria (agent-executable only):
- [ ] `evidence/task-9-omo-main-session.txt` contains the actual OMO run transcript and exits without indefinite wait.
- [ ] `evidence/task-9-omo-log.txt` contains prompt gate dispatch/failure/retry evidence and no final stale `reserved` skip for the target session.
- [ ] The owned tmux session is removed by name and no tmux server kill is used.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: OMO main-session recovery does not hang after prompt failure
Tool: tmux
Steps: mkdir -p evidence; truncate or mark /tmp/oh-my-opencode.log with a QA delimiter; start a task-owned tmux session named omo-main-prompt-race-qa from /Users/yeongyu/local-workspaces/gpt 5.5 xhigh; run the built local CLI or local plugin against sibling OpenCode with a prompt that triggers fallback/recovery; wait via tmux wait-for; capture pane to evidence/task-9-omo-main-session.txt and log slice to evidence/task-9-omo-log.txt; kill only omo-main-prompt-race-qa.
Expected: transcript reaches a terminal success or expected handled failure, and log shows any failed/reserved prompt is followed by cleanup/retry/requeue rather than permanent hang.
Evidence: evidence/task-9-omo-main-session.txt
Scenario: no stale prompt reservation after QA
Tool: bash
Steps: rg -n "prompt-async-gate.*(reserved|failed|dispatched)|model-fallback|runtime-fallback" evidence/task-9-omo-log.txt | tee evidence/task-9-prompt-gate-log.txt
Expected: output shows the target session's failed prompt path and a subsequent dispatch/requeue; it does not end with only a reserved skip.
Evidence: evidence/task-9-prompt-gate-log.txt
```
Commit: NO | Message: `n/a` | Files: [`evidence/task-9-omo-main-session.txt`, `evidence/task-9-omo-log.txt`, `.debugging`]
- [ ] 10. Manual QA background and team routes
What to do: Exercise at least one background-agent parent wake/resume path and one team live-delivery/wake path in real local execution or, if team-mode live execution is blocked by credentials/config, use the closest agent-executed CLI/tool invocation plus captured logs and explain the limitation in `.debugging`. The key pass condition is no permanent prompt reservation after a failed prompt delivery; background wakes requeue and team messages remain available through inbox fallback.
Must NOT do: Do not create nested teams. Do not leave team worktrees, mailbox files, or tmux panes unmanaged. Do not remove user team config.
Parallelization: Can parallel: YES | Wave 3 | Blocks: [12] | Blocked by: [6, 7]
References (executor has NO interview context — be exhaustive):
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:137` — background parent wake prompt dispatch.
- Pattern: `src/features/background-agent/parent-wake-notifier.ts:153` — failed wake throws into requeue path.
- Pattern: `src/features/background-agent/manager.ts:1293` — background resume prompt dispatch.
- Pattern: `src/features/team-mode/tools/messaging.ts:168` — live delivery reserves mailbox entry.
- Pattern: `src/features/team-mode/tools/messaging.ts:202` — live delivery prompt dispatch.
- Pattern: `src/features/team-mode/tools/messaging.ts:241` — catch releases mailbox reservation.
- Pattern: `src/hooks/team-session-events/team-idle-wake-hint.ts:114` — wake hint prompt dispatch.
- Pattern: `src/features/team-mode/AGENTS.md:1` — team-mode overview and guardrails.
Acceptance criteria (agent-executable only):
- [ ] `evidence/task-10-background-route.txt` contains real background route evidence showing no hang and correct requeue/handled failure.
- [ ] `evidence/task-10-team-route.txt` contains real team route evidence or a documented blocked-run fallback with the exact command and reason.
- [ ] `.debugging` records any created team run, worktree, tmux session, mailbox path, and cleanup command.
- [ ] Any created team/task artifacts are cleaned up or explicitly preserved only if they are required evidence.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: background failed prompt route is handled
Tool: tmux
Steps: run a task-owned local OMO session that launches or resumes a background task with a controlled prompt failure; capture task output and /tmp/oh-my-opencode.log to evidence/task-10-background-route.txt; clean up only task-owned tmux sessions.
Expected: background task reaches completed, error, interrupt, or requeued state; it does not remain indefinitely running due to a reserved prompt gate.
Evidence: evidence/task-10-background-route.txt
Scenario: team live delivery failure falls back without stale reservation
Tool: bash
Steps: execute the smallest team-mode command sequence available in this worktree/config to send a member message or wake hint; if full team-mode is unavailable, run the team-mode focused test plus log why real execution is blocked; capture output to evidence/task-10-team-route.txt.
Expected: real route or documented blocked fallback shows failed prompt delivery releases mailbox reservation or leaves inbox fallback available.
Evidence: evidence/task-10-team-route.txt
```
Commit: NO | Message: `n/a` | Files: [`evidence/task-10-background-route.txt`, `evidence/task-10-team-route.txt`, `.debugging`]
- [ ] 11. Local full verification and atomic commits
What to do: Run focused tests, full Bun tests, typecheck, and build locally. Group commits by logical unit if earlier tasks have not already committed. Preserve `.debugging` updates and keep evidence files uncommitted unless the user explicitly wants evidence committed.
Must NOT do: Do not commit unrelated dirty files. Do not modify `package.json` version. Do not use `--no-verify`.
Parallelization: Can parallel: YES | Wave 3 | Blocks: [12] | Blocked by: [1, 2, 3, 4, 5, 6, 7]
References (executor has NO interview context — be exhaustive):
- Pattern: `package.json:25` — build script.
- Pattern: `package.json:36` — typecheck script.
- Pattern: `package.json:38` — test script.
- Pattern: `src/hooks/shared/prompt-async-gate.test.ts:282` — gate timeout regression area.
- Pattern: `src/plugin/event.model-fallback.test.ts:169` — model-fallback race regression area.
- Pattern: `src/hooks/runtime-fallback/index.test.ts:1274` — runtime-fallback in-flight regression area.
- Pattern: `src/features/background-agent/manager.test.ts:7548` — background stale prompt error regression area.
- Pattern: `src/features/team-mode/tools/messaging.test.ts` — team messaging coverage.
- Pattern: `src/shared/prompt-async-route-audit.test.ts:249` — raw prompt audit.
- External: `/Users/yeongyu/.agents/skills/git-master/SKILL.md` — atomic commit guidance.
Acceptance criteria (agent-executable only):
- [ ] `bun test src/hooks/shared/prompt-async-gate.test.ts src/shared/prompt-async-route-audit.test.ts src/plugin/event.model-fallback.test.ts src/hooks/runtime-fallback/index.test.ts --bail` passes.
- [ ] `bun test src/features/background-agent/manager.test.ts src/features/team-mode/tools/messaging.test.ts src/hooks/team-session-events/team-idle-wake-hint.test.ts --bail` passes.
- [ ] `bun test` passes.
- [ ] `bun run typecheck` passes.
- [ ] `bun run build` passes.
- [ ] `git status --short` shows only intended committed changes plus untracked evidence if evidence is intentionally not committed.
- [ ] `git log --oneline origin/dev..HEAD` shows atomic conventional commits and no WIP commits.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: full local verification passes
Tool: bash
Steps: mkdir -p evidence; bun test 2>&1 | tee evidence/task-11-bun-test.txt; bun run typecheck 2>&1 | tee evidence/task-11-typecheck.txt; bun run build 2>&1 | tee evidence/task-11-build.txt
Expected: all three commands exit 0.
Evidence: evidence/task-11-bun-test.txt
Scenario: commit history is clean
Tool: bash
Steps: git status --short | tee evidence/task-11-git-status.txt; git log --oneline origin/dev..HEAD | tee evidence/task-11-git-log.txt
Expected: no unrelated tracked changes remain unstaged/uncommitted; commits are conventional and logically atomic.
Evidence: evidence/task-11-git-log.txt
```
Commit: YES | Message: `chore(prompt-race): verify local prompt recovery fix` | Files: [`.debugging`, `src/shared/prompt-async-gate.ts`, `src/hooks/shared/prompt-async-gate.test.ts`, `src/shared/prompt-async-route-audit.test.ts`, `src/plugin/event.ts`, `src/plugin/event.model-fallback.test.ts`, `src/hooks/runtime-fallback/auto-retry.ts`, `src/hooks/runtime-fallback/*.test.ts`, `src/features/background-agent/*.ts`, `src/features/background-agent/*.test.ts`, `src/features/team-mode/**/*.ts`, `src/features/team-mode/**/*.test.ts`, `src/hooks/team-session-events/*.ts`, `src/hooks/team-session-events/*.test.ts`]
- [ ] 12. Create PR, pass CI/reviews, merge, and clean up worktree
What to do: Push branch, create a PR against `dev`, run the verification loop until CI, review-work, and Cubic all pass. Use a PR body file under `/tmp/pull-request-prompt-hang-race-<timestamp>.md` and get user confirmation before `gh pr create` per project instruction. After all gates pass, merge as requested by the PR workflow, then remove `/Users/yeongyu/local-workspaces/gpt 5.5 xhigh` worktree after merge.
Must NOT do: Do not create the PR body inline. Do not merge before CI, review-work, and Cubic all pass. Do not remove the worktree before merge. Do not delete user data or unrelated worktrees.
Parallelization: Can parallel: NO | Wave 4 | Blocks: [] | Blocked by: [8, 9, 10, 11]
References (executor has NO interview context — be exhaustive):
- External: `/Users/yeongyu/local-workspaces/gpt 5.5 xhigh/.agents/skills/work-with-pr/SKILL.md` — PR lifecycle requires CI, review-work, Cubic, merge, and worktree cleanup.
- External: `/Users/yeongyu/.agents/skills/git-master/SKILL.md` — commit history must be atomic before push.
- Pattern: `package.json:25` — build command mirrors CI build.
- Pattern: `package.json:36` — typecheck command mirrors CI typecheck.
- Pattern: `package.json:38` — test command mirrors CI root test.
- Pattern: `.debugging:88` — worktree removal is already tracked as a cleanup artifact.
- Pattern: `.debugging:89` — branch cleanup is already tracked as a cleanup artifact.
Acceptance criteria (agent-executable only):
- [ ] PR exists and targets `dev`; `gh pr view --json number,baseRefName,headRefName,url` saved to `evidence/task-12-pr.json`.
- [ ] `gh pr checks --watch --fail-fast` passes for the PR head.
- [ ] review-work final report has no blocking issues and is saved to `evidence/task-12-review-work.txt`.
- [ ] Cubic comment says no issues found, or equivalent pass status, saved to `evidence/task-12-cubic.txt`.
- [ ] PR is merged.
- [ ] `git worktree list` no longer contains `/Users/yeongyu/local-workspaces/gpt 5.5 xhigh` after merge cleanup.
QA scenarios (MANDATORY — task incomplete without these):
```
Scenario: PR gates all pass
Tool: bash
Steps: mkdir -p evidence; gh pr view --json number,baseRefName,headRefName,url > evidence/task-12-pr.json; gh pr checks --watch --fail-fast 2>&1 | tee evidence/task-12-ci.txt; run review-work and save its final report to evidence/task-12-review-work.txt; query PR comments/reviews for Cubic and save pass evidence to evidence/task-12-cubic.txt.
Expected: PR targets dev, CI exits 0, review-work has no blocking issues, and Cubic reports no issues found.
Evidence: evidence/task-12-ci.txt
Scenario: merged branch worktree cleanup
Tool: bash
Steps: after merge, run git worktree list | tee evidence/task-12-worktrees-before-cleanup.txt; remove only /Users/yeongyu/local-workspaces/gpt 5.5 xhigh with git worktree remove; run git worktree list | tee evidence/task-12-worktrees-after-cleanup.txt.
Expected: after-cleanup evidence does not contain /Users/yeongyu/local-workspaces/gpt 5.5 xhigh.
Evidence: evidence/task-12-worktrees-after-cleanup.txt
```
Commit: NO | Message: `n/a` | Files: [`/tmp/pull-request-prompt-hang-race-<timestamp>.md`, `evidence/task-12-pr.json`, `evidence/task-12-ci.txt`, `evidence/task-12-review-work.txt`, `evidence/task-12-cubic.txt`]
## Final verification wave (MANDATORY — after all implementation tasks)
> Runs in PARALLEL. ALL must APPROVE. Surface results to the caller and wait for an explicit "okay" before declaring complete.
- [ ] F1. Plan compliance audit — every task done, every acceptance criterion met
- [ ] F2. Code quality review — diagnostics clean, idioms match, no dead code
- [ ] F3. Real manual QA — every QA scenario executed with evidence captured
- [ ] F4. Scope fidelity — nothing extra shipped beyond Must-Have, nothing Must-NOT-Have introduced
## Commit strategy
- One logical change per commit. Conventional Commits (`<type>(<scope>): <subject>` body + footer).
- Atomic: every commit builds and passes tests on its own.
- No "WIP" / "fix typo squash later" commits on the final branch — clean up before merge.
- Recommended commit sequence:
- `test(debugging): document prompt async race evidence`
- `fix(prompt-gate): release failed dispatch reservations`
- `test(prompt-gate): audit internal prompt routes`
- `test(model-fallback): cover prompt async error overlap`
- `fix(runtime-fallback): clear failed retry prompt state`
- `test(background-agent): cover failed prompt wake recovery`
- `test(team-mode): cover failed live prompt delivery`
- Reference the plan file path in the final commit footer: `Plan: plans/fix-prompt-hang-race.md`.
## Success criteria
- All Must-Have shipped; all QA scenarios pass with captured evidence; F1F4 approved; commit history clean.
-60
View File
@@ -1,60 +0,0 @@
# Migrate Legacy Workspace State To .omo
## Goal
Move project-scoped OMO state from `.sisyphus` to `.omo`, while preserving existing legacy state by copying `.sisyphus` into `.omo` on plugin startup when `.sisyphus` is detected.
## Task Graph
1. Add shared legacy workspace migration helper.
- Depends on: none.
- Acceptance: copies nested files from `.sisyphus` into `.omo`, creates missing directories, does not overwrite existing `.omo` files, and returns whether anything migrated.
2. Invoke migration at plugin startup.
- Depends on: task 1.
- Acceptance: startup calls the helper before managers/tools/hooks are created.
3. Switch runtime state constants to `.omo`.
- Depends on: task 1.
- Acceptance: Boulder and run-continuation writes land under `.omo`.
4. Switch guardrails and prompt-facing workspace paths to `.omo`.
- Depends on: task 3.
- Acceptance: Prometheus, Atlas, notepad, write-existing guard, and plan extraction surfaces point at `.omo`.
5. Update docs, ignore rules, and generated schema.
- Depends on: tasks 3-4.
- Acceptance: user-facing storage docs and schema examples no longer advertise `.sisyphus` for active workspace state.
6. Verify and ship.
- Depends on: tasks 1-5.
- Acceptance: focused tests, typecheck, full test suite, build, manual QA, CI, GPT-5.2 review, and Cubic all pass before merge.
## Dependency Matrix
| Task | Blocks | Verification |
| --- | --- | --- |
| Migration helper | Startup invocation, path switch | `bun test src/shared/legacy-workspace-migration.test.ts` |
| Startup invocation | Manual QA | `bun test src/testing/create-plugin-module.test.ts` or equivalent startup test |
| Runtime constants | Hook and CLI behavior | Boulder/run-continuation focused tests |
| Guardrails/prompts | Agent workflow behavior | Prometheus/Atlas/write guard focused tests |
| Docs/schema | PR review and release docs | `bun run build:schema`, `rg "\\.sisyphus"` audit |
| Verification | Merge | local gates, PR CI, reviews |
## QA Scenarios
- Happy path: project has `.sisyphus/plans/a.md` and no `.omo`; startup creates `.omo/plans/a.md` with same content.
- Existing target: project has `.sisyphus/plans/a.md` and `.omo/plans/a.md`; migration leaves `.omo/plans/a.md` unchanged.
- Mixed tree: project has `.sisyphus/plans/a.md` and `.omo/notepads/note.md`; migration copies only missing legacy files.
- Runtime write: `writeBoulderState()` creates `.omo/boulder.json`.
- Continuation marker: `setContinuationMarkerSource()` writes `.omo/run-continuation/<session>.json`.
- Guardrail: Prometheus may write `.omo/plans/*.md` and is blocked outside `.omo`.
- Adjacent compatibility: rules injector still discovers legacy `.sisyphus/rules` alongside `.omo/rules` if kept for transition.
## Commit Strategy
1. `feat(workspace): migrate legacy sisyphus state to omo`
- Migration helper, startup invocation, runtime constants, and direct tests.
2. `docs(workspace): document omo workspace paths`
- Docs, `.gitignore`, schema output, and prompt/docs wording updates if separated cleanly.