Merge master into fix/subagent-stack-end-result

This commit is contained in:
Tianyi Cui
2026-08-02 23:13:00 +08:00
154 changed files with 2735 additions and 472 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/core/agent/README.md
README.md: 5f9fdc44794a562c2b0da39e1a43a01aa19c451b
README.zh.md: f088b8c354f0aec1b51d7ff8ae706ddc49265a20
README.md: 98421aa6de3d6778702665854ed723507e933028
README.zh.md: bfc8d68a9656a29a809de0848986e4ee9eb3fe7c

View File

@@ -62,7 +62,7 @@ The handle every plugin programs against:
- `agent.send(message, options)` — the one delivery primitive over the (`target` × `wakeup`) matrix. `message` is an already identified, frozen `UserMessage`; callers normally create it with `createUserMessage()` before routing begins. `SendOptions` owns only the `target` and `wakeup` policy. Each accepted FIFO occurrence receives its own `InboxItemId`, even when callers reuse a `MessageId`; `agent/inbox/enqueue`/`update` and the terminal `dequeue` or `discard` carry that complete `InboxItem`. `target: 'next-turn'` queues one independent FIFO item that, if admitted, becomes the sole ordinary prompt in its turn. `target: 'next-step'` with `wakeup: true` submits steering, while `target: 'next-step'` with `wakeup: false` injects durable context without running the model. The [one-send-one-turn Agent Note](../../../.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md) owns the turn rationale.
- `agent.reserveTurnAdmission()` — synchronously reserve the idle boundary before any queued waking prompt can claim its turn. An accepted prompt, including a same-tick pending wake, has right of way and makes reservation return `undefined`. Later sends keep their ordinary IDs, FIFO placement, and wakeup facts while held; `acceptsNextStep` remains false, `inject()` is not withheld, `whenIdle()` counts the reservation as activity, and the returned release is idempotent. This narrow coordination capability lets standalone durable operations such as manual compaction finish and flush before queued prompts derive from the session.
- `agent.updateInbox(itemId, action)` — synchronously edits or removes one still-pending queued occurrence. Edit keeps its `MessageId`, `InboxItemId`, source, and FIFO position while replacing frozen content; remove emits the occurrence's terminal discard. Steering and claimed occurrences return `not-found`.
- `agent.updateInbox(itemId, action)` — synchronously edits, removes, or strictly steers one still-pending queued occurrence. Edit keeps its `MessageId`, `InboxItemId`, source, and FIFO position while replacing frozen content; remove emits the occurrence's terminal discard. Strict steer requires `acceptsNextStep`, ends the queued occurrence, and accepts the same immutable message as a new steering occurrence with a new `InboxItemId`; a closed window returns `steer-unavailable` without mutation. Pending steering and claimed occurrences return `not-found`.
- `agent.followup(input)` — the `next-turn`/wakeup preset of `send()`: queue an ordinary follow-up turn and wake the driver.
- `agent.steer(input)` — the `next-step`/wakeup preset: submit one identified message and receive its `SteeringReceipt`. During prompt admission or an open turn, the message stages for the next safe request boundary without dispatching `agent/prompt-submit`; outside that acceptance window, it becomes a woken queued prompt. `receipt.outcome` resolves `admitted` with the turn and step only after the loop logs the message, captures it in immutable request history, and commits `step/start`. A turn-concluding tool result, broad cancellation, disposal, or pre-admission failure resolves it `rejected`; `cancel(..., { keepInbox: true })` and non-terminal routing preserve pending delivery. Reliable callers await the receipt, while best-effort UI steering may ignore it.
- `agent.inject(input)` — the `next-step`/no-wakeup preset: append model-facing context without running the model; the next request sees a verbatim user-role message whose provenance is carried by the required `input.source`. During prompt admission or an open turn, injection waits in the outbox for the next safe boundary. Outside that acceptance window, it appends immediately without opening a turn; a context-only admission batch takes this fallback if admission closes without a turn, while context staged beside steering remains pending with it. Persistence reacts to `session/event` independently. Injection emits no `agent/inbox/*` event.

View File

@@ -62,7 +62,7 @@ Agent *创建* 由实现 `AgentFactory` 的插件(`dsh-agent-loop`)提供,
- `agent.send(message, options)`:覆盖(`target` × `wakeup`)矩阵的唯一投递原语。`message` 是已有标识且已冻结的 `UserMessage`;调用方通常会在开始路由前使用 `createUserMessage()` 创建它。`SendOptions` 只持有 `target``wakeup` 策略。每次获准进入 FIFO 的项都会获得独立的 `InboxItemId`,即使调用方复用了同一个 `MessageId``agent/inbox/enqueue``update` 及终态 `dequeue``discard` 都会携带这一完整 `InboxItem``target: 'next-turn'` 排队一条独立 FIFO 项,获准后成为其轮次中唯一的普通提示词。`target: 'next-step'``wakeup: true` 提交 steering中途引导`target: 'next-step'``wakeup: false` 注入持久上下文,不运行模型。轮次原理由 [one-send-one-turn Agent Note](../../../.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md)拥有。
- `agent.reserveTurnAdmission()`:在任何已排队唤醒提示词认领其轮次之前,同步预留空闲边界。已获接纳的提示词拥有优先权,包括同一 tick 内仍在等待唤醒的项,此时预留返回 `undefined`。预留期间,之后发送的项保留其普通 ID、FIFO 位置与唤醒信息;`acceptsNextStep` 保持 false`inject()` 不受阻塞,`whenIdle()` 将该预留计为活动返回的释放函数可幂等调用。这项范围有限的协调能力使手动压缩compaction等独立持久操作能够在排队提示词从会话派生内容前完成并 flush。
- `agent.updateInbox(itemId, action)`:同步编辑移除一个仍处于待处理状态的 queued 入队项。编辑会替换已冻结的内容,同时保留其 `MessageId``InboxItemId`、来源与 FIFO 位置;移除会发出该项的终态 discard。steering 和已被认领的项会返回 `not-found`
- `agent.updateInbox(itemId, action)`:同步编辑移除一个仍处于待处理状态的 queued 入队项,或对其执行严格 steering。编辑会替换已冻结的内容,同时保留其 `MessageId``InboxItemId`、来源与 FIFO 位置;移除会发出该项的终态 discard。严格 steering 要求 `acceptsNextStep` 为 true它会结束 queued 单次入队项,并把同一条不可变消息接受为新的 steering 单次入队项,后者使用新的 `InboxItemId`。窗口关闭时返回 `steer-unavailable`,且不做任何变更。待处理 steering 和已被认领的项会返回 `not-found`
- `agent.followup(input)``send()``next-turn`wakeup 预设:排队一个普通后续轮次并唤醒驱动器。
- `agent.steer(input)``next-step`wakeup 预设:提交一条已有标识的消息,并取得其 `SteeringReceipt`。提示词接纳期间或轮次打开时,消息会为下一个安全请求边界暂存,且不分发 `agent/prompt-submit`;该接收窗口之外则成为会唤醒驱动器的排队提示词。只有循环记录消息、将其捕获到不可变请求历史并提交 `step/start` 后,`receipt.outcome` 才会解析为 `admitted`并附带轮次与步骤。结束轮次的工具结果、广义取消、dispose资源释放或准入前故障会使其解析为 `rejected``cancel(..., { keepInbox: true })` 和非终止型路由会保留待处理投递。需要可靠投递的调用方应等待回执;尽力执行的 UI steering 可以忽略它。
- `agent.inject(input)``next-step`/不唤醒预设:追加面向模型的上下文而不运行模型;下一次请求会看到一条逐字的 user role 消息,其来源由必填的 `input.source` 携带。提示词接纳期间或轮次打开时,注入会在 outbox 中等待下一个安全边界。该接收窗口之外,它会立即追加而不开启轮次;如果接纳结束却未开启轮次,仅含上下文的接纳批次会采用这一回退,而与 steering 一同暂存的上下文则会随其继续待处理。持久化独立地响应 `session/event`。注入不发出 `agent/inbox/*` 事件。

View File

@@ -54,9 +54,10 @@ export interface InboxItem {
export type InboxAction =
| { readonly kind: 'edit'; readonly content: ContentBlock[] }
| { readonly kind: 'remove' }
| { readonly kind: 'steer' }
/** Result of applying an inbox action at the synchronous ownership boundary. */
export type InboxActionResult = 'applied' | 'not-found'
export type InboxActionResult = 'applied' | 'not-found' | 'steer-unavailable'
/** Final admission outcome for one call to {@link Agent.steer}. */
export type SteeringOutcome =
@@ -209,10 +210,13 @@ export interface Agent {
/**
* Mutate one still-pending queued occurrence synchronously. Editing preserves
* the message identity and queue position; removal publishes its terminal
* discard. Steering occurrences and driver-claimed items return `not-found`.
* discard. Steer strictly transfers the message into the current next-step
* window, or returns `steer-unavailable` without changing the queued
* occurrence. Steering occurrences and driver-claimed items return
* `not-found`.
* @param id - independently addressable queued occurrence.
* @param action - edit or remove operation.
* @returns whether the pending occurrence was found and updated.
* @param action - edit, remove, or strict steer operation.
* @returns the applied outcome or the reason no mutation occurred.
*/
updateInbox(id: InboxItemId, action: InboxAction): InboxActionResult