## Summary
Fixes the `session.subscribe is not a function` crash that breaks every
engine workflow step (Plan Review, Code Review) executed by an ACP agent
(Hermes ACP, Prime, Grok).
Introduced by the ACP custom-tools bridge (#3476) in the sense that it
made the gap reachable: ACP sessions stream through the bridging client
handler onto `callbacks` and never exposed the pi-style `subscribe()`
that the engine's AgentSession contract promises. Two production call
sites call it unconditionally:
- `packages/engine/src/executor/execute-workflow-step.ts` (workflow
steps — Plan Review / Code Review)
- `packages/engine/src/pi.ts` fallback wiring (`wireFallbackHooks`,
`promptableSession.subscribe`)
(`reviewer.ts` guards with `typeof session.subscribe === "function"`;
the other paths do not.)
Real-world symptom (verified on 0.77.0-beta.6 with a Hermes ACP agent):
every Plan Review attempt fails before producing a verdict:
```
[pre-merge] Workflow step failed: Plan Review
outcome: Plan Review failed before producing a verdict: session.subscribe is not a function
Plan Review provider retry budget exhausted
```
## Fix
Fix at the seam rather than guarding every call site forever:
- `AcpRuntimeAdapter.createSession` wraps the raw callbacks so each
forwarded text/thinking/tool event is **also** replayed to subscribers
as the pi-shaped event consumers parse (`message_update` +
`assistantMessageEvent.{text_delta,thinking_delta}`,
`tool_execution_start/end`)
- exposes `session.subscribe(handler)` returning an unsubscribe
function; dispose clears subscribers
- merges engine `taskEnv` into the subprocess env behind the existing
allow-list trust boundary (KTD6b preserved — only allow-listed keys
forward, task values win)
- original callback delivery unchanged; subscriber exceptions are
isolated so a faulty consumer can't break the streaming bridge
## Testing
- New regression tests in `runtime-adapter.test.ts` against the real
echo-agent fixture (`ACP_FIXTURE_RICH_PROMPT=1`):
- subscribe replays text/thinking/tool events as pi-shaped events
- unsubscribe stops delivery
- dual delivery: original `onText` callback AND subscriber both fire
- Full plugin suite: 255 passed (21 files)
- `tsc --noEmit` clean for the plugin
- changeset for `@runfusion/fusion` (patch, bugfix)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added session event subscriptions with unsubscribe support.
* Streamed text, thinking, and tool updates are replayed in a consistent
event format.
* Added support for task-specific environment values when launching
subprocesses.
* **Bug Fixes**
* Ensured streamed events reach both existing callbacks and subscribers.
* Isolated subscriber errors so they do not interrupt other handlers.
* Session cleanup now stops further subscription notifications.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->