Skip to content

ACP ends registered-command streaming before later execution cycles finish #53914

Open

Description

@danyel117

Functional context

When an ACP client such as Zed invokes a registered long-running command, its executor may submit several OpenCode execution cycles before returning. ACP should continue streaming those cycles and return session/prompt only when the command ends. Completing one execution or phase does not mean the overall command is finished.

On v2 at 434f7b2 (2.0.24 manifest), ACP correlates the first inbox with the prepared slash-command ID, while the executor's session.prompt uses a different ID. Its event consumer also exits at the first terminal execution. Later text, tools, permissions and goal progress can therefore disappear while work continues. Promise command executors additionally receive no abort signal. Explicit Cancel during an idle gap needs a session interrupt hook, while ACP transport shutdown must preserve persisted goals rather than call the user interruption endpoint.

Related user report: prevalentWare/opencode-goal-plugin#59

Scope

  • packages/cli/src/acp/turn.ts and translate.ts: keep registered-command streaming for its full executor lifetime, preserve the latest execution outcome and drain an in-flight final cycle.
  • packages/plugin/src/promise/command.ts and adapter.ts: pass the per-command cancellation signal to Promise executors without changing normal one-argument callbacks.
  • Expose an explicit session interruption hook to Promise/Effect plugins, including idle sessions. Distinguish session/cancel, close/delete and $/cancel_request from ACP transport shutdown; clear cancellation intent after each interrupt. Plan projection is an independent follow-up in ACP does not project plugin plan metadata live or during replay #53928.
  • Use the existing v2 APIs and tests; no public HTTP protocol or workflow changes.

Acceptance criteria

  • A registered executor admits cycle 1 with an executor-owned inbox ID, cycle 1 completes, and cycle 2 streams while the same ACP prompt remains pending.
  • If the executor returns during the last execution, its final text/tool updates and terminal outcome arrive before the ACP response.
  • Cancel during busy work or an idle gap interrupts the registered executor and exposes an aborted signal; a cooperating plugin can persist cancellation and suppress scheduled continuation.
  • Quitting the ACP client does not call the explicit user interruption API. A late request abort cannot leave intent that affects the next prompt. Explicit Cancel still notifies the hook on idle sessions.
  • Normal prompts, permission handling, usage updates, retries and cancellation retain their existing behavior.

Functional tests

Use a local registered command fixture that holds its command endpoint, calls session.prompt, completes one execution, admits another, and sends final text before returning. Assert both chunks arrive, no early response occurs, and cancelling the prompt aborts the executor. Use an isolated state file and local fake SSE provider for a goal plugin integration: run multiple continuation cycles before completing the overall goal. Cancel during an idle continuation timer and verify no next cycle starts; quit ACP and verify the goal stays active. Hold the final usage GET, cancel that prompt, start another prompt, close transport and assert no false user interruption. No live provider credentials are necessary.

Existing checks: bun run check at repository root with required Bun 1.4.2; bun test test/acp --timeout 30000 in packages/cli; bun test test/session-activation.test.ts in packages/core.

Out of scope

Client UI redesign, goal planning semantics or goal persistence inside OpenCode core, arbitrary idle timeouts, provider behavior, CI/workflows, infrastructure, deployment and changes to ordinary prompt lifetime. Plan projection is tracked separately in #53928.

Dependencies

None for command lifetime, abort signals or explicit interruption hooks. A goal plugin is only an optional integration consumer, not needed for the ACP unit suite. #53928 is independent.

Activity

  1. opencode-agent commented on Oct 8, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this with the ACP wire-fixture tests, on both v2.0.24 and the latest v2 (62e087f).

    The test uses a fake registered-command endpoint that stays open and sends two execution cycles, each with its own inbox ID (delivered, execution.started, text deltas, execution.succeeded), before returning. With /review now, the ACP session/prompt resolves with end_turn and the client receives no agent_message_chunk updates at all, from either cycle. A normal prompt with the same events streams fine.

    This fits your analysis. translate.ts only starts streaming after a session.inbox.delivered whose inboxID equals the prepared prompt ID, and the command endpoint never receives that ID. Separately, turn.ts returns succeeded for commands as soon as the command request finishes, without waiting for the event stream.

    I did not test the abort-signal part or the metadata.acp.plan projection (that one is a new proposal). Your PR #53913 is linked for maintainers to review.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions