3d393c36a3
* opencode: surface subagent events via injected plugin opencode's cli/cmd/run.ts event loop filters all message.part.updated events to the orchestrator's session id (`part.sessionID !== sessionID` continue), so subagent-internal tool_use / text / step events were silently discarded by the CLI in --format json mode. opencode plugins, by contrast, receive every bus event via bus.subscribeAll() regardless of session. ship a per-run plugin (action/agents/opencodePlugin.ts) that re-emits non-orchestrator message.part.updated events as `pullfrog_bus_event` envelopes on opencode's stdout. the plugin is staged into <XDG_CONFIG_HOME>/opencode/plugin/pullfrog-events.ts which is already redirected to ctx.tmpdir — never the user's repo working tree. the plugin also forwards the orchestrator's task tool dispatch at state.status="running" — that's the first moment state.input is populated with description / subagent_type / prompt and it lands BEFORE the subagent's first message.part.updated. forwarding this lets SessionLabeler register the lens label early, so subagent events bind to the correct lens name (e.g. lens:correctness) instead of the subagent#N fallback. the existing tool_use handler dedupes on callID so the late status=completed event from the CLI doesn't double-record. the parent's pullfrog_bus_event handler synthesizes the equivalent CLI-style event for each part type (tool/step-start/step-finish/text) and dispatches through the same handlers used by orchestrator events, so labeling, tool-call rendering, and the formatWithLabel magenta prefix all share one code path. verified end-to-end via `pnpm play --local --raw` with a prompt that dispatches a reviewfrog subagent: orchestrator's task call now logs "» dispatching subagent: lens:read-readme-and-report-purpose" before the subagent runs, the subagent's read tool call surfaces with [lens:...] magenta prefix, and the run-end "subagent finished" attribution shows the lens name. also adds an AGENTS.md rule formalizing the no-write-to-repo invariant: action runtime must never write into the user's working tree; auxiliary files go in ctx.tmpdir via HOME / XDG_CONFIG_HOME. * drop opencodePlugin.test.ts — bullshit-test cleanup these tests spied on process.stdout.write, loaded the plugin source into a temp file via dynamic import, and asserted the output strings matched the plugin source i'd just hand-written. zero unique signal over the e2e run in preview repo, plus they violate AGENTS.md's "mocks tend to add ceremony and brittleness" rule. real signal lives in the e2e: lens label rendering, dispatch attribution, no double events. if a syntactic regression in the plugin source ever ships, opencode logs it on plugin load and the e2e fails fast — the unit tests would catch the same regression no faster. * remove isPausedExternally — plugin makes it unnecessary empirical proof from PR #634's e2e debug trace: ~3.3 pullfrog_bus_event lines per second arrive on the parent's child.stdout pipe during a typical subagent run. each one fires updateActivity() and resets lastActivityTime, so the inner spawn activity timer naturally stays armed-but-not-fired throughout the subagent's lifetime — no suspend predicate needed. drop: - SpawnOptions.isPausedExternally + the check in spawn()'s activity loop - isSubagentInFlight() in opencode.ts + its callsite - two isPausedExternally unit tests in subprocess.test.ts keep: - killGroup (the actual zombie-prevention fix; still tested) - the plugin (action/agents/opencodePlugin.ts; the architectural fix) - everything in opencode.ts that derives lens labels from task dispatches the only edge case isPausedExternally covered that the plugin doesn't is a non-streaming provider going silent for >5min during a single LLM call inside a subagent. that's a provider-behavior question, not a harness-architecture one — best fixed at the provider level if it shows up. defense-in-depth that adds indirection is harmful when the upstream architectural fix is already in place. * opencode: address review feedback on bus envelope routing three findings from PR #634 review (2026-05-08T22:13:44Z): 1. token/cost double-count: routing subagent step_finish through the orchestrator's handler folded subagent tokens/cost into the run-wide accumulators that flow to logTokenTable + AgentUsage. neighbouring init/text handlers all gate on ORCHESTRATOR_LABEL for exactly this reason. fix: drop step_start AND step_finish from the bus envelope handler — those carry orchestrator-scoped state (currentStepId, stepHistory, token accumulators) that subagent events shouldn't touch. tool calls and text from subagents still surface — that's the user-visible activity. 2. subagent tool errors invisible: routed status="error" tool parts into handlers.tool_use which only emits "» <tool>(...)" with no error indication. fix: extend handlers.tool_use itself to log "» tool call failed: <msg>" when state.status==="error". benefits the orchestrator path too — opencode CLI also emits failed tool calls as tool_use at status=error and we were swallowing the failure signal there as well. 3. stale comments + leaked local paths: plugin source had /tmp/opencode-investigate/... paths from my local clone, specific line numbers from opencode's dev branch that don't match v1.1.56, forkDetach claim that's wrong for the pinned version, and JSDoc that still listed message.updated/session.error in the forwarded set after the runtime filter narrowed to message.part.updated only. fix: drop machine-local paths, drop version-fragile line numbers, correct the forwarded-set list, generalize the "why no @opencode-ai/plugin import" rationale to be version-agnostic. second review (2026-05-08T22:27:58Z) confirms these are the only findings still open — no new issues from the isPausedExternally removal.