Files
qwen-code/scripts
qqqys 645d32eed3 feat(acp): run the model round of fresh prompts on the shared agent loop (#15509)
* feat(core): let a host run its own rounds on the shared model round

ACP keeps its tool step while its model round moves to LlmClient, so it
needs a few things the client did not expose:

- Turn gives Thought the chunk's raw text and ToolCallRequest the
  functionCall it was built from;
- with SendMessageOptions.toolCallPreparations, Turn yields a
  ToolCallPreparation event for a call the model started streaming,
  also in a blocking Stop hook's continuation; without it Turn yields
  the events it did, which matters to the Hosted Harness;
- SendMessageOptions.userTurnRecordedByHost skips the UserQuery's user
  message record and todo work chain start;
- admitUserPrompt takes the hook's submitted_prompt and a signal;
- the agent loop exports runModelRound.

Part of #15327.

* docs(design): plan PR 2 in three steps and describe PR 2a

* feat(acp): run the model round of fresh prompts on the shared agent loop

With experimental.unifiedAgentLoop on, a fresh user prompt outside a
Goal, channel or goal-proposal approval turn sends each model round
through LlmClient via runModelRound. The client then does what ACP
re-implements: UserPromptSubmit on the verdict admitUserPrompt took at
the same point, memory recall, the file-history snapshot, system
reminders, workflow intent, MessageDisplay, compaction, the session
token gate and the external Stop hook. ACP keeps its tool step, its
user message record, its Stop loop for the Todo Stop Guard and
background results, mid-turn input and its session/update output.

The Web Shell settings panel now lists the setting. A new no-AK suite
drives ACP twice per scenario and requires the same requests, updates
and stop reasons, apart from three asserted differences.

Part of #15327.

* docs(design): link the PR 2a pull request

* fix(acp): keep the Stop hook in ACP's Stop loop on the shared round

Review fixes for the shared model round of fresh ACP prompts:

- the client skips its Stop hook (new stopHookRunByHost send option) and
  ACP's Stop loop fires it as with the setting off, so ACP's own
  continuations stay interruptible and are judged by the hook, and the
  Todo Stop Guard is suspended at the cap
- the queued-input window closes when a response finishes
- a round that ends with a stop reason discards its resolved tool cards
- ACP throws the error the provider threw (getStructuredErrorCause)
- one failure helper for both rounds, one compression notice, and
  getThoughtSummary built on getThoughtText

* fix(acp): leave the interaction span and tool-call guards to ACP on the shared round

Second review round for the shared model round of fresh ACP prompts:

- interactionSpanByHost: the client neither starts nor ends an
  interaction span, so ACP's span (with its daemon parent) is the only one
- toolLoopGuardsByHost: the client skips its always-on tool-call checks,
  the exploration budget and reminder, the stateful-read result checks and
  the tool-call heuristics (new LoopCheckOptions.toolCallPatterns); ACP's
  daemon guards stay the only ones, so the reminder is no longer sent twice
- a stream Error is thrown after the round, so the client's error epilogue
  runs first
- ACP schedules the managed memory tasks itself when no UserQuery or
  ToolResult round ended the response, as after queued input cut it short
- parity: a scenario past the per-turn tool-call cap; fix the
  typecheck:integration errors in the parity suite

* docs(design): list host-owned send options and move each surface's default on its own

- Add a ledger of the send options that tell the client to skip part of
  its work for one host, with the step that removes each, and a
  Stop-point continuations subsection for ACP's own continuations.
- Swap ACP's next steps: PR 2b moves the tool batch of fresh prompt turns
  onto the scheduler, PR 2c moves the other turns onto the whole loop, so
  each ACP loop body changes once.
- Each surface changes its default on its own once it meets the listed
  conditions, starting with headless, and drops its current loop one
  release later.

* fix(acp): keep tool-result recall and run memory tasks after the Stop loop on the shared round

Third review round for the shared model round of fresh ACP prompts:

- toolLoopGuardsByHost now skips only the tool-call guards, the
  exploration reminder and the todo reminder ACP's tool-result message
  carries; a tool-result send again delivers the memory recall and the
  queued loop reminder and runs the size-only microcompaction
- under stopHookRunByHost the client no longer schedules the managed
  memory tasks when a round ends; ACP runs them through the now public
  runManagedAutoMemoryBackgroundTasks once its Stop loop ends the turn,
  with its existing end_turn and loop-protection conditions
- a UserPromptSubmit block of a tool-result round shows the block notice
  and suspends the Todo Stop Guard instead of ending the turn silently
- core values come from their defining modules, not the package root

* test(acp): leave the follow-up suggestion out of the ACP parity suite

The suggestion request runs after session/prompt returns and may or may
not land before the agent is stopped, so one arm could record an extra
request. The suite compares the prompt's model rounds only.

* fix(acp): snapshot the user turn and open send-now from the request on the shared round

ACP records the user turn before the shared round's first send, but the
round left the file-history snapshot to that send. A prompt cancelled
between admission and the first send kept its user turn without a
snapshot, and rewindToTurn indexes snapshots by user-turn position. ACP
takes the snapshot again, as on the current path, and
userTurnRecordedByHost also turns off the client's snapshot.

The response-interrupt window opened on the client's first event, while
the current path's watch() is open once the request is out, so input
sent before a slow first token waited for the first chunk. The window
now opens at the send; a cut is held while the round's request has not
reached history, so it cannot drop the message it carries, and the
first event pulls again.

* fix(acp): recall from the prompt text and close blocked tool results on the shared round

- Pass the raw prompt text as `memoryRecallQuery`, so managed-memory recall
  no longer searches resolved @-file contents and host notices.
- When UserPromptSubmit blocks a tool-result round, close its calls with an
  explicit block result instead of leaving them to the orphan repair, whose
  "retry if needed" text invites rerunning the tool into the same block.
- Take the memory task promises the client queues after each turn; ACP
  reports no memory updates, so they otherwise accumulate for the session.

* docs(design): drop the recall-text difference and note the blocked tool-result close

* fix(acp): keep unsent input and per-response facts on the shared round

- Start fresh response facts and report usage per request when the
  client's next-speaker check continues within one round, so a
  continuation's complete edit is not refused for the earlier response's
  max-tokens cut, and its usage is not dropped.
- When the round throws before its request pushed the message, such as a
  cancel during a tool-result round's UserPromptSubmit hook, keep the
  message in history as the non-throwing stops do, so tool results that
  already ran are not left to the "retry if needed" repair.
- When UserPromptSubmit blocks a tool-result round, keep the input taken
  with the results (already shown as delivered) beside the blocked
  results instead of dropping it.
- Cover the merged hook-context dedup's empty-request append in core.

* test(acp): pin the unsent-message keep on a tool-result hook timeout

A tool-result round's UserPromptSubmit request carries no abort signal,
so a cancel does not make it throw; the case that throws before the push
is the hook request failing, such as its 60 s message-bus timeout. Test
that case instead, and say so in the comment.
2026-10-26 04:49:42 +00:00
..
2025-08-27 15:23:21 +08:00