Closes five gaps in the ultrawork prompt versus codex-plugins' parallel
directive, applied to all three model variants (default/Claude, GPT, Gemini)
with prompt-engineering entropy gate (each addition encodes a distinct
binding boolean, not narrative reinforcement):
1. TDD-MANDATORY (was conditional "when test infrastructure exists"):
every production change follows RED -> GREEN -> SURFACE. Failing test
first, capture assertion msg, smallest change to flip green, exercise
real surface, capture artifact. Exemption whitelist: formatting /
comment-only / version bump / rename-only, each must be justified
in writing; unjustified exemption = rejection.
2. Scenario contract (was free-form Test Plan Template): require 3+
scenarios upfront covering happy path, edge (boundary / empty /
malformed / concurrent), adjacent-surface regression. Each scenario
binds a binary pass condition, a real-surface artifact source, and
a test file + test id written test-first.
3. RED->GREEN evidence capture (was "all tests pass"): every scenario
requires TWO captured artifacts -- RED assertion msg before the
change AND GREEN assertion msg after -- alongside the real-surface
artifact (tmux / curl / browser / Playwright / computer-use /
CLI stdout / parsed config / DB diff). Tests are the floor (always
required); surface artifact is the ceiling (also required).
4. Durable notepad: mktemp -t ulw-*.md with append-only sections
(Plan, Scenarios, Now, Todo, Findings, Learnings). Survives context
loss; resume by re-reading.
5. Reviewer gate: trigger when user said strictly / rigorously /
"deeply", or task touches 3+ files / 20+ turns / 30+ min, or it is
refactor / migration / perf / security work. Reviewer verdict is
binding ("looks good but..." = rejection). Loop until unconditional
approval.
Plus: TODO format upgraded from vague "track every step" to atomic
`path: <action> for <scenario-id> -- verify by <check>` with a GOOD
test-first / impl pair example and a BAD list including
"production code before its failing test".
Per-variant adaptation:
- default.ts (Claude): full structured sections.
- gpt.ts (GPT-5.x): outcome-first prose, shorter prose per gpt-5.5 guide.
- gemini.ts: explicit enforcement framing + anti-optimism checkpoint
upgraded with a TDD-violation question (#7).
Verified by:
- bun test src/hooks/keyword-detector/ (119 pass / 0 fail).
- lsp_diagnostics clean on all three files.
- Module-load smoke test confirms each exported message string parses
and contains the new section anchors (TDD MANDATORY, SCENARIO
CONTRACT, DURABLE NOTEPAD, REVIEWER GATE).
Char deltas (directive body only):
- default 13646 -> 17144 (+26%)
- gpt 6740 -> 9215 (+37%, was the leanest start)
- gemini 14196 -> 16136 (+14%)
Existing tests only assert presence of "ULTRAWORK MODE ENABLED!" which
is preserved verbatim in every variant.
The lead currently leaves teams alive after the task list drains because
none of the prompt surfaces tell it WHEN to close or HOW. omx-style
'self-closing' behavior was missing for four reasons (diagnosed via
prompt-engineering A/B/C: wrong / misframed / missing):
1. builtin team-mode skill 'Lifecycle' (B+C): 'phase ends / shape
outgrown' is qualitative, so the model maps it to 'wait for user'.
Step 6 jumped to team_delete without the request/approve pair the
tool contract requires. Replaced with a 'Closure Contract' (a
computable predicate over team_task_list + team_status) and an
explicit 'Closure Sequence' (request -> approve -> delete, with
force=true reserved for unrecoverable paths only).
2. TEAM_MESSAGE keyword injection (C): spent 100%% of its one-shot
budget on routing ('do not substitute delegate_task'), 0%% on
closure. Added the same closure rule in compressed form. Kept the
'NEVER substitute with delegate_task' literal that
keyword-detector/index.test.ts depends on.
3. team-mode-status-injector body (C): the only per-session injection
for team mode had no closure obligation. Replaced the optional
'load the team-mode skill ... otherwise use the team_* tools'
sentence with a 'Closure invariant' clause that ties the check to
every team_task_update.
4. member-guidance Wrap-up (A+B): step 3 said 'so the lead can decide
whether to request shutdown', but team_shutdown_request is
lead-only - members cannot initiate it. Step ordering also placed
the completion message before team_task_update, so the lead's
closable check would see stale data. Reordered to
task_update -> check task_list for new work -> if nothing left,
send a single 'closure-ready' message and idle. Test assertion
updated to match the new accurate contract.
Also: stripped Korean alternation from TEAM_PATTERN per directive
('절대로 코드 내에 한국어 적지 마라'). Pattern is now
/\\bteam[\\s_-]?mode\\b/i. Removed 4 Korean test cases
(2 positive triggers + 2 false-positive guards) that the pattern no
longer needs to defend, and updated the keyword-detector AGENTS.md
row.
Net: -71 lines across prompt surfaces. The Closure Contract is the
only addition; everything else tightened.
Tests: 428/428 pass across src/features/team-mode/,
src/features/builtin-skills/, src/hooks/keyword-detector/,
src/hooks/team-mode-status-injector/, src/hooks/team-mailbox-injector/,
src/hooks/team-tool-gating/, src/hooks/team-session-events/.
LSP: no errors introduced (one pre-existing error in
keyword-detector/index.test.ts confirmed pre-existing on dev).
When verification_pending is true and the agent has dispatched an Oracle
verification (verification_attempt_id is set), session.idle events that
arrive before tool-execute-after stores the Oracle session ID
(verification_session_id still undefined) caused handlePendingVerification
to fall through to handleFailedVerification. This injected a duplicate
'verification failed' continuation prompt, spawning a second Oracle.
The fix adds a guard in handlePendingVerification: when
verification_attempt_id is set but verification_session_id is not, Oracle
dispatch is in flight and the handler returns early instead of declaring
failure. The pending wake will retry on the next session.idle.
Regression test added in given/when/then style proving the race sequence:
1. ULW loop detects DONE, enters verification_pending
2. Oracle dispatch stamps verification_attempt_id (tool-execute-before)
3. Second session.idle fires before tool-execute-after stores session ID
4. Handler must NOT call handleFailedVerification
RED (before fix): 2 prompt injections (duplicate Oracle)
GREEN (after fix): 1 prompt injection (correct)
Fixes#4256Fixes#4019
When a user explicitly configures a model for the multimodal-looker
agent (e.g. zhipuai-coding-plan/glm-5.1), treat that model as
vision-capable even when its provider config does not declare
modalities.input or capabilities.input.image. This unblocks
vision-capable models that the provider config does not advertise.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The OpenCode TUI renders background subagent session entries using
`props.metadata.sessionId` as the navigation target. When the wait-loop
in delegate-task and background-task tools exits before the session is
assigned, but the session is created moments later (before metadata
publish), the published metadata had `sessionId: undefined`, leaving
the TUI entry stuck spinning with no clickable target.
Add a single late-fallback `manager.getTask(task.id)?.sessionId` check
between the wait-loop exit and metadata publish in both paths. This
closes the narrow race window that produced the symptom in #4252.
Regression test: `late-session-id-capture.test.ts` mocks the exact race
(launch returns no sessionId; getTask returns it after the wait loop).
Verified:
- npm run build: PASS
- npm test: exit code 0 (one unrelated pre-existing failure in
sisyphus-task > browserProvider propagation re: agent-browser skill)
Atlas was the only agent that bailed early (returning undefined) when
applyModelResolution returned undefined, even if the user had explicitly
configured agents.atlas.model. The general-agents.ts path already handled
this case by falling back to the user's override; sisyphus has its own
explicit-config check. Atlas had neither, so under edge cases (cold
provider cache, no system default, empty availableModels) Atlas was
silently dropped and OpenCode used its built-in default — surfaced as
the hardcoded claude-sonnet-4-6 in the reported bug.
This aligns atlas-agent.ts with the same defensive pattern used in
general-agents.ts: when resolution fails but the user has an explicit
model override, honor it directly instead of dropping the agent.
Adds a regression test covering the scenario where availableModels is
empty AND systemDefaultModel is undefined AND no provider cache exists.
Upstream OpenCode's Global.Path.bin resolves to ~/.cache/opencode/bin
(XDG cache), where its ripgrep auto-installer and many LSP servers land.
OMO's getOpenCodeBundledRg only checked the data-dir variant
(~/.local/share/opencode/bin), so when OpenCode had already downloaded
rg into its cache directory OMO would skip it and either redownload via
its own fallback installer or fall back to system grep.
Probe the cache-backed bin path first so OMO reuses tools OpenCode has
already installed.
Ship the documented Team Mode security-research capability as an .agents skill and expose /security-research through transition command wrappers.
The exact security-review slug has no reachable history hits; security-research is the documented missing artifact from README and issue #3887.
Refs #3887
Plan: plans/security-research-restore.md
In v4.1.0+, users observed duplicate assistant streams rendering the same
content in two languages simultaneously (e.g. Chinese + English), most often
at the end of a turn.
Root cause: ParentWakeNotifier.requeueWake() unconditionally requeued ANY
wake that arrived during the background-agent-parent-wake post-dispatch
hold window. When a duplicate completion edge fired during that hold, the
same wake was replayed after the hold expired, triggering a second prompt
dispatch and a parallel assistant stream.
The fix compares the new wake against dispatchedParentWakes.get(sessionID)
and drops identical wakes during the gate hold, while preserving the existing
requeue behavior for genuinely-new wakes and failed-dispatch retries.
Regression test added in given/when/then style covering the duplicate-during-
hold scenario (TDD red-then-green).
Fixes#4256Fixes#4019
Same Web-Response-on-Node hazard existed in ripgrep auto-download flow,
zip extraction helpers, and binary downloader streams. Switch to the new
Node-safe reader and ensure no spawn path escapes as unhandledRejection.
Related to #3919.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
OpenCode Desktop v1.14.41+ runs OMO inside a Node.js utility process.
The previous `new Response(proc.stdout).text()` call is Bun-/Web-API-specific
and crashed the Desktop sidecar on Windows when grep/glob were invoked.
Switch glob/grep cli to the new process-stream-reader + search-process-output
helpers. Behavior on Bun and CLI/Linux/macOS is unchanged. ripgrep auto-download,
PowerShell fallback, and rgSemaphore are preserved.
Fixes#3919
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Introduces:
- src/shared/process-stream-reader.ts: Buffer-concat stream reader compatible with both Bun and Node ChildProcess stdout (replaces Web Response API usage)
- src/tools/shared/search-process-output.ts: structured subprocess output collector with timeout, kill, and rejection cleanup
- bun-spawn-shim hardened: Node path forces windowsHide: true; spawn errors no longer escape as unhandledRejection
Foundation for #3919 fix.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>