You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(acp): goal-unavailable notice gating on resume (#503)
## Summary
Goal-notice fixes for nori-client MCP goal resume, split out from the
original combined cleanup PR (the browser-CDP fixes now live in #505
targeting main).
- Surface the goal-unavailable notice for **all** in-play goals on
resume, not just the first.
- Stop recording the goal-unavailable notice to the transcript (it is a
transient UI notice, not conversation history).
- Document the goal-notice gating and non-recording-on-resume behavior.
- Share a panic-safe `EnvGuard` across the goal resume tests.
Targets `feat/nori-client-mcp-cleanup`.
## Test plan
- [x] `cargo test -p nori-acp` (574 pass, with the feat-branch mock
agent built)
- [x] `cargo clippy -p nori-acp --tests` (clean)
- [x] `cargo fmt --check`
Copy file name to clipboardExpand all lines: nori-rs/acp/docs.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -127,13 +127,13 @@ The model-facing tool contract is intentionally narrower than the user-facing `/
127
127
128
128
Before user prompts are submitted to the ACP runtime, `user_input.rs` prepends the current goal as a structured `<goal_context>` block when a goal exists. Hook context is still applied before goal context, and compact summaries remain the outermost framing instruction, so resumed/compacted turns retain their existing prompt-ordering invariant while still carrying goal state to the agent. The prompt goal context and hidden continuation prompt use the same compact elapsed-time and token-count formatting as the visible TUI goal summary.
129
129
130
-
Agents that are not advertised the local `nori-client` server do not receive goal context through prompt transformation and do not receive hidden goal-continuation prompts. Backend `ThreadGoal*` operations from user-facing paths emit an unavailable notice instead of mutating goal state, because those agents cannot call `update_goal` to close the loop. If transcript replay restores an active goal into a non-MCP session, resume emits the same unavailable notice after the replayed goal snapshot rather than silently leaving automation inert. The local MCP server is the required automation path for active goals, while transcript replay, usage accounting, and ordinary prompts continue without it. The intended degradation path is a concise first-prompt `<context>` block with Nori CLI context, the open source repo URL, and a note that MCP-backed Nori affordances are unavailable, not repeated prompt-prefix workarounds on every turn.
130
+
Agents that are not advertised the local `nori-client` server do not receive goal context through prompt transformation and do not receive hidden goal-continuation prompts. Backend `ThreadGoal*` operations from user-facing paths emit an unavailable notice instead of mutating goal state, because those agents cannot call `update_goal` to close the loop. That op-time notice is emitted directly to the client channel and deliberately **not** recorded to the transcript -- like resume notices it is a transient affordance derived from session state, so recording it would replay and accumulate a duplicate notice on every `/goal` op. If transcript replay restores any in-play goal (active, paused, blocked, or usage-limited) into a non-MCP session, resume emits the same unavailable notice after the replayed goal snapshot rather than the `/goal resume` affordance, which would mislead since `/goal` is disabled for non-MCP agents. The local MCP server is the required automation path for active goals, while transcript replay, usage accounting, and ordinary prompts continue without it. The intended degradation path is a concise first-prompt `<context>` block with Nori CLI context, the open source repo URL, and a note that MCP-backed Nori affordances are unavailable, not repeated prompt-prefix workarounds on every turn.
131
131
132
132
After an active goal mutation or a visible user prompt completes with `StopReason::EndTurn`, `session_runtime_driver.rs` may submit a hidden goal-continuation prompt to the same ACP session. `thread_goal.rs` owns the continuation prompt text so it is derived from the current backend goal snapshot, not from TUI state or transcript text. The driver only starts a continuation when the goal is active, the reducer has returned to idle, and no queued user work remains. Chaining from one hidden `GoalContinuation` into another is gated on `goal_mcp_connected`, an `@/nori-rs/acp/src/backend/mod.rs` session flag that `NoriClientService::initialize` (the rmcp `ServerHandler::initialize` hook) flips only after the local MCP server receives an `initialize` request. The first successful initialize also re-emits `SessionCapabilitiesChanged` with `nori_client.initialized = true`. This is a safety invariant: until the agent has actually connected to the `nori-client` endpoint it has no way to mark the goal complete, so unbounded continuation-to-continuation chaining is not allowed. Agents without a connected goal MCP endpoint receive at most one hidden continuation per active goal mutation or visible user turn.
133
133
134
134
Goal state is also part of the replay contract. `transcript.rs` passes Nori-owned goal update and clear events through replay, and `session.rs` seeds `ThreadGoalState` from those transcript-derived events before ACP session setup advertises local MCP tools. Server-side `session/load` can also emit ACP replay notifications while loading; those normalized client events are deferred until backend setup completes, then combined with the transcript replay events before rebuilding `ThreadGoalState`. This ordering matters because ACP agents replay their own session history, but they do not replay Nori-owned `ThreadGoalUpdated` events, so the transcript remains authoritative for goal state even when the agent emits load replay notifications.
135
135
136
-
When a resumed goal is paused, blocked, or usage-limited, `thread_goal.rs` derives a one-time `SessionUpdateInfo` notice from the rehydrated snapshot so the TUI can show why goal automation will not continue until the user resumes, edits, clears, or resolves the blocker. That notice is emitted directly by `session.rs` after deferred replay events and is not written back into the transcript, so each future resume still derives its notice from goal state instead of accumulating duplicate history entries. ACP usage updates still normalize to `SessionUpdateInfo`, but `session_runtime_driver.rs` observes those events and asks the goal state to refresh `tokens_used`; when a goal exists, the backend emits a follow-up `ThreadGoalUpdated` snapshot so the TUI and transcript stay synchronized with usage accounting.
136
+
When a resumed goal is paused, blocked, or usage-limited **and goal automation is available**, `thread_goal.rs` derives a one-time `SessionUpdateInfo` resume notice from the rehydrated snapshot so the TUI can show why goal automation will not continue until the user resumes, edits, clears, or resolves the blocker. `ThreadGoalState::resume_notice_for` centralizes this decision: with automation available it returns the status-specific resume notice (suggesting `/goal resume`); without automation it returns the unavailable notice for any in-play goal instead, since the resume affordance would be misleading. Either notice is emitted directly by `session.rs` after deferred replay events and is not written back into the transcript, so each future resume still derives its notice from goal state instead of accumulating duplicate history entries. ACP usage updates still normalize to `SessionUpdateInfo`, but `session_runtime_driver.rs` observes those events and asks the goal state to refresh `tokens_used`; when a goal exists, the backend emits a follow-up `ThreadGoalUpdated` snapshot so the TUI and transcript stay synchronized with usage accounting.
0 commit comments