mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-21 14:21:48 +00:00
fix(agents): Confirm initial input after transcript flush
This commit is contained in:
parent
f20fc3346b
commit
954c1ffa29
6 changed files with 73 additions and 8 deletions
|
|
@ -414,9 +414,10 @@ Message sequence is monotonic per thread. `triggerMessageIds` means durably
|
|||
booked; `acceptedMessageIds` means the runtime queue accepted those inputs;
|
||||
`consumedMessageIds` is recorded from the correlated `EXTERNAL_MESSAGE` event
|
||||
when the old background runtime drains them. In the ACP session path, daemon
|
||||
queue-only input carries the run metadata and delivery id; the child checks the
|
||||
ambient binding, flushes its mid-turn transcript record, then records the consumed
|
||||
window under the workspace lock. Queue acceptance is not that receipt.
|
||||
input carries the run metadata and delivery watermark; the session checks the
|
||||
ambient binding, flushes the initial or mid-turn transcript record, then records
|
||||
the consumed window under the workspace lock. Queue acceptance is not that
|
||||
receipt.
|
||||
`committedThroughSequence` advances only across a
|
||||
contiguous consumed context window. On a failed enqueue, execution failure, or
|
||||
daemon restart, reconciliation rebooks everything not consumed and committed.
|
||||
|
|
@ -435,10 +436,11 @@ different processes and their wall clocks can disagree.
|
|||
|
||||
At run start and direct delivery, the dispatcher sends one contiguous context
|
||||
window through `contextThroughSequence`, not just the triggering ids. The
|
||||
initial prompt is consumed when the turn starts; direct input becomes consumed
|
||||
only on its correlated runtime event. This makes the scalar watermark honest
|
||||
even when intervening posts targeted another agent. A clean but `unclosed`
|
||||
return blocks the workflow yet commits only demonstrably consumed input. A
|
||||
initial prompt becomes consumed only after its transcript record is durable;
|
||||
direct input follows the same transcript-before-receipt order. This makes the
|
||||
scalar watermark honest even when intervening posts targeted another agent. A
|
||||
clean but `unclosed` return blocks the workflow yet commits only demonstrably
|
||||
consumed input. A
|
||||
durable `blocked`/`review` close marker likewise lets restart reconciliation
|
||||
finish the workflow close without pretending an accepted-but-undrained message
|
||||
was read.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ Current run ids, timestamps and limitations are recorded in
|
|||
| Concurrent same-thread handoff and human acceptance | Clean three-run peer handoff; 8,999 ms overlapping run lifetimes; both results submitted and Chrome Mark done succeeded. |
|
||||
| Child delegation and parent report | Earlier ACP demo reached done on child and parent, with manual startup retries; not a clean first-attempt run. |
|
||||
| Live human input | Same-run mid-turn transcript and consumed window verified; final review contains the correction. Late-drain/crash cases remain open. |
|
||||
| Initial input receipt | A real assigned run persisted its initial prompt, consumed the matching trigger and reached review. The transcript-before-receipt crash window remains to be force-killed. |
|
||||
| Cancellation | Working → stopping → cancelled observed; usage retained; unresponsive-child case remains open. |
|
||||
| Running task after daemon restart | SIGKILL after a durable checkpoint exposed a startup-discovery defect; after its fix, the same run/session recovered as attempt 2 and reached human acceptance. Other crash windows remain open. |
|
||||
| Human resolves a blocker | Real thread_block question, human selection, same-session continuation, JSON review, and Chrome Mark done verified. Other blocker-acknowledgement scopes remain an owner decision. |
|
||||
|
|
@ -239,6 +240,18 @@ the watchdog. The long replay run also exposed the conservative accounting
|
|||
policy visibly: it closed at `666,749 / 200,000` tokens because the limit is
|
||||
checked at admission, not between tool rounds.
|
||||
|
||||
**Initial-receipt observation (2026-09-08).** Real ACP run
|
||||
`rn_bcd7f190-5958-4dcd-9c92-ea207d4aad16` posted
|
||||
`INITIAL-RECEIPT-8391` and closed with `thread_review`. Its durable trigger
|
||||
`ms_c7dcb7be-dda0-496f-90f7-e3663dec5130` is the sole consumed id and the
|
||||
committed watermark is sequence 1. Reloading session
|
||||
`b48bad11-6e7c-5110-92a1-e560bf56eec6` from disk found the assigned prompt and
|
||||
marker in its transcript. The session path now flushes that initial user record
|
||||
before writing the receipt; dispatch no longer marks it consumed before
|
||||
activation. This proves the normal path and source order, not the named
|
||||
process-exit injection between those two writes. No build, lint, test suite, or
|
||||
CI ran.
|
||||
|
||||
### Step 9 — REST and Web Shell
|
||||
|
||||
Design direction is settled ahead of the build in [`2026-09-07-workspace-agents-web-shell-design.md`](./2026-09-07-workspace-agents-web-shell-design.md): the thread view is a ledger of outstanding obligations with the conversation as evidence, not a chat log with a status badge. It inherits Web Shell's existing tokens and adds no new colour or typeface. Step 9 renders `resolveThreadStatus`'s `status` and `reason` rather than inventing a second status vocabulary, shows one lane per agent that has worked the thread, and bounds every transcript view to the run's own slice.
|
||||
|
|
|
|||
|
|
@ -140,6 +140,23 @@ recorded a valid repeated receipt once. No build, lint, typecheck or test suite
|
|||
ran. Crash/late-drain/close races on the new ACP path still require live checks;
|
||||
this supersedes earlier statements below that live input is unconnected.
|
||||
|
||||
#### ACP initial input — transcript before receipt
|
||||
|
||||
The initial launch path no longer reports its input consumed while the session
|
||||
is merely prepared. `Session` records and flushes the initial user message
|
||||
before advancing the durable consumed watermark; a failed receipt leaves the
|
||||
input eligible for replay.
|
||||
|
||||
Real task `th_c7646c7b-7f4c-4996-9560-42721f125d0d` ran on the existing
|
||||
demo-worker session. Run `rn_bcd7f190-5958-4dcd-9c92-ea207d4aad16` posted
|
||||
`INITIAL-RECEIPT-8391`, called `thread_review`, and finished with trigger
|
||||
`ms_c7dcb7be-dda0-496f-90f7-e3663dec5130` consumed at watermark 1. Reloading
|
||||
the session from disk found both the assigned prompt and marker in its
|
||||
transcript. This observes the normal model path and checks the persisted result;
|
||||
the exact process-exit window between transcript flush and receipt remains an
|
||||
unrun failure injection. No build, lint, typecheck, test suite, CI, or new PR
|
||||
was used.
|
||||
|
||||
#### Unread input at close
|
||||
|
||||
Source inspection found that explicit close used to promote every accepted id
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ import {
|
|||
runWithAgentRunContext,
|
||||
requireAgentRunContext,
|
||||
consumeAgentInput,
|
||||
readThread,
|
||||
type AgentRunContext,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { NOT_CURRENTLY_GENERATING_CANCEL_MESSAGE } from '@qwen-code/acp-bridge/bridgeErrors';
|
||||
|
|
@ -5456,6 +5457,37 @@ export class Session implements SessionContext {
|
|||
} else {
|
||||
recorder?.recordUserMessage(promptText);
|
||||
}
|
||||
const agentRun = parsePromptAgentRun(params);
|
||||
if (agentRun) {
|
||||
try {
|
||||
const thread = await readThread(
|
||||
this.config.getWorkingDir(),
|
||||
agentRun.threadId,
|
||||
);
|
||||
const delivered = thread?.messages.find(
|
||||
(message) =>
|
||||
message.sequence === agentRun.contextThroughSequence,
|
||||
);
|
||||
if (!recorder || !delivered) {
|
||||
throw new Error(
|
||||
'Agent input requires a transcript and delivery watermark',
|
||||
);
|
||||
}
|
||||
await recorder.flush();
|
||||
await consumeAgentInput(
|
||||
this.config.getWorkingDir(),
|
||||
delivered.id,
|
||||
delivered.sequence,
|
||||
);
|
||||
} catch (error) {
|
||||
// The model may run twice after a receipt failure; losing the
|
||||
// task would be worse than replaying its durable input.
|
||||
debugLogger.warn(
|
||||
'Agent input receipt failed; replay remains pending',
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ describe('session dispatch port', () => {
|
|||
expect(result.status).toBe('started');
|
||||
expect(sendPrompt).not.toHaveBeenCalled();
|
||||
if (result.status !== 'started') throw new Error('Expected prepared session');
|
||||
expect(result.consumedOnStart).toBe(false);
|
||||
result.activate?.();
|
||||
expect(contextOf(sendPrompt)?.agentRun).toEqual({
|
||||
workspaceId: 'ws_1',
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ export function createSessionDispatchPort(
|
|||
return {
|
||||
status: 'started',
|
||||
sessionId,
|
||||
consumedOnStart: true,
|
||||
consumedOnStart: false,
|
||||
activate() {
|
||||
const execution: AgentBodyState = {
|
||||
kind: 'running',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue