From 953548cd59b9c5a8b7e8551465daecee675916d7 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 17:50:30 +0800 Subject: [PATCH 01/29] feat(web): continue generation after transient request failures --- apps/web/tests/live-interactions.e2e.ts | 32 +++++++- .../client/ui-conversation/README.i18n.yaml | 4 +- packages/client/ui-conversation/README.md | 2 + packages/client/ui-conversation/README.zh.md | 2 + .../ui-conversation/src/client/apply.ts | 8 ++ .../src/client/contract/slots.ts | 9 ++- .../src/client/input/continuation.ts | 55 +++++++++++++ .../ui-conversation/src/client/input/hub.ts | 42 +++++++++- .../ui-conversation/src/client/locales.ts | 16 ++++ .../src/client/skeleton/InputBar.module.css | 5 ++ .../src/client/skeleton/InputBar.tsx | 29 +++++-- .../tests/apply-inject.client.spec.tsx | 46 +++++++++++ .../tests/continuation-fixture.client.ts | 28 +++++++ .../tests/continuation.client.spec.ts | 81 +++++++++++++++++++ .../tests/input-bar.client.spec.tsx | 40 +++++++-- .../tests/input-matrix.client.spec.tsx | 2 + .../tests/input-scenarios.client.spec.tsx | 2 + .../tests/skeleton.client.spec.tsx | 2 + .../live-interactions/continued.expected.md | 39 +++++++++ .../retry-exhausted.expected.md | 2 +- 20 files changed, 426 insertions(+), 20 deletions(-) create mode 100644 packages/client/ui-conversation/src/client/input/continuation.ts create mode 100644 packages/client/ui-conversation/tests/continuation-fixture.client.ts create mode 100644 packages/client/ui-conversation/tests/continuation.client.spec.ts create mode 100644 snapshots/web/live-interactions/continued.expected.md diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index 1eedb317fd..0a6c78c8fc 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -414,15 +414,16 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { expect(tripwire.warnings).toEqual([]) }, 120_000) - it.skipIf(MODE === 'record')('surfaces the terminal turn error after transient retries exhaust', async () => { + it.skipIf(MODE === 'record')('continues after transient retries exhaust through a localized user message', async () => { // A whole-script replacement: three throw entries cover the first request // plus both budgeted retries (patches cannot reach past the one-call // derived script). The scenario-owned policy keeps exhaustion fast and // jitter-free instead of walking the shared default's five backed-off // attempts. const failure: ReplayEntry = { kind: 'throw', chunks: [], message: 'upstream 503', code: 'SERVER' } + const derived = deriveReplayScript(parseSessionLog(await readFile(FIXTURE, 'utf8'))) await launch( - () => [failure, failure, failure], + () => [failure, failure, failure, ...derived], { mode: 'normal', maxRetries: 2, retryableCodes: ['SERVER'], backoff: { initialDelayMs: 25, maxDelayMs: 50, jitterRatio: 0 } }, ) onTestFailed(() => saveFailureShot(page, 'web-e2e-retry-exhausted')) @@ -443,6 +444,31 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { // context; the golden pins both. const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold!.workspaceCwd) await compareOrRefreshGolden(RETRY_EXHAUSTED_EXPECTED, snapshot, MODE) + await page.reload({ waitUntil: 'load' }) + const resume = page.getByRole('button', { name: 'Continue generating', exact: true }) + await resume.waitFor() + const input = page.locator('[data-composer-input]').first() + await input.fill('A different instruction') + expect(await resume.count()).toBe(0) + expect(await page.getByRole('button', { name: 'Send message', exact: true }).isEnabled()).toBe(true) + await input.fill('') + await resume.waitFor() + await page.setViewportSize({ width: 390, height: 844 }) + const bounds = await resume.boundingBox() + expect(bounds).not.toBeNull() + expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(390) + expect(await resume.isEnabled()).toBe(true) + const continued = scaffold!.whenTurnSettled() + await resume.click() + await continued + expect(turnEndReasons(sessionEvents)).toEqual(['error', 'completed']) + const userMessages = sessionEvents.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' + ? [event.data.content] : []) + expect(userMessages).toEqual([[{ type: 'text', text: PROMPT }], [{ type: 'text', text: 'continue' }]]) + expect(await resume.count()).toBe(0) + await page.setViewportSize({ width: 1440, height: 900 }) + await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'continued.expected.md'), + await captureStableAria(page, '[class*="centerCol"]', scaffold!.workspaceCwd), MODE) expect(tripwire.pageErrors).toEqual([]) expect(tripwire.warnings).toEqual([]) }, 120_000) @@ -451,7 +477,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { await assertFixtureInventory(SNAPSHOT_DIR, [ 'session.v3.jsonl', 'cancel.expected.md', 'cancel-expanded.expected.md', 'loading.expected.md', 'running-draft.expected.md', 'error-auth.expected.md', - 'retry.expected.md', 'retry-expanded.expected.md', 'retry-request.expected.md', 'retry-exhausted.expected.md', + 'retry.expected.md', 'retry-expanded.expected.md', 'retry-request.expected.md', 'retry-exhausted.expected.md', 'continued.expected.md', ]) }) }) diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index 09a0211fa7..ac94283c97 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -21,8 +21,8 @@ en: cc4dafd1aae02468 zh: be6b67724b7ac4a3 /deepseek-ai-dsh-client-ui-conversation/shell-and-standard-props: - en: c62e2e19c9680567 - zh: e05ba69f967918c0 + en: 483f64e0a313ffaa + zh: 21a243552ac05038 /deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries: en: bb1244310f5c0382 zh: 824b84077432b559 diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 3b57654892..cfe8144cec 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -76,6 +76,8 @@ The composer measures its expanded control groups after size, content, visibilit The context meter follows the [statistics-control radius](../../../docs/ui-radius.md#radius-scale). +An empty, idle ordinary Session composer offers a play action after `RATE_LIMIT`, `SERVER`, `TIMEOUT`, `TRANSPORT`, `EMPTY_RESPONSE`, or `STREAM_CLOSED` ends its latest turn. The failed turn must include admitted user input, a settled model attempt, and a loaded start. Clicking submits a normal user message in the current interface language (`继续` in Chinese, `continue` in English); drafts and attachments keep the Send action. Continued requests retain committed history and use the existing submission lifecycle. + The composer uses the shared [text-input focus policy](../ui-primitives/README.md#use-this-package) on mount, Session switching, and unlock. Identified phones and tablets require tapping the input; desktop devices retain automatic focus. On these mobile devices, a submission that empties the focused draft moves focus to the non-text composer card, dismissing the keyboard while retaining its navigation position. Rejected submissions, retained newer draft text, and another control already holding focus preserve focus. The [Lexical patch](../../../patches/lexical@0.49.0.patch) keeps caret scrolling in the same coordinate system as DOM geometry when a mobile keyboard pans the page. The context-occupancy button shows a ring and percentage below the input card, after the Session statistics. Clicking it opens the token breakdown in a panel kept inside the viewport, including when no statistics are shown; the button stays hidden until context usage and capacity are available. diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index eebbbfca94..f8a1872c79 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -76,6 +76,8 @@ Queue 预览启用空白折叠和文档边缘 trim,保留 emoji,并使用 CS 上下文占用率入口遵循[统计控件圆角规则](../../../docs/ui-radius.zh.md#radius-scale)。 +普通 Session 的输入框在空草稿且空闲时,若最新一轮以 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`、`EMPTY_RESPONSE` 或 `STREAM_CLOSED` 结束,会提供播放图标按钮。失败轮次必须包含已提交的用户输入和已落盘的模型请求尝试,且起始事件已加载。点击后按当前界面语言提交一条普通用户消息(中文为“继续”,英文为“continue”);存在草稿或附件时保留发送按钮。继续请求保留已提交历史,并沿用现有提交流程。 + composer 在挂载、切换 Session 和解除锁定时使用共享的[文字输入框聚焦策略](../ui-primitives/README.zh.md#use-this-package)。识别出的手机和平板需要点击输入框;桌面设备保留自动聚焦。在这些移动设备上,提交清空正在聚焦的草稿后,焦点移到不接受文字输入的 composer 卡片,收起键盘并保留键盘导航位置。被拒绝的提交、保留的新草稿文字,以及已经聚焦的其他控件均保留原有焦点。移动端键盘平移页面时,[Lexical 补丁](../../../patches/lexical@0.49.0.patch)让光标滚动计算与 DOM 几何使用同一坐标系。 上下文占用按钮在输入卡片下方、会话统计右侧显示圆环和百分比。点击按钮可在视口内的面板查看 token 构成,没有统计项时面板也不会越界;上下文用量和容量尚不可用时,按钮保持隐藏。 diff --git a/packages/client/ui-conversation/src/client/apply.ts b/packages/client/ui-conversation/src/client/apply.ts index b3c0d3bcd0..08b136bf69 100644 --- a/packages/client/ui-conversation/src/client/apply.ts +++ b/packages/client/ui-conversation/src/client/apply.ts @@ -78,6 +78,10 @@ const ABSENT_NOTICES = { getSnapshot: (): InputNotice | null => null, subscribe: () => () => {}, } +const ABSENT_CONTINUATION = { + getSnapshot: () => undefined, + subscribe: () => () => {}, +} const ABSENT_BLOCK = { getSnapshot: (): ComposerBlock | undefined => undefined, subscribe: () => () => {}, @@ -482,7 +486,9 @@ export function apply(ctx: Context, config: Config = Config({})): void { retryFileUpload: undefined, toggleCommandMenu: undefined, stop: undefined, + continueGeneration: undefined, hooks: { + continuation: ABSENT_CONTINUATION, stopShortcut, busyEnter: submissionPolicy.busyEnter, fileUploads: ABSENT_FILE_UPLOADS, @@ -561,7 +567,9 @@ export function apply(ctx: Context, config: Config = Config({})): void { }) }, stop: () => { stop(sessionId) }, + continueGeneration: (ending) => { inputHub.continueGeneration(sessionId, ending) }, hooks: { + continuation: inputHub.continuation(sessionId), stopShortcut, busyEnter: submissionPolicy.busyEnter, fileUploads: conversation.fileUploads, diff --git a/packages/client/ui-conversation/src/client/contract/slots.ts b/packages/client/ui-conversation/src/client/contract/slots.ts index caec4849b4..c190443fb7 100644 --- a/packages/client/ui-conversation/src/client/contract/slots.ts +++ b/packages/client/ui-conversation/src/client/contract/slots.ts @@ -14,7 +14,7 @@ import type { } from '@deepseek-ai/dsh-client-ui-slots' import type { SessionPendingInteraction } from '@deepseek-ai/dsh-client-ui-session/client' import type {} from '@deepseek-ai/dsh-client-ui-layout/client' -import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { SessionId, SessionSeq } from '@deepseek-ai/dsh-session/types' import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types' import type { ComposerBlock } from './composer-blocks.ts' import type { DraftAttachmentId, InputActions, InputNotice, InputState } from './input.ts' @@ -529,7 +529,14 @@ export interface ComposerBarInjected { retryFileUpload: ((id: DraftAttachmentId) => void) | undefined toggleCommandMenu: ((selection: EditSelection) => void) | undefined stop: (() => void) | undefined + /** + * Submit a localized continuation message for the displayed failure. + * @param ending - sequence of the displayed failed turn/end. + */ + continueGeneration: ((ending: SessionSeq) => void) | undefined hooks: { + /** Latest transient failure with admitted user input and a loaded turn start. */ + continuation: ObservableSnapshot /** Readable sequence while the fixed Stop command is registered. */ stopShortcut: ObservableSnapshot /** diff --git a/packages/client/ui-conversation/src/client/input/continuation.ts b/packages/client/ui-conversation/src/client/input/continuation.ts new file mode 100644 index 0000000000..261629c084 --- /dev/null +++ b/packages/client/ui-conversation/src/client/input/continuation.ts @@ -0,0 +1,55 @@ +/** Manual continuation of settled transient model-request failures. */ +import type { SessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' +import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store' +import type { SessionSeq } from '@deepseek-ai/dsh-session/types' + +const CONTINUABLE_FAILURES: ReadonlySet = new Set([ + 'RATE_LIMIT', 'SERVER', 'TIMEOUT', 'TRANSPORT', 'EMPTY_RESPONSE', 'STREAM_CLOSED', +]) + +/** + * Derive the latest eligible failure from the loaded history. A loaded turn + * start, admitted user message, and settled failed request are required; + * preparation failures may have consumed input without admitting it. + * @param feed - existing Session event source, including replacement and paging. + * @returns a stable source of the eligible turn/end sequence, absent otherwise. + */ +export function createContinuationSource(feed: SessionEventSource): ObservableSnapshot { + return { + getSnapshot: () => { + const { entries } = feed.getSnapshot() + let ending: SessionSeq | undefined + let admitted = false + let attempted = false + for (let index = entries.length - 1; index >= 0; index--) { + // oxlint-disable-next-line typescript/no-non-null-assertion -- index is bounded by this immutable window's length. + const entry = entries[index]! + if (entry.type === 'transient') return undefined + const event = entry.event + switch (event.type) { + case 'turn/end': + if (ending !== undefined || event.data.reason.kind !== 'error' + || !CONTINUABLE_FAILURES.has(event.data.reason.error.code)) return undefined + ending = event.seq + break + case 'turn/start': + return admitted && attempted ? ending : undefined + case 'assistant/attempt': + attempted = true + break + case 'step/start': + case 'assistant/message': + if (!attempted) return undefined + break + case 'user/message': + if (ending !== undefined && event.surfaceOp === 'append' && event.data.source.kind === 'user') admitted = true + break + default: + break + } + } + return undefined + }, + subscribe: listener => feed.subscribe(listener), + } +} diff --git a/packages/client/ui-conversation/src/client/input/hub.ts b/packages/client/ui-conversation/src/client/input/hub.ts index 02897a18c6..3c247d322e 100644 --- a/packages/client/ui-conversation/src/client/input/hub.ts +++ b/packages/client/ui-conversation/src/client/input/hub.ts @@ -11,7 +11,7 @@ import type { Context } from '@deepseek-ai/cordis' import type { ISessions, SessionBinding, SessionFace, } from '@deepseek-ai/dsh-api-session-controller/client' -import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { SessionId, SessionSeq } from '@deepseek-ai/dsh-session/types' import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client' import type { InboxState } from '@deepseek-ai/dsh-agent/types' import type { ModelSelectionProjection } from '@deepseek-ai/dsh-agent/types' @@ -25,6 +25,7 @@ import type { ComposerBlocks } from '../contract/composer-blocks.ts' import type { InputSubmitMode } from '../contract/composer-submission.ts' import type { PopupDismissFace } from './facade.ts' import { SessionInputShell } from './facade.ts' +import { createContinuationSource } from './continuation.ts' import { reportMessageSubmission } from './submission-analytics.ts' import { readConversationDraft } from '../stores.ts' @@ -57,6 +58,7 @@ interface ConversationAttachmentFace { /** Session-addressed input facade registry (SessionInputResolver face + composer-layer extras). */ export class InputHub implements SessionInputResolver { private readonly shells = new WeakMap() + private readonly continuations = new WeakMap>() /** * @param ctx - client root context (services resolved lazily per call — boot order stays free). @@ -207,6 +209,44 @@ export class InputHub implements SessionInputResolver { return this.shell(id) } + /** + * Read manual-continuation eligibility from the retained Session history. + * @param id - retained ordinary Session identity. + * @returns the identity-stable eligibility source. + */ + continuation(id: SessionId): ObservableSnapshot { + const binding = this.sessions().binding(id) + if (binding === undefined) throw new Error(`conversation.input: session "${id}" resolved no binding`) + let source = this.continuations.get(binding) + if (source === undefined) { + source = createContinuationSource(binding.eventSource) + this.continuations.set(binding, source) + } + return source + } + + /** + * Submit localized continuation text through the ordinary composer. Stale + * failures, active work, and drafts keep their current input unchanged. + * @param id - retained ordinary Session identity. + * @param ending - failure the displayed button addresses. + */ + continueGeneration(id: SessionId, ending: SessionSeq): void { + const binding = this.sessions().binding(id) + if (binding === undefined || this.continuation(id).getSnapshot() !== ending) return + const session = binding.session.getSnapshot() + const shell = this.shellFor(binding) + const input = shell.snapshot + const inbox = binding.session.projections.faceOf('inbox').getSnapshot() as InboxState | undefined + if (session.running || session.removed || session.openState !== 'open' || session.subagent !== null + || session.pendingSubmissions.length > 0 || input.phase !== 'plain' + || input.draft.trim() !== '' || input.attachmentIds.length > 0 + || (inbox?.['next-turn'].length ?? 0) > 0 || (inbox?.['next-step'].length ?? 0) > 0 + || this.conversation().blocks.storeFor(id).getSnapshot() !== undefined) return + shell.setDraft(this.t('input.continueMessage')) + shell.submit('queue', 'click') + } + /** * Query file intake without creating a Session input. * @param id - target Session. diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts index 5606842886..c9dc074e1c 100644 --- a/packages/client/ui-conversation/src/client/locales.ts +++ b/packages/client/ui-conversation/src/client/locales.ts @@ -28,6 +28,8 @@ export const zh = { 'input.file': '文件', 'input.stop': '停止生成', 'input.send': '发送消息', + 'input.continue': '继续生成', + 'input.continueMessage': '继续', 'input.sending': '正在发送消息', 'input.send.queue': '排队发送', 'input.send.steer': '插话发送', @@ -412,6 +414,8 @@ export const en = { 'input.file': 'File', 'input.stop': 'Stop generating', 'input.send': 'Send message', + 'input.continue': 'Continue generating', + 'input.continueMessage': 'continue', 'input.sending': 'Sending message', 'input.send.queue': 'Queue message', 'input.send.steer': 'Steer message', @@ -795,6 +799,8 @@ export const ja = { 'input.file': 'ファイル', 'input.stop': '生成を停止', 'input.send': 'メッセージを送信', + 'input.continue': '生成を続ける', + 'input.continueMessage': '続けて', 'input.sending': 'メッセージを送信中', 'input.send.queue': 'メッセージをキューに追加', 'input.send.steer': '実行中の指示にメッセージを追加', @@ -1176,6 +1182,8 @@ export const es = { 'input.file': 'Archivo', 'input.stop': 'Detener generación', 'input.send': 'Enviar mensaje', + 'input.continue': 'Continuar generando', + 'input.continueMessage': 'continúa', 'input.sending': 'Enviando mensaje', 'input.send.queue': 'mensaje de cola', 'input.send.steer': 'Redirigir mensaje', @@ -1559,6 +1567,8 @@ export const fr = { 'input.file': 'Fichier', 'input.stop': 'Arrêter la génération', 'input.send': 'Envoyer le message', + 'input.continue': 'Continuer la génération', + 'input.continueMessage': 'continue', 'input.sending': 'Envoi du message', 'input.send.queue': "Message de file d'attente", 'input.send.steer': 'Guider avec ce message', @@ -1942,6 +1952,8 @@ export const de = { 'input.file': 'Datei', 'input.stop': 'Generierung stoppen', 'input.send': 'Nachricht senden', + 'input.continue': 'Generierung fortsetzen', + 'input.continueMessage': 'weiter', 'input.sending': 'Nachricht wird gesendet', 'input.send.queue': 'Warteschlangennachricht', 'input.send.steer': 'Mit Nachricht lenken', @@ -2325,6 +2337,8 @@ export const ru = { 'input.file': 'Файл', 'input.stop': 'Остановить генерацию', 'input.send': 'Отправить сообщение', + 'input.continue': 'Продолжить генерацию', + 'input.continueMessage': 'продолжай', 'input.sending': 'Отправка сообщения', 'input.send.queue': 'Сообщение в очереди', 'input.send.steer': 'Направить сообщение', @@ -2708,6 +2722,8 @@ export const pt = { 'input.file': 'Arquivo', 'input.stop': 'Parar geração', 'input.send': 'Enviar mensagem', + 'input.continue': 'Continuar gerando', + 'input.continueMessage': 'continue', 'input.sending': 'Enviando mensagem', 'input.send.queue': 'Mensagem de fila', 'input.send.steer': 'Orientar com mensagem', diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.module.css b/packages/client/ui-conversation/src/client/skeleton/InputBar.module.css index 7d6ae29adb..26203b9d11 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.module.css +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.module.css @@ -407,6 +407,11 @@ transform: translateY(-2px); } +/* The triangle fills half its canvas; match the 14px arrow without widening the grid track. */ +.continueIcon { + transform: scale(1.75); +} + @media (hover: hover) { .primary:hover:not(:disabled) { background: var(--dsw-alias-button-info-hover); diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx index 09a43ec6fc..9d46a571d3 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx @@ -17,7 +17,7 @@ import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useStat import type { ChangeEvent, KeyboardEvent, MouseEvent } from 'react' import clsx from 'clsx' import { - focusWithoutRing, IconPlusOutlineMedium, IconWarningOutlineRegular, isMobileDevice, Toast, Tooltip, + focusWithoutRing, IconPlusOutlineMedium, IconTriangleRightFillRegular, IconWarningOutlineRegular, isMobileDevice, Toast, Tooltip, } from '@deepseek-ai/dsh-client-ui-primitives' // Type-only: the `plan` projection key merge (the TodoDock posture — the // composer reads a host-computed value; the domain owns the key). @@ -46,9 +46,9 @@ export type InputBarProps = ComposerBarProps export const InputBar = memo(function InputBar({ useSession, useInput, inputActions, keyboard, addFiles, removeAttachment, resolveDraftAttachments, retryFileUpload, - toggleCommandMenu, stop, t, + toggleCommandMenu, stop, continueGeneration, t, renderSlot, useBusyEnter, useFileUploads, useNotices, useLexicon, useMenuLauncher, useStopShortcut, - useAccess, + useAccess, useContinuation, useProjection, sessionId, variant, disabled: inert = false, blocked, workspacePickerOpen = false, onRequestWorkspace, placeholder, accessory, @@ -70,6 +70,9 @@ export const InputBar = memo(function InputBar({ useEffect(() => { setActivity(false) }, [sessionId]) const promptError = useSession(s => s.promptError) ?? null const running = useSession(s => s.running) ?? false + const continuation = useContinuation(value => value) + const historyOpen = useSession(s => s.openState === 'open') ?? false + const submissionPending = useSession(s => s.pendingSubmissions.length > 0) ?? false const subagent = useSession(s => s.subagent) ?? null const removed = useSession(s => s.removed) ?? false // Plan mode swaps the composer placeholder (the projection is the folded @@ -362,8 +365,12 @@ export const InputBar = memo(function InputBar({ // exposes Stop independently, and so does a running session while a // message waits for the Host. const primaryStops = running && subagent === null && !sending && (empty || blocked !== undefined) + const primaryContinues = !running && subagent === null && empty && continuation !== undefined + && historyOpen && !submissionPending && (input?.queue.length ?? 0) === 0 + && !disabled && input.phase === 'plain' && continueGeneration !== undefined // Disabled native buttons may omit mouseleave; their tooltip must close from state. - const primaryDisabled = primaryStops ? stopAction === undefined : empty || disabled || machineBusy || uploadsPending + const primaryDisabled = primaryStops ? stopAction === undefined + : (!primaryContinues && empty) || disabled || machineBusy || uploadsPending const interruptible = running && (continuable || (sending && subagent === null)) const primarySubmitMode = resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable) const plainMessageDraft = !empty && input?.phase === 'plain' && !draft.trimStart().startsWith('/') @@ -371,14 +378,20 @@ export const InputBar = memo(function InputBar({ ? t('input.stop') : sending ? t('input.sending') - : running && steeringAvailable && !disabled && !uploadsPending && plainMessageDraft - ? t(primarySubmitMode === 'steer' ? 'input.send.steer' : 'input.send.queue') - : t('input.send') + : primaryContinues + ? t('input.continue') + : running && steeringAvailable && !disabled && !uploadsPending && plainMessageDraft + ? t(primarySubmitMode === 'steer' ? 'input.send.steer' : 'input.send.queue') + : t('input.send') const onPrimary = (): void => { if (primaryStops) { stopAction?.() return } + if (primaryContinues) { + if (!primaryDisabled) continueGeneration(continuation) + return + } if (keyboard === undefined) return // absent machine: the button is disabled /* v8 ignore next -- defensive: the primary button is disabled for empty, disabled, and pending-upload states. */ if (!empty && !disabled && !machineBusy && !uploadsPending) keyboard.submit(primarySubmitMode, 'click') @@ -560,6 +573,8 @@ export const InputBar = memo(function InputBar({ + ) : primaryContinues ? ( + ) : ( diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index 523fcffaae..dc69dad5bc 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -23,6 +23,8 @@ import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types' import { createConversationStore } from '../src/client/stores.ts' import type { SessionInputShell } from '../src/client/input/facade.ts' import { RemoteError } from '@deepseek-ai/dsh-client-test-runtime' +import { SessionSeq } from '@deepseek-ai/dsh-session/types' +import { continuationTurn } from './continuation-fixture.client.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap { @@ -158,6 +160,50 @@ async function bench(renderQuote = false) { } describe('Conversation inject API', () => { + it.each([['zh', '继续'], ['en', 'continue']])('submits one continuation in the current %s locale', async (locale, message) => { + const b = await bench() + onTestFinished(() => b.runtime.dispose()) + await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } })) + const composer = b.composerApi(ROOT) + expect(b.composerApi(ROOT).hooks.continuation).toBe(composer.hooks.continuation) + expect(composer.hooks.continuation.getSnapshot()).toBe(5) + b.runtime.ctx.locale.setLocale(locale) + const pending = Promise.withResolvers>>() + onTestFinished(() => { pending.resolve({ ok: true, value: { accepted: true } }) }) + b.sessionFake.prompt.mockImplementationOnce(() => pending.promise) + composer.continueGeneration!(SessionSeq(5)) + composer.continueGeneration!(SessionSeq(5)) + expect(composer.keyboard!.snapshot.phase).toBe('sending') + await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce() }) + expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([{ type: 'text', text: message }]) + expect(b.sessionFake.prompt.mock.calls[0]?.[1]).toBe('queue') + pending.resolve({ ok: true, value: { accepted: true } }) + await vi.waitFor(() => { expect(composer.keyboard!.snapshot.phase).toBe('plain') }) + expect(composer.keyboard!.snapshot.draft).toBe('') + }) + + it('rechecks the addressed failure, draft, and live activity before continuing', async () => { + const b = await bench() + onTestFinished(() => b.runtime.dispose()) + await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'TIMEOUT', message: 'timeout' } })) + const composer = b.composerApi(ROOT) + const input = b.inputApi(ROOT) + composer.continueGeneration!(SessionSeq(1)) + input.actions.setDraft('Keep my new instruction') + composer.continueGeneration!(SessionSeq(5)) + expect(input.state.getSnapshot().draft).toBe('Keep my new instruction') + input.actions.setDraft('') + await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.running = true }) + composer.continueGeneration!(SessionSeq(5)) + await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.running = false }) + await b.runtime.sessions.appendEvent(ROOT, { + type: 'event', event: { type: 'turn/start', seq: SessionSeq(6), time: 1, data: { turn: 2 } }, + }) + composer.continueGeneration!(SessionSeq(5)) + expect(input.state.getSnapshot().draft).toBe('') + expect(b.sessionFake.prompt).not.toHaveBeenCalled() + }) + it('renders literal quote text through the actual Factory and keyed node registration', async () => { const b = await bench(true) onTestFinished(() => b.runtime.dispose()) diff --git a/packages/client/ui-conversation/tests/continuation-fixture.client.ts b/packages/client/ui-conversation/tests/continuation-fixture.client.ts new file mode 100644 index 0000000000..20dc42fdc9 --- /dev/null +++ b/packages/client/ui-conversation/tests/continuation-fixture.client.ts @@ -0,0 +1,28 @@ +/** Admitted input and a settled turn for manual-continuation tests. */ +import type { SessionLiveEventEntry } from '@deepseek-ai/dsh-api-session-controller/client' +import { createUserMessage } from '@deepseek-ai/dsh-llm' +import { SessionSeq } from '@deepseek-ai/dsh-session/types' +import type { TurnEndReason } from '@deepseek-ai/dsh-session/types' + +/** + * Build one settled turn with ordinary user input. + * @param reason - recorded turn ending. + * @param number - turn number and corresponding event offset. + * @returns ordered durable entries. + */ +export function continuationTurn(reason: TurnEndReason, number = 1): SessionLiveEventEntry[] { + const offset = (number - 1) * 6 + return [ + { type: 'event', event: { type: 'turn/start', seq: SessionSeq(offset), time: 0, data: { turn: number } } }, + { type: 'event', event: { type: 'step/start', seq: SessionSeq(offset + 1), time: 0, data: { turn: number, step: 1 } } }, + { type: 'event', event: { + type: 'user/message', seq: SessionSeq(offset + 2), time: 0, surfaceOp: 'append', + data: createUserMessage({ content: [{ type: 'text', text: 'task' }], source: { kind: 'user' } }), + } }, + { type: 'event', event: { + type: 'assistant/attempt', seq: SessionSeq(offset + 3), time: 0, data: { turn: number, step: 1, stream: [] }, + } }, + { type: 'event', event: { type: 'step/end', seq: SessionSeq(offset + 4), time: 0, data: { turn: number, step: 1 } } }, + { type: 'event', event: { type: 'turn/end', seq: SessionSeq(offset + 5), time: 0, data: { turn: number, reason } } }, + ] +} diff --git a/packages/client/ui-conversation/tests/continuation.client.spec.ts b/packages/client/ui-conversation/tests/continuation.client.spec.ts new file mode 100644 index 0000000000..ba101e7737 --- /dev/null +++ b/packages/client/ui-conversation/tests/continuation.client.spec.ts @@ -0,0 +1,81 @@ +import { describe, expect, it, vi } from 'vitest' +import { MutableSessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' +import type { SessionLiveEventEntry } from '@deepseek-ai/dsh-api-session-controller/client' +import { SessionSeq } from '@deepseek-ai/dsh-session/types' +import type { TurnEndReason } from '@deepseek-ai/dsh-session/types' +import { createContinuationSource } from '../src/client/input/continuation.ts' +import { continuationTurn as turn } from './continuation-fixture.client.ts' + +const failed = (code: string): TurnEndReason => ({ kind: 'error', error: { code, message: code } }) + +describe('manual generation continuation', () => { + it.each(['RATE_LIMIT', 'SERVER', 'TIMEOUT', 'TRANSPORT', 'EMPTY_RESPONSE', 'STREAM_CLOSED'])( + 'offers continuation for an admitted request ending with %s', (code) => { + const feed = new MutableSessionEventSource() + feed.replace(turn(failed(code)), false) + expect(createContinuationSource(feed).getSnapshot()).toBe(5) + }, + ) + + it.each(['AUTH', 'QUOTA', 'ACCOUNT_QUOTA', 'MISSING_CREDENTIAL', 'INVALID_CREDENTIAL', 'UNKNOWN_MODEL', + 'INVALID_REQUEST', 'CONTEXT_WINDOW_EXCEEDED', 'TOOL_ACTIVATION_FAILED', 'MALFORMED_RESPONSE', 'PI_AI_ERROR', 'UNKNOWN'])( + 'leaves %s to its own recovery action', (code) => { + const feed = new MutableSessionEventSource() + feed.replace(turn(failed(code)), false) + expect(createContinuationSource(feed).getSnapshot()).toBeUndefined() + }, + ) + + it.each([ + { kind: 'completed' }, { kind: 'max-tokens' }, { kind: 'blocked' }, + { kind: 'interrupted' }, { kind: 'forked' }, { kind: 'aborted', reason: { kind: 'user' } }, + ])('leaves a $kind turn unchanged', (reason) => { + const feed = new MutableSessionEventSource() + feed.replace(turn(reason), false) + expect(createContinuationSource(feed).getSnapshot()).toBeUndefined() + }) + + it('requires user input from the failed turn to have entered model history', () => { + const feed = new MutableSessionEventSource() + const source = createContinuationSource(feed) + const entries = turn(failed('TIMEOUT'), 2) + feed.replace([...turn({ kind: 'completed' }), ...entries.filter(entry => entry.event.type !== 'user/message')], false) + expect(source.getSnapshot()).toBeUndefined() + const user = entries[2]! + if (user.event.type !== 'user/message') throw new Error('expected user input') + const replacement: SessionLiveEventEntry = { type: 'event', event: { + ...user.event, surfaceOp: { op: 'replace', startSeq: SessionSeq(0), endSeq: SessionSeq(1) }, + } } + feed.replace(entries.map(entry => entry === user ? replacement : entry), false) + expect(source.getSnapshot()).toBeUndefined() + }) + + it('keeps failures before request dispatch in their preparation flow', () => { + const feed = new MutableSessionEventSource() + const source = createContinuationSource(feed) + feed.replace(turn(failed('TIMEOUT')).filter(entry => entry.event.type !== 'assistant/attempt'), false) + expect(source.getSnapshot()).toBeUndefined() + }) + + it('restores eligibility from paging and replacement, and retires it when a later turn starts', () => { + const feed = new MutableSessionEventSource() + const source = createContinuationSource(feed) + const entries = turn(failed('SERVER')) + feed.replace(entries.slice(1), true) + expect(source.getSnapshot()).toBeUndefined() + feed.prepend(entries.slice(0, 1), false) + expect(source.getSnapshot()).toBe(5) + expect(createContinuationSource(feed).getSnapshot()).toBe(5) + const listener = vi.fn() + const unsubscribe = source.subscribe(listener) + feed.append({ type: 'event', event: { type: 'turn/start', seq: SessionSeq(6), time: 1, data: { turn: 2 } } }) + expect(listener).toHaveBeenCalledOnce() + expect(source.getSnapshot()).toBeUndefined() + unsubscribe() + feed.replace(turn({ kind: 'completed' }), false) + expect(listener).toHaveBeenCalledOnce() + expect(source.getSnapshot()).toBeUndefined() + feed.replace([], false) + expect(source.getSnapshot()).toBeUndefined() + }) +}) diff --git a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx index a657abf806..0ba4d5f288 100644 --- a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx @@ -25,7 +25,7 @@ import type { SessionListState, SessionSnapshot } from '@deepseek-ai/dsh-api-ses import type { ContextPressureProjection } from '@deepseek-ai/dsh-token-meter/client' import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts' import type { Context } from '@deepseek-ai/cordis' -import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { SessionId, SessionSeq } from '@deepseek-ai/dsh-session/types' import type { SubmitOutcome } from '../src/client/contract/input.ts' import { SessionInputShell } from '../src/client/input/facade.ts' import { $replaceDetectSpanWithText, $selectDetectSpan } from '../src/client/input/editor/span-map.ts' @@ -81,6 +81,7 @@ interface BenchOptions { } draft?: string running?: boolean + continuation?: SessionSeq subagent?: Exclude disabled?: boolean inert?: boolean @@ -161,6 +162,8 @@ function bench(over?: BenchOptions) { if (over?.draft !== undefined && over.draft !== '') shell.setDraft(over.draft) if (over?.attachments !== undefined) shell.addAttachments(over.attachments.map(attachment => attachment.id)) const stop = vi.fn() + const continueGeneration = vi.fn() + const continuation = createSnapshotStore(over?.continuation) const removeAttachment = vi.fn((id: DraftAttachmentId) => { shell.removeAttachment(id) }) const menuLauncher = createSnapshotStore(over?.commandMenuOpen === true ? 'command' : null) const busyEnter = createSnapshotStore<'queue' | 'steer'>(over?.busyEnter ?? 'queue') @@ -215,6 +218,8 @@ function bench(over?: BenchOptions) { }), toggleCommandMenu: over?.toggleCommandMenu ?? vi.fn(), useBusyEnter: bindSnapshotSelector(busyEnter), + continueGeneration, + useContinuation: bindSnapshotSelector(continuation), useStopShortcut: bindSnapshotSelector(stopShortcut), useNotices: bindSnapshotSelector(shell.notices), useLexicon: bindSnapshotSelector(shell.lexicon), @@ -252,20 +257,45 @@ function bench(over?: BenchOptions) { const plainMessageDraft = sendableDraft && !uploadsPending && !(over?.draft?.trimStart().startsWith('/') ?? false) const primaryLabel = primaryStops ? '停止生成' - : over?.running === true && steeringAvailable && !composerLocked && plainMessageDraft - ? (over.busyEnter === 'steer' ? '插话发送' : '排队发送') - : '发送消息' + : over?.continuation !== undefined && !sendableDraft && over.running !== true && over.subagent === undefined && !composerLocked + ? '继续生成' + : over?.running === true && steeringAvailable && !composerLocked && plainMessageDraft + ? (over.busyEnter === 'steer' ? '插话发送' : '排队发送') + : '发送消息' const button = view.container.querySelector(`button[aria-label="${primaryLabel}"]`)! const interruptButton = view.container.querySelector('button[aria-label="停止生成"]') return { view, textarea, button, interruptButton, props, sink, shell, wiring: shell, session, stop, removeAttachment, slotCalls, - menuLauncher, busyEnter, stopShortcut, + menuLauncher, busyEnter, stopShortcut, continuation, continueGeneration, steerQueue: over?.steerQueue, get placeholder() { return placeholderOf(view.container) }, get inputDisabled() { return textarea.getAttribute('aria-disabled') === 'true' }, } } +it('offers the play action only for an empty, idle composer with an eligible failure', async () => { + const b = bench({ continuation: 2 as SessionSeq }) + expect(b.button.disabled).toBe(false) + expect(b.button.querySelector('svg')).not.toBeNull() + fireEvent.click(b.button) + expect(b.continueGeneration).toHaveBeenCalledExactlyOnceWith(2) + expect(b.sink).not.toHaveBeenCalled() + await act(async () => { b.shell.setDraft('A new instruction') }) + const send = b.view.getByRole('button', { name: '发送消息' }) + fireEvent.click(send) + await vi.waitFor(() => { expect(b.sink).toHaveBeenCalledOnce() }) + expect(b.sink.mock.calls[0]?.[0]).toBe('A new instruction') + expect(b.continueGeneration).toHaveBeenCalledOnce() +}) + +it.each([ + { running: true }, { attachments: [{ kind: 'image' as const, id: 'image' as DraftAttachmentId, file: new File([], 'x.png'), previewUrl: 'blob:test' }] }, + { blocked: { reason: 'Select a model' } }, { subagent: CONTINUABLE_CHILD }, { deniedEndpoints: ['session/prompt'] }, +])('keeps the continue action unavailable with %j', (options) => { + const b = bench({ ...options, continuation: 2 as SessionSeq }) + expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() +}) + function attachmentOwner(slotCalls: readonly { key: string; owner: unknown }[]): ComposerAttachmentsOwnerProps { for (let i = slotCalls.length - 1; i >= 0; i -= 1) { const call = slotCalls[i] diff --git a/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx b/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx index 6a18cede9a..524a9af703 100644 --- a/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx @@ -84,6 +84,8 @@ function mountBar(shell: SessionInputShell, over?: { running?: boolean; disabled })), toggleCommandMenu: vi.fn(), useBusyEnter: bindSnapshotSelector(createSnapshotStore<'queue' | 'steer'>('queue')), + continueGeneration: undefined, + useContinuation: bindSnapshotSelector(createSnapshotStore(undefined)), useStopShortcut: bindSnapshotSelector(createSnapshotStore([])), useNotices: bindSnapshotSelector(shell.notices), useLexicon: bindSnapshotSelector(shell.lexicon), diff --git a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx index 380097b5d1..ab9c460af8 100644 --- a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx @@ -200,6 +200,8 @@ async function scopedBench(register?: (inputTriggers: InputTriggerService) => vo useMenuLauncher: bindSnapshotSelector(controller.launcher), renderSlot: (() => null) as InputBarProps['renderSlot'], stop: vi.fn(), + continueGeneration: undefined, + useContinuation: bindSnapshotSelector(createSnapshotStore(undefined)), useStopShortcut: bindSnapshotSelector(createSnapshotStore([])), t: makeTranslate(zh, commonZh), variant: 'composer', diff --git a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx index c061935f89..421da563ff 100644 --- a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx +++ b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx @@ -309,6 +309,8 @@ function mount( resolveDraftAttachments={() => []} toggleCommandMenu={vi.fn()} useBusyEnter={bindSnapshotSelector(createSnapshotStore<'queue' | 'steer'>('queue'))} + continueGeneration={undefined} + useContinuation={bindSnapshotSelector(createSnapshotStore(undefined))} useStopShortcut={bindSnapshotSelector(createSnapshotStore([]))} useNotices={bindSnapshotSelector(wiring.notices)} useLexicon={bindSnapshotSelector(wiring.lexicon)} diff --git a/snapshots/web/live-interactions/continued.expected.md b/snapshots/web/live-interactions/continued.expected.md new file mode 100644 index 0000000000..ab51db50fc --- /dev/null +++ b/snapshots/web/live-interactions/continued.expected.md @@ -0,0 +1,39 @@ +- banner: + - navigation "Session hierarchy": Reply with a one-sentence description + - text: Standard mode + - button "More actions" + - button "Open right sidebar" + - tablist: + - tab "Chat" [selected] + - tab "Trajectory" + - button "More conversation views" +- navigation "Turn navigation": + - button "Jump to turn 1" + - button "Jump to turn 2" +- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}} +- button "Copy" +- status: Failed +- button "Failed" [disabled] [expanded] +- group: + - status: Retried model request (2/2) · {{duration}} +- status: + - text: This turn failedupstream 503 + - code: SERVER +- text: continue {{clock}} +- button "Copy" +- status: Completed +- button "Completed in {{duration}}" +- paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. +- button "Copy" +- button "Good response" +- button "Bad response" +- button "Branch into a new conversation" +- text: {{clock}} +- textbox "Message or run a task, / commands, @ files or sessions" +- button "Add files or run commands" +- 'button "Access mode, current: Workspace Write"': Workspace Write +- button "Select model, current DeepSeek-V4-Flash": DeepSeek-V4-Flash +- button "Send message" [disabled] +- button "2 turns 2 steps · {{throughput}} tok/s": 2 turns 2 steps{{throughput}} tok/s +- button "7.9K tok · Cache hit 99%": 7.9K tokCache hit 99% +- button "6% of context used": 6% diff --git a/snapshots/web/live-interactions/retry-exhausted.expected.md b/snapshots/web/live-interactions/retry-exhausted.expected.md index 91ca93cf5e..a99b5a3cbb 100644 --- a/snapshots/web/live-interactions/retry-exhausted.expected.md +++ b/snapshots/web/live-interactions/retry-exhausted.expected.md @@ -20,5 +20,5 @@ - button "Add files or run commands" - 'button "Access mode, current: Workspace Write"': Workspace Write - button "Select model, current DeepSeek-V4-Flash": DeepSeek-V4-Flash -- button "Send message" [disabled] +- button "Continue generating" - text: 1 turns 1 steps From a8b1a9b4a8ccac657aa77c08cab5ad9c7d9a18ee Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 18:14:51 +0800 Subject: [PATCH 02/29] fix(web): balance continue icon with composer controls --- apps/web/tests/live-interactions.e2e.ts | 8 +++++--- .../src/client/skeleton/InputBar.module.css | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index 0a6c78c8fc..0e8d7a04f0 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -454,9 +454,11 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { await input.fill('') await resume.waitFor() await page.setViewportSize({ width: 390, height: 844 }) - const bounds = await resume.boundingBox() - expect(bounds).not.toBeNull() - expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(390) + // ResizeObserver collapses the model label after the viewport changes. + await expect.poll(async () => { + const bounds = await resume.boundingBox() + return bounds !== null && bounds.x + bounds.width <= 390 + }).toBe(true) expect(await resume.isEnabled()).toBe(true) const continued = scaffold!.whenTurnSettled() await resume.click() diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.module.css b/packages/client/ui-conversation/src/client/skeleton/InputBar.module.css index 26203b9d11..a2dcaca5cc 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.module.css +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.module.css @@ -407,9 +407,9 @@ transform: translateY(-2px); } -/* The triangle fills half its canvas; match the 14px arrow without widening the grid track. */ +/* Solid artwork uses a smaller footprint than the thin send arrow. */ .continueIcon { - transform: scale(1.75); + transform: scale(1.5); } @media (hover: hover) { From 38e5684d5c4b61502ca7f90fd3e08a7e0e3577ca Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 19:59:54 +0800 Subject: [PATCH 03/29] fix(web): project continuation eligibility from complete session history --- ...6-manual-generation-continuation.i18n.yaml | 22 +++ ...26-10-26-manual-generation-continuation.md | 33 +++++ ...10-26-manual-generation-continuation.zh.md | 33 +++++ apps/web/tests/live-interactions.e2e.ts | 11 ++ docs/config-catalog.md | 2 +- docs/config-catalog.zh.md | 2 +- .../guide.i18n.yaml | 16 ++ .../guide.md | 25 ++++ .../guide.zh.md | 25 ++++ .../client/ui-conversation/README.i18n.yaml | 27 ++-- packages/client/ui-conversation/README.md | 17 ++- packages/client/ui-conversation/README.zh.md | 17 ++- packages/client/ui-conversation/package.json | 9 +- .../src/client/contract/slots.ts | 2 +- .../ui-conversation/src/client/index.ts | 1 + .../src/client/input/continuation.ts | 55 ------- .../ui-conversation/src/client/input/hub.ts | 21 ++- .../ui-conversation/src/client/locales.ts | 1 + .../src/client/skeleton/InputBar.tsx | 6 +- .../ui-conversation/src/continuation-types.ts | 9 ++ .../ui-conversation/src/host/continuation.ts | 55 +++++++ packages/client/ui-conversation/src/index.ts | 13 +- .../tests/apply-inject.client.spec.tsx | 140 +++++++++++++++++- .../ui-conversation/tests/config.host.spec.ts | 32 +++- ...ture.client.ts => continuation-fixture.ts} | 4 +- .../tests/continuation.client.spec.ts | 81 ---------- .../tests/continuation.host.spec.ts | 139 +++++++++++++++++ .../ui-conversation/tsconfig.client.json | 3 +- .../client/ui-conversation/tsconfig.host.json | 13 +- pnpm-lock.yaml | 15 +- .../verify-package-readme-model-experience.ts | 1 - .../live-interactions/continued.expected.md | 2 +- 32 files changed, 646 insertions(+), 186 deletions(-) create mode 100644 .agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml create mode 100644 .agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md create mode 100644 .agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.i18n.yaml create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md delete mode 100644 packages/client/ui-conversation/src/client/input/continuation.ts create mode 100644 packages/client/ui-conversation/src/continuation-types.ts create mode 100644 packages/client/ui-conversation/src/host/continuation.ts rename packages/client/ui-conversation/tests/{continuation-fixture.client.ts => continuation-fixture.ts} (92%) delete mode 100644 packages/client/ui-conversation/tests/continuation.client.spec.ts create mode 100644 packages/client/ui-conversation/tests/continuation.host.spec.ts diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml new file mode 100644 index 0000000000..320476d704 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml @@ -0,0 +1,22 @@ +# Bilingual-pair consistency record for 2026-10-26-manual-generation-continuation.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md +/agent-note-manual-generation-continuation: + en: 327aaa1cdc63b1f5 + zh: 1147b4a23e51772c +/agent-note-manual-generation-continuation/problem: + en: 027e01e4586da32f + zh: dce2c6858ef709c1 +/agent-note-manual-generation-continuation/decision: + en: dc5f066e9c0bbb23 + zh: 6e54f59a34d8fca5 +/agent-note-manual-generation-continuation/alternatives-considered: + en: b32c1050bc0ca9de + zh: 5e950f842f944818 +/agent-note-manual-generation-continuation/consequences: + en: c825acfaae8f049d + zh: e4768dab9d8edde3 +/agent-note-manual-generation-continuation/verification: + en: bd59f8845a358dec + zh: cfcd30ca521e0111 diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md new file mode 100644 index 0000000000..8060f1bde4 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md @@ -0,0 +1,33 @@ +# Agent Note: Manual generation continuation + +Status: implemented + +English | [中文](2026-10-26-manual-generation-continuation.zh.md) + +## Problem + +Users need another turn after a model request fails. A browser page can omit the failed turn's start; streaming must not repeatedly copy loaded history. + +## Decision + +The play action submits localized ordinary user text through the existing composer, retaining admission, logging, failure drafts, and telemetry. No resume RPC, AgentLoop change, or Session event format is introduced. + +The fixed-size Host projection `conversationContinuation` recovers from complete Session history. Eligibility requires admitted ordinary input and a failed attempt, independently of the browser's 500-message page. + +The six-code manual allowlist remains independent of [automatic retry policy](../../../../packages/llm/llm-retry/README.md). Pending [injected context](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md) in `next-step` accompanies the new waking message; pending ordinary user submissions prevent duplicates. + +## Alternatives considered + +**Window scanning or entries caching.** Each revision can materialize a fresh window; caching cannot recover unloaded facts. + +**Use provider retryableCodes.** Deployment retry policies could expose authentication or other unsuitable errors as UI actions. + +**Add a resume RPC.** Ordinary submission already owns wakeup, admission, error reporting, and history. + +## Consequences + +Host activation requires `sessionProjections`; custom compositions follow the [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md). Fixed-size state avoids Client history scans without introducing a second submission lifecycle. + +## Verification + +Tests cover checkpoint recovery, long-turn paging, injected context, refusal and retained drafts, and zero Client event-window reads. The recorded Web scenario exercises failure followed by continuation. diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md new file mode 100644 index 0000000000..c7c0d9ee13 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md @@ -0,0 +1,33 @@ +# Agent Note: 手动继续生成 + +Status: implemented + +[English](2026-10-26-manual-generation-continuation.md) | 中文 + +## 问题 + +模型请求失败后,用户需要发起下一轮。浏览器分页可能不含失败轮次的起点;流式输出也不能反复复制已加载历史。 + +## 决策 + +播放按钮通过现有 composer 提交本地化的普通用户文本,沿用准入、日志、失败草稿和遥测语义。不引入 resume RPC,不修改 AgentLoop 或 Session 事件格式。 + +固定大小的 Host 投影 `conversationContinuation` 从完整 Session 历史恢复。资格要求已准入的普通用户输入和失败尝试,不依赖浏览器的 500 条消息分页。 + +六类手动继续白名单独立于[自动重试策略](../../../../packages/llm/llm-retry/README.zh.md)。`next-step` 中待处理的[注入上下文](../architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md) 随新的唤醒消息进入请求;待提交的普通用户消息仍阻止重复提交。 + +## 考虑过的替代方案 + +**窗口扫描或 entries 缓存。** 每个 revision 都可能物化新窗口;缓存无法恢复尚未加载的事实。 + +**采用提供方的 retryableCodes。** 部署重试策略可能把认证等不适合的错误也变成界面动作。 + +**新增 resume RPC。** 普通提交已经拥有唤醒、准入、错误报告和历史。 + +## 影响 + +Host 激活需要 `sessionProjections`;自定义组合按[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md) 调整。固定大小的状态避免 Client 历史扫描,也无需第二套提交生命周期。 + +## 验证 + +测试覆盖检查点恢复、长轮次分页、注入上下文、拒绝与草稿保留,以及不读取 Client 事件窗口。已录制的 Web 场景验证失败后的继续流程。 diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index 0e8d7a04f0..d1e90d0080 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -448,6 +448,16 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { const resume = page.getByRole('button', { name: 'Continue generating', exact: true }) await resume.waitFor() const input = page.locator('[data-composer-input]').first() + await input.fill('/plan ') + await input.press('Enter') + const plan = page.getByRole('button', { name: 'Plan mode on, press to turn off', exact: true }) + await plan.waitFor() + await expect.poll(() => input.textContent()).toBe('') + await plan.click() + await plan.waitFor({ state: 'hidden' }) + await resume.waitFor() + expect(await resume.isEnabled()).toBe(true) + expect(turnEndReasons(sessionEvents)).toEqual(['error']) await input.fill('A different instruction') expect(await resume.count()).toBe(0) expect(await page.getByRole('button', { name: 'Send message', exact: true }).isEnabled()).toBe(true) @@ -464,6 +474,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { await resume.click() await continued expect(turnEndReasons(sessionEvents)).toEqual(['error', 'completed']) + expect(sessionEvents.some(event => event.type === 'user/message' && event.data.source.kind === 'plan-mode')).toBe(true) const userMessages = sessionEvents.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' ? [event.data.content] : []) expect(userMessages).toEqual([[{ type: 'text', text: PROMPT }], [{ type: 'text', text: 'continue' }]]) diff --git a/docs/config-catalog.md b/docs/config-catalog.md index be0d89caf2..43726adfb5 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -4713,7 +4713,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-client-ui-brand-official` | — | [`packages/client/ui-brand-official/src/index.ts`](../packages/client/ui-brand-official/src/index.ts) | | `@deepseek-ai/dsh-client-ui-chat` | — | [`packages/client/ui-chat/src/index.ts`](../packages/client/ui-chat/src/index.ts) | | `@deepseek-ai/dsh-client-ui-commands` | — | [`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts) | -| `@deepseek-ai/dsh-client-ui-conversation` | — | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-conversation` | `sessionProjections` | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | | `@deepseek-ai/dsh-client-ui-cordis` | — | [`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts) | | `@deepseek-ai/dsh-client-ui-creation-recommendations` | — | [`packages/experimental/client-ui-creation-recommendations/src/index.ts`](../packages/experimental/client-ui-creation-recommendations/src/index.ts) | | `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` | — | [`packages/client/ui-deepseek-model-label-1017/src/index.ts`](../packages/client/ui-deepseek-model-label-1017/src/index.ts) | diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 23165f5f85..470c40d5b0 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -4715,7 +4715,7 @@ export interface Config { | `@deepseek-ai/dsh-client-ui-brand-official` | — | [`packages/client/ui-brand-official/src/index.ts`](../packages/client/ui-brand-official/src/index.ts) | | `@deepseek-ai/dsh-client-ui-chat` | — | [`packages/client/ui-chat/src/index.ts`](../packages/client/ui-chat/src/index.ts) | | `@deepseek-ai/dsh-client-ui-commands` | — | [`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts) | -| `@deepseek-ai/dsh-client-ui-conversation` | — | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-conversation` | `sessionProjections` | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | | `@deepseek-ai/dsh-client-ui-cordis` | — | [`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts) | | `@deepseek-ai/dsh-client-ui-creation-recommendations` | — | [`packages/experimental/client-ui-creation-recommendations/src/index.ts`](../packages/experimental/client-ui-creation-recommendations/src/index.ts) | | `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` | — | [`packages/client/ui-deepseek-model-label-1017/src/index.ts`](../packages/client/ui-deepseek-model-label-1017/src/index.ts) | diff --git a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.i18n.yaml new file mode 100644 index 0000000000..50094c2b2c --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md +/: + en: cd293f657bb1ab7a + zh: 7d942448b488e22c +/provide-session-projections-for-conversation: + en: 6ec06cc57348d333 + zh: 86665b791e3b2e02 +/provide-session-projections-for-conversation/change: + en: b0048aafcb91d8a5 + zh: c913215a68c5ff5b +/provide-session-projections-for-conversation/migration: + en: e06d674bafc37abe + zh: b68409bfbc936d19 diff --git a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md new file mode 100644 index 0000000000..52fa806c20 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md @@ -0,0 +1,25 @@ +--- +kind: upgrade-guide +description: "Custom Conversation compositions must provide sessionProjections to activate the Host plugin." +--- +# Provide Session projections for Conversation + +English | [中文](guide.zh.md) + +## Change + +The Host entry of `@deepseek-ai/dsh-client-ui-conversation` requires the `sessionProjections` service to publish manual continuation eligibility. Custom compositions that mounted Conversation without a projection provider must add one. The shipped Web and Desktop compositions already provide it. Session logs do not require migration. + +## Migration + +1. Check the custom profile's bundles and `cordis.patch.yml` for an existing `@deepseek-ai/dsh-session-projection` entry. Keep that provider when present. +2. If the composition has none, add this row to the profile's `cordis.patch.yml`: + + ```yaml + - id: session-projection + name: '@deepseek-ai/dsh-session-projection' + ``` + +3. Restart the profile and verify that the Conversation Host entry activates. After a supported model-request failure, an empty, idle ordinary composer must offer the play action. + +See the [Conversation reference](../../../../packages/client/ui-conversation/README.md#shell-and-standard-props) for the supported failures and submission behavior. diff --git a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md new file mode 100644 index 0000000000..1277f31bf5 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md @@ -0,0 +1,25 @@ +--- +kind: upgrade-guide +description: "自定义 Conversation 组合必须提供 sessionProjections 才能激活 Host 插件。" +--- +# 为 Conversation 提供 Session 投影 + +[English](guide.md) | 中文 + +## 变更 + +`@deepseek-ai/dsh-client-ui-conversation` 的 Host 入口需要 `sessionProjections` 服务来发布手动继续资格。挂载 Conversation 却没有投影提供方的自定义组合必须补上提供方。随产品提供的 Web 和 Desktop 组合已经提供它。Session 日志不需要迁移。 + +## 迁移 + +1. 检查自定义 profile 的 bundle 和 `cordis.patch.yml` 中是否已有 `@deepseek-ai/dsh-session-projection` entry;存在时保留该提供方。 +2. 如果组合中没有该提供方,在 profile 的 `cordis.patch.yml` 中添加以下条目: + + ```yaml + - id: session-projection + name: '@deepseek-ai/dsh-session-projection' + ``` + +3. 重启 profile 并确认 Conversation Host 入口已激活。支持的模型请求失败后,空草稿且空闲的普通 composer 应提供播放按钮。 + +支持的失败类型和提交行为见 [Conversation 参考](../../../../packages/client/ui-conversation/README.zh.md#shell-and-standard-props)。 diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index ac94283c97..2b4fd0c5db 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -21,20 +21,29 @@ en: cc4dafd1aae02468 zh: be6b67724b7ac4a3 /deepseek-ai-dsh-client-ui-conversation/shell-and-standard-props: - en: 483f64e0a313ffaa - zh: 21a243552ac05038 + en: ac170c9895e17840 + zh: c7603d365134ce89 /deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries: en: bb1244310f5c0382 zh: 824b84077432b559 /deepseek-ai-dsh-client-ui-conversation/model-experience: - en: e0a05b73ba3e4318 - zh: dc85ab8433bb4456 -/deepseek-ai-dsh-client-ui-conversation/model-experience/kv-cache-effect: - en: 9f4e0da7c8addf88 - zh: 1a33d9981af75643 + en: 215e7ba838619b7b + zh: a311da3843709f90 +/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-message: + en: 30f3836663c881ca + zh: ee3e1d1df8603fad +/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-message/what-the-model-sees: + en: d566958ff2f23584 + zh: f6ebea015ba61872 +/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-message/token-effect: + en: 0ad06a5163a62c50 + zh: d25b93fcb2783566 +/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-message/kv-cache-effect: + en: 326e23f5913e3fc0 + zh: 865969505814e0af /deepseek-ai-dsh-client-ui-conversation/known-limitations-and-deferred-work: - en: 38d243630d0e4c38 - zh: a58cdc35b664020b + en: d76daa90b6d6f600 + zh: 37a9f7d402b2552b /deepseek-ai-dsh-client-ui-conversation/known-limitations-and-deferred-work/dev-note: en: e67f633dc6fe7773 zh: e88aeb617ffb74ea diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index cfe8144cec..eadcdf2467 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -76,7 +76,9 @@ The composer measures its expanded control groups after size, content, visibilit The context meter follows the [statistics-control radius](../../../docs/ui-radius.md#radius-scale). -An empty, idle ordinary Session composer offers a play action after `RATE_LIMIT`, `SERVER`, `TIMEOUT`, `TRANSPORT`, `EMPTY_RESPONSE`, or `STREAM_CLOSED` ends its latest turn. The failed turn must include admitted user input, a settled model attempt, and a loaded start. Clicking submits a normal user message in the current interface language (`继续` in Chinese, `continue` in English); drafts and attachments keep the Send action. Continued requests retain committed history and use the existing submission lifecycle. +An empty, idle ordinary Session composer offers a play action after `RATE_LIMIT`, `SERVER`, `TIMEOUT`, `TRANSPORT`, `EMPTY_RESPONSE`, or `STREAM_CLOSED` ends its latest turn. The failed turn must include admitted ordinary user input and a settled failed model attempt. Clicking submits a normal user message in the current interface language; drafts and attachments keep the Send action. Continued requests retain committed history and use the existing submission lifecycle. + +The Host requires `sessionProjections` and publishes `conversationContinuation`; eligibility is restored from Session history independently of the browser's loaded history pages. The composer uses the shared [text-input focus policy](../ui-primitives/README.md#use-this-package) on mount, Session switching, and unlock. Identified phones and tablets require tapping the input; desktop devices retain automatic focus. On these mobile devices, a submission that empties the focused draft moves focus to the non-text composer card, dismissing the keyboard while retaining its navigation position. Rejected submissions, retained newer draft text, and another control already holding focus preserve focus. The [Lexical patch](../../../patches/lexical@0.49.0.patch) keeps caret scrolling in the same coordinate system as DOM geometry when a mobile keyboard pans the page. @@ -187,11 +189,19 @@ The selector must be a pure function of the owner currency. Its non-null return ## Model Experience -None, as this package renders browser state and sends user-admitted inputs through Session Controller APIs without constructing model requests. +### Manual continuation message + +#### What the model sees + +The play action submits one ordinary user message using the interface language selected at the click: Chinese `继续`, English/French/Portuguese `continue`, Japanese `続けて`, Spanish `continúa`, German `weiter`, or Russian `продолжай`. The admitted message is stored in the Session log and included in subsequent model history; the action does not reconstruct text from the failed attempt. + +#### Token effect + +Each continuation admitted into model history adds its short localized text and the ordinary user-message overhead. The exact token count depends on the model tokenizer. A submission refused or failed before admission adds no continuation text to model history. #### KV Cache effect -None; Conversation assembly and browser input state do not alter provider-side prompt caching. +The continuation message extends retained history without replacing earlier messages or prompt sections. It preserves an already-reusable prefix; provider cache availability, reuse policy, and eviction remain outside this package's control. ## Known Limitations and Deferred Work @@ -199,6 +209,7 @@ None; Conversation assembly and browser input state do not alter provider-side p - **Only registered targets can render** — the shell deliberately has no implicit fallback target beyond the registered `chat` preference. - **Factory occurrences inherit their render-position Session** — `conversation.content` does not accept an independently addressed Session; that requires a separate Session-provider capability. +- **Manual continuation uses a fixed product allowlist** — the supported failure codes are independent of the provider's configurable `retryableCodes`; changing automatic retry policy does not expand or reduce this action. `STREAM_CLOSED` permits an explicit new user turn even when the default policy does not retry it. That code can also identify a protocol violation, so another request is not guaranteed to recover. diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index f8a1872c79..3573e9c7b4 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -76,7 +76,9 @@ Queue 预览启用空白折叠和文档边缘 trim,保留 emoji,并使用 CS 上下文占用率入口遵循[统计控件圆角规则](../../../docs/ui-radius.zh.md#radius-scale)。 -普通 Session 的输入框在空草稿且空闲时,若最新一轮以 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`、`EMPTY_RESPONSE` 或 `STREAM_CLOSED` 结束,会提供播放图标按钮。失败轮次必须包含已提交的用户输入和已落盘的模型请求尝试,且起始事件已加载。点击后按当前界面语言提交一条普通用户消息(中文为“继续”,英文为“continue”);存在草稿或附件时保留发送按钮。继续请求保留已提交历史,并沿用现有提交流程。 +普通 Session 的输入框在空草稿且空闲时,若最新一轮以 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`、`EMPTY_RESPONSE` 或 `STREAM_CLOSED` 结束,会提供播放图标按钮。失败轮次必须包含已进入模型历史的普通用户输入和已落盘的失败模型请求尝试。点击后按当前界面语言提交一条普通用户消息;存在草稿或附件时保留发送按钮。继续请求保留已提交历史,并沿用现有提交流程。 + +Host 需要 `sessionProjections`,并发布 `conversationContinuation`;继续资格从 Session 历史恢复,不受浏览器已加载历史分页的影响。 composer 在挂载、切换 Session 和解除锁定时使用共享的[文字输入框聚焦策略](../ui-primitives/README.zh.md#use-this-package)。识别出的手机和平板需要点击输入框;桌面设备保留自动聚焦。在这些移动设备上,提交清空正在聚焦的草稿后,焦点移到不接受文字输入的 composer 卡片,收起键盘并保留键盘导航位置。被拒绝的提交、保留的新草稿文字,以及已经聚焦的其他控件均保留原有焦点。移动端键盘平移页面时,[Lexical 补丁](../../../patches/lexical@0.49.0.patch)让光标滚动计算与 DOM 几何使用同一坐标系。 @@ -187,11 +189,19 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match ## 模型体验 -无,因为本包渲染浏览器状态,并通过 Session Controller API 发送用户确认提交的输入,而不构造模型请求。 +### 手动继续消息 + +#### 模型看到什么 + +播放按钮按点击时选中的界面语言提交一条普通用户消息:中文为 `继续`,英语、法语和葡萄牙语为 `continue`,日语为 `続けて`,西班牙语为 `continúa`,德语为 `weiter`,俄语为 `продолжай`。已准入的消息写入 Session 日志并进入后续模型历史;该操作不从失败尝试中重建文本。 + +#### Token 影响 + +每条进入模型历史的继续消息增加其本地化短文本和普通用户消息的封装开销。准确 token 数取决于模型 tokenizer。在准入前被拒绝或失败的提交不会向模型历史添加继续文本。 #### KV Cache 影响 -无;Conversation 组装和浏览器输入状态不会改变提供方侧的 prompt cache。 +继续消息在保留历史后追加,不替换更早的消息或 prompt section。它保留原本可复用的前缀;提供方缓存的可用性、复用策略和淘汰规则不由本包控制。 ## 已知限制与暂缓事项 @@ -199,6 +209,7 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match - **只有已注册 target 可以渲染**——除已注册的 `chat` 偏好外,shell 刻意不提供隐式 fallback target。 - **Factory occurrence 继承渲染位置的 Session**——`conversation.content` 不接受独立寻址的 Session;该能力需要单独的 Session provider。 +- **手动继续使用固定产品白名单**——支持的失败码独立于提供方可配置的 `retryableCodes`;修改自动重试策略不会扩展或缩小此操作的范围。即使默认策略不重试 `STREAM_CLOSED`,用户仍可明确发起新轮次。该错误码也可能表示协议违规,因此再次请求不保证能够恢复。 diff --git a/packages/client/ui-conversation/package.json b/packages/client/ui-conversation/package.json index 050319168e..c4b2317d94 100644 --- a/packages/client/ui-conversation/package.json +++ b/packages/client/ui-conversation/package.json @@ -47,7 +47,9 @@ }, "license": "MIT", "dependencies": { - "@deepseek-ai/schemastery": "workspace:~" + "@deepseek-ai/schemastery": "workspace:~", + "zod": "4.4.3", + "@deepseek-ai/dsh-brand": "workspace:*" }, "peerDependencies": { "@deepseek-ai/cordis": "workspace:~" @@ -58,7 +60,6 @@ "@deepseek-ai/dsh-api-session-controller": "workspace:*", "@deepseek-ai/dsh-api-workspace-controller": "workspace:*", "@deepseek-ai/dsh-attachment": "workspace:*", - "@deepseek-ai/dsh-brand": "workspace:*", "@deepseek-ai/dsh-client-locale": "workspace:*", "@deepseek-ai/dsh-client-file-upload": "workspace:*", "@deepseek-ai/dsh-client-store": "workspace:*", @@ -86,7 +87,6 @@ "react": "^18.2.0", "@lexical/headless": "^0.49.0", "react-dom": "^18.2.0", - "zod": "4.4.3", "lexical": "^0.49.0", "clsx": "^2.0.0", "@lexical/utils": "^0.49.0", @@ -97,7 +97,8 @@ "@deepseek-ai/dsh-settings": "workspace:*", "@deepseek-ai/dsh-client-shortcuts": "workspace:*", "@deepseek-ai/dsh-client-product-analytics": "workspace:*", - "@deepseek-ai/dsh-message-quotes": "workspace:*" + "@deepseek-ai/dsh-message-quotes": "workspace:*", + "@deepseek-ai/dsh-session-projection": "workspace:*" }, "files": [ "locale/*.json", diff --git a/packages/client/ui-conversation/src/client/contract/slots.ts b/packages/client/ui-conversation/src/client/contract/slots.ts index c190443fb7..068fb0026e 100644 --- a/packages/client/ui-conversation/src/client/contract/slots.ts +++ b/packages/client/ui-conversation/src/client/contract/slots.ts @@ -535,7 +535,7 @@ export interface ComposerBarInjected { */ continueGeneration: ((ending: SessionSeq) => void) | undefined hooks: { - /** Latest transient failure with admitted user input and a loaded turn start. */ + /** Host-projected latest eligible failure, excluding pending human steering. */ continuation: ObservableSnapshot /** Readable sequence while the fixed Stop command is registered. */ stopShortcut: ObservableSnapshot diff --git a/packages/client/ui-conversation/src/client/index.ts b/packages/client/ui-conversation/src/client/index.ts index d4f06accc5..65d7edc6b2 100644 --- a/packages/client/ui-conversation/src/client/index.ts +++ b/packages/client/ui-conversation/src/client/index.ts @@ -1,5 +1,6 @@ /** Browser Conversation assemble core, React adapter, shell, input, and text projection plugin. */ export type {} from './user-text/session-reference-rule.ts' +export type {} from '../continuation-types.ts' /** Declaration-merged node payloads; IDs also identify the corresponding keyed renderer. */ export interface UserTextDataMap { diff --git a/packages/client/ui-conversation/src/client/input/continuation.ts b/packages/client/ui-conversation/src/client/input/continuation.ts deleted file mode 100644 index 261629c084..0000000000 --- a/packages/client/ui-conversation/src/client/input/continuation.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** Manual continuation of settled transient model-request failures. */ -import type { SessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' -import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store' -import type { SessionSeq } from '@deepseek-ai/dsh-session/types' - -const CONTINUABLE_FAILURES: ReadonlySet = new Set([ - 'RATE_LIMIT', 'SERVER', 'TIMEOUT', 'TRANSPORT', 'EMPTY_RESPONSE', 'STREAM_CLOSED', -]) - -/** - * Derive the latest eligible failure from the loaded history. A loaded turn - * start, admitted user message, and settled failed request are required; - * preparation failures may have consumed input without admitting it. - * @param feed - existing Session event source, including replacement and paging. - * @returns a stable source of the eligible turn/end sequence, absent otherwise. - */ -export function createContinuationSource(feed: SessionEventSource): ObservableSnapshot { - return { - getSnapshot: () => { - const { entries } = feed.getSnapshot() - let ending: SessionSeq | undefined - let admitted = false - let attempted = false - for (let index = entries.length - 1; index >= 0; index--) { - // oxlint-disable-next-line typescript/no-non-null-assertion -- index is bounded by this immutable window's length. - const entry = entries[index]! - if (entry.type === 'transient') return undefined - const event = entry.event - switch (event.type) { - case 'turn/end': - if (ending !== undefined || event.data.reason.kind !== 'error' - || !CONTINUABLE_FAILURES.has(event.data.reason.error.code)) return undefined - ending = event.seq - break - case 'turn/start': - return admitted && attempted ? ending : undefined - case 'assistant/attempt': - attempted = true - break - case 'step/start': - case 'assistant/message': - if (!attempted) return undefined - break - case 'user/message': - if (ending !== undefined && event.surfaceOp === 'append' && event.data.source.kind === 'user') admitted = true - break - default: - break - } - } - return undefined - }, - subscribe: listener => feed.subscribe(listener), - } -} diff --git a/packages/client/ui-conversation/src/client/input/hub.ts b/packages/client/ui-conversation/src/client/input/hub.ts index 3c247d322e..36ea1c6b4b 100644 --- a/packages/client/ui-conversation/src/client/input/hub.ts +++ b/packages/client/ui-conversation/src/client/input/hub.ts @@ -25,7 +25,7 @@ import type { ComposerBlocks } from '../contract/composer-blocks.ts' import type { InputSubmitMode } from '../contract/composer-submission.ts' import type { PopupDismissFace } from './facade.ts' import { SessionInputShell } from './facade.ts' -import { createContinuationSource } from './continuation.ts' +import type {} from '../../continuation-types.ts' import { reportMessageSubmission } from './submission-analytics.ts' import { readConversationDraft } from '../stores.ts' @@ -210,7 +210,7 @@ export class InputHub implements SessionInputResolver { } /** - * Read manual-continuation eligibility from the retained Session history. + * Read Host-projected eligibility while excluding pending human steering. * @param id - retained ordinary Session identity. * @returns the identity-stable eligibility source. */ @@ -219,7 +219,20 @@ export class InputHub implements SessionInputResolver { if (binding === undefined) throw new Error(`conversation.input: session "${id}" resolved no binding`) let source = this.continuations.get(binding) if (source === undefined) { - source = createContinuationSource(binding.eventSource) + const ending = binding.session.projections.faceOf('conversationContinuation') + const inbox = binding.session.projections.faceOf('inbox') + source = { + getSnapshot: () => { + const pending = inbox.getSnapshot() as InboxState | undefined + return pending?.['next-step'].some(message => message.source.kind === 'user') + ? undefined : (ending.getSnapshot() as SessionSeq | null | undefined) ?? undefined + }, + subscribe: (listener) => { + const unsubscribeEnding = ending.subscribe(listener) + const unsubscribeInbox = inbox.subscribe(listener) + return () => { unsubscribeInbox(); unsubscribeEnding() } + }, + } this.continuations.set(binding, source) } return source @@ -241,7 +254,7 @@ export class InputHub implements SessionInputResolver { if (session.running || session.removed || session.openState !== 'open' || session.subagent !== null || session.pendingSubmissions.length > 0 || input.phase !== 'plain' || input.draft.trim() !== '' || input.attachmentIds.length > 0 - || (inbox?.['next-turn'].length ?? 0) > 0 || (inbox?.['next-step'].length ?? 0) > 0 + || (inbox?.['next-turn'].length ?? 0) > 0 || this.conversation().blocks.storeFor(id).getSnapshot() !== undefined) return shell.setDraft(this.t('input.continueMessage')) shell.submit('queue', 'click') diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts index c9dc074e1c..0ace610330 100644 --- a/packages/client/ui-conversation/src/client/locales.ts +++ b/packages/client/ui-conversation/src/client/locales.ts @@ -29,6 +29,7 @@ export const zh = { 'input.stop': '停止生成', 'input.send': '发送消息', 'input.continue': '继续生成', + // Model-visible text, durably logged when admitted as an ordinary user message. 'input.continueMessage': '继续', 'input.sending': '正在发送消息', 'input.send.queue': '排队发送', diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx index 9d46a571d3..962edd854f 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx @@ -366,8 +366,8 @@ export const InputBar = memo(function InputBar({ // message waits for the Host. const primaryStops = running && subagent === null && !sending && (empty || blocked !== undefined) const primaryContinues = !running && subagent === null && empty && continuation !== undefined - && historyOpen && !submissionPending && (input?.queue.length ?? 0) === 0 - && !disabled && input.phase === 'plain' && continueGeneration !== undefined + && historyOpen && !submissionPending && !disabled && input.queue.length === 0 + && input.phase === 'plain' && continueGeneration !== undefined // Disabled native buttons may omit mouseleave; their tooltip must close from state. const primaryDisabled = primaryStops ? stopAction === undefined : (!primaryContinues && empty) || disabled || machineBusy || uploadsPending @@ -389,7 +389,7 @@ export const InputBar = memo(function InputBar({ return } if (primaryContinues) { - if (!primaryDisabled) continueGeneration(continuation) + continueGeneration(continuation) return } if (keyboard === undefined) return // absent machine: the button is disabled diff --git a/packages/client/ui-conversation/src/continuation-types.ts b/packages/client/ui-conversation/src/continuation-types.ts new file mode 100644 index 0000000000..9acece7469 --- /dev/null +++ b/packages/client/ui-conversation/src/continuation-types.ts @@ -0,0 +1,9 @@ +/** Host-derived eligibility for an explicit generation-continuation message. */ +import type { SessionSeq } from '@deepseek-ai/dsh-session/types' + +declare module '@deepseek-ai/dsh-session-projection/types' { + interface SessionProjectionMap { + /** Eligible latest turn/end, or null while no settled request can be continued. */ + conversationContinuation: SessionSeq | null + } +} diff --git a/packages/client/ui-conversation/src/host/continuation.ts b/packages/client/ui-conversation/src/host/continuation.ts new file mode 100644 index 0000000000..ef2239338e --- /dev/null +++ b/packages/client/ui-conversation/src/host/continuation.ts @@ -0,0 +1,55 @@ +/** Constant-size continuation eligibility over the complete durable Session. */ +import type {} from '../continuation-types.ts' +import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection' +import type { SessionSeq } from '@deepseek-ai/dsh-session/types' +import { brandNumber } from '@deepseek-ai/dsh-brand' +import { z } from 'zod' + +// Explicit manual requests use a fixed product policy, independent of automatic retry configuration. +const CONTINUABLE_FAILURES: ReadonlySet = new Set([ + 'RATE_LIMIT', 'SERVER', 'TIMEOUT', 'TRANSPORT', 'EMPTY_RESPONSE', 'STREAM_CLOSED', +]) + +const endingSchema = z.number().int().nonnegative().transform(brandNumber).nullable() +const stateSchema = z.object({ + admitted: z.boolean(), + failedAttempt: z.boolean(), + ending: endingSchema, +}) + +type ContinuationState = z.infer + +const EMPTY: ContinuationState = { admitted: false, failedAttempt: false, ending: null } + +/** Eligibility requires admitted human input and a failed settlement in the last request step. */ +export const continuationProjectionDefinition = { + key: 'conversationContinuation', + stateVersion: 1, + stateSchema, + init: (): ContinuationState => EMPTY, + apply: (state, event) => { + switch (event.type) { + case 'turn/start': + return EMPTY + case 'step/start': + case 'assistant/message': + return state.failedAttempt ? { ...state, failedAttempt: false } : state + case 'user/message': + return !state.admitted && event.surfaceOp === 'append' && event.data.source.kind === 'user' + ? { ...state, admitted: true } : state + case 'assistant/attempt': + return state.failedAttempt ? state : { ...state, failedAttempt: true } + case 'turn/end': { + const ending = event.data.reason.kind === 'error' && CONTINUABLE_FAILURES.has(event.data.reason.error.code) + && state.admitted && state.failedAttempt ? event.seq : null + return ending === null ? EMPTY : { ...EMPTY, ending } + } + default: + return state + } + }, + wire: { + viewSchema: endingSchema, + view: (state: ContinuationState) => state.ending, + }, +} satisfies ProjectionDefinition<'conversationContinuation', ContinuationState> diff --git a/packages/client/ui-conversation/src/index.ts b/packages/client/ui-conversation/src/index.ts index afb38b5fe0..6c7515a448 100644 --- a/packages/client/ui-conversation/src/index.ts +++ b/packages/client/ui-conversation/src/index.ts @@ -1,5 +1,6 @@ -/** Host registration for browser conversation preferences. */ +/** Host continuation eligibility and browser conversation preferences. */ import type {} from '@deepseek-ai/dsh-settings' +export type {} from './continuation-types.ts' import type { Volatile, Context } from '@deepseek-ai/cordis' import type { BusyEnterBehavior } from './submission-settings.ts' @@ -7,6 +8,7 @@ import z from '@deepseek-ai/schemastery' import { BUSY_ENTER_FIELD } from './submission-settings.ts' import { ConversationSettingsFields } from './submission-settings.ts' +import { continuationProjectionDefinition } from './host/continuation.ts' export { BUSY_ENTER_BEHAVIORS, BUSY_ENTER_FIELD, CONVERSATION_SETTINGS_NAMESPACE, @@ -24,9 +26,14 @@ export const Config = z.object({ [BUSY_ENTER_FIELD]: ConversationSettingsFields[BUSY_ENTER_FIELD].volatile(), }) -/** Host preferences are consumed through the configuration form projection. +/** Required registry for the durable generation-continuation projection. */ +export const inject = ['sessionProjections'] + +/** Register generation-continuation eligibility and browser preferences. * @param ctx Plugin context used for optional settings presentation. + * @returns when continuation state is registered and available to Session carriers. */ -export function apply(ctx: Context): void { +export async function apply(ctx: Context): Promise { ctx.inject(['settings'], (child) => { child.effect(() => child.settings.configure({ auto: false }, ctx.fiber)) }) + await ctx.sessionProjections.register(continuationProjectionDefinition) } diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index dc69dad5bc..8f2b967e06 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -6,6 +6,7 @@ import { registerComposerKeymap } from '../src/client/input/editor/keymap.ts' import { createSnapshotStore } from '@deepseek-ai/dsh-client-store' import { describe, expect, it, onTestFinished, vi } from 'vitest' import type { CommandContribution, CommandUiContract } from '@deepseek-ai/dsh-client-ui-commands/client' +import type { SessionRequestId } from '@deepseek-ai/dsh-api-session-controller/types' import type { ISession, SessionReference } from '@deepseek-ai/dsh-api-session-controller/client' import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client' import * as messageQuotes from '@deepseek-ai/dsh-message-quotes/client' @@ -24,7 +25,11 @@ import { createConversationStore } from '../src/client/stores.ts' import type { SessionInputShell } from '../src/client/input/facade.ts' import { RemoteError } from '@deepseek-ai/dsh-client-test-runtime' import { SessionSeq } from '@deepseek-ai/dsh-session/types' -import { continuationTurn } from './continuation-fixture.client.ts' +import { createUserMessage, LlmAttemptId } from '@deepseek-ai/dsh-llm' +import { MutableSessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' +import type { SessionEventWindow } from '@deepseek-ai/dsh-api-session-controller/client' +import type { InboxWireState } from '@deepseek-ai/dsh-agent/types' +import { continuationTurn } from './continuation-fixture.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap { @@ -164,6 +169,7 @@ describe('Conversation inject API', () => { const b = await bench() onTestFinished(() => b.runtime.dispose()) await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } })) + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) const composer = b.composerApi(ROOT) expect(b.composerApi(ROOT).hooks.continuation).toBe(composer.hooks.continuation) expect(composer.hooks.continuation.getSnapshot()).toBe(5) @@ -186,6 +192,7 @@ describe('Conversation inject API', () => { const b = await bench() onTestFinished(() => b.runtime.dispose()) await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'TIMEOUT', message: 'timeout' } })) + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) const composer = b.composerApi(ROOT) const input = b.inputApi(ROOT) composer.continueGeneration!(SessionSeq(1)) @@ -199,11 +206,142 @@ describe('Conversation inject API', () => { await b.runtime.sessions.appendEvent(ROOT, { type: 'event', event: { type: 'turn/start', seq: SessionSeq(6), time: 1, data: { turn: 2 } }, }) + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', null) composer.continueGeneration!(SessionSeq(5)) expect(input.state.getSnapshot().draft).toBe('') expect(b.sessionFake.prompt).not.toHaveBeenCalled() }) + it('keeps continuation available with injected mode context but excludes pending human steering', async () => { + const b = await bench() + onTestFinished(() => b.runtime.dispose()) + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) + const composer = b.composerApi(ROOT) + const source = composer.hooks.continuation + const notified = vi.fn() + const unsubscribe = source.subscribe(notified) + onTestFinished(unsubscribe) + const context = { id: 'context', role: 'user', content: [{ type: 'text', text: 'approval policy context' }], source: { kind: 'user-approval' } } + const inbox: InboxWireState = { 'next-turn': [], 'next-step': [context] } + await b.runtime.sessions.setProjection(ROOT, 'inbox', inbox) + expect(source.getSnapshot()).toBe(5) + const user = createUserMessage({ content: [{ type: 'text', text: 'new instruction' }], source: { kind: 'user' } }) + await b.runtime.sessions.setProjection(ROOT, 'inbox', { ...inbox, 'next-step': [context, user] }) + expect(source.getSnapshot()).toBeUndefined() + composer.continueGeneration!(SessionSeq(5)) + expect(b.sessionFake.prompt).not.toHaveBeenCalled() + await b.runtime.sessions.setProjection(ROOT, 'inbox', inbox) + expect(source.getSnapshot()).toBe(5) + composer.continueGeneration!(SessionSeq(5)) + await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce() }) + expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([{ type: 'text', text: '继续' }]) + expect(notified).toHaveBeenCalledTimes(3) + }) + + it('uses the Host projection on partial pages and does no history materialization for streaming chunks', async () => { + const b = await bench() + onTestFinished(() => b.runtime.dispose()) + await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } }).slice(1), true) + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) + const source = b.composerApi(ROOT).hooks.continuation + expect(source.getSnapshot()).toBe(5) + const history = Array.from({ length: 1666 }, (_, index) => continuationTurn({ kind: 'completed' }, index + 1)).flat() + history.push(...continuationTurn({ kind: 'completed' }, 1667).slice(0, 4)) + await b.runtime.sessions.replaceEvents(ROOT, history) + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', null) + const feed = b.runtime.sessions.binding(ROOT)!.eventSource + if (!(feed instanceof MutableSessionEventSource)) throw new Error('expected mutable fixture feed') + const rawSnapshot = feed.getSnapshot.bind(feed) + const windows = new WeakMap() + let materializations = 0 + const read = vi.spyOn(feed, 'getSnapshot').mockImplementation(() => { + const window = rawSnapshot() + let observed = windows.get(window) + if (observed === undefined) { + observed = { + get entries() { materializations++; return window.entries }, + hasMore: window.hasMore, revision: window.revision, change: window.change, + } + windows.set(window, observed) + } + return observed + }) + onTestFinished(() => { read.mockRestore() }) + const notified = vi.fn() + const unsubscribe = source.subscribe(notified) + onTestFinished(unsubscribe) + for (let index = 0; index < 2000; index++) { + feed.append({ type: 'transient', event: { type: 'assistant/live-chunk', seq: 9999.5, time: index, + data: { attemptId: LlmAttemptId('streaming'), turn: 1667, step: 1, chunk: { type: 'text-delta', index: 0, text: 'x' } }, + } }) + expect(source.getSnapshot()).toBeUndefined() + } + expect(materializations).toBe(0) + expect(notified).not.toHaveBeenCalled() + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(10001)) + expect(notified).toHaveBeenCalledOnce() + expect(source.getSnapshot()).toBe(10001) + unsubscribe() + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', null) + expect(notified).toHaveBeenCalledOnce() + }) + + it.each(['next-turn', 'pending', 'block', 'attachment', 'subagent', 'removed', 'closed'] as const)( + 'rejects a stale continuation click after %s changes', async (condition) => { + const b = await bench() + onTestFinished(() => b.runtime.dispose()) + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) + const composer = b.composerApi(ROOT) + switch (condition) { + case 'pending': + await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.pendingSubmissions = [{ requestId: 'pending' as SessionRequestId, placement: 'transcript', time: 0, text: 'newer message', attachments: [] }] }) + break + case 'next-turn': + await b.runtime.sessions.setProjection(ROOT, 'inbox', { 'next-turn': [createUserMessage({ content: [{ type: 'text', text: 'queued' }], source: { kind: 'user' } })], 'next-step': [] }) + break + case 'block': + b.runtime.ctx.conversation.blocks.set(ROOT, { reason: 'Choose a model' }) + break + case 'attachment': + expect(composer.addFiles?.([new File([Uint8Array.of(1)], 'draft.pdf', { type: 'application/pdf' })])).toBeNull() + break + case 'subagent': + await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.subagent = { address: { parentSessionId: ROOT, childSessionId: ROOT, mode: 'continuable' }, parentAvailable: true } }) + break + case 'removed': + await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.removed = true }) + break + case 'closed': + await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.openState = 'error' }) + break + } + const before = b.inputApi(ROOT).state.getSnapshot().draft + composer.continueGeneration!(SessionSeq(5)) + expect(b.inputApi(ROOT).state.getSnapshot().draft).toBe(before) + expect(b.sessionFake.prompt).not.toHaveBeenCalled() + }, + ) + + it.each(['refused', 'failed'] as const)('retains a visible editable continuation draft when submission is %s', async (outcome) => { + const b = await bench() + onTestFinished(() => b.runtime.dispose()) + await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) + const composer = b.composerApi(ROOT) + if (outcome === 'refused') b.sessionFake.prompt.mockImplementationOnce(async () => { + const error = new RemoteError('gateway/internal', 'request refused', {}) + await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.promptError = { op: 'send', error } }) + return { ok: false, error } + }) + else b.sessionFake.prompt.mockRejectedValueOnce(new Error('connection lost')) + composer.continueGeneration!(SessionSeq(5)) + await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce(); expect(composer.keyboard!.snapshot.phase).toBe('plain') }) + expect(composer.keyboard!.snapshot.draft).toBe('继续') + if (outcome === 'refused') expect(b.runtime.sessions.binding(ROOT)!.session.getSnapshot().promptError?.error.message).toBe('request refused') + else expect(composer.hooks.notices.getSnapshot()).toMatchObject({ level: 'error', text: 'connection lost' }) + composer.continueGeneration!(SessionSeq(5)) + expect(b.sessionFake.prompt).toHaveBeenCalledOnce() + }) + it('renders literal quote text through the actual Factory and keyed node registration', async () => { const b = await bench(true) onTestFinished(() => b.runtime.dispose()) diff --git a/packages/client/ui-conversation/tests/config.host.spec.ts b/packages/client/ui-conversation/tests/config.host.spec.ts index 7b87d23d59..402063cedc 100644 --- a/packages/client/ui-conversation/tests/config.host.spec.ts +++ b/packages/client/ui-conversation/tests/config.host.spec.ts @@ -1,17 +1,22 @@ -import { Context } from '@deepseek-ai/cordis' -import { describe, expect, it } from 'vitest' +import SessionStore from '@deepseek-ai/dsh-session' +import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' +import { Context, FiberState } from '@deepseek-ai/cordis' +import { describe, expect, it, onTestFinished } from 'vitest' import * as HostPlugin from '../src/index.ts' import { liveConfig, omitsGeneratedPage } from '../../../settings/settings/tests/live-config.ts' import { plainConfig } from '../../../settings/settings/src/schema.ts' import { - DEFAULT_BUSY_ENTER_BEHAVIOR, Config, apply, + DEFAULT_BUSY_ENTER_BEHAVIOR, } from '@deepseek-ai/dsh-client-ui-conversation' describe('ui-conversation host', () => { it('registers, validates, and disposes the durable busy-Enter preference', async () => { const ctx = new Context() - const configuration = await liveConfig(ctx, { Config, apply }) + onTestFinished(() => ctx.fiber.dispose()) + await ctx.plugin(SessionStore) + await ctx.plugin(SessionProjectionRegistry) + const configuration = await liveConfig(ctx, HostPlugin) const { fiber } = configuration expect(plainConfig(configuration.fiber.effectiveConfig)).toEqual({ busyEnter: DEFAULT_BUSY_ENTER_BEHAVIOR }) await configuration.update({ busyEnter: 'steer' }) @@ -21,4 +26,21 @@ describe('ui-conversation host', () => { }) }) -it('keeps its own instance off the generated Settings pages', () => omitsGeneratedPage(ctx => ctx.plugin(HostPlugin))) +it('keeps its own instance off the generated Settings pages', () => omitsGeneratedPage(async (ctx) => { + onTestFinished(() => ctx.fiber.dispose()) + await ctx.plugin(SessionStore) + await ctx.plugin(SessionProjectionRegistry) + return ctx.plugin(HostPlugin) +})) + +it('waits for the required projection registry before activation', async () => { + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + await ctx.plugin(SessionStore) + const fiber = ctx.plugin(HostPlugin) + await ctx.fiber.await() + expect(fiber.state).toBe(FiberState.PENDING) + await ctx.plugin(SessionProjectionRegistry) + await fiber + expect(fiber.state).toBe(FiberState.ACTIVE) +}) diff --git a/packages/client/ui-conversation/tests/continuation-fixture.client.ts b/packages/client/ui-conversation/tests/continuation-fixture.ts similarity index 92% rename from packages/client/ui-conversation/tests/continuation-fixture.client.ts rename to packages/client/ui-conversation/tests/continuation-fixture.ts index 20dc42fdc9..392f01d39a 100644 --- a/packages/client/ui-conversation/tests/continuation-fixture.client.ts +++ b/packages/client/ui-conversation/tests/continuation-fixture.ts @@ -1,5 +1,5 @@ /** Admitted input and a settled turn for manual-continuation tests. */ -import type { SessionLiveEventEntry } from '@deepseek-ai/dsh-api-session-controller/client' +import type { SessionEvent } from '@deepseek-ai/dsh-session/types' import { createUserMessage } from '@deepseek-ai/dsh-llm' import { SessionSeq } from '@deepseek-ai/dsh-session/types' import type { TurnEndReason } from '@deepseek-ai/dsh-session/types' @@ -10,7 +10,7 @@ import type { TurnEndReason } from '@deepseek-ai/dsh-session/types' * @param number - turn number and corresponding event offset. * @returns ordered durable entries. */ -export function continuationTurn(reason: TurnEndReason, number = 1): SessionLiveEventEntry[] { +export function continuationTurn(reason: TurnEndReason, number = 1): { type: 'event'; event: SessionEvent }[] { const offset = (number - 1) * 6 return [ { type: 'event', event: { type: 'turn/start', seq: SessionSeq(offset), time: 0, data: { turn: number } } }, diff --git a/packages/client/ui-conversation/tests/continuation.client.spec.ts b/packages/client/ui-conversation/tests/continuation.client.spec.ts deleted file mode 100644 index ba101e7737..0000000000 --- a/packages/client/ui-conversation/tests/continuation.client.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { MutableSessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' -import type { SessionLiveEventEntry } from '@deepseek-ai/dsh-api-session-controller/client' -import { SessionSeq } from '@deepseek-ai/dsh-session/types' -import type { TurnEndReason } from '@deepseek-ai/dsh-session/types' -import { createContinuationSource } from '../src/client/input/continuation.ts' -import { continuationTurn as turn } from './continuation-fixture.client.ts' - -const failed = (code: string): TurnEndReason => ({ kind: 'error', error: { code, message: code } }) - -describe('manual generation continuation', () => { - it.each(['RATE_LIMIT', 'SERVER', 'TIMEOUT', 'TRANSPORT', 'EMPTY_RESPONSE', 'STREAM_CLOSED'])( - 'offers continuation for an admitted request ending with %s', (code) => { - const feed = new MutableSessionEventSource() - feed.replace(turn(failed(code)), false) - expect(createContinuationSource(feed).getSnapshot()).toBe(5) - }, - ) - - it.each(['AUTH', 'QUOTA', 'ACCOUNT_QUOTA', 'MISSING_CREDENTIAL', 'INVALID_CREDENTIAL', 'UNKNOWN_MODEL', - 'INVALID_REQUEST', 'CONTEXT_WINDOW_EXCEEDED', 'TOOL_ACTIVATION_FAILED', 'MALFORMED_RESPONSE', 'PI_AI_ERROR', 'UNKNOWN'])( - 'leaves %s to its own recovery action', (code) => { - const feed = new MutableSessionEventSource() - feed.replace(turn(failed(code)), false) - expect(createContinuationSource(feed).getSnapshot()).toBeUndefined() - }, - ) - - it.each([ - { kind: 'completed' }, { kind: 'max-tokens' }, { kind: 'blocked' }, - { kind: 'interrupted' }, { kind: 'forked' }, { kind: 'aborted', reason: { kind: 'user' } }, - ])('leaves a $kind turn unchanged', (reason) => { - const feed = new MutableSessionEventSource() - feed.replace(turn(reason), false) - expect(createContinuationSource(feed).getSnapshot()).toBeUndefined() - }) - - it('requires user input from the failed turn to have entered model history', () => { - const feed = new MutableSessionEventSource() - const source = createContinuationSource(feed) - const entries = turn(failed('TIMEOUT'), 2) - feed.replace([...turn({ kind: 'completed' }), ...entries.filter(entry => entry.event.type !== 'user/message')], false) - expect(source.getSnapshot()).toBeUndefined() - const user = entries[2]! - if (user.event.type !== 'user/message') throw new Error('expected user input') - const replacement: SessionLiveEventEntry = { type: 'event', event: { - ...user.event, surfaceOp: { op: 'replace', startSeq: SessionSeq(0), endSeq: SessionSeq(1) }, - } } - feed.replace(entries.map(entry => entry === user ? replacement : entry), false) - expect(source.getSnapshot()).toBeUndefined() - }) - - it('keeps failures before request dispatch in their preparation flow', () => { - const feed = new MutableSessionEventSource() - const source = createContinuationSource(feed) - feed.replace(turn(failed('TIMEOUT')).filter(entry => entry.event.type !== 'assistant/attempt'), false) - expect(source.getSnapshot()).toBeUndefined() - }) - - it('restores eligibility from paging and replacement, and retires it when a later turn starts', () => { - const feed = new MutableSessionEventSource() - const source = createContinuationSource(feed) - const entries = turn(failed('SERVER')) - feed.replace(entries.slice(1), true) - expect(source.getSnapshot()).toBeUndefined() - feed.prepend(entries.slice(0, 1), false) - expect(source.getSnapshot()).toBe(5) - expect(createContinuationSource(feed).getSnapshot()).toBe(5) - const listener = vi.fn() - const unsubscribe = source.subscribe(listener) - feed.append({ type: 'event', event: { type: 'turn/start', seq: SessionSeq(6), time: 1, data: { turn: 2 } } }) - expect(listener).toHaveBeenCalledOnce() - expect(source.getSnapshot()).toBeUndefined() - unsubscribe() - feed.replace(turn({ kind: 'completed' }), false) - expect(listener).toHaveBeenCalledOnce() - expect(source.getSnapshot()).toBeUndefined() - feed.replace([], false) - expect(source.getSnapshot()).toBeUndefined() - }) -}) diff --git a/packages/client/ui-conversation/tests/continuation.host.spec.ts b/packages/client/ui-conversation/tests/continuation.host.spec.ts new file mode 100644 index 0000000000..1b0dd1c7b3 --- /dev/null +++ b/packages/client/ui-conversation/tests/continuation.host.spec.ts @@ -0,0 +1,139 @@ +/** Complete-log continuation projection, including recovery and publication. */ +import { Context } from '@deepseek-ai/cordis' +import SessionStore, { SessionId, SessionSeq, SessionLogOffset } from '@deepseek-ai/dsh-session' +import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' +import { createAssistantMessage, createUserMessage } from '@deepseek-ai/dsh-llm' +import type { SessionEvent, TurnEndReason } from '@deepseek-ai/dsh-session/types' +import { expect, it, onTestFinished, vi } from 'vitest' +import * as plugin from '../src/index.ts' +import { continuationProjectionDefinition as definition } from '../src/host/continuation.ts' +import { paginate } from '@deepseek-ai/dsh-api-session-controller/src/history.ts' +import { continuationTurn as turn } from './continuation-fixture.ts' + +const failed = (code: string): TurnEndReason => ({ kind: 'error', error: { code, message: code } }) +function fold(events: readonly SessionEvent[]) { + return events.reduce((state, event) => definition.apply(state, event), definition.init()) +} +const events = (reason: TurnEndReason) => turn(reason).map(entry => entry.event) + +it.each(['RATE_LIMIT', 'SERVER', 'TIMEOUT', 'TRANSPORT', 'EMPTY_RESPONSE', 'STREAM_CLOSED'])( + 'offers a manual new turn after %s independently of automatic retries', (code) => { + expect(definition.wire.view(fold(events(failed(code))))).toBe(5) + }, +) +it.each(['AUTH', 'QUOTA', 'ACCOUNT_QUOTA', 'MISSING_CREDENTIAL', 'INVALID_CREDENTIAL', 'UNKNOWN_MODEL', + 'INVALID_REQUEST', 'CONTEXT_WINDOW_EXCEEDED', 'TOOL_ACTIVATION_FAILED', 'MALFORMED_RESPONSE', 'PI_AI_ERROR', 'UNKNOWN'])( + 'leaves %s to its own recovery action', (code) => { + expect(definition.wire.view(fold(events(failed(code))))).toBeNull() + }, +) +it.each([ + { kind: 'completed' }, { kind: 'max-tokens' }, { kind: 'blocked' }, + { kind: 'interrupted' }, { kind: 'forked' }, { kind: 'aborted', reason: { kind: 'user' } }, +])('leaves a $kind turn unchanged', (reason) => { + expect(definition.wire.view(fold(events(reason)))).toBeNull() +}) + +it('requires admitted human input and a failed settlement in the last step', () => { + const base = events(failed('TIMEOUT')) + expect(fold(base.filter(event => event.type !== 'user/message')).ending).toBeNull() + expect(fold(base.filter(event => event.type !== 'assistant/attempt')).ending).toBeNull() + const user = base.find(event => event.type === 'user/message')! + const replacement: SessionEvent<'user/message'> = { ...user, + surfaceOp: { op: 'replace', startSeq: SessionSeq(0), endSeq: SessionSeq(1) }, + } + expect(fold(base.map(event => event === user ? replacement : event)).ending).toBeNull() + const injected: SessionEvent<'user/message'> = { ...user, data: createUserMessage({ + content: [{ type: 'text', text: 'mode context' }], source: { kind: 'user-approval' }, + }) } + expect(fold(base.map(event => event === user ? injected : event)).ending).toBeNull() + const prefix = base.slice(0, -1) + const step: SessionEvent<'step/start'> = { type: 'step/start', seq: SessionSeq(5), time: 0, data: { turn: 1, step: 2 } } + expect(fold([...prefix, step, base.at(-1)!]).ending).toBeNull() + const success: SessionEvent<'assistant/message'> = { type: 'assistant/message', seq: SessionSeq(5), time: 0, + surfaceOp: 'append', data: { turn: 1, step: 1, stream: [], message: createAssistantMessage({ + content: [{ type: 'text', text: 'done' }], source: { provider: 'test', model: 'test' }, + }) }, + } + expect(fold([...prefix, success, base.at(-1)!]).ending).toBeNull() +}) + +it('keeps unrelated events and repeated settlements reference-stable', () => { + const base = events(failed('SERVER')) + const state = fold(base.slice(0, -1)) + expect(definition.apply(state, base[2]!)).toBe(state) + expect(definition.apply(state, base[3]!)).toBe(state) + expect(definition.apply(state, base[4]!)).toBe(state) + expect(definition.apply(definition.init(), base[1]!)).toBe(definition.init()) + const reset = definition.apply(state, base[1]!) + expect(reset.failedAttempt).toBe(false) +}) + +it('restores a checkpoint plus suffix identically to full replay and retires on a new turn', () => { + const base = events(failed('SERVER')) + const prefix = fold(base.slice(0, 3)) + const restored = definition.stateSchema.parse(JSON.parse(JSON.stringify(prefix))) + expect(base.slice(3).reduce((state, event) => definition.apply(state, event), restored)).toEqual(fold(base)) + const ended = fold(base) + expect(definition.apply(ended, { type: 'turn/start', seq: SessionSeq(6), time: 1, data: { turn: 2 } }).ending).toBeNull() + expect(definition.wire.viewSchema.parse(ended.ending)).toBe(5) + expect(() => definition.stateSchema.parse({ ...ended, ending: -1 })).toThrow() + expect(() => definition.wire.viewSchema.parse('5')).toThrow() +}) + +it('publishes a complete-log result independently of the visible page and releases registration', async () => { + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + await ctx.plugin(SessionStore) + await ctx.plugin(SessionProjectionRegistry) + const fiber = await ctx.plugin(plugin) + const session = ctx.sessions.create(SessionId('continued')) + expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBeNull() + const history: SessionEvent[] = [] + ctx.on('session/event', (_session, event) => { history.push(event) }) + const changed = vi.fn[0]>() + ctx.sessionProjections.onChanged(changed) + session.append('turn/start', { turn: 1 }) + expect(changed).not.toHaveBeenCalled() + session.append('step/start', { turn: 1, step: 1 }) + session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'task' }], source: { kind: 'user' } }), { surfaceOp: 'append' }) + // A long failed turn exceeds the browser's 500-message page budget. + for (let index = 0; index < 600; index++) { + session.append('assistant/message', { turn: 1, step: index + 1, stream: [], message: createAssistantMessage({ content: [{ type: 'text', text: 'progress' }], source: { provider: 'test', model: 'test' } }) }, { surfaceOp: 'append' }) + session.append('step/end', { turn: 1, step: index + 1 }) + session.append('step/start', { turn: 1, step: index + 2 }) + } + session.append('assistant/attempt', { turn: 1, step: 601, stream: [] }) + const ending = session.append('turn/end', { turn: 1, reason: failed('SERVER') }).seq + const page = paginate(history, undefined, 500, ending, { minMessages: 50, minTurns: 2 }) + expect(page.hasMore).toBe(true) + expect(page.events.some(event => event.type === 'turn/start')).toBe(false) + expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBe(ending) + expect(changed.mock.calls.map(([, key, value]) => [key, value])).toEqual([['conversationContinuation', null], ['conversationContinuation', ending]]) + expect(ctx.sessionProjections.checkpoint(session).conversationContinuation) + .toEqual({ ver: 1, seq: ending, val: { admitted: false, failedAttempt: false, ending } }) + session.append('turn/start', { turn: 2 }) + expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBeNull() + expect(changed).toHaveBeenCalledTimes(3) + await fiber.dispose() + expect(ctx.sessionProjections.snapshot(session).values).not.toHaveProperty('conversationContinuation') +}) + +it('restores valid, absent and outdated checkpoints through the registry', async () => { + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + await ctx.plugin(SessionStore) + await ctx.plugin(SessionProjectionRegistry) + await ctx.plugin(plugin) + const session = ctx.sessions.create(SessionId('restored')) + const log = events(failed('SERVER')) + const checkpoint = { conversationContinuation: { ver: 1, seq: SessionSeq(2), val: fold(log.slice(0, 3)) } } + const restore = (rows: typeof checkpoint | Record, tail: readonly SessionEvent[], base: number) => + ctx.sessionProjections.restore(rows, tail, SessionLogOffset(base), session.header, session.inheritedEventCount) + expect(restore(checkpoint, log.slice(3), 3).snapshot.values.conversationContinuation).toBe(5) + expect(restore({}, log, 0).snapshot.values.conversationContinuation).toBe(5) + const outdated = { conversationContinuation: { ...checkpoint.conversationContinuation, ver: 0 } } + expect(restore(outdated, log, 0).snapshot.values.conversationContinuation).toBe(5) + expect(() => restore(outdated, log.slice(3), 3)).toThrow('re-read from seq 0') + expect(() => restore({}, log.slice(3), 3)).toThrow('re-read from seq 0') +}) diff --git a/packages/client/ui-conversation/tsconfig.client.json b/packages/client/ui-conversation/tsconfig.client.json index 0339eebbe6..cc0d628ef0 100644 --- a/packages/client/ui-conversation/tsconfig.client.json +++ b/packages/client/ui-conversation/tsconfig.client.json @@ -107,6 +107,7 @@ } ], "exclude": [ - "src/index.ts" + "src/index.ts", + "src/host" ] } diff --git a/packages/client/ui-conversation/tsconfig.host.json b/packages/client/ui-conversation/tsconfig.host.json index ec30b21bfe..cc278423fc 100644 --- a/packages/client/ui-conversation/tsconfig.host.json +++ b/packages/client/ui-conversation/tsconfig.host.json @@ -7,7 +7,9 @@ }, "files": [ "src/submission-settings.ts", - "src/index.ts" + "src/index.ts", + "src/continuation-types.ts", + "src/host/continuation.ts" ], "references": [ { @@ -18,6 +20,15 @@ }, { "path": "../../settings/settings" + }, + { + "path": "../../core/session" + }, + { + "path": "../../session/session-projection" + }, + { + "path": "../../util/brand" } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 281ea5d111..489c15f56e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4001,9 +4001,15 @@ importers: packages/client/ui-conversation: dependencies: + '@deepseek-ai/dsh-brand': + specifier: workspace:* + version: link:../../util/brand '@deepseek-ai/schemastery': specifier: link:../../../vendor/schemastery version: link:../../../vendor/schemastery + zod: + specifier: 4.4.3 + version: 4.4.3 devDependencies: '@deepseek-ai/cordis': specifier: workspace:~ @@ -4023,9 +4029,6 @@ importers: '@deepseek-ai/dsh-attachment': specifier: workspace:* version: link:../../attachment/attachment - '@deepseek-ai/dsh-brand': - specifier: workspace:* - version: link:../../util/brand '@deepseek-ai/dsh-client-file-upload': specifier: workspace:* version: link:../file-upload @@ -4089,6 +4092,9 @@ importers: '@deepseek-ai/dsh-session': specifier: workspace:* version: link:../../core/session + '@deepseek-ai/dsh-session-projection': + specifier: workspace:* + version: link:../../session/session-projection '@deepseek-ai/dsh-settings': specifier: workspace:* version: link:../../settings/settings @@ -4137,9 +4143,6 @@ importers: react-dom: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) - zod: - specifier: 4.4.3 - version: 4.4.3 packages/client/ui-deepseek-model-label-1017: devDependencies: diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index aed0e5a121..e236f4e02b 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -138,7 +138,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/client/ui-sidebar-right': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/client/ui-sidebar': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/client/ui-brand-official': { kind: 'none', reason: 'Browser-side presentation occupants; registers nothing model-facing.' }, - 'packages/client/ui-conversation': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/client/ui-approval': { kind: 'none', reason: 'Browser-side approval presentation; registers nothing model-facing.' }, 'packages/client/ui-chat': { kind: 'none', reason: 'Browser-side Chat presentation; registers nothing model-facing.' }, 'packages/client/ui-message-feedback': { kind: 'none', reason: 'Browser-side controls over log-only message feedback; ratings and notes never enter model context or change model-input tokens.' }, diff --git a/snapshots/web/live-interactions/continued.expected.md b/snapshots/web/live-interactions/continued.expected.md index ab51db50fc..1eb97847c2 100644 --- a/snapshots/web/live-interactions/continued.expected.md +++ b/snapshots/web/live-interactions/continued.expected.md @@ -19,7 +19,7 @@ - status: - text: This turn failedupstream 503 - code: SERVER -- text: continue {{clock}} +- text: plan Plan mode on. Use /plan off to leave. plan Plan mode off. continue {{clock}} - button "Copy" - status: Completed - button "Completed in {{duration}}" From d8254627feb3d4d1017819cc9e3bc0da31926b80 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 22:12:13 +0800 Subject: [PATCH 04/29] feat(core): continue failed turns through empty prompt input --- ...6-manual-generation-continuation.i18n.yaml | 20 ++--- ...26-10-26-manual-generation-continuation.md | 18 ++-- ...10-26-manual-generation-continuation.zh.md | 20 ++--- apps/web/tests/live-interactions.e2e.ts | 6 +- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 2 + docs/architecture.zh.md | 2 + docs/config-catalog.md | 2 +- docs/config-catalog.zh.md | 2 +- .../guide.i18n.yaml | 16 ---- .../guide.md | 25 ------ .../guide.zh.md | 25 ------ .../empty-prompt-continuation/guide.i18n.yaml | 16 ++++ .../empty-prompt-continuation/guide.md | 19 +++++ .../empty-prompt-continuation/guide.zh.md | 19 +++++ .../api/session-controller/README.i18n.yaml | 4 +- packages/api/session-controller/README.md | 2 + packages/api/session-controller/README.zh.md | 2 + .../api/session-controller/src/commands.ts | 29 +++++++ .../session-controller/src}/continuation.ts | 22 +++-- packages/api/session-controller/src/index.ts | 4 +- packages/api/session-controller/src/types.ts | 8 +- .../tests/continuation-fixture.ts | 0 .../tests/continuation.host.spec.ts | 43 ++++++++-- .../tests/empty-prompt.host.spec.ts | 82 +++++++++++++++++++ .../tests/session-cold.host.spec.ts | 1 - .../api/session-controller/tsconfig.host.json | 6 +- .../client/ui-conversation/README.i18n.yaml | 28 +++---- packages/client/ui-conversation/README.md | 12 +-- packages/client/ui-conversation/README.zh.md | 12 +-- packages/client/ui-conversation/package.json | 8 +- .../src/client/contract/input.ts | 2 + .../src/client/contract/slots.ts | 2 +- .../ui-conversation/src/client/index.ts | 1 - .../src/client/input/facade.ts | 7 +- .../ui-conversation/src/client/input/hub.ts | 10 +-- .../src/client/input/machine.ts | 2 +- .../ui-conversation/src/client/locales.ts | 9 -- .../ui-conversation/src/continuation-types.ts | 9 -- packages/client/ui-conversation/src/index.ts | 14 ++-- .../tests/apply-inject.client.spec.tsx | 12 +-- .../ui-conversation/tests/config.host.spec.ts | 14 +--- .../client/ui-conversation/tsconfig.host.json | 13 +-- packages/core/agent-loop/README.i18n.yaml | 4 +- packages/core/agent-loop/README.md | 2 + packages/core/agent-loop/README.zh.md | 2 + packages/core/agent-loop/src/agent.ts | 2 +- .../agent-loop/tests/request-error.spec.ts | 27 +++++- packages/core/agent/README.i18n.yaml | 4 +- packages/core/agent/README.md | 2 + packages/core/agent/README.zh.md | 2 + packages/core/agent/src/runtime-types.ts | 4 +- pnpm-lock.yaml | 12 +-- scripts/smoke-python-runtime.py | 9 +- .../scheduler-recovery/requests.json | 4 - .../scheduler-recovery/result.json | 2 - .../scheduler-recovery/session.v4.jsonl | 3 +- snapshots/sdk/sdk.snapshot.ts | 19 ++++- .../notifications.expected.jsonl | 9 +- .../tool-scheduler-recovery/session.v4.jsonl | 3 +- .../live-interactions/continued.expected.md | 2 +- 61 files changed, 411 insertions(+), 255 deletions(-) delete mode 100644 docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.i18n.yaml delete mode 100644 docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md delete mode 100644 docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md rename packages/{client/ui-conversation/src/host => api/session-controller/src}/continuation.ts (65%) rename packages/{client/ui-conversation => api/session-controller}/tests/continuation-fixture.ts (100%) rename packages/{client/ui-conversation => api/session-controller}/tests/continuation.host.spec.ts (78%) create mode 100644 packages/api/session-controller/tests/empty-prompt.host.spec.ts delete mode 100644 packages/client/ui-conversation/src/continuation-types.ts diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml index 320476d704..0b66ec2f1d 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml @@ -6,17 +6,17 @@ en: 327aaa1cdc63b1f5 zh: 1147b4a23e51772c /agent-note-manual-generation-continuation/problem: - en: 027e01e4586da32f - zh: dce2c6858ef709c1 + en: f118844946afdbdc + zh: f1fe37d042f88b24 /agent-note-manual-generation-continuation/decision: - en: dc5f066e9c0bbb23 - zh: 6e54f59a34d8fca5 + en: 32e75f0c2a35bfec + zh: 343fedcd8f53a6a8 /agent-note-manual-generation-continuation/alternatives-considered: - en: b32c1050bc0ca9de - zh: 5e950f842f944818 + en: 58bb948d210d42ea + zh: 5dc920d222d71218 /agent-note-manual-generation-continuation/consequences: - en: c825acfaae8f049d - zh: e4768dab9d8edde3 + en: e86aee3a7da7649f + zh: d58e8cc94e5008bf /agent-note-manual-generation-continuation/verification: - en: bd59f8845a358dec - zh: cfcd30ca521e0111 + en: e31a5bd5826c689f + zh: 02ac700146246664 diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md index 8060f1bde4..ab91eb5f7b 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md @@ -6,28 +6,28 @@ English | [中文](2026-10-26-manual-generation-continuation.zh.md) ## Problem -Users need another turn after a model request fails. A browser page can omit the failed turn's start; streaming must not repeatedly copy loaded history. +Users need another request after a model failure without inserting an artificial instruction. Browser paging can omit the failed turn's start, and streaming must not repeatedly copy history. ## Decision -The play action submits localized ordinary user text through the existing composer, retaining admission, logging, failure drafts, and telemetry. No resume RPC, AgentLoop change, or Session event format is introduced. +The play action submits `content: []` through the existing Session prompt API. AgentLoop retains the identified inbox input and starts a new balanced turn, but omits the empty ordinary input from model-visible `user/message` events. The original failed turn remains closed. -The fixed-size Host projection `conversationContinuation` recovers from complete Session history. Eligibility requires admitted ordinary input and a failed attempt, independently of the browser's 500-message page. +The Session API owns the fixed-size `conversationContinuation` projection and shares its value with the browser. Admission requires an eligible failure, true idle ownership, and no pending human input. An accepted empty input carries the admitted work into its next turn, preserving eligibility if that request also fails. -The six-code manual allowlist remains independent of [automatic retry policy](../../../../packages/llm/llm-retry/README.md). Pending [injected context](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md) in `next-step` accompanies the new waking message; pending ordinary user submissions prevent duplicates. +The six-code manual allowlist stays independent of provider retry policy. Non-waking injected context accompanies the request. Durable inbox receipts retain request-id deduplication after the empty input is claimed; no new Remote operation or Session event format is required. ## Alternatives considered -**Window scanning or entries caching.** Each revision can materialize a fresh window; caching cannot recover unloaded facts. +**Localized or hidden continuation text.** Adds model-visible instructions and changes retained history. -**Use provider retryableCodes.** Deployment retry policies could expose authentication or other unsuitable errors as UI actions. +**A separate resume operation.** The existing prompt and inbox already carry identified control input and own wakeup. -**Add a resume RPC.** Ordinary submission already owns wakeup, admission, error reporting, and history. +**Keep the failed turn waiting for a click.** Changes failure settlement, idle status, cancellation, and model-selection semantics. ## Consequences -Host activation requires `sessionProjections`; custom compositions follow the [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md). Fixed-size state avoids Client history scans without introducing a second submission lifecycle. +Ordinary empty-draft Enter remains inert. The explicit action reuses submission state and error reporting without an echo or generated draft. Pre-step rejection and cleared batches still prevent model calls. ## Verification -Tests cover checkpoint recovery, long-turn paging, injected context, refusal and retained drafts, and zero Client event-window reads. The recorded Web scenario exercises failure followed by continuation. +Unit and assembled scenarios cover repeated failure, request deduplication, injected context, balanced turns, retained tools, one original user message, and zero Client event-window reads. diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md index c7c0d9ee13..21ff2cf50a 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md @@ -6,28 +6,28 @@ Status: implemented ## 问题 -模型请求失败后,用户需要发起下一轮。浏览器分页可能不含失败轮次的起点;流式输出也不能反复复制已加载历史。 +模型请求失败后,用户需要再次请求,但不应插入人为构造的指令。浏览器分页可能缺少失败轮次起点,流式输出也不能反复复制历史。 ## 决策 -播放按钮通过现有 composer 提交本地化的普通用户文本,沿用准入、日志、失败草稿和遥测语义。不引入 resume RPC,不修改 AgentLoop 或 Session 事件格式。 +播放按钮通过现有 Session prompt API 提交 `content: []`。AgentLoop 保留带身份的 inbox 输入并启动正常闭合的新轮次,但不将空普通输入写成模型可见的 `user/message`。原失败轮次保持闭合。 -固定大小的 Host 投影 `conversationContinuation` 从完整 Session 历史恢复。资格要求已准入的普通用户输入和失败尝试,不依赖浏览器的 500 条消息分页。 +Session API 拥有固定大小的 `conversationContinuation` 投影,向浏览器发布相同的值。准入要求失败符合条件、持有真正的空闲状态且没有待消费的用户输入。已接受的空输入将原先准入的工作带入新轮次,因此请求再次失败后仍保留继续资格。 -六类手动继续白名单独立于[自动重试策略](../../../../packages/llm/llm-retry/README.zh.md)。`next-step` 中待处理的[注入上下文](../architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md) 随新的唤醒消息进入请求;待提交的普通用户消息仍阻止重复提交。 +六码手动白名单独立于提供方重试策略。非唤醒的注入上下文随请求进入。空输入被领取后,持久 inbox 回执仍支持 request-id 去重;不需要新增 Remote 操作或 Session 事件格式。 ## 考虑过的替代方案 -**窗口扫描或 entries 缓存。** 每个 revision 都可能物化新窗口;缓存无法恢复尚未加载的事实。 +**本地化或隐藏的继续文本。** 增加模型可见指令并改变保留历史。 -**采用提供方的 retryableCodes。** 部署重试策略可能把认证等不适合的错误也变成界面动作。 +**单独的 resume 操作。** 现有 prompt 和 inbox 已可承载带身份的控制输入并拥有唤醒流程。 -**新增 resume RPC。** 普通提交已经拥有唤醒、准入、错误报告和历史。 +**保持失败轮次等待点击。** 改变失败结算、空闲状态、取消和模型选择语义。 -## 影响 +## 后果 -Host 激活需要 `sessionProjections`;自定义组合按[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md) 调整。固定大小的状态避免 Client 历史扫描,也无需第二套提交生命周期。 +普通空草稿回车仍不执行。显式继续沿用提交状态与错误报告,不创建消息回显或生成草稿。pre-step 拒绝和被清空的批次仍阻止模型调用。 ## 验证 -测试覆盖检查点恢复、长轮次分页、注入上下文、拒绝与草稿保留,以及不读取 Client 事件窗口。已录制的 Web 场景验证失败后的继续流程。 +单元和组装场景覆盖重复失败、请求去重、注入上下文、轮次闭合、工具结果保留、仅一条原始用户消息,以及客户端不读取完整事件窗口。 diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index d1e90d0080..eda193847c 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -414,7 +414,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { expect(tripwire.warnings).toEqual([]) }, 120_000) - it.skipIf(MODE === 'record')('continues after transient retries exhaust through a localized user message', async () => { + it.skipIf(MODE === 'record')('continues after transient retries exhaust without another user message', async () => { // A whole-script replacement: three throw entries cover the first request // plus both budgeted retries (patches cannot reach past the one-call // derived script). The scenario-owned policy keeps exhaustion fast and @@ -477,7 +477,9 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { expect(sessionEvents.some(event => event.type === 'user/message' && event.data.source.kind === 'plan-mode')).toBe(true) const userMessages = sessionEvents.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' ? [event.data.content] : []) - expect(userMessages).toEqual([[{ type: 'text', text: PROMPT }], [{ type: 'text', text: 'continue' }]]) + expect(userMessages).toEqual([[{ type: 'text', text: PROMPT }]]) + expect(sessionEvents.filter(event => event.type === 'turn/start')).toHaveLength(2) + expect(sessionEvents.filter(event => event.type === 'turn/end')).toHaveLength(2) expect(await resume.count()).toBe(0) await page.setViewportSize({ width: 1440, height: 900 }) await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'continued.expected.md'), diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index c8543cdc36..d1e20ef6b6 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -19,12 +19,12 @@ zh: 70d87a12c43e042a /deepseek-harness-architecture/core-packages: en: 20ef882e44b06a10 - zh: c10c3934ed5570e9 + zh: 0d69c1500650868f /deepseek-harness-architecture/events: en: 693407efb233f961 zh: 37b9dd0677d4405c /deepseek-harness-architecture/turn-flow: - en: 1a79a70b62c04cdb + en: 22964b8327adc3f6 zh: c86d765e21430405 /deepseek-harness-architecture/session-log: en: 8a7f19b95d426c99 diff --git a/docs/architecture.md b/docs/architecture.md index e6bede8d0e..ed35ab94af 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -119,6 +119,8 @@ Details: the [sequence diagram](agent-lifecycle.md), the [tool pipeline](tool-ex [`ctx.tools.execute()`](subsystems/tools.md) receives raw batches and concurrency limits. +An identified empty ordinary input starts a new turn from committed history without creating a model-visible user message. It remains logged in the inbox, passes normal pre-step admission, and closes through the same turn and step lifecycle. + ## Session log The session log is the source of the context the model sees. `deriveMessages()` projects model history from it. Each `assistant/message` embeds the exact compact timed stream that produced its assembled content; `assistant/attempt` retains settled failed, retried, cancelled, and stream-error attempts without adding model history. Fork, resume, transcripts, telemetry, and persistence all derive from these durable settlements, while live UI incrementality comes from `agent/assistant-stream`; a hard process loss before settlement leaves no durable attempt stream ([decision](../.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md)). diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 8863c8047c..64c3be1ddd 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -66,6 +66,8 @@ Electron 使用 Electron Node 模式启动私有 Desktop Host。Host 调用共 +带身份的空普通输入基于已提交历史启动新轮次,不生成模型可见的用户消息。它仍记录在 inbox 中,经过正常 pre-step 准入,并通过相同的轮次和步骤生命周期闭合。 + ## 事件 事件就是扩展点,而选对事件域是大多数改动的第一个决定。 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 43726adfb5..be0d89caf2 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -4713,7 +4713,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-client-ui-brand-official` | — | [`packages/client/ui-brand-official/src/index.ts`](../packages/client/ui-brand-official/src/index.ts) | | `@deepseek-ai/dsh-client-ui-chat` | — | [`packages/client/ui-chat/src/index.ts`](../packages/client/ui-chat/src/index.ts) | | `@deepseek-ai/dsh-client-ui-commands` | — | [`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts) | -| `@deepseek-ai/dsh-client-ui-conversation` | `sessionProjections` | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-conversation` | — | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | | `@deepseek-ai/dsh-client-ui-cordis` | — | [`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts) | | `@deepseek-ai/dsh-client-ui-creation-recommendations` | — | [`packages/experimental/client-ui-creation-recommendations/src/index.ts`](../packages/experimental/client-ui-creation-recommendations/src/index.ts) | | `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` | — | [`packages/client/ui-deepseek-model-label-1017/src/index.ts`](../packages/client/ui-deepseek-model-label-1017/src/index.ts) | diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 470c40d5b0..23165f5f85 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -4715,7 +4715,7 @@ export interface Config { | `@deepseek-ai/dsh-client-ui-brand-official` | — | [`packages/client/ui-brand-official/src/index.ts`](../packages/client/ui-brand-official/src/index.ts) | | `@deepseek-ai/dsh-client-ui-chat` | — | [`packages/client/ui-chat/src/index.ts`](../packages/client/ui-chat/src/index.ts) | | `@deepseek-ai/dsh-client-ui-commands` | — | [`packages/client/ui-commands/src/index.ts`](../packages/client/ui-commands/src/index.ts) | -| `@deepseek-ai/dsh-client-ui-conversation` | `sessionProjections` | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-conversation` | — | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) | | `@deepseek-ai/dsh-client-ui-cordis` | — | [`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts) | | `@deepseek-ai/dsh-client-ui-creation-recommendations` | — | [`packages/experimental/client-ui-creation-recommendations/src/index.ts`](../packages/experimental/client-ui-creation-recommendations/src/index.ts) | | `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` | — | [`packages/client/ui-deepseek-model-label-1017/src/index.ts`](../packages/client/ui-deepseek-model-label-1017/src/index.ts) | diff --git a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.i18n.yaml deleted file mode 100644 index 50094c2b2c..0000000000 --- a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.i18n.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md -/: - en: cd293f657bb1ab7a - zh: 7d942448b488e22c -/provide-session-projections-for-conversation: - en: 6ec06cc57348d333 - zh: 86665b791e3b2e02 -/provide-session-projections-for-conversation/change: - en: b0048aafcb91d8a5 - zh: c913215a68c5ff5b -/provide-session-projections-for-conversation/migration: - en: e06d674bafc37abe - zh: b68409bfbc936d19 diff --git a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md deleted file mode 100644 index 52fa806c20..0000000000 --- a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -kind: upgrade-guide -description: "Custom Conversation compositions must provide sessionProjections to activate the Host plugin." ---- -# Provide Session projections for Conversation - -English | [中文](guide.zh.md) - -## Change - -The Host entry of `@deepseek-ai/dsh-client-ui-conversation` requires the `sessionProjections` service to publish manual continuation eligibility. Custom compositions that mounted Conversation without a projection provider must add one. The shipped Web and Desktop compositions already provide it. Session logs do not require migration. - -## Migration - -1. Check the custom profile's bundles and `cordis.patch.yml` for an existing `@deepseek-ai/dsh-session-projection` entry. Keep that provider when present. -2. If the composition has none, add this row to the profile's `cordis.patch.yml`: - - ```yaml - - id: session-projection - name: '@deepseek-ai/dsh-session-projection' - ``` - -3. Restart the profile and verify that the Conversation Host entry activates. After a supported model-request failure, an empty, idle ordinary composer must offer the play action. - -See the [Conversation reference](../../../../packages/client/ui-conversation/README.md#shell-and-standard-props) for the supported failures and submission behavior. diff --git a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md deleted file mode 100644 index 1277f31bf5..0000000000 --- a/docs/upgrade-guide/v0.2.1-rc.3/conversation-continuation-projection/guide.zh.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -kind: upgrade-guide -description: "自定义 Conversation 组合必须提供 sessionProjections 才能激活 Host 插件。" ---- -# 为 Conversation 提供 Session 投影 - -[English](guide.md) | 中文 - -## 变更 - -`@deepseek-ai/dsh-client-ui-conversation` 的 Host 入口需要 `sessionProjections` 服务来发布手动继续资格。挂载 Conversation 却没有投影提供方的自定义组合必须补上提供方。随产品提供的 Web 和 Desktop 组合已经提供它。Session 日志不需要迁移。 - -## 迁移 - -1. 检查自定义 profile 的 bundle 和 `cordis.patch.yml` 中是否已有 `@deepseek-ai/dsh-session-projection` entry;存在时保留该提供方。 -2. 如果组合中没有该提供方,在 profile 的 `cordis.patch.yml` 中添加以下条目: - - ```yaml - - id: session-projection - name: '@deepseek-ai/dsh-session-projection' - ``` - -3. 重启 profile 并确认 Conversation Host 入口已激活。支持的模型请求失败后,空草稿且空闲的普通 composer 应提供播放按钮。 - -支持的失败类型和提交行为见 [Conversation 参考](../../../../packages/client/ui-conversation/README.zh.md#shell-and-standard-props)。 diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml new file mode 100644 index 0000000000..cbda06d0c8 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md +/: + en: 7a194a68f49ac09a + zh: 4d7bdff82c571c62 +/observe-empty-prompt-admission-through-the-inbox: + en: 450277479989809e + zh: 4b01f252faeae19c +/observe-empty-prompt-admission-through-the-inbox/change: + en: e1c081d5321f3256 + zh: a5dd5585fe4300b3 +/observe-empty-prompt-admission-through-the-inbox/migration: + en: 36e80226df9dcf54 + zh: 81a9c0bd5c667470 diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md new file mode 100644 index 0000000000..16876dd26c --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md @@ -0,0 +1,19 @@ +--- +kind: upgrade-guide +description: "Empty ordinary Agent inputs start requests without producing user/message events." +--- +# Observe empty prompt admission through the inbox + +English | [中文](guide.zh.md) + +## Change + +An ordinary Agent input with `content: []` starts a new turn from committed history without appending an empty `user/message`. The identified input remains in durable inbox events. Custom consumers that wait for every accepted input to produce a user message must observe its inbox receipt instead. Session logs need no migration; existing recorded empty user messages keep their historical interpretation. + +The Web Session prompt endpoint accepts an empty array only for an eligible failed, idle ordinary Session in queue mode. Whitespace-only text parts remain invalid. + +## Migration + +1. Keep ordinary text and attachment inputs unchanged. Use an empty array only when requesting another model call without new user text. +2. Match acceptance by the input identity in `agent/inbox/spliced`, then observe the normal turn/status lifecycle. Do not wait for an empty `user/message`. +3. Verify that continuation adds a balanced turn without adding a user message. The shipped TypeScript and Python SDK clients already observe inbox receipts. diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md new file mode 100644 index 0000000000..9c879cc2aa --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md @@ -0,0 +1,19 @@ +--- +kind: upgrade-guide +description: "空的普通 Agent 输入发起请求,但不产生 user/message 事件。" +--- +# 通过 inbox 观察空 prompt 准入 + +[English](guide.md) | 中文 + +## 变更 + +普通 Agent 输入的 `content: []` 基于已提交历史启动新轮次,不再追加空的 `user/message`。带身份的输入仍保留在持久 inbox 事件中。若自定义消费者等待每条已接受输入产生用户消息,应改为观察对应 inbox 回执。Session 日志无需迁移,历史空用户消息的解释保持不变。 + +Web Session prompt 端点仅在普通 Session 空闲、最新失败符合继续条件且使用 queue 模式时接受空数组。仅含空白的文本块仍无效。 + +## 迁移 + +1. 普通文本和附件输入保持不变。仅在需要再次请求模型而不添加用户文本时使用空数组。 +2. 通过 `agent/inbox/spliced` 中的输入身份匹配准入,然后观察正常轮次与状态生命周期。不要等待空的 `user/message`。 +3. 验证继续操作增加正常闭合的轮次,但不增加用户消息。内置 TypeScript 和 Python SDK 客户端已观察 inbox 回执。 diff --git a/packages/api/session-controller/README.i18n.yaml b/packages/api/session-controller/README.i18n.yaml index 010c41bf94..78f64d3acc 100644 --- a/packages/api/session-controller/README.i18n.yaml +++ b/packages/api/session-controller/README.i18n.yaml @@ -24,8 +24,8 @@ en: 954923654eebe2dc zh: 6bd9a7d2ea965aed /session-controller/configuration: - en: c92590fbaf75e45e - zh: fcac67017bb0e3ab + en: 2a91d9f904e90756 + zh: 8affb15ccc571829 /session-controller/model-experience: en: 30b917bf822bb4df zh: b0b023b5b526c317 diff --git a/packages/api/session-controller/README.md b/packages/api/session-controller/README.md index 6fd7b24561..1b11d04106 100644 --- a/packages/api/session-controller/README.md +++ b/packages/api/session-controller/README.md @@ -111,6 +111,8 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a Blockquotes travel as ordinary prompt text without quote metadata. Local submission echoes and durable messages use the same text rendering rules; queue edits replace content while retaining prompt correlation. +An empty `prompt.content` array requests a new turn only for an eligible failed, idle ordinary Session in queue mode. Pending human input prevents admission; non-waking injected context is retained. `conversationContinuation` supplies the shared six-code manual policy, independently of provider retry configuration. The input is logged through the inbox for request-id deduplication, but creates no model-visible user message. Whitespace-only text parts remain invalid. + ## Model Experience None, as invoked Agent commands own any model-visible effect. diff --git a/packages/api/session-controller/README.zh.md b/packages/api/session-controller/README.zh.md index 8729f555ba..cb8a1ba818 100644 --- a/packages/api/session-controller/README.zh.md +++ b/packages/api/session-controller/README.zh.md @@ -111,6 +111,8 @@ GUI 模型选择要求确切提供方/模型对出现在可用目录中;不 Blockquote 作为普通 prompt 文本传递,不携带 quote 元数据。本地提交回显与持久消息使用同一文本渲染规则;队列编辑替换 content,同时保留 prompt 关联信息。 +空的 `prompt.content` 数组仅在普通 Session 空闲、最新失败符合继续条件且使用 queue 模式时请求新轮次。待消费的用户输入阻止准入,非唤醒的注入上下文保留。`conversationContinuation` 提供共用的六码手动策略,独立于提供方重试配置。输入通过 inbox 持久记录,用于 request-id 去重,但不生成模型可见的用户消息。仅含空白的文本块仍无效。 + ## 模型体验 无;任何模型可见效果都由被调用的 Agent 命令负责。 diff --git a/packages/api/session-controller/src/commands.ts b/packages/api/session-controller/src/commands.ts index 5309872331..0b62ff724a 100644 --- a/packages/api/session-controller/src/commands.ts +++ b/packages/api/session-controller/src/commands.ts @@ -1,5 +1,8 @@ /** Session commands whose activation policy is explicit at each Remote method. */ +import type { ProjectionHandle } from '@deepseek-ai/dsh-session-projection' +import { continuationProjectionDefinition, type ContinuationState } from './continuation.ts' + import { modelAvailable } from './catalog.ts' import { randomUUID } from 'node:crypto' import type { Context } from '@deepseek-ai/cordis' @@ -83,6 +86,13 @@ function latestCompletedPrefixBoundary(events: readonly SessionEvent[]): Session /** Implements Session business commands delegated by the Session Controller Remote service. */ export class SessionCommandController { + private continuation!: ProjectionHandle + + /** Register complete-history empty-prompt eligibility. @returns when command admission is ready. */ + async initialize(): Promise { + this.continuation = await this.ctx.sessionProjections.register(continuationProjectionDefinition) + } + /** * @param ctx - Host context carrying Agent, model, attachment, title, and Workspace services. * @param agents - sole owner of create, resume, and Agent composition. @@ -304,6 +314,22 @@ export class SessionCommandController { async prompt(request: SessionPromptRequest, source: MessageSource): Promise { const agent = await this.resolveAgent(request.sessionId) if (hasPromptRequest(agent, request.requestId)) return { accepted: true } + if (request.content.length === 0) { + try { + return await agent.runMaintenance(async () => { + const inbox = agent.inbox + if (request.mode !== 'queue' || this.continuation.stateOf(agent.session).ending === null + || inbox.nextTurn.length > 0 || inbox.nextStep.some(message => message.source.kind === 'user')) { + throw new RemoteError('session/agent-busy', 'generation continuation is unavailable', {}) + } + agent.followup(createUserMessage({ content: [], source })) + return { accepted: true as const } + }) + } catch (error: unknown) { + if (remoteErrorOf(error) !== undefined) throw error + throw new RemoteError('session/agent-busy', 'generation continuation was not accepted', { reason: String(error) }) + } + } const admission = resolvePromptFileReceipts( request.content, receiptId => this.ctx.fileUploads.resolve(agent, receiptId), @@ -547,6 +573,9 @@ function hasPromptRequest(agent: Agent, requestId: SessionRequestId): boolean { if (agent.inbox.nextTurn.some(matches) || agent.inbox.nextStep.some(matches)) return true // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. return agent.session.snapshotEvents().some((event) => { + if (event.type === 'agent/inbox/spliced') { + return event.data.inserted.some(message => message.content.length === 0 && matches(message)) + } if (event.type !== 'user/message') return false const source = event.data.source return source.kind === 'user' && 'rpcId' in source && source.rpcId === requestId diff --git a/packages/client/ui-conversation/src/host/continuation.ts b/packages/api/session-controller/src/continuation.ts similarity index 65% rename from packages/client/ui-conversation/src/host/continuation.ts rename to packages/api/session-controller/src/continuation.ts index ef2239338e..8673cfc731 100644 --- a/packages/client/ui-conversation/src/host/continuation.ts +++ b/packages/api/session-controller/src/continuation.ts @@ -1,5 +1,5 @@ /** Constant-size continuation eligibility over the complete durable Session. */ -import type {} from '../continuation-types.ts' +import type {} from './types.ts' import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection' import type { SessionSeq } from '@deepseek-ai/dsh-session/types' import { brandNumber } from '@deepseek-ai/dsh-brand' @@ -15,27 +15,35 @@ const stateSchema = z.object({ admitted: z.boolean(), failedAttempt: z.boolean(), ending: endingSchema, + continuing: z.boolean(), }) -type ContinuationState = z.infer +/** Recoverable manual-continuation facts, including an accepted empty input awaiting its turn. */ +export type ContinuationState = z.infer -const EMPTY: ContinuationState = { admitted: false, failedAttempt: false, ending: null } +const EMPTY: ContinuationState = { admitted: false, failedAttempt: false, ending: null, continuing: false } -/** Eligibility requires admitted human input and a failed settlement in the last request step. */ +/** Eligibility requires admitted human work and a failed settlement in the last request step. */ export const continuationProjectionDefinition = { key: 'conversationContinuation', - stateVersion: 1, + stateVersion: 2, stateSchema, init: (): ContinuationState => EMPTY, apply: (state, event) => { switch (event.type) { + case 'agent/inbox/spliced': + if (state.continuing && event.data.target === 'next-turn' && event.data.outcome === 'canceled' + && event.data.start === 0 && (event.data.removedCount ?? 0) > 0) return { ...state, continuing: false } + return state.ending !== null && !state.continuing && event.data.target === 'next-turn' + && event.data.inserted.some(message => message.source.kind === 'user' && message.content.length === 0) + ? { ...state, continuing: true } : state case 'turn/start': - return EMPTY + return state.continuing ? { ...EMPTY, admitted: true } : EMPTY case 'step/start': case 'assistant/message': return state.failedAttempt ? { ...state, failedAttempt: false } : state case 'user/message': - return !state.admitted && event.surfaceOp === 'append' && event.data.source.kind === 'user' + return !state.admitted && event.surfaceOp === 'append' && event.data.source.kind === 'user' && event.data.content.length > 0 ? { ...state, admitted: true } : state case 'assistant/attempt': return state.failedAttempt ? state : { ...state, failedAttempt: true } diff --git a/packages/api/session-controller/src/index.ts b/packages/api/session-controller/src/index.ts index 94d08c77e3..f87c4f0b0f 100644 --- a/packages/api/session-controller/src/index.ts +++ b/packages/api/session-controller/src/index.ts @@ -192,6 +192,8 @@ export class SessionController extends TypertRemoteService { this.agents = new ApiSessionAgentController(ctx) this.agents.initialize() this.commands = new SessionCommandController(ctx, this.agents, process.cwd()) + await this.commands.initialize() + ctx.fiber.assertActive() ctx.effect(() => ctx.fileUploads.registerAgentResolver(async (sessionId) => { const result = await this.agents.resolveAgent(sessionId) if ('error' in result) throw result.error @@ -464,7 +466,7 @@ export class SessionController extends TypertRemoteService { @Remote('prompt') async prompt(request: SessionPromptRequest, signal: AbortSignal): Promise { signal.throwIfAborted() - if (!hasPromptContent(request.content)) { + if (request.content.length > 0 && !hasPromptContent(request.content)) { throw new RemoteError('gateway/bad-request', 'prompt content must include non-whitespace text or an attachment', {}) } const clientTimeZone = request.clientTimeZone === undefined diff --git a/packages/api/session-controller/src/types.ts b/packages/api/session-controller/src/types.ts index e3e2ea375a..798ca0e04c 100644 --- a/packages/api/session-controller/src/types.ts +++ b/packages/api/session-controller/src/types.ts @@ -8,7 +8,7 @@ import type { import type { Branded } from '@deepseek-ai/dsh-brand' import type { LlmAttemptId, MessageId } from '@deepseek-ai/dsh-llm/brand' import type { TextBlock } from '@deepseek-ai/dsh-llm' -import type { SessionId, SessionSeqCursor } from '@deepseek-ai/dsh-session/types' +import type { SessionId, SessionSeq, SessionSeqCursor } from '@deepseek-ai/dsh-session/types' import type { SessionProjectionMap, SessionProjectionSummaryExcluded, SessionProjectionSummaryValues } from '@deepseek-ai/dsh-session-projection/types' import type { Principal } from '@deepseek-ai/dsh-typert-protocol' import type { JsonValue } from '@deepseek-ai/dsh-util-values' @@ -16,6 +16,8 @@ import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types' declare module '@deepseek-ai/dsh-session-projection/types' { interface SessionProjectionMap { + /** Eligible latest failed turn/end for an empty prompt, or null. */ + conversationContinuation: SessionSeq | null /** Persisted facts used to summarize a Session without activating it. */ sessionListMetadata: SessionListMetadata /** @@ -317,11 +319,11 @@ export interface SessionForkValue { /** Session prompt request. */ export interface SessionPromptRequest { - /** Client-minted identity persisted on the exact accepted user message. */ + /** Client-minted identity persisted on the accepted inbox input, including empty continuation. */ readonly requestId: SessionRequestId readonly sessionId: SessionId readonly mode: 'queue' | 'steer' - /** At least one non-whitespace text part or attachment. */ + /** Non-whitespace text or attachments; an empty array continues an eligible failed idle Session. */ readonly content: readonly PromptContentPart[] readonly clientTimeZone?: string } diff --git a/packages/client/ui-conversation/tests/continuation-fixture.ts b/packages/api/session-controller/tests/continuation-fixture.ts similarity index 100% rename from packages/client/ui-conversation/tests/continuation-fixture.ts rename to packages/api/session-controller/tests/continuation-fixture.ts diff --git a/packages/client/ui-conversation/tests/continuation.host.spec.ts b/packages/api/session-controller/tests/continuation.host.spec.ts similarity index 78% rename from packages/client/ui-conversation/tests/continuation.host.spec.ts rename to packages/api/session-controller/tests/continuation.host.spec.ts index 1b0dd1c7b3..f87b29809f 100644 --- a/packages/client/ui-conversation/tests/continuation.host.spec.ts +++ b/packages/api/session-controller/tests/continuation.host.spec.ts @@ -5,9 +5,9 @@ import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import { createAssistantMessage, createUserMessage } from '@deepseek-ai/dsh-llm' import type { SessionEvent, TurnEndReason } from '@deepseek-ai/dsh-session/types' import { expect, it, onTestFinished, vi } from 'vitest' -import * as plugin from '../src/index.ts' -import { continuationProjectionDefinition as definition } from '../src/host/continuation.ts' -import { paginate } from '@deepseek-ai/dsh-api-session-controller/src/history.ts' + +import { continuationProjectionDefinition as definition } from '../src/continuation.ts' +import { paginate } from '../src/history.ts' import { continuationTurn as turn } from './continuation-fixture.ts' const failed = (code: string): TurnEndReason => ({ kind: 'error', error: { code, message: code } }) @@ -86,7 +86,7 @@ it('publishes a complete-log result independently of the visible page and releas onTestFinished(() => ctx.fiber.dispose()) await ctx.plugin(SessionStore) await ctx.plugin(SessionProjectionRegistry) - const fiber = await ctx.plugin(plugin) + const fiber = await ctx.sessionProjections.register(definition) const session = ctx.sessions.create(SessionId('continued')) expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBeNull() const history: SessionEvent[] = [] @@ -111,7 +111,7 @@ it('publishes a complete-log result independently of the visible page and releas expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBe(ending) expect(changed.mock.calls.map(([, key, value]) => [key, value])).toEqual([['conversationContinuation', null], ['conversationContinuation', ending]]) expect(ctx.sessionProjections.checkpoint(session).conversationContinuation) - .toEqual({ ver: 1, seq: ending, val: { admitted: false, failedAttempt: false, ending } }) + .toEqual({ ver: 2, seq: ending, val: { admitted: false, failedAttempt: false, ending, continuing: false } }) session.append('turn/start', { turn: 2 }) expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBeNull() expect(changed).toHaveBeenCalledTimes(3) @@ -124,10 +124,10 @@ it('restores valid, absent and outdated checkpoints through the registry', async onTestFinished(() => ctx.fiber.dispose()) await ctx.plugin(SessionStore) await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(plugin) + await ctx.sessionProjections.register(definition) const session = ctx.sessions.create(SessionId('restored')) const log = events(failed('SERVER')) - const checkpoint = { conversationContinuation: { ver: 1, seq: SessionSeq(2), val: fold(log.slice(0, 3)) } } + const checkpoint = { conversationContinuation: { ver: 2, seq: SessionSeq(2), val: fold(log.slice(0, 3)) } } const restore = (rows: typeof checkpoint | Record, tail: readonly SessionEvent[], base: number) => ctx.sessionProjections.restore(rows, tail, SessionLogOffset(base), session.header, session.inheritedEventCount) expect(restore(checkpoint, log.slice(3), 3).snapshot.values.conversationContinuation).toBe(5) @@ -137,3 +137,32 @@ it('restores valid, absent and outdated checkpoints through the registry', async expect(() => restore(outdated, log.slice(3), 3)).toThrow('re-read from seq 0') expect(() => restore({}, log.slice(3), 3)).toThrow('re-read from seq 0') }) + +it('carries admitted work through an empty continuation and retires a canceled queued control', async () => { + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + await ctx.plugin(SessionStore) + await ctx.plugin(SessionProjectionRegistry) + const handle = await ctx.sessionProjections.register(definition) + const session = ctx.sessions.create(SessionId('empty-repeat')) + for (const event of events(failed('SERVER'))) { + if (event.type === 'user/message') session.append(event.type, event.data, { surfaceOp: 'append' }) + else if (event.type === 'turn/start') session.append(event.type, event.data) + else if (event.type === 'step/start' || event.type === 'step/end') session.append(event.type, event.data) + else if (event.type === 'assistant/attempt') session.append(event.type, event.data) + else if (event.type === 'turn/end') session.append(event.type, event.data) + } + const input = createUserMessage({ content: [], source: { kind: 'user' } }) + session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [input] }) + expect(handle.stateOf(session).continuing).toBe(true) + session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, removedCount: 1, inserted: [], outcome: 'canceled' }) + expect(handle.stateOf(session).continuing).toBe(false) + session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [input] }) + session.append('turn/start', { turn: 2 }) + expect(handle.stateOf(session).admitted).toBe(true) + session.append('step/start', { turn: 2, step: 1 }) + session.append('assistant/attempt', { turn: 2, step: 1, stream: [] }) + session.append('step/end', { turn: 2, step: 1 }) + const ending = session.append('turn/end', { turn: 2, reason: failed('SERVER') }).seq + expect(handle.stateOf(session).ending).toBe(ending) +}) diff --git a/packages/api/session-controller/tests/empty-prompt.host.spec.ts b/packages/api/session-controller/tests/empty-prompt.host.spec.ts new file mode 100644 index 0000000000..5f8d913f40 --- /dev/null +++ b/packages/api/session-controller/tests/empty-prompt.host.spec.ts @@ -0,0 +1,82 @@ +/** Empty prompt admission over the real AgentLoop and the Session Remote owner. */ +import { Context } from '@deepseek-ai/cordis' +import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' +import { createUserMessage, LlmError } from '@deepseek-ai/dsh-llm' +import { SessionId } from '@deepseek-ai/dsh-session' +import { expect, it, onTestFinished } from 'vitest' +import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' +import type { SessionPromptRequest, SessionRequestId } from '../src/types.ts' +import { createSessionTestRemote } from './test-remote.ts' + +async function setup() { + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + await mountAgentLoopTestDependencies(ctx) + await ctx.plugin(AgentLoop, { agents: [] }) + const fail = (): never => { throw new LlmError('busy', 'SERVER') } + const adapter = new MockAdapter([fail, fail, textResponse('done')]) + ctx.effect(() => ctx.llm.registerAdapter(['mock'], adapter)) + const remote = await createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'mock', model: 'mock' }), cwd: '/workspace' }) + const agent = await ctx.agentLoop.create(SessionId('empty-prompt'), { provider: 'mock', model: 'mock' }) + const request = (id: string, mode: SessionPromptRequest['mode'] = 'queue'): SessionPromptRequest => ({ + sessionId: agent.id, requestId: id as SessionRequestId, content: [], mode, + }) + return { ctx, remote, agent, adapter, request } +} + +it('admits repeated empty prompts after failures without extra user messages or duplicate requests', async () => { + const { remote, agent, adapter, request } = await setup() + agent.followup(createUserMessage({ content: [{ type: 'text', text: 'original task' }], source: { kind: 'user' } })) + await agent.whenIdle() + const first = request('continue-1') + expect((await remote.prompt(first)).ok).toBe(true) + await agent.whenIdle() + expect(adapter.requests).toHaveLength(2) + expect((await remote.prompt(first)).ok).toBe(true) + await agent.whenIdle() + expect(adapter.requests).toHaveLength(2) + expect((await remote.prompt(request('continue-2'))).ok).toBe(true) + await agent.whenIdle() + expect(adapter.requests).toHaveLength(3) + const events = agent.session.snapshotEvents() + expect(events.filter(event => event.type === 'user/message' && event.data.source.kind === 'user')).toHaveLength(1) + expect(events.flatMap(event => event.type === 'turn/end' ? [event.data.reason.kind] : [])).toEqual(['error', 'error', 'completed']) + expect(events.filter(event => event.type === 'turn/start')).toHaveLength(3) + expect((await remote.prompt(request('after-completion'))).ok).toBe(false) +}) + +it('refuses empty fresh sessions, steering, queued human input and maintenance without waking work', async () => { + const { remote, agent, adapter, request } = await setup() + expect((await remote.prompt(request('empty-fresh'))).ok).toBe(false) + expect(adapter.requests).toHaveLength(0) + agent.followup(createUserMessage({ content: [{ type: 'text', text: 'original task' }], source: { kind: 'user' } })) + await agent.whenIdle() + expect((await remote.prompt(request('empty-steer', 'steer'))).ok).toBe(false) + const pending = createUserMessage({ content: [{ type: 'text', text: 'queued' }], source: { kind: 'user' } }) + agent.inbox.append('next-turn', pending) + expect((await remote.prompt(request('empty-queued'))).ok).toBe(false) + agent.inbox.clear() + agent.inject(pending) + expect((await remote.prompt(request('empty-steering'))).ok).toBe(false) + agent.inbox.clear() + await agent.runMaintenance(async () => { + expect((await remote.prompt(request('empty-maintenance'))).ok).toBe(false) + }) + expect(adapter.requests).toHaveLength(1) +}) + +it('consumes injected context with the empty prompt', async () => { + const { ctx, remote, agent, adapter, request } = await setup() + agent.followup(createUserMessage({ content: [{ type: 'text', text: 'original task' }], source: { kind: 'user' } })) + await agent.whenIdle() + agent.inject(createUserMessage({ content: [{ type: 'text', text: 'approval context' }], source: { kind: 'user-approval' } })) + agent.model.select({ provider: 'mock', model: 'changed' }) + expect((await remote.prompt(request('empty-context'))).ok).toBe(true) + await agent.whenIdle() + expect(adapter.requests).toHaveLength(2) + expect(JSON.stringify(adapter.requests[1]!.messages)).toContain('approval context') + expect(adapter.requests[1]!.model).toBe('changed') + expect(agent.inbox.nextStep).toHaveLength(0) + expect(ctx.sessionProjections.snapshot(agent.session).values.conversationContinuation).not.toBeNull() +}) diff --git a/packages/api/session-controller/tests/session-cold.host.spec.ts b/packages/api/session-controller/tests/session-cold.host.spec.ts index 560c05514f..5139baf644 100644 --- a/packages/api/session-controller/tests/session-cold.host.spec.ts +++ b/packages/api/session-controller/tests/session-cold.host.spec.ts @@ -924,7 +924,6 @@ describe('sessions.prompt synchronous rejection', () => { }) const initialEvents = session.snapshotEvents() const rejectedContent: readonly SessionPromptRequest['content'][] = [ - [], [{ type: 'text', text: '' }], [{ type: 'text', text: ' \t\n' }, { type: 'text', text: '' }], ] diff --git a/packages/api/session-controller/tsconfig.host.json b/packages/api/session-controller/tsconfig.host.json index acadf82099..168fd5a0c7 100644 --- a/packages/api/session-controller/tsconfig.host.json +++ b/packages/api/session-controller/tsconfig.host.json @@ -25,7 +25,8 @@ "src/list.ts", "src/media-references.ts", "src/archived-session-gate.ts", - "src/skill-catalog.ts" + "src/skill-catalog.ts", + "src/continuation.ts" ], "references": [ { @@ -126,6 +127,9 @@ }, { "path": "../../session/working-directory" + }, + { + "path": "../../util/brand" } ] } diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index 2b4fd0c5db..6ee1b33613 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -21,26 +21,26 @@ en: cc4dafd1aae02468 zh: be6b67724b7ac4a3 /deepseek-ai-dsh-client-ui-conversation/shell-and-standard-props: - en: ac170c9895e17840 - zh: c7603d365134ce89 + en: 8f3eb5f46b48df30 + zh: a1a1da12767f4e92 /deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries: en: bb1244310f5c0382 zh: 824b84077432b559 /deepseek-ai-dsh-client-ui-conversation/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 -/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-message: - en: 30f3836663c881ca - zh: ee3e1d1df8603fad -/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-message/what-the-model-sees: - en: d566958ff2f23584 - zh: f6ebea015ba61872 -/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-message/token-effect: - en: 0ad06a5163a62c50 - zh: d25b93fcb2783566 -/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-message/kv-cache-effect: - en: 326e23f5913e3fc0 - zh: 865969505814e0af +/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request: + en: 6a7ae99570c9c007 + zh: 26e86063e97d82c7 +/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/what-the-model-sees: + en: b1376c710fa71ce3 + zh: e751e1a490c36bd8 +/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/token-effect: + en: a5bbf55d50568fa2 + zh: 6c43af211055db93 +/deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/kv-cache-effect: + en: bbb4b8186c85f62d + zh: 222397b869a60df2 /deepseek-ai-dsh-client-ui-conversation/known-limitations-and-deferred-work: en: d76daa90b6d6f600 zh: 37a9f7d402b2552b diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index eadcdf2467..4a408c5950 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -76,9 +76,9 @@ The composer measures its expanded control groups after size, content, visibilit The context meter follows the [statistics-control radius](../../../docs/ui-radius.md#radius-scale). -An empty, idle ordinary Session composer offers a play action after `RATE_LIMIT`, `SERVER`, `TIMEOUT`, `TRANSPORT`, `EMPTY_RESPONSE`, or `STREAM_CLOSED` ends its latest turn. The failed turn must include admitted ordinary user input and a settled failed model attempt. Clicking submits a normal user message in the current interface language; drafts and attachments keep the Send action. Continued requests retain committed history and use the existing submission lifecycle. +An empty, idle ordinary Session composer offers a play action after `RATE_LIMIT`, `SERVER`, `TIMEOUT`, `TRANSPORT`, `EMPTY_RESPONSE`, or `STREAM_CLOSED` ends its latest turn. The failed turn must include admitted ordinary user input and a settled failed model attempt. Clicking submits `content: []` through the existing Session prompt API; drafts and attachments keep the Send action. A new balanced turn uses committed history without adding a user message. Refusal or transport failure leaves the draft empty and reports the error through the ordinary submission lifecycle. -The Host requires `sessionProjections` and publishes `conversationContinuation`; eligibility is restored from Session history independently of the browser's loaded history pages. +The Session API owns `conversationContinuation`, shared by Host admission and browser presentation. It restores eligibility from complete Session history and preserves it across repeated failed empty continuations, independently of loaded browser pages. The composer uses the shared [text-input focus policy](../ui-primitives/README.md#use-this-package) on mount, Session switching, and unlock. Identified phones and tablets require tapping the input; desktop devices retain automatic focus. On these mobile devices, a submission that empties the focused draft moves focus to the non-text composer card, dismissing the keyboard while retaining its navigation position. Rejected submissions, retained newer draft text, and another control already holding focus preserve focus. The [Lexical patch](../../../patches/lexical@0.49.0.patch) keeps caret scrolling in the same coordinate system as DOM geometry when a mobile keyboard pans the page. @@ -189,19 +189,19 @@ The selector must be a pure function of the owner currency. Its non-null return ## Model Experience -### Manual continuation message +### Manual continuation request #### What the model sees -The play action submits one ordinary user message using the interface language selected at the click: Chinese `继续`, English/French/Portuguese `continue`, Japanese `続けて`, Spanish `continúa`, German `weiter`, or Russian `продолжай`. The admitted message is stored in the Session log and included in subsequent model history; the action does not reconstruct text from the failed attempt. +The play action starts a new request from committed Session history. Its empty input is a durable inbox command, not a model-visible user message; it adds no localized or hidden continuation instruction. Pending injected context follows ordinary admission. #### Token effect -Each continuation admitted into model history adds its short localized text and the ordinary user-message overhead. The exact token count depends on the model tokenizer. A submission refused or failed before admission adds no continuation text to model history. +The action adds no continuation text or empty user-message overhead. The request still incurs the ordinary cost of retained history, current prompt sections, tool declarations, and any newly admitted context. #### KV Cache effect -The continuation message extends retained history without replacing earlier messages or prompt sections. It preserves an already-reusable prefix; provider cache availability, reuse policy, and eviction remain outside this package's control. +The control input does not alter model history. Prefix reuse still depends on the current model, prompt, tools, injected context, and provider cache policy. ## Known Limitations and Deferred Work diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index 3573e9c7b4..85df62d84b 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -76,9 +76,9 @@ Queue 预览启用空白折叠和文档边缘 trim,保留 emoji,并使用 CS 上下文占用率入口遵循[统计控件圆角规则](../../../docs/ui-radius.zh.md#radius-scale)。 -普通 Session 的输入框在空草稿且空闲时,若最新一轮以 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`、`EMPTY_RESPONSE` 或 `STREAM_CLOSED` 结束,会提供播放图标按钮。失败轮次必须包含已进入模型历史的普通用户输入和已落盘的失败模型请求尝试。点击后按当前界面语言提交一条普通用户消息;存在草稿或附件时保留发送按钮。继续请求保留已提交历史,并沿用现有提交流程。 +空闲且草稿为空的普通 Session,在最新轮次以 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`、`EMPTY_RESPONSE` 或 `STREAM_CLOSED` 结束后显示播放按钮。失败工作必须有已准入的普通用户输入和失败请求尝试。点击通过现有 Session prompt API 提交 `content: []`;已有草稿或附件仍使用发送按钮。新轮次正常闭合,使用已提交历史而不新增用户消息。请求被拒绝或传输失败时,草稿保持为空,错误沿用普通提交流程展示。 -Host 需要 `sessionProjections`,并发布 `conversationContinuation`;继续资格从 Session 历史恢复,不受浏览器已加载历史分页的影响。 +Session API 拥有 `conversationContinuation`,Host 准入和浏览器展示共用该投影。它从完整 Session 历史恢复资格,并在空输入继续再次失败时保留资格,不依赖浏览器已加载的历史分页。 composer 在挂载、切换 Session 和解除锁定时使用共享的[文字输入框聚焦策略](../ui-primitives/README.zh.md#use-this-package)。识别出的手机和平板需要点击输入框;桌面设备保留自动聚焦。在这些移动设备上,提交清空正在聚焦的草稿后,焦点移到不接受文字输入的 composer 卡片,收起键盘并保留键盘导航位置。被拒绝的提交、保留的新草稿文字,以及已经聚焦的其他控件均保留原有焦点。移动端键盘平移页面时,[Lexical 补丁](../../../patches/lexical@0.49.0.patch)让光标滚动计算与 DOM 几何使用同一坐标系。 @@ -189,19 +189,19 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match ## 模型体验 -### 手动继续消息 +### 手动继续请求 #### 模型看到什么 -播放按钮按点击时选中的界面语言提交一条普通用户消息:中文为 `继续`,英语、法语和葡萄牙语为 `continue`,日语为 `続けて`,西班牙语为 `continúa`,德语为 `weiter`,俄语为 `продолжай`。已准入的消息写入 Session 日志并进入后续模型历史;该操作不从失败尝试中重建文本。 +播放按钮基于已提交的 Session 历史发起新请求。空输入作为持久 inbox 控制记录,不成为模型可见的用户消息;不会添加本地化或隐藏的继续指令。待注入上下文遵循普通准入流程。 #### Token 影响 -每条进入模型历史的继续消息增加其本地化短文本和普通用户消息的封装开销。准确 token 数取决于模型 tokenizer。在准入前被拒绝或失败的提交不会向模型历史添加继续文本。 +该操作不增加继续文本或空用户消息的封装开销。请求仍承担保留历史、当前提示词、工具声明以及新准入上下文的正常输入成本。 #### KV Cache 影响 -继续消息在保留历史后追加,不替换更早的消息或 prompt section。它保留原本可复用的前缀;提供方缓存的可用性、复用策略和淘汰规则不由本包控制。 +控制输入不改变模型历史。前缀复用仍取决于当前模型、提示词、工具、注入上下文及提供方缓存策略。 ## 已知限制与暂缓事项 diff --git a/packages/client/ui-conversation/package.json b/packages/client/ui-conversation/package.json index c4b2317d94..d38d3d7ebe 100644 --- a/packages/client/ui-conversation/package.json +++ b/packages/client/ui-conversation/package.json @@ -47,9 +47,7 @@ }, "license": "MIT", "dependencies": { - "@deepseek-ai/schemastery": "workspace:~", - "zod": "4.4.3", - "@deepseek-ai/dsh-brand": "workspace:*" + "@deepseek-ai/schemastery": "workspace:~" }, "peerDependencies": { "@deepseek-ai/cordis": "workspace:~" @@ -98,7 +96,9 @@ "@deepseek-ai/dsh-client-shortcuts": "workspace:*", "@deepseek-ai/dsh-client-product-analytics": "workspace:*", "@deepseek-ai/dsh-message-quotes": "workspace:*", - "@deepseek-ai/dsh-session-projection": "workspace:*" + "@deepseek-ai/dsh-session-projection": "workspace:*", + "@deepseek-ai/dsh-brand": "workspace:*", + "zod": "4.4.3" }, "files": [ "locale/*.json", diff --git a/packages/client/ui-conversation/src/client/contract/input.ts b/packages/client/ui-conversation/src/client/contract/input.ts index 82f030c775..9cc7e4b566 100644 --- a/packages/client/ui-conversation/src/client/contract/input.ts +++ b/packages/client/ui-conversation/src/client/contract/input.ts @@ -352,6 +352,8 @@ export type InputEvent = /** Enter submission with the current clipboard projection, its reference chips, its quotes, and its attachments. */ | { readonly type: 'enter' + /** Explicit continuation may submit an empty draft through the default sink. */ + readonly allowEmpty?: boolean readonly mode: InputSubmitMode readonly draft: string readonly occurrences: readonly Occurrence[] diff --git a/packages/client/ui-conversation/src/client/contract/slots.ts b/packages/client/ui-conversation/src/client/contract/slots.ts index 068fb0026e..4d701b2af8 100644 --- a/packages/client/ui-conversation/src/client/contract/slots.ts +++ b/packages/client/ui-conversation/src/client/contract/slots.ts @@ -530,7 +530,7 @@ export interface ComposerBarInjected { toggleCommandMenu: ((selection: EditSelection) => void) | undefined stop: (() => void) | undefined /** - * Submit a localized continuation message for the displayed failure. + * Submit empty continuation input for the displayed failure. * @param ending - sequence of the displayed failed turn/end. */ continueGeneration: ((ending: SessionSeq) => void) | undefined diff --git a/packages/client/ui-conversation/src/client/index.ts b/packages/client/ui-conversation/src/client/index.ts index 65d7edc6b2..d4f06accc5 100644 --- a/packages/client/ui-conversation/src/client/index.ts +++ b/packages/client/ui-conversation/src/client/index.ts @@ -1,6 +1,5 @@ /** Browser Conversation assemble core, React adapter, shell, input, and text projection plugin. */ export type {} from './user-text/session-reference-rule.ts' -export type {} from '../continuation-types.ts' /** Declaration-merged node payloads; IDs also identify the corresponding keyed renderer. */ export interface UserTextDataMap { diff --git a/packages/client/ui-conversation/src/client/input/facade.ts b/packages/client/ui-conversation/src/client/input/facade.ts index 6306216b1c..e82acfdd35 100644 --- a/packages/client/ui-conversation/src/client/input/facade.ts +++ b/packages/client/ui-conversation/src/client/input/facade.ts @@ -388,7 +388,7 @@ export class SessionInputShell implements SessionInput { * (adjudicating/submitting/sending) force-closes the transient layers: the * popup dismisses and the menu tracks frozen. */ - submit(mode: InputSubmitMode = 'queue', source?: 'click' | 'enter'): void { + submit(mode: InputSubmitMode = 'queue', source?: 'click' | 'enter', allowEmpty = false): void { if (this.disposed) return const timestamp = Date.now() let state: MessageSubmissionState | undefined @@ -409,6 +409,7 @@ export class SessionInputShell implements SessionInput { } this.dispatchRun({ type: 'enter', + ...allowEmpty ? { allowEmpty: true } : {}, mode, draft: this.projection.clipboardText, occurrences: [...this.projection.occurrences], @@ -726,6 +727,10 @@ export class SessionInputShell implements SessionInput { }) const send = (text: string): void => { if (this.dead(attempt)) return + if (text === '' && draft !== '' && attachmentIds.length === 0) { + this.settleSink(attempt, Promise.resolve({ kind: 'success' })) + return + } this.settleSink(attempt, this.deps.defaultSink(text, attachmentIds, mode, attempt.signal, () => { this.acceptSink(attempt) })) } if (!(prompt instanceof Promise)) { send(prompt); return } diff --git a/packages/client/ui-conversation/src/client/input/hub.ts b/packages/client/ui-conversation/src/client/input/hub.ts index 36ea1c6b4b..d1454d29d7 100644 --- a/packages/client/ui-conversation/src/client/input/hub.ts +++ b/packages/client/ui-conversation/src/client/input/hub.ts @@ -25,7 +25,6 @@ import type { ComposerBlocks } from '../contract/composer-blocks.ts' import type { InputSubmitMode } from '../contract/composer-submission.ts' import type { PopupDismissFace } from './facade.ts' import { SessionInputShell } from './facade.ts' -import type {} from '../../continuation-types.ts' import { reportMessageSubmission } from './submission-analytics.ts' import { readConversationDraft } from '../stores.ts' @@ -239,7 +238,7 @@ export class InputHub implements SessionInputResolver { } /** - * Submit localized continuation text through the ordinary composer. Stale + * Submit empty continuation input through the ordinary composer. Stale * failures, active work, and drafts keep their current input unchanged. * @param id - retained ordinary Session identity. * @param ending - failure the displayed button addresses. @@ -256,8 +255,7 @@ export class InputHub implements SessionInputResolver { || input.draft.trim() !== '' || input.attachmentIds.length > 0 || (inbox?.['next-turn'].length ?? 0) > 0 || this.conversation().blocks.storeFor(id).getSnapshot() !== undefined) return - shell.setDraft(this.t('input.continueMessage')) - shell.submit('queue', 'click') + shell.submit('queue', 'click', true) } /** @@ -304,7 +302,9 @@ export class InputHub implements SessionInputResolver { signal: AbortSignal, accept: () => void, ): Promise { - if (text === '' && attachmentIds.length === 0) return Promise.resolve({ kind: 'success' }) + if (text === '' && attachmentIds.length === 0) { + return session.prompt([], mode, signal).then(result => result.ok ? { kind: 'success' } : { kind: 'error' }) + } return this.conversation().sendSession(session, text, attachmentIds, mode, signal, accept) } diff --git a/packages/client/ui-conversation/src/client/input/machine.ts b/packages/client/ui-conversation/src/client/input/machine.ts index 16fe7dd034..8545f964e5 100644 --- a/packages/client/ui-conversation/src/client/input/machine.ts +++ b/packages/client/ui-conversation/src/client/input/machine.ts @@ -144,7 +144,7 @@ export class SubmitMachine { return [{ type: 'begin-submit', attempt, claim: this.claim, args: argsAfter(draft, this.claim.token) }] } const trimmed = draft.trim() - if (trimmed === '' && attachmentIds.length === 0) return [] + if (trimmed === '' && attachmentIds.length === 0 && ev.allowEmpty !== true) return [] const attempt = this.beginAttempt(mode, draft, occurrences, quotes, attachmentIds, submission) if (trimmed.startsWith('/')) { this.phase = 'adjudicating' diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts index 0ace610330..0598cee7d9 100644 --- a/packages/client/ui-conversation/src/client/locales.ts +++ b/packages/client/ui-conversation/src/client/locales.ts @@ -29,8 +29,6 @@ export const zh = { 'input.stop': '停止生成', 'input.send': '发送消息', 'input.continue': '继续生成', - // Model-visible text, durably logged when admitted as an ordinary user message. - 'input.continueMessage': '继续', 'input.sending': '正在发送消息', 'input.send.queue': '排队发送', 'input.send.steer': '插话发送', @@ -416,7 +414,6 @@ export const en = { 'input.stop': 'Stop generating', 'input.send': 'Send message', 'input.continue': 'Continue generating', - 'input.continueMessage': 'continue', 'input.sending': 'Sending message', 'input.send.queue': 'Queue message', 'input.send.steer': 'Steer message', @@ -801,7 +798,6 @@ export const ja = { 'input.stop': '生成を停止', 'input.send': 'メッセージを送信', 'input.continue': '生成を続ける', - 'input.continueMessage': '続けて', 'input.sending': 'メッセージを送信中', 'input.send.queue': 'メッセージをキューに追加', 'input.send.steer': '実行中の指示にメッセージを追加', @@ -1184,7 +1180,6 @@ export const es = { 'input.stop': 'Detener generación', 'input.send': 'Enviar mensaje', 'input.continue': 'Continuar generando', - 'input.continueMessage': 'continúa', 'input.sending': 'Enviando mensaje', 'input.send.queue': 'mensaje de cola', 'input.send.steer': 'Redirigir mensaje', @@ -1569,7 +1564,6 @@ export const fr = { 'input.stop': 'Arrêter la génération', 'input.send': 'Envoyer le message', 'input.continue': 'Continuer la génération', - 'input.continueMessage': 'continue', 'input.sending': 'Envoi du message', 'input.send.queue': "Message de file d'attente", 'input.send.steer': 'Guider avec ce message', @@ -1954,7 +1948,6 @@ export const de = { 'input.stop': 'Generierung stoppen', 'input.send': 'Nachricht senden', 'input.continue': 'Generierung fortsetzen', - 'input.continueMessage': 'weiter', 'input.sending': 'Nachricht wird gesendet', 'input.send.queue': 'Warteschlangennachricht', 'input.send.steer': 'Mit Nachricht lenken', @@ -2339,7 +2332,6 @@ export const ru = { 'input.stop': 'Остановить генерацию', 'input.send': 'Отправить сообщение', 'input.continue': 'Продолжить генерацию', - 'input.continueMessage': 'продолжай', 'input.sending': 'Отправка сообщения', 'input.send.queue': 'Сообщение в очереди', 'input.send.steer': 'Направить сообщение', @@ -2724,7 +2716,6 @@ export const pt = { 'input.stop': 'Parar geração', 'input.send': 'Enviar mensagem', 'input.continue': 'Continuar gerando', - 'input.continueMessage': 'continue', 'input.sending': 'Enviando mensagem', 'input.send.queue': 'Mensagem de fila', 'input.send.steer': 'Orientar com mensagem', diff --git a/packages/client/ui-conversation/src/continuation-types.ts b/packages/client/ui-conversation/src/continuation-types.ts deleted file mode 100644 index 9acece7469..0000000000 --- a/packages/client/ui-conversation/src/continuation-types.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** Host-derived eligibility for an explicit generation-continuation message. */ -import type { SessionSeq } from '@deepseek-ai/dsh-session/types' - -declare module '@deepseek-ai/dsh-session-projection/types' { - interface SessionProjectionMap { - /** Eligible latest turn/end, or null while no settled request can be continued. */ - conversationContinuation: SessionSeq | null - } -} diff --git a/packages/client/ui-conversation/src/index.ts b/packages/client/ui-conversation/src/index.ts index 6c7515a448..6ef779d42b 100644 --- a/packages/client/ui-conversation/src/index.ts +++ b/packages/client/ui-conversation/src/index.ts @@ -1,6 +1,5 @@ -/** Host continuation eligibility and browser conversation preferences. */ +/** Browser conversation preferences. */ import type {} from '@deepseek-ai/dsh-settings' -export type {} from './continuation-types.ts' import type { Volatile, Context } from '@deepseek-ai/cordis' import type { BusyEnterBehavior } from './submission-settings.ts' @@ -8,7 +7,6 @@ import z from '@deepseek-ai/schemastery' import { BUSY_ENTER_FIELD } from './submission-settings.ts' import { ConversationSettingsFields } from './submission-settings.ts' -import { continuationProjectionDefinition } from './host/continuation.ts' export { BUSY_ENTER_BEHAVIORS, BUSY_ENTER_FIELD, CONVERSATION_SETTINGS_NAMESPACE, @@ -26,14 +24,12 @@ export const Config = z.object({ [BUSY_ENTER_FIELD]: ConversationSettingsFields[BUSY_ENTER_FIELD].volatile(), }) -/** Required registry for the durable generation-continuation projection. */ -export const inject = ['sessionProjections'] +/** Settings integration is optional and does not block activation. */ +export const inject = [] -/** Register generation-continuation eligibility and browser preferences. +/** Register browser conversation preferences. * @param ctx Plugin context used for optional settings presentation. - * @returns when continuation state is registered and available to Session carriers. */ -export async function apply(ctx: Context): Promise { +export function apply(ctx: Context): void { ctx.inject(['settings'], (child) => { child.effect(() => child.settings.configure({ auto: false }, ctx.fiber)) }) - await ctx.sessionProjections.register(continuationProjectionDefinition) } diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index 8f2b967e06..a1cf51452a 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -29,7 +29,7 @@ import { createUserMessage, LlmAttemptId } from '@deepseek-ai/dsh-llm' import { MutableSessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' import type { SessionEventWindow } from '@deepseek-ai/dsh-api-session-controller/client' import type { InboxWireState } from '@deepseek-ai/dsh-agent/types' -import { continuationTurn } from './continuation-fixture.ts' +import { continuationTurn } from '../../../api/session-controller/tests/continuation-fixture.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap { @@ -165,7 +165,7 @@ async function bench(renderQuote = false) { } describe('Conversation inject API', () => { - it.each([['zh', '继续'], ['en', 'continue']])('submits one continuation in the current %s locale', async (locale, message) => { + it.each(['zh', 'en'])('submits empty continuation input in the %s locale', async (locale) => { const b = await bench() onTestFinished(() => b.runtime.dispose()) await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } })) @@ -181,7 +181,7 @@ describe('Conversation inject API', () => { composer.continueGeneration!(SessionSeq(5)) expect(composer.keyboard!.snapshot.phase).toBe('sending') await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce() }) - expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([{ type: 'text', text: message }]) + expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([]) expect(b.sessionFake.prompt.mock.calls[0]?.[1]).toBe('queue') pending.resolve({ ok: true, value: { accepted: true } }) await vi.waitFor(() => { expect(composer.keyboard!.snapshot.phase).toBe('plain') }) @@ -322,7 +322,7 @@ describe('Conversation inject API', () => { }, ) - it.each(['refused', 'failed'] as const)('retains a visible editable continuation draft when submission is %s', async (outcome) => { + it.each(['refused', 'failed'] as const)('keeps continuation input empty and exposes submission failure when %s', async (outcome) => { const b = await bench() onTestFinished(() => b.runtime.dispose()) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) @@ -335,11 +335,11 @@ describe('Conversation inject API', () => { else b.sessionFake.prompt.mockRejectedValueOnce(new Error('connection lost')) composer.continueGeneration!(SessionSeq(5)) await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce(); expect(composer.keyboard!.snapshot.phase).toBe('plain') }) - expect(composer.keyboard!.snapshot.draft).toBe('继续') + expect(composer.keyboard!.snapshot.draft).toBe('') if (outcome === 'refused') expect(b.runtime.sessions.binding(ROOT)!.session.getSnapshot().promptError?.error.message).toBe('request refused') else expect(composer.hooks.notices.getSnapshot()).toMatchObject({ level: 'error', text: 'connection lost' }) composer.continueGeneration!(SessionSeq(5)) - expect(b.sessionFake.prompt).toHaveBeenCalledOnce() + await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledTimes(2) }) }) it('renders literal quote text through the actual Factory and keyed node registration', async () => { diff --git a/packages/client/ui-conversation/tests/config.host.spec.ts b/packages/client/ui-conversation/tests/config.host.spec.ts index 402063cedc..25420eff00 100644 --- a/packages/client/ui-conversation/tests/config.host.spec.ts +++ b/packages/client/ui-conversation/tests/config.host.spec.ts @@ -1,6 +1,6 @@ import SessionStore from '@deepseek-ai/dsh-session' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import { Context, FiberState } from '@deepseek-ai/cordis' +import { Context } from '@deepseek-ai/cordis' import { describe, expect, it, onTestFinished } from 'vitest' import * as HostPlugin from '../src/index.ts' import { liveConfig, omitsGeneratedPage } from '../../../settings/settings/tests/live-config.ts' @@ -32,15 +32,3 @@ it('keeps its own instance off the generated Settings pages', () => omitsGenerat await ctx.plugin(SessionProjectionRegistry) return ctx.plugin(HostPlugin) })) - -it('waits for the required projection registry before activation', async () => { - const ctx = new Context() - onTestFinished(() => ctx.fiber.dispose()) - await ctx.plugin(SessionStore) - const fiber = ctx.plugin(HostPlugin) - await ctx.fiber.await() - expect(fiber.state).toBe(FiberState.PENDING) - await ctx.plugin(SessionProjectionRegistry) - await fiber - expect(fiber.state).toBe(FiberState.ACTIVE) -}) diff --git a/packages/client/ui-conversation/tsconfig.host.json b/packages/client/ui-conversation/tsconfig.host.json index cc278423fc..ec30b21bfe 100644 --- a/packages/client/ui-conversation/tsconfig.host.json +++ b/packages/client/ui-conversation/tsconfig.host.json @@ -7,9 +7,7 @@ }, "files": [ "src/submission-settings.ts", - "src/index.ts", - "src/continuation-types.ts", - "src/host/continuation.ts" + "src/index.ts" ], "references": [ { @@ -20,15 +18,6 @@ }, { "path": "../../settings/settings" - }, - { - "path": "../../core/session" - }, - { - "path": "../../session/session-projection" - }, - { - "path": "../../util/brand" } ] } diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 33c53afffa..2a3ecdf694 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -51,8 +51,8 @@ en: eea56903c4ff82f5 zh: c0d5abb5767780ed /deepseek-ai-dsh-agent-loop/further-exploration: - en: 7ac519f8e635acbc - zh: 975e514915dc6c9f + en: 687e42aa91c15be5 + zh: 0e2d860391a9ce89 /deepseek-ai-dsh-agent-loop/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 28b862ff9d..9beebb6543 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -147,6 +147,8 @@ The package-level contract is enough for most consumers; read these when you nee ----- +Empty ordinary input remains an identified inbox command, so it can wake a new turn while adding no `user/message` to the model surface. It passes normal pre-step admission; a rejected or removed input does not authorize a model request. Turn and step endings retain their normal success, failure, and cancellation handling. + ## Model Experience ### Complete conversation request diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index f872d4289e..8bbbd145c5 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -147,6 +147,8 @@ loop 在发布工厂前等待 inbox 与轮次边界注册完成,并将句柄 ----- +空的普通输入仍是带身份的 inbox 控制记录,可以唤醒新轮次,但不向模型消息面添加 `user/message`。它经过正常 pre-step 准入;被拒绝或移除的输入不授权模型请求。轮次和步骤结束沿用正常的成功、失败与取消处理。 + ## 模型体验 ### 完整对话请求 diff --git a/packages/core/agent-loop/src/agent.ts b/packages/core/agent-loop/src/agent.ts index 03c4c0c2bf..619cca96ba 100644 --- a/packages/core/agent-loop/src/agent.ts +++ b/packages/core/agent-loop/src/agent.ts @@ -460,7 +460,7 @@ export class ReactLoopAgent implements Agent { this.session.append('user/message', context, { surfaceOp: 'append' }) contextAdmitted = true } - } else { + } else if (message.source.kind !== 'user' || message.content.length > 0) { this.session.append('user/message', message, { surfaceOp: 'append' }) } } diff --git a/packages/core/agent-loop/tests/request-error.spec.ts b/packages/core/agent-loop/tests/request-error.spec.ts index 5287cb4deb..16c07fcf4e 100644 --- a/packages/core/agent-loop/tests/request-error.spec.ts +++ b/packages/core/agent-loop/tests/request-error.spec.ts @@ -1,5 +1,5 @@ import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, onTestFinished } from 'vitest' import { Context } from '@deepseek-ai/cordis' import AgentRegistry from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' @@ -32,6 +32,31 @@ function fail(message: string, code: string): () => never { } describe('agent/request-error', () => { + it('continues repeated failures through empty followups with balanced turns and one admitted user message', async () => { + const adapter = new MockAdapter([fail('busy', 'SERVER'), fail('still busy', 'SERVER'), textResponse('done')]) + const ctx = await harness(adapter) + onTestFinished(() => ctx.fiber.dispose()) + const agent = await ctx.agentLoop.create(SessionId('empty-continuation'), { provider: 'mock', model: 'mock' }) + agent.followup(createUserMessage({ content: [{ type: 'text', text: 'original task' }], source: { kind: 'user' } })) + await agent.whenIdle() + for (let index = 0; index < 2; index++) { + agent.followup(createUserMessage({ content: [], source: { kind: 'user' } })) + await agent.whenIdle() + } + expect(adapter.requests).toHaveLength(3) + for (const request of adapter.requests) { + expect(request.messages.filter(message => message.role === 'user').map(message => message.content)) + .toEqual([[{ type: 'text', text: 'original task' }]]) + } + const events = agent.session.snapshotEvents() + expect(events.filter(event => event.type === 'user/message')).toHaveLength(1) + expect(events.flatMap(event => event.type === 'turn/start' || event.type === 'turn/end' + ? [`${event.type}:${event.data.turn}`] : [])) + .toEqual(['turn/start:1', 'turn/end:1', 'turn/start:2', 'turn/end:2', 'turn/start:3', 'turn/end:3']) + expect(events.flatMap(event => event.type === 'turn/end' ? [event.data.reason.kind] : [])) + .toEqual(['error', 'error', 'completed']) + }) + it('does not offer middleware failures to request recovery', async () => { const adapter = new MockAdapter([textResponse('unused')]) const ctx = await harness(adapter) diff --git a/packages/core/agent/README.i18n.yaml b/packages/core/agent/README.i18n.yaml index 53a219df1a..3aeeb9a406 100644 --- a/packages/core/agent/README.i18n.yaml +++ b/packages/core/agent/README.i18n.yaml @@ -60,8 +60,8 @@ en: e48eae6c7de88cf8 zh: 1f5b37bdb0e53788 /deepseek-ai-dsh-agent/further-exploration: - en: 4ef57cfafa344581 - zh: 73575876bfdc7a18 + en: d9d9bda2357ef857 + zh: 5fa1368d5f8912e2 /deepseek-ai-dsh-agent/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index a7433347f8..2f7c830c7c 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -151,6 +151,8 @@ The package-level contract is enough for most consumers; read these when you nee ----- +An ordinary `followup` with `content: []` is an explicit request to start a new turn from committed history. It retains the identified inbox receipt and normal lifecycle without creating a model-visible user message. A pre-step rejection or a decision rewritten to an empty batch still prevents the request. + ## Model Experience ### User, steering, and injected messages diff --git a/packages/core/agent/README.zh.md b/packages/core/agent/README.zh.md index a92b8338e0..1eb86a7a5e 100644 --- a/packages/core/agent/README.zh.md +++ b/packages/core/agent/README.zh.md @@ -151,6 +151,8 @@ await handle.agent.whenIdle() ----- +普通 `followup` 的 `content: []` 明确请求基于已提交历史启动新轮次。它保留带身份的 inbox 回执和正常生命周期,不生成模型可见的用户消息。pre-step 拒绝或改写为空批次的决策仍会阻止请求。 + ## 模型体验 ### 用户、steering 与注入消息 diff --git a/packages/core/agent/src/runtime-types.ts b/packages/core/agent/src/runtime-types.ts index 2b1698e39c..58d883721c 100644 --- a/packages/core/agent/src/runtime-types.ts +++ b/packages/core/agent/src/runtime-types.ts @@ -232,6 +232,7 @@ declare module './types.ts' { /** * Route identified input to an inbox boundary and optionally wake the driver. + * Empty ordinary input drives work without becoming a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -245,7 +246,8 @@ declare module './types.ts' { /** * Queue an ordinary follow-up turn and wake the driver. The item becomes the - * sole ordinary message of its own turn. + * sole ordinary message of its own turn. An empty ordinary input starts a + * turn from committed history without appending a model-visible user message. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 489c15f56e..a8056e3e2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4001,15 +4001,9 @@ importers: packages/client/ui-conversation: dependencies: - '@deepseek-ai/dsh-brand': - specifier: workspace:* - version: link:../../util/brand '@deepseek-ai/schemastery': specifier: link:../../../vendor/schemastery version: link:../../../vendor/schemastery - zod: - specifier: 4.4.3 - version: 4.4.3 devDependencies: '@deepseek-ai/cordis': specifier: workspace:~ @@ -4029,6 +4023,9 @@ importers: '@deepseek-ai/dsh-attachment': specifier: workspace:* version: link:../../attachment/attachment + '@deepseek-ai/dsh-brand': + specifier: workspace:* + version: link:../../util/brand '@deepseek-ai/dsh-client-file-upload': specifier: workspace:* version: link:../file-upload @@ -4143,6 +4140,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) + zod: + specifier: 4.4.3 + version: 4.4.3 packages/client/ui-deepseek-model-label-1017: devDependencies: diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py index 3141b7bb24..fe17f6f952 100644 --- a/scripts/smoke-python-runtime.py +++ b/scripts/smoke-python-runtime.py @@ -113,7 +113,6 @@ RESTART_SECOND_SESSION_ID = "process-two" RESUME_PROMPT = "Continue the first Python SDK session after restarting the runtime." RESUME_TEXT = "RESUMED_PROCESS_OK" RECOVERY_PROMPT = "Run three todo updates; the scheduler will reject the second call." -RECOVERY_CONTINUE_PROMPT = "Continue after the scheduler failure without repeating any tools." RECOVERY_TEXT = "SCHEDULER_RECOVERY_OK" RECOVERY_SESSION_ID = "scheduler-recovery" RECOVERY_CALL_IDS = ("scheduler-complete", "scheduler-fail", "scheduler-unstarted") @@ -348,7 +347,7 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]: block.get("text") for block in latest.get("content", []) if isinstance(block, dict) and block.get("type") == "text" - and block.get("text") in {RECOVERY_PROMPT, RECOVERY_CONTINUE_PROMPT} + and block.get("text") == RECOVERY_PROMPT ), None) if recovery_prompt == RECOVERY_PROMPT: assert_advertised_tool(body, "todo_write") @@ -358,7 +357,9 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]: chunks.extend({**chunk, "index": index} for chunk in call[1:-2]) chunks.extend(call[-2:]) return chunks - if recovery_prompt == RECOVERY_CONTINUE_PROMPT: + if any(block.get("tool_use_id") == RECOVERY_CALL_IDS[-1] + for message in messages for block in message.get("content", []) + if isinstance(block, dict) and block.get("type") == "tool_result"): results = [ block for message in messages for block in message.get("content", []) @@ -1909,7 +1910,7 @@ def smoke_sdk_scheduler_recovery(base_url: str, executable: Path, update_snapsho with connect() as harness: failed = harness.run(RECOVERY_PROMPT, session_id=RECOVERY_SESSION_ID) - continued = harness.run(RECOVERY_CONTINUE_PROMPT, session_id=RECOVERY_SESSION_ID) + continued = harness.run([], session_id=RECOVERY_SESSION_ID) results = [failed, continued] if [result.finish_reason for result in results] != ["error", "completed"]: outcomes = [ diff --git a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/requests.json b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/requests.json index f83fee366e..41cd02a726 100644 --- a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/requests.json +++ b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/requests.json @@ -97,10 +97,6 @@ } ], "is_error": true - }, - { - "type": "text", - "text": "Continue after the scheduler failure without repeating any tools." } ] } diff --git a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json index 46704a4ab7..7172a0edd1 100644 --- a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json +++ b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json @@ -57,7 +57,6 @@ "turn/start", "agent/inbox/spliced", "step/start", - "user/message", "assistant/message", "step/end", "turn/end" @@ -71,7 +70,6 @@ "session.event", "session.event", "session.event", - "session.event", "session.status" ] } diff --git a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl index c679ff36b7..37ccfaa185 100644 --- a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl +++ b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl @@ -21,11 +21,10 @@ {"type":"tool/result","data":{"turn":1,"step":1,"message":{"id":"interrupted-tool-result-scheduler-unstarted-19","role":"tool","toolCallId":"scheduler-unstarted","isError":true,"source":{"kind":"tool","callId":"scheduler-unstarted"},"content":[{"type":"text","text":"The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed."}]},"error":{"name":"ToolNotStartedError","code":"TOOL_NOT_STARTED"}},"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} -{"type":"user/message","data":{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"} {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/snapshots/sdk/sdk.snapshot.ts b/snapshots/sdk/sdk.snapshot.ts index 1a687411fc..ddd859bd38 100644 --- a/snapshots/sdk/sdk.snapshot.ts +++ b/snapshots/sdk/sdk.snapshot.ts @@ -485,11 +485,23 @@ function modelFromSession(log: string): { provider: string; model: string } { function turnActions(log: string): TurnAction[] { const actions: TurnAction[] = [] let current: TurnAction | undefined + let emptyQueued = false for (const record of records(log)) { + if (record.type === 'agent/inbox/spliced') { + const data = record.data as JsonObject | undefined + if (data?.target === 'next-turn' && Array.isArray(data.inserted)) { + emptyQueued ||= data.inserted.some(value => { + const message = value as JsonObject + return (message.source as JsonObject | undefined)?.kind === 'user' + && Array.isArray(message.content) && message.content.length === 0 + }) + } + } if (record.type === 'turn/start') { const data = record.data as JsonObject | undefined if (typeof data?.turn !== 'number') throw new Error('SDK snapshot turn/start has no turn') - current = { turn: data.turn } + current = { turn: data.turn, ...emptyQueued ? { content: [] } : {} } + emptyQueued = false continue } if (record.type === 'user/message' && current !== undefined && current.content === undefined) { @@ -768,7 +780,8 @@ async function runScenario(scenario: CorpusScenario): Promise<{ session = await harness.resumeSession(sessionId) expect(session.id).toBe(sessionId) } - const result = await session.run(materializeInput(action.content, scenario, cwd, liveSessions), { + const input = materializeInput(action.content, scenario, cwd, liveSessions) + const result = await session.run(input, { onNotification: (notification) => { notifications.push(notification) observe(notification) @@ -1117,6 +1130,8 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => { } } }) } if (scenario.name === 'tool-scheduler-recovery') { + expect(records(ordered[0]!.content).filter(event => event.type === 'user/message' + && (event.data['source'] as { kind: string }).kind === 'user')).toHaveLength(1) expect(results).toHaveLength(2) const events = results.flatMap(result => result.events) expect(results[1]?.finalResponse, JSON.stringify(events.filter(event => event.type === 'turn/end'))) diff --git a/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl b/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl index 885df800f9..cfe4388d42 100644 --- a/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl +++ b/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl @@ -19,13 +19,12 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":20,"time":0,"data":{"turn":1,"step":1}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":21,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":22,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":22,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"running"}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":23,"time":0,"data":{"turn":2}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":24,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":25,"time":0,"data":{"turn":2,"step":1}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":26,"time":0,"data":{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":27,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":64,"outputTokens":28},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":28}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":28,"time":0,"data":{"turn":2,"step":1}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":29,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":26,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":64,"outputTokens":28},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":28}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":27,"time":0,"data":{"turn":2,"step":1}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":28,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} diff --git a/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl b/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl index 1eb28fe0c1..31eb0f7d38 100644 --- a/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl +++ b/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl @@ -21,11 +21,10 @@ {"type":"tool/result","data":{"turn":1,"step":1,"message":{"id":"interrupted-tool-result-scheduler-unstarted-19","role":"tool","toolCallId":"scheduler-unstarted","isError":true,"source":{"kind":"tool","callId":"scheduler-unstarted"},"content":[{"type":"text","text":"The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed."}]},"error":{"name":"ToolNotStartedError","code":"TOOL_NOT_STARTED"}},"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:6}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"user"},"role":"user","id":"{{message:6}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} -{"type":"user/message","data":{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:6}}"},"surfaceOp":"append"} {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":64,"outputTokens":28},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":28}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/snapshots/web/live-interactions/continued.expected.md b/snapshots/web/live-interactions/continued.expected.md index 1eb97847c2..0b4bcd0205 100644 --- a/snapshots/web/live-interactions/continued.expected.md +++ b/snapshots/web/live-interactions/continued.expected.md @@ -19,7 +19,7 @@ - status: - text: This turn failedupstream 503 - code: SERVER -- text: plan Plan mode on. Use /plan off to leave. plan Plan mode off. continue {{clock}} +- text: plan Plan mode on. Use /plan off to leave. plan Plan mode off. - button "Copy" - status: Completed - button "Completed in {{duration}}" From 4a4aabaa57dd3203d1424f97d91fb7361d77dcea Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 22:16:24 +0800 Subject: [PATCH 05/29] fix(core): align empty continuation checks and generated references --- docs/architecture.i18n.yaml | 6 ++-- docs/architecture.md | 4 +-- docs/architecture.zh.md | 4 +-- docs/event-producer-consumer.md | 32 +++++++++---------- docs/event-producer-consumer.zh.md | 32 +++++++++---------- docs/persistence-catalog.i18n.yaml | 8 ++--- docs/persistence-catalog.md | 4 +-- docs/persistence-catalog.zh.md | 4 +-- docs/persistence-schema.json | 4 +-- docs/subsystems/core.md | 4 ++- docs/subsystems/core.zh.md | 4 ++- .../api/session-controller/README.i18n.yaml | 4 +-- packages/api/session-controller/README.md | 2 +- packages/api/session-controller/README.zh.md | 2 +- .../api/session-controller/src/commands.ts | 2 +- .../client/ui-conversation/README.i18n.yaml | 4 +-- packages/client/ui-conversation/README.md | 2 +- packages/client/ui-conversation/README.zh.md | 2 +- .../src/client/input/facade.ts | 1 + .../tests/apply-inject.client.spec.tsx | 2 +- packages/core/agent-loop/README.i18n.yaml | 4 +-- packages/core/agent-loop/README.md | 2 +- packages/core/agent-loop/README.zh.md | 2 +- packages/core/agent/README.i18n.yaml | 4 +-- packages/core/agent/README.md | 2 +- packages/core/agent/README.zh.md | 2 +- 26 files changed, 72 insertions(+), 71 deletions(-) diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index d1e20ef6b6..e9bb6328e2 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -19,13 +19,13 @@ zh: 70d87a12c43e042a /deepseek-harness-architecture/core-packages: en: 20ef882e44b06a10 - zh: 0d69c1500650868f + zh: c10c3934ed5570e9 /deepseek-harness-architecture/events: en: 693407efb233f961 zh: 37b9dd0677d4405c /deepseek-harness-architecture/turn-flow: - en: 22964b8327adc3f6 - zh: c86d765e21430405 + en: adffae46f8181328 + zh: 1009d3a5934cd1ba /deepseek-harness-architecture/session-log: en: 8a7f19b95d426c99 zh: d5bb0538404e20ea diff --git a/docs/architecture.md b/docs/architecture.md index ed35ab94af..cd561ac353 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -107,7 +107,7 @@ turn/end Factory setup follows SessionStore entry and `session/created`, preceding `agent/created`; closing events commit before removal. -One inbox feeds the driver; injected context waits for a waking message. AgentLoop’s durable `inbox` projection exposes pending input without live Agents. +The durable `inbox` projection exposes pending input. Empty ordinary input starts a turn without a model-visible message; injected context waits for waking input. Before assembly, the loop snapshots Session model selection in `AssembleContext.modelSelection` and `agent/pre-step.modelSelection`; retries retain it; omitted reasoning effort uses adapter defaults. `agent/pre-step` listeners accept, rewrite, or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, the deprecated `agent/request` hook adjusts controls without changing the provider, model, or effort selection, and `prepareCall()` binds the adapter before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The system prompt travels as `system/message` history: empty text clears it, capable routes append updates, and other routes consolidate it at the first system node. [Agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) owns prompt admission and replacement details. @@ -119,8 +119,6 @@ Details: the [sequence diagram](agent-lifecycle.md), the [tool pipeline](tool-ex [`ctx.tools.execute()`](subsystems/tools.md) receives raw batches and concurrency limits. -An identified empty ordinary input starts a new turn from committed history without creating a model-visible user message. It remains logged in the inbox, passes normal pre-step admission, and closes through the same turn and step lifecycle. - ## Session log The session log is the source of the context the model sees. `deriveMessages()` projects model history from it. Each `assistant/message` embeds the exact compact timed stream that produced its assembled content; `assistant/attempt` retains settled failed, retried, cancelled, and stream-error attempts without adding model history. Fork, resume, transcripts, telemetry, and persistence all derive from these durable settlements, while live UI incrementality comes from `agent/assistant-stream`; a hard process loss before settlement leaves no durable attempt stream ([decision](../.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md)). diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 64c3be1ddd..02c7423e56 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -66,8 +66,6 @@ Electron 使用 Electron Node 模式启动私有 Desktop Host。Host 调用共 -带身份的空普通输入基于已提交历史启动新轮次,不生成模型可见的用户消息。它仍记录在 inbox 中,经过正常 pre-step 准入,并通过相同的轮次和步骤生命周期闭合。 - ## 事件 事件就是扩展点,而选对事件域是大多数改动的第一个决定。 @@ -113,7 +111,7 @@ turn/end 工厂 setup 在 Session 进入 SessionStore 并完成 `session/created` 之后、`agent/created` 之前运行;收尾事件在移除之前提交。 -输入通过同一个 inbox 到达驱动器;注入的上下文等待一条唤醒消息。AgentLoop 的持久 `inbox` 投影使待处理输入在没有活跃 Agent 时仍可读取。 +持久 `inbox` 投影公开待消费输入。空普通输入启动轮次而不新增模型可见消息;注入上下文等待唤醒输入。 循环在组装前将 Session 模型选择快照到 `AssembleContext.modelSelection` 与 `agent/pre-step.modelSelection`;重试保留该选择,省略推理强度时使用适配器默认值。`agent/pre-step` 监听器接纳、改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,已弃用的 `agent/request` hook 在不改变提供方、模型或推理强度选择的前提下调整控制参数,`prepareCall()` 绑定适配器,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。系统提示词通过 `system/message` 历史传递:空文本清除提示词,具备能力的路由追加更新,其他路由将其归并到首个系统节点。[Agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation) 说明提示词准入与替换的细节。 diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 4ae036085f..c2f38b3416 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -12,22 +12,22 @@ Application listeners are outside this package matrix. Desktop Host subscribes t | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:199`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | -| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team` | -| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | -| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | -| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:689`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:669`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:696`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:675`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:682`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:401`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:292`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:301`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team` | +| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:431`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | +| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:330`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:338`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:319`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:352`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:391`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:311`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:419`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:691`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:671`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:698`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:677`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:684`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes`, `server` | | `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index a385c1f64a..ee937cf3b9 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -14,22 +14,22 @@ | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:199`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | -| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team` | -| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | -| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | -| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:689`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:669`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:696`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:675`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:682`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:401`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:292`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:301`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team` | +| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:431`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | +| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:330`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:338`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:319`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:352`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:391`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:311`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:419`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:691`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:671`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:698`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:677`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:684`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes`, `server` | | `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 280eda500e..0774d434a7 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -1350,8 +1350,8 @@ en: 2786fef560851bb4 zh: 5d787475356b2606 /session-persistence-event-catalog/resolved-persistence-types/string: - en: 1e12d380c4988d40 - zh: 14a781104e00f360 + en: 1e484bd12747db9f + zh: 1e6fc6aa9066a702 /session-persistence-event-catalog/resolved-persistence-types/string~2: en: 0089620bf4638263 zh: 1922bef1713094bb @@ -1857,8 +1857,8 @@ en: 5a0bbf4c76266ac5 zh: 0b20854f90f9d74d /session-persistence-event-catalog/resolved-persistence-types/kind-user: - en: e98b716228feb157 - zh: 2bb593ae9cd0ce0d + en: 3d3ae20cba45bae8 + zh: 04ad200556cd8ced /session-persistence-event-catalog/resolved-persistence-types/kind-user-approval: en: a52d2dfbaf844127 zh: 4c5306ca40f20873 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 97cfa4bd63..a498e62e2e 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -5601,7 +5601,7 @@ Array of `number`. SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b` -Sources: [`packages/api/session-controller/src/types.ts:386`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) +Sources: [`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) `string` @@ -7657,7 +7657,7 @@ Sources: [`packages/core/tools/src/index.ts:28`](../packages/core/tools/src/inde SHA-256: `6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def` -Sources: [`packages/api/session-controller/src/types.ts:395`](../packages/api/session-controller/src/types.ts) +Sources: [`packages/api/session-controller/src/types.ts:397`](../packages/api/session-controller/src/types.ts) | Property | Presence | Type | |---|---|---| diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index c1dc641ae7..d75ff6f2b1 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -5603,7 +5603,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b` -来源:[`packages/api/session-controller/src/types.ts:386`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) +来源:[`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) `string` @@ -7659,7 +7659,7 @@ SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543` SHA-256: `6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def` -来源:[`packages/api/session-controller/src/types.ts:395`](../packages/api/session-controller/src/types.ts) +来源:[`packages/api/session-controller/src/types.ts:397`](../packages/api/session-controller/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index c6c15a7c90..70f64325e4 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -251,7 +251,7 @@ {"digest":"4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731","names":["packages/llm/llm/src/message.ts#ToolResultMessage"],"sources":["packages/llm/llm/src/message.ts:173"]}, {"digest":"4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea","names":[],"sources":[]}, {"digest":"4bb451330ff699f36b45539ceb0eb2606ead72084e2c5bb637c36cc80e2252db","names":[],"sources":["packages/core/session/src/types.ts:198"]}, - {"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:386","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/agent-team/src/types.ts:21","packages/experimental/agent-team/src/types.ts:33","packages/experimental/agent-team/src/types.ts:9","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/workflow/workflow/src/types.ts:13"]}, + {"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:388","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/agent-team/src/types.ts:21","packages/experimental/agent-team/src/types.ts:33","packages/experimental/agent-team/src/types.ts:9","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/workflow/workflow/src/types.ts:13"]}, {"digest":"4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159","names":[],"sources":[]}, {"digest":"4e44c9118152f6829634bfd508e47669ab0497caffa380c94b4be91f9390d35d","names":[],"sources":[]}, {"digest":"4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c","names":["packages/llm/llm/src/types.ts#TextBlock"],"sources":["packages/llm/llm/src/types.ts:62","packages/web/web-search-deepseek/src/provider.ts:66"]}, @@ -334,7 +334,7 @@ {"digest":"6ccb6bc423fb3952458abf9c1b1c3284208b7e660344b612cc55da773d61afe4","names":[],"sources":[]}, {"digest":"6cda0d6d3b4336d83998fe580628426df81c2afbab5d768b8813b56d569120e8","names":[],"sources":[]}, {"digest":"6cf082d0fb6034dea61636787ff0ddf43b6e59aad71e644435a479bc9b83ad65","names":[],"sources":[]}, - {"digest":"6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def","names":[],"sources":["packages/api/session-controller/src/types.ts:395"]}, + {"digest":"6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def","names":[],"sources":["packages/api/session-controller/src/types.ts:397"]}, {"digest":"6e52dd55a1d48954ed5464ec46fe34d9eb65f709baabc9561e7a279910b0c406","names":[],"sources":["packages/preset/agent-preset-registry/src/session.ts:31"]}, {"digest":"6ea608bdc8a7a63d3d23b84c49721325094db88d136482e43a820945295eee3e","names":[],"sources":[]}, {"digest":"6ebcc2dbec9f2dabca9f668ae6d39e877899b180740570f453409db339221df3","names":[],"sources":[]}, diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 86df19aa94..d11e03488a 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -104,6 +104,7 @@ interface Agent { /** * Route identified input to an inbox boundary and optionally wake the driver. + * Empty ordinary input drives work without becoming a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -117,7 +118,8 @@ interface Agent { /** * Queue an ordinary follow-up turn and wake the driver. The item becomes the - * sole ordinary message of its own turn. + * sole ordinary message of its own turn. An empty ordinary input starts a + * turn from committed history without appending a model-visible user message. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index db4bb206f5..7e4c682f69 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -108,6 +108,7 @@ interface Agent { /** * Route identified input to an inbox boundary and optionally wake the driver. + * Empty ordinary input drives work without becoming a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -121,7 +122,8 @@ interface Agent { /** * Queue an ordinary follow-up turn and wake the driver. The item becomes the - * sole ordinary message of its own turn. + * sole ordinary message of its own turn. An empty ordinary input starts a + * turn from committed history without appending a model-visible user message. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/packages/api/session-controller/README.i18n.yaml b/packages/api/session-controller/README.i18n.yaml index 78f64d3acc..a1daccc8f0 100644 --- a/packages/api/session-controller/README.i18n.yaml +++ b/packages/api/session-controller/README.i18n.yaml @@ -24,8 +24,8 @@ en: 954923654eebe2dc zh: 6bd9a7d2ea965aed /session-controller/configuration: - en: 2a91d9f904e90756 - zh: 8affb15ccc571829 + en: 94cb7b73b385b320 + zh: 1f3bc1e6552a01a3 /session-controller/model-experience: en: 30b917bf822bb4df zh: b0b023b5b526c317 diff --git a/packages/api/session-controller/README.md b/packages/api/session-controller/README.md index 1b11d04106..371ee3c13e 100644 --- a/packages/api/session-controller/README.md +++ b/packages/api/session-controller/README.md @@ -110,9 +110,9 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a Blockquotes travel as ordinary prompt text without quote metadata. Local submission echoes and durable messages use the same text rendering rules; queue edits replace content while retaining prompt correlation. - An empty `prompt.content` array requests a new turn only for an eligible failed, idle ordinary Session in queue mode. Pending human input prevents admission; non-waking injected context is retained. `conversationContinuation` supplies the shared six-code manual policy, independently of provider retry configuration. The input is logged through the inbox for request-id deduplication, but creates no model-visible user message. Whitespace-only text parts remain invalid. + ## Model Experience None, as invoked Agent commands own any model-visible effect. diff --git a/packages/api/session-controller/README.zh.md b/packages/api/session-controller/README.zh.md index cb8a1ba818..9e698a26e7 100644 --- a/packages/api/session-controller/README.zh.md +++ b/packages/api/session-controller/README.zh.md @@ -110,9 +110,9 @@ GUI 模型选择要求确切提供方/模型对出现在可用目录中;不 Blockquote 作为普通 prompt 文本传递,不携带 quote 元数据。本地提交回显与持久消息使用同一文本渲染规则;队列编辑替换 content,同时保留 prompt 关联信息。 - 空的 `prompt.content` 数组仅在普通 Session 空闲、最新失败符合继续条件且使用 queue 模式时请求新轮次。待消费的用户输入阻止准入,非唤醒的注入上下文保留。`conversationContinuation` 提供共用的六码手动策略,独立于提供方重试配置。输入通过 inbox 持久记录,用于 request-id 去重,但不生成模型可见的用户消息。仅含空白的文本块仍无效。 + ## 模型体验 无;任何模型可见效果都由被调用的 Agent 命令负责。 diff --git a/packages/api/session-controller/src/commands.ts b/packages/api/session-controller/src/commands.ts index 0b62ff724a..b43333ebc0 100644 --- a/packages/api/session-controller/src/commands.ts +++ b/packages/api/session-controller/src/commands.ts @@ -320,7 +320,7 @@ export class SessionCommandController { const inbox = agent.inbox if (request.mode !== 'queue' || this.continuation.stateOf(agent.session).ending === null || inbox.nextTurn.length > 0 || inbox.nextStep.some(message => message.source.kind === 'user')) { - throw new RemoteError('session/agent-busy', 'generation continuation is unavailable', {}) + throw new RemoteError('session/agent-busy', 'generation continuation is unavailable', { reason: 'NO_CONTINUABLE_FAILURE_OR_PENDING_INPUT' }) } agent.followup(createUserMessage({ content: [], source })) return { accepted: true as const } diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index 6ee1b33613..8507f1dd99 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -33,8 +33,8 @@ en: 6a7ae99570c9c007 zh: 26e86063e97d82c7 /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/what-the-model-sees: - en: b1376c710fa71ce3 - zh: e751e1a490c36bd8 + en: cfd91cdd0132732b + zh: 298d1ce1b973e7b0 /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/token-effect: en: a5bbf55d50568fa2 zh: 6c43af211055db93 diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 4a408c5950..8955b71b31 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -193,7 +193,7 @@ The selector must be a pure function of the owner currency. Its non-null return #### What the model sees -The play action starts a new request from committed Session history. Its empty input is a durable inbox command, not a model-visible user message; it adds no localized or hidden continuation instruction. Pending injected context follows ordinary admission. +The play action starts a new request from committed Session history. Its `content: []` input is a durable inbox command, not a model-visible user message; it adds no localized or hidden continuation instruction. Pending injected context follows ordinary admission. #### Token effect diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index 85df62d84b..65c9feda93 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -193,7 +193,7 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match #### 模型看到什么 -播放按钮基于已提交的 Session 历史发起新请求。空输入作为持久 inbox 控制记录,不成为模型可见的用户消息;不会添加本地化或隐藏的继续指令。待注入上下文遵循普通准入流程。 +播放按钮基于已提交的 Session 历史发起新请求。`content: []` 作为持久 inbox 控制记录,不成为模型可见的用户消息;不会添加本地化或隐藏的继续指令。待注入上下文遵循普通准入流程。 #### Token 影响 diff --git a/packages/client/ui-conversation/src/client/input/facade.ts b/packages/client/ui-conversation/src/client/input/facade.ts index e82acfdd35..cb8472f746 100644 --- a/packages/client/ui-conversation/src/client/input/facade.ts +++ b/packages/client/ui-conversation/src/client/input/facade.ts @@ -387,6 +387,7 @@ export class SessionInputShell implements SessionInput { * out from the machine; this method only feeds the event. Lock entry * (adjudicating/submitting/sending) force-closes the transient layers: the * popup dismisses and the menu tracks frozen. + * @param allowEmpty - explicitly admit empty continuation input through the default sink. */ submit(mode: InputSubmitMode = 'queue', source?: 'click' | 'enter', allowEmpty = false): void { if (this.disposed) return diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index a1cf51452a..58dff85859 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -234,7 +234,7 @@ describe('Conversation inject API', () => { expect(source.getSnapshot()).toBe(5) composer.continueGeneration!(SessionSeq(5)) await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce() }) - expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([{ type: 'text', text: '继续' }]) + expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([]) expect(notified).toHaveBeenCalledTimes(3) }) diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 2a3ecdf694..5b88c245a3 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -51,8 +51,8 @@ en: eea56903c4ff82f5 zh: c0d5abb5767780ed /deepseek-ai-dsh-agent-loop/further-exploration: - en: 687e42aa91c15be5 - zh: 0e2d860391a9ce89 + en: bd0b5d56ebef7e23 + zh: 52911b993bc41afb /deepseek-ai-dsh-agent-loop/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 9beebb6543..b18484ad82 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -146,9 +146,9 @@ The package-level contract is enough for most consumers; read these when you nee ----- - Empty ordinary input remains an identified inbox command, so it can wake a new turn while adding no `user/message` to the model surface. It passes normal pre-step admission; a rejected or removed input does not authorize a model request. Turn and step endings retain their normal success, failure, and cancellation handling. + ## Model Experience ### Complete conversation request diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index 8bbbd145c5..54395d27dc 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -146,9 +146,9 @@ loop 在发布工厂前等待 inbox 与轮次边界注册完成,并将句柄 ----- - 空的普通输入仍是带身份的 inbox 控制记录,可以唤醒新轮次,但不向模型消息面添加 `user/message`。它经过正常 pre-step 准入;被拒绝或移除的输入不授权模型请求。轮次和步骤结束沿用正常的成功、失败与取消处理。 + ## 模型体验 ### 完整对话请求 diff --git a/packages/core/agent/README.i18n.yaml b/packages/core/agent/README.i18n.yaml index 3aeeb9a406..a7bdc11ed0 100644 --- a/packages/core/agent/README.i18n.yaml +++ b/packages/core/agent/README.i18n.yaml @@ -60,8 +60,8 @@ en: e48eae6c7de88cf8 zh: 1f5b37bdb0e53788 /deepseek-ai-dsh-agent/further-exploration: - en: d9d9bda2357ef857 - zh: 5fa1368d5f8912e2 + en: faf384f89b18b671 + zh: 42400f79f5ab84b0 /deepseek-ai-dsh-agent/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index 2f7c830c7c..aba28e301e 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -150,9 +150,9 @@ The package-level contract is enough for most consumers; read these when you nee ----- - An ordinary `followup` with `content: []` is an explicit request to start a new turn from committed history. It retains the identified inbox receipt and normal lifecycle without creating a model-visible user message. A pre-step rejection or a decision rewritten to an empty batch still prevents the request. + ## Model Experience ### User, steering, and injected messages diff --git a/packages/core/agent/README.zh.md b/packages/core/agent/README.zh.md index 1eb86a7a5e..c317176430 100644 --- a/packages/core/agent/README.zh.md +++ b/packages/core/agent/README.zh.md @@ -150,9 +150,9 @@ await handle.agent.whenIdle() ----- - 普通 `followup` 的 `content: []` 明确请求基于已提交历史启动新轮次。它保留带身份的 inbox 回执和正常生命周期,不生成模型可见的用户消息。pre-step 拒绝或改写为空批次的决策仍会阻止请求。 + ## 模型体验 ### 用户、steering 与注入消息 From 4daabfd629bbcdaeab1e2c21304f4b3ea6bcbf0f Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 22:20:41 +0800 Subject: [PATCH 06/29] fix(test): include shared continuation events in client checks --- tsconfig.client.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsconfig.client.json b/tsconfig.client.json index 514c1f9deb..36babf0686 100644 --- a/tsconfig.client.json +++ b/tsconfig.client.json @@ -29,6 +29,8 @@ "packages/experimental/*/src/css-modules.d.ts", "packages/client/*/tests/**/*.ts", "packages/client/*/tests/**/*.tsx", + // Shared Session event data has no Host Context augmentation. + "packages/api/session-controller/tests/continuation-fixture.ts", "benchmarks/**/*.client.ts", "benchmarks/**/*.client.tsx", "benchmarks/support/**/*.ts", From 1b907b26aacdf43d07d9046868badd80bbfae2a4 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 22:25:05 +0800 Subject: [PATCH 07/29] test(web): pin continuation without a user message bubble --- packages/api/session-controller/src/commands.ts | 2 +- packages/api/session-controller/tests/session-cold.host.spec.ts | 2 +- snapshots/web/live-interactions/continued.expected.md | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/api/session-controller/src/commands.ts b/packages/api/session-controller/src/commands.ts index b43333ebc0..a258825c66 100644 --- a/packages/api/session-controller/src/commands.ts +++ b/packages/api/session-controller/src/commands.ts @@ -62,7 +62,7 @@ type PromptContentCandidate = /** * Whether prompt content carries non-whitespace text or an attachment. * @param content - submitted or edited prompt parts. - * @returns false for content the Agent must not receive. + * @returns whether any part supplies ordinary prompt content. */ export function hasPromptContent(content: readonly PromptContentCandidate[]): boolean { return content.some(part => part.type !== 'text' || part.text.trim().length > 0) diff --git a/packages/api/session-controller/tests/session-cold.host.spec.ts b/packages/api/session-controller/tests/session-cold.host.spec.ts index 5139baf644..7e58d39aca 100644 --- a/packages/api/session-controller/tests/session-cold.host.spec.ts +++ b/packages/api/session-controller/tests/session-cold.host.spec.ts @@ -874,7 +874,7 @@ async function modelFor(ctx: Context, session: Session): Promise { } describe('sessions.prompt synchronous rejection', () => { - it('rejects content without non-whitespace text or an attachment before delivery or Session events', async () => { + it('rejects whitespace-only text parts before delivery or Session events', async () => { const ctx = new Context() await ctx.plugin(SessionStore) await ctx.plugin(SessionProjectionRegistry) diff --git a/snapshots/web/live-interactions/continued.expected.md b/snapshots/web/live-interactions/continued.expected.md index 0b4bcd0205..16c21a89f1 100644 --- a/snapshots/web/live-interactions/continued.expected.md +++ b/snapshots/web/live-interactions/continued.expected.md @@ -20,7 +20,6 @@ - text: This turn failedupstream 503 - code: SERVER - text: plan Plan mode on. Use /plan off to leave. plan Plan mode off. -- button "Copy" - status: Completed - button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. From 53d4ed8f217ea157081a6ad8de7bf99e6e3a4c95 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 22:40:07 +0800 Subject: [PATCH 08/29] refactor(api): use canceled inbox splice semantics --- packages/api/session-controller/src/continuation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/session-controller/src/continuation.ts b/packages/api/session-controller/src/continuation.ts index 8673cfc731..5d8e97553e 100644 --- a/packages/api/session-controller/src/continuation.ts +++ b/packages/api/session-controller/src/continuation.ts @@ -33,7 +33,7 @@ export const continuationProjectionDefinition = { switch (event.type) { case 'agent/inbox/spliced': if (state.continuing && event.data.target === 'next-turn' && event.data.outcome === 'canceled' - && event.data.start === 0 && (event.data.removedCount ?? 0) > 0) return { ...state, continuing: false } + && event.data.start === 0) return { ...state, continuing: false } return state.ending !== null && !state.continuing && event.data.target === 'next-turn' && event.data.inserted.some(message => message.source.kind === 'user' && message.content.length === 0) ? { ...state, continuing: true } : state From 04f1a699ee54d7210632a58252fa713e791ccd93 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 26 Oct 2026 22:49:03 +0800 Subject: [PATCH 09/29] fix(api): return the synchronous continuation admission promise --- packages/api/session-controller/src/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/session-controller/src/commands.ts b/packages/api/session-controller/src/commands.ts index a258825c66..88d7486cbe 100644 --- a/packages/api/session-controller/src/commands.ts +++ b/packages/api/session-controller/src/commands.ts @@ -316,14 +316,14 @@ export class SessionCommandController { if (hasPromptRequest(agent, request.requestId)) return { accepted: true } if (request.content.length === 0) { try { - return await agent.runMaintenance(async () => { + return await agent.runMaintenance(() => { const inbox = agent.inbox if (request.mode !== 'queue' || this.continuation.stateOf(agent.session).ending === null || inbox.nextTurn.length > 0 || inbox.nextStep.some(message => message.source.kind === 'user')) { throw new RemoteError('session/agent-busy', 'generation continuation is unavailable', { reason: 'NO_CONTINUABLE_FAILURE_OR_PENDING_INPUT' }) } agent.followup(createUserMessage({ content: [], source })) - return { accepted: true as const } + return Promise.resolve({ accepted: true as const }) }) } catch (error: unknown) { if (remoteErrorOf(error) !== undefined) throw error From 2e0c73a89599a50102448b8f67199565f04b45c6 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 27 Oct 2026 12:07:24 +0800 Subject: [PATCH 10/29] refactor(agent): record continuation as retry control input --- ...6-manual-generation-continuation.i18n.yaml | 4 +- ...26-10-26-manual-generation-continuation.md | 6 +- ...10-26-manual-generation-continuation.zh.md | 6 +- apps/web/tests/live-interactions.e2e.ts | 9 +- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 4 +- docs/architecture.zh.md | 4 +- docs/event-producer-consumer.md | 10 +- docs/event-producer-consumer.zh.md | 10 +- docs/persistence-catalog.i18n.yaml | 160 ++++++----- docs/persistence-catalog.md | 199 ++++++++++---- docs/persistence-catalog.zh.md | 199 ++++++++++---- .../2026-10-27-retry-control-input.i18n.yaml | 28 ++ .../2026-10-27-retry-control-input.md | 64 +++++ ...2026-10-27-retry-control-input.schema.json | 257 ++++++++++++++++++ .../2026-10-27-retry-control-input.zh.md | 64 +++++ docs/persistence-schema.json | 79 +++--- docs/subsystems/core.md | 8 +- docs/subsystems/core.zh.md | 8 +- .../empty-prompt-continuation/guide.i18n.yaml | 22 +- .../empty-prompt-continuation/guide.md | 15 +- .../empty-prompt-continuation/guide.zh.md | 15 +- .../api/session-controller/README.i18n.yaml | 4 +- packages/api/session-controller/README.md | 2 +- packages/api/session-controller/README.zh.md | 2 +- .../api/session-controller/src/commands.ts | 7 +- .../session-controller/src/continuation.ts | 17 +- packages/api/session-controller/src/index.ts | 2 +- packages/api/session-controller/src/types.ts | 2 + .../tests/continuation.host.spec.ts | 44 +-- .../tests/empty-prompt.host.spec.ts | 17 +- .../client/ui-conversation/README.i18n.yaml | 12 +- packages/client/ui-conversation/README.md | 6 +- packages/client/ui-conversation/README.zh.md | 6 +- packages/core/agent-loop/README.i18n.yaml | 8 +- packages/core/agent-loop/README.md | 4 +- packages/core/agent-loop/README.zh.md | 4 +- packages/core/agent-loop/src/agent.ts | 7 +- .../agent-loop/tests/request-error.spec.ts | 32 ++- packages/core/agent/README.i18n.yaml | 8 +- packages/core/agent/README.md | 4 +- packages/core/agent/README.zh.md | 4 +- packages/core/agent/src/runtime-types.ts | 8 +- packages/core/agent/src/types.ts | 18 +- .../core/session/src/known-event-types.ts | 1 + packages/sdk/client/src/client.ts | 2 +- packages/sdk/protocol/README.i18n.yaml | 4 +- packages/sdk/protocol/README.md | 2 +- packages/sdk/protocol/README.zh.md | 2 +- packages/sdk/protocol/src/types.ts | 4 +- packages/sdk/server/README.i18n.yaml | 8 +- packages/sdk/server/README.md | 4 +- packages/sdk/server/README.zh.md | 4 +- packages/sdk/server/src/server.ts | 4 +- scripts/smoke-python-runtime.py | 5 + .../scheduler-recovery/result.json | 2 + .../scheduler-recovery/session.v4.jsonl | 3 +- snapshots/sdk/sdk.snapshot.ts | 19 +- .../notifications.expected.jsonl | 9 +- .../tool-scheduler-recovery/session.v4.jsonl | 3 +- 60 files changed, 1083 insertions(+), 386 deletions(-) create mode 100644 docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml create mode 100644 docs/persistence-changes/2026-10-27-retry-control-input.md create mode 100644 docs/persistence-changes/2026-10-27-retry-control-input.schema.json create mode 100644 docs/persistence-changes/2026-10-27-retry-control-input.zh.md diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml index 0b66ec2f1d..8c9fc03d74 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml @@ -9,8 +9,8 @@ en: f118844946afdbdc zh: f1fe37d042f88b24 /agent-note-manual-generation-continuation/decision: - en: 32e75f0c2a35bfec - zh: 343fedcd8f53a6a8 + en: d384d0ea6c502908 + zh: 90e0cb41a5a0d125 /agent-note-manual-generation-continuation/alternatives-considered: en: 58bb948d210d42ea zh: 5dc920d222d71218 diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md index ab91eb5f7b..ededd88ffb 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md @@ -10,11 +10,11 @@ Users need another request after a model failure without inserting an artificial ## Decision -The play action submits `content: []` through the existing Session prompt API. AgentLoop retains the identified inbox input and starts a new balanced turn, but omits the empty ordinary input from model-visible `user/message` events. The original failed turn remains closed. +The play action submits `content: []` through the existing Session prompt API. The API assigns `source.kind: 'retry'`. AgentLoop retains the identified inbox input and starts a new balanced turn; acceptance records `agent/retry` instead of a model-visible `user/message`. Ordinary empty user-message behavior is unchanged. The original failed turn remains closed. -The Session API owns the fixed-size `conversationContinuation` projection and shares its value with the browser. Admission requires an eligible failure, true idle ownership, and no pending human input. An accepted empty input carries the admitted work into its next turn, preserving eligibility if that request also fails. +The Session API owns the fixed-size `conversationContinuation` projection and shares its value with the browser. Admission requires an eligible failure, true idle ownership, and no pending human input. An accepted retry event admits the next turn, preserving eligibility if that request also fails. -The six-code manual allowlist stays independent of provider retry policy. Non-waking injected context accompanies the request. Durable inbox receipts retain request-id deduplication after the empty input is claimed; no new Remote operation or Session event format is required. +The six-code manual allowlist stays independent of provider retry policy. Non-waking injected context accompanies the request. The retry event retains request-id deduplication after the inbox input is claimed; no new Remote operation is required. An explicit control source prevents unrelated empty user inputs from changing behavior, and a separate event keeps chat rendering free of retry-specific filtering. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md index 21ff2cf50a..b3d659edf2 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md @@ -10,11 +10,11 @@ Status: implemented ## 决策 -播放按钮通过现有 Session prompt API 提交 `content: []`。AgentLoop 保留带身份的 inbox 输入并启动正常闭合的新轮次,但不将空普通输入写成模型可见的 `user/message`。原失败轮次保持闭合。 +播放按钮通过现有 Session prompt API 提交 `content: []`。API 指定 `source.kind: 'retry'`。AgentLoop 保留带身份的 inbox 输入并启动正常闭合的新轮次;接受后记录 `agent/retry`,而不写入模型可见的 `user/message`。普通空用户消息的行为保持不变。原失败轮次保持闭合。 -Session API 拥有固定大小的 `conversationContinuation` 投影,向浏览器发布相同的值。准入要求失败符合条件、持有真正的空闲状态且没有待消费的用户输入。已接受的空输入将原先准入的工作带入新轮次,因此请求再次失败后仍保留继续资格。 +Session API 拥有固定大小的 `conversationContinuation` 投影,向浏览器发布相同的值。准入要求失败符合条件、持有真正的空闲状态且没有待消费的用户输入。已接受的重试事件使新轮次获得准入,因此请求再次失败后仍保留继续资格。 -六码手动白名单独立于提供方重试策略。非唤醒的注入上下文随请求进入。空输入被领取后,持久 inbox 回执仍支持 request-id 去重;不需要新增 Remote 操作或 Session 事件格式。 +六码手动白名单独立于提供方重试策略。非唤醒的注入上下文随请求进入。inbox 输入被领取后,重试事件仍支持 request-id 去重;不需要新增 Remote 操作。显式控制来源避免改变无关空用户输入的行为,独立事件则使聊天渲染无需针对重试过滤。 ## 考虑过的替代方案 diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index eda193847c..bde84fe2d1 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -414,7 +414,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { expect(tripwire.warnings).toEqual([]) }, 120_000) - it.skipIf(MODE === 'record')('continues after transient retries exhaust without another user message', async () => { + it.skipIf(MODE === 'record')('continues after transient retries exhaust without another user bubble', async () => { // A whole-script replacement: three throw entries cover the first request // plus both budgeted retries (patches cannot reach past the one-call // derived script). The scenario-owned policy keeps exhaustion fast and @@ -476,8 +476,11 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { expect(turnEndReasons(sessionEvents)).toEqual(['error', 'completed']) expect(sessionEvents.some(event => event.type === 'user/message' && event.data.source.kind === 'plan-mode')).toBe(true) const userMessages = sessionEvents.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' - ? [event.data.content] : []) - expect(userMessages).toEqual([[{ type: 'text', text: PROMPT }]]) + ? [event.data] : []) + expect(userMessages.map(message => message.content)).toEqual([[{ type: 'text', text: PROMPT }]]) + expect(sessionEvents.filter(event => event.type === 'agent/retry')).toEqual([ + expect.objectContaining({ data: expect.objectContaining({ turn: 2, step: 1, source: expect.objectContaining({ kind: 'retry' }) }) }), + ]) expect(sessionEvents.filter(event => event.type === 'turn/start')).toHaveLength(2) expect(sessionEvents.filter(event => event.type === 'turn/end')).toHaveLength(2) expect(await resume.count()).toBe(0) diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index e9bb6328e2..5dfa2c2995 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -24,8 +24,8 @@ en: 693407efb233f961 zh: 37b9dd0677d4405c /deepseek-harness-architecture/turn-flow: - en: adffae46f8181328 - zh: 1009d3a5934cd1ba + en: 3770eacd9c3bae28 + zh: bd7b8a0374ba0ff8 /deepseek-harness-architecture/session-log: en: 8a7f19b95d426c99 zh: d5bb0538404e20ea diff --git a/docs/architecture.md b/docs/architecture.md index cd561ac353..064026d4d2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -107,9 +107,9 @@ turn/end Factory setup follows SessionStore entry and `session/created`, preceding `agent/created`; closing events commit before removal. -The durable `inbox` projection exposes pending input. Empty ordinary input starts a turn without a model-visible message; injected context waits for waking input. +One inbox feeds the driver; injected context waits for a waking message. AgentLoop’s durable `inbox` projection exposes pending input without live Agents. -Before assembly, the loop snapshots Session model selection in `AssembleContext.modelSelection` and `agent/pre-step.modelSelection`; retries retain it; omitted reasoning effort uses adapter defaults. `agent/pre-step` listeners accept, rewrite, or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, the deprecated `agent/request` hook adjusts controls without changing the provider, model, or effort selection, and `prepareCall()` binds the adapter before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The system prompt travels as `system/message` history: empty text clears it, capable routes append updates, and other routes consolidate it at the first system node. [Agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) owns prompt admission and replacement details. +Before assembly, the loop snapshots Session model selection in `AssembleContext.modelSelection` and `agent/pre-step.modelSelection`; retries retain it; omitted reasoning effort uses adapter defaults. `agent/pre-step` listeners accept, rewrite, or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, the deprecated `agent/request` hook adjusts controls without changing the provider, model, or effort selection, and `prepareCall()` binds the adapter before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Empty retry-source controls record `agent/retry` instead of entering model history; they retain ordinary inbox and turn lifecycle. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The system prompt travels as `system/message` history: empty text clears it, capable routes append updates, and other routes consolidate it at the first system node. [Agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) owns prompt admission and replacement details. The loop sends immutable requests with live cancellation and reuses freeze evidence for frozen identities; [agent-loop](../packages/core/agent-loop/README.md) owns construction and cancellation causes. diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 02c7423e56..e836ab7452 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -111,9 +111,9 @@ turn/end 工厂 setup 在 Session 进入 SessionStore 并完成 `session/created` 之后、`agent/created` 之前运行;收尾事件在移除之前提交。 -持久 `inbox` 投影公开待消费输入。空普通输入启动轮次而不新增模型可见消息;注入上下文等待唤醒输入。 +输入通过同一个 inbox 到达驱动器;注入的上下文等待一条唤醒消息。AgentLoop 的持久 `inbox` 投影使待处理输入在没有活跃 Agent 时仍可读取。 -循环在组装前将 Session 模型选择快照到 `AssembleContext.modelSelection` 与 `agent/pre-step.modelSelection`;重试保留该选择,省略推理强度时使用适配器默认值。`agent/pre-step` 监听器接纳、改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,已弃用的 `agent/request` hook 在不改变提供方、模型或推理强度选择的前提下调整控制参数,`prepareCall()` 绑定适配器,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。系统提示词通过 `system/message` 历史传递:空文本清除提示词,具备能力的路由追加更新,其他路由将其归并到首个系统节点。[Agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation) 说明提示词准入与替换的细节。 +循环在组装前将 Session 模型选择快照到 `AssembleContext.modelSelection` 与 `agent/pre-step.modelSelection`;重试保留该选择,省略推理强度时使用适配器默认值。`agent/pre-step` 监听器接纳、改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,已弃用的 `agent/request` hook 在不改变提供方、模型或推理强度选择的前提下调整控制参数,`prepareCall()` 绑定适配器,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。系统提示词通过 `system/message` 历史传递:空文本清除提示词,具备能力的路由追加更新,其他路由将其归并到首个系统节点。[Agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation) 说明提示词准入与替换的细节。 空的 retry 来源控制输入记录 `agent/retry`,不进入模型历史;inbox 与轮次生命周期仍按普通流程处理。 循环发送不可变请求,同时保持取消有效,仅对完全冻结的对象复用冻结证据;[agent-loop](../packages/core/agent-loop/README.zh.md) 负责请求构建与取消原因。 diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 1c1bdb2d05..fbe751ca65 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -23,11 +23,11 @@ Application listeners are outside this package matrix. Desktop Host subscribes t | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:391`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | | `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:311`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:419`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:691`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:671`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:698`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:677`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:684`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:693`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:673`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:700`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:679`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:686`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes`, `server` | | `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index f793641ff3..30163a2e5f 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -25,11 +25,11 @@ | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:391`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | | `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:311`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:419`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:691`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:671`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:698`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:677`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:684`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:693`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:673`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:700`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:679`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | +| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:686`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes`, `server` | | `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 0774d434a7..3214b08f10 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -9,8 +9,8 @@ en: 1387c4505dde2faf zh: 48570fa1c13d6910 /session-persistence-event-catalog/persistence-type-fingerprints: - en: 519d46d6078d3f68 - zh: ec9aec262312c573 + en: 84b66494d8162dc6 + zh: f89855f2a8e9a367 /session-persistence-event-catalog/event-envelope: en: 56414ffd00cdc32a zh: 6dc11eb013e95e41 @@ -21,8 +21,11 @@ en: 9188862058c6986b zh: 9188862058c6986b /session-persistence-event-catalog/events/agent/agent-inbox-spliced-log-only: - en: 481727894895b43b - zh: 098bc9788bf88968 + en: 9ae614c041a27169 + zh: 8d669dd582b88f79 +/session-persistence-event-catalog/events/agent/agent-retry-log-only: + en: 559e0484e8ae8904 + zh: 8967a336e5aef50c /session-persistence-event-catalog/events/agent-preset: en: 49891ca9be5ac123 zh: 49891ca9be5ac123 @@ -141,8 +144,8 @@ en: 9edf4ef31e60e880 zh: 9edf4ef31e60e880 /session-persistence-event-catalog/events/model/model-selection-log-only: - en: c2a60ec7d297ed79 - zh: a2f6b2d3c733ce96 + en: 404c54b08c088438 + zh: eb86ed4e7d0e9228 /session-persistence-event-catalog/events/permission: en: 19917bc5438e60af zh: 19917bc5438e60af @@ -330,8 +333,11 @@ en: b3b528acadd04168 zh: b3b528acadd04168 /session-persistence-event-catalog/resolved-persistence-types/agent-inbox-spliced: - en: 8f6d91375141d923 - zh: 8f6d91375141d923 + en: 8719b8f3a0388f64 + zh: 8719b8f3a0388f64 +/session-persistence-event-catalog/resolved-persistence-types/agent-retry: + en: f634a360f6f4a5a7 + zh: f634a360f6f4a5a7 /session-persistence-event-catalog/resolved-persistence-types/allowed-once: en: 6c3effe2abc1aa53 zh: 6c3effe2abc1aa53 @@ -711,8 +717,11 @@ en: ade80ed58d3b63b2 zh: ade80ed58d3b63b2 /session-persistence-event-catalog/resolved-persistence-types/resume: - en: e27772872766bd6d - zh: e27772872766bd6d + en: f9a34b26963fc22d + zh: f9a34b26963fc22d +/session-persistence-event-catalog/resolved-persistence-types/retry: + en: 68c90ebe83c80004 + zh: 68c90ebe83c80004 /session-persistence-event-catalog/resolved-persistence-types/runtime-context: en: 784f0d4afdaaa472 zh: 784f0d4afdaaa472 @@ -1008,11 +1017,11 @@ en: a6a2e38fdd6d127a zh: 1fe82356d89f5769 /session-persistence-event-catalog/resolved-persistence-types/deepseeksearchllmrequest: - en: cf3979c698e2b2ac - zh: 6252c8b668faa706 + en: 96cd6b26224ff94f + zh: cf340bff9692f438 /session-persistence-event-catalog/resolved-persistence-types/developermessage: - en: 16992572a6919e52 - zh: 853f4873fd520444 + en: 6a4c81ce3ec34434 + zh: 96bced8b8cad4b1a /session-persistence-event-catalog/resolved-persistence-types/epochheader: en: 3bdc8ecd0bdf0c9c zh: 0fe5b75c174ebab4 @@ -1077,8 +1086,8 @@ en: a3087ab7bebe9371 zh: abf6afc2f9f5b692 /session-persistence-event-catalog/resolved-persistence-types/inboxtarget: - en: b3b7b8a3414bcb6b - zh: 06be5b8e56bacedf + en: f17ed3bd806e279f + zh: 72920ace94922d45 /session-persistence-event-catalog/resolved-persistence-types/jobregistereddata: en: f604eebb973fe2cf zh: 0c0aba1e17ecf66e @@ -1122,11 +1131,11 @@ en: 6dbe099c62f7c226 zh: 6fd788f63c038c91 /session-persistence-event-catalog/resolved-persistence-types/llmretrystartedeventdata: - en: 5d61649043e317aa - zh: bdf9b9d553d291cf + en: dc14ea1c81e3d226 + zh: 21a2b974bdc708d2 /session-persistence-event-catalog/resolved-persistence-types/message: - en: 56953b055da0dc2b - zh: dcaa193ddb2fece0 + en: 7b11edaf7aa790ae + zh: 514e2826b20dd7d3 /session-persistence-event-catalog/resolved-persistence-types/messagefeedbackdelete: en: c582953270d4ca44 zh: 966ccfe2e7660a2a @@ -1137,20 +1146,20 @@ en: f58575a1fc96df42 zh: 9f0c6ed9033e5c9d /session-persistence-event-catalog/resolved-persistence-types/messagefeedbackrating: - en: 70ade19513d0070f - zh: ff9acb50974a32ae + en: 9897f3cb7a6c7233 + zh: 30e377f7c302a32b /session-persistence-event-catalog/resolved-persistence-types/messagesource: - en: b1bd7c8bd2309d45 - zh: d434e41517c3620d + en: 91e395f09fd30c1a + zh: 5efbc52f4e72e049 /session-persistence-event-catalog/resolved-persistence-types/message~2: - en: dcd71b3190faff96 - zh: b2c07224705aa52e + en: 11eafb2bccde2b25 + zh: 681c0cdb27f6a1c0 /session-persistence-event-catalog/resolved-persistence-types/modelmessagesource: en: 5f9154ca34235ab9 zh: a833105910ebfa82 /session-persistence-event-catalog/resolved-persistence-types/modelselection: - en: 6e301c0ea35d9e0e - zh: 6fbf1d318db9a4e0 + en: 970930c4a8b1bad8 + zh: 6c596aba5e3c5020 /session-persistence-event-catalog/resolved-persistence-types/oneshotscheduledispatchchange: en: a3300babe57b6b67 zh: 4aaf06a149b399c3 @@ -1206,11 +1215,11 @@ en: 9a54c4478ad3b620 zh: 02c4c1fd7e252770 /session-persistence-event-catalog/resolved-persistence-types/sessiontitleeventdata: - en: 46baac9dcdfb1b63 - zh: 30a357bb8405c147 + en: c8e30c2e9886e459 + zh: d7b53b79e0849e64 /session-persistence-event-catalog/resolved-persistence-types/sessiontitlellmrequesteventdata: - en: 035b656ba6500174 - zh: fc9d4d7cd154ca78 + en: aefe8cd3c2b273b2 + zh: b2e21ce4ca05f3fb /session-persistence-event-catalog/resolved-persistence-types/sessiontitlemodelidentity: en: 9531468264499498 zh: 9da23c0ad2776563 @@ -1317,14 +1326,14 @@ en: 778debd61e0284a2 zh: 2cc82ad9e80f18b1 /session-persistence-event-catalog/resolved-persistence-types/turnendreason: - en: 3cbc1b5ae251451f - zh: a95ff0555e368432 + en: 2ef8e1ebf9e02934 + zh: 986c170add1fb987 /session-persistence-event-catalog/resolved-persistence-types/usermessage: - en: 1b30ae0450f156bd - zh: 00bb0efae7ac741e + en: cf92679783fc00c6 + zh: 7a6ba95081ca4ace /session-persistence-event-catalog/resolved-persistence-types/usermessage~2: - en: 1f242f48a3c8c230 - zh: 2516be50ce573b0c + en: f2f335cf1b45d3f9 + zh: 4df02f25e9a324dd /session-persistence-event-catalog/resolved-persistence-types/workflowagentoutcome: en: d0a3f9aab18c3de5 zh: 76ac69a835ca4e29 @@ -1371,9 +1380,12 @@ en: f692844b29923b2e zh: 72f12bca08d63267 /session-persistence-event-catalog/resolved-persistence-types/union-2-variants~2: - en: 69d360cf5a1ae6b4 - zh: f23faf39a2a0e6f1 + en: 2453bf81cd9cd1f6 + zh: b1cef542e013c238 /session-persistence-event-catalog/resolved-persistence-types/union-2-variants~3: + en: 1137a9a48ddad4b3 + zh: 222c1545cdb19d76 +/session-persistence-event-catalog/resolved-persistence-types/union-2-variants~4: en: 5a1af9849b0967ea zh: 1085d793bbc47360 /session-persistence-event-catalog/resolved-persistence-types/union-3-variants: @@ -1443,8 +1455,11 @@ en: 43cf5cd666b591a0 zh: 7c7a3011f8681f12 /session-persistence-event-catalog/resolved-persistence-types/childcreatedat-childid-label-mode~6: - en: a1f188c9058cf621 - zh: eb43c0f045fb9afb + en: 959fc569699d21f0 + zh: b48a0a3b1639957d +/session-persistence-event-catalog/resolved-persistence-types/clienttimezone-kind-retry-principal-rpcid: + en: 1c523784a95eff23 + zh: e19a26cb9ae14e12 /session-persistence-event-catalog/resolved-persistence-types/code-name-reason: en: 25df2af19bc9c46d zh: ebb6ecbeee25c9be @@ -1503,11 +1518,11 @@ en: 51af619ed8532a43 zh: a5ad878c63d0e046 /session-persistence-event-catalog/resolved-persistence-types/header-reason-startsseries: - en: 27f5b76003ac1ead - zh: 2a5a0490cc22512b + en: 616424f7d89b2617 + zh: 1a90b1c57ecb4f88 /session-persistence-event-catalog/resolved-persistence-types/headerseq-message-step-turn: - en: 598021cdfe2a8687 - zh: 02e63a8b453f2f4e + en: bcdb5e4a5a9b0aac + zh: a214e6242694bfdd /session-persistence-event-catalog/resolved-persistence-types/height-width: en: a5df50710083ac25 zh: aad3ccf6f6de70e4 @@ -1524,11 +1539,14 @@ en: 41f73c58a77dfa28 zh: af77f4aee3cb4a40 /session-persistence-event-catalog/resolved-persistence-types/inherited: - en: 7e64cae3bd1b71e9 - zh: 7451c0639044c59a + en: 7b92563703daff7b + zh: c91b356048b96141 +/session-persistence-event-catalog/resolved-persistence-types/inputid-source-step-turn: + en: 930d9c64fe39857d + zh: 65cd07dc3c7789db /session-persistence-event-catalog/resolved-persistence-types/inserted-outcome-removedcount-start: - en: df2dd98c35b34bd2 - zh: ee25992a3f29d561 + en: 4edbaf2329a66619 + zh: 7d29428252d9d84f /session-persistence-event-catalog/resolved-persistence-types/interrupted-message-step-stream: en: 8e86cd838e3e4a49 zh: f5fec2a8adf1f3cc @@ -1719,8 +1737,11 @@ en: 1f6dbdbf5d923df0 zh: aea576e5ec34c0fc /session-persistence-event-catalog/resolved-persistence-types/kind-repeat-tool-reminder-form-snapshot: - en: e9c88cceef2d8432 - zh: 14fb0897543b6960 + en: fb3160150710d86b + zh: 27a71fa774531620 +/session-persistence-event-catalog/resolved-persistence-types/kind-retry: + en: db4c8e67a5176225 + zh: 762719a4d2ab1f73 /session-persistence-event-catalog/resolved-persistence-types/kind-runtime-context: en: 68b5598e21cf8ba2 zh: 95ef1685af64a5f4 @@ -1944,11 +1965,14 @@ en: 374b8adee8d571fa zh: b21057efc58e6490 /session-persistence-event-catalog/resolved-persistence-types/type-agent-preset-selected: - en: a990181189cf77ac - zh: 70198727356d1ad3 + en: 7626d618ab37dec4 + zh: 662b0be4c51feebc /session-persistence-event-catalog/resolved-persistence-types/type-agent-inbox-spliced: - en: f3b491e65c79b3a0 - zh: 2d3e53ceb67a55a3 + en: 60466c7cafae41b5 + zh: 9dee130ddd7b0fd1 +/session-persistence-event-catalog/resolved-persistence-types/type-agent-retry: + en: 7a5e1c92d8760186 + zh: ae4173989281facd /session-persistence-event-catalog/resolved-persistence-types/type-approval-asked: en: 9c77f18b0dad22f0 zh: 8894cf2878fe90db @@ -1992,11 +2016,11 @@ en: ba73fb10b7e2ffc9 zh: 8d5c97e63e5f16da /session-persistence-event-catalog/resolved-persistence-types/type-deliverables-presented: - en: 17956f440c284ea3 - zh: 5dbfd68d50483047 + en: 7fbea1c722f8c2f3 + zh: d732349d783b9b01 /session-persistence-event-catalog/resolved-persistence-types/type-developer-message: - en: ded2f3b02f776800 - zh: 4d6619c283a18e3c + en: f231622830903402 + zh: 03457dc27539fafd /session-persistence-event-catalog/resolved-persistence-types/type-disabled: en: 585627a50ece4e5f zh: 157837aee3da0ac3 @@ -2070,11 +2094,11 @@ en: acf7e21e4d21ebff zh: fb83a853634d204a /session-persistence-event-catalog/resolved-persistence-types/type-session-title: - en: f0acf1c873cd9e2a - zh: b8e175e14306afd4 + en: a642705c8e44a3c9 + zh: 10aa1aff0d3766d3 /session-persistence-event-catalog/resolved-persistence-types/type-session-title-llm-request: - en: ab4f20d890eed5e5 - zh: 6e4035a166821157 + en: dd94dea63f118b12 + zh: e7feae9219037e9a /session-persistence-event-catalog/resolved-persistence-types/type-step-end: en: 7fc233c4a569dae2 zh: bc2b3fed4a397c00 @@ -2151,11 +2175,11 @@ en: 1b045de96172be4b zh: 8a15c6f35b576b37 /session-persistence-event-catalog/resolved-persistence-types/type-usage: - en: 4ed36e4498f5f203 - zh: 7b3fcddec14c306b + en: 311b42bd63735e87 + zh: 37ea452a672fca21 /session-persistence-event-catalog/resolved-persistence-types/type-user-message: - en: 5b8341a673284ec1 - zh: e3264a827babffeb + en: 07e60b23dc23e0a3 + zh: dc74747ec207e1bb /session-persistence-event-catalog/resolved-persistence-types/type-web-deepseek-search-llm-request: en: 99c8837344178a47 zh: bf746f5678346596 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index a498e62e2e..70e16e7ce7 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -23,7 +23,8 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`HeaderLine`](#persistence-type-sha256-18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f) | | `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`union (3 variants)`](#persistence-type-sha256-5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc) | | `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`{ type: "agent-preset/selected" }`](#persistence-type-sha256-a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b) | -| `event:agent/inbox/spliced` | event | `9e8043098fb89db6d7c92a92c70146c5c8d24fa88f733c687fa29e5e7a58ab30` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-9e8043098fb89db6d7c92a92c70146c5c8d24fa88f733c687fa29e5e7a58ab30) | +| `event:agent/inbox/spliced` | event | `a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664) | +| `event:agent/retry` | event | `d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c` | [`{ type: "agent/retry" }`](#persistence-type-sha256-d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c) | | `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`{ type: "approval/asked" }`](#persistence-type-sha256-3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1) | | `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`{ type: "approval/decided" }`](#persistence-type-sha256-bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3) | | `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`{ type: "approval/policy" }`](#persistence-type-sha256-26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78) | @@ -36,7 +37,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`{ type: "compaction/start" }`](#persistence-type-sha256-db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965) | | `event:compaction/summary` | event | `e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602` | [`{ type: "compaction/summary" }`](#persistence-type-sha256-e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602) | | `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`{ type: "deliverables/presented" }`](#persistence-type-sha256-13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176) | -| `event:developer/message` | event | `95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f` | [`{ type: "developer/message" }`](#persistence-type-sha256-95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f) | +| `event:developer/message` | event | `92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583` | [`{ type: "developer/message" }`](#persistence-type-sha256-92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583) | | `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`{ type: "feedback/message-delete" }`](#persistence-type-sha256-3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796) | | `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`{ type: "feedback/message-put" }`](#persistence-type-sha256-b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf) | | `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`{ type: "feedback/record" }`](#persistence-type-sha256-b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8) | @@ -58,7 +59,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) | | `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) | | `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) | -| `event:session/title-llm-request` | event | `eaa9125aec0fb28478096c46e73e21bf512ab0d84d826bfb41e7308707734cb5` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-eaa9125aec0fb28478096c46e73e21bf512ab0d84d826bfb41e7308707734cb5) | +| `event:session/title-llm-request` | event | `fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172) | | `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) | | `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) | | `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) | @@ -80,7 +81,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:tool/result` | event | `7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397` | [`{ type: "tool/result" }`](#persistence-type-sha256-7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397) | | `event:turn/end` | event | `0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce` | [`{ type: "turn/end" }`](#persistence-type-sha256-0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce) | | `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`{ type: "turn/start" }`](#persistence-type-sha256-aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8) | -| `event:user/message` | event | `39adb7fce8475af6402acd925ea79697b3341c22422ac32833d71217d84e987f` | [`{ type: "user/message" }`](#persistence-type-sha256-39adb7fce8475af6402acd925ea79697b3341c22422ac32833d71217d84e987f) | +| `event:user/message` | event | `c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5` | [`{ type: "user/message" }`](#persistence-type-sha256-c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5) | | `event:web/deepseek-search-llm-request` | event | `0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52` | [`{ type: "web/deepseek-search-llm-request" }`](#persistence-type-sha256-0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52) | | `event:working-directory/change` | event | `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` | [`{ type: "working-directory/change" }`](#persistence-type-sha256-124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3) | | `event:workspace/changes` | event | `e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72` | [`{ type: "workspace/changes" }`](#persistence-type-sha256-e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72) | @@ -185,7 +186,20 @@ Sources: [`packages/core/session/src/types.ts:439`](../packages/core/session/src } ``` -Source: [`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) +Source: [`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/types.ts) + + + +#### `agent/retry` — log-only + +```ts persistence-catalog +/** An admitted retry control, correlated with its durable inbox input; never enters model history. */ +'agent/retry': { turn: number; step: number; inputId: MessageId; source: Extract } +``` + +Types: [MessageSource](subsystems/core.md) + +Source: [`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/types.ts) ### `agent-preset/*` @@ -700,7 +714,7 @@ Source: [`packages/llm/llm-retry/src/types.ts:11`](../packages/llm/llm-retry/src 'model/selection': ModelSelection ``` -Source: [`packages/core/agent/src/types.ts:121`](../packages/core/agent/src/types.ts) +Source: [`packages/core/agent/src/types.ts:130`](../packages/core/agent/src/types.ts) ### `permission/*` @@ -1405,6 +1419,14 @@ SHA-256: `829a2fb3be6efd4ba36aef8ed2d381a4c6fb2a0aaf50c823a6f3f8290786b809` `"agent/inbox/spliced"` + + +### `"agent/retry"` + +SHA-256: `30093f9f63a120cb932f07577594c04afcec3f0bd02859154a6e5b0e79a46359` + +`"agent/retry"` + ### `"allowed-once"` @@ -2427,6 +2449,14 @@ SHA-256: `34d1ab5c5df378186d6054b1a1beea9a41e41965d6f336f0bc0025c441a82e66` `"resume"` + + +### `"retry"` + +SHA-256: `63ba8895cc09e4693cf0819fdcd18c084bef195febda5b6a23af279b3852be36` + +`"retry"` + ### `"runtime-context"` @@ -3375,7 +3405,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:55`](../packages/web | `body` | required | [`{ max_tokens, messages, model, thinking?, … }`](#persistence-type-sha256-9c001ecb5e1d3c3894410db2a1e383842e5f8454fb52182338686158abc1b3ee) | | `endpoint` | required | `string` | - + @@ -3383,7 +3413,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:55`](../packages/web ### `DeveloperMessage` -SHA-256: `cebb7e9f3f663e660a531d9764ecfb9fa7fc73c22bf8d628f888e9ecf18c5cf0` +SHA-256: `0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc` Sources: [`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message.ts) @@ -3392,7 +3422,7 @@ Sources: [`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | required | `string` | | `role` | required | `"developer"` | -| `source` | required | [`MessageSource`](#persistence-type-sha256-b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591) | +| `source` | required | [`MessageSource`](#persistence-type-sha256-e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c) | Source compatibility: `source` — `session-source-attribution` v1; `session.developer-message.source`; `kind`; `preserve`. @@ -3798,7 +3828,7 @@ Array of [`ImageOffloadTarget`](#persistence-type-sha256-1cde1bedde79c5e8a56aee9 SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c` -Sources: [`packages/core/agent/src/types.ts:40`](../packages/core/agent/src/types.ts) +Sources: [`packages/core/agent/src/types.ts:47`](../packages/core/agent/src/types.ts) One of: @@ -4091,7 +4121,7 @@ Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/sr | `step` | required | `number` | | `turn` | required | `number` | - + @@ -4099,15 +4129,15 @@ Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/sr ### `Message` -SHA-256: `4f9125fa028a56286fac70176ecc670d12f4402d8cbe112e14f0639f5e8e5c9d` +SHA-256: `7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2` Sources: [`packages/llm/llm/src/message.ts:196`](../packages/llm/llm/src/message.ts) One of: - [`ToolResultMessage`](#persistence-type-sha256-4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731) -- [`DeveloperMessage`](#persistence-type-sha256-cebb7e9f3f663e660a531d9764ecfb9fa7fc73c22bf8d628f888e9ecf18c5cf0) -- [`UserMessage`](#persistence-type-sha256-8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14) +- [`DeveloperMessage`](#persistence-type-sha256-0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc) +- [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) - [`SystemMessage`](#persistence-type-sha256-09f3f8dc417f278257202714132f98ba7b52957d7b558c4a04456603890eb363) - [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) @@ -4184,7 +4214,7 @@ One of: - `"negative"` - `"positive"` - + @@ -4192,7 +4222,7 @@ One of: ### `MessageSource` -SHA-256: `b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591` +SHA-256: `e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c` Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message.ts) @@ -4254,6 +4284,8 @@ Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message | `repeat-tool-reminder` | `"relay"` | none | [`{ kind: "repeat-tool-reminder", form: "relay" }`](#persistence-type-sha256-4991dfc415c9efebc36fa6969e1798c7d2e42396cc1e6e0ac7d7c89c5c5c4aea) | | `repeat-tool-reminder` | `"snapshot"` | `sections`: [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | [`{ kind: "repeat-tool-reminder", form: "snapshot" }`](#persistence-type-sha256-db06ef35b27a38425046b2228fa6bce9582abe140d781d363137eb2f4d233a1c) | | `repeat-tool-reminder` | not declared | none | [`{ kind: "repeat-tool-reminder" }`](#persistence-type-sha256-b062078e899958f99ae464514578b8c093924dfe34bd9a835932b420ff2150b1) | +| `retry` | not declared | none | [`{ kind: "retry" }`](#persistence-type-sha256-414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9) | +| `retry` | not declared | `rpcId`: `string` | [`{ clientTimeZone?, kind: "retry", principal?, rpcId }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | | `runtime-context` | `"catalog"` | none | [`{ kind: "runtime-context", form: "catalog" }`](#persistence-type-sha256-58015da1c1950a66daf66ae24913d6a1fc6d1d9ac441b155f8847f7f8584ba8e) | | `runtime-context` | `"instructions"` | none | [`{ kind: "runtime-context", form: "instructions" }`](#persistence-type-sha256-0eeb17e1df89b1ebc2902116fe7145e0d321ef0db26e6bdebc802cb8513a01c1) | | `runtime-context` | `"notice"` | `summary`: `string` | [`{ kind: "runtime-context", form: "notice" }`](#persistence-type-sha256-cda2345a9be9d139df49ccbd16dcba716f3440178bf20ff8b801fda1f5eb77dc) | @@ -4317,13 +4349,13 @@ Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message | `webhook` | `"notice"` | `deliveryId`: `string`; `provider`: `string`; `ruleId`: `string`; `source`: `string`; `summary`: `string` | [`{ kind: "webhook", form: "notice" }`](#persistence-type-sha256-9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94) | | `working-directory` | not declared | none | [`{ kind: "working-directory" }`](#persistence-type-sha256-2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5) | - + ### `Message[]` -SHA-256: `d2b617d9dfcc4e0a4b60732d8ba1effc632e3b75124b93b064c07814a0a716bc` +SHA-256: `2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff` -Array of [`Message`](#persistence-type-sha256-4f9125fa028a56286fac70176ecc670d12f4402d8cbe112e14f0639f5e8e5c9d). +Array of [`Message`](#persistence-type-sha256-7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2). @@ -4354,7 +4386,7 @@ Sources: [`packages/llm/llm/src/message.ts:24`](../packages/llm/llm/src/message. SHA-256: `f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b` -Sources: [`packages/core/agent/src/types.ts:79`](../packages/core/agent/src/types.ts) +Sources: [`packages/core/agent/src/types.ts:86`](../packages/core/agent/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -4707,7 +4739,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/ | `source` | required | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) | | `title` | required | `string` | - + @@ -4715,7 +4747,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/ ### `SessionTitleLlmRequestEventData` -SHA-256: `9808c3d0f0a1be8fe313b4148ba22c691c4d59bd2c67aef3c63cfe8043148c5f` +SHA-256: `8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60` Sources: [`packages/session/session-title-llm/src/index.ts:37`](../packages/session/session-title-llm/src/index.ts) @@ -4723,7 +4755,7 @@ Sources: [`packages/session/session-title-llm/src/index.ts:37`](../packages/sess |---|---|---| | `maxTokens` | required | `number` | | `messageSeqs` | required | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | -| `messages` | required | [`Message[]`](#persistence-type-sha256-d2b617d9dfcc4e0a4b60732d8ba1effc632e3b75124b93b064c07814a0a716bc) | +| `messages` | required | [`Message[]`](#persistence-type-sha256-2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff) | | `reasoningEffort` | optional | `string` | | `route` | required | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) | | `system` | required | `string` | @@ -5380,7 +5412,7 @@ Sources: [`packages/core/session/src/types.ts:238`](../packages/core/session/src | `interrupted` | not declared | none | [`{ kind: "interrupted" }`](#persistence-type-sha256-56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23) | | `max-tokens` | not declared | none | [`{ kind: "max-tokens" }`](#persistence-type-sha256-ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7) | - + @@ -5388,7 +5420,7 @@ Sources: [`packages/core/session/src/types.ts:238`](../packages/core/session/src ### `UserMessage` -SHA-256: `8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14` +SHA-256: `9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263` Sources: [`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message.ts) @@ -5397,19 +5429,19 @@ Sources: [`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | required | `string` | | `role` | required | `"user"` | -| `source` | required | [`MessageSource`](#persistence-type-sha256-b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591) | +| `source` | required | [`MessageSource`](#persistence-type-sha256-e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c) | Source compatibility: `source` — `session-source-attribution` v1; `session.user-message.source`; `kind`; `preserve`. Attribution-only additions: `action-cards`, `tmux-context`, `user-question-reply`, `working-directory`. - + ### `UserMessage[]` -SHA-256: `aada2771a250136cc3a839b0507e9c6502622a2897716903942297bcb6661292` +SHA-256: `347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6` -Array of [`UserMessage`](#persistence-type-sha256-8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14). +Array of [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263). @@ -5673,6 +5705,17 @@ One of: - `"fork"` - `"fresh"` + + +### `union (2 variants)` + +SHA-256: `671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1` + +| kind | Form property | Other required fields | Full definition | +|---|---|---|---| +| `retry` | not declared | none | [`{ kind: "retry" }`](#persistence-type-sha256-414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9) | +| `retry` | not declared | `rpcId`: `string` | [`{ clientTimeZone?, kind: "retry", principal?, rpcId }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | + ### `union (2 variants)` @@ -5991,6 +6034,21 @@ SHA-256: `cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09` | `mode` | required | `"external"` | | `version` | required | `2` | + + +### `{ clientTimeZone?, kind: "retry", principal?, rpcId }` + +SHA-256: `e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664` + +Sources: [`packages/api/session-controller/src/types.ts:399`](../packages/api/session-controller/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `clientTimeZone` | optional | `string` | +| `kind` | required | `"retry"` | +| `principal` | optional | [`Principal`](#persistence-type-sha256-deebfc7c62177ea840f23c6362b8615bc78ffa93377dd5d243c04e541c413af5) | +| `rpcId` | required | `string` | + ### `{ code, name, reason? }` @@ -6294,18 +6352,18 @@ Sources: [`packages/core/session/src/types.ts:398`](../packages/core/session/src | `reason` | required | [`RequestHeaderReason`](#persistence-type-sha256-530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6) | | `startsSeries` | optional | `true` | - + ### `{ headerSeq?, message, step, turn }` -SHA-256: `12f9a5a7506bca46d6ef8e3c71826c4bf40e1389bfe0236cfffaea0c3fb367d2` +SHA-256: `d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2` Sources: [`packages/core/session/src/types.ts:317`](../packages/core/session/src/types.ts) | Property | Presence | Type | |---|---|---| | `headerSeq` | optional | `number` | -| `message` | required | [`DeveloperMessage`](#persistence-type-sha256-cebb7e9f3f663e660a531d9764ecfb9fa7fc73c22bf8d628f888e9ecf18c5cf0) | +| `message` | required | [`DeveloperMessage`](#persistence-type-sha256-0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc) | | `step` | required | `number` | | `turn` | required | `number` | @@ -6389,17 +6447,32 @@ Sources: [`packages/core/session/src/types.ts:435`](../packages/core/session/src |---|---|---| | `inherited` | optional | `true` | - + -### `{ inserted, outcome?, removedCount?, start, … }` +### `{ inputId, source, step, turn }` -SHA-256: `c433ef580a5b229bbe68274a7164aa3a8e8040d7217f010b610ca0696a84253c` +SHA-256: `2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3` -Sources: [`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) +Sources: [`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/types.ts) | Property | Presence | Type | |---|---|---| -| `inserted` | required | [`UserMessage[]`](#persistence-type-sha256-aada2771a250136cc3a839b0507e9c6502622a2897716903942297bcb6661292) | +| `inputId` | required | `string` | +| `source` | required | [`union (2 variants)`](#persistence-type-sha256-671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1) | +| `step` | required | `number` | +| `turn` | required | `number` | + + + +### `{ inserted, outcome?, removedCount?, start, … }` + +SHA-256: `c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039` + +Sources: [`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `inserted` | required | [`UserMessage[]`](#persistence-type-sha256-347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6) | | `outcome` | optional | `"canceled"` | | `removedCount` | optional | `number` | | `start` | required | `number` | @@ -7147,6 +7220,18 @@ SHA-256: `db06ef35b27a38425046b2228fa6bce9582abe140d781d363137eb2f4d233a1c` | `kind` | required | `"repeat-tool-reminder"` | | `sections` | required | [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | + + +### `{ kind: "retry" }` + +SHA-256: `414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9` + +Sources: [`packages/core/agent/src/types.ts:24`](../packages/core/agent/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"retry"` | + ### `{ kind: "runtime-context" }` @@ -8046,22 +8131,38 @@ SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | `time` | required | `number` | | `type` | required | `"agent-preset/selected"` | - + ### `{ type: "agent/inbox/spliced" }` -SHA-256: `9e8043098fb89db6d7c92a92c70146c5c8d24fa88f733c687fa29e5e7a58ab30` +SHA-256: `a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664` | Property | Presence | Type | |---|---|---| -| `data` | required | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-c433ef580a5b229bbe68274a7164aa3a8e8040d7217f010b610ca0696a84253c) | +| `data` | required | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | | `type` | required | `"agent/inbox/spliced"` | + + + + +### `{ type: "agent/retry" }` + +SHA-256: `d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`{ inputId, source, step, turn }`](#persistence-type-sha256-2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"agent/retry"` | + @@ -8297,17 +8398,17 @@ SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | `time` | required | `number` | | `type` | required | `"deliverables/presented"` | - + ### `{ type: "developer/message" }` -SHA-256: `95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f` +SHA-256: `92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583` | Property | Presence | Type | |---|---|---| -| `data` | required | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-12f9a5a7506bca46d6ef8e3c71826c4bf40e1389bfe0236cfffaea0c3fb367d2) | +| `data` | required | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `sourceEventSeqs` | optional | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8707,17 +8808,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | `time` | required | `number` | | `type` | required | `"session/title"` | - + ### `{ type: "session/title-llm-request" }` -SHA-256: `eaa9125aec0fb28478096c46e73e21bf512ab0d84d826bfb41e7308707734cb5` +SHA-256: `fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172` | Property | Presence | Type | |---|---|---| -| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-9808c3d0f0a1be8fe313b4148ba22c691c4d59bd2c67aef3c63cfe8043148c5f) | +| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | @@ -9140,17 +9241,17 @@ Sources: [`packages/llm/llm/src/types.ts:476`](../packages/llm/llm/src/types.ts) | `type` | required | `"usage"` | | `usage` | required | [`TokenUsage`](#persistence-type-sha256-fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394) | - + ### `{ type: "user/message" }` -SHA-256: `39adb7fce8475af6402acd925ea79697b3341c22422ac32833d71217d84e987f` +SHA-256: `c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5` | Property | Presence | Type | |---|---|---| -| `data` | required | [`UserMessage`](#persistence-type-sha256-8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14) | +| `data` | required | [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `sourceEventSeqs` | optional | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index d75ff6f2b1..47f1c30ed9 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -25,7 +25,8 @@ | `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`HeaderLine`](#persistence-type-sha256-18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f) | | `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`union (3 variants)`](#persistence-type-sha256-5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc) | | `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`{ type: "agent-preset/selected" }`](#persistence-type-sha256-a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b) | -| `event:agent/inbox/spliced` | event | `9e8043098fb89db6d7c92a92c70146c5c8d24fa88f733c687fa29e5e7a58ab30` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-9e8043098fb89db6d7c92a92c70146c5c8d24fa88f733c687fa29e5e7a58ab30) | +| `event:agent/inbox/spliced` | event | `a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664) | +| `event:agent/retry` | event | `d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c` | [`{ type: "agent/retry" }`](#persistence-type-sha256-d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c) | | `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`{ type: "approval/asked" }`](#persistence-type-sha256-3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1) | | `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`{ type: "approval/decided" }`](#persistence-type-sha256-bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3) | | `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`{ type: "approval/policy" }`](#persistence-type-sha256-26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78) | @@ -38,7 +39,7 @@ | `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`{ type: "compaction/start" }`](#persistence-type-sha256-db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965) | | `event:compaction/summary` | event | `e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602` | [`{ type: "compaction/summary" }`](#persistence-type-sha256-e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602) | | `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`{ type: "deliverables/presented" }`](#persistence-type-sha256-13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176) | -| `event:developer/message` | event | `95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f` | [`{ type: "developer/message" }`](#persistence-type-sha256-95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f) | +| `event:developer/message` | event | `92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583` | [`{ type: "developer/message" }`](#persistence-type-sha256-92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583) | | `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`{ type: "feedback/message-delete" }`](#persistence-type-sha256-3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796) | | `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`{ type: "feedback/message-put" }`](#persistence-type-sha256-b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf) | | `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`{ type: "feedback/record" }`](#persistence-type-sha256-b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8) | @@ -60,7 +61,7 @@ | `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) | | `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) | | `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) | -| `event:session/title-llm-request` | event | `eaa9125aec0fb28478096c46e73e21bf512ab0d84d826bfb41e7308707734cb5` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-eaa9125aec0fb28478096c46e73e21bf512ab0d84d826bfb41e7308707734cb5) | +| `event:session/title-llm-request` | event | `fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172) | | `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) | | `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) | | `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) | @@ -82,7 +83,7 @@ | `event:tool/result` | event | `7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397` | [`{ type: "tool/result" }`](#persistence-type-sha256-7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397) | | `event:turn/end` | event | `0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce` | [`{ type: "turn/end" }`](#persistence-type-sha256-0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce) | | `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`{ type: "turn/start" }`](#persistence-type-sha256-aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8) | -| `event:user/message` | event | `39adb7fce8475af6402acd925ea79697b3341c22422ac32833d71217d84e987f` | [`{ type: "user/message" }`](#persistence-type-sha256-39adb7fce8475af6402acd925ea79697b3341c22422ac32833d71217d84e987f) | +| `event:user/message` | event | `c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5` | [`{ type: "user/message" }`](#persistence-type-sha256-c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5) | | `event:web/deepseek-search-llm-request` | event | `0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52` | [`{ type: "web/deepseek-search-llm-request" }`](#persistence-type-sha256-0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52) | | `event:working-directory/change` | event | `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` | [`{ type: "working-directory/change" }`](#persistence-type-sha256-124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3) | | `event:workspace/changes` | event | `e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72` | [`{ type: "workspace/changes" }`](#persistence-type-sha256-e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72) | @@ -187,7 +188,20 @@ export type SessionEvent = { } ``` -来源:[`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/types.ts) + + + +#### `agent/retry` — log-only + +```ts persistence-catalog +/** An admitted retry control, correlated with its durable inbox input; never enters model history. */ +'agent/retry': { turn: number; step: number; inputId: MessageId; source: Extract } +``` + +类型:[MessageSource](subsystems/core.zh.md) + +来源:[`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/types.ts) ### `agent-preset/*` @@ -702,7 +716,7 @@ export type SessionEvent = { 'model/selection': ModelSelection ``` -来源:[`packages/core/agent/src/types.ts:121`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:130`](../packages/core/agent/src/types.ts) ### `permission/*` @@ -1407,6 +1421,14 @@ SHA-256: `829a2fb3be6efd4ba36aef8ed2d381a4c6fb2a0aaf50c823a6f3f8290786b809` `"agent/inbox/spliced"` + + +### `"agent/retry"` + +SHA-256: `30093f9f63a120cb932f07577594c04afcec3f0bd02859154a6e5b0e79a46359` + +`"agent/retry"` + ### `"allowed-once"` @@ -2429,6 +2451,14 @@ SHA-256: `34d1ab5c5df378186d6054b1a1beea9a41e41965d6f336f0bc0025c441a82e66` `"resume"` + + +### `"retry"` + +SHA-256: `63ba8895cc09e4693cf0819fdcd18c084bef195febda5b6a23af279b3852be36` + +`"retry"` + ### `"runtime-context"` @@ -3377,7 +3407,7 @@ SHA-256: `abd6ca62a5ab166553848324a23fef90eed7f59c8021bd63e7ea5bc6fd666b0c` | `body` | 必需 | [`{ max_tokens, messages, model, thinking?, … }`](#persistence-type-sha256-9c001ecb5e1d3c3894410db2a1e383842e5f8454fb52182338686158abc1b3ee) | | `endpoint` | 必需 | `string` | - + @@ -3385,7 +3415,7 @@ SHA-256: `abd6ca62a5ab166553848324a23fef90eed7f59c8021bd63e7ea5bc6fd666b0c` ### `DeveloperMessage` -SHA-256: `cebb7e9f3f663e660a531d9764ecfb9fa7fc73c22bf8d628f888e9ecf18c5cf0` +SHA-256: `0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc` 来源:[`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message.ts) @@ -3394,7 +3424,7 @@ SHA-256: `cebb7e9f3f663e660a531d9764ecfb9fa7fc73c22bf8d628f888e9ecf18c5cf0` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | 必需 | `string` | | `role` | 必需 | `"developer"` | -| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591) | +| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c) | 消息源兼容策略:`source` — `session-source-attribution` v1; `session.developer-message.source`; `kind`; `preserve`. @@ -3800,7 +3830,7 @@ SHA-256: `9e2c06d8653585b33d72d5c1a80da79c8a24a88c284409bca15cc0f0ead5ebfb` SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c` -来源:[`packages/core/agent/src/types.ts:40`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:47`](../packages/core/agent/src/types.ts) 以下类型之一: @@ -4093,7 +4123,7 @@ SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` | `step` | 必需 | `number` | | `turn` | 必需 | `number` | - + @@ -4101,15 +4131,15 @@ SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` ### `Message` -SHA-256: `4f9125fa028a56286fac70176ecc670d12f4402d8cbe112e14f0639f5e8e5c9d` +SHA-256: `7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2` 来源:[`packages/llm/llm/src/message.ts:196`](../packages/llm/llm/src/message.ts) 以下类型之一: - [`ToolResultMessage`](#persistence-type-sha256-4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731) -- [`DeveloperMessage`](#persistence-type-sha256-cebb7e9f3f663e660a531d9764ecfb9fa7fc73c22bf8d628f888e9ecf18c5cf0) -- [`UserMessage`](#persistence-type-sha256-8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14) +- [`DeveloperMessage`](#persistence-type-sha256-0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc) +- [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) - [`SystemMessage`](#persistence-type-sha256-09f3f8dc417f278257202714132f98ba7b52957d7b558c4a04456603890eb363) - [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) @@ -4186,7 +4216,7 @@ SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` - `"negative"` - `"positive"` - + @@ -4194,7 +4224,7 @@ SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` ### `MessageSource` -SHA-256: `b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591` +SHA-256: `e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c` 来源:[`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message.ts) @@ -4256,6 +4286,8 @@ SHA-256: `b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591` | `repeat-tool-reminder` | `"relay"` | 无 | [`{ kind: "repeat-tool-reminder", form: "relay" }`](#persistence-type-sha256-4991dfc415c9efebc36fa6969e1798c7d2e42396cc1e6e0ac7d7c89c5c5c4aea) | | `repeat-tool-reminder` | `"snapshot"` | `sections`: [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | [`{ kind: "repeat-tool-reminder", form: "snapshot" }`](#persistence-type-sha256-db06ef35b27a38425046b2228fa6bce9582abe140d781d363137eb2f4d233a1c) | | `repeat-tool-reminder` | 未声明 | 无 | [`{ kind: "repeat-tool-reminder" }`](#persistence-type-sha256-b062078e899958f99ae464514578b8c093924dfe34bd9a835932b420ff2150b1) | +| `retry` | 未声明 | 无 | [`{ kind: "retry" }`](#persistence-type-sha256-414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9) | +| `retry` | 未声明 | `rpcId`: `string` | [`{ clientTimeZone?, kind: "retry", principal?, rpcId }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | | `runtime-context` | `"catalog"` | 无 | [`{ kind: "runtime-context", form: "catalog" }`](#persistence-type-sha256-58015da1c1950a66daf66ae24913d6a1fc6d1d9ac441b155f8847f7f8584ba8e) | | `runtime-context` | `"instructions"` | 无 | [`{ kind: "runtime-context", form: "instructions" }`](#persistence-type-sha256-0eeb17e1df89b1ebc2902116fe7145e0d321ef0db26e6bdebc802cb8513a01c1) | | `runtime-context` | `"notice"` | `summary`: `string` | [`{ kind: "runtime-context", form: "notice" }`](#persistence-type-sha256-cda2345a9be9d139df49ccbd16dcba716f3440178bf20ff8b801fda1f5eb77dc) | @@ -4319,13 +4351,13 @@ SHA-256: `b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591` | `webhook` | `"notice"` | `deliveryId`: `string`; `provider`: `string`; `ruleId`: `string`; `source`: `string`; `summary`: `string` | [`{ kind: "webhook", form: "notice" }`](#persistence-type-sha256-9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94) | | `working-directory` | 未声明 | 无 | [`{ kind: "working-directory" }`](#persistence-type-sha256-2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5) | - + ### `Message[]` -SHA-256: `d2b617d9dfcc4e0a4b60732d8ba1effc632e3b75124b93b064c07814a0a716bc` +SHA-256: `2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff` -[`Message`](#persistence-type-sha256-4f9125fa028a56286fac70176ecc670d12f4402d8cbe112e14f0639f5e8e5c9d) 的数组。 +[`Message`](#persistence-type-sha256-7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2) 的数组。 @@ -4356,7 +4388,7 @@ SHA-256: `719c51f67074f97034a6c94297fe1f4b5d3c8d392c57586dab732aa5398c7a91` SHA-256: `f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b` -来源:[`packages/core/agent/src/types.ts:79`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:86`](../packages/core/agent/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -4709,7 +4741,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` | `source` | 必需 | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) | | `title` | 必需 | `string` | - + @@ -4717,7 +4749,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` ### `SessionTitleLlmRequestEventData` -SHA-256: `9808c3d0f0a1be8fe313b4148ba22c691c4d59bd2c67aef3c63cfe8043148c5f` +SHA-256: `8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60` 来源:[`packages/session/session-title-llm/src/index.ts:37`](../packages/session/session-title-llm/src/index.ts) @@ -4725,7 +4757,7 @@ SHA-256: `9808c3d0f0a1be8fe313b4148ba22c691c4d59bd2c67aef3c63cfe8043148c5f` |---|---|---| | `maxTokens` | 必需 | `number` | | `messageSeqs` | 必需 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | -| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-d2b617d9dfcc4e0a4b60732d8ba1effc632e3b75124b93b064c07814a0a716bc) | +| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff) | | `reasoningEffort` | 可选 | `string` | | `route` | 必需 | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) | | `system` | 必需 | `string` | @@ -5382,7 +5414,7 @@ SHA-256: `f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402` | `interrupted` | 未声明 | 无 | [`{ kind: "interrupted" }`](#persistence-type-sha256-56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23) | | `max-tokens` | 未声明 | 无 | [`{ kind: "max-tokens" }`](#persistence-type-sha256-ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7) | - + @@ -5390,7 +5422,7 @@ SHA-256: `f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402` ### `UserMessage` -SHA-256: `8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14` +SHA-256: `9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263` 来源:[`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message.ts) @@ -5399,19 +5431,19 @@ SHA-256: `8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | 必需 | `string` | | `role` | 必需 | `"user"` | -| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591) | +| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c) | 消息源兼容策略:`source` — `session-source-attribution` v1; `session.user-message.source`; `kind`; `preserve`. 仅表示归属的新增 kind:`action-cards`, `tmux-context`, `user-question-reply`, `working-directory`. - + ### `UserMessage[]` -SHA-256: `aada2771a250136cc3a839b0507e9c6502622a2897716903942297bcb6661292` +SHA-256: `347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6` -[`UserMessage`](#persistence-type-sha256-8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14) 的数组。 +[`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) 的数组。 @@ -5675,6 +5707,17 @@ SHA-256: `50cf02d875f8a1f1411f4a6fbcffab9120ccef5f74a47331fbf29480e5f68f48` - `"fork"` - `"fresh"` + + +### `union (2 variants)` + +SHA-256: `671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1` + +| kind | form 属性 | 其他必需字段 | 完整定义 | +|---|---|---|---| +| `retry` | 未声明 | 无 | [`{ kind: "retry" }`](#persistence-type-sha256-414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9) | +| `retry` | 未声明 | `rpcId`: `string` | [`{ clientTimeZone?, kind: "retry", principal?, rpcId }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | + ### `union (2 variants)` @@ -5993,6 +6036,21 @@ SHA-256: `cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09` | `mode` | 必需 | `"external"` | | `version` | 必需 | `2` | + + +### `{ clientTimeZone?, kind: "retry", principal?, rpcId }` + +SHA-256: `e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664` + +来源:[`packages/api/session-controller/src/types.ts:399`](../packages/api/session-controller/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `clientTimeZone` | 可选 | `string` | +| `kind` | 必需 | `"retry"` | +| `principal` | 可选 | [`Principal`](#persistence-type-sha256-deebfc7c62177ea840f23c6362b8615bc78ffa93377dd5d243c04e541c413af5) | +| `rpcId` | 必需 | `string` | + ### `{ code, name, reason? }` @@ -6296,18 +6354,18 @@ SHA-256: `ad0c38a0adb82a94c925456df41586d9b5f9202fca18593eae2213d4445b4585` | `reason` | 必需 | [`RequestHeaderReason`](#persistence-type-sha256-530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6) | | `startsSeries` | 可选 | `true` | - + ### `{ headerSeq?, message, step, turn }` -SHA-256: `12f9a5a7506bca46d6ef8e3c71826c4bf40e1389bfe0236cfffaea0c3fb367d2` +SHA-256: `d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2` 来源:[`packages/core/session/src/types.ts:317`](../packages/core/session/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| | `headerSeq` | 可选 | `number` | -| `message` | 必需 | [`DeveloperMessage`](#persistence-type-sha256-cebb7e9f3f663e660a531d9764ecfb9fa7fc73c22bf8d628f888e9ecf18c5cf0) | +| `message` | 必需 | [`DeveloperMessage`](#persistence-type-sha256-0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc) | | `step` | 必需 | `number` | | `turn` | 必需 | `number` | @@ -6391,17 +6449,32 @@ SHA-256: `17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3` |---|---|---| | `inherited` | 可选 | `true` | - + -### `{ inserted, outcome?, removedCount?, start, … }` +### `{ inputId, source, step, turn }` -SHA-256: `c433ef580a5b229bbe68274a7164aa3a8e8040d7217f010b610ca0696a84253c` +SHA-256: `2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3` -来源:[`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| -| `inserted` | 必需 | [`UserMessage[]`](#persistence-type-sha256-aada2771a250136cc3a839b0507e9c6502622a2897716903942297bcb6661292) | +| `inputId` | 必需 | `string` | +| `source` | 必需 | [`union (2 variants)`](#persistence-type-sha256-671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1) | +| `step` | 必需 | `number` | +| `turn` | 必需 | `number` | + + + +### `{ inserted, outcome?, removedCount?, start, … }` + +SHA-256: `c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039` + +来源:[`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `inserted` | 必需 | [`UserMessage[]`](#persistence-type-sha256-347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6) | | `outcome` | 可选 | `"canceled"` | | `removedCount` | 可选 | `number` | | `start` | 必需 | `number` | @@ -7149,6 +7222,18 @@ SHA-256: `db06ef35b27a38425046b2228fa6bce9582abe140d781d363137eb2f4d233a1c` | `kind` | 必需 | `"repeat-tool-reminder"` | | `sections` | 必需 | [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | + + +### `{ kind: "retry" }` + +SHA-256: `414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9` + +来源:[`packages/core/agent/src/types.ts:24`](../packages/core/agent/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"retry"` | + ### `{ kind: "runtime-context" }` @@ -8048,22 +8133,38 @@ SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | `time` | 必需 | `number` | | `type` | 必需 | `"agent-preset/selected"` | - + ### `{ type: "agent/inbox/spliced" }` -SHA-256: `9e8043098fb89db6d7c92a92c70146c5c8d24fa88f733c687fa29e5e7a58ab30` +SHA-256: `a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-c433ef580a5b229bbe68274a7164aa3a8e8040d7217f010b610ca0696a84253c) | +| `data` | 必需 | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | | `type` | 必需 | `"agent/inbox/spliced"` | + + + + +### `{ type: "agent/retry" }` + +SHA-256: `d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`{ inputId, source, step, turn }`](#persistence-type-sha256-2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"agent/retry"` | + @@ -8299,17 +8400,17 @@ SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | `time` | 必需 | `number` | | `type` | 必需 | `"deliverables/presented"` | - + ### `{ type: "developer/message" }` -SHA-256: `95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f` +SHA-256: `92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-12f9a5a7506bca46d6ef8e3c71826c4bf40e1389bfe0236cfffaea0c3fb367d2) | +| `data` | 必需 | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `sourceEventSeqs` | 可选 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8709,17 +8810,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | `time` | 必需 | `number` | | `type` | 必需 | `"session/title"` | - + ### `{ type: "session/title-llm-request" }` -SHA-256: `eaa9125aec0fb28478096c46e73e21bf512ab0d84d826bfb41e7308707734cb5` +SHA-256: `fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-9808c3d0f0a1be8fe313b4148ba22c691c4d59bd2c67aef3c63cfe8043148c5f) | +| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | @@ -9142,17 +9243,17 @@ SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515` | `type` | 必需 | `"usage"` | | `usage` | 必需 | [`TokenUsage`](#persistence-type-sha256-fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394) | - + ### `{ type: "user/message" }` -SHA-256: `39adb7fce8475af6402acd925ea79697b3341c22422ac32833d71217d84e987f` +SHA-256: `c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`UserMessage`](#persistence-type-sha256-8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14) | +| `data` | 必需 | [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `sourceEventSeqs` | 可选 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml b/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml new file mode 100644 index 0000000000..3513ba5aaa --- /dev/null +++ b/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-10-27-retry-control-input.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-10-27-retry-control-input.md +/: + en: 3d5a424ae7d7e46b + zh: c22a8e4a62d8cc1a +/2026-10-27-retry-control-input: + en: 32c7e9cc4a9f86b4 + zh: f1cb7169df15e8e4 +/2026-10-27-retry-control-input/summary: + en: e3a11c473fcdc452 + zh: 02ce33e05c10de71 +/2026-10-27-retry-control-input/table-of-contents: + en: 6cf314a069ceb451 + zh: 9adff678e9ecab3b +/2026-10-27-retry-control-input/declaration: + en: 6907136f28e48c90 + zh: 02f8298485f3d8e7 +/2026-10-27-retry-control-input/compatibility: + en: 4487a595853a65d6 + zh: d9b356c77ccab559 +/2026-10-27-retry-control-input/verification: + en: da37e5d4769c40fe + zh: 992f66b1a2c66fb2 +/2026-10-27-retry-control-input/dev-note: + en: b75c8458928b72b9 + zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.md b/docs/persistence-changes/2026-10-27-retry-control-input.md new file mode 100644 index 0000000000..3faecc4503 --- /dev/null +++ b/docs/persistence-changes/2026-10-27-retry-control-input.md @@ -0,0 +1,64 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# 2026-10-27-retry-control-input + +English | [中文](2026-10-27-retry-control-input.zh.md) + +## Summary + +Add an explicit retry control source and the required agent/retry acceptance event, correlated with durable inbox identity, turn, and step. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-27-retry-control-input +baseline: false +changes: + - root: "event:agent/inbox/spliced" + previous: "2026-10-25-skill-catalog-incomplete" + after: "a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664" + decision: same-version + - root: "event:agent/retry" + previous: null + after: "d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c" + decision: same-version + - root: "event:developer/message" + previous: "2026-10-25-skill-catalog-incomplete" + after: "92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583" + decision: same-version + - root: "event:session/title-llm-request" + previous: "2026-10-25-skill-catalog-incomplete" + after: "fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172" + decision: same-version + - root: "event:user/message" + previous: "2026-10-25-skill-catalog-incomplete" + after: "c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5" + decision: same-version +``` + + +## Compatibility + +Updated readers continue accepting older logs with no retry controls. Older readers reject the unknown required agent/retry event before treating a retry as ordinary user content. The retry discriminator also distinguishes queued controls from ordinary empty user messages. API and both SDK consumers are updated together; the current Session format version is retained because event and source discriminators prevent unsafe interpretation. + + +## Verification + +Source review confirms controls retain existing inbox and turn lifecycle while agent/retry has no model-surface operation. Regression coverage covers repeated retry, input-id correlation, balanced turns, ordinary empty messages, non-empty retry rejection, deduplication, checkpoint replay, browser behavior, and both SDK recovery recordings. Execution is pending post-push validation. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.schema.json b/docs/persistence-changes/2026-10-27-retry-control-input.schema.json new file mode 100644 index 0000000000..54eea6ef36 --- /dev/null +++ b/docs/persistence-changes/2026-10-27-retry-control-input.schema.json @@ -0,0 +1,257 @@ +{ + "formatVersion": 2, + "roots": [ + {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664"}, + {"key":"event:agent/retry","kind":"event","event":"agent/retry","surface":false,"digest":"d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c"}, + {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583"}, + {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172"}, + {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5"} + ], + "types": [], + "nodes": { + "0203243b72fc867a": {"kind":"object","properties":[{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "022623b9ef98ec47": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "02b2c23f2d94b028": {"kind":"object","properties":[{"name":"kind","type":"c5687b1313206a6f","optional":false}],"indices":[]}, + "05205074b24d8340": {"kind":"literal","value":"action-cards"}, + "0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"}, + "07e1a58c58b593f5": {"kind":"object","properties":[{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "09655b34d7f3cf1e": {"kind":"literal","value":"system-prompt"}, + "09f3f8dc417f2782": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"8af841fc9b265ade","optional":false},{"name":"source","type":"65a8400e451e667e","optional":false}],"indices":[]}, + "0ed8d1108fe7a5e1": {"kind":"object","properties":[{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "0eeb17e1df89b1eb": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "0ef3072084202b08": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"e3e9f4d1e245b470","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, + "0f46153645c29784": {"kind":"object","properties":[{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "0fe52387f6505270": {"kind":"literal","value":"plan-mode"}, + "11638dc58a31496f": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "12d5808fac1996b9": {"kind":"object","properties":[{"name":"attachment","type":"b0b0eb1b16490f6b","optional":false},{"name":"offloaded","type":"dacf3d5f3e4ff619","optional":true},{"name":"type","type":"fb8b6a2e53cb9cf4","optional":false}],"indices":[]}, + "1379f0b72275994d": {"kind":"literal","value":"developer/message"}, + "16d0507cb94b4cfc": {"kind":"literal","value":"relay"}, + "1922cd79a70806c1": {"kind":"literal","value":"skill-invocation"}, + "1938c8c20f1cba7c": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "1e709346005826eb": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "1f0133217eeeffd8": {"kind":"literal","value":"tool-call"}, + "1f1385dbc3e875c0": {"kind":"literal","value":"repeat-tool-reminder"}, + "1fdad624334d3548": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "2152caf1a71e588e": {"kind":"object","properties":[{"name":"kind","type":"2e4debca12f2c449","optional":false},{"name":"messageId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderName","type":"4cd48f3e5108bd6e","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "2235d957bf4b7c6c": {"kind":"object","properties":[{"name":"kind","type":"7b1ce60193fa8b69","optional":false}],"indices":[]}, + "224aa11d4d5f88c4": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "23af3759678a30de": {"kind":"literal","value":"dsh-session-title-llm"}, + "24b10f7cdae64928": {"kind":"literal","value":"runtime-context"}, + "24ce31aa528272e5": {"kind":"object","properties":[{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "260523b412c19ad8": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "2765e9119edf5816": {"kind":"literal","value":"model"}, + "277b61f9c6f81704": {"kind":"literal","value":"file"}, + "28936c614bd4e7e2": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "290f32bfd81ff85a": {"kind":"object","properties":[{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "297fbd8067d7815a": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "2a668a48dec17397": {"kind":"object","properties":[{"name":"inputId","type":"4cd48f3e5108bd6e","optional":false},{"name":"source","type":"671e72b8036a3afb","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "2aad1179f9576314": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "2ab12a0e345658f9": {"kind":"array","element":"83da72c6857601c7"}, + "2e4debca12f2c449": {"kind":"literal","value":"team-message"}, + "2f1e6a7ebace43d7": {"kind":"union","types":["3f74ba74acd10644","58f94390b982f8a5","de30b1fa12c75f81","ebb768d85ec87fb6"]}, + "2f3f201c8846e19c": {"kind":"literal","value":"tool-goal"}, + "2f5b14bc2ccbe881": {"kind":"array","element":"7cf091467eb28887"}, + "30093f9f63a120cb": {"kind":"literal","value":"agent/retry"}, + "312aee2f0c42aabb": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "31907de956375182": {"kind":"object","properties":[{"name":"compactionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"6b23ed9ce1b397e6","optional":false},{"name":"sourceCommandId","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, + "3247e566f4340029": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "335e242de1fcc17b": {"kind":"union","types":["bcf0caf62d964b2f","e438a9d4fb883b3e"]}, + "347c1718600bbb38": {"kind":"array","element":"9632024d168f8169"}, + "383cf73ae4748e5b": {"kind":"literal","value":"recall"}, + "3b04790e08b35241": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "3b4f2c9ef2c1217e": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "3bd652ebfa8726b3": {"kind":"union","types":["0657d3ca5ac4e7bf","938f7c7357bc403e"]}, + "3bdb8dc6989655b0": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "3c2535ca0a2062f1": {"kind":"literal","value":"reasoning"}, + "3e2ece4967cfebe9": {"kind":"literal","value":"next-step"}, + "3f74ba74acd10644": {"kind":"literal","value":"image/jpeg"}, + "408927267422a7b2": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "414a44a96f06339f": {"kind":"object","properties":[{"name":"kind","type":"63ba8895cc09e469","optional":false}],"indices":[]}, + "43e311235aa537df": {"kind":"object","properties":[{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"83d6916021eede8f","optional":false}],"indices":[]}, + "45f005e6bbb76452": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "4991dfc415c9efeb": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "49dab019d00e8c26": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "4a9bef9f27a94252": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"82470020b5bfcb1a","optional":false},{"name":"senderSessionId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "4b259f7f8f0a44ad": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"isError","type":"aa93010d0e521ad7","optional":true},{"name":"role","type":"a13d8f379222ece8","optional":false},{"name":"source","type":"d9c462b9f62fdd3e","optional":false},{"name":"toolCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "4cd48f3e5108bd6e": {"kind":"primitive","type":"string"}, + "4e44c9118152f682": {"kind":"literal","value":"text"}, + "4e88776858652856": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"4e44c9118152f682","optional":false}],"indices":[]}, + "4e97c3d85c0fc817": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"bf16d2e98a7f1389","optional":false},{"name":"senderSessionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "4edec1b770e8ec4c": {"kind":"literal","value":"ptc-mode"}, + "4f91f187baeabef5": {"kind":"object","properties":[{"name":"capturedFormatVersion","type":"0657d3ca5ac4e7bf","optional":true},{"name":"capturedThroughSeq","type":"3bd652ebfa8726b3","optional":false},{"name":"compacted","type":"aa93010d0e521ad7","optional":false},{"name":"inputIndex","type":"0657d3ca5ac4e7bf","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"omittedBytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"omittedMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"originalMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"retainedMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"truncated","type":"aa93010d0e521ad7","optional":false}],"indices":[]}, + "52c6113cb0e53f34": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "53cf1704ae0709c7": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "5654685d7557ec74": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "58015da1c1950a66": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "587bb11da730e06a": {"kind":"literal","value":"session/title-llm-request"}, + "589d0d91239681f8": {"kind":"literal","value":"developer"}, + "58f94390b982f8a5": {"kind":"literal","value":"image/png"}, + "5bb69b88517f6214": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "5bc66269cfaf614c": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "5cdbf342b7748d15": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "5d03ba38734809bc": {"kind":"array","element":"0657d3ca5ac4e7bf"}, + "5d7a64ed6aac5551": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "6085488d223aeffd": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "6292bcbedaa97fea": {"kind":"object","properties":[{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "62ecb62dc0fe1b5e": {"kind":"literal","value":"next-turn"}, + "63ba8895cc09e469": {"kind":"literal","value":"retry"}, + "65a8400e451e667e": {"kind":"object","properties":[{"name":"kind","type":"09655b34d7f3cf1e","optional":false}],"indices":[]}, + "65d583e66518c523": {"kind":"object","properties":[{"name":"entries","type":"aba175fb20ffc711","optional":false},{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"incomplete","type":"dacf3d5f3e4ff619","optional":true},{"name":"kind","type":"6ebcc2dbec9f2dab","optional":false},{"name":"update","type":"dacf3d5f3e4ff619","optional":true}],"indices":[]}, + "671e72b8036a3afb": {"kind":"union","types":["414a44a96f06339f","e103228e063b4762"]}, + "672c4aa2a9b736e3": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"1922cd79a70806c1","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "68f44f59e91c4139": {"kind":"literal","value":"user"}, + "6b23ed9ce1b397e6": {"kind":"literal","value":"compact-checkpoint"}, + "6ccb6bc423fb3952": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "6dacb1584f2bfbe2": {"kind":"object","properties":[{"name":"clientTimeZone","type":"4cd48f3e5108bd6e","optional":true},{"name":"kind","type":"68f44f59e91c4139","optional":false},{"name":"principal","type":"deebfc7c62177ea8","optional":true},{"name":"rpcId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "6ea608bdc8a7a63d": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "6ebcc2dbec9f2dab": {"kind":"literal","value":"skill-catalog"}, + "6f22a9d02b9a1f11": {"kind":"object","properties":[{"name":"baseline","type":"dacf3d5f3e4ff619","optional":true},{"name":"baselineIdentity","type":"4cd48f3e5108bd6e","optional":true},{"name":"changes","type":"2ab12a0e345658f9","optional":false},{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"fbd2c74b346a1595","optional":false}],"indices":[]}, + "6f44f32be9bcd9a8": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "6f9e00383836275e": {"kind":"literal","value":"snapshot"}, + "719c51f67074f970": {"kind":"object","properties":[{"name":"kind","type":"2765e9119edf5816","optional":false},{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"replayState","type":"bbc5696e3f13c758","optional":true}],"indices":[]}, + "7382c4fc95fe8c5d": {"kind":"literal","value":"remove"}, + "7554565be416b034": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "756b5e23a5cb2c7f": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "78a9be340090b735": {"kind":"literal","value":"tmux-context"}, + "7b1ce60193fa8b69": {"kind":"literal","value":"working-directory"}, + "7cf091467eb28887": {"kind":"union","types":["09f3f8dc417f2782","0ef3072084202b08","4b259f7f8f0a44ad","9632024d168f8169","fa87537f4a9c1e25"]}, + "7df62978a2b7de85": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "7eaef7a763ba5b94": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "7f13860ee89a2d35": {"kind":"literal","value":"session-reference"}, + "7f959241c24128b4": {"kind":"object","properties":[{"name":"goalId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"b815d7b91cdd9fc6","optional":false},{"name":"revision","type":"0657d3ca5ac4e7bf","optional":false},{"name":"round","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "8064c03c87c357fd": {"kind":"union","types":["12d5808fac1996b9","43e311235aa537df","4e88776858652856","814d9434e1ddf907","8e611bf46821e500","c1f0f17b6493b85d","f2da0f27c1e6add5"]}, + "80677c8c8b804189": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "8067b0d232c350f1": {"kind":"literal","value":"time-context"}, + "814d9434e1ddf907": {"kind":"object","properties":[{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"3c2535ca0a2062f1","optional":false}],"indices":[]}, + "82470020b5bfcb1a": {"kind":"literal","value":"agent-message"}, + "825ca23416b1ea9d": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "829a2fb3be6efd4b": {"kind":"literal","value":"agent/inbox/spliced"}, + "83079c8a3f733ac3": {"kind":"object","properties":[{"name":"description","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "8338ee303e2bd448": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "83d6916021eede8f": {"kind":"literal","value":"tool-removal"}, + "83da72c6857601c7": {"kind":"object","properties":[{"name":"action","type":"904bf499d98be4a3","optional":false},{"name":"digest","type":"4cd48f3e5108bd6e","optional":true},{"name":"path","type":"4cd48f3e5108bd6e","optional":false},{"name":"scope","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "854db3efdfb0cd39": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "85d6d96ff8842974": {"kind":"literal","value":"answered"}, + "85f9b505b7bdff1f": {"kind":"literal","value":"tool-addition"}, + "85fe96bf15d5c755": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "8620189c8e335827": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "86e1e384495ee09a": {"kind":"object","properties":[{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"text","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "87b1a33564b9a4d6": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "88841202942c7683": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "8a046b52331959aa": {"kind":"object","properties":[{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "8af841fc9b265ade": {"kind":"literal","value":"system"}, + "8c1c4f72755a78ea": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "8c627d7a45fa406c": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "8e611bf46821e500": {"kind":"object","properties":[{"name":"tool","type":"fcf427290c6213c2","optional":true},{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"85f9b505b7bdff1f","optional":false}],"indices":[]}, + "8eda0195dd1d4a3f": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"2f5b14bc2ccbe881","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "8f324cea3916c051": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "8fcb0b44a575e998": {"kind":"object","properties":[{"name":"kind","type":"a720757f704a2512","optional":false}],"indices":[]}, + "904bf499d98be4a3": {"kind":"union","types":["7382c4fc95fe8c5d","b765ee31033cea46","f6cb462f808309e3"]}, + "92b82e8b7bbb38b3": {"kind":"object","properties":[{"name":"data","type":"d582ddae3f21edf5","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, + "938f7c7357bc403e": {"kind":"primitive","type":"null"}, + "95a3ffccbf3c8afc": {"kind":"literal","value":"owner"}, + "95c0e88fcf0bafb2": {"kind":"literal","value":"assistant"}, + "9632024d168f8169": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"e3e9f4d1e245b470","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, + "9645cf93769a6dfb": {"kind":"literal","value":"user-question-reply"}, + "96bfb916a727cd44": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "96f4a9c81fc0f940": {"kind":"union","types":["3e2ece4967cfebe9","62ecb62dc0fe1b5e"]}, + "99135d8a39379779": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "99e7e7d7fed55ae6": {"kind":"literal","value":"tool-jobs"}, + "99f0e98184709ad1": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "9ac52f0f84fec2eb": {"kind":"literal","value":"model-selection"}, + "9edc162949abf93b": {"kind":"object","properties":[{"name":"deliveryId","type":"4cd48f3e5108bd6e","optional":false},{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"db6c5246911d47a7","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"ruleId","type":"4cd48f3e5108bd6e","optional":false},{"name":"source","type":"4cd48f3e5108bd6e","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "9f65c43e340bba31": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "a13d8f379222ece8": {"kind":"literal","value":"tool"}, + "a15a19ef63cf5d1b": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "a16e567c8cd2c55c": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "a2066bd52f61218b": {"kind":"object","properties":[{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, + "a3c6f1ea38e9a61d": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "a6d8c25f4b2ca2d1": {"kind":"object","properties":[{"name":"data","type":"c66c6c3b411abf46","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, + "a720757f704a2512": {"kind":"literal","value":"tool-registry"}, + "a9059a81430b3207": {"kind":"literal","value":"hooks-claude-code"}, + "aa2bf9e82b9fdeb9": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, + "aa93010d0e521ad7": {"kind":"primitive","type":"boolean"}, + "aae343bc3d663c83": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "aba175fb20ffc711": {"kind":"array","element":"83079c8a3f733ac3"}, + "ada310bf0bdb8fed": {"kind":"object","properties":[{"name":"kind","type":"68f44f59e91c4139","optional":false}],"indices":[]}, + "ade057f4fdc387ae": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "aec150797b655c5f": {"kind":"literal","value":"catalog"}, + "b062078e899958f9": {"kind":"object","properties":[{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "b0b0eb1b16490f6b": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"mediaType","type":"2f1e6a7ebace43d7","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":true},{"name":"originalDimensions","type":"0f46153645c29784","optional":true},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "b2a5c8c008a31e3d": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, + "b3681c026a0b98e9": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "b4ec0af39fd52ed9": {"kind":"object","properties":[{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "b52f215a6cd69105": {"kind":"array","element":"8064c03c87c357fd"}, + "b613f83572fe2fbf": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "b6f94c17dd9bbd7c": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, + "b70f3e81fe11b7a5": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "b765ee31033cea46": {"kind":"literal","value":"set"}, + "b79641a9d25e7566": {"kind":"object","properties":[{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "b815d7b91cdd9fc6": {"kind":"literal","value":"goal"}, + "ba2a1a8f194b1bb2": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "ba4c40b4381e0dcd": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "baccc97ec34be6c4": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "bb94f3217aa56c88": {"kind":"literal","value":"hooks-codex"}, + "bbc5696e3f13c758": {"kind":"opaque","reason":"unknown"}, + "bcf0caf62d964b2f": {"kind":"object","properties":[{"name":"endSeq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"op","type":"f6cb462f808309e3","optional":false},{"name":"startSeq","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "bf16d2e98a7f1389": {"kind":"literal","value":"subagent-settled"}, + "c1f0f17b6493b85d": {"kind":"object","properties":[{"name":"arguments","type":"4cd48f3e5108bd6e","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"1f0133217eeeffd8","optional":false}],"indices":[]}, + "c5228b7a55498d84": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "c55827fba7018198": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"9645cf93769a6dfb","optional":false},{"name":"outcome","type":"85d6d96ff8842974","optional":false}],"indices":[]}, + "c5687b1313206a6f": {"kind":"literal","value":"cordis-host-runner"}, + "c5bbcb3435908af2": {"kind":"object","properties":[{"name":"data","type":"9632024d168f8169","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, + "c66c6c3b411abf46": {"kind":"object","properties":[{"name":"inserted","type":"347c1718600bbb38","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, + "c748fb42c80a1200": {"kind":"array","element":"86e1e384495ee09a"}, + "cb1c8959e1726f82": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "cda2345a9be9d139": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "d4e4d575abeb4b72": {"kind":"literal","value":"canceled"}, + "d582ddae3f21edf5": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"0ef3072084202b08","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "d85590a5675c6e2f": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "d8dde9629b7323b0": {"kind":"object","properties":[{"name":"data","type":"2a668a48dec17397","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"30093f9f63a120cb","optional":false}],"indices":[]}, + "d9c462b9f62fdd3e": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"a13d8f379222ece8","optional":false}],"indices":[]}, + "d9edf1862e4471d6": {"kind":"literal","value":"schedule"}, + "dacf3d5f3e4ff619": {"kind":"literal","value":true}, + "db06ef35b27a3842": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "db6c5246911d47a7": {"kind":"literal","value":"webhook"}, + "dba54c09c6e7aafc": {"kind":"object","properties":[{"name":"kind","type":"4edec1b770e8ec4c","optional":false}],"indices":[]}, + "dd00cdccd0ba487b": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "dd5ee8762bd8a14b": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "dd6cc2bb2a8f2824": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, + "dde0cdf9d426768b": {"kind":"object","properties":[{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "de25352d2dc41963": {"kind":"literal","value":"user/message"}, + "de30b1fa12c75f81": {"kind":"literal","value":"image/gif"}, + "deaae5cd30987d4f": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "deebfc7c62177ea8": {"kind":"object","properties":[{"name":"kind","type":"95a3ffccbf3c8afc","optional":false}],"indices":[]}, + "df843efd87c78cf8": {"kind":"array","element":"4f91f187baeabef5"}, + "e103228e063b4762": {"kind":"object","properties":[{"name":"clientTimeZone","type":"4cd48f3e5108bd6e","optional":true},{"name":"kind","type":"63ba8895cc09e469","optional":false},{"name":"principal","type":"deebfc7c62177ea8","optional":true},{"name":"rpcId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "e3e9f4d1e245b470": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","414a44a96f06339f","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","65d583e66518c523","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e103228e063b4762","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, + "e438a9d4fb883b3e": {"kind":"literal","value":"append"}, + "e7f4cbb3a57f4cbd": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, + "e8de0f457fed5b9f": {"kind":"literal","value":"notice"}, + "ebb768d85ec87fb6": {"kind":"literal","value":"image/webp"}, + "edbbfff1128099fd": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "ee577a0cc18664c9": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "f19bee84bf3d2090": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "f230c0b583e23b25": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "f29d63336e8266d8": {"kind":"object","properties":[{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "f2da0f27c1e6add5": {"kind":"object","properties":[{"name":"attachment","type":"f19bee84bf3d2090","optional":false},{"name":"type","type":"277b61f9c6f81704","optional":false}],"indices":[]}, + "f4a66f654996c093": {"kind":"object","properties":[{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "f56c5b59ca016131": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"7f13860ee89a2d35","optional":false},{"name":"references","type":"df843efd87c78cf8","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, + "f6329fb30c7ea801": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "f6605d6c57933aea": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "f68c335306535a00": {"kind":"literal","value":"user-approval"}, + "f6afe41e3926f422": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, + "f6cb462f808309e3": {"kind":"literal","value":"replace"}, + "f7283223aacdc39b": {"kind":"object","properties":[{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, + "f88f1b7b6bb733f6": {"kind":"literal","value":1}, + "f97f47b0eb87905e": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "fa87537f4a9c1e25": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"95c0e88fcf0bafb2","optional":false},{"name":"source","type":"719c51f67074f970","optional":false}],"indices":[]}, + "fb28ad7d7aabceb5": {"kind":"object","properties":[{"name":"data","type":"8eda0195dd1d4a3f","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, + "fb8b6a2e53cb9cf4": {"kind":"literal","value":"image"}, + "fbd2c74b346a1595": {"kind":"literal","value":"agent-instructions"}, + "fcf427290c6213c2": {"kind":"primitive","type":"never"}, + "fcf81d3c58c13593": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, + "fe1a7acdeed4d0f5": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "fee1caf8ab6a65c1": {"kind":"literal","value":"instructions"} + } +} diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.zh.md b/docs/persistence-changes/2026-10-27-retry-control-input.zh.md new file mode 100644 index 0000000000..672f1d032c --- /dev/null +++ b/docs/persistence-changes/2026-10-27-retry-control-input.zh.md @@ -0,0 +1,64 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 2026-10-27-retry-control-input + +[English](2026-10-27-retry-control-input.md) | 中文 + +## 概述 + +新增显式 retry 控制来源与必读 agent/retry 接受事件,关联持久 inbox 身份、轮次和步骤。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-27-retry-control-input +baseline: false +changes: + - root: "event:agent/inbox/spliced" + previous: "2026-10-25-skill-catalog-incomplete" + after: "a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664" + decision: same-version + - root: "event:agent/retry" + previous: null + after: "d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c" + decision: same-version + - root: "event:developer/message" + previous: "2026-10-25-skill-catalog-incomplete" + after: "92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583" + decision: same-version + - root: "event:session/title-llm-request" + previous: "2026-10-25-skill-catalog-incomplete" + after: "fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172" + decision: same-version + - root: "event:user/message" + previous: "2026-10-25-skill-catalog-incomplete" + after: "c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5" + decision: same-version +``` + + +## 兼容性 + +更新后的读取方继续接受没有重试控制输入的旧日志。旧读取方会拒绝未知必读 agent/retry 事件,不会将重试当作普通用户内容。retry 判别标签也将排队控制输入与普通空用户消息区分开。API 与两套 SDK 消费方同步更新;事件及来源判别标签可阻止不安全解释,因此保留当前 Session 格式版本。 + + +## 验证 + +源码审查确认控制输入保留现有 inbox 和轮次生命周期,agent/retry 不携带模型表面操作。回归用例覆盖连续重试、输入身份关联、轮次闭合、普通空消息、拒绝非空重试、去重、检查点回放、浏览器行为及两套 SDK 恢复录制。执行验证将在 push 后进行。 + + +## 开发备注 + +无。 diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index 70f64325e4..07e2ce9415 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -5,7 +5,8 @@ {"key":"JsonlHeaderLine","kind":"header","digest":"18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f"}, {"key":"SessionEventEnvelope","kind":"envelope","digest":"5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc"}, {"key":"event:agent-preset/selected","kind":"event","event":"agent-preset/selected","surface":false,"digest":"a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b"}, - {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"9e8043098fb89db6d7c92a92c70146c5c8d24fa88f733c687fa29e5e7a58ab30"}, + {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664"}, + {"key":"event:agent/retry","kind":"event","event":"agent/retry","surface":false,"digest":"d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c"}, {"key":"event:approval/asked","kind":"event","event":"approval/asked","surface":false,"digest":"3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1"}, {"key":"event:approval/decided","kind":"event","event":"approval/decided","surface":false,"digest":"bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3"}, {"key":"event:approval/policy","kind":"event","event":"approval/policy","surface":false,"digest":"26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78"}, @@ -18,7 +19,7 @@ {"key":"event:compaction/start","kind":"event","event":"compaction/start","surface":false,"digest":"db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965"}, {"key":"event:compaction/summary","kind":"event","event":"compaction/summary","surface":false,"digest":"e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602"}, {"key":"event:deliverables/presented","kind":"event","event":"deliverables/presented","surface":false,"digest":"13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176"}, - {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f"}, + {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583"}, {"key":"event:feedback/message-delete","kind":"event","event":"feedback/message-delete","surface":false,"digest":"3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796"}, {"key":"event:feedback/message-put","kind":"event","event":"feedback/message-put","surface":false,"digest":"b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf"}, {"key":"event:feedback/record","kind":"event","event":"feedback/record","surface":false,"digest":"b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8"}, @@ -40,7 +41,7 @@ {"key":"event:session-log-deepseek/delivery-accepted","kind":"event","event":"session-log-deepseek/delivery-accepted","surface":false,"digest":"d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9"}, {"key":"event:session/end-seed","kind":"event","event":"session/end-seed","surface":false,"digest":"5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e"}, {"key":"event:session/title","kind":"event","event":"session/title","surface":false,"digest":"1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5"}, - {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"eaa9125aec0fb28478096c46e73e21bf512ab0d84d826bfb41e7308707734cb5"}, + {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172"}, {"key":"event:step/end","kind":"event","event":"step/end","surface":false,"digest":"e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05"}, {"key":"event:step/start","kind":"event","event":"step/start","surface":false,"digest":"4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986"}, {"key":"event:subagent/catalog","kind":"event","event":"subagent/catalog","surface":false,"digest":"53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac"}, @@ -62,7 +63,7 @@ {"key":"event:tool/result","kind":"event","event":"tool/result","surface":true,"digest":"7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397"}, {"key":"event:turn/end","kind":"event","event":"turn/end","surface":false,"digest":"0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce"}, {"key":"event:turn/start","kind":"event","event":"turn/start","surface":false,"digest":"aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8"}, - {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"39adb7fce8475af6402acd925ea79697b3341c22422ac32833d71217d84e987f"}, + {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5"}, {"key":"event:web/deepseek-search-llm-request","kind":"event","event":"web/deepseek-search-llm-request","surface":false,"digest":"0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52"}, {"key":"event:working-directory/change","kind":"event","event":"working-directory/change","surface":false,"digest":"124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3"}, {"key":"event:workspace/changes","kind":"event","event":"workspace/changes","surface":false,"digest":"e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72"} @@ -98,6 +99,7 @@ {"digest":"0e7b2a97f54f1afdb8cdfad1635abe9145327db539bfb69b4045c19d8c17755d","names":[],"sources":[]}, {"digest":"0ed8d1108fe7a5e1c3078ef1faea12199a56b6b1216ccaceb0f6103b0c9a7691","names":[],"sources":[]}, {"digest":"0eeb17e1df89b1ebc2902116fe7145e0d321ef0db26e6bdebc802cb8513a01c1","names":[],"sources":[]}, + {"digest":"0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc","names":["packages/llm/llm/src/message.ts#DeveloperMessage"],"sources":["packages/llm/llm/src/message.ts:157"]}, {"digest":"0f46153645c297846a3fad3911636659ca4717a5e7d71c9f50bffacd312115ef","names":[],"sources":["packages/attachment/attachment/src/types.ts:28"]}, {"digest":"0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce","names":[],"sources":[]}, {"digest":"0fe52387f65052702ed9d7cd59db826468936595d1b3eec207e7a823adc719db","names":[],"sources":[]}, @@ -109,7 +111,6 @@ {"digest":"124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3","names":[],"sources":[]}, {"digest":"12a0cfa619ac4a5fc6c3e8e359c8dae15769edc61497a575935c78d5842f8e42","names":[],"sources":[]}, {"digest":"12d5808fac1996b9b3c2afa806e03022252ba2d7b88f835862ae1896591f60c7","names":["packages/llm/llm/src/types.ts#ImageBlock"],"sources":["packages/llm/llm/src/types.ts:79"]}, - {"digest":"12f9a5a7506bca46d6ef8e3c71826c4bf40e1389bfe0236cfffaea0c3fb367d2","names":[],"sources":["packages/core/session/src/types.ts:317"]}, {"digest":"1379f0b72275994d20f1ddca2d60a96ab16ec80397ca9f1af21bdc67ba5c269c","names":[],"sources":[]}, {"digest":"13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176","names":[],"sources":[]}, {"digest":"14230f9b8b5797bb7e8c621fa636531cfbde2ffba1ef5f1fd058216ff21952a0","names":[],"sources":[]}, @@ -173,6 +174,7 @@ {"digest":"28936c614bd4e7e2774806edcbb3cbfd3ee643ac1cbf0bbdff43d915505a8fe3","names":[],"sources":[]}, {"digest":"290f32bfd81ff85a8095886a8066fae06e885942b63a8bcd6a8d66d328445808","names":[],"sources":[]}, {"digest":"297fbd8067d7815adae02b89790ec6ff5fb7c2cb0d0eb026e1746c24ec857761","names":[],"sources":[]}, + {"digest":"2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3","names":[],"sources":["packages/core/agent/src/types.ts:113"]}, {"digest":"2aad1179f957631477b582e57cd8f538cb82618fc3fb816bde1171154235b065","names":[],"sources":[]}, {"digest":"2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee","names":[],"sources":[]}, {"digest":"2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095","names":[],"sources":["packages/llm/llm/src/types.ts:473"]}, @@ -186,7 +188,9 @@ {"digest":"2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e","names":["packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot"],"sources":["packages/experimental/agent-team/src/types.ts:129"]}, {"digest":"2f1e6a7ebace43d727ae138ddcd352eb3813960009e5805b8c5ac84300c97716","names":["packages/attachment/attachment/src/types.ts#ImageMediaType"],"sources":["packages/attachment/attachment/src/types.ts:8"]}, {"digest":"2f3f201c8846e19c20bb75c02280e3d4eb3c57509e4715f0c0a4d1d4f91a7a06","names":[],"sources":[]}, + {"digest":"2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff","names":[],"sources":[]}, {"digest":"2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f","names":["packages/core/tool-executor-ptc/src/types.ts#PtcDispatchStartEventData"],"sources":["packages/core/tool-executor-ptc/src/types.ts:12"]}, + {"digest":"30093f9f63a120cb932f07577594c04afcec3f0bd02859154a6e5b0e79a46359","names":[],"sources":[]}, {"digest":"30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f","names":["packages/session/session-title/src/types.ts#SessionTitleSource"],"sources":["packages/session/session-title/src/types.ts:28"]}, {"digest":"312aee2f0c42aabbae6cc60d92d596d8453c0aeb061c5868d1755a69ef49e69a","names":[],"sources":[]}, {"digest":"31907de9563751822af6d9be02bafaf26faa098bbd1cb412524134d577cc1648","names":["packages/compaction/compaction/src/checkpoint.ts#CompactionCheckpointSource"],"sources":["packages/compaction/compaction/src/checkpoint.ts:22"]}, @@ -195,6 +199,7 @@ {"digest":"3305a4bb6b6c29b482ba91d59c60b47e8f98282d3dacc8c29e4a4c2712209bdd","names":[],"sources":[]}, {"digest":"33341aac09312300de5a178e6cc6f6544a4876f7f710cfea55eda473c8d329ef","names":["packages/llm/llm/src/assistant-stream.ts#AssistantStreamRecord"],"sources":["packages/llm/llm/src/assistant-stream.ts:20"]}, {"digest":"335e242de1fcc17b6ca920fc420d71bec2d76e53e37955c00948b65ab77f05c5","names":["packages/core/session/src/types.ts#SurfaceOp"],"sources":["packages/core/session/src/types.ts:470"]}, + {"digest":"347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6","names":[],"sources":[]}, {"digest":"347de3380f2da308d0c74e08821f9e85559f55af4c5010795b110d00f7fc9dbd","names":[],"sources":[]}, {"digest":"34d1ab5c5df378186d6054b1a1beea9a41e41965d6f336f0bc0025c441a82e66","names":[],"sources":[]}, {"digest":"35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc","names":[],"sources":[]}, @@ -207,7 +212,6 @@ {"digest":"383cf73ae4748e5beb281ae74857ba1ffb92ae01075b9fbf51af78b666007a8f","names":[],"sources":[]}, {"digest":"3860b4757d512a2869be833acb0faf834335b1b056bf3f83df5474dce95f8fa6","names":[],"sources":["packages/llm/llm/src/assistant-stream.ts:35"]}, {"digest":"390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2","names":["packages/feedback/message-feedback/src/types.ts#MessageFeedbackRating"],"sources":["packages/feedback/message-feedback/src/types.ts:17"]}, - {"digest":"39adb7fce8475af6402acd925ea79697b3341c22422ac32833d71217d84e987f","names":[],"sources":[]}, {"digest":"39cf2d3ff8bac81c62d49ac2ba38a095b18e80f3ad700819fa1f650a4e60197f","names":["packages/jobs/jobs/src/types.ts#JobToolCall"],"sources":["packages/jobs/jobs/src/types.ts:16"]}, {"digest":"3b04790e08b35241ffa0d38f6d7e4f8788aa921341f6c0d7574a329397c59408","names":[],"sources":[]}, {"digest":"3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48","names":[],"sources":[]}, @@ -227,6 +231,7 @@ {"digest":"3f74ba74acd10644c1ed6947a411d8088bd2cfac74f61bb8540f96d68e31f004","names":[],"sources":[]}, {"digest":"3fdf9582c3b5597df104d32d97dab6c6ee3a93794f3034ec6ada6272404ea27f","names":["packages/schedule/schedule/src/types.ts#LegacyAfterScheduleRecord"],"sources":["packages/schedule/schedule/src/types.ts:161"]}, {"digest":"408927267422a7b2beb6df354ff7c1406d929a5646554f48adf85eb8372f811c","names":[],"sources":[]}, + {"digest":"414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9","names":[],"sources":["packages/core/agent/src/types.ts:24"]}, {"digest":"4208123b50df5006b181481ab45fcf1cde807b88d3fd4d340090bc2e202fac41","names":[],"sources":[]}, {"digest":"42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f","names":[],"sources":[]}, {"digest":"4312134fc6f9cad507c31390bd3d147535492dd77621ec517c5e85f853d53a03","names":[],"sources":[]}, @@ -258,7 +263,6 @@ {"digest":"4e97c3d85c0fc817ee58873c38c25a2474abaaa0af4839311b50b68db3b8cf1a","names":["packages/subagent/subagent/src/continuation-messages.ts#SubagentSettledMessageSource"],"sources":["packages/subagent/subagent/src/continuation-messages.ts:29"]}, {"digest":"4edec1b770e8ec4ce1220bf42edc5ac46713a1b48d4caa778dcfc98bad1f28a3","names":[],"sources":[]}, {"digest":"4f507598cf476214aa7646ce7841a7064a9dcb05d0a1eaaefaf79c2b738f160f","names":[],"sources":[]}, - {"digest":"4f9125fa028a56286fac70176ecc670d12f4402d8cbe112e14f0639f5e8e5c9d","names":["packages/llm/llm/src/message.ts#Message"],"sources":["packages/llm/llm/src/message.ts:196"]}, {"digest":"4f91f187baeabef5c7950c4780d31bbe39f3345dff591a6cf26ac7e135ac1cf4","names":[],"sources":["packages/context/session-reference/src/types.ts:18"]}, {"digest":"4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc","names":[],"sources":[]}, {"digest":"50cf02d875f8a1f1411f4a6fbcffab9120ccef5f74a47331fbf29480e5f68f48","names":[],"sources":[]}, @@ -311,11 +315,13 @@ {"digest":"6292bcbedaa97feaec5b48a03bb8144b581b93309d9d6c905f26da7a6a7a0a4f","names":[],"sources":[]}, {"digest":"62e6a429ff3f390a46ac39fb468692032f42936ca0db19b53454184189401379","names":[],"sources":[]}, {"digest":"62ecb62dc0fe1b5e70217f8a80c39256a40ee3a1917be14e5ca899d20c35f7fd","names":[],"sources":[]}, + {"digest":"63ba8895cc09e4693cf0819fdcd18c084bef195febda5b6a23af279b3852be36","names":[],"sources":[]}, {"digest":"6487b10ba7542f3c447b1e41a1c6a590b7bfafe0b9b66b49f34028da85bb970f","names":["packages/subagent/subagent/src/descriptor.ts#ContinuableSubagentDescriptorData"],"sources":["packages/subagent/subagent/src/descriptor.ts:72"]}, {"digest":"6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25","names":["packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot"],"sources":["packages/experimental/agent-team/src/types.ts:48"]}, {"digest":"65a8400e451e667e5b6e34df72ceb81fca8c12330330fa72083ad40279b189ad","names":["packages/llm/llm/src/message.ts#SystemPromptMessageSource"],"sources":["packages/llm/llm/src/message.ts:35"]}, {"digest":"65d583e66518c5236015e0846470fb32efcf92375ed306aef14e50a9d2cd2099","names":["packages/skill/tool-skill/src/index.ts#SkillCatalogSource"],"sources":["packages/skill/tool-skill/src/index.ts:35"]}, {"digest":"66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2","names":["packages/llm/llm/src/types.ts#ToolSchema"],"sources":["packages/llm/llm/src/types.ts:491"]}, + {"digest":"671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1","names":[],"sources":[]}, {"digest":"672c4aa2a9b736e3d6e5da6346d2e2ed5e3573f2d0baafa18e7407c9f3fee3db","names":["packages/skill/skill/src/index.ts#SkillInvocationSource"],"sources":["packages/skill/skill/src/index.ts:146"]}, {"digest":"67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0","names":["packages/llm/llm/src/call-config.ts#LlmCallConfig"],"sources":["packages/llm/llm/src/call-config.ts:31"]}, {"digest":"68141cbe3994a5ffcd064c07c75996538437fd9dbf77ce8a2927e89ec577fabe","names":[],"sources":[]}, @@ -364,6 +370,7 @@ {"digest":"7b1ce60193fa8b69292e5843858d9cee7dc565dcb40d33c136e5d7e8b8d4296a","names":[],"sources":[]}, {"digest":"7c6129b6961cf4b2a030f36c78d2b0a5be38617651a9b81322e054ee512bee06","names":["packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete"],"sources":["packages/feedback/message-feedback/src/types.ts:46"]}, {"digest":"7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397","names":[],"sources":[]}, + {"digest":"7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2","names":["packages/llm/llm/src/message.ts#Message"],"sources":["packages/llm/llm/src/message.ts:196"]}, {"digest":"7d226b9fd95b4ba213d4237c22e930a91c3c769c95ca7f92977b9d625ba13438","names":["packages/goal/goal/src/domain.ts#GoalChangeMeta"],"sources":["packages/goal/goal/src/domain.ts:44"]}, {"digest":"7d54a5a5e629e2e7783bfac72683bcc5e969e9f43426b9289826aaf294ff6344","names":["packages/feedback/command-feedback/src/types.ts#FeedbackRecord"],"sources":["packages/feedback/command-feedback/src/types.ts:27"]}, {"digest":"7d65acf0a3a43a8f445ce5d3320b42eca492739fc931b2a7516bfb8368dc2c68","names":["packages/llm/llm/src/types.ts#ContentBlockType"],"sources":["packages/llm/llm/src/types.ts:148"]}, @@ -405,7 +412,6 @@ {"digest":"8ad45ad053b4d89f8865c17cc9ff3b8748e86045f3f0fb816269a34af2619141","names":[],"sources":["packages/experimental/hook-protocol/src/types.ts:31"]}, {"digest":"8af841fc9b265ade3441caf9fe40dd24db24226befba48150e6d0eeccc1389c3","names":[],"sources":[]}, {"digest":"8bb8c3751a6ce64b61aba1ec7ae798c00a5803c4d868a0430873216c6569ed52","names":[],"sources":["packages/session/session-log-deepseek/src/types.ts:81"]}, - {"digest":"8bca2b4f53ec7745b0484c6e7d3a6beb2ddd6cea82fc81786eaa3c0d065bbc14","names":["packages/llm/llm/src/message.ts#UserMessage"],"sources":["packages/llm/llm/src/message.ts:162"]}, {"digest":"8c1c4f72755a78ea163523a46565316e36e9b644382eb48e6635c71a6f412dd2","names":[],"sources":[]}, {"digest":"8c627d7a45fa406cea879a3922105e27ed4c3c7966e264afaf11ea7a330f2dd1","names":[],"sources":[]}, {"digest":"8cdd3fdd295cee53035de16a1b23411716f75e9f994fe9510cac5d7fef605c76","names":[],"sources":[]}, @@ -413,10 +419,12 @@ {"digest":"8e611bf46821e5003a1605a73560a6f56388295149d449dfe2142d5ec335f29c","names":["packages/llm/llm/src/types.ts#ToolAdditionBlock"],"sources":["packages/llm/llm/src/types.ts:115"]}, {"digest":"8e8155594e07812356b67d732196dda894b0f9c91a5969d51612cf490b0a016d","names":[],"sources":[]}, {"digest":"8e8e91646a5cae3fc78f7532a0013ff2088ae47077642c2edd507a508209b8af","names":["packages/schedule/schedule/src/types.ts#OneShotScheduleDispatchChange"],"sources":["packages/schedule/schedule/src/types.ts:237"]}, + {"digest":"8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60","names":["packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData"],"sources":["packages/session/session-title-llm/src/index.ts:37"]}, {"digest":"8f324cea3916c0517261e88167def8efb3a9994c1135e23d40ed9fd48f8c7bd4","names":[],"sources":[]}, {"digest":"8f6d609794bf5afc01d7ccf3e03811d32f4b8047f58a2c9a98db0154c2ad7cb9","names":[],"sources":[]}, {"digest":"8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543","names":[],"sources":["packages/core/tools/src/index.ts:28"]}, {"digest":"904bf499d98be4a3a5f836fcac533b6c4899569b018071e6604e1d32657d00fd","names":[],"sources":[]}, + {"digest":"92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583","names":[],"sources":[]}, {"digest":"930c1cd289def119d443e19dd90008916bf035f0a4d23e07241e7f050f2b89b7","names":["packages/interaction/user-approval/src/types.ts#ApprovalOutcome"],"sources":["packages/interaction/user-approval/src/types.ts:32"]}, {"digest":"935c8d486292b13c87b57ca280eb8c58b5b2f5239be86fcd4334c33fdeb87682","names":[],"sources":[]}, {"digest":"938f7c7357bc403ea3b281a9dd016939df7a0880fc8db12612585be574d0d615","names":[],"sources":[]}, @@ -426,12 +434,11 @@ {"digest":"94c56aeb1564ad735eb773d88636b40761d063efa1610f1308004d32923f5378","names":[],"sources":[]}, {"digest":"95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65","names":[],"sources":["packages/llm/llm/src/types.ts:472"]}, {"digest":"95a3ffccbf3c8afcf9a2f3b355f724da8e07e98dc47ee741166dde7f9ea058bd","names":[],"sources":[]}, - {"digest":"95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f","names":[],"sources":[]}, {"digest":"95c0e88fcf0bafb2087f912ad9e2369de6ae6530cc0c4c2772c96205b57f7f60","names":[],"sources":[]}, + {"digest":"9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263","names":["packages/llm/llm/src/message.ts#UserMessage"],"sources":["packages/llm/llm/src/message.ts:162"]}, {"digest":"9645cf93769a6dfb227bd400bd6cb9fa7bab9140f4a851e4ae92514ade6e3114","names":[],"sources":[]}, {"digest":"96bfb916a727cd4445a594df264c523fc039749a31bfed924b8cc7bb308a5bb0","names":[],"sources":[]}, - {"digest":"96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c","names":["packages/core/agent/src/types.ts#InboxTarget"],"sources":["packages/core/agent/src/types.ts:40"]}, - {"digest":"9808c3d0f0a1be8fe313b4148ba22c691c4d59bd2c67aef3c63cfe8043148c5f","names":["packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData"],"sources":["packages/session/session-title-llm/src/index.ts:37"]}, + {"digest":"96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c","names":["packages/core/agent/src/types.ts#InboxTarget"],"sources":["packages/core/agent/src/types.ts:47"]}, {"digest":"9890113e97dd3541787bfe6704f2b223d3a890b5325ca0acae2b0398909b413e","names":[],"sources":["packages/sandbox/sandbox-policy/src/session-mode.ts:33"]}, {"digest":"99135d8a3937977955eaac5f8f04f930dcd1562f8450a54a51cde9b921dd59a6","names":[],"sources":[]}, {"digest":"9947aa5938fe19a895298da412917acbf9ffb5093a73ab8baa257e6ae9e63a07","names":[],"sources":[]}, @@ -449,7 +456,6 @@ {"digest":"9e07693fd7cd9babe0688c22c986e4d519ae1c0e40eeea13b2dc583bed2abc86","names":[],"sources":[]}, {"digest":"9e2c06d8653585b33d72d5c1a80da79c8a24a88c284409bca15cc0f0ead5ebfb","names":[],"sources":[]}, {"digest":"9e41386b3a0c9572b0d63078492ebb3997da7d3a830d44e0259418bd02f4bcb2","names":[],"sources":["packages/compaction/compaction/src/types.ts:38","packages/compaction/compaction/src/types.ts:84"]}, - {"digest":"9e8043098fb89db6d7c92a92c70146c5c8d24fa88f733c687fa29e5e7a58ab30","names":[],"sources":[]}, {"digest":"9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94","names":[],"sources":["packages/experimental/webhook/src/types.ts:74"]}, {"digest":"9f65c43e340bba31cdc0fc37bd93ccb68d5e9b36f47a68c207e95a8e68620a5d","names":[],"sources":[]}, {"digest":"a0a2e5c42e1c929445ecd1cd70f49be6b66441894ec72c08e8ae332821d4a3cb","names":[],"sources":[]}, @@ -469,6 +475,7 @@ {"digest":"a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c","names":[],"sources":["packages/experimental/agent-team/src/types.ts:232"]}, {"digest":"a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc","names":[],"sources":[]}, {"digest":"a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb","names":["packages/experimental/hook-protocol/src/types.ts#HookDialect"],"sources":["packages/experimental/hook-protocol/src/types.ts:48"]}, + {"digest":"a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664","names":[],"sources":[]}, {"digest":"a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c","names":["packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData"],"sources":["packages/workflow/tool-workflow/src/types.ts:14"]}, {"digest":"a720757f704a2512e9d2f20da4a093e582472176236a75ab8b8eb7aaff17d85a","names":[],"sources":[]}, {"digest":"a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f","names":[],"sources":[]}, @@ -484,7 +491,6 @@ {"digest":"aa7bc2817014ca6c45719b1c193c5c131be718a0921f34345098ff250f444626","names":["packages/feedback/message-feedback/src/types.ts#MessageFeedbackPut"],"sources":["packages/feedback/message-feedback/src/types.ts:38"]}, {"digest":"aa93010d0e521ad7c42273f58d5c9b8bf1dfc94edf1b805a3971b364a35c1877","names":[],"sources":[]}, {"digest":"aacda84a69739238aa782eea902d3073017d9d74b9808de2bf5a83c9738cbdc6","names":[],"sources":[]}, - {"digest":"aada2771a250136cc3a839b0507e9c6502622a2897716903942297bcb6661292","names":[],"sources":[]}, {"digest":"aae343bc3d663c83f2e8b5e1c7eea5d509dc7a5361bc8bd8b5ac3bb8d3b1ed56","names":[],"sources":[]}, {"digest":"ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7","names":[],"sources":["packages/core/session/src/types.ts:220","packages/llm/llm/src/types.ts:159"]}, {"digest":"aba175fb20ffc71112c55efe70458ff5d7dbfb83501892b4ba8d077f0ea5d7ba","names":[],"sources":[]}, @@ -503,7 +509,6 @@ {"digest":"b0127044ab31a702bddfd785d345f5abd7a70876746e895ce443afa3e60ddf2d","names":[],"sources":[]}, {"digest":"b062078e899958f99ae464514578b8c093924dfe34bd9a835932b420ff2150b1","names":[],"sources":[]}, {"digest":"b0b0eb1b16490f6bee135b2377e8f8c39227974c799ad7d49ff418af23ecbc35","names":["packages/attachment/attachment/src/types.ts#ImageAttachmentRef"],"sources":["packages/attachment/attachment/src/types.ts:11"]}, - {"digest":"b112459dcb0686091c31ce6d213b2a4934b5ad19d37cc5b599e46f5a9d715591","names":["packages/llm/llm/src/message.ts#MessageSource"],"sources":["packages/llm/llm/src/message.ts:136"]}, {"digest":"b158f3ff6923714807c0d73882f0b05dc87f8fa85c9e90814852d48bbc866081","names":["packages/core/session/src/types.ts#RequestContext"],"sources":["packages/core/session/src/types.ts:260"]}, {"digest":"b1664fa89f8eab49a14e7e3168d8bc4da4aa9fd76332f6c4a053d839e5276ae7","names":[],"sources":[]}, {"digest":"b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1","names":[],"sources":["packages/experimental/agent-team/src/types.ts:230"]}, @@ -556,13 +561,14 @@ {"digest":"c219efbdfd53b836786202ab4532913d555798f0a8615a4e9629736e3799fb70","names":[],"sources":[]}, {"digest":"c2deb7c9183735d4e0fffddf5c623f4e34daa1c8e96b111982e957dfcbbcf896","names":["packages/workflow/workflow/src/types.ts#WorkflowAgentOutcome"],"sources":["packages/workflow/workflow/src/types.ts:110"]}, {"digest":"c2f49004d9ae38a964e80850c87bc6849d6a872091a1d43e99eedf00c641e2a3","names":[],"sources":[]}, - {"digest":"c433ef580a5b229bbe68274a7164aa3a8e8040d7217f010b610ca0696a84253c","names":[],"sources":["packages/core/agent/src/types.ts:110"]}, {"digest":"c43b119ce579b3e9983a0d3adc4ba402ebb9ba773d89738a2dab01ecf5813943","names":[],"sources":[]}, {"digest":"c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49","names":[],"sources":[]}, {"digest":"c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005","names":[],"sources":["packages/interaction/user-questions/src/types.ts:134"]}, {"digest":"c5687b1313206a6ff334066047b6a0681611467dc4df8c5a8e71d0b6df39311f","names":[],"sources":[]}, {"digest":"c589e938cc0e4989df8b82abef6339de621f0dc4fe62c230d145b94996af7f92","names":[],"sources":[]}, {"digest":"c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6","names":["packages/util/values/src/index.ts#JsonValue"],"sources":["packages/util/values/src/index.ts:4"]}, + {"digest":"c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5","names":[],"sources":[]}, + {"digest":"c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039","names":[],"sources":["packages/core/agent/src/types.ts:119"]}, {"digest":"c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262","names":[],"sources":[]}, {"digest":"c8e6f48878cc2a2f0dc59682280d81f407943fb503f32c64bf9ff3d9393c3eff","names":[],"sources":[]}, {"digest":"c99e20441bf15341e9db54385e558e5c03d21dff2eb1ac08ccbf47c2d8747954","names":[],"sources":[]}, @@ -575,20 +581,20 @@ {"digest":"cda2345a9be9d139df49ccbd16dcba716f3440178bf20ff8b801fda1f5eb77dc","names":[],"sources":[]}, {"digest":"cdca80b8dad24d4e7b1d5a288a8475ba8d8d46be385bd2da85f122ee836f1670","names":[],"sources":[]}, {"digest":"cdee10e87dbed8440d8b6935888624884878efc7af95402e94171b29f173dced","names":[],"sources":["packages/llm/llm-retry/src/types.ts:17"]}, - {"digest":"cebb7e9f3f663e660a531d9764ecfb9fa7fc73c22bf8d628f888e9ecf18c5cf0","names":["packages/llm/llm/src/message.ts#DeveloperMessage"],"sources":["packages/llm/llm/src/message.ts:157"]}, {"digest":"cef171adf021a1867d244618a3dbbaafc22beb17c7f3df3ee203393271c8c6f6","names":["packages/core/tool-executor-ptc/src/types.ts#PtcDispatchEventData"],"sources":["packages/core/tool-executor-ptc/src/types.ts:21"]}, {"digest":"cffcdc19881ab61ec0a9ce18743996f7ac44ac8889f34dfac36ae745574aa22f","names":["packages/goal/goal/src/domain.ts#GoalClearChangeMeta"],"sources":["packages/goal/goal/src/domain.ts:35"]}, {"digest":"d0fbb0ca1bbba059830826f33f801da1aced35a2504376d61e020bdfec8d50c0","names":[],"sources":[]}, {"digest":"d28765f87d6b6ab408e0b2f3e7e297791b004b806d8e302c49903ebae815feb2","names":[],"sources":[]}, - {"digest":"d2b617d9dfcc4e0a4b60732d8ba1effc632e3b75124b93b064c07814a0a716bc","names":[],"sources":[]}, {"digest":"d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d","names":[],"sources":["packages/llm/llm/src/types.ts:475"]}, {"digest":"d4c799e3d51cae1040362ba4968efc484739e14416c9c1df20c2f108b4ef2eb5","names":[],"sources":[]}, {"digest":"d4e4d575abeb4b72d616dc65c17ac0eaa943cc135609395044c38d5afcb10e31","names":[],"sources":[]}, + {"digest":"d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2","names":[],"sources":["packages/core/session/src/types.ts:317"]}, {"digest":"d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a","names":[],"sources":[]}, {"digest":"d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9","names":[],"sources":[]}, {"digest":"d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5","names":[],"sources":[]}, {"digest":"d6e654b88b584fcdf290a56802b1730bc7b9847ae86baa4265ab7769eb48990c","names":[],"sources":[]}, {"digest":"d85590a5675c6e2fd8164e5792a6cceac714605b8ac228adae5ee668facb1490","names":[],"sources":[]}, + {"digest":"d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c","names":[],"sources":[]}, {"digest":"d9196086e41e4dd5c2ce87f5c25529c8112a1731d258cf0728747d41a9ee3241","names":[],"sources":[]}, {"digest":"d9c462b9f62fdd3efc76a44b9524dc8781f6b91cd9c7e584b17d971defafebab","names":["packages/llm/llm/src/message.ts#ToolMessageSource"],"sources":["packages/llm/llm/src/message.ts:29"]}, {"digest":"d9edf1862e4471d6ad8a106a3f4160e0643874368707d50ecd8e27e5ee7a2567","names":[],"sources":[]}, @@ -612,6 +618,7 @@ {"digest":"df843efd87c78cf8de98f8e0d0216956cd568d0d0e56a22c853663e274aa1a18","names":[],"sources":[]}, {"digest":"dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd","names":["packages/experimental/agent-team/src/types.ts#TeamMemberPhase"],"sources":["packages/experimental/agent-team/src/types.ts:45"]}, {"digest":"e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05","names":[],"sources":[]}, + {"digest":"e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664","names":[],"sources":["packages/api/session-controller/src/types.ts:399"]}, {"digest":"e2ad8f5bcd813f268ffef1b10a12d7ddd2b8ced8b9b6d0275b3f6b7ed094a187","names":[],"sources":[]}, {"digest":"e2d0367e5716bc188435e51718ebff8ce873190625fa93b1c5331102d98758fa","names":[],"sources":[]}, {"digest":"e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602","names":[],"sources":[]}, @@ -619,6 +626,7 @@ {"digest":"e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72","names":[],"sources":[]}, {"digest":"e31572180d6b28b73f073ad30ae19d5d82982d3daf984723984e23da1e7c459a","names":[],"sources":[]}, {"digest":"e3e77b26f0148755a505f5b8ea843a3be117827d4703bb5754d9dc8ddbef521f","names":[],"sources":[]}, + {"digest":"e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c","names":["packages/llm/llm/src/message.ts#MessageSource"],"sources":["packages/llm/llm/src/message.ts:136"]}, {"digest":"e41bdebe246abe17154de7e7a1d81035437eee8d20e7f1edff0d91e2598ad828","names":[],"sources":[]}, {"digest":"e42b0f40b5d0b628c6e1c44a8fb9eda0780037a81959e1b93d2dfb47b46b0e87","names":[],"sources":[]}, {"digest":"e438a9d4fb883b3e4a8d9fc8913aa1550ed50eb384bfbf943100a5605b537498","names":[],"sources":[]}, @@ -638,7 +646,6 @@ {"digest":"e95f957154b2b1b58692af53d4131180b6dd19e2264869f876dbaa959991494e","names":[],"sources":[]}, {"digest":"ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403","names":[],"sources":["packages/experimental/agent-team/src/types.ts:234"]}, {"digest":"eaa71be0fbf0f702df8ea6c11d0aa2f9782263d45a10d0fb14eda662f4bfe04c","names":[],"sources":["packages/core/session/src/types.ts:201"]}, - {"digest":"eaa9125aec0fb28478096c46e73e21bf512ab0d84d826bfb41e7308707734cb5","names":[],"sources":[]}, {"digest":"ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8","names":[],"sources":[]}, {"digest":"ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93","names":[],"sources":[]}, {"digest":"ec8b5ff7c77ca6893ac7ba5a0c5b2aefeb52f12c64450b7359660c2a7e8a53d7","names":[],"sources":["packages/core/session/src/types.ts:234"]}, @@ -651,7 +658,7 @@ {"digest":"ef226a90ddac6cd8e631a3a337a3a3e27f9494be92befb7c718b404681d75e21","names":[],"sources":[]}, {"digest":"ef4f00923b9edbae15c7ef05320280bf61147ca17146cf59690359bfd5e5da45","names":[],"sources":[]}, {"digest":"f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c","names":[],"sources":[]}, - {"digest":"f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b","names":["packages/core/agent/src/types.ts#ModelSelection"],"sources":["packages/core/agent/src/types.ts:79"]}, + {"digest":"f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b","names":["packages/core/agent/src/types.ts#ModelSelection"],"sources":["packages/core/agent/src/types.ts:86"]}, {"digest":"f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402","names":["packages/core/session/src/types.ts#TurnEndReason"],"sources":["packages/core/session/src/types.ts:238"]}, {"digest":"f1996af2602e6f62b5ab179ca5c4af00d8753938b74cc6ef102de09e99e74a7e","names":[],"sources":[]}, {"digest":"f19bee84bf3d20904553cdcd57465232bc1c2a194f54c4d195292ba0487223a6","names":["packages/attachment/attachment/src/types.ts#FileAttachmentRef"],"sources":["packages/attachment/attachment/src/types.ts:39"]}, @@ -679,6 +686,7 @@ {"digest":"fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be","names":["packages/llm/llm/src/message.ts#AssistantMessage"],"sources":["packages/llm/llm/src/message.ts:167"]}, {"digest":"fab2bb09bc5e3b9db0685e8f5500b029af1a2b33e70bc703c1d5d0f596fbc1fd","names":["packages/sandbox/sandbox/src/index.ts#SandboxMode"],"sources":["packages/sandbox/sandbox/src/index.ts:30"]}, {"digest":"fab35c4520883c0d69e42d079c9f543fc5ac7a2d15b537b5572fcd99059c7018","names":[],"sources":[]}, + {"digest":"fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172","names":[],"sources":[]}, {"digest":"fb5e0517050d2c6d40e5b8145db1f7c5ca7044d5d50fada6ff80e0b22c6ca973","names":[],"sources":[]}, {"digest":"fb8b6a2e53cb9cf44c551176de0fb77d1ddc60037b99a891ff8a860b6d5c29a0","names":[],"sources":[]}, {"digest":"fbd2c74b346a1595b376139c587fadd23e4a21acc9c0abce342975b6cfc60986","names":[],"sources":[]}, @@ -722,6 +730,7 @@ "0e7b2a97f54f1afd": {"kind":"literal","value":"schedule/change"}, "0ed8d1108fe7a5e1": {"kind":"object","properties":[{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, "0eeb17e1df89b1eb": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, + "0ef3072084202b08": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"e3e9f4d1e245b470","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "0f46153645c29784": {"kind":"object","properties":[{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "0f8512903d94f57a": {"kind":"object","properties":[{"name":"data","type":"e481c46af3813e00","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"4312134fc6f9cad5","optional":false}],"indices":[]}, "0fe52387f6505270": {"kind":"literal","value":"plan-mode"}, @@ -733,7 +742,6 @@ "124aa62078ca3311": {"kind":"object","properties":[{"name":"data","type":"da60ed58a8fa23d7","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"d4c799e3d51cae10","optional":false}],"indices":[]}, "12a0cfa619ac4a5f": {"kind":"literal","value":"read-only"}, "12d5808fac1996b9": {"kind":"object","properties":[{"name":"attachment","type":"b0b0eb1b16490f6b","optional":false},{"name":"offloaded","type":"dacf3d5f3e4ff619","optional":true},{"name":"type","type":"fb8b6a2e53cb9cf4","optional":false}],"indices":[]}, - "12f9a5a7506bca46": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"cebb7e9f3f663e66","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "1379f0b72275994d": {"kind":"literal","value":"developer/message"}, "13d3d180f977bf78": {"kind":"object","properties":[{"name":"data","type":"1528539c63db8b23","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1a2718b00937b236","optional":false}],"indices":[]}, "14230f9b8b5797bb": {"kind":"literal","value":"chunk"}, @@ -797,6 +805,7 @@ "28936c614bd4e7e2": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "290f32bfd81ff85a": {"kind":"object","properties":[{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, "297fbd8067d7815a": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, + "2a668a48dec17397": {"kind":"object","properties":[{"name":"inputId","type":"4cd48f3e5108bd6e","optional":false},{"name":"source","type":"671e72b8036a3afb","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "2aad1179f9576314": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "2ab12a0e345658f9": {"kind":"array","element":"83da72c6857601c7"}, "2abb711460e6238d": {"kind":"object","properties":[{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"6f091fe32ab24407","optional":false}],"indices":[]}, @@ -810,7 +819,9 @@ "2eafefdc6f79a492": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderName","type":"4cd48f3e5108bd6e","optional":false},{"name":"targetId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "2f1e6a7ebace43d7": {"kind":"union","types":["3f74ba74acd10644","58f94390b982f8a5","de30b1fa12c75f81","ebb768d85ec87fb6"]}, "2f3f201c8846e19c": {"kind":"literal","value":"tool-goal"}, + "2f5b14bc2ccbe881": {"kind":"array","element":"7cf091467eb28887"}, "2fe5026daf14bd4f": {"kind":"object","properties":[{"name":"arguments","type":"bbc5696e3f13c758","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"parentCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"rootCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"subCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "30093f9f63a120cb": {"kind":"literal","value":"agent/retry"}, "30d5a537b2966857": {"kind":"union","types":["1b5649238876e288","ada310bf0bdb8fed","b81e9de81a481b24"]}, "312aee2f0c42aabb": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "31907de956375182": {"kind":"object","properties":[{"name":"compactionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"6b23ed9ce1b397e6","optional":false},{"name":"sourceCommandId","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, @@ -819,6 +830,7 @@ "3305a4bb6b6c29b4": {"kind":"union","types":["0341d4c3dfcd5325","07eccc9ea2f9b543","5bd310104cfe448e","7180844e1066230c","9d80129e13edfb6f","e4cac694f8b6e1d5","f05fb85e715af4ab"]}, "33341aac09312300": {"kind":"union","types":["3860b4757d512a28","487b65c37bfedb8b","71bb3254ec8f1e49","e72269f931559e90"]}, "335e242de1fcc17b": {"kind":"union","types":["bcf0caf62d964b2f","e438a9d4fb883b3e"]}, + "347c1718600bbb38": {"kind":"array","element":"9632024d168f8169"}, "347de3380f2da308": {"kind":"literal","value":"job/settled"}, "34d1ab5c5df37818": {"kind":"literal","value":"resume"}, "35203ba7ad5ef6f9": {"kind":"object","properties":[{"name":"data","type":"f09e5e7acf5ff5cc","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1d5f04a03ea75fc8","optional":false}],"indices":[]}, @@ -831,7 +843,6 @@ "383cf73ae4748e5b": {"kind":"literal","value":"recall"}, "3860b4757d512a28": {"kind":"object","properties":[{"name":"args","type":"93c33d9687613293","optional":false},{"name":"dt","type":"5d03ba38734809bc","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":true},{"name":"time0","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"616f93a5047de668","optional":false}],"indices":[]}, "390eed1028aba027": {"kind":"union","types":["b1f62380f6746fed","df5d9709b7db2a72"]}, - "39adb7fce8475af6": {"kind":"object","properties":[{"name":"data","type":"8bca2b4f53ec7745","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, "39cf2d3ff8bac81c": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"rootCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "3b04790e08b35241": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, "3b1be838223869fe": {"kind":"object","properties":[{"name":"data","type":"e4c18e294232c3ba","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"c589e938cc0e4989","optional":false}],"indices":[]}, @@ -851,6 +862,7 @@ "3f74ba74acd10644": {"kind":"literal","value":"image/jpeg"}, "3fdf9582c3b5597d": {"kind":"object","properties":[{"name":"afterSeconds","type":"0657d3ca5ac4e7bf","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"5b232a9b9bfcc0fc","optional":false},{"name":"prompt","type":"4cd48f3e5108bd6e","optional":false},{"name":"scheduledAt","type":"4cd48f3e5108bd6e","optional":false},{"name":"title","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, "408927267422a7b2": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, + "414a44a96f06339f": {"kind":"object","properties":[{"name":"kind","type":"63ba8895cc09e469","optional":false}],"indices":[]}, "4208123b50df5006": {"kind":"object","properties":[{"name":"data","type":"ad0c38a0adb82a94","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"3e0eff5946255ff0","optional":false}],"indices":[]}, "42e0916e0dda5f6d": {"kind":"object","properties":[{"name":"data","type":"7f9365bae9fcb73e","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"fe792a6cd4304945","optional":false}],"indices":[]}, "4312134fc6f9cad5": {"kind":"literal","value":"turn/end"}, @@ -882,7 +894,6 @@ "4e97c3d85c0fc817": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"bf16d2e98a7f1389","optional":false},{"name":"senderSessionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "4edec1b770e8ec4c": {"kind":"literal","value":"ptc-mode"}, "4f507598cf476214": {"kind":"literal","value":"series"}, - "4f9125fa028a5628": {"kind":"union","types":["09f3f8dc417f2782","4b259f7f8f0a44ad","8bca2b4f53ec7745","cebb7e9f3f663e66","fa87537f4a9c1e25"]}, "4f91f187baeabef5": {"kind":"object","properties":[{"name":"capturedFormatVersion","type":"0657d3ca5ac4e7bf","optional":true},{"name":"capturedThroughSeq","type":"3bd652ebfa8726b3","optional":false},{"name":"compacted","type":"aa93010d0e521ad7","optional":false},{"name":"inputIndex","type":"0657d3ca5ac4e7bf","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"omittedBytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"omittedMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"originalMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"retainedMessages","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"truncated","type":"aa93010d0e521ad7","optional":false}],"indices":[]}, "4fb59762612c3e3a": {"kind":"object","properties":[{"name":"data","type":"b1ebd5cda47d90d9","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"18fcdd273a2f2f9d","optional":false}],"indices":[]}, "50cf02d875f8a1f1": {"kind":"union","types":["2da725e554cd8930","dd2780e7e0768433"]}, @@ -935,11 +946,13 @@ "6292bcbedaa97fea": {"kind":"object","properties":[{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, "62e6a429ff3f390a": {"kind":"union","types":["1d8798b1a5e8c545","1e346446e183ee76","34d1ab5c5df37818","7624ca28c61ca0ea","ad22a5ca5bbaab2e","d9196086e41e4dd5"]}, "62ecb62dc0fe1b5e": {"kind":"literal","value":"next-turn"}, + "63ba8895cc09e469": {"kind":"literal","value":"retry"}, "6487b10ba7542f3c": {"kind":"object","properties":[{"name":"agentModel","type":"4cd48f3e5108bd6e","optional":true},{"name":"agentProvider","type":"4cd48f3e5108bd6e","optional":true},{"name":"agentReasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"mode","type":"e5d3828df1ec3e2a","optional":false},{"name":"persona","type":"4cd48f3e5108bd6e","optional":true},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"toolFilter","type":"3cfc3a56502da1f8","optional":true},{"name":"version","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "6598cd6da253948d": {"kind":"object","properties":[{"name":"context","type":"50cf02d875f8a1f1","optional":false},{"name":"description","type":"4cd48f3e5108bd6e","optional":false},{"name":"error","type":"4cd48f3e5108bd6e","optional":true},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"phase","type":"dfce5db4aa42b7eb","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "65a8400e451e667e": {"kind":"object","properties":[{"name":"kind","type":"09655b34d7f3cf1e","optional":false}],"indices":[]}, "65d583e66518c523": {"kind":"object","properties":[{"name":"entries","type":"aba175fb20ffc711","optional":false},{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"incomplete","type":"dacf3d5f3e4ff619","optional":true},{"name":"kind","type":"6ebcc2dbec9f2dab","optional":false},{"name":"update","type":"dacf3d5f3e4ff619","optional":true}],"indices":[]}, "66ba70f6f068da44": {"kind":"object","properties":[{"name":"deferLoading","type":"dacf3d5f3e4ff619","optional":true},{"name":"description","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"parameters","type":"0aa62ab51b36c0e6","optional":false}],"indices":[]}, + "671e72b8036a3afb": {"kind":"union","types":["414a44a96f06339f","e103228e063b4762"]}, "672c4aa2a9b736e3": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"1922cd79a70806c1","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "67d5f73ceae82f52": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":true},{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"stop","type":"93c33d9687613293","optional":true},{"name":"temperature","type":"0657d3ca5ac4e7bf","optional":true}],"indices":[]}, "68141cbe3994a5ff": {"kind":"literal","value":"max-tokens"}, @@ -988,6 +1001,7 @@ "7b1ce60193fa8b69": {"kind":"literal","value":"working-directory"}, "7c6129b6961cf4b2": {"kind":"object","properties":[{"name":"messageId","type":"4cd48f3e5108bd6e","optional":false},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "7c9f44e90a0058f4": {"kind":"object","properties":[{"name":"data","type":"5302e76315127761","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"570e850224346133","optional":false}],"indices":[]}, + "7cf091467eb28887": {"kind":"union","types":["09f3f8dc417f2782","0ef3072084202b08","4b259f7f8f0a44ad","9632024d168f8169","fa87537f4a9c1e25"]}, "7d226b9fd95b4ba2": {"kind":"union","types":["1df34a6c086866ff","cffcdc19881ab61e"]}, "7d54a5a5e629e2e7": {"kind":"object","properties":[{"name":"category","type":"3305a4bb6b6c29b4","optional":true},{"name":"text","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, "7d65acf0a3a43a8f": {"kind":"union","types":["1f0133217eeeffd8","277b61f9c6f81704","3c2535ca0a2062f1","4e44c9118152f682","83d6916021eede8f","85f9b505b7bdff1f","fb8b6a2e53cb9cf4"]}, @@ -1029,7 +1043,6 @@ "8ad45ad053b4d89f": {"kind":"object","properties":[{"name":"decision","type":"4cd48f3e5108bd6e","optional":false},{"name":"durationMs","type":"0657d3ca5ac4e7bf","optional":false},{"name":"exitCode","type":"0657d3ca5ac4e7bf","optional":true},{"name":"handlerId","type":"4cd48f3e5108bd6e","optional":false},{"name":"point","type":"4cd48f3e5108bd6e","optional":false},{"name":"stderrSummary","type":"4cd48f3e5108bd6e","optional":true},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "8af841fc9b265ade": {"kind":"literal","value":"system"}, "8bb8c3751a6ce64b": {"kind":"object","properties":[{"name":"sessionFormatVersion","type":"0657d3ca5ac4e7bf","optional":true},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"throughSeq","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, - "8bca2b4f53ec7745": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"b112459dcb068609","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "8c1c4f72755a78ea": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "8c627d7a45fa406c": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "8cdd3fdd295cee53": {"kind":"literal","value":"command/done"}, @@ -1037,10 +1050,12 @@ "8e611bf46821e500": {"kind":"object","properties":[{"name":"tool","type":"fcf427290c6213c2","optional":true},{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"85f9b505b7bdff1f","optional":false}],"indices":[]}, "8e8155594e078123": {"kind":"literal","value":"workspace/changes"}, "8e8e91646a5cae3f": {"kind":"object","properties":[{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"operation","type":"26c879bdd130e8b8","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, + "8eda0195dd1d4a3f": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"2f5b14bc2ccbe881","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "8f324cea3916c051": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, "8f6d609794bf5afc": {"kind":"tuple","elements":[{"type":"4e88776858652856","optional":false,"rest":false}]}, "8fcb0b44a575e998": {"kind":"object","properties":[{"name":"kind","type":"a720757f704a2512","optional":false}],"indices":[]}, "904bf499d98be4a3": {"kind":"union","types":["7382c4fc95fe8c5d","b765ee31033cea46","f6cb462f808309e3"]}, + "92b82e8b7bbb38b3": {"kind":"object","properties":[{"name":"data","type":"d582ddae3f21edf5","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, "930c1cd289def119": {"kind":"union","types":["2142e391b9ccb60d","2c4c21f19f426a58","811229eedcec5797","fab35c4520883c0d"]}, "935c8d486292b13c": {"kind":"literal","value":"web_search"}, "938f7c7357bc403e": {"kind":"primitive","type":"null"}, @@ -1050,12 +1065,11 @@ "94c56aeb1564ad73": {"kind":"literal","value":"in_progress"}, "95080295115fbd83": {"kind":"object","properties":[{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"5abab3a27fd27366","optional":false}],"indices":[]}, "95a3ffccbf3c8afc": {"kind":"literal","value":"owner"}, - "95acdbd066cf445d": {"kind":"object","properties":[{"name":"data","type":"12f9a5a7506bca46","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, "95c0e88fcf0bafb2": {"kind":"literal","value":"assistant"}, + "9632024d168f8169": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"e3e9f4d1e245b470","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "9645cf93769a6dfb": {"kind":"literal","value":"user-question-reply"}, "96bfb916a727cd44": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, "96f4a9c81fc0f940": {"kind":"union","types":["3e2ece4967cfebe9","62ecb62dc0fe1b5e"]}, - "9808c3d0f0a1be8f": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"d2b617d9dfcc4e0a","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "9890113e97dd3541": {"kind":"object","properties":[{"name":"mode","type":"fab2bb09bc5e3b9d","optional":false},{"name":"source","type":"76632533f67879dc","optional":true}],"indices":[]}, "99135d8a39379779": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, "9947aa5938fe19a8": {"kind":"union","types":["576a2de8355eb098","a99ec0993c62c058"]}, @@ -1073,7 +1087,6 @@ "9e07693fd7cd9bab": {"kind":"literal","value":"ask"}, "9e2c06d8653585b3": {"kind":"array","element":"1cde1bedde79c5e8"}, "9e41386b3a0c9572": {"kind":"object","properties":[{"name":"end","type":"0657d3ca5ac4e7bf","optional":false},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, - "9e8043098fb89db6": {"kind":"object","properties":[{"name":"data","type":"c433ef580a5b229b","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, "9edc162949abf93b": {"kind":"object","properties":[{"name":"deliveryId","type":"4cd48f3e5108bd6e","optional":false},{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"db6c5246911d47a7","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"ruleId","type":"4cd48f3e5108bd6e","optional":false},{"name":"source","type":"4cd48f3e5108bd6e","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "9f65c43e340bba31": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, "a0a2e5c42e1c9294": {"kind":"object","properties":[{"name":"data","type":"2e3523541e942ef6","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"0e7b2a97f54f1afd","optional":false}],"indices":[]}, @@ -1093,6 +1106,7 @@ "a40d12070f6f4a12": {"kind":"object","properties":[{"name":"task","type":"51edec100659da3a","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]}, "a6567ccb2e530606": {"kind":"object","properties":[{"name":"data","type":"0d47a8f3d847243c","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"0a8a062bc9c3c0d9","optional":false}],"indices":[]}, "a6b41f5bede8094d": {"kind":"union","types":["15495614ccfa9cbd","aff60ef947c18b01"]}, + "a6d8c25f4b2ca2d1": {"kind":"object","properties":[{"name":"data","type":"c66c6c3b411abf46","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, "a6da9ad5d42ae83a": {"kind":"object","properties":[{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"runId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "a720757f704a2512": {"kind":"literal","value":"tool-registry"}, "a7cf43ce7c2a4c03": {"kind":"object","properties":[{"name":"data","type":"20a57544bbf204da","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"f634871d1e18b1eb","optional":false}],"indices":[]}, @@ -1108,7 +1122,6 @@ "aa7bc2817014ca6c": {"kind":"object","properties":[{"name":"item","type":"3d7d07060a7be779","optional":false},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "aa93010d0e521ad7": {"kind":"primitive","type":"boolean"}, "aacda84a69739238": {"kind":"literal","value":"subagent"}, - "aada2771a250136c": {"kind":"array","element":"8bca2b4f53ec7745"}, "aae343bc3d663c83": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, "ab7ec5f9eb2c6dfa": {"kind":"object","properties":[{"name":"kind","type":"68141cbe3994a5ff","optional":false}],"indices":[]}, "aba175fb20ffc711": {"kind":"array","element":"83079c8a3f733ac3"}, @@ -1127,7 +1140,6 @@ "b0127044ab31a702": {"kind":"object","properties":[{"name":"data","type":"ca59530d1a4f3e19","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1986c2664009c991","optional":false}],"indices":[]}, "b062078e899958f9": {"kind":"object","properties":[{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, "b0b0eb1b16490f6b": {"kind":"object","properties":[{"name":"attachmentId","type":"4cd48f3e5108bd6e","optional":false},{"name":"bytes","type":"0657d3ca5ac4e7bf","optional":false},{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"mediaType","type":"2f1e6a7ebace43d7","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":true},{"name":"originalDimensions","type":"0f46153645c29784","optional":true},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, - "b112459dcb068609": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","65d583e66518c523","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, "b158f3ff69237148": {"kind":"object","properties":[{"name":"contextWindow","type":"0657d3ca5ac4e7bf","optional":true},{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"systemPromptUpdate","type":"f311671e07bc7fa6","optional":true}],"indices":[]}, "b1664fa89f8eab49": {"kind":"array","element":"bbc5696e3f13c758"}, "b1ebd5cda47d90d9": {"kind":"object","properties":[{"name":"member","type":"6598cd6da253948d","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]}, @@ -1180,13 +1192,14 @@ "c219efbdfd53b836": {"kind":"literal","value":"step/end"}, "c2deb7c9183735d4": {"kind":"union","types":["1ebc6f9ff3aa9e3a","2142e391b9ccb60d","b57a2889408de36e"]}, "c2f49004d9ae38a9": {"kind":"union","types":["1ebc6f9ff3aa9e3a","b57a2889408de36e","ef226a90ddac6cd8"]}, - "c433ef580a5b229b": {"kind":"object","properties":[{"name":"inserted","type":"aada2771a250136c","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, "c43b119ce579b3e9": {"kind":"literal","value":"paused"}, "c5228b7a55498d84": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "c55827fba7018198": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"9645cf93769a6dfb","optional":false},{"name":"outcome","type":"85d6d96ff8842974","optional":false}],"indices":[]}, "c5687b1313206a6f": {"kind":"literal","value":"cordis-host-runner"}, "c589e938cc0e4989": {"kind":"literal","value":"tool/call"}, "c592ce75aab73fca": {"kind":"union","types":["0657d3ca5ac4e7bf","4cd48f3e5108bd6e","529e0ccd0e34079a","72ec79127a9c0d02","938f7c7357bc403e","aa93010d0e521ad7"]}, + "c5bbcb3435908af2": {"kind":"object","properties":[{"name":"data","type":"9632024d168f8169","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, + "c66c6c3b411abf46": {"kind":"object","properties":[{"name":"inserted","type":"347c1718600bbb38","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, "c748fb42c80a1200": {"kind":"array","element":"86e1e384495ee09a"}, "c8e6f48878cc2a2f": {"kind":"literal","value":"active"}, "c99e20441bf15341": {"kind":"literal","value":"feedback/record"}, @@ -1199,20 +1212,20 @@ "cda2345a9be9d139": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "cdca80b8dad24d4e": {"kind":"literal","value":"tool-workflow/run-start"}, "cdee10e87dbed844": {"kind":"object","properties":[{"name":"delayMs","type":"0657d3ca5ac4e7bf","optional":false},{"name":"failure","type":"5f2117d1856a5ab4","optional":false},{"name":"maxRetries","type":"0657d3ca5ac4e7bf","optional":false},{"name":"mode","type":"3bc287e765d58dc3","optional":false},{"name":"policyKey","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"retry","type":"0657d3ca5ac4e7bf","optional":false},{"name":"retryId","type":"4cd48f3e5108bd6e","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, - "cebb7e9f3f663e66": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"b112459dcb068609","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "cef171adf021a186": {"kind":"object","properties":[{"name":"arguments","type":"bbc5696e3f13c758","optional":false},{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"error","type":"a81059ca06999da1","optional":true},{"name":"isError","type":"aa93010d0e521ad7","optional":false},{"name":"meta","type":"c592ce75aab73fca","optional":true},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"parentCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"rootCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"subCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "cffcdc19881ab61e": {"kind":"object","properties":[{"name":"cleared","type":"0b6d718c693fdf7f","optional":false},{"name":"clearedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"kind","type":"237c004c73b6b288","optional":false},{"name":"operation","type":"e7e154d47d5fbc53","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, "d0fbb0ca1bbba059": {"kind":"literal","value":"delete"}, "d28765f87d6b6ab4": {"kind":"literal","value":"one-shot"}, - "d2b617d9dfcc4e0a": {"kind":"array","element":"4f9125fa028a5628"}, "d4b739491689cc6b": {"kind":"object","properties":[{"name":"block","type":"8064c03c87c357fd","optional":false},{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"eefbea819caeeea9","optional":false}],"indices":[]}, "d4c799e3d51cae10": {"kind":"literal","value":"working-directory/change"}, "d4e4d575abeb4b72": {"kind":"literal","value":"canceled"}, + "d582ddae3f21edf5": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"0ef3072084202b08","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "d595ec73b32b016a": {"kind":"object","properties":[{"name":"data","type":"a40d12070f6f4a12","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"b57ae1b37e52bee0","optional":false}],"indices":[]}, "d63b8b8ffad9c02f": {"kind":"object","properties":[{"name":"data","type":"8bb8c3751a6ce64b","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"47e5d84f1a93450a","optional":false}],"indices":[]}, "d66e11dddf5dfa3c": {"kind":"object","properties":[{"name":"data","type":"cef171adf021a186","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"fb5e0517050d2c6d","optional":false}],"indices":[]}, "d6e654b88b584fcd": {"kind":"literal","value":"fallback"}, "d85590a5675c6e2f": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "d8dde9629b7323b0": {"kind":"object","properties":[{"name":"data","type":"2a668a48dec17397","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"30093f9f63a120cb","optional":false}],"indices":[]}, "d9196086e41e4dd5": {"kind":"literal","value":"complete"}, "d9c462b9f62fdd3e": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"a13d8f379222ece8","optional":false}],"indices":[]}, "d9edf1862e4471d6": {"kind":"literal","value":"schedule"}, @@ -1236,6 +1249,7 @@ "df843efd87c78cf8": {"kind":"array","element":"4f91f187baeabef5"}, "dfce5db4aa42b7eb": {"kind":"union","types":["871bfd6f7bb91061","b57a2889408de36e","c8e6f48878cc2a2f"]}, "e0a787e6ec76c7c9": {"kind":"object","properties":[{"name":"data","type":"ef11a3b7fa2033ae","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"c219efbdfd53b836","optional":false}],"indices":[]}, + "e103228e063b4762": {"kind":"object","properties":[{"name":"clientTimeZone","type":"4cd48f3e5108bd6e","optional":true},{"name":"kind","type":"63ba8895cc09e469","optional":false},{"name":"principal","type":"deebfc7c62177ea8","optional":true},{"name":"rpcId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "e2ad8f5bcd813f26": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"mode","type":"e5d3828df1ec3e2a","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, "e2d0367e5716bc18": {"kind":"literal","value":"compaction/start"}, "e2f9a41e0989f54e": {"kind":"object","properties":[{"name":"data","type":"9947aa5938fe19a8","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"ef4f00923b9edbae","optional":false}],"indices":[]}, @@ -1243,6 +1257,7 @@ "e308ccf867a5398e": {"kind":"object","properties":[{"name":"data","type":"f90eb4ab2d3897bc","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"8e8155594e078123","optional":false}],"indices":[]}, "e31572180d6b28b7": {"kind":"literal","value":"provider"}, "e3e77b26f0148755": {"kind":"object","properties":[{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "e3e9f4d1e245b470": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","414a44a96f06339f","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","65d583e66518c523","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e103228e063b4762","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, "e41bdebe246abe17": {"kind":"literal","value":"session"}, "e42b0f40b5d0b628": {"kind":"literal","value":"every"}, "e438a9d4fb883b3e": {"kind":"literal","value":"append"}, @@ -1262,7 +1277,6 @@ "e95f957154b2b1b5": {"kind":"literal","value":"assistant/attempt"}, "ea08ec1df0c33108": {"kind":"object","properties":[{"name":"message","type":"2eafefdc6f79a492","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]}, "eaa71be0fbf0f702": {"kind":"object","properties":[{"name":"kind","type":"ad3b56aa2fc1ad42","optional":false}],"indices":[]}, - "eaa9125aec0fb284": {"kind":"object","properties":[{"name":"data","type":"9808c3d0f0a1be8f","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, "ebb768d85ec87fb6": {"kind":"literal","value":"image/webp"}, "ec38b5949af8eaca": {"kind":"object","properties":[{"name":"data","type":"2fe5026daf14bd4f","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"2aed07493b1c65b6","optional":false}],"indices":[]}, "ec8b5ff7c77ca689": {"kind":"object","properties":[{"name":"kind","type":"b2245a906e13aa94","optional":false}],"indices":[]}, @@ -1303,6 +1317,7 @@ "fa87537f4a9c1e25": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"95c0e88fcf0bafb2","optional":false},{"name":"source","type":"719c51f67074f970","optional":false}],"indices":[]}, "fab2bb09bc5e3b9d": {"kind":"union","types":["12a0cfa619ac4a5f","18fce9540bb20897","a3edd1efd1fef21b"]}, "fab35c4520883c0d": {"kind":"literal","value":"rejected"}, + "fb28ad7d7aabceb5": {"kind":"object","properties":[{"name":"data","type":"8eda0195dd1d4a3f","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, "fb5e0517050d2c6d": {"kind":"literal","value":"tool/ptc-dispatch"}, "fb8b6a2e53cb9cf4": {"kind":"literal","value":"image"}, "fbd2c74b346a1595": {"kind":"literal","value":"agent-instructions"}, diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index d11e03488a..d709735217 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -104,7 +104,8 @@ interface Agent { /** * Route identified input to an inbox boundary and optionally wake the driver. - * Empty ordinary input drives work without becoming a model-visible message. + * A retry-source input must be empty; admission records `agent/retry` + * without adding a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -117,9 +118,8 @@ interface Agent { send(message: UserMessage, target: InboxTarget, wakeup: boolean): void /** - * Queue an ordinary follow-up turn and wake the driver. The item becomes the - * sole ordinary message of its own turn. An empty ordinary input starts a - * turn from committed history without appending a model-visible user message. + * Queue a follow-up turn and wake the driver. The item is its sole ordinary + * prompt, or an empty retry-source control with no model-visible message. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 7e4c682f69..11fa2a6ffe 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -108,7 +108,8 @@ interface Agent { /** * Route identified input to an inbox boundary and optionally wake the driver. - * Empty ordinary input drives work without becoming a model-visible message. + * A retry-source input must be empty; admission records `agent/retry` + * without adding a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -121,9 +122,8 @@ interface Agent { send(message: UserMessage, target: InboxTarget, wakeup: boolean): void /** - * Queue an ordinary follow-up turn and wake the driver. The item becomes the - * sole ordinary message of its own turn. An empty ordinary input starts a - * turn from committed history without appending a model-visible user message. + * Queue a follow-up turn and wake the driver. The item is its sole ordinary + * prompt, or an empty retry-source control with no model-visible message. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml index cbda06d0c8..9573c050b1 100644 --- a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml +++ b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml @@ -3,14 +3,14 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md /: - en: 7a194a68f49ac09a - zh: 4d7bdff82c571c62 -/observe-empty-prompt-admission-through-the-inbox: - en: 450277479989809e - zh: 4b01f252faeae19c -/observe-empty-prompt-admission-through-the-inbox/change: - en: e1c081d5321f3256 - zh: a5dd5585fe4300b3 -/observe-empty-prompt-admission-through-the-inbox/migration: - en: 36e80226df9dcf54 - zh: 81a9c0bd5c667470 + en: f5e2d8120bcf97bd + zh: 64b692f69ec7b0d4 +/empty-prompts-and-retry-controls: + en: e26b58650aa8e914 + zh: c0e5a824ec3bea40 +/empty-prompts-and-retry-controls/change: + en: bf69985ccd8cc89a + zh: b1046f8c98cbdb43 +/empty-prompts-and-retry-controls/migration: + en: 028f7fd6db73a36b + zh: b72f840db5301d6c diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md index 16876dd26c..c74f3c2047 100644 --- a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md +++ b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md @@ -1,19 +1,20 @@ --- kind: upgrade-guide -description: "Empty ordinary Agent inputs start requests without producing user/message events." +description: Empty API and SDK prompts become retry controls whose required Session event needs an updated reader. --- -# Observe empty prompt admission through the inbox + +# Empty prompts and retry controls English | [中文](guide.zh.md) ## Change -An ordinary Agent input with `content: []` starts a new turn from committed history without appending an empty `user/message`. The identified input remains in durable inbox events. Custom consumers that wait for every accepted input to produce a user message must observe its inbox receipt instead. Session logs need no migration; existing recorded empty user messages keep their historical interpretation. +The Session prompt API accepts `content: []` to continue eligible failed, idle sessions in queue mode. SDK `session/prompt` maps empty `contentBlocks` to the same retry-source control. Acceptance records `agent/retry` with `turn`, `step`, `inputId`, and `source`, instead of a `user/message`. A new turn still starts and closes normally. Direct Agent callers using ordinary empty user messages retain their existing behavior. -The Web Session prompt endpoint accepts an empty array only for an eligible failed, idle ordinary Session in queue mode. Whitespace-only text parts remain invalid. +The new required event and retry source keep the current Session format version. Updated readers accept older logs; older readers reject logs containing the unknown required event. See the [persistence acknowledgement](../../../persistence-changes/2026-10-27-retry-control-input.md). ## Migration -1. Keep ordinary text and attachment inputs unchanged. Use an empty array only when requesting another model call without new user text. -2. Match acceptance by the input identity in `agent/inbox/spliced`, then observe the normal turn/status lifecycle. Do not wait for an empty `user/message`. -3. Verify that continuation adds a balanced turn without adding a user message. The shipped TypeScript and Python SDK clients already observe inbox receipts. +1. Upgrade Session readers and SDK runtimes together before opening logs containing retry controls. +2. Consumers correlating empty API or SDK prompts with `user/message` must use the durable inbox identity and `agent/retry.inputId`. Handle the retry source without treating it as model-visible user content. +3. Verify that a continued failure produces a balanced new turn and `agent/retry`, while derived model history contains no additional user message. diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md index 9c879cc2aa..66f436fae9 100644 --- a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md +++ b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md @@ -1,19 +1,20 @@ --- kind: upgrade-guide -description: "空的普通 Agent 输入发起请求,但不产生 user/message 事件。" +description: 空 API 和 SDK 提示词变为重试控制输入,其必读 Session 事件需要更新后的读取方。 --- -# 通过 inbox 观察空 prompt 准入 + +# 空提示词与重试控制输入 [English](guide.md) | 中文 ## 变更 -普通 Agent 输入的 `content: []` 基于已提交历史启动新轮次,不再追加空的 `user/message`。带身份的输入仍保留在持久 inbox 事件中。若自定义消费者等待每条已接受输入产生用户消息,应改为观察对应 inbox 回执。Session 日志无需迁移,历史空用户消息的解释保持不变。 +Session prompt API 接受 `content: []`,在 queue 模式下继续符合条件且空闲的失败会话。SDK `session/prompt` 将空 `contentBlocks` 映射到同样的 retry 来源控制输入。接受后记录带 `turn`、`step`、`inputId` 和 `source` 的 `agent/retry`,而非 `user/message`。新轮次仍正常开启和闭合。直接使用普通空用户消息的 Agent 调用方保留原有行为。 -Web Session prompt 端点仅在普通 Session 空闲、最新失败符合继续条件且使用 queue 模式时接受空数组。仅含空白的文本块仍无效。 +新增必读事件及 retry 来源保留当前 Session 格式版本。更新后的读取方接受旧日志;旧读取方会拒绝包含未知必读事件的日志。见[持久化确认记录](../../../persistence-changes/2026-10-27-retry-control-input.zh.md)。 ## 迁移 -1. 普通文本和附件输入保持不变。仅在需要再次请求模型而不添加用户文本时使用空数组。 -2. 通过 `agent/inbox/spliced` 中的输入身份匹配准入,然后观察正常轮次与状态生命周期。不要等待空的 `user/message`。 -3. 验证继续操作增加正常闭合的轮次,但不增加用户消息。内置 TypeScript 和 Python SDK 客户端已观察 inbox 回执。 +1. 打开包含重试控制输入的日志前,同时升级 Session 读取方与 SDK 运行时。 +2. 通过 `user/message` 关联空 API 或 SDK 提示词的消费方,改用持久 inbox 身份与 `agent/retry.inputId`。处理 retry 来源时,不应将其视为模型可见用户内容。 +3. 确认继续失败请求会产生正常开启并闭合的新轮次和 `agent/retry`,而派生模型历史中没有额外用户消息。 diff --git a/packages/api/session-controller/README.i18n.yaml b/packages/api/session-controller/README.i18n.yaml index a1daccc8f0..99568c2d0c 100644 --- a/packages/api/session-controller/README.i18n.yaml +++ b/packages/api/session-controller/README.i18n.yaml @@ -24,8 +24,8 @@ en: 954923654eebe2dc zh: 6bd9a7d2ea965aed /session-controller/configuration: - en: 94cb7b73b385b320 - zh: 1f3bc1e6552a01a3 + en: c1b6d81775d0b56e + zh: 67c3ef16ff39c6c3 /session-controller/model-experience: en: 30b917bf822bb4df zh: b0b023b5b526c317 diff --git a/packages/api/session-controller/README.md b/packages/api/session-controller/README.md index 371ee3c13e..77b6f9eb1b 100644 --- a/packages/api/session-controller/README.md +++ b/packages/api/session-controller/README.md @@ -110,7 +110,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a Blockquotes travel as ordinary prompt text without quote metadata. Local submission echoes and durable messages use the same text rendering rules; queue edits replace content while retaining prompt correlation. -An empty `prompt.content` array requests a new turn only for an eligible failed, idle ordinary Session in queue mode. Pending human input prevents admission; non-waking injected context is retained. `conversationContinuation` supplies the shared six-code manual policy, independently of provider retry configuration. The input is logged through the inbox for request-id deduplication, but creates no model-visible user message. Whitespace-only text parts remain invalid. +An empty `prompt.content` array requests a new turn only for an eligible failed, idle ordinary Session in queue mode. Pending human input prevents admission; non-waking injected context is retained. `conversationContinuation` supplies the shared six-code manual policy, independently of provider retry configuration. The input uses `source.kind: 'retry'` and ordinary inbox admission; Loop acceptance records `agent/retry` with the input identity and caller metadata for deduplication and audit, without adding a model-visible message. Whitespace-only text parts remain invalid. ## Model Experience diff --git a/packages/api/session-controller/README.zh.md b/packages/api/session-controller/README.zh.md index 9e698a26e7..53387989ad 100644 --- a/packages/api/session-controller/README.zh.md +++ b/packages/api/session-controller/README.zh.md @@ -110,7 +110,7 @@ GUI 模型选择要求确切提供方/模型对出现在可用目录中;不 Blockquote 作为普通 prompt 文本传递,不携带 quote 元数据。本地提交回显与持久消息使用同一文本渲染规则;队列编辑替换 content,同时保留 prompt 关联信息。 -空的 `prompt.content` 数组仅在普通 Session 空闲、最新失败符合继续条件且使用 queue 模式时请求新轮次。待消费的用户输入阻止准入,非唤醒的注入上下文保留。`conversationContinuation` 提供共用的六码手动策略,独立于提供方重试配置。输入通过 inbox 持久记录,用于 request-id 去重,但不生成模型可见的用户消息。仅含空白的文本块仍无效。 +空的 `prompt.content` 数组仅在普通 Session 空闲、最新失败符合继续条件且使用 queue 模式时请求新轮次。待消费的用户输入阻止准入,非唤醒的注入上下文保留。`conversationContinuation` 提供共用的六码手动策略,独立于提供方重试配置。输入使用 `source.kind: 'retry'` 并沿用普通 inbox 准入;Loop 接受后记录带输入身份和调用方元数据的 `agent/retry`,供去重和审计使用,不增加模型可见消息。仅含空白的文本块仍无效。 ## 模型体验 diff --git a/packages/api/session-controller/src/commands.ts b/packages/api/session-controller/src/commands.ts index 88d7486cbe..15b110c8ac 100644 --- a/packages/api/session-controller/src/commands.ts +++ b/packages/api/session-controller/src/commands.ts @@ -568,13 +568,14 @@ function resolvePromptFileReceipts( function hasPromptRequest(agent: Agent, requestId: SessionRequestId): boolean { const matches = (message: UserMessage): boolean => { const source = message.source - return source.kind === 'user' && 'rpcId' in source && source.rpcId === requestId + return (source.kind === 'user' || source.kind === 'retry') && 'rpcId' in source && source.rpcId === requestId } if (agent.inbox.nextTurn.some(matches) || agent.inbox.nextStep.some(matches)) return true // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. return agent.session.snapshotEvents().some((event) => { - if (event.type === 'agent/inbox/spliced') { - return event.data.inserted.some(message => message.content.length === 0 && matches(message)) + if (event.type === 'agent/retry') { + const source = event.data.source + return 'rpcId' in source && source.rpcId === requestId } if (event.type !== 'user/message') return false const source = event.data.source diff --git a/packages/api/session-controller/src/continuation.ts b/packages/api/session-controller/src/continuation.ts index 5d8e97553e..0e8247688a 100644 --- a/packages/api/session-controller/src/continuation.ts +++ b/packages/api/session-controller/src/continuation.ts @@ -15,30 +15,25 @@ const stateSchema = z.object({ admitted: z.boolean(), failedAttempt: z.boolean(), ending: endingSchema, - continuing: z.boolean(), }) -/** Recoverable manual-continuation facts, including an accepted empty input awaiting its turn. */ +/** Recoverable manual-continuation facts over admitted user input and retry controls. */ export type ContinuationState = z.infer -const EMPTY: ContinuationState = { admitted: false, failedAttempt: false, ending: null, continuing: false } +const EMPTY: ContinuationState = { admitted: false, failedAttempt: false, ending: null } /** Eligibility requires admitted human work and a failed settlement in the last request step. */ export const continuationProjectionDefinition = { key: 'conversationContinuation', - stateVersion: 2, + stateVersion: 3, stateSchema, init: (): ContinuationState => EMPTY, apply: (state, event) => { switch (event.type) { - case 'agent/inbox/spliced': - if (state.continuing && event.data.target === 'next-turn' && event.data.outcome === 'canceled' - && event.data.start === 0) return { ...state, continuing: false } - return state.ending !== null && !state.continuing && event.data.target === 'next-turn' - && event.data.inserted.some(message => message.source.kind === 'user' && message.content.length === 0) - ? { ...state, continuing: true } : state case 'turn/start': - return state.continuing ? { ...EMPTY, admitted: true } : EMPTY + return EMPTY + case 'agent/retry': + return state.admitted ? state : { ...state, admitted: true } case 'step/start': case 'assistant/message': return state.failedAttempt ? { ...state, failedAttempt: false } : state diff --git a/packages/api/session-controller/src/index.ts b/packages/api/session-controller/src/index.ts index b5599f3a22..a10dcf5578 100644 --- a/packages/api/session-controller/src/index.ts +++ b/packages/api/session-controller/src/index.ts @@ -483,7 +483,7 @@ export class SessionController extends TypertRemoteService { // The caller is recorded only when it is not the owner, so owner messages keep their exact source. const principal = principalOf(this.ctx.invocation) const source: MessageSource = { - kind: 'user', + kind: request.content.length === 0 ? 'retry' : 'user', rpcId: request.requestId, ...(clientTimeZone === undefined ? {} : { clientTimeZone }), ...(principal === undefined ? {} : { principal }), diff --git a/packages/api/session-controller/src/types.ts b/packages/api/session-controller/src/types.ts index 798ca0e04c..91818f9748 100644 --- a/packages/api/session-controller/src/types.ts +++ b/packages/api/session-controller/src/types.ts @@ -395,6 +395,8 @@ declare module '@deepseek-ai/dsh-llm' { * caller other than the owner, so an absent `principal` means the owner. */ 'user-rpc': { kind: 'user'; rpcId: SessionRequestId; clientTimeZone?: string; principal?: Principal } + /** Browser retry correlation with the same caller metadata as an ordinary prompt. */ + 'retry-rpc': { kind: 'retry'; rpcId: SessionRequestId; clientTimeZone?: string; principal?: Principal } } } diff --git a/packages/api/session-controller/tests/continuation.host.spec.ts b/packages/api/session-controller/tests/continuation.host.spec.ts index f87b29809f..f2ae6ecf35 100644 --- a/packages/api/session-controller/tests/continuation.host.spec.ts +++ b/packages/api/session-controller/tests/continuation.host.spec.ts @@ -111,7 +111,7 @@ it('publishes a complete-log result independently of the visible page and releas expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBe(ending) expect(changed.mock.calls.map(([, key, value]) => [key, value])).toEqual([['conversationContinuation', null], ['conversationContinuation', ending]]) expect(ctx.sessionProjections.checkpoint(session).conversationContinuation) - .toEqual({ ver: 2, seq: ending, val: { admitted: false, failedAttempt: false, ending, continuing: false } }) + .toEqual({ ver: 3, seq: ending, val: { admitted: false, failedAttempt: false, ending } }) session.append('turn/start', { turn: 2 }) expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBeNull() expect(changed).toHaveBeenCalledTimes(3) @@ -127,42 +127,28 @@ it('restores valid, absent and outdated checkpoints through the registry', async await ctx.sessionProjections.register(definition) const session = ctx.sessions.create(SessionId('restored')) const log = events(failed('SERVER')) - const checkpoint = { conversationContinuation: { ver: 2, seq: SessionSeq(2), val: fold(log.slice(0, 3)) } } + const checkpoint = { conversationContinuation: { ver: 3, seq: SessionSeq(2), val: fold(log.slice(0, 3)) } } const restore = (rows: typeof checkpoint | Record, tail: readonly SessionEvent[], base: number) => ctx.sessionProjections.restore(rows, tail, SessionLogOffset(base), session.header, session.inheritedEventCount) expect(restore(checkpoint, log.slice(3), 3).snapshot.values.conversationContinuation).toBe(5) expect(restore({}, log, 0).snapshot.values.conversationContinuation).toBe(5) - const outdated = { conversationContinuation: { ...checkpoint.conversationContinuation, ver: 0 } } + const outdated = { conversationContinuation: { ...checkpoint.conversationContinuation, ver: 2 } } expect(restore(outdated, log, 0).snapshot.values.conversationContinuation).toBe(5) expect(() => restore(outdated, log.slice(3), 3)).toThrow('re-read from seq 0') expect(() => restore({}, log.slice(3), 3)).toThrow('re-read from seq 0') }) -it('carries admitted work through an empty continuation and retires a canceled queued control', async () => { - const ctx = new Context() - onTestFinished(() => ctx.fiber.dispose()) - await ctx.plugin(SessionStore) - await ctx.plugin(SessionProjectionRegistry) - const handle = await ctx.sessionProjections.register(definition) - const session = ctx.sessions.create(SessionId('empty-repeat')) - for (const event of events(failed('SERVER'))) { - if (event.type === 'user/message') session.append(event.type, event.data, { surfaceOp: 'append' }) - else if (event.type === 'turn/start') session.append(event.type, event.data) - else if (event.type === 'step/start' || event.type === 'step/end') session.append(event.type, event.data) - else if (event.type === 'assistant/attempt') session.append(event.type, event.data) - else if (event.type === 'turn/end') session.append(event.type, event.data) +it('admits retry controls independently of empty ordinary user messages', () => { + const base = events(failed('SERVER')) + const input = base.find(event => event.type === 'user/message')! + const empty: SessionEvent<'user/message'> = { ...input, type: 'user/message', data: createUserMessage({ + content: [], source: { kind: 'user' }, + }) } + expect(fold(base.map(event => event === input ? empty : event)).ending).toBeNull() + const retry: SessionEvent<'agent/retry'> = { type: 'agent/retry', seq: empty.seq, time: empty.time, + data: { turn: 1, step: 1, inputId: empty.data.id, source: { kind: 'retry' } }, } - const input = createUserMessage({ content: [], source: { kind: 'user' } }) - session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [input] }) - expect(handle.stateOf(session).continuing).toBe(true) - session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, removedCount: 1, inserted: [], outcome: 'canceled' }) - expect(handle.stateOf(session).continuing).toBe(false) - session.append('agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [input] }) - session.append('turn/start', { turn: 2 }) - expect(handle.stateOf(session).admitted).toBe(true) - session.append('step/start', { turn: 2, step: 1 }) - session.append('assistant/attempt', { turn: 2, step: 1, stream: [] }) - session.append('step/end', { turn: 2, step: 1 }) - const ending = session.append('turn/end', { turn: 2, reason: failed('SERVER') }).seq - expect(handle.stateOf(session).ending).toBe(ending) + expect(fold(base.map(event => event === input ? retry : event)).ending).toBe(5) + const admitted = definition.apply(definition.init(), retry) + expect(definition.apply(admitted, retry)).toBe(admitted) }) diff --git a/packages/api/session-controller/tests/empty-prompt.host.spec.ts b/packages/api/session-controller/tests/empty-prompt.host.spec.ts index 5f8d913f40..835d7ae442 100644 --- a/packages/api/session-controller/tests/empty-prompt.host.spec.ts +++ b/packages/api/session-controller/tests/empty-prompt.host.spec.ts @@ -25,7 +25,7 @@ async function setup() { return { ctx, remote, agent, adapter, request } } -it('admits repeated empty prompts after failures without extra user messages or duplicate requests', async () => { +it('records retry controls without user messages or duplicate requests', async () => { const { remote, agent, adapter, request } = await setup() agent.followup(createUserMessage({ content: [{ type: 'text', text: 'original task' }], source: { kind: 'user' } })) await agent.whenIdle() @@ -40,7 +40,20 @@ it('admits repeated empty prompts after failures without extra user messages or await agent.whenIdle() expect(adapter.requests).toHaveLength(3) const events = agent.session.snapshotEvents() - expect(events.filter(event => event.type === 'user/message' && event.data.source.kind === 'user')).toHaveLength(1) + const messages = events.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' ? [event.data] : []) + expect(messages).toHaveLength(1) + const retries = events.flatMap(event => event.type === 'agent/retry' ? [event.data] : []) + expect(retries).toEqual([ + { turn: 2, step: 1, inputId: expect.any(String), source: { kind: 'retry', rpcId: 'continue-1' } }, + { turn: 3, step: 1, inputId: expect.any(String), source: { kind: 'retry', rpcId: 'continue-2' } }, + ]) + const controls = events.flatMap(event => event.type === 'agent/inbox/spliced' ? event.data.inserted : []) + .filter(message => message.source.kind === 'retry') + expect(retries.map(retry => retry.inputId)).toEqual(controls.map(control => control.id)) + for (const call of adapter.requests) { + expect(call.messages.filter(message => message.role === 'user').map(message => message.content)) + .toEqual([[{ type: 'text', text: 'original task' }]]) + } expect(events.flatMap(event => event.type === 'turn/end' ? [event.data.reason.kind] : [])).toEqual(['error', 'error', 'completed']) expect(events.filter(event => event.type === 'turn/start')).toHaveLength(3) expect((await remote.prompt(request('after-completion'))).ok).toBe(false) diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index 8507f1dd99..5ec07de08b 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -33,14 +33,14 @@ en: 6a7ae99570c9c007 zh: 26e86063e97d82c7 /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/what-the-model-sees: - en: cfd91cdd0132732b - zh: 298d1ce1b973e7b0 + en: 5c81348e39a58220 + zh: d61ecf2b51b2d583 /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/token-effect: - en: a5bbf55d50568fa2 - zh: 6c43af211055db93 + en: 777a5c51789aba0c + zh: 9a710c83d1765fba /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/kv-cache-effect: - en: bbb4b8186c85f62d - zh: 222397b869a60df2 + en: 4889ad2beba4cc52 + zh: 778e8d7803481cd3 /deepseek-ai-dsh-client-ui-conversation/known-limitations-and-deferred-work: en: d76daa90b6d6f600 zh: 37a9f7d402b2552b diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 8955b71b31..9e06885423 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -193,15 +193,15 @@ The selector must be a pure function of the owner currency. Its non-null return #### What the model sees -The play action starts a new request from committed Session history. Its `content: []` input is a durable inbox command, not a model-visible user message; it adds no localized or hidden continuation instruction. Pending injected context follows ordinary admission. +The play action submits `content: []` through the existing Session prompt API as a retry control. Loop acceptance records `agent/retry` without adding a `user/message` or a chat bubble. Pending injected context follows ordinary admission; the action adds no localized or hidden continuation text. #### Token effect -The action adds no continuation text or empty user-message overhead. The request still incurs the ordinary cost of retained history, current prompt sections, tool declarations, and any newly admitted context. +The control adds no model tokens. The request still incurs the ordinary cost of retained history, current prompt sections, tool declarations, and any newly admitted context. #### KV Cache effect -The control input does not alter model history. Prefix reuse still depends on the current model, prompt, tools, injected context, and provider cache policy. +The control leaves model history unchanged. Prefix reuse depends on the current model, prompt, tools, injected context, and provider cache policy. ## Known Limitations and Deferred Work diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index 65c9feda93..152417372c 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -193,15 +193,15 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match #### 模型看到什么 -播放按钮基于已提交的 Session 历史发起新请求。`content: []` 作为持久 inbox 控制记录,不成为模型可见的用户消息;不会添加本地化或隐藏的继续指令。待注入上下文遵循普通准入流程。 +播放按钮通过现有 Session prompt API 将 `content: []` 提交为重试控制输入。Loop 接受后记录 `agent/retry`,不增加 `user/message` 或聊天气泡。待注入上下文遵循普通准入流程;该操作不添加本地化或隐藏的继续文本。 #### Token 影响 -该操作不增加继续文本或空用户消息的封装开销。请求仍承担保留历史、当前提示词、工具声明以及新准入上下文的正常输入成本。 +控制输入不增加模型 token。请求仍承担保留历史、当前提示词、工具声明以及新准入上下文的正常输入成本。 #### KV Cache 影响 -控制输入不改变模型历史。前缀复用仍取决于当前模型、提示词、工具、注入上下文及提供方缓存策略。 +控制输入不改变模型历史。前缀复用取决于当前模型、提示词、工具、注入上下文及提供方缓存策略。 ## 已知限制与暂缓事项 diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 5b88c245a3..2d3b453c44 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -45,14 +45,14 @@ en: 3b48a480036bd5df zh: 28cea2036b0dccee /deepseek-ai-dsh-agent-loop/understand-the-implementation/turn-and-step-flow: - en: ad92c751515520ff - zh: ca6b24db5ab4e471 + en: c9f75ebd07873e6a + zh: 863bc96addeb8eb9 /deepseek-ai-dsh-agent-loop/understand-the-implementation/failure-and-cancellation: en: eea56903c4ff82f5 zh: c0d5abb5767780ed /deepseek-ai-dsh-agent-loop/further-exploration: - en: bd0b5d56ebef7e23 - zh: 52911b993bc41afb + en: 7ac519f8e635acbc + zh: 975e514915dc6c9f /deepseek-ai-dsh-agent-loop/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index b18484ad82..021401061a 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -117,6 +117,8 @@ The loop is the production acquisition point for session write handles. When `ct ### Turn and step flow +An empty input with `source.kind: 'retry'` is a control: on first-attempt admission, the Loop records `agent/retry` with its inbox identity, turn, step, and source instead of a model-visible `user/message`. A retry control containing content fails with `INVALID_REQUEST`. Queue ownership, pre-step decisions, and turn settlement remain the same as for ordinary input; ordinary empty user messages are still committed. + The driver owns one agent for its lifetime and runs inside `ctx.agents.withInitiator(agent, ...)`. `AgentLoop` registers the standard `inbox` projection for its service lifetime, so cold reads work before any Agent exists and after all Agents unload. It also owns `modelSelection` and constructs an `AgentModel` for each Agent, with the public [Model API](../agent/README.md#model-selection). Its package-internal `ReactLoopInbox` uses that shared projection for structural commands and loop-only claims. At a turn boundary it opens the durable turn, then atomically claims pending next-step input plus one queued prompt; between steps it claims only next-step input. The driver assembles the prompt and tools and runs `agent/pre-step`; the fallback projects runtime context after listeners run. A rejected decision or empty first batch opens no step. On the first attempt after acceptance, `step/start` precedes the `agent/request` waterfall and `prepareCall()`; neither async phase sees the pending system prompt or accepted users committed to history, and cancellation during either commits neither. For every attempt, the loop then synchronously reconciles the rendered prompt against the surviving `system/message` nodes using the prepared call capability, refreshes registered runtime facts without reassembling the bound prompt or tools, appends the accepted `user/message` batch only on the first attempt with its owned context candidate replaced in place, and restores removed runtime context, logs the header and context as needed, and derives and freezes the request before streaming through that bound prepared call. Retries reuse the same rendered assembly without repeating assembly, `agent/pre-step`, or user admission. Reconciliation sees pre-step and retry compaction; a broken series consolidates the prompt at the head rather than appending an update after already-committed users. The request is `header.config`, `deriveMessages()`, and `header.tools`; it carries no `system` field. Each model attempt emits one process-local `start`, emits every `chunk` only after the matching durable assistant-frame settlement, and emits exactly one terminal `end`; final assembly or message-append failure settles it as `aborted`, while `committed` follows the durable `assistant/message`. Each successful model call appends one message anchor, and a cancelled stream appends an `interrupted: true` anchor with the delivered prefix so the next request contains what the user saw. Within a step, exclusive calls form barriers and parallel-safe calls use the bounded rolling pool; policy, durable results... (line truncated to 2000 chars) Prompt admission uses the actual `prepareCall()` result, not the preceding `request/context`. With no system node, even an empty prompt is appended (reserving node 0 without a wire message). On an incapable route, or at a new request series whose effective text differs, a non-empty rendering is consolidated at the first system node: each non-empty later system node receives a logged per-node empty replacement, then the head is rewritten if needed. Dormant empty tails need no replacement and do not determine the effective text. A series start whose latest surviving node already holds the rendering produces no system event, so the request keeps the prefix it had. On a continuing `in-history` series, an unchanged effective prompt produces no event and a non-empty change appends. An empty rendering clears every non-empty later system node through a logged per-node empty replacement, then empties the head if needed, regardless of route or series state. No older instructions remain model-visible. An empty head with no active later system node means no prompt; repeated clears and resume leave it empty. A restored non-empty prompt follows the same route/series rule: a continuing capable route may append it, while an incapable route or a new series whose effective text differs refills the head. A step starts a series when the pre-step decision declares `startsRequestSeries` or `session.surface.contentGeneration` changed since attachment or the last request (a replacement or image-offload decision). Visible tool-schema changes force prompt consolidation only when the prepared route lacks `toolUpdate`. Routes supporting both tool and system-prompt updates can append a changed prompt and activate added tools without rewriting earlier messages. If another admission condition replaces a system node, the request starts a new series with current tool declarations. Resume and a provider or model swap alone continue the series; the prepared route still governs admission. Per-node empty replacements preserve intervening history without a surface delete operation. @@ -146,8 +148,6 @@ The package-level contract is enough for most consumers; read these when you nee ----- -Empty ordinary input remains an identified inbox command, so it can wake a new turn while adding no `user/message` to the model surface. It passes normal pre-step admission; a rejected or removed input does not authorize a model request. Turn and step endings retain their normal success, failure, and cancellation handling. - ## Model Experience diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index 54395d27dc..1937358731 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -117,6 +117,8 @@ loop 在发布工厂前等待 inbox 与轮次边界注册完成,并将句柄 ### 轮次与步骤流程 +带 `source.kind: 'retry'` 的空输入属于控制输入:首次尝试准入时,Loop 记录带 inbox 身份、轮次、步骤和来源的 `agent/retry`,而不写入模型可见的 `user/message`。包含内容的重试控制输入以 `INVALID_REQUEST` 失败。队列所有权、pre-step 决策和轮次结算与普通输入一致;普通空用户消息仍会提交。 + 驱动器在其整个生命周期内拥有一个 agent,并在 `ctx.agents.withInitiator(agent, ...)` 内运行。`AgentLoop` 在服务生命周期内注册标准 `inbox` 投影,因此冷读取在没有 Agent 和所有 Agent 卸载后都可用。它还拥有 `modelSelection`,并为每个 Agent 构建 `AgentModel`,实现公开的 [Model API](../agent/README.zh.md#model-selection)。其包内部 `ReactLoopInbox` 使用该共享投影执行结构化命令与仅供 loop 使用的领取操作。在轮次边界,它先打开持久轮次,再原子领取待处理的 next-step 输入与一条排队提示词;在步骤之间则只领取 next-step 输入。驱动器组装提示词与工具,并运行 `agent/pre-step`;fallback 在监听器执行后投影运行时上下文。被拒绝的决定或空的首批输入不打开步骤。接纳后的首次尝试先记录 `step/start`,再运行 `agent/request` waterfall 与 `prepareCall()`;这两个异步阶段都看不到待提交的系统提示词与已接纳用户消息进入历史,在任一阶段取消都不会提交这两者。每次尝试时,循环随后依据已准备调用的能力,同步将渲染后的提示词与存活的 `system/message` 节点协调一致、刷新已注册运行时事实而不重新组装绑定的提示词或工具、仅在首次尝试追加已接纳的 `user/message` 批次并原位替换循环自有上下文候选、恢复被移除的运行时上下文、按需记录 header 与 context,再派生并冻结请求,通过该绑定的已准备调用发起流式请求。重试复用同一份已渲染组装结果,不重复组装、`agent/pre-step` 或用户消息准入。协调过程可见 pre-step 与重试中的压缩;序列中断时将提示词归并到头部,而非在已提交用户消息之后追加更新。请求由 `header.config`、`deriveMessages()` 与 `header.tools` 构成,不携带 `system` 字段。每次模型尝试会发出一个进程本地 `start`,仅在匹配的持久 assistant-frame 结算之后发出各个 `chunk`,并恰好发出一个终态 `end`;最终组装或消息追加失败时以 `aborted` 结算,`committed` 则出现在持久 `assistant/message` 之后。每次成功的模型调用都恰好追加一个 message 锚点,被取消的流则追加带 `interrupted: true` 的锚点并携带已交付前缀,使下一次请求包含用户看到的内容。在步骤内,独占调用形成屏障,并行安全调用使用有界滚动池;策略、持久结果与结果上下文保持模型顺序。 提示词准入依据实际的 `prepareCall()` 结果,而非先前的 `request/context`。没有系统节点时,即使提示词为空也追加(预留第 0 号节点,但不产生协议消息)。在不具备能力的路由上,或有效文本不同的新请求序列开始时,非空渲染文本归并到首个系统节点:每个非空的后续系统节点分别收到有日志记录的空内容替换,随后按需重写头节点。未生效的空尾节点无需替换,也不决定有效文本。新序列开始时若最新存活节点已经持有该渲染文本,则不产生系统事件,请求保留原有前缀。延续中的 `in-history` 序列在有效提示词不变时不产生事件,非空变更则追加。无论路由或序列状态如何,空渲染文本都会通过有日志记录的逐节点空内容替换清除每个非空的后续系统节点,再按需清空头节点。模型不会继续看到旧指令。空头节点且没有生效的后续系统节点表示没有提示词;重复清除与恢复会话都保持为空。重新提供的非空提示词遵循同一路由/序列规则:延续中的具备能力路由可以追加它,不具备能力的路由或有效文本不同的新序列则重新填充头节点。以下情况开启序列:pre-step 决定声明 `startsRequestSeries`,或 `session.surface.contentGeneration` 自附接或上次请求以来发生变化(替换或图片省略决定)。仅当已准备路由未声明 `toolUpdate` 时,可见工具 schema 变化才强制归并提示词。同时支持工具和系统提示词更新的路由可以追加变更后的提示词并激活新增工具,无需改写此前消息。如果其他准入条件替换系统节点,请求会以当前工具声明开启新序列。恢复与单纯的提供方或模型切换都延续序列;准入仍由已准备的路由决定。逐节点的空内容替换保留其间历史,无需 surface 删除操作。 @@ -146,8 +148,6 @@ loop 在发布工厂前等待 inbox 与轮次边界注册完成,并将句柄 ----- -空的普通输入仍是带身份的 inbox 控制记录,可以唤醒新轮次,但不向模型消息面添加 `user/message`。它经过正常 pre-step 准入;被拒绝或移除的输入不授权模型请求。轮次和步骤结束沿用正常的成功、失败与取消处理。 - ## 模型体验 diff --git a/packages/core/agent-loop/src/agent.ts b/packages/core/agent-loop/src/agent.ts index 619cca96ba..8150c334cc 100644 --- a/packages/core/agent-loop/src/agent.ts +++ b/packages/core/agent-loop/src/agent.ts @@ -455,12 +455,15 @@ export class ReactLoopAgent implements Agent { let contextAdmitted = false if (firstAttempt) { for (const message of decision.messages) { - if (message.source.kind === 'runtime-context') { + if (message.source.kind === 'retry') { + if (message.content.length > 0) throw new LlmError('Retry control input must be empty', 'INVALID_REQUEST') + this.session.append('agent/retry', { turn, step, inputId: message.id, source: message.source }) + } else if (message.source.kind === 'runtime-context') { if (context !== undefined && !contextAdmitted) { this.session.append('user/message', context, { surfaceOp: 'append' }) contextAdmitted = true } - } else if (message.source.kind !== 'user' || message.content.length > 0) { + } else { this.session.append('user/message', message, { surfaceOp: 'append' }) } } diff --git a/packages/core/agent-loop/tests/request-error.spec.ts b/packages/core/agent-loop/tests/request-error.spec.ts index 16c07fcf4e..ed95db70b3 100644 --- a/packages/core/agent-loop/tests/request-error.spec.ts +++ b/packages/core/agent-loop/tests/request-error.spec.ts @@ -32,15 +32,16 @@ function fail(message: string, code: string): () => never { } describe('agent/request-error', () => { - it('continues repeated failures through empty followups with balanced turns and one admitted user message', async () => { + it('records repeated retry controls with balanced turns and no additional model messages', async () => { const adapter = new MockAdapter([fail('busy', 'SERVER'), fail('still busy', 'SERVER'), textResponse('done')]) const ctx = await harness(adapter) onTestFinished(() => ctx.fiber.dispose()) - const agent = await ctx.agentLoop.create(SessionId('empty-continuation'), { provider: 'mock', model: 'mock' }) + const agent = await ctx.agentLoop.create(SessionId('retry-controls'), { provider: 'mock', model: 'mock' }) agent.followup(createUserMessage({ content: [{ type: 'text', text: 'original task' }], source: { kind: 'user' } })) await agent.whenIdle() - for (let index = 0; index < 2; index++) { - agent.followup(createUserMessage({ content: [], source: { kind: 'user' } })) + const controls = Array.from({ length: 2 }, () => createUserMessage({ content: [], source: { kind: 'retry' } })) + for (const control of controls) { + agent.followup(control) await agent.whenIdle() } expect(adapter.requests).toHaveLength(3) @@ -50,6 +51,9 @@ describe('agent/request-error', () => { } const events = agent.session.snapshotEvents() expect(events.filter(event => event.type === 'user/message')).toHaveLength(1) + expect(events.flatMap(event => event.type === 'agent/retry' ? [event.data] : [])).toEqual( + controls.map((control, index) => ({ turn: index + 2, step: 1, inputId: control.id, source: { kind: 'retry' } })), + ) expect(events.flatMap(event => event.type === 'turn/start' || event.type === 'turn/end' ? [`${event.type}:${event.data.turn}`] : [])) .toEqual(['turn/start:1', 'turn/end:1', 'turn/start:2', 'turn/end:2', 'turn/start:3', 'turn/end:3']) @@ -57,6 +61,26 @@ describe('agent/request-error', () => { .toEqual(['error', 'error', 'completed']) }) + it('preserves ordinary empty user input and rejects retry controls containing content', async () => { + const adapter = new MockAdapter([textResponse('done')]) + const ctx = await harness(adapter) + onTestFinished(() => ctx.fiber.dispose()) + const agent = await ctx.agentLoop.create(SessionId('retry-validation'), { provider: 'mock', model: 'mock' }) + const empty = createUserMessage({ content: [], source: { kind: 'user' } }) + agent.followup(empty) + await agent.whenIdle() + expect(adapter.requests[0]?.messages.filter(message => message.role === 'user')).toEqual([empty]) + agent.followup(createUserMessage({ content: [{ type: 'text', text: 'must not disappear' }], source: { kind: 'retry' } })) + await agent.whenIdle() + expect(adapter.requests).toHaveLength(1) + const events = agent.session.snapshotEvents() + expect(events.filter(event => event.type === 'agent/retry')).toHaveLength(0) + expect(events.filter(event => event.type === 'user/message')).toMatchObject([{ data: empty }]) + expect(events.filter(event => event.type === 'turn/end').at(-1)).toMatchObject({ + data: { turn: 2, reason: { kind: 'error', error: { code: 'INVALID_REQUEST' } } }, + }) + }) + it('does not offer middleware failures to request recovery', async () => { const adapter = new MockAdapter([textResponse('unused')]) const ctx = await harness(adapter) diff --git a/packages/core/agent/README.i18n.yaml b/packages/core/agent/README.i18n.yaml index a7bdc11ed0..44fd486abc 100644 --- a/packages/core/agent/README.i18n.yaml +++ b/packages/core/agent/README.i18n.yaml @@ -24,8 +24,8 @@ en: 85b0ff72a187110f zh: 3f61c1568d27b8bc /deepseek-ai-dsh-agent/use-this-package/drive-an-agent-s-conversation: - en: 71e258897e2e7d73 - zh: abf0991012faa17e + en: 09f6d66dddba9ae7 + zh: f3cd42db2375b705 /deepseek-ai-dsh-agent/use-this-package/scope-registrations-to-one-agent: en: 2594a1e02adea801 zh: cae8860369728efb @@ -60,8 +60,8 @@ en: e48eae6c7de88cf8 zh: 1f5b37bdb0e53788 /deepseek-ai-dsh-agent/further-exploration: - en: faf384f89b18b671 - zh: 42400f79f5ab84b0 + en: 4ef57cfafa344581 + zh: 73575876bfdc7a18 /deepseek-ai-dsh-agent/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index aba28e301e..441d453f7f 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -55,7 +55,7 @@ For scoped switch notices, call `installModelSelection(agentCtx)` from `dsh-agen ### Drive an agent's conversation -The handle's methods route identified user-role messages into the agent's inbox. `followup()` queues an ordinary next-turn prompt and wakes the driver; `steer()` submits next-step input and wakes it; `inject()` adds model-facing context without waking the driver, so it lands in the next admitted step. `cancel(cause)` aborts the active activity and, unless `keepInbox` is set, clears pending work; `whenIdle()` resolves after the whole agent reaches quiescence. +The handle's methods route identified user-role messages into the agent's inbox. `followup()` queues an ordinary next-turn prompt and wakes the driver; `steer()` submits next-step input and wakes it; `inject()` adds model-facing context without waking the driver, so it lands in the next admitted step. An empty input with `source.kind: 'retry'` requests another model call through the same inbox; the shipped Loop records its acceptance as `agent/retry` without adding model-visible content. Ordinary empty user inputs retain ordinary message admission. `cancel(cause)` aborts the active activity and, unless `keepInbox` is set, clears pending work; `whenIdle()` resolves after the whole agent reaches quiescence. ```text handle.agent.followup({ @@ -150,8 +150,6 @@ The package-level contract is enough for most consumers; read these when you nee ----- -An ordinary `followup` with `content: []` is an explicit request to start a new turn from committed history. It retains the identified inbox receipt and normal lifecycle without creating a model-visible user message. A pre-step rejection or a decision rewritten to an empty batch still prevents the request. - ## Model Experience diff --git a/packages/core/agent/README.zh.md b/packages/core/agent/README.zh.md index c317176430..66367a5f6e 100644 --- a/packages/core/agent/README.zh.md +++ b/packages/core/agent/README.zh.md @@ -55,7 +55,7 @@ await handle.dispose() // stops the loop, unregisters, removes the session, un ### 驱动 agent 的对话 -句柄的方法把带标识的 user 角色消息路由进 agent 的收件箱。`followup()` 排队一条普通的下一个轮次提示词并唤醒驱动器;`steer()` 提交下一步输入并唤醒它;`inject()` 添加面向模型的上下文但不唤醒驱动器,因此它落在下一个被接纳的步骤中。`cancel(cause)` 中止当前活动,并在未设置 `keepInbox` 时清除待处理工作;`whenIdle()` 会在整个 agent 达到完全停稳后完成。 +句柄的方法把带标识的 user 角色消息路由进 agent 的收件箱。`followup()` 排队一条普通的下一个轮次提示词并唤醒驱动器;`steer()` 提交下一步输入并唤醒它;`inject()` 添加面向模型的上下文但不唤醒驱动器,因此它落在下一个被接纳的步骤中。带 `source.kind: 'retry'` 的空输入通过同一 inbox 请求再次调用模型;内置 Loop 将接受记录为 `agent/retry`,不增加模型可见内容。普通空用户输入仍遵循普通消息准入。`cancel(cause)` 中止当前活动,并在未设置 `keepInbox` 时清除待处理工作;`whenIdle()` 会在整个 agent 达到完全停稳后完成。 ```text handle.agent.followup({ @@ -150,8 +150,6 @@ await handle.agent.whenIdle() ----- -普通 `followup` 的 `content: []` 明确请求基于已提交历史启动新轮次。它保留带身份的 inbox 回执和正常生命周期,不生成模型可见的用户消息。pre-step 拒绝或改写为空批次的决策仍会阻止请求。 - ## 模型体验 diff --git a/packages/core/agent/src/runtime-types.ts b/packages/core/agent/src/runtime-types.ts index 58d883721c..5ae5c35aff 100644 --- a/packages/core/agent/src/runtime-types.ts +++ b/packages/core/agent/src/runtime-types.ts @@ -232,7 +232,8 @@ declare module './types.ts' { /** * Route identified input to an inbox boundary and optionally wake the driver. - * Empty ordinary input drives work without becoming a model-visible message. + * A retry-source input must be empty; admission records `agent/retry` + * without adding a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -245,9 +246,8 @@ declare module './types.ts' { send(message: UserMessage, target: InboxTarget, wakeup: boolean): void /** - * Queue an ordinary follow-up turn and wake the driver. The item becomes the - * sole ordinary message of its own turn. An empty ordinary input starts a - * turn from committed history without appending a model-visible user message. + * Queue a follow-up turn and wake the driver. The item is its sole ordinary + * prompt, or an empty retry-source control with no model-visible message. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/packages/core/agent/src/types.ts b/packages/core/agent/src/types.ts index 06d2c88ab1..defbd94bec 100644 --- a/packages/core/agent/src/types.ts +++ b/packages/core/agent/src/types.ts @@ -4,8 +4,8 @@ * @module @deepseek-ai/dsh-agent/types */ -import type { ReasoningEffortId } from '@deepseek-ai/dsh-llm/brand' -import type { UserMessage } from '@deepseek-ai/dsh-llm/types' +import type { MessageId, ReasoningEffortId } from '@deepseek-ai/dsh-llm/brand' +import type { MessageSource, UserMessage } from '@deepseek-ai/dsh-llm/types' // Type-only: the Workspace registry's archive-admission family map this registry merges `turn` into. import type {} from '@deepseek-ai/dsh-workspace/types' import type { OptionalSessionSeq, SessionId, SessionSeq } from '@deepseek-ai/dsh-session/types' @@ -18,6 +18,13 @@ export interface Agent { readonly id: SessionId } +declare module '@deepseek-ai/dsh-llm' { + interface MessageSourceMap { + /** Empty control input requesting a model call without adding user text. */ + retry: { kind: 'retry' } + } +} + declare module '@deepseek-ai/dsh-workspace/types' { interface SessionActivityKindMap { /** The session's own Agent is inside a turn, including one waiting for an approval or an answer. */ @@ -102,6 +109,13 @@ declare module '@deepseek-ai/dsh-session-projection/types' { declare module '@deepseek-ai/dsh-session/types' { interface SessionEventMap { + /** An admitted retry control, correlated with its durable inbox input; never enters model history. */ + 'agent/retry': { + turn: number + step: number + inputId: MessageId + source: Extract + } /** * One normalized mutation of an agent's durable pending-message lists. * The session-projection registry applies the committed event before diff --git a/packages/core/session/src/known-event-types.ts b/packages/core/session/src/known-event-types.ts index a0f5c17795..71a61005df 100644 --- a/packages/core/session/src/known-event-types.ts +++ b/packages/core/session/src/known-event-types.ts @@ -22,6 +22,7 @@ export const KNOWN_SESSION_EVENT_TYPES: ReadonlySet = new Set([ 'agent-preset/selected', 'agent/inbox/spliced', + 'agent/retry', 'approval/asked', 'approval/decided', 'approval/policy', diff --git a/packages/sdk/client/src/client.ts b/packages/sdk/client/src/client.ts index 4bc8140c02..56f7401b7a 100644 --- a/packages/sdk/client/src/client.ts +++ b/packages/sdk/client/src/client.ts @@ -344,7 +344,7 @@ export class HarnessClient { /** * Queue one prompt and return its durable inbox identity. * @param sessionId - target session; an unknown id creates it. - * @param contentBlocks - the user message, sent verbatim. + * @param contentBlocks - user content, or an empty array to retry without adding a model-visible message. * @returns the queued message id. */ async prompt(sessionId: string, contentBlocks: SdkPromptContentBlock[]): Promise { diff --git a/packages/sdk/protocol/README.i18n.yaml b/packages/sdk/protocol/README.i18n.yaml index 2aed1dc438..c29b0abca2 100644 --- a/packages/sdk/protocol/README.i18n.yaml +++ b/packages/sdk/protocol/README.i18n.yaml @@ -27,8 +27,8 @@ en: 9dcd999ae8c66b71 zh: 174c5fe0cbb0a9e4 /deepseek-ai-dsh-sdk-protocol/use-this-package/payload-semantics: - en: 3cf5d4ffbaf37f69 - zh: 23c4450e31c91d67 + en: f5f0ff9090bdd076 + zh: 83e43639a892879a /deepseek-ai-dsh-sdk-protocol/understand-the-implementation: en: a11b136d8296deda zh: 85081da5be53c9e1 diff --git a/packages/sdk/protocol/README.md b/packages/sdk/protocol/README.md index 93a34454af..2240c78cf7 100644 --- a/packages/sdk/protocol/README.md +++ b/packages/sdk/protocol/README.md @@ -69,7 +69,7 @@ An answering host sets `initialize.capabilities.userQuestions: true`. The runtim ### Payload semantics -`SessionPromptResult.messageId` identifies the queued user message; it does not identify a later assistant message, turn ending, or prompt result. `SdkPromptContentBlock` accepts ordinary durable content plus `SdkEncodedImageBlock { type: "image", data, mimeType }`; the server converts encoded images to durable references before enqueue. `InitializeParams.reasoningEffort` is an optional non-empty adapter-owned identifier for the selected provider/model route; omission preserves that model's default. `InitializeParams.maxTokens` is an optional positive safe integer that caps each conversation-model output for SDK-created agents and their in-process descendants; omission lets the selected adapter's exact-model default apply. The server resolves the exact route during initialization and rejects `session/prompt` until that handshake succeeds, so a missing adapter, unavailable model, or unsupported effort cannot fall back to constructor defaults. `SubagentFinishedNotification.lastAssistantMessage` carries the child's last non-empty assistant message, or its accumulated assistant text when no such message exists; the field is absent when the child produced neither. `serverInfo.name` stays the wire-stable `deepseek-harness-sdk-runtime`. Notification payloads depend on `SessionEvent` (`dsh-session`), `ContentBlock` (`dsh-llm`), and `SubagentStopReason` (`dsh-subagent`), so the session vocabulary is part of the wire contract. +`SessionPromptResult.messageId` identifies the queued prompt or retry control; it does not identify a later assistant message, turn ending, or prompt result. An empty `contentBlocks` array queues an empty retry-source control; admission records `agent/retry` rather than `user/message`. `SdkPromptContentBlock` accepts ordinary durable content plus `SdkEncodedImageBlock { type: "image", data, mimeType }`; the server converts encoded images to durable references before enqueue. `InitializeParams.reasoningEffort` is an optional non-empty adapter-owned identifier for the selected provider/model route; omission preserves that model's default. `InitializeParams.maxTokens` is an optional positive safe integer that caps each conversation-model output for SDK-created agents and their in-process descendants; omission lets the selected adapter's exact-model default apply. The server resolves the exact route during initialization and rejects `session/prompt` until that handshake succeeds, so a missing adapter, unavailable model, or unsupported effort cannot fall back to constructor defaults. `SubagentFinishedNotification.lastAssistantMessage` carries the child's last non-empty assistant message, or its accumulated assistant text when no such message exists; the field is absent when the child produced neither. `serverInfo.name` stays the wire-stable `deepseek-harness-sdk-runtime`. Notification payloads depend on `SessionEvent` (`dsh-session`), `ContentBlock` (`dsh-llm`), and `SubagentStopReason` (`dsh-subagent`), so the session vocabulary is part of the wire contract. ----- diff --git a/packages/sdk/protocol/README.zh.md b/packages/sdk/protocol/README.zh.md index f81f58c223..f5339e55c9 100644 --- a/packages/sdk/protocol/README.zh.md +++ b/packages/sdk/protocol/README.zh.md @@ -69,7 +69,7 @@ kind: "package-library" ### 载荷语义 -`SessionPromptResult.messageId` 标识已排队的用户消息;它不标识后续的助手消息、轮次结束或提示词结果。`SdkPromptContentBlock` 接受普通持久内容以及 `SdkEncodedImageBlock { type: "image", data, mimeType }`;服务器在入队前把编码图像转换为持久引用。`InitializeParams.reasoningEffort` 是所选提供方/模型路由可选的非空适配器自有标识符;省略时保留该模型的默认值。`InitializeParams.maxTokens` 是可选的正安全整数,用于限制 SDK 创建的 agent 及其进程内后代的每次对话模型输出;省略时应用所选适配器的确切模型默认值。服务器会在初始化期间解析确切路由,并在握手成功前拒绝 `session/prompt`,因此缺少适配器、模型不可用或推理强度不受支持时,不会回退到构造期默认值。`SubagentFinishedNotification.lastAssistantMessage` 携带子 agent 最后一条非空 assistant 消息;若不存在这类消息,则携带其累积的 assistant 文本;子 agent 两种输出均未产生时,该字段缺省。`serverInfo.name` 的协议值固定为 `deepseek-harness-sdk-runtime`。通知载荷依赖 `SessionEvent`(`dsh-session`)、`ContentBlock`(`dsh-llm`)与 `SubagentStopReason`(`dsh-subagent`),因此会话词汇是协议格式约定的一部分。 +`SessionPromptResult.messageId` 标识已排队的提示词或重试控制输入;它不标识后续的助手消息、轮次结束或提示词结果。空 `contentBlocks` 数组将空的 retry 来源控制输入排入队列;准入记录 `agent/retry`,而非 `user/message`。`SdkPromptContentBlock` 接受普通持久内容以及 `SdkEncodedImageBlock { type: "image", data, mimeType }`;服务器在入队前把编码图像转换为持久引用。`InitializeParams.reasoningEffort` 是所选提供方/模型路由可选的非空适配器自有标识符;省略时保留该模型的默认值。`InitializeParams.maxTokens` 是可选的正安全整数,用于限制 SDK 创建的 agent 及其进程内后代的每次对话模型输出;省略时应用所选适配器的确切模型默认值。服务器会在初始化期间解析确切路由,并在握手成功前拒绝 `session/prompt`,因此缺少适配器、模型不可用或推理强度不受支持时,不会回退到构造期默认值。`SubagentFinishedNotification.lastAssistantMessage` 携带子 agent 最后一条非空 assistant 消息;若不存在这类消息,则携带其累积的 assistant 文本;子 agent 两种输出均未产生时,该字段缺省。`serverInfo.name` 的协议值固定为 `deepseek-harness-sdk-runtime`。通知载荷依赖 `SessionEvent`(`dsh-session`)、`ContentBlock`(`dsh-llm`)与 `SubagentStopReason`(`dsh-subagent`),因此会话词汇是协议格式约定的一部分。 ----- diff --git a/packages/sdk/protocol/src/types.ts b/packages/sdk/protocol/src/types.ts index cfa746cac2..dd3c86a930 100644 --- a/packages/sdk/protocol/src/types.ts +++ b/packages/sdk/protocol/src/types.ts @@ -67,7 +67,7 @@ export interface InitializeResult { export interface SessionPromptParams { /** The SDK-side session id; an unknown id lazily creates the agent+session pair. */ sessionId: string - /** The prompt content blocks, sent verbatim as the user message. */ + /** Prompt content; an empty array requests a retry control without a model-visible user message. */ contentBlocks: SdkPromptContentBlock[] } @@ -85,7 +85,7 @@ export type SdkPromptContentBlock = ContentBlock | SdkEncodedImageBlock /** Durable enqueue receipt for one prompt. */ export interface SessionPromptResult { - /** Identity of the queued user message. */ + /** Identity of the queued prompt or retry control. */ messageId: string } diff --git a/packages/sdk/server/README.i18n.yaml b/packages/sdk/server/README.i18n.yaml index b56838412f..7ad56c42a9 100644 --- a/packages/sdk/server/README.i18n.yaml +++ b/packages/sdk/server/README.i18n.yaml @@ -27,8 +27,8 @@ en: 784b68b5a498f2d5 zh: 2c3c5a1490845179 /deepseek-ai-dsh-sdk-jsonrpc-server/use-this-package/what-sdk-clients-can-do: - en: 3e657fa4c9586454 - zh: 26d572f91e124cc5 + en: 0936bb4ac5f59851 + zh: fb91df93242fc80e /deepseek-ai-dsh-sdk-jsonrpc-server/use-this-package/native-user-questions: en: 5100c311c8476f93 zh: 60d4bc3165731246 @@ -60,8 +60,8 @@ en: 04d1b73cd8e06dfe zh: cd67fbbfdf39f776 /deepseek-ai-dsh-sdk-jsonrpc-server/model-experience/sdk-user-message/what-the-model-sees: - en: 2b5a80ec4b4df11e - zh: e459fe77cc14d9b8 + en: 57ed296ea4f0ec42 + zh: a0bf3746ce9bdf01 /deepseek-ai-dsh-sdk-jsonrpc-server/model-experience/sdk-user-message/token-effect: en: 372ed851986b76d2 zh: 075b94a565275368 diff --git a/packages/sdk/server/README.md b/packages/sdk/server/README.md index e9007e593c..f3dcb00c29 100644 --- a/packages/sdk/server/README.md +++ b/packages/sdk/server/README.md @@ -53,7 +53,7 @@ Stdout carries only JSON-RPC frames, so clients can parse every byte; diagnostic ### What SDK clients can do -`initialize` is the runtime-readiness boundary: when the server is mounted by a Loader composition, it waits for the current plugin tree to settle before replying, so async sibling capabilities such as initial MCP tool discovery are visible to the first prompt. The handshake returns the wire-stable identity `deepseek-harness-sdk-runtime`. The server validates the provider/model route and optional non-empty `reasoningEffort` through the selected adapter before it stores them; omission stores no effort, so the model retains its own default. An optional positive `maxTokens` becomes the request output cap of each SDK-created agent and its in-process descendants, while omission applies the selected adapter or provider route default. JSON-RPC requests may dispatch concurrently, so `session/prompt` rejects until one `initialize` has completed successfully; clients must await the handshake before sending prompts. An accepted prompt queues one identified user message and immediately returns `{ messageId }`; the server then streams every durable fact as `session.event` and every whole-agent lifecycle transition as `session.status`. It does not assign an assistant message or `turn/end` to a prompt, and independent requests may enqueue more work on the same session. Persistence roots and persona come from the surrounding composition. +`initialize` is the runtime-readiness boundary: when the server is mounted by a Loader composition, it waits for the current plugin tree to settle before replying, so async sibling capabilities such as initial MCP tool discovery are visible to the first prompt. The handshake returns the wire-stable identity `deepseek-harness-sdk-runtime`. The server validates the provider/model route and optional non-empty `reasoningEffort` through the selected adapter before it stores them; omission stores no effort, so the model retains its own default. An optional positive `maxTokens` becomes the request output cap of each SDK-created agent and its in-process descendants, while omission applies the selected adapter or provider route default. JSON-RPC requests may dispatch concurrently, so `session/prompt` rejects until one `initialize` has completed successfully; clients must await the handshake before sending prompts. An accepted prompt queues one identified input and immediately returns `{ messageId }`; the server then streams every durable fact as `session.event` and every whole-agent lifecycle transition as `session.status`. It does not assign an assistant message or `turn/end` to a prompt, and independent requests may enqueue more work on the same session. Persistence roots and persona come from the surrounding composition. `session/wait` observes an existing SDK-owned session through managed descendant completion and any resulting root turns. It rejects unknown or retired roots, never creates a session, and responds after the root stays idle across the descendant check. Idle children with parked inbox messages remain resident but do not delay this response; later waking input can resume them. Session notifications emitted before completion precede the response on the stdio transport. A live Agent failure remains associated with that exact session until a later committed `turn/end` accounts for the outcome; otherwise the wait rejects, including when the failure preceded the wait request. Errors already represented by a terminal remain in Session events, and later successful activity clears an earlier live failure. @@ -120,7 +120,7 @@ Read these pages when the plugin contract is not enough. They move from the wire #### What the model sees -For each accepted `session/prompt`, text and durable content references enter one user message verbatim. Inline `SdkEncodedImageBlock` values are validated and committed through the composition's attachment store first, so the session log retains content-addressed image references rather than base64 bytes. This package adds no system-prompt prose or tool schema; those come from the other plugins in the composition. +For each non-empty accepted `session/prompt`, text and durable content references enter one user message verbatim. An empty content array queues a retry-source control; Loop admission records `agent/retry` without adding model-visible content. Inline `SdkEncodedImageBlock` values are validated and committed through the composition's attachment store first, so the session log retains content-addressed image references rather than base64 bytes. This package adds no system-prompt prose or tool schema; those come from the other plugins in the composition. #### Token effect diff --git a/packages/sdk/server/README.zh.md b/packages/sdk/server/README.zh.md index 56ae2f8bdf..4d56722a7f 100644 --- a/packages/sdk/server/README.zh.md +++ b/packages/sdk/server/README.zh.md @@ -53,7 +53,7 @@ Stdout 只承载 JSON-RPC 帧,客户端可以逐字节解析;诊断信息应 ### SDK 客户端可以做什么 -`initialize` 是运行时就绪边界:服务器由 Loader 组合挂载时,会等待当前插件树完成所有加载任务后再响应,因此首次提示词能够看到 MCP 初始工具发现等异步同级能力。握手返回协议稳定标识 `deepseek-harness-sdk-runtime`。服务器会通过所选适配器校验提供方/模型路由与可选的非空 `reasoningEffort`,再保存这些值;省略时不会保存推理强度,因此模型保留自身默认值。可选的正整数 `maxTokens` 会成为每个 SDK 创建的 agent 及其进程内后代的请求输出上限,省略时则应用所选适配器或提供方路由的默认值。JSON-RPC 请求可能并发分派,因此在一次 `initialize` 成功完成之前,`session/prompt` 会拒绝;客户端必须等待握手完成后再发送提示词。已接受的提示词会把一条带标识的用户消息排入队列,并立即返回 `{ messageId }`;服务器随后把每个持久事实作为 `session.event`、把整个 agent 生命周期的每次状态转换作为 `session.status` 流式发出。它不会把某条助手消息或 `turn/end` 归属于某个提示词,同一会话上的独立请求可以继续排入更多工作。持久化根目录与 persona 来自外围组合。 +`initialize` 是运行时就绪边界:服务器由 Loader 组合挂载时,会等待当前插件树完成所有加载任务后再响应,因此首次提示词能够看到 MCP 初始工具发现等异步同级能力。握手返回协议稳定标识 `deepseek-harness-sdk-runtime`。服务器会通过所选适配器校验提供方/模型路由与可选的非空 `reasoningEffort`,再保存这些值;省略时不会保存推理强度,因此模型保留自身默认值。可选的正整数 `maxTokens` 会成为每个 SDK 创建的 agent 及其进程内后代的请求输出上限,省略时则应用所选适配器或提供方路由的默认值。JSON-RPC 请求可能并发分派,因此在一次 `initialize` 成功完成之前,`session/prompt` 会拒绝;客户端必须等待握手完成后再发送提示词。已接受的提示词会把一条带标识的输入排入队列,并立即返回 `{ messageId }`;服务器随后把每个持久事实作为 `session.event`、把整个 agent 生命周期的每次状态转换作为 `session.status` 流式发出。它不会把某条助手消息或 `turn/end` 归属于某个提示词,同一会话上的独立请求可以继续排入更多工作。持久化根目录与 persona 来自外围组合。 `session/wait` 观察已有 SDK 会话,直到受管理后代及其触发的根 Agent 后续轮次完成。它拒绝未知或已释放的根 Agent,不会创建会话,并在后代检查期间根 Agent 持续空闲后响应。收件箱消息处于停放状态的空闲子级继续驻留,但不延迟此响应;后续唤醒输入可使其继续运行。完成前发出的会话通知在 stdio 传输上先于响应到达。Agent 的运行时错误会保留在对应的确切会话中,直到后续提交的 `turn/end` 记录结果;否则等待请求会失败,即使错误发生在等待请求之前。终止事件已经记录的错误仍通过会话事件表达,后续成功的活动会清除较早的运行时错误。 @@ -120,7 +120,7 @@ Stdout 只承载 JSON-RPC 帧,客户端可以逐字节解析;诊断信息应 #### 模型看到什么 -对于每个已接受的 `session/prompt`,文本和持久内容引用会原样进入一条用户消息。内联 `SdkEncodedImageBlock` 会先通过组合中的附件存储完成校验与提交,因此会话日志保留内容寻址的图片引用而不是 base64 字节。此包不会添加系统提示词文本或工具 schema;这些内容来自组合中的其他插件。 +对于每个非空且已接受的 `session/prompt`,文本和持久内容引用会原样进入一条用户消息。空内容数组将 retry 来源控制输入排入队列;Loop 准入记录 `agent/retry`,不增加模型可见内容。内联 `SdkEncodedImageBlock` 会先通过组合中的附件存储完成校验与提交,因此会话日志保留内容寻址的图片引用而不是 base64 字节。此包不会添加系统提示词文本或工具 schema;这些内容来自组合中的其他插件。 #### Token 影响 diff --git a/packages/sdk/server/src/server.ts b/packages/sdk/server/src/server.ts index 0dceccc59c..8bb25cb1db 100644 --- a/packages/sdk/server/src/server.ts +++ b/packages/sdk/server/src/server.ts @@ -247,7 +247,7 @@ export class HarnessSdkJsonRpcServer { /** * Queue one identified prompt without assigning later activity to it. - * @param params - target session and user content. + * @param params - target session and user content; an empty array queues a retry control. * @returns the durable message identity. */ async prompt(params: SessionPromptParams): Promise { @@ -263,7 +263,7 @@ export class HarnessSdkJsonRpcServer { this.assertLiveAgent(rec, params.sessionId) const message = createUserMessage({ content, - source: { kind: 'user' }, + source: { kind: content.length === 0 ? 'retry' : 'user' }, }) rec.handle.agent.followup(message) return { messageId: message.id } diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py index fe17f6f952..0ea6f3831c 100644 --- a/scripts/smoke-python-runtime.py +++ b/scripts/smoke-python-runtime.py @@ -1925,6 +1925,11 @@ def smoke_sdk_scheduler_recovery(base_url: str, executable: Path, update_snapsho raise AssertionError(f"scheduler recovery expected two serialized model requests: {requests}") logs = read_session_logs(sessions) records = logs[RECOVERY_SESSION_ID] + prompts = [record for record in records if record.get("type") == "user/message" + and record["data"]["source"].get("kind") == "user"] + retries = [record for record in records if record.get("type") == "agent/retry"] + if len(prompts) != 1 or len(retries) != 1: + raise AssertionError("scheduler recovery must record one original prompt and one retry control") calls = [record["data"]["callId"] for record in records if record.get("type") == "tool/call"] if calls != list(RECOVERY_CALL_IDS[:2]): raise AssertionError(f"scheduler recovery dispatched the unstarted call: {calls}") diff --git a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json index 7172a0edd1..e023466dea 100644 --- a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json +++ b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json @@ -57,6 +57,7 @@ "turn/start", "agent/inbox/spliced", "step/start", + "agent/retry", "assistant/message", "step/end", "turn/end" @@ -70,6 +71,7 @@ "session.event", "session.event", "session.event", + "session.event", "session.status" ] } diff --git a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl index 37ccfaa185..acab2bcdb6 100644 --- a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl +++ b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl @@ -21,10 +21,11 @@ {"type":"tool/result","data":{"turn":1,"step":1,"message":{"id":"interrupted-tool-result-scheduler-unstarted-19","role":"tool","toolCallId":"scheduler-unstarted","isError":true,"source":{"kind":"tool","callId":"scheduler-unstarted"},"content":[{"type":"text","text":"The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed."}]},"error":{"name":"ToolNotStartedError","code":"TOOL_NOT_STARTED"}},"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"retry"},"role":"user","id":"{{messageId}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} +{"type":"agent/retry","data":{"turn":2,"step":1,"inputId":"{{messageId}}","source":{"kind":"retry"}}} {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/snapshots/sdk/sdk.snapshot.ts b/snapshots/sdk/sdk.snapshot.ts index ddd859bd38..c4c5a27aa7 100644 --- a/snapshots/sdk/sdk.snapshot.ts +++ b/snapshots/sdk/sdk.snapshot.ts @@ -485,23 +485,15 @@ function modelFromSession(log: string): { provider: string; model: string } { function turnActions(log: string): TurnAction[] { const actions: TurnAction[] = [] let current: TurnAction | undefined - let emptyQueued = false for (const record of records(log)) { - if (record.type === 'agent/inbox/spliced') { - const data = record.data as JsonObject | undefined - if (data?.target === 'next-turn' && Array.isArray(data.inserted)) { - emptyQueued ||= data.inserted.some(value => { - const message = value as JsonObject - return (message.source as JsonObject | undefined)?.kind === 'user' - && Array.isArray(message.content) && message.content.length === 0 - }) - } - } if (record.type === 'turn/start') { const data = record.data as JsonObject | undefined if (typeof data?.turn !== 'number') throw new Error('SDK snapshot turn/start has no turn') - current = { turn: data.turn, ...emptyQueued ? { content: [] } : {} } - emptyQueued = false + current = { turn: data.turn } + continue + } + if (record.type === 'agent/retry' && current !== undefined) { + current = { turn: current.turn, content: [] } continue } if (record.type === 'user/message' && current !== undefined && current.content === undefined) { @@ -1132,6 +1124,7 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => { if (scenario.name === 'tool-scheduler-recovery') { expect(records(ordered[0]!.content).filter(event => event.type === 'user/message' && (event.data['source'] as { kind: string }).kind === 'user')).toHaveLength(1) + expect(records(ordered[0]!.content).filter(event => event.type === 'agent/retry')).toHaveLength(1) expect(results).toHaveLength(2) const events = results.flatMap(result => result.events) expect(results[1]?.finalResponse, JSON.stringify(events.filter(event => event.type === 'turn/end'))) diff --git a/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl b/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl index cfe4388d42..01f324070c 100644 --- a/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl +++ b/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl @@ -19,12 +19,13 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":20,"time":0,"data":{"turn":1,"step":1}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":21,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":22,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":22,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"retry"},"role":"user","id":"{{sessionId}}"}]}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"running"}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":23,"time":0,"data":{"turn":2}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":24,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":25,"time":0,"data":{"turn":2,"step":1}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":26,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":64,"outputTokens":28},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":28}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":27,"time":0,"data":{"turn":2,"step":1}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":28,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/retry","seq":26,"time":0,"data":{"turn":2,"step":1,"inputId":"{{sessionId}}","source":{"kind":"retry"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":27,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":64,"outputTokens":28},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":28}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":28,"time":0,"data":{"turn":2,"step":1}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":29,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} diff --git a/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl b/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl index 31eb0f7d38..704d83c721 100644 --- a/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl +++ b/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl @@ -21,10 +21,11 @@ {"type":"tool/result","data":{"turn":1,"step":1,"message":{"id":"interrupted-tool-result-scheduler-unstarted-19","role":"tool","toolCallId":"scheduler-unstarted","isError":true,"source":{"kind":"tool","callId":"scheduler-unstarted"},"content":[{"type":"text","text":"The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed."}]},"error":{"name":"ToolNotStartedError","code":"TOOL_NOT_STARTED"}},"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"user"},"role":"user","id":"{{message:6}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"retry"},"role":"user","id":"{{message:6}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} +{"type":"agent/retry","data":{"turn":2,"step":1,"inputId":"{{message:6}}","source":{"kind":"retry"}}} {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":64,"outputTokens":28},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":28}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} From 40599db7c85fb030b2086bd4da81c32c3170ac41 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 27 Oct 2026 12:11:22 +0800 Subject: [PATCH 11/29] test(agent): verify retry control identity across SDK recovery --- apps/web/tests/live-interactions.e2e.ts | 6 +++--- docs/architecture.i18n.yaml | 4 ++-- docs/architecture.md | 4 ++-- docs/architecture.zh.md | 4 ++-- docs/persistence-catalog.i18n.yaml | 12 ++++++------ docs/persistence-catalog.md | 13 +++++++++---- docs/persistence-catalog.zh.md | 13 +++++++++---- .../historical-formats/README.i18n.yaml | 4 ++-- .../historical-formats/README.md | 2 +- .../historical-formats/README.zh.md | 2 +- docs/persistence-schema.json | 2 +- .../tests/empty-prompt.host.spec.ts | 7 ++++--- scripts/smoke-python-runtime.py | 8 ++++++++ 13 files changed, 50 insertions(+), 31 deletions(-) diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index bde84fe2d1..394da150c3 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -478,9 +478,9 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { const userMessages = sessionEvents.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' ? [event.data] : []) expect(userMessages.map(message => message.content)).toEqual([[{ type: 'text', text: PROMPT }]]) - expect(sessionEvents.filter(event => event.type === 'agent/retry')).toEqual([ - expect.objectContaining({ data: expect.objectContaining({ turn: 2, step: 1, source: expect.objectContaining({ kind: 'retry' }) }) }), - ]) + const retries = sessionEvents.filter(event => event.type === 'agent/retry') + expect(retries).toHaveLength(1) + expect(retries[0]?.data).toMatchObject({ turn: 2, step: 1, source: { kind: 'retry' } }) expect(sessionEvents.filter(event => event.type === 'turn/start')).toHaveLength(2) expect(sessionEvents.filter(event => event.type === 'turn/end')).toHaveLength(2) expect(await resume.count()).toBe(0) diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 5dfa2c2995..ca357a4a9c 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -24,8 +24,8 @@ en: 693407efb233f961 zh: 37b9dd0677d4405c /deepseek-harness-architecture/turn-flow: - en: 3770eacd9c3bae28 - zh: bd7b8a0374ba0ff8 + en: d67fe476bec779ea + zh: 774a0913a8fb03d0 /deepseek-harness-architecture/session-log: en: 8a7f19b95d426c99 zh: d5bb0538404e20ea diff --git a/docs/architecture.md b/docs/architecture.md index 064026d4d2..1ffee7bf2f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -107,9 +107,9 @@ turn/end Factory setup follows SessionStore entry and `session/created`, preceding `agent/created`; closing events commit before removal. -One inbox feeds the driver; injected context waits for a waking message. AgentLoop’s durable `inbox` projection exposes pending input without live Agents. +`inbox` persists pending input; injected context waits for waking input. Retry controls record `agent/retry` without changing turn settlement. -Before assembly, the loop snapshots Session model selection in `AssembleContext.modelSelection` and `agent/pre-step.modelSelection`; retries retain it; omitted reasoning effort uses adapter defaults. `agent/pre-step` listeners accept, rewrite, or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, the deprecated `agent/request` hook adjusts controls without changing the provider, model, or effort selection, and `prepareCall()` binds the adapter before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Empty retry-source controls record `agent/retry` instead of entering model history; they retain ordinary inbox and turn lifecycle. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The system prompt travels as `system/message` history: empty text clears it, capable routes append updates, and other routes consolidate it at the first system node. [Agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) owns prompt admission and replacement details. +Before assembly, the loop snapshots Session model selection in `AssembleContext.modelSelection` and `agent/pre-step.modelSelection`; retries retain it; omitted reasoning effort uses adapter defaults. `agent/pre-step` listeners accept, rewrite, or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, the deprecated `agent/request` hook adjusts controls without changing the provider, model, or effort selection, and `prepareCall()` binds the adapter before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The system prompt travels as `system/message` history: empty text clears it, capable routes append updates, and other routes consolidate it at the first system node. [Agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) owns prompt admission and replacement details. The loop sends immutable requests with live cancellation and reuses freeze evidence for frozen identities; [agent-loop](../packages/core/agent-loop/README.md) owns construction and cancellation causes. diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index e836ab7452..42297aca67 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -111,9 +111,9 @@ turn/end 工厂 setup 在 Session 进入 SessionStore 并完成 `session/created` 之后、`agent/created` 之前运行;收尾事件在移除之前提交。 -输入通过同一个 inbox 到达驱动器;注入的上下文等待一条唤醒消息。AgentLoop 的持久 `inbox` 投影使待处理输入在没有活跃 Agent 时仍可读取。 +`inbox` 持久化待消费输入;注入上下文等待唤醒输入。重试控制输入记录 `agent/retry`,不改变轮次结算。 -循环在组装前将 Session 模型选择快照到 `AssembleContext.modelSelection` 与 `agent/pre-step.modelSelection`;重试保留该选择,省略推理强度时使用适配器默认值。`agent/pre-step` 监听器接纳、改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,已弃用的 `agent/request` hook 在不改变提供方、模型或推理强度选择的前提下调整控制参数,`prepareCall()` 绑定适配器,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。系统提示词通过 `system/message` 历史传递:空文本清除提示词,具备能力的路由追加更新,其他路由将其归并到首个系统节点。[Agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation) 说明提示词准入与替换的细节。 空的 retry 来源控制输入记录 `agent/retry`,不进入模型历史;inbox 与轮次生命周期仍按普通流程处理。 +循环在组装前将 Session 模型选择快照到 `AssembleContext.modelSelection` 与 `agent/pre-step.modelSelection`;重试保留该选择,省略推理强度时使用适配器默认值。`agent/pre-step` 监听器接纳、改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,已弃用的 `agent/request` hook 在不改变提供方、模型或推理强度选择的前提下调整控制参数,`prepareCall()` 绑定适配器,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。系统提示词通过 `system/message` 历史传递:空文本清除提示词,具备能力的路由追加更新,其他路由将其归并到首个系统节点。[Agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation) 说明提示词准入与替换的细节。 循环发送不可变请求,同时保持取消有效,仅对完全冻结的对象复用冻结证据;[agent-loop](../packages/core/agent-loop/README.zh.md) 负责请求构建与取消原因。 diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 3214b08f10..546008ca82 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -21,8 +21,8 @@ en: 9188862058c6986b zh: 9188862058c6986b /session-persistence-event-catalog/events/agent/agent-inbox-spliced-log-only: - en: 9ae614c041a27169 - zh: 8d669dd582b88f79 + en: b647f3f80ec8b407 + zh: d5e43856cfd85ddb /session-persistence-event-catalog/events/agent/agent-retry-log-only: en: 559e0484e8ae8904 zh: 8967a336e5aef50c @@ -144,8 +144,8 @@ en: 9edf4ef31e60e880 zh: 9edf4ef31e60e880 /session-persistence-event-catalog/events/model/model-selection-log-only: - en: 404c54b08c088438 - zh: eb86ed4e7d0e9228 + en: 608081c311c1bf13 + zh: bcd14effec5f6c78 /session-persistence-event-catalog/events/permission: en: 19917bc5438e60af zh: 19917bc5438e60af @@ -1545,8 +1545,8 @@ en: 930d9c64fe39857d zh: 65cd07dc3c7789db /session-persistence-event-catalog/resolved-persistence-types/inserted-outcome-removedcount-start: - en: 4edbaf2329a66619 - zh: 7d29428252d9d84f + en: 473f9e6ab2b776dc + zh: ce701e3ff6ab63ab /session-persistence-event-catalog/resolved-persistence-types/interrupted-message-step-stream: en: 8e86cd838e3e4a49 zh: f5fec2a8adf1f3cc diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 70e16e7ce7..f3cfff9e7f 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -186,7 +186,7 @@ Sources: [`packages/core/session/src/types.ts:439`](../packages/core/session/src } ``` -Source: [`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/types.ts) +Source: [`packages/core/agent/src/types.ts:124`](../packages/core/agent/src/types.ts) @@ -194,7 +194,12 @@ Source: [`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/type ```ts persistence-catalog /** An admitted retry control, correlated with its durable inbox input; never enters model history. */ -'agent/retry': { turn: number; step: number; inputId: MessageId; source: Extract } +'agent/retry': { + turn: number + step: number + inputId: MessageId + source: Extract +} ``` Types: [MessageSource](subsystems/core.md) @@ -714,7 +719,7 @@ Source: [`packages/llm/llm-retry/src/types.ts:11`](../packages/llm/llm-retry/src 'model/selection': ModelSelection ``` -Source: [`packages/core/agent/src/types.ts:130`](../packages/core/agent/src/types.ts) +Source: [`packages/core/agent/src/types.ts:135`](../packages/core/agent/src/types.ts) ### `permission/*` @@ -6468,7 +6473,7 @@ Sources: [`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/typ SHA-256: `c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039` -Sources: [`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/types.ts) +Sources: [`packages/core/agent/src/types.ts:124`](../packages/core/agent/src/types.ts) | Property | Presence | Type | |---|---|---| diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 47f1c30ed9..05543778cb 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -188,7 +188,7 @@ export type SessionEvent = { } ``` -来源:[`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:124`](../packages/core/agent/src/types.ts) @@ -196,7 +196,12 @@ export type SessionEvent = { ```ts persistence-catalog /** An admitted retry control, correlated with its durable inbox input; never enters model history. */ -'agent/retry': { turn: number; step: number; inputId: MessageId; source: Extract } +'agent/retry': { + turn: number + step: number + inputId: MessageId + source: Extract +} ``` 类型:[MessageSource](subsystems/core.zh.md) @@ -716,7 +721,7 @@ export type SessionEvent = { 'model/selection': ModelSelection ``` -来源:[`packages/core/agent/src/types.ts:130`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:135`](../packages/core/agent/src/types.ts) ### `permission/*` @@ -6470,7 +6475,7 @@ SHA-256: `2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3` SHA-256: `c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039` -来源:[`packages/core/agent/src/types.ts:119`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:124`](../packages/core/agent/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| diff --git a/docs/persistence-changes/historical-formats/README.i18n.yaml b/docs/persistence-changes/historical-formats/README.i18n.yaml index 40d842e063..cae6902733 100644 --- a/docs/persistence-changes/historical-formats/README.i18n.yaml +++ b/docs/persistence-changes/historical-formats/README.i18n.yaml @@ -15,8 +15,8 @@ en: 14c57f30e676f6e3 zh: b1a0ccf26fab3727 /session-persistence-formats/format-references: - en: e3511a4ac3bc4457 - zh: 828f3c39f80d5370 + en: 83fd4c4276a2600d + zh: 4f3431c960a7f8da /session-persistence-formats/scope-and-evidence: en: b220062139ddac1e zh: 7c4c0efe05dafa9c diff --git a/docs/persistence-changes/historical-formats/README.md b/docs/persistence-changes/historical-formats/README.md index 74c575b300..da1a2d629e 100644 --- a/docs/persistence-changes/historical-formats/README.md +++ b/docs/persistence-changes/historical-formats/README.md @@ -32,7 +32,7 @@ The index is generated from validated snapshots and the current writer constant. | 1 | PR #3349 | [V1](v1.md) | [JSON](v1.schema.json) | 54 / 415 | | 2 | `dsh-v0.1.3-alpha.2` | [V2](v2.md) | [JSON](v2.schema.json) | 56 / 435 | | 3 | PR #4320 | [V3](v3.md) | [JSON](v3.schema.json) | 60 / 467 | -| 4 | Current checkout | [Current catalog](../../persistence-catalog.md) | [JSON](../../persistence-schema.json) | 65 / 622 | +| 4 | Current checkout | [Current catalog](../../persistence-catalog.md) | [JSON](../../persistence-schema.json) | 66 / 629 | diff --git a/docs/persistence-changes/historical-formats/README.zh.md b/docs/persistence-changes/historical-formats/README.zh.md index 12eb600771..8a2460c46c 100644 --- a/docs/persistence-changes/historical-formats/README.zh.md +++ b/docs/persistence-changes/historical-formats/README.zh.md @@ -32,7 +32,7 @@ description: "查找从 V0 到工作区写入器版本的每个 Session 格式 | 1 | PR #3349 | [V1](v1.zh.md) | [JSON](v1.schema.json) | 54 / 415 | | 2 | `dsh-v0.1.3-alpha.2` | [V2](v2.zh.md) | [JSON](v2.schema.json) | 56 / 435 | | 3 | PR #4320 | [V3](v3.zh.md) | [JSON](v3.schema.json) | 60 / 467 | -| 4 | 当前工作树 | [当前目录](../../persistence-catalog.zh.md) | [JSON](../../persistence-schema.json) | 65 / 622 | +| 4 | 当前工作树 | [当前目录](../../persistence-catalog.zh.md) | [JSON](../../persistence-schema.json) | 66 / 629 | diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index 07e2ce9415..f7630fdb61 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -568,7 +568,7 @@ {"digest":"c589e938cc0e4989df8b82abef6339de621f0dc4fe62c230d145b94996af7f92","names":[],"sources":[]}, {"digest":"c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6","names":["packages/util/values/src/index.ts#JsonValue"],"sources":["packages/util/values/src/index.ts:4"]}, {"digest":"c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5","names":[],"sources":[]}, - {"digest":"c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039","names":[],"sources":["packages/core/agent/src/types.ts:119"]}, + {"digest":"c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039","names":[],"sources":["packages/core/agent/src/types.ts:124"]}, {"digest":"c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262","names":[],"sources":[]}, {"digest":"c8e6f48878cc2a2f0dc59682280d81f407943fb503f32c64bf9ff3d9393c3eff","names":[],"sources":[]}, {"digest":"c99e20441bf15341e9db54385e558e5c03d21dff2eb1ac08ccbf47c2d8747954","names":[],"sources":[]}, diff --git a/packages/api/session-controller/tests/empty-prompt.host.spec.ts b/packages/api/session-controller/tests/empty-prompt.host.spec.ts index 835d7ae442..50d4871ee8 100644 --- a/packages/api/session-controller/tests/empty-prompt.host.spec.ts +++ b/packages/api/session-controller/tests/empty-prompt.host.spec.ts @@ -43,9 +43,10 @@ it('records retry controls without user messages or duplicate requests', async ( const messages = events.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' ? [event.data] : []) expect(messages).toHaveLength(1) const retries = events.flatMap(event => event.type === 'agent/retry' ? [event.data] : []) - expect(retries).toEqual([ - { turn: 2, step: 1, inputId: expect.any(String), source: { kind: 'retry', rpcId: 'continue-1' } }, - { turn: 3, step: 1, inputId: expect.any(String), source: { kind: 'retry', rpcId: 'continue-2' } }, + expect(retries).toHaveLength(2) + expect(retries).toMatchObject([ + { turn: 2, step: 1, source: { kind: 'retry', rpcId: 'continue-1' } }, + { turn: 3, step: 1, source: { kind: 'retry', rpcId: 'continue-2' } }, ]) const controls = events.flatMap(event => event.type === 'agent/inbox/spliced' ? event.data.inserted : []) .filter(message => message.source.kind === 'retry') diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py index 0ea6f3831c..24bc9f15f1 100644 --- a/scripts/smoke-python-runtime.py +++ b/scripts/smoke-python-runtime.py @@ -1930,6 +1930,13 @@ def smoke_sdk_scheduler_recovery(base_url: str, executable: Path, update_snapsho retries = [record for record in records if record.get("type") == "agent/retry"] if len(prompts) != 1 or len(retries) != 1: raise AssertionError("scheduler recovery must record one original prompt and one retry control") + retry_id = retries[0]["data"]["inputId"] + queued_retry_ids = [message.get("id") for record in records + if record.get("type") == "agent/inbox/spliced" + for message in record["data"]["inserted"] + if message.get("source", {}).get("kind") == "retry"] + if not isinstance(retry_id, str) or queued_retry_ids != [retry_id]: + raise AssertionError("scheduler recovery lost the retry control's durable inbox identity") calls = [record["data"]["callId"] for record in records if record.get("type") == "tool/call"] if calls != list(RECOVERY_CALL_IDS[:2]): raise AssertionError(f"scheduler recovery dispatched the unstarted call: {calls}") @@ -1946,6 +1953,7 @@ def smoke_sdk_scheduler_recovery(base_url: str, executable: Path, update_snapsho replacements = [ *snapshot_directory_replacements(root, "{{cwd}}"), (RECOVERY_SESSION_ID, "{{parent}}"), + (retry_id, "{{messageId}}"), ] result_value = [{ "session_id": result.session_id, From 22db5af493661e9da3c37d3915ae58029052dd39 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 27 Oct 2026 12:15:57 +0800 Subject: [PATCH 12/29] docs(agent): record retry control compatibility and validation --- .../2026-10-27-retry-control-input.i18n.yaml | 8 ++++---- .../persistence-changes/2026-10-27-retry-control-input.md | 4 ++-- .../2026-10-27-retry-control-input.zh.md | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml b/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml index 3513ba5aaa..502105b258 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml +++ b/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml @@ -18,11 +18,11 @@ en: 6907136f28e48c90 zh: 02f8298485f3d8e7 /2026-10-27-retry-control-input/compatibility: - en: 4487a595853a65d6 - zh: d9b356c77ccab559 + en: ffc72b74956201d6 + zh: 78cfe9d2353ca739 /2026-10-27-retry-control-input/verification: - en: da37e5d4769c40fe - zh: 992f66b1a2c66fb2 + en: fbd40f2d4da56e1e + zh: 82546f33ad58b08b /2026-10-27-retry-control-input/dev-note: en: b75c8458928b72b9 zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.md b/docs/persistence-changes/2026-10-27-retry-control-input.md index 3faecc4503..10ebe9fe04 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.md +++ b/docs/persistence-changes/2026-10-27-retry-control-input.md @@ -51,12 +51,12 @@ changes: ## Compatibility -Updated readers continue accepting older logs with no retry controls. Older readers reject the unknown required agent/retry event before treating a retry as ordinary user content. The retry discriminator also distinguishes queued controls from ordinary empty user messages. API and both SDK consumers are updated together; the current Session format version is retained because event and source discriminators prevent unsafe interpretation. +Updated readers preserve older logs and ordinary empty user-message admission. Older readers refuse the new required agent/retry event before append or tail repair. A queued control predating acceptance still has the existing identified empty UserMessage representation; an older Loop can consume it using its ordinary empty-input behavior, retaining inbox identity and adding no user text. API and both SDK consumers recognize retry controls and the acceptance event together. The current Session format version is retained; mixed ordinary messages and retry events remain readable by the updated reader. ## Verification -Source review confirms controls retain existing inbox and turn lifecycle while agent/retry has no model-surface operation. Regression coverage covers repeated retry, input-id correlation, balanced turns, ordinary empty messages, non-empty retry rejection, deduplication, checkpoint replay, browser behavior, and both SDK recovery recordings. Execution is pending post-push validation. +Focused AgentLoop, Agent, Session API, and SDK-server tests passed: 91 files, 1806 tests, with 100% statements, branches, functions, and lines for agent.ts and continuation.ts. Browser continuation replay and TypeScript/Python SDK scheduler-recovery replay passed; assertions retain one original user message, correlate retry input identity, preserve tool results, and close new turns normally. Current-event and native-source admission tests cover unknown required-event refusal without mutation and preservation of extensible source metadata. Build, aggregate checks, and typed lint passed. doc-sync passed 42 gates; its remaining repository-reference failure is the unchanged python/sdk/uv.lock:113 commit hash. ## Dev Note diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.zh.md b/docs/persistence-changes/2026-10-27-retry-control-input.zh.md index 672f1d032c..5729a666e7 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.zh.md +++ b/docs/persistence-changes/2026-10-27-retry-control-input.zh.md @@ -51,12 +51,12 @@ changes: ## 兼容性 -更新后的读取方继续接受没有重试控制输入的旧日志。旧读取方会拒绝未知必读 agent/retry 事件,不会将重试当作普通用户内容。retry 判别标签也将排队控制输入与普通空用户消息区分开。API 与两套 SDK 消费方同步更新;事件及来源判别标签可阻止不安全解释,因此保留当前 Session 格式版本。 +更新后的读取方保留旧日志含义和普通空用户消息准入。旧读取方会在追加或尾部修复前拒绝新的必读 agent/retry 事件。接受前的排队控制输入仍采用现有带身份的空 UserMessage 表示;旧 Loop 可按其普通空输入行为消费,保留 inbox 身份且不增加用户文本。API 与两套 SDK 消费方同步识别重试控制输入及接受事件。保留当前 Session 格式版本;更新后的读取方可读取普通消息与重试事件混合的日志。 ## 验证 -源码审查确认控制输入保留现有 inbox 和轮次生命周期,agent/retry 不携带模型表面操作。回归用例覆盖连续重试、输入身份关联、轮次闭合、普通空消息、拒绝非空重试、去重、检查点回放、浏览器行为及两套 SDK 恢复录制。执行验证将在 push 后进行。 +AgentLoop、Agent、Session API 与 SDK 服务端定向测试通过:91 个文件、1806 个测试,agent.ts 和 continuation.ts 的语句、分支、函数及行覆盖率均为 100%。浏览器继续回放与 TypeScript/Python SDK scheduler-recovery 回放通过;断言保留一条原始用户消息、关联重试输入身份、保留工具结果且新轮次正常闭合。当前事件与原生来源准入测试覆盖未知必读事件在不修改数据的情况下拒绝,以及保留可扩展来源元数据。构建、聚合检查和类型感知 lint 通过。doc-sync 有 42 项通过;剩余仓库引用失败来自未改动的 python/sdk/uv.lock:113 提交哈希。 ## 开发备注 From 818e8eced2ad059827ae9985bfdc6e00b74a8e86 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 2 Nov 2026 15:58:09 +0800 Subject: [PATCH 13/29] refactor(web): use empty retry user messages without loop changes --- ...6-manual-generation-continuation.i18n.yaml | 4 +- ...26-10-26-manual-generation-continuation.md | 6 +- ...10-26-manual-generation-continuation.zh.md | 6 +- apps/web/tests/live-interactions.e2e.ts | 4 +- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 2 +- docs/architecture.zh.md | 2 +- docs/event-producer-consumer.md | 22 +- docs/event-producer-consumer.zh.md | 22 +- docs/persistence-catalog.i18n.yaml | 154 +++++++------- docs/persistence-catalog.md | 192 +++++------------- docs/persistence-catalog.zh.md | 192 +++++------------- .../2026-10-27-retry-control-input.i18n.yaml | 12 +- .../2026-10-27-retry-control-input.md | 18 +- ...2026-10-27-retry-control-input.schema.json | 40 ++-- .../2026-10-27-retry-control-input.zh.md | 18 +- docs/persistence-schema.json | 75 +++---- docs/subsystems/core.md | 6 +- docs/subsystems/core.zh.md | 6 +- .../empty-prompt-continuation/guide.i18n.yaml | 16 -- .../empty-prompt-continuation/guide.md | 20 -- .../empty-prompt-continuation/guide.zh.md | 20 -- .../api/session-controller/README.i18n.yaml | 4 +- packages/api/session-controller/README.md | 2 +- packages/api/session-controller/README.zh.md | 2 +- .../api/session-controller/src/commands.ts | 9 +- .../session-controller/src/continuation.ts | 10 +- packages/api/session-controller/src/types.ts | 2 +- .../tests/continuation.host.spec.ts | 20 +- .../tests/empty-prompt.host.spec.ts | 18 +- packages/client/ui-chat/README.i18n.yaml | 4 +- packages/client/ui-chat/README.md | 2 + packages/client/ui-chat/README.zh.md | 2 + .../src/client/conversation-nodes/message.ts | 1 + ...nversation-node-definitions.client.spec.ts | 26 +++ .../client/ui-conversation/README.i18n.yaml | 12 +- packages/client/ui-conversation/README.md | 6 +- packages/client/ui-conversation/README.zh.md | 6 +- packages/core/agent-loop/README.i18n.yaml | 4 +- packages/core/agent-loop/README.md | 2 - packages/core/agent-loop/README.zh.md | 2 - packages/core/agent-loop/src/agent.ts | 5 +- .../agent-loop/tests/request-error.spec.ts | 51 +---- packages/core/agent/README.i18n.yaml | 4 +- packages/core/agent/README.md | 2 +- packages/core/agent/README.zh.md | 2 +- packages/core/agent/src/runtime-types.ts | 6 +- packages/core/agent/src/types.ts | 18 +- .../core/session/src/known-event-types.ts | 1 - packages/sdk/client/src/client.ts | 2 +- packages/sdk/protocol/README.i18n.yaml | 4 +- packages/sdk/protocol/README.md | 2 +- packages/sdk/protocol/README.zh.md | 2 +- packages/sdk/protocol/src/types.ts | 4 +- packages/sdk/server/README.i18n.yaml | 8 +- packages/sdk/server/README.md | 4 +- packages/sdk/server/README.zh.md | 4 +- packages/sdk/server/src/server.ts | 4 +- scripts/smoke-python-runtime.py | 22 +- .../scheduler-recovery/requests.json | 4 + .../scheduler-recovery/result.json | 2 +- .../scheduler-recovery/session.v4.jsonl | 4 +- snapshots/sdk/sdk.snapshot.ts | 10 +- .../notifications.expected.jsonl | 4 +- .../tool-scheduler-recovery/session.v4.jsonl | 4 +- 65 files changed, 412 insertions(+), 736 deletions(-) delete mode 100644 docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml delete mode 100644 docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md delete mode 100644 docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml index 8c9fc03d74..0ed154a1c4 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml @@ -9,8 +9,8 @@ en: f118844946afdbdc zh: f1fe37d042f88b24 /agent-note-manual-generation-continuation/decision: - en: d384d0ea6c502908 - zh: 90e0cb41a5a0d125 + en: b9860cafbc20e453 + zh: a274a3c7f33bc056 /agent-note-manual-generation-continuation/alternatives-considered: en: 58bb948d210d42ea zh: 5dc920d222d71218 diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md index ededd88ffb..01f4a88314 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md @@ -10,11 +10,11 @@ Users need another request after a model failure without inserting an artificial ## Decision -The play action submits `content: []` through the existing Session prompt API. The API assigns `source.kind: 'retry'`. AgentLoop retains the identified inbox input and starts a new balanced turn; acceptance records `agent/retry` instead of a model-visible `user/message`. Ordinary empty user-message behavior is unchanged. The original failed turn remains closed. +The play action submits `content: []` through the existing Session prompt API. The API assigns `source.kind: 'retry'`; unchanged AgentLoop admission records an empty `user/message` and opens a normally balanced new turn. Chat omits the empty retry node while the log retains the input. The original failed turn remains closed. -The Session API owns the fixed-size `conversationContinuation` projection and shares its value with the browser. Admission requires an eligible failure, true idle ownership, and no pending human input. An accepted retry event admits the next turn, preserving eligibility if that request also fails. +The Session API owns the fixed-size `conversationContinuation` projection and shares its value with the browser. Admission requires an eligible failure, true idle ownership, and no pending human input. An appended empty retry message admits the new turn, preserving eligibility if its request also fails. -The six-code manual allowlist stays independent of provider retry policy. Non-waking injected context accompanies the request. The retry event retains request-id deduplication after the inbox input is claimed; no new Remote operation is required. An explicit control source prevents unrelated empty user inputs from changing behavior, and a separate event keeps chat rendering free of retry-specific filtering. +The six-code manual allowlist stays independent of provider retry policy. Non-waking injected context accompanies the request. The accepted user message retains request-id deduplication after the inbox input is claimed. Reusing ordinary message admission avoids a Loop branch and a separate durable acceptance event. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md index b3d659edf2..fc6fcf6bf2 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md @@ -10,11 +10,11 @@ Status: implemented ## 决策 -播放按钮通过现有 Session prompt API 提交 `content: []`。API 指定 `source.kind: 'retry'`。AgentLoop 保留带身份的 inbox 输入并启动正常闭合的新轮次;接受后记录 `agent/retry`,而不写入模型可见的 `user/message`。普通空用户消息的行为保持不变。原失败轮次保持闭合。 +播放按钮通过现有 Session prompt API 提交 `content: []`。API 指定 `source.kind: 'retry'`;AgentLoop 沿用普通准入,记录空 `user/message` 并开启正常闭合的新轮次。Chat 不生成空重试节点,日志保留该输入。原失败轮次保持闭合。 -Session API 拥有固定大小的 `conversationContinuation` 投影,向浏览器发布相同的值。准入要求失败符合条件、持有真正的空闲状态且没有待消费的用户输入。已接受的重试事件使新轮次获得准入,因此请求再次失败后仍保留继续资格。 +Session API 拥有固定大小的 `conversationContinuation` 投影,向浏览器发布相同的值。准入要求失败符合条件、持有真正的空闲状态且没有待消费的用户输入。追加的空重试消息使新轮次获得准入,因此请求再次失败后仍保留继续资格。 -六码手动白名单独立于提供方重试策略。非唤醒的注入上下文随请求进入。inbox 输入被领取后,重试事件仍支持 request-id 去重;不需要新增 Remote 操作。显式控制来源避免改变无关空用户输入的行为,独立事件则使聊天渲染无需针对重试过滤。 +六码手动白名单独立于提供方重试策略。非唤醒的注入上下文随请求进入。inbox 输入被领取后,已接受的用户消息仍支持 request-id 去重。复用普通消息准入避免增加 Loop 分支及独立持久接受事件。 ## 考虑过的替代方案 diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index 394da150c3..ebb69fa246 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -478,9 +478,9 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { const userMessages = sessionEvents.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' ? [event.data] : []) expect(userMessages.map(message => message.content)).toEqual([[{ type: 'text', text: PROMPT }]]) - const retries = sessionEvents.filter(event => event.type === 'agent/retry') + const retries = sessionEvents.filter(event => event.type === 'user/message' && event.data.source.kind === 'retry') expect(retries).toHaveLength(1) - expect(retries[0]?.data).toMatchObject({ turn: 2, step: 1, source: { kind: 'retry' } }) + expect(retries[0]?.data).toMatchObject({ content: [], source: { kind: 'retry' } }) expect(sessionEvents.filter(event => event.type === 'turn/start')).toHaveLength(2) expect(sessionEvents.filter(event => event.type === 'turn/end')).toHaveLength(2) expect(await resume.count()).toBe(0) diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index ca357a4a9c..c8543cdc36 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -24,8 +24,8 @@ en: 693407efb233f961 zh: 37b9dd0677d4405c /deepseek-harness-architecture/turn-flow: - en: d67fe476bec779ea - zh: 774a0913a8fb03d0 + en: 1a79a70b62c04cdb + zh: c86d765e21430405 /deepseek-harness-architecture/session-log: en: 8a7f19b95d426c99 zh: d5bb0538404e20ea diff --git a/docs/architecture.md b/docs/architecture.md index 1ffee7bf2f..e6bede8d0e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -107,7 +107,7 @@ turn/end Factory setup follows SessionStore entry and `session/created`, preceding `agent/created`; closing events commit before removal. -`inbox` persists pending input; injected context waits for waking input. Retry controls record `agent/retry` without changing turn settlement. +One inbox feeds the driver; injected context waits for a waking message. AgentLoop’s durable `inbox` projection exposes pending input without live Agents. Before assembly, the loop snapshots Session model selection in `AssembleContext.modelSelection` and `agent/pre-step.modelSelection`; retries retain it; omitted reasoning effort uses adapter defaults. `agent/pre-step` listeners accept, rewrite, or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, the deprecated `agent/request` hook adjusts controls without changing the provider, model, or effort selection, and `prepareCall()` binds the adapter before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The system prompt travels as `system/message` history: empty text clears it, capable routes append updates, and other routes consolidate it at the first system node. [Agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) owns prompt admission and replacement details. diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 42297aca67..8863c8047c 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -111,7 +111,7 @@ turn/end 工厂 setup 在 Session 进入 SessionStore 并完成 `session/created` 之后、`agent/created` 之前运行;收尾事件在移除之前提交。 -`inbox` 持久化待消费输入;注入上下文等待唤醒输入。重试控制输入记录 `agent/retry`,不改变轮次结算。 +输入通过同一个 inbox 到达驱动器;注入的上下文等待一条唤醒消息。AgentLoop 的持久 `inbox` 投影使待处理输入在没有活跃 Agent 时仍可读取。 循环在组装前将 Session 模型选择快照到 `AssembleContext.modelSelection` 与 `agent/pre-step.modelSelection`;重试保留该选择,省略推理强度时使用适配器默认值。`agent/pre-step` 监听器接纳、改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,已弃用的 `agent/request` hook 在不改变提供方、模型或推理强度选择的前提下调整控制参数,`prepareCall()` 绑定适配器,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。系统提示词通过 `system/message` 历史传递:空文本清除提示词,具备能力的路由追加更新,其他路由将其归并到首个系统节点。[Agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation) 说明提示词准入与替换的细节。 diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index fbe751ca65..ba53e59c41 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -12,17 +12,17 @@ Application listeners are outside this package matrix. Desktop Host subscribes t | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:199`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | -| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:401`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:292`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:301`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team` | -| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:431`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | -| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:330`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:338`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:319`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `client-ui-forge`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:352`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `client-ui-forge`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | -| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:391`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:311`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:419`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team` | +| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | +| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `client-ui-forge`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `client-ui-forge`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:693`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:673`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:700`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 30163a2e5f..fb32e351ec 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -14,17 +14,17 @@ | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:199`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | -| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:401`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:292`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:301`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team` | -| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:431`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | -| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:330`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:338`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:319`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `client-ui-forge`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:352`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `client-ui-forge`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | -| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:391`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:311`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:419`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team` | +| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | +| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `client-ui-forge`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `client-ui-forge`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:693`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:673`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:700`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 546008ca82..60155c2552 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -9,8 +9,8 @@ en: 1387c4505dde2faf zh: 48570fa1c13d6910 /session-persistence-event-catalog/persistence-type-fingerprints: - en: 84b66494d8162dc6 - zh: f89855f2a8e9a367 + en: 6d903c4dcebd5e55 + zh: f4ce8faa270eed69 /session-persistence-event-catalog/event-envelope: en: 56414ffd00cdc32a zh: 6dc11eb013e95e41 @@ -21,11 +21,8 @@ en: 9188862058c6986b zh: 9188862058c6986b /session-persistence-event-catalog/events/agent/agent-inbox-spliced-log-only: - en: b647f3f80ec8b407 - zh: d5e43856cfd85ddb -/session-persistence-event-catalog/events/agent/agent-retry-log-only: - en: 559e0484e8ae8904 - zh: 8967a336e5aef50c + en: 481727894895b43b + zh: 098bc9788bf88968 /session-persistence-event-catalog/events/agent-preset: en: 49891ca9be5ac123 zh: 49891ca9be5ac123 @@ -144,8 +141,8 @@ en: 9edf4ef31e60e880 zh: 9edf4ef31e60e880 /session-persistence-event-catalog/events/model/model-selection-log-only: - en: 608081c311c1bf13 - zh: bcd14effec5f6c78 + en: c2a60ec7d297ed79 + zh: a2f6b2d3c733ce96 /session-persistence-event-catalog/events/permission: en: 19917bc5438e60af zh: 19917bc5438e60af @@ -333,11 +330,8 @@ en: b3b528acadd04168 zh: b3b528acadd04168 /session-persistence-event-catalog/resolved-persistence-types/agent-inbox-spliced: - en: 8719b8f3a0388f64 - zh: 8719b8f3a0388f64 -/session-persistence-event-catalog/resolved-persistence-types/agent-retry: - en: f634a360f6f4a5a7 - zh: f634a360f6f4a5a7 + en: 8f6d91375141d923 + zh: 8f6d91375141d923 /session-persistence-event-catalog/resolved-persistence-types/allowed-once: en: 6c3effe2abc1aa53 zh: 6c3effe2abc1aa53 @@ -1017,11 +1011,11 @@ en: a6a2e38fdd6d127a zh: 1fe82356d89f5769 /session-persistence-event-catalog/resolved-persistence-types/deepseeksearchllmrequest: - en: 96cd6b26224ff94f - zh: cf340bff9692f438 + en: 4429f5b1e3309d4e + zh: ba5abe98dd015d38 /session-persistence-event-catalog/resolved-persistence-types/developermessage: - en: 6a4c81ce3ec34434 - zh: 96bced8b8cad4b1a + en: 830b55a7bcab2baf + zh: e46378185d37f860 /session-persistence-event-catalog/resolved-persistence-types/epochheader: en: 3bdc8ecd0bdf0c9c zh: 0fe5b75c174ebab4 @@ -1086,8 +1080,8 @@ en: a3087ab7bebe9371 zh: abf6afc2f9f5b692 /session-persistence-event-catalog/resolved-persistence-types/inboxtarget: - en: f17ed3bd806e279f - zh: 72920ace94922d45 + en: b3b7b8a3414bcb6b + zh: 06be5b8e56bacedf /session-persistence-event-catalog/resolved-persistence-types/jobregistereddata: en: f604eebb973fe2cf zh: 0c0aba1e17ecf66e @@ -1131,11 +1125,11 @@ en: 6dbe099c62f7c226 zh: 6fd788f63c038c91 /session-persistence-event-catalog/resolved-persistence-types/llmretrystartedeventdata: - en: dc14ea1c81e3d226 - zh: 21a2b974bdc708d2 + en: 1fe9a5b33357642e + zh: 352d910d62e922e0 /session-persistence-event-catalog/resolved-persistence-types/message: - en: 7b11edaf7aa790ae - zh: 514e2826b20dd7d3 + en: 3c7755aba7093404 + zh: 642b01489e18a22c /session-persistence-event-catalog/resolved-persistence-types/messagefeedbackdelete: en: c582953270d4ca44 zh: 966ccfe2e7660a2a @@ -1146,20 +1140,20 @@ en: f58575a1fc96df42 zh: 9f0c6ed9033e5c9d /session-persistence-event-catalog/resolved-persistence-types/messagefeedbackrating: - en: 9897f3cb7a6c7233 - zh: 30e377f7c302a32b + en: c46a7f9a66fd764c + zh: a9d7321c0f508108 /session-persistence-event-catalog/resolved-persistence-types/messagesource: - en: 91e395f09fd30c1a - zh: 5efbc52f4e72e049 + en: f1367e01e2f4c8e2 + zh: 2ecf507595f66ef4 /session-persistence-event-catalog/resolved-persistence-types/message~2: - en: 11eafb2bccde2b25 - zh: 681c0cdb27f6a1c0 + en: 9656e6246dd99f3c + zh: 22287e6ea97f8753 /session-persistence-event-catalog/resolved-persistence-types/modelmessagesource: en: 5f9154ca34235ab9 zh: a833105910ebfa82 /session-persistence-event-catalog/resolved-persistence-types/modelselection: - en: 970930c4a8b1bad8 - zh: 6c596aba5e3c5020 + en: 6e301c0ea35d9e0e + zh: 6fbf1d318db9a4e0 /session-persistence-event-catalog/resolved-persistence-types/oneshotscheduledispatchchange: en: a3300babe57b6b67 zh: 4aaf06a149b399c3 @@ -1215,11 +1209,11 @@ en: 9a54c4478ad3b620 zh: 02c4c1fd7e252770 /session-persistence-event-catalog/resolved-persistence-types/sessiontitleeventdata: - en: c8e30c2e9886e459 - zh: d7b53b79e0849e64 + en: 5a906176753fc58b + zh: e2d731496f8b23e9 /session-persistence-event-catalog/resolved-persistence-types/sessiontitlellmrequesteventdata: - en: aefe8cd3c2b273b2 - zh: b2e21ce4ca05f3fb + en: c32b552ced3a18c1 + zh: 213ca8c8b37c31a8 /session-persistence-event-catalog/resolved-persistence-types/sessiontitlemodelidentity: en: 9531468264499498 zh: 9da23c0ad2776563 @@ -1326,14 +1320,14 @@ en: 778debd61e0284a2 zh: 2cc82ad9e80f18b1 /session-persistence-event-catalog/resolved-persistence-types/turnendreason: - en: 2ef8e1ebf9e02934 - zh: 986c170add1fb987 + en: a0c7b52cb964ae8f + zh: 81cbc87e60f6ade9 /session-persistence-event-catalog/resolved-persistence-types/usermessage: - en: cf92679783fc00c6 - zh: 7a6ba95081ca4ace + en: fb21eb99ea969574 + zh: fec20e55aa05d7c5 /session-persistence-event-catalog/resolved-persistence-types/usermessage~2: - en: f2f335cf1b45d3f9 - zh: 4df02f25e9a324dd + en: 0611b6d12f78702d + zh: 31b75bfe7b7d40bc /session-persistence-event-catalog/resolved-persistence-types/workflowagentoutcome: en: d0a3f9aab18c3de5 zh: 76ac69a835ca4e29 @@ -1380,12 +1374,9 @@ en: f692844b29923b2e zh: 72f12bca08d63267 /session-persistence-event-catalog/resolved-persistence-types/union-2-variants~2: - en: 2453bf81cd9cd1f6 - zh: b1cef542e013c238 + en: 69d360cf5a1ae6b4 + zh: f23faf39a2a0e6f1 /session-persistence-event-catalog/resolved-persistence-types/union-2-variants~3: - en: 1137a9a48ddad4b3 - zh: 222c1545cdb19d76 -/session-persistence-event-catalog/resolved-persistence-types/union-2-variants~4: en: 5a1af9849b0967ea zh: 1085d793bbc47360 /session-persistence-event-catalog/resolved-persistence-types/union-3-variants: @@ -1455,11 +1446,8 @@ en: 43cf5cd666b591a0 zh: 7c7a3011f8681f12 /session-persistence-event-catalog/resolved-persistence-types/childcreatedat-childid-label-mode~6: - en: 959fc569699d21f0 - zh: b48a0a3b1639957d -/session-persistence-event-catalog/resolved-persistence-types/clienttimezone-kind-retry-principal-rpcid: - en: 1c523784a95eff23 - zh: e19a26cb9ae14e12 + en: a1f188c9058cf621 + zh: eb43c0f045fb9afb /session-persistence-event-catalog/resolved-persistence-types/code-name-reason: en: 25df2af19bc9c46d zh: ebb6ecbeee25c9be @@ -1518,11 +1506,11 @@ en: 51af619ed8532a43 zh: a5ad878c63d0e046 /session-persistence-event-catalog/resolved-persistence-types/header-reason-startsseries: - en: 616424f7d89b2617 - zh: 1a90b1c57ecb4f88 + en: 961852f66c238083 + zh: 8a30206c20f2fca4 /session-persistence-event-catalog/resolved-persistence-types/headerseq-message-step-turn: - en: bcdb5e4a5a9b0aac - zh: a214e6242694bfdd + en: d796d855f59c6f85 + zh: ab8107c62429dd94 /session-persistence-event-catalog/resolved-persistence-types/height-width: en: a5df50710083ac25 zh: aad3ccf6f6de70e4 @@ -1539,14 +1527,11 @@ en: 41f73c58a77dfa28 zh: af77f4aee3cb4a40 /session-persistence-event-catalog/resolved-persistence-types/inherited: - en: 7b92563703daff7b - zh: c91b356048b96141 -/session-persistence-event-catalog/resolved-persistence-types/inputid-source-step-turn: - en: 930d9c64fe39857d - zh: 65cd07dc3c7789db + en: 0aa14eeb9c8e5478 + zh: 748c490277d7527c /session-persistence-event-catalog/resolved-persistence-types/inserted-outcome-removedcount-start: - en: 473f9e6ab2b776dc - zh: ce701e3ff6ab63ab + en: bf592ceb678b2716 + zh: 98dc3413fffdda88 /session-persistence-event-catalog/resolved-persistence-types/interrupted-message-step-stream: en: 8e86cd838e3e4a49 zh: f5fec2a8adf1f3cc @@ -1737,11 +1722,11 @@ en: 1f6dbdbf5d923df0 zh: aea576e5ec34c0fc /session-persistence-event-catalog/resolved-persistence-types/kind-repeat-tool-reminder-form-snapshot: - en: fb3160150710d86b - zh: 27a71fa774531620 + en: 3ca87431bb7851c1 + zh: c224d081708e65b0 /session-persistence-event-catalog/resolved-persistence-types/kind-retry: - en: db4c8e67a5176225 - zh: 762719a4d2ab1f73 + en: 1ebd6745d9ba8848 + zh: 99a5c07b3273add2 /session-persistence-event-catalog/resolved-persistence-types/kind-runtime-context: en: 68b5598e21cf8ba2 zh: 95ef1685af64a5f4 @@ -1965,14 +1950,11 @@ en: 374b8adee8d571fa zh: b21057efc58e6490 /session-persistence-event-catalog/resolved-persistence-types/type-agent-preset-selected: - en: 7626d618ab37dec4 - zh: 662b0be4c51feebc + en: 3a88f63d767f655f + zh: 564857af30d0fd51 /session-persistence-event-catalog/resolved-persistence-types/type-agent-inbox-spliced: - en: 60466c7cafae41b5 - zh: 9dee130ddd7b0fd1 -/session-persistence-event-catalog/resolved-persistence-types/type-agent-retry: - en: 7a5e1c92d8760186 - zh: ae4173989281facd + en: cd60aa3e918b40db + zh: e09d742578208572 /session-persistence-event-catalog/resolved-persistence-types/type-approval-asked: en: 9c77f18b0dad22f0 zh: 8894cf2878fe90db @@ -2016,11 +1998,11 @@ en: ba73fb10b7e2ffc9 zh: 8d5c97e63e5f16da /session-persistence-event-catalog/resolved-persistence-types/type-deliverables-presented: - en: 7fbea1c722f8c2f3 - zh: d732349d783b9b01 + en: 6e6dab3be34ac480 + zh: ba927e2a338f0d4b /session-persistence-event-catalog/resolved-persistence-types/type-developer-message: - en: f231622830903402 - zh: 03457dc27539fafd + en: b7df6a22211a0e9d + zh: e039166aaa7e1eee /session-persistence-event-catalog/resolved-persistence-types/type-disabled: en: 585627a50ece4e5f zh: 157837aee3da0ac3 @@ -2094,11 +2076,11 @@ en: acf7e21e4d21ebff zh: fb83a853634d204a /session-persistence-event-catalog/resolved-persistence-types/type-session-title: - en: a642705c8e44a3c9 - zh: 10aa1aff0d3766d3 + en: f9f575c4264cc6bb + zh: 8943cbde299074fc /session-persistence-event-catalog/resolved-persistence-types/type-session-title-llm-request: - en: dd94dea63f118b12 - zh: e7feae9219037e9a + en: 5e0b07a77d30ce24 + zh: 72f71d67f6f94eea /session-persistence-event-catalog/resolved-persistence-types/type-step-end: en: 7fc233c4a569dae2 zh: bc2b3fed4a397c00 @@ -2175,11 +2157,11 @@ en: 1b045de96172be4b zh: 8a15c6f35b576b37 /session-persistence-event-catalog/resolved-persistence-types/type-usage: - en: 311b42bd63735e87 - zh: 37ea452a672fca21 + en: f191fa5878a52c80 + zh: 8e49f35534b8b254 /session-persistence-event-catalog/resolved-persistence-types/type-user-message: - en: 07e60b23dc23e0a3 - zh: dc74747ec207e1bb + en: 71edbd2d3b4d32e0 + zh: fdb2f237554aa760 /session-persistence-event-catalog/resolved-persistence-types/type-web-deepseek-search-llm-request: en: 99c8837344178a47 zh: bf746f5678346596 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index f3cfff9e7f..cea10e65d2 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -23,8 +23,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`HeaderLine`](#persistence-type-sha256-18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f) | | `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`union (3 variants)`](#persistence-type-sha256-5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc) | | `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`{ type: "agent-preset/selected" }`](#persistence-type-sha256-a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b) | -| `event:agent/inbox/spliced` | event | `a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664) | -| `event:agent/retry` | event | `d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c` | [`{ type: "agent/retry" }`](#persistence-type-sha256-d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c) | +| `event:agent/inbox/spliced` | event | `2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee) | | `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`{ type: "approval/asked" }`](#persistence-type-sha256-3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1) | | `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`{ type: "approval/decided" }`](#persistence-type-sha256-bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3) | | `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`{ type: "approval/policy" }`](#persistence-type-sha256-26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78) | @@ -37,7 +36,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`{ type: "compaction/start" }`](#persistence-type-sha256-db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965) | | `event:compaction/summary` | event | `e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602` | [`{ type: "compaction/summary" }`](#persistence-type-sha256-e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602) | | `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`{ type: "deliverables/presented" }`](#persistence-type-sha256-13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176) | -| `event:developer/message` | event | `92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583` | [`{ type: "developer/message" }`](#persistence-type-sha256-92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583) | +| `event:developer/message` | event | `caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec` | [`{ type: "developer/message" }`](#persistence-type-sha256-caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec) | | `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`{ type: "feedback/message-delete" }`](#persistence-type-sha256-3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796) | | `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`{ type: "feedback/message-put" }`](#persistence-type-sha256-b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf) | | `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`{ type: "feedback/record" }`](#persistence-type-sha256-b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8) | @@ -59,7 +58,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) | | `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) | | `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) | -| `event:session/title-llm-request` | event | `fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172) | +| `event:session/title-llm-request` | event | `eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255) | | `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) | | `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) | | `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) | @@ -81,7 +80,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:tool/result` | event | `7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397` | [`{ type: "tool/result" }`](#persistence-type-sha256-7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397) | | `event:turn/end` | event | `0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce` | [`{ type: "turn/end" }`](#persistence-type-sha256-0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce) | | `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`{ type: "turn/start" }`](#persistence-type-sha256-aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8) | -| `event:user/message` | event | `c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5` | [`{ type: "user/message" }`](#persistence-type-sha256-c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5) | +| `event:user/message` | event | `0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b` | [`{ type: "user/message" }`](#persistence-type-sha256-0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b) | | `event:web/deepseek-search-llm-request` | event | `0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52` | [`{ type: "web/deepseek-search-llm-request" }`](#persistence-type-sha256-0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52) | | `event:working-directory/change` | event | `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` | [`{ type: "working-directory/change" }`](#persistence-type-sha256-124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3) | | `event:workspace/changes` | event | `e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72` | [`{ type: "workspace/changes" }`](#persistence-type-sha256-e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72) | @@ -186,25 +185,7 @@ Sources: [`packages/core/session/src/types.ts:439`](../packages/core/session/src } ``` -Source: [`packages/core/agent/src/types.ts:124`](../packages/core/agent/src/types.ts) - - - -#### `agent/retry` — log-only - -```ts persistence-catalog -/** An admitted retry control, correlated with its durable inbox input; never enters model history. */ -'agent/retry': { - turn: number - step: number - inputId: MessageId - source: Extract -} -``` - -Types: [MessageSource](subsystems/core.md) - -Source: [`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/types.ts) +Source: [`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) ### `agent-preset/*` @@ -719,7 +700,7 @@ Source: [`packages/llm/llm-retry/src/types.ts:11`](../packages/llm/llm-retry/src 'model/selection': ModelSelection ``` -Source: [`packages/core/agent/src/types.ts:135`](../packages/core/agent/src/types.ts) +Source: [`packages/core/agent/src/types.ts:121`](../packages/core/agent/src/types.ts) ### `permission/*` @@ -1424,14 +1405,6 @@ SHA-256: `829a2fb3be6efd4ba36aef8ed2d381a4c6fb2a0aaf50c823a6f3f8290786b809` `"agent/inbox/spliced"` - - -### `"agent/retry"` - -SHA-256: `30093f9f63a120cb932f07577594c04afcec3f0bd02859154a6e5b0e79a46359` - -`"agent/retry"` - ### `"allowed-once"` @@ -3410,7 +3383,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:55`](../packages/web | `body` | required | [`{ max_tokens, messages, model, thinking?, … }`](#persistence-type-sha256-9c001ecb5e1d3c3894410db2a1e383842e5f8454fb52182338686158abc1b3ee) | | `endpoint` | required | `string` | - + @@ -3418,7 +3391,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:55`](../packages/web ### `DeveloperMessage` -SHA-256: `0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc` +SHA-256: `5ca953e599fc0c19c32a9a8c7e3cd57fcfaf0b91d6bd4a4d007b3be37b33dddd` Sources: [`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message.ts) @@ -3427,7 +3400,7 @@ Sources: [`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | required | `string` | | `role` | required | `"developer"` | -| `source` | required | [`MessageSource`](#persistence-type-sha256-e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c) | +| `source` | required | [`MessageSource`](#persistence-type-sha256-8e1f9a193d6937a5909f4b2576a2b8392e8fc14652683e1e60b833d5ba39fc3e) | Source compatibility: `source` — `session-source-attribution` v1; `session.developer-message.source`; `kind`; `preserve`. @@ -3833,7 +3806,7 @@ Array of [`ImageOffloadTarget`](#persistence-type-sha256-1cde1bedde79c5e8a56aee9 SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c` -Sources: [`packages/core/agent/src/types.ts:47`](../packages/core/agent/src/types.ts) +Sources: [`packages/core/agent/src/types.ts:40`](../packages/core/agent/src/types.ts) One of: @@ -4126,7 +4099,7 @@ Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/sr | `step` | required | `number` | | `turn` | required | `number` | - + @@ -4134,15 +4107,15 @@ Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/sr ### `Message` -SHA-256: `7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2` +SHA-256: `c52b170222374a79f2c1248606e20ca64ef1143f531d89a49f011dfe46f44f67` Sources: [`packages/llm/llm/src/message.ts:196`](../packages/llm/llm/src/message.ts) One of: - [`ToolResultMessage`](#persistence-type-sha256-4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731) -- [`DeveloperMessage`](#persistence-type-sha256-0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc) -- [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) +- [`DeveloperMessage`](#persistence-type-sha256-5ca953e599fc0c19c32a9a8c7e3cd57fcfaf0b91d6bd4a4d007b3be37b33dddd) +- [`UserMessage`](#persistence-type-sha256-183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156) - [`SystemMessage`](#persistence-type-sha256-09f3f8dc417f278257202714132f98ba7b52957d7b558c4a04456603890eb363) - [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) @@ -4219,7 +4192,7 @@ One of: - `"negative"` - `"positive"` - + @@ -4227,7 +4200,7 @@ One of: ### `MessageSource` -SHA-256: `e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c` +SHA-256: `8e1f9a193d6937a5909f4b2576a2b8392e8fc14652683e1e60b833d5ba39fc3e` Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message.ts) @@ -4289,8 +4262,7 @@ Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message | `repeat-tool-reminder` | `"relay"` | none | [`{ kind: "repeat-tool-reminder", form: "relay" }`](#persistence-type-sha256-4991dfc415c9efebc36fa6969e1798c7d2e42396cc1e6e0ac7d7c89c5c5c4aea) | | `repeat-tool-reminder` | `"snapshot"` | `sections`: [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | [`{ kind: "repeat-tool-reminder", form: "snapshot" }`](#persistence-type-sha256-db06ef35b27a38425046b2228fa6bce9582abe140d781d363137eb2f4d233a1c) | | `repeat-tool-reminder` | not declared | none | [`{ kind: "repeat-tool-reminder" }`](#persistence-type-sha256-b062078e899958f99ae464514578b8c093924dfe34bd9a835932b420ff2150b1) | -| `retry` | not declared | none | [`{ kind: "retry" }`](#persistence-type-sha256-414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9) | -| `retry` | not declared | `rpcId`: `string` | [`{ clientTimeZone?, kind: "retry", principal?, rpcId }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | +| `retry` | not declared | `rpcId`: `string` | [`{ kind: "retry" }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | | `runtime-context` | `"catalog"` | none | [`{ kind: "runtime-context", form: "catalog" }`](#persistence-type-sha256-58015da1c1950a66daf66ae24913d6a1fc6d1d9ac441b155f8847f7f8584ba8e) | | `runtime-context` | `"instructions"` | none | [`{ kind: "runtime-context", form: "instructions" }`](#persistence-type-sha256-0eeb17e1df89b1ebc2902116fe7145e0d321ef0db26e6bdebc802cb8513a01c1) | | `runtime-context` | `"notice"` | `summary`: `string` | [`{ kind: "runtime-context", form: "notice" }`](#persistence-type-sha256-cda2345a9be9d139df49ccbd16dcba716f3440178bf20ff8b801fda1f5eb77dc) | @@ -4354,13 +4326,13 @@ Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message | `webhook` | `"notice"` | `deliveryId`: `string`; `provider`: `string`; `ruleId`: `string`; `source`: `string`; `summary`: `string` | [`{ kind: "webhook", form: "notice" }`](#persistence-type-sha256-9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94) | | `working-directory` | not declared | none | [`{ kind: "working-directory" }`](#persistence-type-sha256-2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5) | - + ### `Message[]` -SHA-256: `2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff` +SHA-256: `e9fad8e4e6a99024b0351f9656cf18296a5b8a769dae6748719b402a5d2ffd17` -Array of [`Message`](#persistence-type-sha256-7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2). +Array of [`Message`](#persistence-type-sha256-c52b170222374a79f2c1248606e20ca64ef1143f531d89a49f011dfe46f44f67). @@ -4391,7 +4363,7 @@ Sources: [`packages/llm/llm/src/message.ts:24`](../packages/llm/llm/src/message. SHA-256: `f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b` -Sources: [`packages/core/agent/src/types.ts:86`](../packages/core/agent/src/types.ts) +Sources: [`packages/core/agent/src/types.ts:79`](../packages/core/agent/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -4744,7 +4716,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/ | `source` | required | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) | | `title` | required | `string` | - + @@ -4752,7 +4724,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/ ### `SessionTitleLlmRequestEventData` -SHA-256: `8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60` +SHA-256: `5a29653980f4013ea268503865080d46e2b31f47327b652e079342ac922cb730` Sources: [`packages/session/session-title-llm/src/index.ts:37`](../packages/session/session-title-llm/src/index.ts) @@ -4760,7 +4732,7 @@ Sources: [`packages/session/session-title-llm/src/index.ts:37`](../packages/sess |---|---|---| | `maxTokens` | required | `number` | | `messageSeqs` | required | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | -| `messages` | required | [`Message[]`](#persistence-type-sha256-2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff) | +| `messages` | required | [`Message[]`](#persistence-type-sha256-e9fad8e4e6a99024b0351f9656cf18296a5b8a769dae6748719b402a5d2ffd17) | | `reasoningEffort` | optional | `string` | | `route` | required | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) | | `system` | required | `string` | @@ -5417,7 +5389,7 @@ Sources: [`packages/core/session/src/types.ts:238`](../packages/core/session/src | `interrupted` | not declared | none | [`{ kind: "interrupted" }`](#persistence-type-sha256-56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23) | | `max-tokens` | not declared | none | [`{ kind: "max-tokens" }`](#persistence-type-sha256-ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7) | - + @@ -5425,7 +5397,7 @@ Sources: [`packages/core/session/src/types.ts:238`](../packages/core/session/src ### `UserMessage` -SHA-256: `9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263` +SHA-256: `183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156` Sources: [`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message.ts) @@ -5434,19 +5406,19 @@ Sources: [`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | required | `string` | | `role` | required | `"user"` | -| `source` | required | [`MessageSource`](#persistence-type-sha256-e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c) | +| `source` | required | [`MessageSource`](#persistence-type-sha256-8e1f9a193d6937a5909f4b2576a2b8392e8fc14652683e1e60b833d5ba39fc3e) | Source compatibility: `source` — `session-source-attribution` v1; `session.user-message.source`; `kind`; `preserve`. Attribution-only additions: `action-cards`, `tmux-context`, `user-question-reply`, `working-directory`. - + ### `UserMessage[]` -SHA-256: `347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6` +SHA-256: `e139090c38f84d8d8d96dab87bf573bb0ab220d39aab891519ed27f27ffc8f82` -Array of [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263). +Array of [`UserMessage`](#persistence-type-sha256-183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156). @@ -5710,17 +5682,6 @@ One of: - `"fork"` - `"fresh"` - - -### `union (2 variants)` - -SHA-256: `671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1` - -| kind | Form property | Other required fields | Full definition | -|---|---|---|---| -| `retry` | not declared | none | [`{ kind: "retry" }`](#persistence-type-sha256-414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9) | -| `retry` | not declared | `rpcId`: `string` | [`{ clientTimeZone?, kind: "retry", principal?, rpcId }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | - ### `union (2 variants)` @@ -6039,21 +6000,6 @@ SHA-256: `cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09` | `mode` | required | `"external"` | | `version` | required | `2` | - - -### `{ clientTimeZone?, kind: "retry", principal?, rpcId }` - -SHA-256: `e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664` - -Sources: [`packages/api/session-controller/src/types.ts:399`](../packages/api/session-controller/src/types.ts) - -| Property | Presence | Type | -|---|---|---| -| `clientTimeZone` | optional | `string` | -| `kind` | required | `"retry"` | -| `principal` | optional | [`Principal`](#persistence-type-sha256-deebfc7c62177ea840f23c6362b8615bc78ffa93377dd5d243c04e541c413af5) | -| `rpcId` | required | `string` | - ### `{ code, name, reason? }` @@ -6357,18 +6303,18 @@ Sources: [`packages/core/session/src/types.ts:398`](../packages/core/session/src | `reason` | required | [`RequestHeaderReason`](#persistence-type-sha256-530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6) | | `startsSeries` | optional | `true` | - + ### `{ headerSeq?, message, step, turn }` -SHA-256: `d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2` +SHA-256: `acbb9d5bfdd3562851984bd42bbb2441bcb58f959a1ae0474d22a6cc60517c43` Sources: [`packages/core/session/src/types.ts:317`](../packages/core/session/src/types.ts) | Property | Presence | Type | |---|---|---| | `headerSeq` | optional | `number` | -| `message` | required | [`DeveloperMessage`](#persistence-type-sha256-0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc) | +| `message` | required | [`DeveloperMessage`](#persistence-type-sha256-5ca953e599fc0c19c32a9a8c7e3cd57fcfaf0b91d6bd4a4d007b3be37b33dddd) | | `step` | required | `number` | | `turn` | required | `number` | @@ -6452,32 +6398,17 @@ Sources: [`packages/core/session/src/types.ts:435`](../packages/core/session/src |---|---|---| | `inherited` | optional | `true` | - - -### `{ inputId, source, step, turn }` - -SHA-256: `2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3` - -Sources: [`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/types.ts) - -| Property | Presence | Type | -|---|---|---| -| `inputId` | required | `string` | -| `source` | required | [`union (2 variants)`](#persistence-type-sha256-671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1) | -| `step` | required | `number` | -| `turn` | required | `number` | - - + ### `{ inserted, outcome?, removedCount?, start, … }` -SHA-256: `c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039` +SHA-256: `21c17014f8879acbe61e6abc51a884504a13d5e2b9c45a4b7e4fba1fd706d1a7` -Sources: [`packages/core/agent/src/types.ts:124`](../packages/core/agent/src/types.ts) +Sources: [`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) | Property | Presence | Type | |---|---|---| -| `inserted` | required | [`UserMessage[]`](#persistence-type-sha256-347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6) | +| `inserted` | required | [`UserMessage[]`](#persistence-type-sha256-e139090c38f84d8d8d96dab87bf573bb0ab220d39aab891519ed27f27ffc8f82) | | `outcome` | optional | `"canceled"` | | `removedCount` | optional | `number` | | `start` | required | `number` | @@ -7225,17 +7156,20 @@ SHA-256: `db06ef35b27a38425046b2228fa6bce9582abe140d781d363137eb2f4d233a1c` | `kind` | required | `"repeat-tool-reminder"` | | `sections` | required | [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | - + ### `{ kind: "retry" }` -SHA-256: `414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9` +SHA-256: `e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664` -Sources: [`packages/core/agent/src/types.ts:24`](../packages/core/agent/src/types.ts) +Sources: [`packages/api/session-controller/src/types.ts:399`](../packages/api/session-controller/src/types.ts) | Property | Presence | Type | |---|---|---| +| `clientTimeZone` | optional | `string` | | `kind` | required | `"retry"` | +| `principal` | optional | [`Principal`](#persistence-type-sha256-deebfc7c62177ea840f23c6362b8615bc78ffa93377dd5d243c04e541c413af5) | +| `rpcId` | required | `string` | @@ -8136,38 +8070,22 @@ SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | `time` | required | `number` | | `type` | required | `"agent-preset/selected"` | - + ### `{ type: "agent/inbox/spliced" }` -SHA-256: `a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664` +SHA-256: `2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee` | Property | Presence | Type | |---|---|---| -| `data` | required | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039) | +| `data` | required | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-21c17014f8879acbe61e6abc51a884504a13d5e2b9c45a4b7e4fba1fd706d1a7) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | | `type` | required | `"agent/inbox/spliced"` | - - - - -### `{ type: "agent/retry" }` - -SHA-256: `d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c` - -| Property | Presence | Type | -|---|---|---| -| `data` | required | [`{ inputId, source, step, turn }`](#persistence-type-sha256-2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3) | -| `ignorable` | optional | `true` | -| `seq` | required | `number` | -| `time` | required | `number` | -| `type` | required | `"agent/retry"` | - @@ -8403,17 +8321,17 @@ SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | `time` | required | `number` | | `type` | required | `"deliverables/presented"` | - + ### `{ type: "developer/message" }` -SHA-256: `92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583` +SHA-256: `caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec` | Property | Presence | Type | |---|---|---| -| `data` | required | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2) | +| `data` | required | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-acbb9d5bfdd3562851984bd42bbb2441bcb58f959a1ae0474d22a6cc60517c43) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `sourceEventSeqs` | optional | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8813,17 +8731,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | `time` | required | `number` | | `type` | required | `"session/title"` | - + ### `{ type: "session/title-llm-request" }` -SHA-256: `fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172` +SHA-256: `eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255` | Property | Presence | Type | |---|---|---| -| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60) | +| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-5a29653980f4013ea268503865080d46e2b31f47327b652e079342ac922cb730) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | @@ -9246,17 +9164,17 @@ Sources: [`packages/llm/llm/src/types.ts:476`](../packages/llm/llm/src/types.ts) | `type` | required | `"usage"` | | `usage` | required | [`TokenUsage`](#persistence-type-sha256-fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394) | - + ### `{ type: "user/message" }` -SHA-256: `c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5` +SHA-256: `0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b` | Property | Presence | Type | |---|---|---| -| `data` | required | [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) | +| `data` | required | [`UserMessage`](#persistence-type-sha256-183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `sourceEventSeqs` | optional | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 05543778cb..82a5cad84e 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -25,8 +25,7 @@ | `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`HeaderLine`](#persistence-type-sha256-18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f) | | `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`union (3 variants)`](#persistence-type-sha256-5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc) | | `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`{ type: "agent-preset/selected" }`](#persistence-type-sha256-a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b) | -| `event:agent/inbox/spliced` | event | `a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664) | -| `event:agent/retry` | event | `d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c` | [`{ type: "agent/retry" }`](#persistence-type-sha256-d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c) | +| `event:agent/inbox/spliced` | event | `2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee) | | `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`{ type: "approval/asked" }`](#persistence-type-sha256-3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1) | | `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`{ type: "approval/decided" }`](#persistence-type-sha256-bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3) | | `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`{ type: "approval/policy" }`](#persistence-type-sha256-26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78) | @@ -39,7 +38,7 @@ | `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`{ type: "compaction/start" }`](#persistence-type-sha256-db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965) | | `event:compaction/summary` | event | `e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602` | [`{ type: "compaction/summary" }`](#persistence-type-sha256-e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602) | | `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`{ type: "deliverables/presented" }`](#persistence-type-sha256-13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176) | -| `event:developer/message` | event | `92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583` | [`{ type: "developer/message" }`](#persistence-type-sha256-92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583) | +| `event:developer/message` | event | `caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec` | [`{ type: "developer/message" }`](#persistence-type-sha256-caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec) | | `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`{ type: "feedback/message-delete" }`](#persistence-type-sha256-3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796) | | `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`{ type: "feedback/message-put" }`](#persistence-type-sha256-b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf) | | `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`{ type: "feedback/record" }`](#persistence-type-sha256-b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8) | @@ -61,7 +60,7 @@ | `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) | | `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) | | `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) | -| `event:session/title-llm-request` | event | `fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172) | +| `event:session/title-llm-request` | event | `eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255) | | `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) | | `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) | | `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) | @@ -83,7 +82,7 @@ | `event:tool/result` | event | `7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397` | [`{ type: "tool/result" }`](#persistence-type-sha256-7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397) | | `event:turn/end` | event | `0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce` | [`{ type: "turn/end" }`](#persistence-type-sha256-0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce) | | `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`{ type: "turn/start" }`](#persistence-type-sha256-aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8) | -| `event:user/message` | event | `c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5` | [`{ type: "user/message" }`](#persistence-type-sha256-c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5) | +| `event:user/message` | event | `0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b` | [`{ type: "user/message" }`](#persistence-type-sha256-0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b) | | `event:web/deepseek-search-llm-request` | event | `0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52` | [`{ type: "web/deepseek-search-llm-request" }`](#persistence-type-sha256-0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52) | | `event:working-directory/change` | event | `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` | [`{ type: "working-directory/change" }`](#persistence-type-sha256-124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3) | | `event:workspace/changes` | event | `e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72` | [`{ type: "workspace/changes" }`](#persistence-type-sha256-e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72) | @@ -188,25 +187,7 @@ export type SessionEvent = { } ``` -来源:[`packages/core/agent/src/types.ts:124`](../packages/core/agent/src/types.ts) - - - -#### `agent/retry` — log-only - -```ts persistence-catalog -/** An admitted retry control, correlated with its durable inbox input; never enters model history. */ -'agent/retry': { - turn: number - step: number - inputId: MessageId - source: Extract -} -``` - -类型:[MessageSource](subsystems/core.zh.md) - -来源:[`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) ### `agent-preset/*` @@ -721,7 +702,7 @@ export type SessionEvent = { 'model/selection': ModelSelection ``` -来源:[`packages/core/agent/src/types.ts:135`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:121`](../packages/core/agent/src/types.ts) ### `permission/*` @@ -1426,14 +1407,6 @@ SHA-256: `829a2fb3be6efd4ba36aef8ed2d381a4c6fb2a0aaf50c823a6f3f8290786b809` `"agent/inbox/spliced"` - - -### `"agent/retry"` - -SHA-256: `30093f9f63a120cb932f07577594c04afcec3f0bd02859154a6e5b0e79a46359` - -`"agent/retry"` - ### `"allowed-once"` @@ -3412,7 +3385,7 @@ SHA-256: `abd6ca62a5ab166553848324a23fef90eed7f59c8021bd63e7ea5bc6fd666b0c` | `body` | 必需 | [`{ max_tokens, messages, model, thinking?, … }`](#persistence-type-sha256-9c001ecb5e1d3c3894410db2a1e383842e5f8454fb52182338686158abc1b3ee) | | `endpoint` | 必需 | `string` | - + @@ -3420,7 +3393,7 @@ SHA-256: `abd6ca62a5ab166553848324a23fef90eed7f59c8021bd63e7ea5bc6fd666b0c` ### `DeveloperMessage` -SHA-256: `0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc` +SHA-256: `5ca953e599fc0c19c32a9a8c7e3cd57fcfaf0b91d6bd4a4d007b3be37b33dddd` 来源:[`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message.ts) @@ -3429,7 +3402,7 @@ SHA-256: `0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | 必需 | `string` | | `role` | 必需 | `"developer"` | -| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c) | +| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-8e1f9a193d6937a5909f4b2576a2b8392e8fc14652683e1e60b833d5ba39fc3e) | 消息源兼容策略:`source` — `session-source-attribution` v1; `session.developer-message.source`; `kind`; `preserve`. @@ -3835,7 +3808,7 @@ SHA-256: `9e2c06d8653585b33d72d5c1a80da79c8a24a88c284409bca15cc0f0ead5ebfb` SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c` -来源:[`packages/core/agent/src/types.ts:47`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:40`](../packages/core/agent/src/types.ts) 以下类型之一: @@ -4128,7 +4101,7 @@ SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` | `step` | 必需 | `number` | | `turn` | 必需 | `number` | - + @@ -4136,15 +4109,15 @@ SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` ### `Message` -SHA-256: `7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2` +SHA-256: `c52b170222374a79f2c1248606e20ca64ef1143f531d89a49f011dfe46f44f67` 来源:[`packages/llm/llm/src/message.ts:196`](../packages/llm/llm/src/message.ts) 以下类型之一: - [`ToolResultMessage`](#persistence-type-sha256-4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731) -- [`DeveloperMessage`](#persistence-type-sha256-0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc) -- [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) +- [`DeveloperMessage`](#persistence-type-sha256-5ca953e599fc0c19c32a9a8c7e3cd57fcfaf0b91d6bd4a4d007b3be37b33dddd) +- [`UserMessage`](#persistence-type-sha256-183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156) - [`SystemMessage`](#persistence-type-sha256-09f3f8dc417f278257202714132f98ba7b52957d7b558c4a04456603890eb363) - [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) @@ -4221,7 +4194,7 @@ SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` - `"negative"` - `"positive"` - + @@ -4229,7 +4202,7 @@ SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` ### `MessageSource` -SHA-256: `e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c` +SHA-256: `8e1f9a193d6937a5909f4b2576a2b8392e8fc14652683e1e60b833d5ba39fc3e` 来源:[`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message.ts) @@ -4291,8 +4264,7 @@ SHA-256: `e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c` | `repeat-tool-reminder` | `"relay"` | 无 | [`{ kind: "repeat-tool-reminder", form: "relay" }`](#persistence-type-sha256-4991dfc415c9efebc36fa6969e1798c7d2e42396cc1e6e0ac7d7c89c5c5c4aea) | | `repeat-tool-reminder` | `"snapshot"` | `sections`: [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | [`{ kind: "repeat-tool-reminder", form: "snapshot" }`](#persistence-type-sha256-db06ef35b27a38425046b2228fa6bce9582abe140d781d363137eb2f4d233a1c) | | `repeat-tool-reminder` | 未声明 | 无 | [`{ kind: "repeat-tool-reminder" }`](#persistence-type-sha256-b062078e899958f99ae464514578b8c093924dfe34bd9a835932b420ff2150b1) | -| `retry` | 未声明 | 无 | [`{ kind: "retry" }`](#persistence-type-sha256-414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9) | -| `retry` | 未声明 | `rpcId`: `string` | [`{ clientTimeZone?, kind: "retry", principal?, rpcId }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | +| `retry` | 未声明 | `rpcId`: `string` | [`{ kind: "retry" }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | | `runtime-context` | `"catalog"` | 无 | [`{ kind: "runtime-context", form: "catalog" }`](#persistence-type-sha256-58015da1c1950a66daf66ae24913d6a1fc6d1d9ac441b155f8847f7f8584ba8e) | | `runtime-context` | `"instructions"` | 无 | [`{ kind: "runtime-context", form: "instructions" }`](#persistence-type-sha256-0eeb17e1df89b1ebc2902116fe7145e0d321ef0db26e6bdebc802cb8513a01c1) | | `runtime-context` | `"notice"` | `summary`: `string` | [`{ kind: "runtime-context", form: "notice" }`](#persistence-type-sha256-cda2345a9be9d139df49ccbd16dcba716f3440178bf20ff8b801fda1f5eb77dc) | @@ -4356,13 +4328,13 @@ SHA-256: `e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c` | `webhook` | `"notice"` | `deliveryId`: `string`; `provider`: `string`; `ruleId`: `string`; `source`: `string`; `summary`: `string` | [`{ kind: "webhook", form: "notice" }`](#persistence-type-sha256-9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94) | | `working-directory` | 未声明 | 无 | [`{ kind: "working-directory" }`](#persistence-type-sha256-2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5) | - + ### `Message[]` -SHA-256: `2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff` +SHA-256: `e9fad8e4e6a99024b0351f9656cf18296a5b8a769dae6748719b402a5d2ffd17` -[`Message`](#persistence-type-sha256-7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2) 的数组。 +[`Message`](#persistence-type-sha256-c52b170222374a79f2c1248606e20ca64ef1143f531d89a49f011dfe46f44f67) 的数组。 @@ -4393,7 +4365,7 @@ SHA-256: `719c51f67074f97034a6c94297fe1f4b5d3c8d392c57586dab732aa5398c7a91` SHA-256: `f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b` -来源:[`packages/core/agent/src/types.ts:86`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:79`](../packages/core/agent/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -4746,7 +4718,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` | `source` | 必需 | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) | | `title` | 必需 | `string` | - + @@ -4754,7 +4726,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` ### `SessionTitleLlmRequestEventData` -SHA-256: `8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60` +SHA-256: `5a29653980f4013ea268503865080d46e2b31f47327b652e079342ac922cb730` 来源:[`packages/session/session-title-llm/src/index.ts:37`](../packages/session/session-title-llm/src/index.ts) @@ -4762,7 +4734,7 @@ SHA-256: `8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60` |---|---|---| | `maxTokens` | 必需 | `number` | | `messageSeqs` | 必需 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | -| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff) | +| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-e9fad8e4e6a99024b0351f9656cf18296a5b8a769dae6748719b402a5d2ffd17) | | `reasoningEffort` | 可选 | `string` | | `route` | 必需 | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) | | `system` | 必需 | `string` | @@ -5419,7 +5391,7 @@ SHA-256: `f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402` | `interrupted` | 未声明 | 无 | [`{ kind: "interrupted" }`](#persistence-type-sha256-56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23) | | `max-tokens` | 未声明 | 无 | [`{ kind: "max-tokens" }`](#persistence-type-sha256-ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7) | - + @@ -5427,7 +5399,7 @@ SHA-256: `f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402` ### `UserMessage` -SHA-256: `9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263` +SHA-256: `183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156` 来源:[`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message.ts) @@ -5436,19 +5408,19 @@ SHA-256: `9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | 必需 | `string` | | `role` | 必需 | `"user"` | -| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c) | +| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-8e1f9a193d6937a5909f4b2576a2b8392e8fc14652683e1e60b833d5ba39fc3e) | 消息源兼容策略:`source` — `session-source-attribution` v1; `session.user-message.source`; `kind`; `preserve`. 仅表示归属的新增 kind:`action-cards`, `tmux-context`, `user-question-reply`, `working-directory`. - + ### `UserMessage[]` -SHA-256: `347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6` +SHA-256: `e139090c38f84d8d8d96dab87bf573bb0ab220d39aab891519ed27f27ffc8f82` -[`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) 的数组。 +[`UserMessage`](#persistence-type-sha256-183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156) 的数组。 @@ -5712,17 +5684,6 @@ SHA-256: `50cf02d875f8a1f1411f4a6fbcffab9120ccef5f74a47331fbf29480e5f68f48` - `"fork"` - `"fresh"` - - -### `union (2 variants)` - -SHA-256: `671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1` - -| kind | form 属性 | 其他必需字段 | 完整定义 | -|---|---|---|---| -| `retry` | 未声明 | 无 | [`{ kind: "retry" }`](#persistence-type-sha256-414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9) | -| `retry` | 未声明 | `rpcId`: `string` | [`{ clientTimeZone?, kind: "retry", principal?, rpcId }`](#persistence-type-sha256-e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664) | - ### `union (2 variants)` @@ -6041,21 +6002,6 @@ SHA-256: `cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09` | `mode` | 必需 | `"external"` | | `version` | 必需 | `2` | - - -### `{ clientTimeZone?, kind: "retry", principal?, rpcId }` - -SHA-256: `e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664` - -来源:[`packages/api/session-controller/src/types.ts:399`](../packages/api/session-controller/src/types.ts) - -| 属性 | 存在性 | 类型 | -|---|---|---| -| `clientTimeZone` | 可选 | `string` | -| `kind` | 必需 | `"retry"` | -| `principal` | 可选 | [`Principal`](#persistence-type-sha256-deebfc7c62177ea840f23c6362b8615bc78ffa93377dd5d243c04e541c413af5) | -| `rpcId` | 必需 | `string` | - ### `{ code, name, reason? }` @@ -6359,18 +6305,18 @@ SHA-256: `ad0c38a0adb82a94c925456df41586d9b5f9202fca18593eae2213d4445b4585` | `reason` | 必需 | [`RequestHeaderReason`](#persistence-type-sha256-530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6) | | `startsSeries` | 可选 | `true` | - + ### `{ headerSeq?, message, step, turn }` -SHA-256: `d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2` +SHA-256: `acbb9d5bfdd3562851984bd42bbb2441bcb58f959a1ae0474d22a6cc60517c43` 来源:[`packages/core/session/src/types.ts:317`](../packages/core/session/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| | `headerSeq` | 可选 | `number` | -| `message` | 必需 | [`DeveloperMessage`](#persistence-type-sha256-0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc) | +| `message` | 必需 | [`DeveloperMessage`](#persistence-type-sha256-5ca953e599fc0c19c32a9a8c7e3cd57fcfaf0b91d6bd4a4d007b3be37b33dddd) | | `step` | 必需 | `number` | | `turn` | 必需 | `number` | @@ -6454,32 +6400,17 @@ SHA-256: `17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3` |---|---|---| | `inherited` | 可选 | `true` | - - -### `{ inputId, source, step, turn }` - -SHA-256: `2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3` - -来源:[`packages/core/agent/src/types.ts:113`](../packages/core/agent/src/types.ts) - -| 属性 | 存在性 | 类型 | -|---|---|---| -| `inputId` | 必需 | `string` | -| `source` | 必需 | [`union (2 variants)`](#persistence-type-sha256-671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1) | -| `step` | 必需 | `number` | -| `turn` | 必需 | `number` | - - + ### `{ inserted, outcome?, removedCount?, start, … }` -SHA-256: `c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039` +SHA-256: `21c17014f8879acbe61e6abc51a884504a13d5e2b9c45a4b7e4fba1fd706d1a7` -来源:[`packages/core/agent/src/types.ts:124`](../packages/core/agent/src/types.ts) +来源:[`packages/core/agent/src/types.ts:110`](../packages/core/agent/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| -| `inserted` | 必需 | [`UserMessage[]`](#persistence-type-sha256-347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6) | +| `inserted` | 必需 | [`UserMessage[]`](#persistence-type-sha256-e139090c38f84d8d8d96dab87bf573bb0ab220d39aab891519ed27f27ffc8f82) | | `outcome` | 可选 | `"canceled"` | | `removedCount` | 可选 | `number` | | `start` | 必需 | `number` | @@ -7227,17 +7158,20 @@ SHA-256: `db06ef35b27a38425046b2228fa6bce9582abe140d781d363137eb2f4d233a1c` | `kind` | 必需 | `"repeat-tool-reminder"` | | `sections` | 必需 | [`ContextSnapshotSection[]`](#persistence-type-sha256-c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262) | - + ### `{ kind: "retry" }` -SHA-256: `414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9` +SHA-256: `e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664` -来源:[`packages/core/agent/src/types.ts:24`](../packages/core/agent/src/types.ts) +来源:[`packages/api/session-controller/src/types.ts:399`](../packages/api/session-controller/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| +| `clientTimeZone` | 可选 | `string` | | `kind` | 必需 | `"retry"` | +| `principal` | 可选 | [`Principal`](#persistence-type-sha256-deebfc7c62177ea840f23c6362b8615bc78ffa93377dd5d243c04e541c413af5) | +| `rpcId` | 必需 | `string` | @@ -8138,38 +8072,22 @@ SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | `time` | 必需 | `number` | | `type` | 必需 | `"agent-preset/selected"` | - + ### `{ type: "agent/inbox/spliced" }` -SHA-256: `a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664` +SHA-256: `2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039) | +| `data` | 必需 | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-21c17014f8879acbe61e6abc51a884504a13d5e2b9c45a4b7e4fba1fd706d1a7) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | | `type` | 必需 | `"agent/inbox/spliced"` | - - - - -### `{ type: "agent/retry" }` - -SHA-256: `d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c` - -| 属性 | 存在性 | 类型 | -|---|---|---| -| `data` | 必需 | [`{ inputId, source, step, turn }`](#persistence-type-sha256-2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3) | -| `ignorable` | 可选 | `true` | -| `seq` | 必需 | `number` | -| `time` | 必需 | `number` | -| `type` | 必需 | `"agent/retry"` | - @@ -8405,17 +8323,17 @@ SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | `time` | 必需 | `number` | | `type` | 必需 | `"deliverables/presented"` | - + ### `{ type: "developer/message" }` -SHA-256: `92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583` +SHA-256: `caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2) | +| `data` | 必需 | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-acbb9d5bfdd3562851984bd42bbb2441bcb58f959a1ae0474d22a6cc60517c43) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `sourceEventSeqs` | 可选 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8815,17 +8733,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | `time` | 必需 | `number` | | `type` | 必需 | `"session/title"` | - + ### `{ type: "session/title-llm-request" }` -SHA-256: `fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172` +SHA-256: `eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60) | +| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-5a29653980f4013ea268503865080d46e2b31f47327b652e079342ac922cb730) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | @@ -9248,17 +9166,17 @@ SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515` | `type` | 必需 | `"usage"` | | `usage` | 必需 | [`TokenUsage`](#persistence-type-sha256-fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394) | - + ### `{ type: "user/message" }` -SHA-256: `c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5` +SHA-256: `0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`UserMessage`](#persistence-type-sha256-9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263) | +| `data` | 必需 | [`UserMessage`](#persistence-type-sha256-183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `sourceEventSeqs` | 可选 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml b/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml index 502105b258..3329a50ca3 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml +++ b/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml @@ -9,8 +9,8 @@ en: 32c7e9cc4a9f86b4 zh: f1cb7169df15e8e4 /2026-10-27-retry-control-input/summary: - en: e3a11c473fcdc452 - zh: 02ce33e05c10de71 + en: 77dd9a1b2db8d968 + zh: c3d0111e34b27fe3 /2026-10-27-retry-control-input/table-of-contents: en: 6cf314a069ceb451 zh: 9adff678e9ecab3b @@ -18,11 +18,11 @@ en: 6907136f28e48c90 zh: 02f8298485f3d8e7 /2026-10-27-retry-control-input/compatibility: - en: ffc72b74956201d6 - zh: 78cfe9d2353ca739 + en: 6dee29fbf0f77087 + zh: 72e0cfdef298a75e /2026-10-27-retry-control-input/verification: - en: fbd40f2d4da56e1e - zh: 82546f33ad58b08b + en: fff40611ec2aea13 + zh: 4811164ae5c33b29 /2026-10-27-retry-control-input/dev-note: en: b75c8458928b72b9 zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.md b/docs/persistence-changes/2026-10-27-retry-control-input.md index 10ebe9fe04..7b5caff111 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.md +++ b/docs/persistence-changes/2026-10-27-retry-control-input.md @@ -9,7 +9,7 @@ English | [中文](2026-10-27-retry-control-input.zh.md) ## Summary -Add an explicit retry control source and the required agent/retry acceptance event, correlated with durable inbox identity, turn, and step. +Add the retry message source for empty Session API continuation prompts; ordinary user/message records retain the input identity and caller metadata. ## Table of Contents @@ -28,35 +28,31 @@ baseline: false changes: - root: "event:agent/inbox/spliced" previous: "2026-10-25-skill-catalog-incomplete" - after: "a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664" - decision: same-version - - root: "event:agent/retry" - previous: null - after: "d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c" + after: "2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee" decision: same-version - root: "event:developer/message" previous: "2026-10-25-skill-catalog-incomplete" - after: "92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583" + after: "caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec" decision: same-version - root: "event:session/title-llm-request" previous: "2026-10-25-skill-catalog-incomplete" - after: "fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172" + after: "eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255" decision: same-version - root: "event:user/message" previous: "2026-10-25-skill-catalog-incomplete" - after: "c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5" + after: "0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b" decision: same-version ``` ## Compatibility -Updated readers preserve older logs and ordinary empty user-message admission. Older readers refuse the new required agent/retry event before append or tail repair. A queued control predating acceptance still has the existing identified empty UserMessage representation; an older Loop can consume it using its ordinary empty-input behavior, retaining inbox identity and adding no user text. API and both SDK consumers recognize retry controls and the acceptance event together. The current Session format version is retained; mixed ordinary messages and retry events remain readable by the updated reader. +The retry source is producer-owned metadata on the existing empty UserMessage representation. AgentLoop, model-history derivation, and SDK prompt behavior are unchanged. Older readers preserve extensible source metadata and consume these messages through ordinary user-message admission; they may display an empty context row. Updated readers retain historical ordinary messages and hide only empty retry messages in Chat. Existing event discriminators and the current Session version remain unchanged; no new required event is introduced. ## Verification -Focused AgentLoop, Agent, Session API, and SDK-server tests passed: 91 files, 1806 tests, with 100% statements, branches, functions, and lines for agent.ts and continuation.ts. Browser continuation replay and TypeScript/Python SDK scheduler-recovery replay passed; assertions retain one original user message, correlate retry input identity, preserve tool results, and close new turns normally. Current-event and native-source admission tests cover unknown required-event refusal without mutation and preservation of extensible source metadata. Build, aggregate checks, and typed lint passed. doc-sync passed 42 gates; its remaining repository-reference failure is the unchanged python/sdk/uv.lock:113 commit hash. +Source review confirms AgentLoop and SDK files match the PR base. Regression cases cover consecutive failures, request deduplication, normal turn settlement, empty retry-message admission, unrelated empty messages, and replay/live/prepend Chat rendering. Updated behavior checks will run after publication as requested. ## Dev Note diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.schema.json b/docs/persistence-changes/2026-10-27-retry-control-input.schema.json index 54eea6ef36..3b89fbb438 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.schema.json +++ b/docs/persistence-changes/2026-10-27-retry-control-input.schema.json @@ -1,11 +1,10 @@ { "formatVersion": 2, "roots": [ - {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664"}, - {"key":"event:agent/retry","kind":"event","event":"agent/retry","surface":false,"digest":"d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c"}, - {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583"}, - {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172"}, - {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5"} + {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee"}, + {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec"}, + {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255"}, + {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b"} ], "types": [], "nodes": { @@ -17,15 +16,16 @@ "07e1a58c58b593f5": {"kind":"object","properties":[{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "09655b34d7f3cf1e": {"kind":"literal","value":"system-prompt"}, "09f3f8dc417f2782": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"8af841fc9b265ade","optional":false},{"name":"source","type":"65a8400e451e667e","optional":false}],"indices":[]}, + "0d6dd68f18afee7b": {"kind":"object","properties":[{"name":"data","type":"183ccd2e6b70ddf9","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, "0ed8d1108fe7a5e1": {"kind":"object","properties":[{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, "0eeb17e1df89b1eb": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, - "0ef3072084202b08": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"e3e9f4d1e245b470","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "0f46153645c29784": {"kind":"object","properties":[{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "0fe52387f6505270": {"kind":"literal","value":"plan-mode"}, "11638dc58a31496f": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "12d5808fac1996b9": {"kind":"object","properties":[{"name":"attachment","type":"b0b0eb1b16490f6b","optional":false},{"name":"offloaded","type":"dacf3d5f3e4ff619","optional":true},{"name":"type","type":"fb8b6a2e53cb9cf4","optional":false}],"indices":[]}, "1379f0b72275994d": {"kind":"literal","value":"developer/message"}, "16d0507cb94b4cfc": {"kind":"literal","value":"relay"}, + "183ccd2e6b70ddf9": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"8e1f9a193d6937a5","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "1922cd79a70806c1": {"kind":"literal","value":"skill-invocation"}, "1938c8c20f1cba7c": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, "1e709346005826eb": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, @@ -33,6 +33,7 @@ "1f1385dbc3e875c0": {"kind":"literal","value":"repeat-tool-reminder"}, "1fdad624334d3548": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, "2152caf1a71e588e": {"kind":"object","properties":[{"name":"kind","type":"2e4debca12f2c449","optional":false},{"name":"messageId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderName","type":"4cd48f3e5108bd6e","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "21c17014f8879acb": {"kind":"object","properties":[{"name":"inserted","type":"e139090c38f84d8d","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, "2235d957bf4b7c6c": {"kind":"object","properties":[{"name":"kind","type":"7b1ce60193fa8b69","optional":false}],"indices":[]}, "224aa11d4d5f88c4": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, "23af3759678a30de": {"kind":"literal","value":"dsh-session-title-llm"}, @@ -44,19 +45,16 @@ "28936c614bd4e7e2": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "290f32bfd81ff85a": {"kind":"object","properties":[{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, "297fbd8067d7815a": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, - "2a668a48dec17397": {"kind":"object","properties":[{"name":"inputId","type":"4cd48f3e5108bd6e","optional":false},{"name":"source","type":"671e72b8036a3afb","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "2aad1179f9576314": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "2ab12a0e345658f9": {"kind":"array","element":"83da72c6857601c7"}, + "2e0cf9dbeea369bc": {"kind":"object","properties":[{"name":"data","type":"21c17014f8879acb","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, "2e4debca12f2c449": {"kind":"literal","value":"team-message"}, "2f1e6a7ebace43d7": {"kind":"union","types":["3f74ba74acd10644","58f94390b982f8a5","de30b1fa12c75f81","ebb768d85ec87fb6"]}, "2f3f201c8846e19c": {"kind":"literal","value":"tool-goal"}, - "2f5b14bc2ccbe881": {"kind":"array","element":"7cf091467eb28887"}, - "30093f9f63a120cb": {"kind":"literal","value":"agent/retry"}, "312aee2f0c42aabb": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "31907de956375182": {"kind":"object","properties":[{"name":"compactionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"6b23ed9ce1b397e6","optional":false},{"name":"sourceCommandId","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, "3247e566f4340029": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "335e242de1fcc17b": {"kind":"union","types":["bcf0caf62d964b2f","e438a9d4fb883b3e"]}, - "347c1718600bbb38": {"kind":"array","element":"9632024d168f8169"}, "383cf73ae4748e5b": {"kind":"literal","value":"recall"}, "3b04790e08b35241": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, "3b4f2c9ef2c1217e": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"bb94f3217aa56c88","optional":false}],"indices":[]}, @@ -66,7 +64,6 @@ "3e2ece4967cfebe9": {"kind":"literal","value":"next-step"}, "3f74ba74acd10644": {"kind":"literal","value":"image/jpeg"}, "408927267422a7b2": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, - "414a44a96f06339f": {"kind":"object","properties":[{"name":"kind","type":"63ba8895cc09e469","optional":false}],"indices":[]}, "43e311235aa537df": {"kind":"object","properties":[{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"83d6916021eede8f","optional":false}],"indices":[]}, "45f005e6bbb76452": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "4991dfc415c9efeb": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, @@ -86,8 +83,10 @@ "587bb11da730e06a": {"kind":"literal","value":"session/title-llm-request"}, "589d0d91239681f8": {"kind":"literal","value":"developer"}, "58f94390b982f8a5": {"kind":"literal","value":"image/png"}, + "5a29653980f4013e": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"e9fad8e4e6a99024","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "5bb69b88517f6214": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "5bc66269cfaf614c": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, + "5ca953e599fc0c19": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"8e1f9a193d6937a5","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "5cdbf342b7748d15": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, "5d03ba38734809bc": {"kind":"array","element":"0657d3ca5ac4e7bf"}, "5d7a64ed6aac5551": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, @@ -97,7 +96,6 @@ "63ba8895cc09e469": {"kind":"literal","value":"retry"}, "65a8400e451e667e": {"kind":"object","properties":[{"name":"kind","type":"09655b34d7f3cf1e","optional":false}],"indices":[]}, "65d583e66518c523": {"kind":"object","properties":[{"name":"entries","type":"aba175fb20ffc711","optional":false},{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"incomplete","type":"dacf3d5f3e4ff619","optional":true},{"name":"kind","type":"6ebcc2dbec9f2dab","optional":false},{"name":"update","type":"dacf3d5f3e4ff619","optional":true}],"indices":[]}, - "671e72b8036a3afb": {"kind":"union","types":["414a44a96f06339f","e103228e063b4762"]}, "672c4aa2a9b736e3": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"1922cd79a70806c1","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "68f44f59e91c4139": {"kind":"literal","value":"user"}, "6b23ed9ce1b397e6": {"kind":"literal","value":"compact-checkpoint"}, @@ -114,7 +112,6 @@ "756b5e23a5cb2c7f": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false}],"indices":[]}, "78a9be340090b735": {"kind":"literal","value":"tmux-context"}, "7b1ce60193fa8b69": {"kind":"literal","value":"working-directory"}, - "7cf091467eb28887": {"kind":"union","types":["09f3f8dc417f2782","0ef3072084202b08","4b259f7f8f0a44ad","9632024d168f8169","fa87537f4a9c1e25"]}, "7df62978a2b7de85": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, "7eaef7a763ba5b94": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, "7f13860ee89a2d35": {"kind":"literal","value":"session-reference"}, @@ -142,16 +139,14 @@ "8af841fc9b265ade": {"kind":"literal","value":"system"}, "8c1c4f72755a78ea": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "8c627d7a45fa406c": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "8e1f9a193d6937a5": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","65d583e66518c523","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e103228e063b4762","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, "8e611bf46821e500": {"kind":"object","properties":[{"name":"tool","type":"fcf427290c6213c2","optional":true},{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"85f9b505b7bdff1f","optional":false}],"indices":[]}, - "8eda0195dd1d4a3f": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"2f5b14bc2ccbe881","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "8f324cea3916c051": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, "8fcb0b44a575e998": {"kind":"object","properties":[{"name":"kind","type":"a720757f704a2512","optional":false}],"indices":[]}, "904bf499d98be4a3": {"kind":"union","types":["7382c4fc95fe8c5d","b765ee31033cea46","f6cb462f808309e3"]}, - "92b82e8b7bbb38b3": {"kind":"object","properties":[{"name":"data","type":"d582ddae3f21edf5","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, "938f7c7357bc403e": {"kind":"primitive","type":"null"}, "95a3ffccbf3c8afc": {"kind":"literal","value":"owner"}, "95c0e88fcf0bafb2": {"kind":"literal","value":"assistant"}, - "9632024d168f8169": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"e3e9f4d1e245b470","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "9645cf93769a6dfb": {"kind":"literal","value":"user-question-reply"}, "96bfb916a727cd44": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, "96f4a9c81fc0f940": {"kind":"union","types":["3e2ece4967cfebe9","62ecb62dc0fe1b5e"]}, @@ -166,13 +161,13 @@ "a16e567c8cd2c55c": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, "a2066bd52f61218b": {"kind":"object","properties":[{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, "a3c6f1ea38e9a61d": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, - "a6d8c25f4b2ca2d1": {"kind":"object","properties":[{"name":"data","type":"c66c6c3b411abf46","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, "a720757f704a2512": {"kind":"literal","value":"tool-registry"}, "a9059a81430b3207": {"kind":"literal","value":"hooks-claude-code"}, "aa2bf9e82b9fdeb9": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, "aa93010d0e521ad7": {"kind":"primitive","type":"boolean"}, "aae343bc3d663c83": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"9ac52f0f84fec2eb","optional":false}],"indices":[]}, "aba175fb20ffc711": {"kind":"array","element":"83079c8a3f733ac3"}, + "acbb9d5bfdd35628": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"5ca953e599fc0c19","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "ada310bf0bdb8fed": {"kind":"object","properties":[{"name":"kind","type":"68f44f59e91c4139","optional":false}],"indices":[]}, "ade057f4fdc387ae": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false}],"indices":[]}, "aec150797b655c5f": {"kind":"literal","value":"catalog"}, @@ -197,17 +192,15 @@ "bf16d2e98a7f1389": {"kind":"literal","value":"subagent-settled"}, "c1f0f17b6493b85d": {"kind":"object","properties":[{"name":"arguments","type":"4cd48f3e5108bd6e","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"1f0133217eeeffd8","optional":false}],"indices":[]}, "c5228b7a55498d84": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "c52b170222374a79": {"kind":"union","types":["09f3f8dc417f2782","183ccd2e6b70ddf9","4b259f7f8f0a44ad","5ca953e599fc0c19","fa87537f4a9c1e25"]}, "c55827fba7018198": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"9645cf93769a6dfb","optional":false},{"name":"outcome","type":"85d6d96ff8842974","optional":false}],"indices":[]}, "c5687b1313206a6f": {"kind":"literal","value":"cordis-host-runner"}, - "c5bbcb3435908af2": {"kind":"object","properties":[{"name":"data","type":"9632024d168f8169","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, - "c66c6c3b411abf46": {"kind":"object","properties":[{"name":"inserted","type":"347c1718600bbb38","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, "c748fb42c80a1200": {"kind":"array","element":"86e1e384495ee09a"}, + "caad8a860d58ff99": {"kind":"object","properties":[{"name":"data","type":"acbb9d5bfdd35628","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, "cb1c8959e1726f82": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, "cda2345a9be9d139": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "d4e4d575abeb4b72": {"kind":"literal","value":"canceled"}, - "d582ddae3f21edf5": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"0ef3072084202b08","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "d85590a5675c6e2f": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, - "d8dde9629b7323b0": {"kind":"object","properties":[{"name":"data","type":"2a668a48dec17397","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"30093f9f63a120cb","optional":false}],"indices":[]}, "d9c462b9f62fdd3e": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"a13d8f379222ece8","optional":false}],"indices":[]}, "d9edf1862e4471d6": {"kind":"literal","value":"schedule"}, "dacf3d5f3e4ff619": {"kind":"literal","value":true}, @@ -224,10 +217,12 @@ "deebfc7c62177ea8": {"kind":"object","properties":[{"name":"kind","type":"95a3ffccbf3c8afc","optional":false}],"indices":[]}, "df843efd87c78cf8": {"kind":"array","element":"4f91f187baeabef5"}, "e103228e063b4762": {"kind":"object","properties":[{"name":"clientTimeZone","type":"4cd48f3e5108bd6e","optional":true},{"name":"kind","type":"63ba8895cc09e469","optional":false},{"name":"principal","type":"deebfc7c62177ea8","optional":true},{"name":"rpcId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, - "e3e9f4d1e245b470": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","414a44a96f06339f","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","65d583e66518c523","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e103228e063b4762","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, + "e139090c38f84d8d": {"kind":"array","element":"183ccd2e6b70ddf9"}, "e438a9d4fb883b3e": {"kind":"literal","value":"append"}, "e7f4cbb3a57f4cbd": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "e8de0f457fed5b9f": {"kind":"literal","value":"notice"}, + "e9fad8e4e6a99024": {"kind":"array","element":"c52b170222374a79"}, + "eb0fc09c936ebe3d": {"kind":"object","properties":[{"name":"data","type":"5a29653980f4013e","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, "ebb768d85ec87fb6": {"kind":"literal","value":"image/webp"}, "edbbfff1128099fd": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "ee577a0cc18664c9": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"8067b0d232c350f1","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, @@ -246,7 +241,6 @@ "f88f1b7b6bb733f6": {"kind":"literal","value":1}, "f97f47b0eb87905e": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"78a9be340090b735","optional":false}],"indices":[]}, "fa87537f4a9c1e25": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"95c0e88fcf0bafb2","optional":false},{"name":"source","type":"719c51f67074f970","optional":false}],"indices":[]}, - "fb28ad7d7aabceb5": {"kind":"object","properties":[{"name":"data","type":"8eda0195dd1d4a3f","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, "fb8b6a2e53cb9cf4": {"kind":"literal","value":"image"}, "fbd2c74b346a1595": {"kind":"literal","value":"agent-instructions"}, "fcf427290c6213c2": {"kind":"primitive","type":"never"}, diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.zh.md b/docs/persistence-changes/2026-10-27-retry-control-input.zh.md index 5729a666e7..253b272dde 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.zh.md +++ b/docs/persistence-changes/2026-10-27-retry-control-input.zh.md @@ -9,7 +9,7 @@ kind: persistence-change ## 概述 -新增显式 retry 控制来源与必读 agent/retry 接受事件,关联持久 inbox 身份、轮次和步骤。 +为空 Session API 继续提示词增加 retry 消息来源;普通 user/message 记录保留输入身份和调用方元数据。 ## 目录 @@ -28,35 +28,31 @@ baseline: false changes: - root: "event:agent/inbox/spliced" previous: "2026-10-25-skill-catalog-incomplete" - after: "a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664" - decision: same-version - - root: "event:agent/retry" - previous: null - after: "d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c" + after: "2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee" decision: same-version - root: "event:developer/message" previous: "2026-10-25-skill-catalog-incomplete" - after: "92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583" + after: "caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec" decision: same-version - root: "event:session/title-llm-request" previous: "2026-10-25-skill-catalog-incomplete" - after: "fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172" + after: "eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255" decision: same-version - root: "event:user/message" previous: "2026-10-25-skill-catalog-incomplete" - after: "c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5" + after: "0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b" decision: same-version ``` ## 兼容性 -更新后的读取方保留旧日志含义和普通空用户消息准入。旧读取方会在追加或尾部修复前拒绝新的必读 agent/retry 事件。接受前的排队控制输入仍采用现有带身份的空 UserMessage 表示;旧 Loop 可按其普通空输入行为消费,保留 inbox 身份且不增加用户文本。API 与两套 SDK 消费方同步识别重试控制输入及接受事件。保留当前 Session 格式版本;更新后的读取方可读取普通消息与重试事件混合的日志。 +retry 来源是现有空 UserMessage 表示中的生产方元数据。AgentLoop、模型历史派生与 SDK 提示词行为保持不变。旧读取方保留可扩展来源元数据,并按普通用户消息准入消费;旧界面可能显示空上下文行。更新后的读取方保留历史普通消息,Chat 仅隐藏空重试消息。现有事件判别标签与当前 Session 版本不变,不新增必读事件。 ## 验证 -AgentLoop、Agent、Session API 与 SDK 服务端定向测试通过:91 个文件、1806 个测试,agent.ts 和 continuation.ts 的语句、分支、函数及行覆盖率均为 100%。浏览器继续回放与 TypeScript/Python SDK scheduler-recovery 回放通过;断言保留一条原始用户消息、关联重试输入身份、保留工具结果且新轮次正常闭合。当前事件与原生来源准入测试覆盖未知必读事件在不修改数据的情况下拒绝,以及保留可扩展来源元数据。构建、聚合检查和类型感知 lint 通过。doc-sync 有 42 项通过;剩余仓库引用失败来自未改动的 python/sdk/uv.lock:113 提交哈希。 +源码审查确认 AgentLoop 与 SDK 文件和 PR 基线一致。回归用例覆盖连续失败、请求去重、正常轮次结算、空重试消息准入、无关空消息,以及回放、实时和前插历史三种 Chat 展示。按要求,更新后的行为检查将在发布后运行。 ## 开发备注 diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index f7630fdb61..b18df4d22b 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -5,8 +5,7 @@ {"key":"JsonlHeaderLine","kind":"header","digest":"18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f"}, {"key":"SessionEventEnvelope","kind":"envelope","digest":"5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc"}, {"key":"event:agent-preset/selected","kind":"event","event":"agent-preset/selected","surface":false,"digest":"a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b"}, - {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664"}, - {"key":"event:agent/retry","kind":"event","event":"agent/retry","surface":false,"digest":"d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c"}, + {"key":"event:agent/inbox/spliced","kind":"event","event":"agent/inbox/spliced","surface":false,"digest":"2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee"}, {"key":"event:approval/asked","kind":"event","event":"approval/asked","surface":false,"digest":"3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1"}, {"key":"event:approval/decided","kind":"event","event":"approval/decided","surface":false,"digest":"bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3"}, {"key":"event:approval/policy","kind":"event","event":"approval/policy","surface":false,"digest":"26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78"}, @@ -19,7 +18,7 @@ {"key":"event:compaction/start","kind":"event","event":"compaction/start","surface":false,"digest":"db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965"}, {"key":"event:compaction/summary","kind":"event","event":"compaction/summary","surface":false,"digest":"e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602"}, {"key":"event:deliverables/presented","kind":"event","event":"deliverables/presented","surface":false,"digest":"13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176"}, - {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583"}, + {"key":"event:developer/message","kind":"event","event":"developer/message","surface":true,"digest":"caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec"}, {"key":"event:feedback/message-delete","kind":"event","event":"feedback/message-delete","surface":false,"digest":"3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796"}, {"key":"event:feedback/message-put","kind":"event","event":"feedback/message-put","surface":false,"digest":"b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf"}, {"key":"event:feedback/record","kind":"event","event":"feedback/record","surface":false,"digest":"b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8"}, @@ -41,7 +40,7 @@ {"key":"event:session-log-deepseek/delivery-accepted","kind":"event","event":"session-log-deepseek/delivery-accepted","surface":false,"digest":"d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9"}, {"key":"event:session/end-seed","kind":"event","event":"session/end-seed","surface":false,"digest":"5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e"}, {"key":"event:session/title","kind":"event","event":"session/title","surface":false,"digest":"1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5"}, - {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172"}, + {"key":"event:session/title-llm-request","kind":"event","event":"session/title-llm-request","surface":false,"digest":"eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255"}, {"key":"event:step/end","kind":"event","event":"step/end","surface":false,"digest":"e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05"}, {"key":"event:step/start","kind":"event","event":"step/start","surface":false,"digest":"4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986"}, {"key":"event:subagent/catalog","kind":"event","event":"subagent/catalog","surface":false,"digest":"53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac"}, @@ -63,7 +62,7 @@ {"key":"event:tool/result","kind":"event","event":"tool/result","surface":true,"digest":"7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397"}, {"key":"event:turn/end","kind":"event","event":"turn/end","surface":false,"digest":"0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce"}, {"key":"event:turn/start","kind":"event","event":"turn/start","surface":false,"digest":"aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8"}, - {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5"}, + {"key":"event:user/message","kind":"event","event":"user/message","surface":true,"digest":"0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b"}, {"key":"event:web/deepseek-search-llm-request","kind":"event","event":"web/deepseek-search-llm-request","surface":false,"digest":"0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52"}, {"key":"event:working-directory/change","kind":"event","event":"working-directory/change","surface":false,"digest":"124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3"}, {"key":"event:workspace/changes","kind":"event","event":"workspace/changes","surface":false,"digest":"e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72"} @@ -95,11 +94,11 @@ {"digest":"0c4205180f75b9ee1e0d878bd9a9851628b5dc18448486389d9c5fdc3f1ccda3","names":[],"sources":[]}, {"digest":"0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52","names":[],"sources":[]}, {"digest":"0d47a8f3d847243c4488e755ad4cab7a04ac5d4621b9c81865524cf3f4d8536f","names":[],"sources":["packages/subagent/tool-subagent/src/model-selection-state.ts:17"]}, + {"digest":"0d6dd68f18afee7bc130aeecc3bb76ee4c1ab472472c76f2f935151f9417681b","names":[],"sources":[]}, {"digest":"0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd","names":[],"sources":[]}, {"digest":"0e7b2a97f54f1afdb8cdfad1635abe9145327db539bfb69b4045c19d8c17755d","names":[],"sources":[]}, {"digest":"0ed8d1108fe7a5e1c3078ef1faea12199a56b6b1216ccaceb0f6103b0c9a7691","names":[],"sources":[]}, {"digest":"0eeb17e1df89b1ebc2902116fe7145e0d321ef0db26e6bdebc802cb8513a01c1","names":[],"sources":[]}, - {"digest":"0ef3072084202b084b2a25678fe9014ec339ee3797bc0bb164538ff3fa5e3fcc","names":["packages/llm/llm/src/message.ts#DeveloperMessage"],"sources":["packages/llm/llm/src/message.ts:157"]}, {"digest":"0f46153645c297846a3fad3911636659ca4717a5e7d71c9f50bffacd312115ef","names":[],"sources":["packages/attachment/attachment/src/types.ts:28"]}, {"digest":"0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce","names":[],"sources":[]}, {"digest":"0fe52387f65052702ed9d7cd59db826468936595d1b3eec207e7a823adc719db","names":[],"sources":[]}, @@ -123,6 +122,7 @@ {"digest":"16d0507cb94b4cfc84a87446b1ba7652d6ec1d65ee1684789e747e30d542800e","names":[],"sources":[]}, {"digest":"17aae616ef020b8b9f011ec398ec935450fb06d3e2a0722c456f63aeaf5ff728","names":[],"sources":[]}, {"digest":"17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3","names":[],"sources":["packages/core/session/src/types.ts:435"]}, + {"digest":"183ccd2e6b70ddf9cc90cc4978bac89e6f38419caa975de020da305f8cb24156","names":["packages/llm/llm/src/message.ts#UserMessage"],"sources":["packages/llm/llm/src/message.ts:162"]}, {"digest":"18dc21f12bbac8ade8377e6fbb54179e20c3d955d73fba3244c57fb6bf001df0","names":["packages/schedule/schedule/src/types.ts#ScheduleDeleteChange"],"sources":["packages/schedule/schedule/src/types.ts:230"]}, {"digest":"18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f","names":["packages/session/session-persistence-jsonl/src/format.ts#HeaderLine"],"sources":["packages/session/session-persistence-jsonl/src/format.ts:83"]}, {"digest":"18fcdd273a2f2f9d813a3be5e71df5903a963a5c88e46b90b14de6e2db890da0","names":[],"sources":[]}, @@ -155,6 +155,7 @@ {"digest":"20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5","names":[],"sources":["packages/plan/plan-mode/src/index.ts:53"]}, {"digest":"2142e391b9ccb60d487afcf373ce74db98d952f69e8289eddb352a7e16e996c5","names":[],"sources":[]}, {"digest":"2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0","names":["packages/experimental/agent-team/src/types.ts#TeamMessageSource"],"sources":["packages/experimental/agent-team/src/types.ts:138"]}, + {"digest":"21c17014f8879acbe61e6abc51a884504a13d5e2b9c45a4b7e4fba1fd706d1a7","names":[],"sources":["packages/core/agent/src/types.ts:110"]}, {"digest":"21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d","names":[],"sources":[]}, {"digest":"2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5","names":[],"sources":["packages/session/working-directory/src/index.ts:29"]}, {"digest":"224aa11d4d5f88c450822d01b99e6ca5e3a9c7f26a5ede10bae33384a09ef4dc","names":[],"sources":[]}, @@ -174,7 +175,6 @@ {"digest":"28936c614bd4e7e2774806edcbb3cbfd3ee643ac1cbf0bbdff43d915505a8fe3","names":[],"sources":[]}, {"digest":"290f32bfd81ff85a8095886a8066fae06e885942b63a8bcd6a8d66d328445808","names":[],"sources":[]}, {"digest":"297fbd8067d7815adae02b89790ec6ff5fb7c2cb0d0eb026e1746c24ec857761","names":[],"sources":[]}, - {"digest":"2a668a48dec1739752123d67c3bc254412d6743dbe3347365e0ed83a542f2bc3","names":[],"sources":["packages/core/agent/src/types.ts:113"]}, {"digest":"2aad1179f957631477b582e57cd8f538cb82618fc3fb816bde1171154235b065","names":[],"sources":[]}, {"digest":"2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee","names":[],"sources":[]}, {"digest":"2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095","names":[],"sources":["packages/llm/llm/src/types.ts:473"]}, @@ -183,14 +183,13 @@ {"digest":"2c64d4bc7264fd3f8bed47e96ab8a593bce05a1e4e1c436dcd244a3b7e0c2a9d","names":[],"sources":[]}, {"digest":"2d11ca7b0d4493e244b74eba093227866b33249841e59a1e9bf56afed38604c3","names":[],"sources":["packages/web/web-search-deepseek/src/provider.ts:71"]}, {"digest":"2da725e554cd89301d829ed9dc45a572c3cd1b93b5e91d0d71f007976d8a0863","names":[],"sources":[]}, + {"digest":"2e0cf9dbeea369bc77843e2ec47b0ebf4542c2ac29523a64f56aab99db49f0ee","names":[],"sources":[]}, {"digest":"2e3523541e942ef6cc9fbc5b91f242e0ca4c6ada479b79b96025b797f6e41f3f","names":["packages/schedule/schedule/src/types.ts#ScheduleChange"],"sources":["packages/schedule/schedule/src/types.ts:256"]}, {"digest":"2e4debca12f2c449f96dc6574f3fbf2fb346193443114a241d01a236362ec48c","names":[],"sources":[]}, {"digest":"2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e","names":["packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot"],"sources":["packages/experimental/agent-team/src/types.ts:129"]}, {"digest":"2f1e6a7ebace43d727ae138ddcd352eb3813960009e5805b8c5ac84300c97716","names":["packages/attachment/attachment/src/types.ts#ImageMediaType"],"sources":["packages/attachment/attachment/src/types.ts:8"]}, {"digest":"2f3f201c8846e19c20bb75c02280e3d4eb3c57509e4715f0c0a4d1d4f91a7a06","names":[],"sources":[]}, - {"digest":"2f5b14bc2ccbe8810568df6392f1a8e0c3a2be59440051b60666266519e403ff","names":[],"sources":[]}, {"digest":"2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f","names":["packages/core/tool-executor-ptc/src/types.ts#PtcDispatchStartEventData"],"sources":["packages/core/tool-executor-ptc/src/types.ts:12"]}, - {"digest":"30093f9f63a120cb932f07577594c04afcec3f0bd02859154a6e5b0e79a46359","names":[],"sources":[]}, {"digest":"30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f","names":["packages/session/session-title/src/types.ts#SessionTitleSource"],"sources":["packages/session/session-title/src/types.ts:28"]}, {"digest":"312aee2f0c42aabbae6cc60d92d596d8453c0aeb061c5868d1755a69ef49e69a","names":[],"sources":[]}, {"digest":"31907de9563751822af6d9be02bafaf26faa098bbd1cb412524134d577cc1648","names":["packages/compaction/compaction/src/checkpoint.ts#CompactionCheckpointSource"],"sources":["packages/compaction/compaction/src/checkpoint.ts:22"]}, @@ -199,7 +198,6 @@ {"digest":"3305a4bb6b6c29b482ba91d59c60b47e8f98282d3dacc8c29e4a4c2712209bdd","names":[],"sources":[]}, {"digest":"33341aac09312300de5a178e6cc6f6544a4876f7f710cfea55eda473c8d329ef","names":["packages/llm/llm/src/assistant-stream.ts#AssistantStreamRecord"],"sources":["packages/llm/llm/src/assistant-stream.ts:20"]}, {"digest":"335e242de1fcc17b6ca920fc420d71bec2d76e53e37955c00948b65ab77f05c5","names":["packages/core/session/src/types.ts#SurfaceOp"],"sources":["packages/core/session/src/types.ts:470"]}, - {"digest":"347c1718600bbb3814ae6b4df06e6aceae35e8a8e8cb3c39ac28b2e932c5abd6","names":[],"sources":[]}, {"digest":"347de3380f2da308d0c74e08821f9e85559f55af4c5010795b110d00f7fc9dbd","names":[],"sources":[]}, {"digest":"34d1ab5c5df378186d6054b1a1beea9a41e41965d6f336f0bc0025c441a82e66","names":[],"sources":[]}, {"digest":"35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc","names":[],"sources":[]}, @@ -231,7 +229,6 @@ {"digest":"3f74ba74acd10644c1ed6947a411d8088bd2cfac74f61bb8540f96d68e31f004","names":[],"sources":[]}, {"digest":"3fdf9582c3b5597df104d32d97dab6c6ee3a93794f3034ec6ada6272404ea27f","names":["packages/schedule/schedule/src/types.ts#LegacyAfterScheduleRecord"],"sources":["packages/schedule/schedule/src/types.ts:161"]}, {"digest":"408927267422a7b2beb6df354ff7c1406d929a5646554f48adf85eb8372f811c","names":[],"sources":[]}, - {"digest":"414a44a96f06339f04657b0e441a2baed954f7e31aa1552f37dbe7107f0df8e9","names":[],"sources":["packages/core/agent/src/types.ts:24"]}, {"digest":"4208123b50df5006b181481ab45fcf1cde807b88d3fd4d340090bc2e202fac41","names":[],"sources":[]}, {"digest":"42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f","names":[],"sources":[]}, {"digest":"4312134fc6f9cad507c31390bd3d147535492dd77621ec517c5e85f853d53a03","names":[],"sources":[]}, @@ -289,6 +286,7 @@ {"digest":"587bb11da730e06aa52b62c087d217d6f8f22459b25f31cb9e1e1baf17944e5e","names":[],"sources":[]}, {"digest":"589d0d91239681f89ee485459dc8c2f73ea47183b5534289d966637024a2c35d","names":[],"sources":[]}, {"digest":"58f94390b982f8a5ac1f4f235ba612557f4a1dc4a995d8b1cec63ce7639decb0","names":[],"sources":[]}, + {"digest":"5a29653980f4013ea268503865080d46e2b31f47327b652e079342ac922cb730","names":["packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData"],"sources":["packages/session/session-title-llm/src/index.ts:37"]}, {"digest":"5abab3a27fd2736660ecddd94b28d6bbf114986dd894880153bd1be3ccacb563","names":[],"sources":[]}, {"digest":"5b232a9b9bfcc0fc6794dab7865bcf203a65785c5de5aa647a51228bff628413","names":[],"sources":[]}, {"digest":"5b6fb1f226ff56402db08a6aae82af76100b14056271875f69ec526a1ae05d51","names":[],"sources":["packages/interaction/commands/src/types.ts:112"]}, @@ -296,6 +294,7 @@ {"digest":"5bc66269cfaf614c0ecda8ecb469f23101a7609793fc2b693ac23c4aecdb575e","names":[],"sources":[]}, {"digest":"5bd310104cfe448e530beb11422d2f791e2e32e8e95fdc528a15f9ca693dae75","names":[],"sources":[]}, {"digest":"5c45bf4c544a7211dcd8ba6ba7e5f1bc39b49e7a9df9d5cbdc8e87c22771b37b","names":[],"sources":[]}, + {"digest":"5ca953e599fc0c19c32a9a8c7e3cd57fcfaf0b91d6bd4a4d007b3be37b33dddd","names":["packages/llm/llm/src/message.ts#DeveloperMessage"],"sources":["packages/llm/llm/src/message.ts:157"]}, {"digest":"5cb63db9fe57dc09c3a1c2b84c3edf73ff5b62960155d8436a2377a5c43ce32c","names":[],"sources":[]}, {"digest":"5cdbf342b7748d152bc987b68f58e565e5bba4b227e076e2d2120d87662e45cd","names":[],"sources":[]}, {"digest":"5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78","names":[],"sources":[]}, @@ -321,7 +320,6 @@ {"digest":"65a8400e451e667e5b6e34df72ceb81fca8c12330330fa72083ad40279b189ad","names":["packages/llm/llm/src/message.ts#SystemPromptMessageSource"],"sources":["packages/llm/llm/src/message.ts:35"]}, {"digest":"65d583e66518c5236015e0846470fb32efcf92375ed306aef14e50a9d2cd2099","names":["packages/skill/tool-skill/src/index.ts#SkillCatalogSource"],"sources":["packages/skill/tool-skill/src/index.ts:35"]}, {"digest":"66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2","names":["packages/llm/llm/src/types.ts#ToolSchema"],"sources":["packages/llm/llm/src/types.ts:491"]}, - {"digest":"671e72b8036a3afb018b38ca5ae9ee3aabe6a314647411655b4b9497b75010b1","names":[],"sources":[]}, {"digest":"672c4aa2a9b736e3d6e5da6346d2e2ed5e3573f2d0baafa18e7407c9f3fee3db","names":["packages/skill/skill/src/index.ts#SkillInvocationSource"],"sources":["packages/skill/skill/src/index.ts:146"]}, {"digest":"67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0","names":["packages/llm/llm/src/call-config.ts#LlmCallConfig"],"sources":["packages/llm/llm/src/call-config.ts:31"]}, {"digest":"68141cbe3994a5ffcd064c07c75996538437fd9dbf77ce8a2927e89ec577fabe","names":[],"sources":[]}, @@ -370,7 +368,6 @@ {"digest":"7b1ce60193fa8b69292e5843858d9cee7dc565dcb40d33c136e5d7e8b8d4296a","names":[],"sources":[]}, {"digest":"7c6129b6961cf4b2a030f36c78d2b0a5be38617651a9b81322e054ee512bee06","names":["packages/feedback/message-feedback/src/types.ts#MessageFeedbackDelete"],"sources":["packages/feedback/message-feedback/src/types.ts:46"]}, {"digest":"7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397","names":[],"sources":[]}, - {"digest":"7cf091467eb288875ea4e88397003a7e43d95ab75afaf1b42554a8cc766799f2","names":["packages/llm/llm/src/message.ts#Message"],"sources":["packages/llm/llm/src/message.ts:196"]}, {"digest":"7d226b9fd95b4ba213d4237c22e930a91c3c769c95ca7f92977b9d625ba13438","names":["packages/goal/goal/src/domain.ts#GoalChangeMeta"],"sources":["packages/goal/goal/src/domain.ts:44"]}, {"digest":"7d54a5a5e629e2e7783bfac72683bcc5e969e9f43426b9289826aaf294ff6344","names":["packages/feedback/command-feedback/src/types.ts#FeedbackRecord"],"sources":["packages/feedback/command-feedback/src/types.ts:27"]}, {"digest":"7d65acf0a3a43a8f445ce5d3320b42eca492739fc931b2a7516bfb8368dc2c68","names":["packages/llm/llm/src/types.ts#ContentBlockType"],"sources":["packages/llm/llm/src/types.ts:148"]}, @@ -415,16 +412,15 @@ {"digest":"8c1c4f72755a78ea163523a46565316e36e9b644382eb48e6635c71a6f412dd2","names":[],"sources":[]}, {"digest":"8c627d7a45fa406cea879a3922105e27ed4c3c7966e264afaf11ea7a330f2dd1","names":[],"sources":[]}, {"digest":"8cdd3fdd295cee53035de16a1b23411716f75e9f994fe9510cac5d7fef605c76","names":[],"sources":[]}, + {"digest":"8e1f9a193d6937a5909f4b2576a2b8392e8fc14652683e1e60b833d5ba39fc3e","names":["packages/llm/llm/src/message.ts#MessageSource"],"sources":["packages/llm/llm/src/message.ts:136"]}, {"digest":"8e31bb958e9b015e6f8407eaade32217068159e816f3cc99c3ad76eb889732e0","names":[],"sources":["packages/llm/llm/src/types.ts:160"]}, {"digest":"8e611bf46821e5003a1605a73560a6f56388295149d449dfe2142d5ec335f29c","names":["packages/llm/llm/src/types.ts#ToolAdditionBlock"],"sources":["packages/llm/llm/src/types.ts:115"]}, {"digest":"8e8155594e07812356b67d732196dda894b0f9c91a5969d51612cf490b0a016d","names":[],"sources":[]}, {"digest":"8e8e91646a5cae3fc78f7532a0013ff2088ae47077642c2edd507a508209b8af","names":["packages/schedule/schedule/src/types.ts#OneShotScheduleDispatchChange"],"sources":["packages/schedule/schedule/src/types.ts:237"]}, - {"digest":"8eda0195dd1d4a3fee80f5e0b9f702fdf0f342c27ece60d44d459899b6a30b60","names":["packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData"],"sources":["packages/session/session-title-llm/src/index.ts:37"]}, {"digest":"8f324cea3916c0517261e88167def8efb3a9994c1135e23d40ed9fd48f8c7bd4","names":[],"sources":[]}, {"digest":"8f6d609794bf5afc01d7ccf3e03811d32f4b8047f58a2c9a98db0154c2ad7cb9","names":[],"sources":[]}, {"digest":"8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543","names":[],"sources":["packages/core/tools/src/index.ts:28"]}, {"digest":"904bf499d98be4a3a5f836fcac533b6c4899569b018071e6604e1d32657d00fd","names":[],"sources":[]}, - {"digest":"92b82e8b7bbb38b3adeb2c07eb76d0f0e0d18c1729808def7004ac2bc9baa583","names":[],"sources":[]}, {"digest":"930c1cd289def119d443e19dd90008916bf035f0a4d23e07241e7f050f2b89b7","names":["packages/interaction/user-approval/src/types.ts#ApprovalOutcome"],"sources":["packages/interaction/user-approval/src/types.ts:32"]}, {"digest":"935c8d486292b13c87b57ca280eb8c58b5b2f5239be86fcd4334c33fdeb87682","names":[],"sources":[]}, {"digest":"938f7c7357bc403ea3b281a9dd016939df7a0880fc8db12612585be574d0d615","names":[],"sources":[]}, @@ -435,10 +431,9 @@ {"digest":"95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65","names":[],"sources":["packages/llm/llm/src/types.ts:472"]}, {"digest":"95a3ffccbf3c8afcf9a2f3b355f724da8e07e98dc47ee741166dde7f9ea058bd","names":[],"sources":[]}, {"digest":"95c0e88fcf0bafb2087f912ad9e2369de6ae6530cc0c4c2772c96205b57f7f60","names":[],"sources":[]}, - {"digest":"9632024d168f8169ad83fe3639da51c864ebb5444c9fc965ac9342611b2d5263","names":["packages/llm/llm/src/message.ts#UserMessage"],"sources":["packages/llm/llm/src/message.ts:162"]}, {"digest":"9645cf93769a6dfb227bd400bd6cb9fa7bab9140f4a851e4ae92514ade6e3114","names":[],"sources":[]}, {"digest":"96bfb916a727cd4445a594df264c523fc039749a31bfed924b8cc7bb308a5bb0","names":[],"sources":[]}, - {"digest":"96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c","names":["packages/core/agent/src/types.ts#InboxTarget"],"sources":["packages/core/agent/src/types.ts:47"]}, + {"digest":"96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c","names":["packages/core/agent/src/types.ts#InboxTarget"],"sources":["packages/core/agent/src/types.ts:40"]}, {"digest":"9890113e97dd3541787bfe6704f2b223d3a890b5325ca0acae2b0398909b413e","names":[],"sources":["packages/sandbox/sandbox-policy/src/session-mode.ts:33"]}, {"digest":"99135d8a3937977955eaac5f8f04f930dcd1562f8450a54a51cde9b921dd59a6","names":[],"sources":[]}, {"digest":"9947aa5938fe19a895298da412917acbf9ffb5093a73ab8baa257e6ae9e63a07","names":[],"sources":[]}, @@ -475,7 +470,6 @@ {"digest":"a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c","names":[],"sources":["packages/experimental/agent-team/src/types.ts:232"]}, {"digest":"a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc","names":[],"sources":[]}, {"digest":"a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb","names":["packages/experimental/hook-protocol/src/types.ts#HookDialect"],"sources":["packages/experimental/hook-protocol/src/types.ts:48"]}, - {"digest":"a6d8c25f4b2ca2d137672a8a278ee1398df3f3cb60f5d32d8e3007c68299b664","names":[],"sources":[]}, {"digest":"a6da9ad5d42ae83a6833a8288b076688e8e0801633ea36cbdd8a30a6d9f3160c","names":["packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunStartData"],"sources":["packages/workflow/tool-workflow/src/types.ts:14"]}, {"digest":"a720757f704a2512e9d2f20da4a093e582472176236a75ab8b8eb7aaff17d85a","names":[],"sources":[]}, {"digest":"a7cf43ce7c2a4c038feed1885cd7a00d5c6ee2d90a7e0d56b46f78a3e1ca327f","names":[],"sources":[]}, @@ -495,6 +489,7 @@ {"digest":"ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7","names":[],"sources":["packages/core/session/src/types.ts:220","packages/llm/llm/src/types.ts:159"]}, {"digest":"aba175fb20ffc71112c55efe70458ff5d7dbfb83501892b4ba8d077f0ea5d7ba","names":[],"sources":[]}, {"digest":"abd6ca62a5ab166553848324a23fef90eed7f59c8021bd63e7ea5bc6fd666b0c","names":["packages/web/web-search-deepseek/src/provider.ts#DeepSeekSearchLlmRequest"],"sources":["packages/web/web-search-deepseek/src/provider.ts:55"]}, + {"digest":"acbb9d5bfdd3562851984bd42bbb2441bcb58f959a1ae0474d22a6cc60517c43","names":[],"sources":["packages/core/session/src/types.ts:317"]}, {"digest":"ad0c38a0adb82a94c925456df41586d9b5f9202fca18593eae2213d4445b4585","names":[],"sources":["packages/core/session/src/types.ts:398"]}, {"digest":"ad22a5ca5bbaab2e1440779cd3648e85ddfc6f73b5207394eda699555cf31430","names":[],"sources":[]}, {"digest":"ad3b56aa2fc1ad4250e7399295d34a8eae592e1907e85852e2291b2e498a30bc","names":[],"sources":[]}, @@ -563,17 +558,17 @@ {"digest":"c2f49004d9ae38a964e80850c87bc6849d6a872091a1d43e99eedf00c641e2a3","names":[],"sources":[]}, {"digest":"c43b119ce579b3e9983a0d3adc4ba402ebb9ba773d89738a2dab01ecf5813943","names":[],"sources":[]}, {"digest":"c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49","names":[],"sources":[]}, + {"digest":"c52b170222374a79f2c1248606e20ca64ef1143f531d89a49f011dfe46f44f67","names":["packages/llm/llm/src/message.ts#Message"],"sources":["packages/llm/llm/src/message.ts:196"]}, {"digest":"c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005","names":[],"sources":["packages/interaction/user-questions/src/types.ts:134"]}, {"digest":"c5687b1313206a6ff334066047b6a0681611467dc4df8c5a8e71d0b6df39311f","names":[],"sources":[]}, {"digest":"c589e938cc0e4989df8b82abef6339de621f0dc4fe62c230d145b94996af7f92","names":[],"sources":[]}, {"digest":"c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6","names":["packages/util/values/src/index.ts#JsonValue"],"sources":["packages/util/values/src/index.ts:4"]}, - {"digest":"c5bbcb3435908af2036df7105883e0a49c30ecc1fae4420aa5c4cf944066eff5","names":[],"sources":[]}, - {"digest":"c66c6c3b411abf46c1f9b99e0019c10c1b6d13ee716a4b5e88f7e1b65feb3039","names":[],"sources":["packages/core/agent/src/types.ts:124"]}, {"digest":"c748fb42c80a120038a8c587af80ec39924803c401364db11c8974d5bf6d0262","names":[],"sources":[]}, {"digest":"c8e6f48878cc2a2f0dc59682280d81f407943fb503f32c64bf9ff3d9393c3eff","names":[],"sources":[]}, {"digest":"c99e20441bf15341e9db54385e558e5c03d21dff2eb1ac08ccbf47c2d8747954","names":[],"sources":[]}, {"digest":"ca085dba2c877feceebf3c62f9d39c6c2a4d2314d97b94b3927a780fe8fe2bb2","names":[],"sources":[]}, {"digest":"ca59530d1a4f3e191b4032bc4a20eded0d827db8b5a7efb27b3af9e9b456c6f2","names":[],"sources":["packages/compaction/compaction/src/types.ts:72"]}, + {"digest":"caad8a860d58ff99ef3576bcb3e484314b1d9d7065b307d14478ec76573ebdec","names":[],"sources":[]}, {"digest":"cb1c8959e1726f82f9a3d2ef2bc90bf8c451a98fd56175e956022124c770c818","names":[],"sources":[]}, {"digest":"cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09","names":[],"sources":[]}, {"digest":"cbca49bde16b656d5b8fbe4a8a507769f7b594c6287782b809c03814941bc5e3","names":[],"sources":[]}, @@ -588,13 +583,11 @@ {"digest":"d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d","names":[],"sources":["packages/llm/llm/src/types.ts:475"]}, {"digest":"d4c799e3d51cae1040362ba4968efc484739e14416c9c1df20c2f108b4ef2eb5","names":[],"sources":[]}, {"digest":"d4e4d575abeb4b72d616dc65c17ac0eaa943cc135609395044c38d5afcb10e31","names":[],"sources":[]}, - {"digest":"d582ddae3f21edf55b6f50454e3015ef78ed3788fee4670827f2c3c4bd0b47e2","names":[],"sources":["packages/core/session/src/types.ts:317"]}, {"digest":"d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a","names":[],"sources":[]}, {"digest":"d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9","names":[],"sources":[]}, {"digest":"d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5","names":[],"sources":[]}, {"digest":"d6e654b88b584fcdf290a56802b1730bc7b9847ae86baa4265ab7769eb48990c","names":[],"sources":[]}, {"digest":"d85590a5675c6e2fd8164e5792a6cceac714605b8ac228adae5ee668facb1490","names":[],"sources":[]}, - {"digest":"d8dde9629b7323b03c1c3fb0db75d6b72c73666dc0240ab7d3e41b9c0c40fc7c","names":[],"sources":[]}, {"digest":"d9196086e41e4dd5c2ce87f5c25529c8112a1731d258cf0728747d41a9ee3241","names":[],"sources":[]}, {"digest":"d9c462b9f62fdd3efc76a44b9524dc8781f6b91cd9c7e584b17d971defafebab","names":["packages/llm/llm/src/message.ts#ToolMessageSource"],"sources":["packages/llm/llm/src/message.ts:29"]}, {"digest":"d9edf1862e4471d6ad8a106a3f4160e0643874368707d50ecd8e27e5ee7a2567","names":[],"sources":[]}, @@ -619,6 +612,7 @@ {"digest":"dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd","names":["packages/experimental/agent-team/src/types.ts#TeamMemberPhase"],"sources":["packages/experimental/agent-team/src/types.ts:45"]}, {"digest":"e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05","names":[],"sources":[]}, {"digest":"e103228e063b4762f2fb51ee5e50acaddd53a513de107efae09bdebf2958a664","names":[],"sources":["packages/api/session-controller/src/types.ts:399"]}, + {"digest":"e139090c38f84d8d8d96dab87bf573bb0ab220d39aab891519ed27f27ffc8f82","names":[],"sources":[]}, {"digest":"e2ad8f5bcd813f268ffef1b10a12d7ddd2b8ced8b9b6d0275b3f6b7ed094a187","names":[],"sources":[]}, {"digest":"e2d0367e5716bc188435e51718ebff8ce873190625fa93b1c5331102d98758fa","names":[],"sources":[]}, {"digest":"e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602","names":[],"sources":[]}, @@ -626,7 +620,6 @@ {"digest":"e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72","names":[],"sources":[]}, {"digest":"e31572180d6b28b73f073ad30ae19d5d82982d3daf984723984e23da1e7c459a","names":[],"sources":[]}, {"digest":"e3e77b26f0148755a505f5b8ea843a3be117827d4703bb5754d9dc8ddbef521f","names":[],"sources":[]}, - {"digest":"e3e9f4d1e245b470cba77c3e6b94ea141408604d693b2a8437278abc8cfd8e2c","names":["packages/llm/llm/src/message.ts#MessageSource"],"sources":["packages/llm/llm/src/message.ts:136"]}, {"digest":"e41bdebe246abe17154de7e7a1d81035437eee8d20e7f1edff0d91e2598ad828","names":[],"sources":[]}, {"digest":"e42b0f40b5d0b628c6e1c44a8fb9eda0780037a81959e1b93d2dfb47b46b0e87","names":[],"sources":[]}, {"digest":"e438a9d4fb883b3e4a8d9fc8913aa1550ed50eb384bfbf943100a5605b537498","names":[],"sources":[]}, @@ -644,8 +637,10 @@ {"digest":"e7f4cbb3a57f4cbdb61ea41e95bcba626ad1fff6bb9b1e063b1d50a32da6e864","names":[],"sources":[]}, {"digest":"e8de0f457fed5b9ff94dc4950b59ef75fd8ba8af665cc7899485924a62653367","names":[],"sources":[]}, {"digest":"e95f957154b2b1b58692af53d4131180b6dd19e2264869f876dbaa959991494e","names":[],"sources":[]}, + {"digest":"e9fad8e4e6a99024b0351f9656cf18296a5b8a769dae6748719b402a5d2ffd17","names":[],"sources":[]}, {"digest":"ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403","names":[],"sources":["packages/experimental/agent-team/src/types.ts:234"]}, {"digest":"eaa71be0fbf0f702df8ea6c11d0aa2f9782263d45a10d0fb14eda662f4bfe04c","names":[],"sources":["packages/core/session/src/types.ts:201"]}, + {"digest":"eb0fc09c936ebe3d3f16e17b89143f26ae4eca3bd8277c7518bafab45a29e255","names":[],"sources":[]}, {"digest":"ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8","names":[],"sources":[]}, {"digest":"ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93","names":[],"sources":[]}, {"digest":"ec8b5ff7c77ca6893ac7ba5a0c5b2aefeb52f12c64450b7359660c2a7e8a53d7","names":[],"sources":["packages/core/session/src/types.ts:234"]}, @@ -658,7 +653,7 @@ {"digest":"ef226a90ddac6cd8e631a3a337a3a3e27f9494be92befb7c718b404681d75e21","names":[],"sources":[]}, {"digest":"ef4f00923b9edbae15c7ef05320280bf61147ca17146cf59690359bfd5e5da45","names":[],"sources":[]}, {"digest":"f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c","names":[],"sources":[]}, - {"digest":"f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b","names":["packages/core/agent/src/types.ts#ModelSelection"],"sources":["packages/core/agent/src/types.ts:86"]}, + {"digest":"f09e5e7acf5ff5ccd193ff2eeec015fa2ff7c29d98bc465f8c3302058d47e29b","names":["packages/core/agent/src/types.ts#ModelSelection"],"sources":["packages/core/agent/src/types.ts:79"]}, {"digest":"f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402","names":["packages/core/session/src/types.ts#TurnEndReason"],"sources":["packages/core/session/src/types.ts:238"]}, {"digest":"f1996af2602e6f62b5ab179ca5c4af00d8753938b74cc6ef102de09e99e74a7e","names":[],"sources":[]}, {"digest":"f19bee84bf3d20904553cdcd57465232bc1c2a194f54c4d195292ba0487223a6","names":["packages/attachment/attachment/src/types.ts#FileAttachmentRef"],"sources":["packages/attachment/attachment/src/types.ts:39"]}, @@ -686,7 +681,6 @@ {"digest":"fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be","names":["packages/llm/llm/src/message.ts#AssistantMessage"],"sources":["packages/llm/llm/src/message.ts:167"]}, {"digest":"fab2bb09bc5e3b9db0685e8f5500b029af1a2b33e70bc703c1d5d0f596fbc1fd","names":["packages/sandbox/sandbox/src/index.ts#SandboxMode"],"sources":["packages/sandbox/sandbox/src/index.ts:30"]}, {"digest":"fab35c4520883c0d69e42d079c9f543fc5ac7a2d15b537b5572fcd99059c7018","names":[],"sources":[]}, - {"digest":"fb28ad7d7aabceb5bda505cf027e0e22354d7f31757337e7e51a4b592feac172","names":[],"sources":[]}, {"digest":"fb5e0517050d2c6d40e5b8145db1f7c5ca7044d5d50fada6ff80e0b22c6ca973","names":[],"sources":[]}, {"digest":"fb8b6a2e53cb9cf44c551176de0fb77d1ddc60037b99a891ff8a860b6d5c29a0","names":[],"sources":[]}, {"digest":"fbd2c74b346a1595b376139c587fadd23e4a21acc9c0abce342975b6cfc60986","names":[],"sources":[]}, @@ -726,11 +720,11 @@ "0c4205180f75b9ee": {"kind":"literal","value":false}, "0c556c9b9f2a9623": {"kind":"object","properties":[{"name":"data","type":"abd6ca62a5ab1665","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"6f611bed14b21065","optional":false}],"indices":[]}, "0d47a8f3d847243c": {"kind":"object","properties":[{"name":"allowedModels","type":"ba77b58995e36f14","optional":false}],"indices":[]}, + "0d6dd68f18afee7b": {"kind":"object","properties":[{"name":"data","type":"183ccd2e6b70ddf9","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, "0e10d7c096b47e61": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"mode","type":"e5d3828df1ec3e2a","optional":false},{"name":"version","type":"93d38436347f67c9","optional":false}],"indices":[]}, "0e7b2a97f54f1afd": {"kind":"literal","value":"schedule/change"}, "0ed8d1108fe7a5e1": {"kind":"object","properties":[{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, "0eeb17e1df89b1eb": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, - "0ef3072084202b08": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"e3e9f4d1e245b470","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "0f46153645c29784": {"kind":"object","properties":[{"name":"height","type":"0657d3ca5ac4e7bf","optional":false},{"name":"width","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "0f8512903d94f57a": {"kind":"object","properties":[{"name":"data","type":"e481c46af3813e00","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"4312134fc6f9cad5","optional":false}],"indices":[]}, "0fe52387f6505270": {"kind":"literal","value":"plan-mode"}, @@ -754,6 +748,7 @@ "16d0507cb94b4cfc": {"kind":"literal","value":"relay"}, "17aae616ef020b8b": {"kind":"literal","value":"at"}, "17d1afb770d99419": {"kind":"object","properties":[{"name":"inherited","type":"dacf3d5f3e4ff619","optional":true}],"indices":[]}, + "183ccd2e6b70ddf9": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"8e1f9a193d6937a5","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "18dc21f12bbac8ad": {"kind":"object","properties":[{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"operation","type":"d0fbb0ca1bbba059","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, "18ee62b8900a4c3d": {"kind":"object","properties":[{"name":"agentPreset","type":"4cd48f3e5108bd6e","optional":true},{"name":"createdAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"cwd","type":"4cd48f3e5108bd6e","optional":true},{"name":"delegationDepth","type":"0657d3ca5ac4e7bf","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"isSeeded","type":"aa93010d0e521ad7","optional":false},{"name":"origin","type":"aacda84a69739238","optional":true},{"name":"parentSession","type":"4cd48f3e5108bd6e","optional":true},{"name":"type","type":"e41bdebe246abe17","optional":false},{"name":"version","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "18fcdd273a2f2f9d": {"kind":"literal","value":"team/member"}, @@ -786,6 +781,7 @@ "20a57544bbf204da": {"kind":"object","properties":[{"name":"active","type":"aa93010d0e521ad7","optional":false}],"indices":[]}, "2142e391b9ccb60d": {"kind":"literal","value":"cancelled"}, "2152caf1a71e588e": {"kind":"object","properties":[{"name":"kind","type":"2e4debca12f2c449","optional":false},{"name":"messageId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderName","type":"4cd48f3e5108bd6e","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "21c17014f8879acb": {"kind":"object","properties":[{"name":"inserted","type":"e139090c38f84d8d","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, "21fb6a90d5068f6a": {"kind":"object","properties":[{"name":"data","type":"ea08ec1df0c33108","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"4e011753f8263f43","optional":false}],"indices":[]}, "2235d957bf4b7c6c": {"kind":"object","properties":[{"name":"kind","type":"7b1ce60193fa8b69","optional":false}],"indices":[]}, "224aa11d4d5f88c4": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, @@ -805,7 +801,6 @@ "28936c614bd4e7e2": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "290f32bfd81ff85a": {"kind":"object","properties":[{"name":"kind","type":"23af3759678a30de","optional":false}],"indices":[]}, "297fbd8067d7815a": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"a9059a81430b3207","optional":false}],"indices":[]}, - "2a668a48dec17397": {"kind":"object","properties":[{"name":"inputId","type":"4cd48f3e5108bd6e","optional":false},{"name":"source","type":"671e72b8036a3afb","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "2aad1179f9576314": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "2ab12a0e345658f9": {"kind":"array","element":"83da72c6857601c7"}, "2abb711460e6238d": {"kind":"object","properties":[{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"6f091fe32ab24407","optional":false}],"indices":[]}, @@ -814,14 +809,13 @@ "2c64d4bc7264fd3f": {"kind":"literal","value":"step/start"}, "2d11ca7b0d4493e2": {"kind":"object","properties":[{"name":"max_uses","type":"0657d3ca5ac4e7bf","optional":false},{"name":"name","type":"935c8d486292b13c","optional":false},{"name":"type","type":"0116b1f89c05221d","optional":false}],"indices":[]}, "2da725e554cd8930": {"kind":"literal","value":"fork"}, + "2e0cf9dbeea369bc": {"kind":"object","properties":[{"name":"data","type":"21c17014f8879acb","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, "2e3523541e942ef6": {"kind":"union","types":["18dc21f12bbac8ad","8e8e91646a5cae3f","e6063d0451dc0202","eea687d437880bd5"]}, "2e4debca12f2c449": {"kind":"literal","value":"team-message"}, "2eafefdc6f79a492": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderId","type":"4cd48f3e5108bd6e","optional":false},{"name":"senderName","type":"4cd48f3e5108bd6e","optional":false},{"name":"targetId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "2f1e6a7ebace43d7": {"kind":"union","types":["3f74ba74acd10644","58f94390b982f8a5","de30b1fa12c75f81","ebb768d85ec87fb6"]}, "2f3f201c8846e19c": {"kind":"literal","value":"tool-goal"}, - "2f5b14bc2ccbe881": {"kind":"array","element":"7cf091467eb28887"}, "2fe5026daf14bd4f": {"kind":"object","properties":[{"name":"arguments","type":"bbc5696e3f13c758","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"parentCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"rootCallId","type":"4cd48f3e5108bd6e","optional":false},{"name":"subCallId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, - "30093f9f63a120cb": {"kind":"literal","value":"agent/retry"}, "30d5a537b2966857": {"kind":"union","types":["1b5649238876e288","ada310bf0bdb8fed","b81e9de81a481b24"]}, "312aee2f0c42aabb": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "31907de956375182": {"kind":"object","properties":[{"name":"compactionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"6b23ed9ce1b397e6","optional":false},{"name":"sourceCommandId","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, @@ -830,7 +824,6 @@ "3305a4bb6b6c29b4": {"kind":"union","types":["0341d4c3dfcd5325","07eccc9ea2f9b543","5bd310104cfe448e","7180844e1066230c","9d80129e13edfb6f","e4cac694f8b6e1d5","f05fb85e715af4ab"]}, "33341aac09312300": {"kind":"union","types":["3860b4757d512a28","487b65c37bfedb8b","71bb3254ec8f1e49","e72269f931559e90"]}, "335e242de1fcc17b": {"kind":"union","types":["bcf0caf62d964b2f","e438a9d4fb883b3e"]}, - "347c1718600bbb38": {"kind":"array","element":"9632024d168f8169"}, "347de3380f2da308": {"kind":"literal","value":"job/settled"}, "34d1ab5c5df37818": {"kind":"literal","value":"resume"}, "35203ba7ad5ef6f9": {"kind":"object","properties":[{"name":"data","type":"f09e5e7acf5ff5cc","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1d5f04a03ea75fc8","optional":false}],"indices":[]}, @@ -862,7 +855,6 @@ "3f74ba74acd10644": {"kind":"literal","value":"image/jpeg"}, "3fdf9582c3b5597d": {"kind":"object","properties":[{"name":"afterSeconds","type":"0657d3ca5ac4e7bf","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"5b232a9b9bfcc0fc","optional":false},{"name":"prompt","type":"4cd48f3e5108bd6e","optional":false},{"name":"scheduledAt","type":"4cd48f3e5108bd6e","optional":false},{"name":"title","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, "408927267422a7b2": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false}],"indices":[]}, - "414a44a96f06339f": {"kind":"object","properties":[{"name":"kind","type":"63ba8895cc09e469","optional":false}],"indices":[]}, "4208123b50df5006": {"kind":"object","properties":[{"name":"data","type":"ad0c38a0adb82a94","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"3e0eff5946255ff0","optional":false}],"indices":[]}, "42e0916e0dda5f6d": {"kind":"object","properties":[{"name":"data","type":"7f9365bae9fcb73e","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"fe792a6cd4304945","optional":false}],"indices":[]}, "4312134fc6f9cad5": {"kind":"literal","value":"turn/end"}, @@ -920,6 +912,7 @@ "587bb11da730e06a": {"kind":"literal","value":"session/title-llm-request"}, "589d0d91239681f8": {"kind":"literal","value":"developer"}, "58f94390b982f8a5": {"kind":"literal","value":"image/png"}, + "5a29653980f4013e": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"e9fad8e4e6a99024","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "5abab3a27fd27366": {"kind":"literal","value":"text-delta"}, "5b232a9b9bfcc0fc": {"kind":"literal","value":"after"}, "5b6fb1f226ff5640": {"kind":"object","properties":[{"name":"commandId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"1ab2ef1296f157ba","optional":false},{"name":"sourceEventSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"text","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, @@ -927,6 +920,7 @@ "5bc66269cfaf614c": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"d9edf1862e4471d6","optional":false}],"indices":[]}, "5bd310104cfe448e": {"kind":"literal","value":"resource-cost"}, "5c45bf4c544a7211": {"kind":"object","properties":[{"name":"data","type":"8a9c990773f798ea","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"ae63d9d1080ed894","optional":false}],"indices":[]}, + "5ca953e599fc0c19": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"589d0d91239681f8","optional":false},{"name":"source","type":"8e1f9a193d6937a5","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.developer-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "5cb63db9fe57dc09": {"kind":"literal","value":"teardown"}, "5cdbf342b7748d15": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"f68c335306535a00","optional":false}],"indices":[]}, "5d03ba38734809bc": {"kind":"array","element":"0657d3ca5ac4e7bf"}, @@ -952,7 +946,6 @@ "65a8400e451e667e": {"kind":"object","properties":[{"name":"kind","type":"09655b34d7f3cf1e","optional":false}],"indices":[]}, "65d583e66518c523": {"kind":"object","properties":[{"name":"entries","type":"aba175fb20ffc711","optional":false},{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"incomplete","type":"dacf3d5f3e4ff619","optional":true},{"name":"kind","type":"6ebcc2dbec9f2dab","optional":false},{"name":"update","type":"dacf3d5f3e4ff619","optional":true}],"indices":[]}, "66ba70f6f068da44": {"kind":"object","properties":[{"name":"deferLoading","type":"dacf3d5f3e4ff619","optional":true},{"name":"description","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"parameters","type":"0aa62ab51b36c0e6","optional":false}],"indices":[]}, - "671e72b8036a3afb": {"kind":"union","types":["414a44a96f06339f","e103228e063b4762"]}, "672c4aa2a9b736e3": {"kind":"object","properties":[{"name":"form","type":"fee1caf8ab6a65c1","optional":false},{"name":"kind","type":"1922cd79a70806c1","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "67d5f73ceae82f52": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":true},{"name":"model","type":"4cd48f3e5108bd6e","optional":false},{"name":"provider","type":"4cd48f3e5108bd6e","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"stop","type":"93c33d9687613293","optional":true},{"name":"temperature","type":"0657d3ca5ac4e7bf","optional":true}],"indices":[]}, "68141cbe3994a5ff": {"kind":"literal","value":"max-tokens"}, @@ -1001,7 +994,6 @@ "7b1ce60193fa8b69": {"kind":"literal","value":"working-directory"}, "7c6129b6961cf4b2": {"kind":"object","properties":[{"name":"messageId","type":"4cd48f3e5108bd6e","optional":false},{"name":"sessionId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "7c9f44e90a0058f4": {"kind":"object","properties":[{"name":"data","type":"5302e76315127761","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"570e850224346133","optional":false}],"indices":[]}, - "7cf091467eb28887": {"kind":"union","types":["09f3f8dc417f2782","0ef3072084202b08","4b259f7f8f0a44ad","9632024d168f8169","fa87537f4a9c1e25"]}, "7d226b9fd95b4ba2": {"kind":"union","types":["1df34a6c086866ff","cffcdc19881ab61e"]}, "7d54a5a5e629e2e7": {"kind":"object","properties":[{"name":"category","type":"3305a4bb6b6c29b4","optional":true},{"name":"text","type":"4cd48f3e5108bd6e","optional":true}],"indices":[]}, "7d65acf0a3a43a8f": {"kind":"union","types":["1f0133217eeeffd8","277b61f9c6f81704","3c2535ca0a2062f1","4e44c9118152f682","83d6916021eede8f","85f9b505b7bdff1f","fb8b6a2e53cb9cf4"]}, @@ -1046,16 +1038,15 @@ "8c1c4f72755a78ea": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"0fe52387f6505270","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, "8c627d7a45fa406c": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "8cdd3fdd295cee53": {"kind":"literal","value":"command/done"}, + "8e1f9a193d6937a5": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","65d583e66518c523","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e103228e063b4762","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, "8e31bb958e9b015e": {"kind":"object","properties":[{"name":"failure","type":"5f2117d1856a5ab4","optional":false},{"name":"kind","type":"1e0b0b66b6f0c53f","optional":false}],"indices":[]}, "8e611bf46821e500": {"kind":"object","properties":[{"name":"tool","type":"fcf427290c6213c2","optional":true},{"name":"toolName","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"85f9b505b7bdff1f","optional":false}],"indices":[]}, "8e8155594e078123": {"kind":"literal","value":"workspace/changes"}, "8e8e91646a5cae3f": {"kind":"object","properties":[{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"operation","type":"26c879bdd130e8b8","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, - "8eda0195dd1d4a3f": {"kind":"object","properties":[{"name":"maxTokens","type":"0657d3ca5ac4e7bf","optional":false},{"name":"messageSeqs","type":"5d03ba38734809bc","optional":false},{"name":"messages","type":"2f5b14bc2ccbe881","optional":false},{"name":"reasoningEffort","type":"4cd48f3e5108bd6e","optional":true},{"name":"route","type":"07e1a58c58b593f5","optional":false},{"name":"system","type":"4cd48f3e5108bd6e","optional":false},{"name":"titleProvider","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "8f324cea3916c051": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"2f3f201c8846e19c","optional":false}],"indices":[]}, "8f6d609794bf5afc": {"kind":"tuple","elements":[{"type":"4e88776858652856","optional":false,"rest":false}]}, "8fcb0b44a575e998": {"kind":"object","properties":[{"name":"kind","type":"a720757f704a2512","optional":false}],"indices":[]}, "904bf499d98be4a3": {"kind":"union","types":["7382c4fc95fe8c5d","b765ee31033cea46","f6cb462f808309e3"]}, - "92b82e8b7bbb38b3": {"kind":"object","properties":[{"name":"data","type":"d582ddae3f21edf5","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, "930c1cd289def119": {"kind":"union","types":["2142e391b9ccb60d","2c4c21f19f426a58","811229eedcec5797","fab35c4520883c0d"]}, "935c8d486292b13c": {"kind":"literal","value":"web_search"}, "938f7c7357bc403e": {"kind":"primitive","type":"null"}, @@ -1066,7 +1057,6 @@ "95080295115fbd83": {"kind":"object","properties":[{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"text","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"5abab3a27fd27366","optional":false}],"indices":[]}, "95a3ffccbf3c8afc": {"kind":"literal","value":"owner"}, "95c0e88fcf0bafb2": {"kind":"literal","value":"assistant"}, - "9632024d168f8169": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"68f44f59e91c4139","optional":false},{"name":"source","type":"e3e9f4d1e245b470","optional":false,"compatibility":{"version":1,"policy":"session-source-attribution","binding":"session.user-message.source","discriminator":"kind","unknownKinds":"preserve","attributionKinds":["action-cards","tmux-context","user-question-reply","working-directory"]}}],"indices":[]}, "9645cf93769a6dfb": {"kind":"literal","value":"user-question-reply"}, "96bfb916a727cd44": {"kind":"object","properties":[{"name":"form","type":"383cf73ae4748e5b","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, "96f4a9c81fc0f940": {"kind":"union","types":["3e2ece4967cfebe9","62ecb62dc0fe1b5e"]}, @@ -1106,7 +1096,6 @@ "a40d12070f6f4a12": {"kind":"object","properties":[{"name":"task","type":"51edec100659da3a","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]}, "a6567ccb2e530606": {"kind":"object","properties":[{"name":"data","type":"0d47a8f3d847243c","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"0a8a062bc9c3c0d9","optional":false}],"indices":[]}, "a6b41f5bede8094d": {"kind":"union","types":["15495614ccfa9cbd","aff60ef947c18b01"]}, - "a6d8c25f4b2ca2d1": {"kind":"object","properties":[{"name":"data","type":"c66c6c3b411abf46","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"829a2fb3be6efd4b","optional":false}],"indices":[]}, "a6da9ad5d42ae83a": {"kind":"object","properties":[{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"runId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, "a720757f704a2512": {"kind":"literal","value":"tool-registry"}, "a7cf43ce7c2a4c03": {"kind":"object","properties":[{"name":"data","type":"20a57544bbf204da","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"f634871d1e18b1eb","optional":false}],"indices":[]}, @@ -1126,6 +1115,7 @@ "ab7ec5f9eb2c6dfa": {"kind":"object","properties":[{"name":"kind","type":"68141cbe3994a5ff","optional":false}],"indices":[]}, "aba175fb20ffc711": {"kind":"array","element":"83079c8a3f733ac3"}, "abd6ca62a5ab1665": {"kind":"object","properties":[{"name":"apiVersion","type":"4cd48f3e5108bd6e","optional":false},{"name":"body","type":"9c001ecb5e1d3c38","optional":false},{"name":"endpoint","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "acbb9d5bfdd35628": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"5ca953e599fc0c19","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "ad0c38a0adb82a94": {"kind":"object","properties":[{"name":"header","type":"9a930283b91d493d","optional":false},{"name":"reason","type":"530da6c0267813bb","optional":false},{"name":"startsSeries","type":"dacf3d5f3e4ff619","optional":true}],"indices":[]}, "ad22a5ca5bbaab2e": {"kind":"literal","value":"edit"}, "ad3b56aa2fc1ad42": {"kind":"literal","value":"legacy"}, @@ -1194,17 +1184,17 @@ "c2f49004d9ae38a9": {"kind":"union","types":["1ebc6f9ff3aa9e3a","b57a2889408de36e","ef226a90ddac6cd8"]}, "c43b119ce579b3e9": {"kind":"literal","value":"paused"}, "c5228b7a55498d84": {"kind":"object","properties":[{"name":"form","type":"6f9e00383836275e","optional":false},{"name":"kind","type":"99e7e7d7fed55ae6","optional":false},{"name":"sections","type":"c748fb42c80a1200","optional":false}],"indices":[]}, + "c52b170222374a79": {"kind":"union","types":["09f3f8dc417f2782","183ccd2e6b70ddf9","4b259f7f8f0a44ad","5ca953e599fc0c19","fa87537f4a9c1e25"]}, "c55827fba7018198": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"9645cf93769a6dfb","optional":false},{"name":"outcome","type":"85d6d96ff8842974","optional":false}],"indices":[]}, "c5687b1313206a6f": {"kind":"literal","value":"cordis-host-runner"}, "c589e938cc0e4989": {"kind":"literal","value":"tool/call"}, "c592ce75aab73fca": {"kind":"union","types":["0657d3ca5ac4e7bf","4cd48f3e5108bd6e","529e0ccd0e34079a","72ec79127a9c0d02","938f7c7357bc403e","aa93010d0e521ad7"]}, - "c5bbcb3435908af2": {"kind":"object","properties":[{"name":"data","type":"9632024d168f8169","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"de25352d2dc41963","optional":false}],"indices":[]}, - "c66c6c3b411abf46": {"kind":"object","properties":[{"name":"inserted","type":"347c1718600bbb38","optional":false},{"name":"outcome","type":"d4e4d575abeb4b72","optional":true},{"name":"removedCount","type":"0657d3ca5ac4e7bf","optional":true},{"name":"start","type":"0657d3ca5ac4e7bf","optional":false},{"name":"target","type":"96f4a9c81fc0f940","optional":false}],"indices":[]}, "c748fb42c80a1200": {"kind":"array","element":"86e1e384495ee09a"}, "c8e6f48878cc2a2f": {"kind":"literal","value":"active"}, "c99e20441bf15341": {"kind":"literal","value":"feedback/record"}, "ca085dba2c877fec": {"kind":"literal","value":"sandbox/mode"}, "ca59530d1a4f3e19": {"kind":"object","properties":[{"name":"compactionId","type":"4cd48f3e5108bd6e","optional":false},{"name":"error","type":"4cd48f3e5108bd6e","optional":true},{"name":"sourceCommandId","type":"4cd48f3e5108bd6e","optional":true},{"name":"turn","type":"3bd652ebfa8726b3","optional":false}],"indices":[]}, + "caad8a860d58ff99": {"kind":"object","properties":[{"name":"data","type":"acbb9d5bfdd35628","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"sourceEventSeqs","type":"5d03ba38734809bc","optional":true},{"name":"surfaceOp","type":"335e242de1fcc17b","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"1379f0b72275994d","optional":false}],"indices":[]}, "cb1c8959e1726f82": {"kind":"object","properties":[{"name":"form","type":"16d0507cb94b4cfc","optional":false},{"name":"kind","type":"24b10f7cdae64928","optional":false}],"indices":[]}, "cbbe8377ee744964": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":true},{"name":"mode","type":"fc351843b8ecbd05","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]}, "cbca49bde16b656d": {"kind":"literal","value":"block-start"}, @@ -1219,13 +1209,11 @@ "d4b739491689cc6b": {"kind":"object","properties":[{"name":"block","type":"8064c03c87c357fd","optional":false},{"name":"index","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"eefbea819caeeea9","optional":false}],"indices":[]}, "d4c799e3d51cae10": {"kind":"literal","value":"working-directory/change"}, "d4e4d575abeb4b72": {"kind":"literal","value":"canceled"}, - "d582ddae3f21edf5": {"kind":"object","properties":[{"name":"headerSeq","type":"0657d3ca5ac4e7bf","optional":true},{"name":"message","type":"0ef3072084202b08","optional":false},{"name":"step","type":"0657d3ca5ac4e7bf","optional":false},{"name":"turn","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "d595ec73b32b016a": {"kind":"object","properties":[{"name":"data","type":"a40d12070f6f4a12","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"b57ae1b37e52bee0","optional":false}],"indices":[]}, "d63b8b8ffad9c02f": {"kind":"object","properties":[{"name":"data","type":"8bb8c3751a6ce64b","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"47e5d84f1a93450a","optional":false}],"indices":[]}, "d66e11dddf5dfa3c": {"kind":"object","properties":[{"name":"data","type":"cef171adf021a186","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"fb5e0517050d2c6d","optional":false}],"indices":[]}, "d6e654b88b584fcd": {"kind":"literal","value":"fallback"}, "d85590a5675c6e2f": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, - "d8dde9629b7323b0": {"kind":"object","properties":[{"name":"data","type":"2a668a48dec17397","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"30093f9f63a120cb","optional":false}],"indices":[]}, "d9196086e41e4dd5": {"kind":"literal","value":"complete"}, "d9c462b9f62fdd3e": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"a13d8f379222ece8","optional":false}],"indices":[]}, "d9edf1862e4471d6": {"kind":"literal","value":"schedule"}, @@ -1250,6 +1238,7 @@ "dfce5db4aa42b7eb": {"kind":"union","types":["871bfd6f7bb91061","b57a2889408de36e","c8e6f48878cc2a2f"]}, "e0a787e6ec76c7c9": {"kind":"object","properties":[{"name":"data","type":"ef11a3b7fa2033ae","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"c219efbdfd53b836","optional":false}],"indices":[]}, "e103228e063b4762": {"kind":"object","properties":[{"name":"clientTimeZone","type":"4cd48f3e5108bd6e","optional":true},{"name":"kind","type":"63ba8895cc09e469","optional":false},{"name":"principal","type":"deebfc7c62177ea8","optional":true},{"name":"rpcId","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, + "e139090c38f84d8d": {"kind":"array","element":"183ccd2e6b70ddf9"}, "e2ad8f5bcd813f26": {"kind":"object","properties":[{"name":"childCreatedAt","type":"0657d3ca5ac4e7bf","optional":false},{"name":"childId","type":"4cd48f3e5108bd6e","optional":false},{"name":"label","type":"4cd48f3e5108bd6e","optional":false},{"name":"mode","type":"e5d3828df1ec3e2a","optional":false},{"name":"version","type":"f88f1b7b6bb733f6","optional":false}],"indices":[]}, "e2d0367e5716bc18": {"kind":"literal","value":"compaction/start"}, "e2f9a41e0989f54e": {"kind":"object","properties":[{"name":"data","type":"9947aa5938fe19a8","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"ef4f00923b9edbae","optional":false}],"indices":[]}, @@ -1257,7 +1246,6 @@ "e308ccf867a5398e": {"kind":"object","properties":[{"name":"data","type":"f90eb4ab2d3897bc","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"8e8155594e078123","optional":false}],"indices":[]}, "e31572180d6b28b7": {"kind":"literal","value":"provider"}, "e3e77b26f0148755": {"kind":"object","properties":[{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"4cd48f3e5108bd6e","optional":false}],"indices":[]}, - "e3e9f4d1e245b470": {"kind":"union","types":["0203243b72fc867a","022623b9ef98ec47","02b2c23f2d94b028","0ed8d1108fe7a5e1","0eeb17e1df89b1eb","11638dc58a31496f","1938c8c20f1cba7c","1e709346005826eb","1fdad624334d3548","2152caf1a71e588e","2235d957bf4b7c6c","224aa11d4d5f88c4","24ce31aa528272e5","260523b412c19ad8","28936c614bd4e7e2","290f32bfd81ff85a","297fbd8067d7815a","2aad1179f9576314","312aee2f0c42aabb","31907de956375182","3247e566f4340029","3b04790e08b35241","3b4f2c9ef2c1217e","3bdb8dc6989655b0","408927267422a7b2","414a44a96f06339f","45f005e6bbb76452","4991dfc415c9efeb","49dab019d00e8c26","4a9bef9f27a94252","4e97c3d85c0fc817","52c6113cb0e53f34","53cf1704ae0709c7","5654685d7557ec74","58015da1c1950a66","5bb69b88517f6214","5bc66269cfaf614c","5cdbf342b7748d15","5d7a64ed6aac5551","6085488d223aeffd","6292bcbedaa97fea","65a8400e451e667e","65d583e66518c523","672c4aa2a9b736e3","6ccb6bc423fb3952","6dacb1584f2bfbe2","6ea608bdc8a7a63d","6f22a9d02b9a1f11","6f44f32be9bcd9a8","719c51f67074f970","7554565be416b034","756b5e23a5cb2c7f","7df62978a2b7de85","7eaef7a763ba5b94","7f959241c24128b4","80677c8c8b804189","825ca23416b1ea9d","8338ee303e2bd448","854db3efdfb0cd39","85fe96bf15d5c755","8620189c8e335827","87b1a33564b9a4d6","88841202942c7683","8a046b52331959aa","8c1c4f72755a78ea","8c627d7a45fa406c","8f324cea3916c051","8fcb0b44a575e998","96bfb916a727cd44","99135d8a39379779","99f0e98184709ad1","9edc162949abf93b","9f65c43e340bba31","a15a19ef63cf5d1b","a16e567c8cd2c55c","a2066bd52f61218b","a3c6f1ea38e9a61d","aa2bf9e82b9fdeb9","aae343bc3d663c83","ada310bf0bdb8fed","ade057f4fdc387ae","b062078e899958f9","b2a5c8c008a31e3d","b3681c026a0b98e9","b4ec0af39fd52ed9","b613f83572fe2fbf","b6f94c17dd9bbd7c","b70f3e81fe11b7a5","b79641a9d25e7566","ba2a1a8f194b1bb2","ba4c40b4381e0dcd","baccc97ec34be6c4","c5228b7a55498d84","c55827fba7018198","cb1c8959e1726f82","cda2345a9be9d139","d85590a5675c6e2f","d9c462b9f62fdd3e","db06ef35b27a3842","dba54c09c6e7aafc","dd00cdccd0ba487b","dd5ee8762bd8a14b","dd6cc2bb2a8f2824","dde0cdf9d426768b","deaae5cd30987d4f","e103228e063b4762","e7f4cbb3a57f4cbd","edbbfff1128099fd","ee577a0cc18664c9","f230c0b583e23b25","f29d63336e8266d8","f4a66f654996c093","f56c5b59ca016131","f6329fb30c7ea801","f6605d6c57933aea","f6afe41e3926f422","f7283223aacdc39b","f97f47b0eb87905e","fcf81d3c58c13593","fe1a7acdeed4d0f5"]}, "e41bdebe246abe17": {"kind":"literal","value":"session"}, "e42b0f40b5d0b628": {"kind":"literal","value":"every"}, "e438a9d4fb883b3e": {"kind":"literal","value":"append"}, @@ -1275,8 +1263,10 @@ "e7f4cbb3a57f4cbd": {"kind":"object","properties":[{"name":"form","type":"e8de0f457fed5b9f","optional":false},{"name":"kind","type":"05205074b24d8340","optional":false},{"name":"summary","type":"4cd48f3e5108bd6e","optional":false},{"name":"through","type":"0657d3ca5ac4e7bf","optional":false}],"indices":[]}, "e8de0f457fed5b9f": {"kind":"literal","value":"notice"}, "e95f957154b2b1b5": {"kind":"literal","value":"assistant/attempt"}, + "e9fad8e4e6a99024": {"kind":"array","element":"c52b170222374a79"}, "ea08ec1df0c33108": {"kind":"object","properties":[{"name":"message","type":"2eafefdc6f79a492","optional":false},{"name":"teamId","type":"4cd48f3e5108bd6e","optional":false},{"name":"version","type":"a0f906856fc06f1c","optional":false}],"indices":[]}, "eaa71be0fbf0f702": {"kind":"object","properties":[{"name":"kind","type":"ad3b56aa2fc1ad42","optional":false}],"indices":[]}, + "eb0fc09c936ebe3d": {"kind":"object","properties":[{"name":"data","type":"5a29653980f4013e","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, "ebb768d85ec87fb6": {"kind":"literal","value":"image/webp"}, "ec38b5949af8eaca": {"kind":"object","properties":[{"name":"data","type":"2fe5026daf14bd4f","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"2aed07493b1c65b6","optional":false}],"indices":[]}, "ec8b5ff7c77ca689": {"kind":"object","properties":[{"name":"kind","type":"b2245a906e13aa94","optional":false}],"indices":[]}, @@ -1317,7 +1307,6 @@ "fa87537f4a9c1e25": {"kind":"object","properties":[{"name":"content","type":"b52f215a6cd69105","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"role","type":"95c0e88fcf0bafb2","optional":false},{"name":"source","type":"719c51f67074f970","optional":false}],"indices":[]}, "fab2bb09bc5e3b9d": {"kind":"union","types":["12a0cfa619ac4a5f","18fce9540bb20897","a3edd1efd1fef21b"]}, "fab35c4520883c0d": {"kind":"literal","value":"rejected"}, - "fb28ad7d7aabceb5": {"kind":"object","properties":[{"name":"data","type":"8eda0195dd1d4a3f","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"587bb11da730e06a","optional":false}],"indices":[]}, "fb5e0517050d2c6d": {"kind":"literal","value":"tool/ptc-dispatch"}, "fb8b6a2e53cb9cf4": {"kind":"literal","value":"image"}, "fbd2c74b346a1595": {"kind":"literal","value":"agent-instructions"}, diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index d709735217..86df19aa94 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -104,8 +104,6 @@ interface Agent { /** * Route identified input to an inbox boundary and optionally wake the driver. - * A retry-source input must be empty; admission records `agent/retry` - * without adding a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -118,8 +116,8 @@ interface Agent { send(message: UserMessage, target: InboxTarget, wakeup: boolean): void /** - * Queue a follow-up turn and wake the driver. The item is its sole ordinary - * prompt, or an empty retry-source control with no model-visible message. + * Queue an ordinary follow-up turn and wake the driver. The item becomes the + * sole ordinary message of its own turn. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 11fa2a6ffe..db4bb206f5 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -108,8 +108,6 @@ interface Agent { /** * Route identified input to an inbox boundary and optionally wake the driver. - * A retry-source input must be empty; admission records `agent/retry` - * without adding a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -122,8 +120,8 @@ interface Agent { send(message: UserMessage, target: InboxTarget, wakeup: boolean): void /** - * Queue a follow-up turn and wake the driver. The item is its sole ordinary - * prompt, or an empty retry-source control with no model-visible message. + * Queue an ordinary follow-up turn and wake the driver. The item becomes the + * sole ordinary message of its own turn. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml deleted file mode 100644 index 9573c050b1..0000000000 --- a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.i18n.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md -/: - en: f5e2d8120bcf97bd - zh: 64b692f69ec7b0d4 -/empty-prompts-and-retry-controls: - en: e26b58650aa8e914 - zh: c0e5a824ec3bea40 -/empty-prompts-and-retry-controls/change: - en: bf69985ccd8cc89a - zh: b1046f8c98cbdb43 -/empty-prompts-and-retry-controls/migration: - en: 028f7fd6db73a36b - zh: b72f840db5301d6c diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md deleted file mode 100644 index c74f3c2047..0000000000 --- a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -kind: upgrade-guide -description: Empty API and SDK prompts become retry controls whose required Session event needs an updated reader. ---- - -# Empty prompts and retry controls - -English | [中文](guide.zh.md) - -## Change - -The Session prompt API accepts `content: []` to continue eligible failed, idle sessions in queue mode. SDK `session/prompt` maps empty `contentBlocks` to the same retry-source control. Acceptance records `agent/retry` with `turn`, `step`, `inputId`, and `source`, instead of a `user/message`. A new turn still starts and closes normally. Direct Agent callers using ordinary empty user messages retain their existing behavior. - -The new required event and retry source keep the current Session format version. Updated readers accept older logs; older readers reject logs containing the unknown required event. See the [persistence acknowledgement](../../../persistence-changes/2026-10-27-retry-control-input.md). - -## Migration - -1. Upgrade Session readers and SDK runtimes together before opening logs containing retry controls. -2. Consumers correlating empty API or SDK prompts with `user/message` must use the durable inbox identity and `agent/retry.inputId`. Handle the retry source without treating it as model-visible user content. -3. Verify that a continued failure produces a balanced new turn and `agent/retry`, while derived model history contains no additional user message. diff --git a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md deleted file mode 100644 index 66f436fae9..0000000000 --- a/docs/upgrade-guide/v0.2.1-rc.3/empty-prompt-continuation/guide.zh.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -kind: upgrade-guide -description: 空 API 和 SDK 提示词变为重试控制输入,其必读 Session 事件需要更新后的读取方。 ---- - -# 空提示词与重试控制输入 - -[English](guide.md) | 中文 - -## 变更 - -Session prompt API 接受 `content: []`,在 queue 模式下继续符合条件且空闲的失败会话。SDK `session/prompt` 将空 `contentBlocks` 映射到同样的 retry 来源控制输入。接受后记录带 `turn`、`step`、`inputId` 和 `source` 的 `agent/retry`,而非 `user/message`。新轮次仍正常开启和闭合。直接使用普通空用户消息的 Agent 调用方保留原有行为。 - -新增必读事件及 retry 来源保留当前 Session 格式版本。更新后的读取方接受旧日志;旧读取方会拒绝包含未知必读事件的日志。见[持久化确认记录](../../../persistence-changes/2026-10-27-retry-control-input.zh.md)。 - -## 迁移 - -1. 打开包含重试控制输入的日志前,同时升级 Session 读取方与 SDK 运行时。 -2. 通过 `user/message` 关联空 API 或 SDK 提示词的消费方,改用持久 inbox 身份与 `agent/retry.inputId`。处理 retry 来源时,不应将其视为模型可见用户内容。 -3. 确认继续失败请求会产生正常开启并闭合的新轮次和 `agent/retry`,而派生模型历史中没有额外用户消息。 diff --git a/packages/api/session-controller/README.i18n.yaml b/packages/api/session-controller/README.i18n.yaml index 99568c2d0c..a1c7a71267 100644 --- a/packages/api/session-controller/README.i18n.yaml +++ b/packages/api/session-controller/README.i18n.yaml @@ -24,8 +24,8 @@ en: 954923654eebe2dc zh: 6bd9a7d2ea965aed /session-controller/configuration: - en: c1b6d81775d0b56e - zh: 67c3ef16ff39c6c3 + en: 8f62f7183c67fc02 + zh: 0e2db400b376629f /session-controller/model-experience: en: 30b917bf822bb4df zh: b0b023b5b526c317 diff --git a/packages/api/session-controller/README.md b/packages/api/session-controller/README.md index 77b6f9eb1b..b6e418b1ac 100644 --- a/packages/api/session-controller/README.md +++ b/packages/api/session-controller/README.md @@ -110,7 +110,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a Blockquotes travel as ordinary prompt text without quote metadata. Local submission echoes and durable messages use the same text rendering rules; queue edits replace content while retaining prompt correlation. -An empty `prompt.content` array requests a new turn only for an eligible failed, idle ordinary Session in queue mode. Pending human input prevents admission; non-waking injected context is retained. `conversationContinuation` supplies the shared six-code manual policy, independently of provider retry configuration. The input uses `source.kind: 'retry'` and ordinary inbox admission; Loop acceptance records `agent/retry` with the input identity and caller metadata for deduplication and audit, without adding a model-visible message. Whitespace-only text parts remain invalid. +An empty `prompt.content` array requests a new turn only for an eligible failed, idle ordinary Session in queue mode. Pending human input prevents admission; non-waking injected context is retained. `conversationContinuation` supplies the shared six-code manual policy, independently of provider retry configuration. The input follows ordinary inbox and Loop admission and records an empty `user/message` with `source.kind: 'retry'`; its input identity and caller metadata support deduplication and recovery. Whitespace-only text parts remain invalid. ## Model Experience diff --git a/packages/api/session-controller/README.zh.md b/packages/api/session-controller/README.zh.md index 53387989ad..f9f6e85c3e 100644 --- a/packages/api/session-controller/README.zh.md +++ b/packages/api/session-controller/README.zh.md @@ -110,7 +110,7 @@ GUI 模型选择要求确切提供方/模型对出现在可用目录中;不 Blockquote 作为普通 prompt 文本传递,不携带 quote 元数据。本地提交回显与持久消息使用同一文本渲染规则;队列编辑替换 content,同时保留 prompt 关联信息。 -空的 `prompt.content` 数组仅在普通 Session 空闲、最新失败符合继续条件且使用 queue 模式时请求新轮次。待消费的用户输入阻止准入,非唤醒的注入上下文保留。`conversationContinuation` 提供共用的六码手动策略,独立于提供方重试配置。输入使用 `source.kind: 'retry'` 并沿用普通 inbox 准入;Loop 接受后记录带输入身份和调用方元数据的 `agent/retry`,供去重和审计使用,不增加模型可见消息。仅含空白的文本块仍无效。 +空的 `prompt.content` 数组仅在普通 Session 空闲、最新失败符合继续条件且使用 queue 模式时请求新轮次。待消费的用户输入阻止准入,非唤醒的注入上下文保留。`conversationContinuation` 提供共用的六码手动策略,独立于提供方重试配置。输入遵循普通 inbox 和 Loop 准入,记录带 `source.kind: 'retry'` 的空 `user/message`;输入身份与调用方元数据用于去重和恢复。仅含空白的文本块仍无效。 ## 模型体验 diff --git a/packages/api/session-controller/src/commands.ts b/packages/api/session-controller/src/commands.ts index 15b110c8ac..ba98b5891f 100644 --- a/packages/api/session-controller/src/commands.ts +++ b/packages/api/session-controller/src/commands.ts @@ -308,7 +308,7 @@ export class SessionCommandController { * Admit one prompt after Agent and attachment validation; the Remote method * has already validated the content and built the durable message source. * @param request - Session identity, prompt content, and delivery mode. - * @param source - the `user-rpc` source recorded on the accepted message. + * @param source - caller metadata recorded on the accepted ordinary or retry user message. * @returns acknowledgement that the Agent accepted the prompt. */ async prompt(request: SessionPromptRequest, source: MessageSource): Promise { @@ -573,12 +573,7 @@ function hasPromptRequest(agent: Agent, requestId: SessionRequestId): boolean { if (agent.inbox.nextTurn.some(matches) || agent.inbox.nextStep.some(matches)) return true // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. return agent.session.snapshotEvents().some((event) => { - if (event.type === 'agent/retry') { - const source = event.data.source - return 'rpcId' in source && source.rpcId === requestId - } if (event.type !== 'user/message') return false - const source = event.data.source - return source.kind === 'user' && 'rpcId' in source && source.rpcId === requestId + return matches(event.data) }) } diff --git a/packages/api/session-controller/src/continuation.ts b/packages/api/session-controller/src/continuation.ts index 0e8247688a..3be2a2be1d 100644 --- a/packages/api/session-controller/src/continuation.ts +++ b/packages/api/session-controller/src/continuation.ts @@ -17,7 +17,7 @@ const stateSchema = z.object({ ending: endingSchema, }) -/** Recoverable manual-continuation facts over admitted user input and retry controls. */ +/** Recoverable manual-continuation facts over admitted user input and empty retry messages. */ export type ContinuationState = z.infer const EMPTY: ContinuationState = { admitted: false, failedAttempt: false, ending: null } @@ -25,20 +25,20 @@ const EMPTY: ContinuationState = { admitted: false, failedAttempt: false, ending /** Eligibility requires admitted human work and a failed settlement in the last request step. */ export const continuationProjectionDefinition = { key: 'conversationContinuation', - stateVersion: 3, + stateVersion: 4, stateSchema, init: (): ContinuationState => EMPTY, apply: (state, event) => { switch (event.type) { case 'turn/start': return EMPTY - case 'agent/retry': - return state.admitted ? state : { ...state, admitted: true } case 'step/start': case 'assistant/message': return state.failedAttempt ? { ...state, failedAttempt: false } : state case 'user/message': - return !state.admitted && event.surfaceOp === 'append' && event.data.source.kind === 'user' && event.data.content.length > 0 + return !state.admitted && event.surfaceOp === 'append' + && ((event.data.source.kind === 'user' && event.data.content.length > 0) + || (event.data.source.kind === 'retry' && event.data.content.length === 0)) ? { ...state, admitted: true } : state case 'assistant/attempt': return state.failedAttempt ? state : { ...state, failedAttempt: true } diff --git a/packages/api/session-controller/src/types.ts b/packages/api/session-controller/src/types.ts index 91818f9748..89301330d6 100644 --- a/packages/api/session-controller/src/types.ts +++ b/packages/api/session-controller/src/types.ts @@ -395,7 +395,7 @@ declare module '@deepseek-ai/dsh-llm' { * caller other than the owner, so an absent `principal` means the owner. */ 'user-rpc': { kind: 'user'; rpcId: SessionRequestId; clientTimeZone?: string; principal?: Principal } - /** Browser retry correlation with the same caller metadata as an ordinary prompt. */ + /** Empty retry user message with browser prompt correlation and caller metadata. */ 'retry-rpc': { kind: 'retry'; rpcId: SessionRequestId; clientTimeZone?: string; principal?: Principal } } } diff --git a/packages/api/session-controller/tests/continuation.host.spec.ts b/packages/api/session-controller/tests/continuation.host.spec.ts index f2ae6ecf35..1ea9f649da 100644 --- a/packages/api/session-controller/tests/continuation.host.spec.ts +++ b/packages/api/session-controller/tests/continuation.host.spec.ts @@ -111,7 +111,7 @@ it('publishes a complete-log result independently of the visible page and releas expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBe(ending) expect(changed.mock.calls.map(([, key, value]) => [key, value])).toEqual([['conversationContinuation', null], ['conversationContinuation', ending]]) expect(ctx.sessionProjections.checkpoint(session).conversationContinuation) - .toEqual({ ver: 3, seq: ending, val: { admitted: false, failedAttempt: false, ending } }) + .toEqual({ ver: 4, seq: ending, val: { admitted: false, failedAttempt: false, ending } }) session.append('turn/start', { turn: 2 }) expect(ctx.sessionProjections.snapshot(session).values.conversationContinuation).toBeNull() expect(changed).toHaveBeenCalledTimes(3) @@ -127,27 +127,35 @@ it('restores valid, absent and outdated checkpoints through the registry', async await ctx.sessionProjections.register(definition) const session = ctx.sessions.create(SessionId('restored')) const log = events(failed('SERVER')) - const checkpoint = { conversationContinuation: { ver: 3, seq: SessionSeq(2), val: fold(log.slice(0, 3)) } } + const checkpoint = { conversationContinuation: { ver: 4, seq: SessionSeq(2), val: fold(log.slice(0, 3)) } } const restore = (rows: typeof checkpoint | Record, tail: readonly SessionEvent[], base: number) => ctx.sessionProjections.restore(rows, tail, SessionLogOffset(base), session.header, session.inheritedEventCount) expect(restore(checkpoint, log.slice(3), 3).snapshot.values.conversationContinuation).toBe(5) expect(restore({}, log, 0).snapshot.values.conversationContinuation).toBe(5) - const outdated = { conversationContinuation: { ...checkpoint.conversationContinuation, ver: 2 } } + const outdated = { conversationContinuation: { ...checkpoint.conversationContinuation, ver: 3 } } expect(restore(outdated, log, 0).snapshot.values.conversationContinuation).toBe(5) expect(() => restore(outdated, log.slice(3), 3)).toThrow('re-read from seq 0') expect(() => restore({}, log.slice(3), 3)).toThrow('re-read from seq 0') }) -it('admits retry controls independently of empty ordinary user messages', () => { +it('admits empty retry messages while ignoring empty ordinary and non-empty retry messages', () => { const base = events(failed('SERVER')) const input = base.find(event => event.type === 'user/message')! const empty: SessionEvent<'user/message'> = { ...input, type: 'user/message', data: createUserMessage({ content: [], source: { kind: 'user' }, }) } expect(fold(base.map(event => event === input ? empty : event)).ending).toBeNull() - const retry: SessionEvent<'agent/retry'> = { type: 'agent/retry', seq: empty.seq, time: empty.time, - data: { turn: 1, step: 1, inputId: empty.data.id, source: { kind: 'retry' } }, + const retry: SessionEvent<'user/message'> = { ...empty, data: createUserMessage({ + content: [], source: { kind: 'retry', rpcId: 'retry' as import('../src/types.ts').SessionRequestId }, + }) } + const nonEmpty: SessionEvent<'user/message'> = { ...retry, data: { ...retry.data, + content: [{ type: 'text', text: 'not an empty retry' }], + } } + expect(fold(base.map(event => event === input ? nonEmpty : event)).ending).toBeNull() + const replacement: SessionEvent<'user/message'> = { ...retry, + surfaceOp: { op: 'replace', startSeq: SessionSeq(0), endSeq: SessionSeq(1) }, } + expect(fold(base.map(event => event === input ? replacement : event)).ending).toBeNull() expect(fold(base.map(event => event === input ? retry : event)).ending).toBe(5) const admitted = definition.apply(definition.init(), retry) expect(definition.apply(admitted, retry)).toBe(admitted) diff --git a/packages/api/session-controller/tests/empty-prompt.host.spec.ts b/packages/api/session-controller/tests/empty-prompt.host.spec.ts index 50d4871ee8..4f38b9901b 100644 --- a/packages/api/session-controller/tests/empty-prompt.host.spec.ts +++ b/packages/api/session-controller/tests/empty-prompt.host.spec.ts @@ -25,7 +25,7 @@ async function setup() { return { ctx, remote, agent, adapter, request } } -it('records retry controls without user messages or duplicate requests', async () => { +it('records empty retry user messages without duplicate requests', async () => { const { remote, agent, adapter, request } = await setup() agent.followup(createUserMessage({ content: [{ type: 'text', text: 'original task' }], source: { kind: 'user' } })) await agent.whenIdle() @@ -42,19 +42,19 @@ it('records retry controls without user messages or duplicate requests', async ( const events = agent.session.snapshotEvents() const messages = events.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'user' ? [event.data] : []) expect(messages).toHaveLength(1) - const retries = events.flatMap(event => event.type === 'agent/retry' ? [event.data] : []) + const retries = events.flatMap(event => event.type === 'user/message' && event.data.source.kind === 'retry' ? [event.data] : []) expect(retries).toHaveLength(2) expect(retries).toMatchObject([ - { turn: 2, step: 1, source: { kind: 'retry', rpcId: 'continue-1' } }, - { turn: 3, step: 1, source: { kind: 'retry', rpcId: 'continue-2' } }, + { content: [], source: { kind: 'retry', rpcId: 'continue-1' } }, + { content: [], source: { kind: 'retry', rpcId: 'continue-2' } }, ]) const controls = events.flatMap(event => event.type === 'agent/inbox/spliced' ? event.data.inserted : []) .filter(message => message.source.kind === 'retry') - expect(retries.map(retry => retry.inputId)).toEqual(controls.map(control => control.id)) - for (const call of adapter.requests) { - expect(call.messages.filter(message => message.role === 'user').map(message => message.content)) - .toEqual([[{ type: 'text', text: 'original task' }]]) - } + expect(retries.map(retry => retry.id)).toEqual(controls.map(control => control.id)) + expect(adapter.requests[1]!.messages.filter(message => message.role === 'user').map(message => message.content)) + .toEqual([[{ type: 'text', text: 'original task' }], []]) + expect(adapter.requests[2]!.messages.filter(message => message.role === 'user').map(message => message.content)) + .toEqual([[{ type: 'text', text: 'original task' }], [], []]) expect(events.flatMap(event => event.type === 'turn/end' ? [event.data.reason.kind] : [])).toEqual(['error', 'error', 'completed']) expect(events.filter(event => event.type === 'turn/start')).toHaveLength(3) expect((await remote.prompt(request('after-completion'))).ok).toBe(false) diff --git a/packages/client/ui-chat/README.i18n.yaml b/packages/client/ui-chat/README.i18n.yaml index 2ff8f221ce..d12e5bfeb8 100644 --- a/packages/client/ui-chat/README.i18n.yaml +++ b/packages/client/ui-chat/README.i18n.yaml @@ -30,8 +30,8 @@ en: 382821ca3ffa60dd zh: 9840ffa07c13082a /deepseek-ai-dsh-client-ui-chat/turn-process-folding: - en: b0d806604bc17006 - zh: 18dabe5dc2d0d419 + en: 0b1a8109ad0fd9bf + zh: f7274b42510550d0 /deepseek-ai-dsh-client-ui-chat/grouped-rendering: en: 3afad0b4c6bf9ce0 zh: d8179ba4ee9fcd61 diff --git a/packages/client/ui-chat/README.md b/packages/client/ui-chat/README.md index ceeae1d086..b87ab07950 100644 --- a/packages/client/ui-chat/README.md +++ b/packages/client/ui-chat/README.md @@ -90,6 +90,8 @@ The completed-turn action footer follows the recorded Turn end. Its action row s ## Turn Process Folding +Empty `user/message` records with `source.kind: 'retry'` remain in Session history while Chat omits their view nodes. Ordinary empty user messages and retry messages containing content retain their normal presentation. + During uninterrupted following, local transcript and steering echoes remain mounted through Inbox acceptance and claim until the durable message arrives, without triggering tail following twice. Pending steering follows Inbox order across clients, using matching local echoes in place. Admitted local steering also suppresses matching stale Inbox rows until the claim projection arrives; steering without a locally tracked submission continues to follow the Inbox projection. After reconnect, Host-owned rows replace receipt-confirmed local echoes; a claim awaiting admission may briefly have no bubble. When Chat ends with an open Turn control and that Turn has no visible input, the first local transcript echo precedes the control. Other echoes remain at the flow tail. The control and echoes share one keyed list, so arrival of the control preserves the echo's mounted identity. Durable inputs replace their matching echoes in the same render. diff --git a/packages/client/ui-chat/README.zh.md b/packages/client/ui-chat/README.zh.md index 9113ed2f81..27efb143ec 100644 --- a/packages/client/ui-chat/README.zh.md +++ b/packages/client/ui-chat/README.zh.md @@ -90,6 +90,8 @@ Assistant 尝试结束且没有可见消息时,Chat 隐藏已发布的 Node, ## 轮次过程折叠 +带 `source.kind: 'retry'` 的空 `user/message` 记录保留在 Session 历史中,Chat 不生成对应展示节点。普通空用户消息与包含内容的重试消息保持正常展示。 + 正常在线时,本地 transcript 与 steering 回显在 Inbox 接受与领取期间保持挂载,直到持久消息到达,不会重复触发跟随底部。跨客户端的待处理 steering 遵循 Inbox 顺序,在匹配位置使用本地回显。已入档的本地 steering 还会排除匹配的旧 Inbox 行,直到领取投影到达;没有本地提交身份的 steering 仍按 Inbox 投影显示。重连后,Host 消息替代已有接收回执的本地回显;已领取但尚未入档时,气泡可能短暂消失。 Chat 末尾为进行中的 Turn 控制行,且该轮尚无可见输入时,第一条本地 transcript 回显显示在控制行前。其他回显保留在正文末尾。控制行与回显共用一个 keyed 列表,因此控制行到达时不会重新挂载回显。持久输入在同一次渲染中替换匹配的回显。 diff --git a/packages/client/ui-chat/src/client/conversation-nodes/message.ts b/packages/client/ui-chat/src/client/conversation-nodes/message.ts index fb7684a8f7..9156b686e1 100644 --- a/packages/client/ui-chat/src/client/conversation-nodes/message.ts +++ b/packages/client/ui-chat/src/client/conversation-nodes/message.ts @@ -136,6 +136,7 @@ export const messageDefinition: ConversationNodeDefinition = { update: context => context.state, buildViewNode: (context) => { if (context.state === undefined) return null + if (context.state.source.kind === 'retry' && context.state.content.length === 0) return null const waking = context.state.kind === 'context' && context.start?.event.type === 'user/message' && context.state.waking === true diff --git a/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts b/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts index e8bf15bddc..b1cb83ddfd 100644 --- a/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts +++ b/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts @@ -615,6 +615,32 @@ describe('built-in conversation node Definitions', () => { }, ) + it.each(['replay', 'live', 'prepend'] as const)('hides empty retry bubbles through %s while retaining other input', (mode) => { + const retry = { id: 'empty-retry', role: 'user', content: [], source: { kind: 'retry' } } + const before = [ + at(1, 'agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [retry] }), + at(2, 'turn/start', { turn: 1 }), + at(3, 'agent/inbox/spliced', { target: 'next-turn', start: 0, removedCount: 1, inserted: [] }), + at(4, 'step/start', { turn: 1, step: 1 }), + ] + const message = at(5, 'user/message', retry, { surfaceOp: 'append' }) + const value = assembler(mode === 'replay' ? [...before, message] : mode === 'prepend' ? [message] : [], mode === 'prepend') + if (mode === 'live') for (const entry of [...before, message]) value.append(entry) + if (mode === 'prepend') value.prepend(before, false) + value.flush() + const current = snapshot(value) + expect(current.nodes.values().filter(candidate => candidate.anchorSeq === 5)).toEqual([]) + expect(node(current, 'unknown')).toBeUndefined() + + value.append(at(6, 'user/message', { ...retry, id: 'retry-with-content', + content: [{ type: 'text', text: 'retained context' }], + }, { surfaceOp: 'append' })) + value.append(at(7, 'user/message', { ...retry, id: 'ordinary-empty', source: { kind: 'user' } }, { surfaceOp: 'append' })) + value.flush() + expect(node(snapshot(value), 'context')?.data).toMatchObject({ content: [{ type: 'text', text: 'retained context' }] }) + expect(node(snapshot(value), 'user')?.data).toMatchObject({ content: [], source: { kind: 'user' } }) + }) + it.each(['replay', 'live', 'prepend'] as const)('classifies waking Inbox messages through %s', (mode) => { const notice = { ...textMessage('notice', 'scheduled work'), source: { kind: 'schedule' } } const human = textMessage('human', 'user task') diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index bd59805648..4b3cbbd9f5 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -33,14 +33,14 @@ en: 6a7ae99570c9c007 zh: 26e86063e97d82c7 /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/what-the-model-sees: - en: 5c81348e39a58220 - zh: d61ecf2b51b2d583 + en: 6b815b6e27a8750b + zh: 87a08fc5b075e626 /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/token-effect: - en: 777a5c51789aba0c - zh: 9a710c83d1765fba + en: 517c56dbd522c929 + zh: 55fa3a02d026e36a /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/kv-cache-effect: - en: 4889ad2beba4cc52 - zh: 778e8d7803481cd3 + en: f8621bbc481c26ae + zh: 5be844989dc80803 /deepseek-ai-dsh-client-ui-conversation/known-limitations-and-deferred-work: en: d76daa90b6d6f600 zh: 37a9f7d402b2552b diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 1d5e96c0c3..1d5e18bf42 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -193,15 +193,15 @@ The selector must be a pure function of the owner currency. Its non-null return #### What the model sees -The play action submits `content: []` through the existing Session prompt API as a retry control. Loop acceptance records `agent/retry` without adding a `user/message` or a chat bubble. Pending injected context follows ordinary admission; the action adds no localized or hidden continuation text. +The play action submits `content: []` through the existing Session prompt API. Ordinary Loop admission records an empty `user/message` with `source.kind: 'retry'`. Chat hides the empty retry bubble. The adapter applies its ordinary message serialization; the action adds no localized or hidden continuation text. Pending injected context follows ordinary admission. #### Token effect -The control adds no model tokens. The request still incurs the ordinary cost of retained history, current prompt sections, tool declarations, and any newly admitted context. +The action adds no continuation text. Empty-message serialization and framing costs depend on the adapter. The request still incurs the ordinary cost of retained history, current prompt sections, tool declarations, and any newly admitted context. #### KV Cache effect -The control leaves model history unchanged. Prefix reuse depends on the current model, prompt, tools, injected context, and provider cache policy. +Serialization of the empty retry message depends on the adapter. Prefix reuse depends on the current model, prompt, tools, injected context, and provider cache policy. ## Known Limitations and Deferred Work diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index deba0b80e4..c14e138895 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -193,15 +193,15 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match #### 模型看到什么 -播放按钮通过现有 Session prompt API 将 `content: []` 提交为重试控制输入。Loop 接受后记录 `agent/retry`,不增加 `user/message` 或聊天气泡。待注入上下文遵循普通准入流程;该操作不添加本地化或隐藏的继续文本。 +播放按钮通过现有 Session prompt API 提交 `content: []`。普通 Loop 准入记录带 `source.kind: 'retry'` 的空 `user/message`。Chat 隐藏空重试气泡。适配器按普通消息序列化处理;该操作不添加本地化或隐藏的继续文本。待注入上下文遵循普通准入流程。 #### Token 影响 -控制输入不增加模型 token。请求仍承担保留历史、当前提示词、工具声明以及新准入上下文的正常输入成本。 +该操作不增加继续文本。空消息的序列化及封装开销取决于适配器。请求仍承担保留历史、当前提示词、工具声明以及新准入上下文的正常输入成本。 #### KV Cache 影响 -控制输入不改变模型历史。前缀复用取决于当前模型、提示词、工具、注入上下文及提供方缓存策略。 +空重试消息的序列化取决于适配器。前缀复用取决于当前模型、提示词、工具、注入上下文及提供方缓存策略。 ## 已知限制与暂缓事项 diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 2d3b453c44..33c53afffa 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -45,8 +45,8 @@ en: 3b48a480036bd5df zh: 28cea2036b0dccee /deepseek-ai-dsh-agent-loop/understand-the-implementation/turn-and-step-flow: - en: c9f75ebd07873e6a - zh: 863bc96addeb8eb9 + en: ad92c751515520ff + zh: ca6b24db5ab4e471 /deepseek-ai-dsh-agent-loop/understand-the-implementation/failure-and-cancellation: en: eea56903c4ff82f5 zh: c0d5abb5767780ed diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 021401061a..28b862ff9d 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -117,8 +117,6 @@ The loop is the production acquisition point for session write handles. When `ct ### Turn and step flow -An empty input with `source.kind: 'retry'` is a control: on first-attempt admission, the Loop records `agent/retry` with its inbox identity, turn, step, and source instead of a model-visible `user/message`. A retry control containing content fails with `INVALID_REQUEST`. Queue ownership, pre-step decisions, and turn settlement remain the same as for ordinary input; ordinary empty user messages are still committed. - The driver owns one agent for its lifetime and runs inside `ctx.agents.withInitiator(agent, ...)`. `AgentLoop` registers the standard `inbox` projection for its service lifetime, so cold reads work before any Agent exists and after all Agents unload. It also owns `modelSelection` and constructs an `AgentModel` for each Agent, with the public [Model API](../agent/README.md#model-selection). Its package-internal `ReactLoopInbox` uses that shared projection for structural commands and loop-only claims. At a turn boundary it opens the durable turn, then atomically claims pending next-step input plus one queued prompt; between steps it claims only next-step input. The driver assembles the prompt and tools and runs `agent/pre-step`; the fallback projects runtime context after listeners run. A rejected decision or empty first batch opens no step. On the first attempt after acceptance, `step/start` precedes the `agent/request` waterfall and `prepareCall()`; neither async phase sees the pending system prompt or accepted users committed to history, and cancellation during either commits neither. For every attempt, the loop then synchronously reconciles the rendered prompt against the surviving `system/message` nodes using the prepared call capability, refreshes registered runtime facts without reassembling the bound prompt or tools, appends the accepted `user/message` batch only on the first attempt with its owned context candidate replaced in place, and restores removed runtime context, logs the header and context as needed, and derives and freezes the request before streaming through that bound prepared call. Retries reuse the same rendered assembly without repeating assembly, `agent/pre-step`, or user admission. Reconciliation sees pre-step and retry compaction; a broken series consolidates the prompt at the head rather than appending an update after already-committed users. The request is `header.config`, `deriveMessages()`, and `header.tools`; it carries no `system` field. Each model attempt emits one process-local `start`, emits every `chunk` only after the matching durable assistant-frame settlement, and emits exactly one terminal `end`; final assembly or message-append failure settles it as `aborted`, while `committed` follows the durable `assistant/message`. Each successful model call appends one message anchor, and a cancelled stream appends an `interrupted: true` anchor with the delivered prefix so the next request contains what the user saw. Within a step, exclusive calls form barriers and parallel-safe calls use the bounded rolling pool; policy, durable results... (line truncated to 2000 chars) Prompt admission uses the actual `prepareCall()` result, not the preceding `request/context`. With no system node, even an empty prompt is appended (reserving node 0 without a wire message). On an incapable route, or at a new request series whose effective text differs, a non-empty rendering is consolidated at the first system node: each non-empty later system node receives a logged per-node empty replacement, then the head is rewritten if needed. Dormant empty tails need no replacement and do not determine the effective text. A series start whose latest surviving node already holds the rendering produces no system event, so the request keeps the prefix it had. On a continuing `in-history` series, an unchanged effective prompt produces no event and a non-empty change appends. An empty rendering clears every non-empty later system node through a logged per-node empty replacement, then empties the head if needed, regardless of route or series state. No older instructions remain model-visible. An empty head with no active later system node means no prompt; repeated clears and resume leave it empty. A restored non-empty prompt follows the same route/series rule: a continuing capable route may append it, while an incapable route or a new series whose effective text differs refills the head. A step starts a series when the pre-step decision declares `startsRequestSeries` or `session.surface.contentGeneration` changed since attachment or the last request (a replacement or image-offload decision). Visible tool-schema changes force prompt consolidation only when the prepared route lacks `toolUpdate`. Routes supporting both tool and system-prompt updates can append a changed prompt and activate added tools without rewriting earlier messages. If another admission condition replaces a system node, the request starts a new series with current tool declarations. Resume and a provider or model swap alone continue the series; the prepared route still governs admission. Per-node empty replacements preserve intervening history without a surface delete operation. diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index 1937358731..f872d4289e 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -117,8 +117,6 @@ loop 在发布工厂前等待 inbox 与轮次边界注册完成,并将句柄 ### 轮次与步骤流程 -带 `source.kind: 'retry'` 的空输入属于控制输入:首次尝试准入时,Loop 记录带 inbox 身份、轮次、步骤和来源的 `agent/retry`,而不写入模型可见的 `user/message`。包含内容的重试控制输入以 `INVALID_REQUEST` 失败。队列所有权、pre-step 决策和轮次结算与普通输入一致;普通空用户消息仍会提交。 - 驱动器在其整个生命周期内拥有一个 agent,并在 `ctx.agents.withInitiator(agent, ...)` 内运行。`AgentLoop` 在服务生命周期内注册标准 `inbox` 投影,因此冷读取在没有 Agent 和所有 Agent 卸载后都可用。它还拥有 `modelSelection`,并为每个 Agent 构建 `AgentModel`,实现公开的 [Model API](../agent/README.zh.md#model-selection)。其包内部 `ReactLoopInbox` 使用该共享投影执行结构化命令与仅供 loop 使用的领取操作。在轮次边界,它先打开持久轮次,再原子领取待处理的 next-step 输入与一条排队提示词;在步骤之间则只领取 next-step 输入。驱动器组装提示词与工具,并运行 `agent/pre-step`;fallback 在监听器执行后投影运行时上下文。被拒绝的决定或空的首批输入不打开步骤。接纳后的首次尝试先记录 `step/start`,再运行 `agent/request` waterfall 与 `prepareCall()`;这两个异步阶段都看不到待提交的系统提示词与已接纳用户消息进入历史,在任一阶段取消都不会提交这两者。每次尝试时,循环随后依据已准备调用的能力,同步将渲染后的提示词与存活的 `system/message` 节点协调一致、刷新已注册运行时事实而不重新组装绑定的提示词或工具、仅在首次尝试追加已接纳的 `user/message` 批次并原位替换循环自有上下文候选、恢复被移除的运行时上下文、按需记录 header 与 context,再派生并冻结请求,通过该绑定的已准备调用发起流式请求。重试复用同一份已渲染组装结果,不重复组装、`agent/pre-step` 或用户消息准入。协调过程可见 pre-step 与重试中的压缩;序列中断时将提示词归并到头部,而非在已提交用户消息之后追加更新。请求由 `header.config`、`deriveMessages()` 与 `header.tools` 构成,不携带 `system` 字段。每次模型尝试会发出一个进程本地 `start`,仅在匹配的持久 assistant-frame 结算之后发出各个 `chunk`,并恰好发出一个终态 `end`;最终组装或消息追加失败时以 `aborted` 结算,`committed` 则出现在持久 `assistant/message` 之后。每次成功的模型调用都恰好追加一个 message 锚点,被取消的流则追加带 `interrupted: true` 的锚点并携带已交付前缀,使下一次请求包含用户看到的内容。在步骤内,独占调用形成屏障,并行安全调用使用有界滚动池;策略、持久结果与结果上下文保持模型顺序。 提示词准入依据实际的 `prepareCall()` 结果,而非先前的 `request/context`。没有系统节点时,即使提示词为空也追加(预留第 0 号节点,但不产生协议消息)。在不具备能力的路由上,或有效文本不同的新请求序列开始时,非空渲染文本归并到首个系统节点:每个非空的后续系统节点分别收到有日志记录的空内容替换,随后按需重写头节点。未生效的空尾节点无需替换,也不决定有效文本。新序列开始时若最新存活节点已经持有该渲染文本,则不产生系统事件,请求保留原有前缀。延续中的 `in-history` 序列在有效提示词不变时不产生事件,非空变更则追加。无论路由或序列状态如何,空渲染文本都会通过有日志记录的逐节点空内容替换清除每个非空的后续系统节点,再按需清空头节点。模型不会继续看到旧指令。空头节点且没有生效的后续系统节点表示没有提示词;重复清除与恢复会话都保持为空。重新提供的非空提示词遵循同一路由/序列规则:延续中的具备能力路由可以追加它,不具备能力的路由或有效文本不同的新序列则重新填充头节点。以下情况开启序列:pre-step 决定声明 `startsRequestSeries`,或 `session.surface.contentGeneration` 自附接或上次请求以来发生变化(替换或图片省略决定)。仅当已准备路由未声明 `toolUpdate` 时,可见工具 schema 变化才强制归并提示词。同时支持工具和系统提示词更新的路由可以追加变更后的提示词并激活新增工具,无需改写此前消息。如果其他准入条件替换系统节点,请求会以当前工具声明开启新序列。恢复与单纯的提供方或模型切换都延续序列;准入仍由已准备的路由决定。逐节点的空内容替换保留其间历史,无需 surface 删除操作。 diff --git a/packages/core/agent-loop/src/agent.ts b/packages/core/agent-loop/src/agent.ts index 8150c334cc..03c4c0c2bf 100644 --- a/packages/core/agent-loop/src/agent.ts +++ b/packages/core/agent-loop/src/agent.ts @@ -455,10 +455,7 @@ export class ReactLoopAgent implements Agent { let contextAdmitted = false if (firstAttempt) { for (const message of decision.messages) { - if (message.source.kind === 'retry') { - if (message.content.length > 0) throw new LlmError('Retry control input must be empty', 'INVALID_REQUEST') - this.session.append('agent/retry', { turn, step, inputId: message.id, source: message.source }) - } else if (message.source.kind === 'runtime-context') { + if (message.source.kind === 'runtime-context') { if (context !== undefined && !contextAdmitted) { this.session.append('user/message', context, { surfaceOp: 'append' }) contextAdmitted = true diff --git a/packages/core/agent-loop/tests/request-error.spec.ts b/packages/core/agent-loop/tests/request-error.spec.ts index ed95db70b3..5287cb4deb 100644 --- a/packages/core/agent-loop/tests/request-error.spec.ts +++ b/packages/core/agent-loop/tests/request-error.spec.ts @@ -1,5 +1,5 @@ import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' -import { describe, expect, it, onTestFinished } from 'vitest' +import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import AgentRegistry from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' @@ -32,55 +32,6 @@ function fail(message: string, code: string): () => never { } describe('agent/request-error', () => { - it('records repeated retry controls with balanced turns and no additional model messages', async () => { - const adapter = new MockAdapter([fail('busy', 'SERVER'), fail('still busy', 'SERVER'), textResponse('done')]) - const ctx = await harness(adapter) - onTestFinished(() => ctx.fiber.dispose()) - const agent = await ctx.agentLoop.create(SessionId('retry-controls'), { provider: 'mock', model: 'mock' }) - agent.followup(createUserMessage({ content: [{ type: 'text', text: 'original task' }], source: { kind: 'user' } })) - await agent.whenIdle() - const controls = Array.from({ length: 2 }, () => createUserMessage({ content: [], source: { kind: 'retry' } })) - for (const control of controls) { - agent.followup(control) - await agent.whenIdle() - } - expect(adapter.requests).toHaveLength(3) - for (const request of adapter.requests) { - expect(request.messages.filter(message => message.role === 'user').map(message => message.content)) - .toEqual([[{ type: 'text', text: 'original task' }]]) - } - const events = agent.session.snapshotEvents() - expect(events.filter(event => event.type === 'user/message')).toHaveLength(1) - expect(events.flatMap(event => event.type === 'agent/retry' ? [event.data] : [])).toEqual( - controls.map((control, index) => ({ turn: index + 2, step: 1, inputId: control.id, source: { kind: 'retry' } })), - ) - expect(events.flatMap(event => event.type === 'turn/start' || event.type === 'turn/end' - ? [`${event.type}:${event.data.turn}`] : [])) - .toEqual(['turn/start:1', 'turn/end:1', 'turn/start:2', 'turn/end:2', 'turn/start:3', 'turn/end:3']) - expect(events.flatMap(event => event.type === 'turn/end' ? [event.data.reason.kind] : [])) - .toEqual(['error', 'error', 'completed']) - }) - - it('preserves ordinary empty user input and rejects retry controls containing content', async () => { - const adapter = new MockAdapter([textResponse('done')]) - const ctx = await harness(adapter) - onTestFinished(() => ctx.fiber.dispose()) - const agent = await ctx.agentLoop.create(SessionId('retry-validation'), { provider: 'mock', model: 'mock' }) - const empty = createUserMessage({ content: [], source: { kind: 'user' } }) - agent.followup(empty) - await agent.whenIdle() - expect(adapter.requests[0]?.messages.filter(message => message.role === 'user')).toEqual([empty]) - agent.followup(createUserMessage({ content: [{ type: 'text', text: 'must not disappear' }], source: { kind: 'retry' } })) - await agent.whenIdle() - expect(adapter.requests).toHaveLength(1) - const events = agent.session.snapshotEvents() - expect(events.filter(event => event.type === 'agent/retry')).toHaveLength(0) - expect(events.filter(event => event.type === 'user/message')).toMatchObject([{ data: empty }]) - expect(events.filter(event => event.type === 'turn/end').at(-1)).toMatchObject({ - data: { turn: 2, reason: { kind: 'error', error: { code: 'INVALID_REQUEST' } } }, - }) - }) - it('does not offer middleware failures to request recovery', async () => { const adapter = new MockAdapter([textResponse('unused')]) const ctx = await harness(adapter) diff --git a/packages/core/agent/README.i18n.yaml b/packages/core/agent/README.i18n.yaml index 44fd486abc..53a219df1a 100644 --- a/packages/core/agent/README.i18n.yaml +++ b/packages/core/agent/README.i18n.yaml @@ -24,8 +24,8 @@ en: 85b0ff72a187110f zh: 3f61c1568d27b8bc /deepseek-ai-dsh-agent/use-this-package/drive-an-agent-s-conversation: - en: 09f6d66dddba9ae7 - zh: f3cd42db2375b705 + en: 71e258897e2e7d73 + zh: abf0991012faa17e /deepseek-ai-dsh-agent/use-this-package/scope-registrations-to-one-agent: en: 2594a1e02adea801 zh: cae8860369728efb diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index 441d453f7f..a7433347f8 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -55,7 +55,7 @@ For scoped switch notices, call `installModelSelection(agentCtx)` from `dsh-agen ### Drive an agent's conversation -The handle's methods route identified user-role messages into the agent's inbox. `followup()` queues an ordinary next-turn prompt and wakes the driver; `steer()` submits next-step input and wakes it; `inject()` adds model-facing context without waking the driver, so it lands in the next admitted step. An empty input with `source.kind: 'retry'` requests another model call through the same inbox; the shipped Loop records its acceptance as `agent/retry` without adding model-visible content. Ordinary empty user inputs retain ordinary message admission. `cancel(cause)` aborts the active activity and, unless `keepInbox` is set, clears pending work; `whenIdle()` resolves after the whole agent reaches quiescence. +The handle's methods route identified user-role messages into the agent's inbox. `followup()` queues an ordinary next-turn prompt and wakes the driver; `steer()` submits next-step input and wakes it; `inject()` adds model-facing context without waking the driver, so it lands in the next admitted step. `cancel(cause)` aborts the active activity and, unless `keepInbox` is set, clears pending work; `whenIdle()` resolves after the whole agent reaches quiescence. ```text handle.agent.followup({ diff --git a/packages/core/agent/README.zh.md b/packages/core/agent/README.zh.md index 66367a5f6e..a92b8338e0 100644 --- a/packages/core/agent/README.zh.md +++ b/packages/core/agent/README.zh.md @@ -55,7 +55,7 @@ await handle.dispose() // stops the loop, unregisters, removes the session, un ### 驱动 agent 的对话 -句柄的方法把带标识的 user 角色消息路由进 agent 的收件箱。`followup()` 排队一条普通的下一个轮次提示词并唤醒驱动器;`steer()` 提交下一步输入并唤醒它;`inject()` 添加面向模型的上下文但不唤醒驱动器,因此它落在下一个被接纳的步骤中。带 `source.kind: 'retry'` 的空输入通过同一 inbox 请求再次调用模型;内置 Loop 将接受记录为 `agent/retry`,不增加模型可见内容。普通空用户输入仍遵循普通消息准入。`cancel(cause)` 中止当前活动,并在未设置 `keepInbox` 时清除待处理工作;`whenIdle()` 会在整个 agent 达到完全停稳后完成。 +句柄的方法把带标识的 user 角色消息路由进 agent 的收件箱。`followup()` 排队一条普通的下一个轮次提示词并唤醒驱动器;`steer()` 提交下一步输入并唤醒它;`inject()` 添加面向模型的上下文但不唤醒驱动器,因此它落在下一个被接纳的步骤中。`cancel(cause)` 中止当前活动,并在未设置 `keepInbox` 时清除待处理工作;`whenIdle()` 会在整个 agent 达到完全停稳后完成。 ```text handle.agent.followup({ diff --git a/packages/core/agent/src/runtime-types.ts b/packages/core/agent/src/runtime-types.ts index 5ae5c35aff..2b1698e39c 100644 --- a/packages/core/agent/src/runtime-types.ts +++ b/packages/core/agent/src/runtime-types.ts @@ -232,8 +232,6 @@ declare module './types.ts' { /** * Route identified input to an inbox boundary and optionally wake the driver. - * A retry-source input must be empty; admission records `agent/retry` - * without adding a model-visible message. * Waking input submitted after active cancellation is queued for the next * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens @@ -246,8 +244,8 @@ declare module './types.ts' { send(message: UserMessage, target: InboxTarget, wakeup: boolean): void /** - * Queue a follow-up turn and wake the driver. The item is its sole ordinary - * prompt, or an empty retry-source control with no model-visible message. + * Queue an ordinary follow-up turn and wake the driver. The item becomes the + * sole ordinary message of its own turn. * @param message - identified prompt content and the source that supplied it. */ followup(message: UserMessage): void diff --git a/packages/core/agent/src/types.ts b/packages/core/agent/src/types.ts index defbd94bec..06d2c88ab1 100644 --- a/packages/core/agent/src/types.ts +++ b/packages/core/agent/src/types.ts @@ -4,8 +4,8 @@ * @module @deepseek-ai/dsh-agent/types */ -import type { MessageId, ReasoningEffortId } from '@deepseek-ai/dsh-llm/brand' -import type { MessageSource, UserMessage } from '@deepseek-ai/dsh-llm/types' +import type { ReasoningEffortId } from '@deepseek-ai/dsh-llm/brand' +import type { UserMessage } from '@deepseek-ai/dsh-llm/types' // Type-only: the Workspace registry's archive-admission family map this registry merges `turn` into. import type {} from '@deepseek-ai/dsh-workspace/types' import type { OptionalSessionSeq, SessionId, SessionSeq } from '@deepseek-ai/dsh-session/types' @@ -18,13 +18,6 @@ export interface Agent { readonly id: SessionId } -declare module '@deepseek-ai/dsh-llm' { - interface MessageSourceMap { - /** Empty control input requesting a model call without adding user text. */ - retry: { kind: 'retry' } - } -} - declare module '@deepseek-ai/dsh-workspace/types' { interface SessionActivityKindMap { /** The session's own Agent is inside a turn, including one waiting for an approval or an answer. */ @@ -109,13 +102,6 @@ declare module '@deepseek-ai/dsh-session-projection/types' { declare module '@deepseek-ai/dsh-session/types' { interface SessionEventMap { - /** An admitted retry control, correlated with its durable inbox input; never enters model history. */ - 'agent/retry': { - turn: number - step: number - inputId: MessageId - source: Extract - } /** * One normalized mutation of an agent's durable pending-message lists. * The session-projection registry applies the committed event before diff --git a/packages/core/session/src/known-event-types.ts b/packages/core/session/src/known-event-types.ts index 71a61005df..a0f5c17795 100644 --- a/packages/core/session/src/known-event-types.ts +++ b/packages/core/session/src/known-event-types.ts @@ -22,7 +22,6 @@ export const KNOWN_SESSION_EVENT_TYPES: ReadonlySet = new Set([ 'agent-preset/selected', 'agent/inbox/spliced', - 'agent/retry', 'approval/asked', 'approval/decided', 'approval/policy', diff --git a/packages/sdk/client/src/client.ts b/packages/sdk/client/src/client.ts index 56f7401b7a..4bc8140c02 100644 --- a/packages/sdk/client/src/client.ts +++ b/packages/sdk/client/src/client.ts @@ -344,7 +344,7 @@ export class HarnessClient { /** * Queue one prompt and return its durable inbox identity. * @param sessionId - target session; an unknown id creates it. - * @param contentBlocks - user content, or an empty array to retry without adding a model-visible message. + * @param contentBlocks - the user message, sent verbatim. * @returns the queued message id. */ async prompt(sessionId: string, contentBlocks: SdkPromptContentBlock[]): Promise { diff --git a/packages/sdk/protocol/README.i18n.yaml b/packages/sdk/protocol/README.i18n.yaml index c29b0abca2..2aed1dc438 100644 --- a/packages/sdk/protocol/README.i18n.yaml +++ b/packages/sdk/protocol/README.i18n.yaml @@ -27,8 +27,8 @@ en: 9dcd999ae8c66b71 zh: 174c5fe0cbb0a9e4 /deepseek-ai-dsh-sdk-protocol/use-this-package/payload-semantics: - en: f5f0ff9090bdd076 - zh: 83e43639a892879a + en: 3cf5d4ffbaf37f69 + zh: 23c4450e31c91d67 /deepseek-ai-dsh-sdk-protocol/understand-the-implementation: en: a11b136d8296deda zh: 85081da5be53c9e1 diff --git a/packages/sdk/protocol/README.md b/packages/sdk/protocol/README.md index 2240c78cf7..93a34454af 100644 --- a/packages/sdk/protocol/README.md +++ b/packages/sdk/protocol/README.md @@ -69,7 +69,7 @@ An answering host sets `initialize.capabilities.userQuestions: true`. The runtim ### Payload semantics -`SessionPromptResult.messageId` identifies the queued prompt or retry control; it does not identify a later assistant message, turn ending, or prompt result. An empty `contentBlocks` array queues an empty retry-source control; admission records `agent/retry` rather than `user/message`. `SdkPromptContentBlock` accepts ordinary durable content plus `SdkEncodedImageBlock { type: "image", data, mimeType }`; the server converts encoded images to durable references before enqueue. `InitializeParams.reasoningEffort` is an optional non-empty adapter-owned identifier for the selected provider/model route; omission preserves that model's default. `InitializeParams.maxTokens` is an optional positive safe integer that caps each conversation-model output for SDK-created agents and their in-process descendants; omission lets the selected adapter's exact-model default apply. The server resolves the exact route during initialization and rejects `session/prompt` until that handshake succeeds, so a missing adapter, unavailable model, or unsupported effort cannot fall back to constructor defaults. `SubagentFinishedNotification.lastAssistantMessage` carries the child's last non-empty assistant message, or its accumulated assistant text when no such message exists; the field is absent when the child produced neither. `serverInfo.name` stays the wire-stable `deepseek-harness-sdk-runtime`. Notification payloads depend on `SessionEvent` (`dsh-session`), `ContentBlock` (`dsh-llm`), and `SubagentStopReason` (`dsh-subagent`), so the session vocabulary is part of the wire contract. +`SessionPromptResult.messageId` identifies the queued user message; it does not identify a later assistant message, turn ending, or prompt result. `SdkPromptContentBlock` accepts ordinary durable content plus `SdkEncodedImageBlock { type: "image", data, mimeType }`; the server converts encoded images to durable references before enqueue. `InitializeParams.reasoningEffort` is an optional non-empty adapter-owned identifier for the selected provider/model route; omission preserves that model's default. `InitializeParams.maxTokens` is an optional positive safe integer that caps each conversation-model output for SDK-created agents and their in-process descendants; omission lets the selected adapter's exact-model default apply. The server resolves the exact route during initialization and rejects `session/prompt` until that handshake succeeds, so a missing adapter, unavailable model, or unsupported effort cannot fall back to constructor defaults. `SubagentFinishedNotification.lastAssistantMessage` carries the child's last non-empty assistant message, or its accumulated assistant text when no such message exists; the field is absent when the child produced neither. `serverInfo.name` stays the wire-stable `deepseek-harness-sdk-runtime`. Notification payloads depend on `SessionEvent` (`dsh-session`), `ContentBlock` (`dsh-llm`), and `SubagentStopReason` (`dsh-subagent`), so the session vocabulary is part of the wire contract. ----- diff --git a/packages/sdk/protocol/README.zh.md b/packages/sdk/protocol/README.zh.md index f5339e55c9..f81f58c223 100644 --- a/packages/sdk/protocol/README.zh.md +++ b/packages/sdk/protocol/README.zh.md @@ -69,7 +69,7 @@ kind: "package-library" ### 载荷语义 -`SessionPromptResult.messageId` 标识已排队的提示词或重试控制输入;它不标识后续的助手消息、轮次结束或提示词结果。空 `contentBlocks` 数组将空的 retry 来源控制输入排入队列;准入记录 `agent/retry`,而非 `user/message`。`SdkPromptContentBlock` 接受普通持久内容以及 `SdkEncodedImageBlock { type: "image", data, mimeType }`;服务器在入队前把编码图像转换为持久引用。`InitializeParams.reasoningEffort` 是所选提供方/模型路由可选的非空适配器自有标识符;省略时保留该模型的默认值。`InitializeParams.maxTokens` 是可选的正安全整数,用于限制 SDK 创建的 agent 及其进程内后代的每次对话模型输出;省略时应用所选适配器的确切模型默认值。服务器会在初始化期间解析确切路由,并在握手成功前拒绝 `session/prompt`,因此缺少适配器、模型不可用或推理强度不受支持时,不会回退到构造期默认值。`SubagentFinishedNotification.lastAssistantMessage` 携带子 agent 最后一条非空 assistant 消息;若不存在这类消息,则携带其累积的 assistant 文本;子 agent 两种输出均未产生时,该字段缺省。`serverInfo.name` 的协议值固定为 `deepseek-harness-sdk-runtime`。通知载荷依赖 `SessionEvent`(`dsh-session`)、`ContentBlock`(`dsh-llm`)与 `SubagentStopReason`(`dsh-subagent`),因此会话词汇是协议格式约定的一部分。 +`SessionPromptResult.messageId` 标识已排队的用户消息;它不标识后续的助手消息、轮次结束或提示词结果。`SdkPromptContentBlock` 接受普通持久内容以及 `SdkEncodedImageBlock { type: "image", data, mimeType }`;服务器在入队前把编码图像转换为持久引用。`InitializeParams.reasoningEffort` 是所选提供方/模型路由可选的非空适配器自有标识符;省略时保留该模型的默认值。`InitializeParams.maxTokens` 是可选的正安全整数,用于限制 SDK 创建的 agent 及其进程内后代的每次对话模型输出;省略时应用所选适配器的确切模型默认值。服务器会在初始化期间解析确切路由,并在握手成功前拒绝 `session/prompt`,因此缺少适配器、模型不可用或推理强度不受支持时,不会回退到构造期默认值。`SubagentFinishedNotification.lastAssistantMessage` 携带子 agent 最后一条非空 assistant 消息;若不存在这类消息,则携带其累积的 assistant 文本;子 agent 两种输出均未产生时,该字段缺省。`serverInfo.name` 的协议值固定为 `deepseek-harness-sdk-runtime`。通知载荷依赖 `SessionEvent`(`dsh-session`)、`ContentBlock`(`dsh-llm`)与 `SubagentStopReason`(`dsh-subagent`),因此会话词汇是协议格式约定的一部分。 ----- diff --git a/packages/sdk/protocol/src/types.ts b/packages/sdk/protocol/src/types.ts index dd3c86a930..cfa746cac2 100644 --- a/packages/sdk/protocol/src/types.ts +++ b/packages/sdk/protocol/src/types.ts @@ -67,7 +67,7 @@ export interface InitializeResult { export interface SessionPromptParams { /** The SDK-side session id; an unknown id lazily creates the agent+session pair. */ sessionId: string - /** Prompt content; an empty array requests a retry control without a model-visible user message. */ + /** The prompt content blocks, sent verbatim as the user message. */ contentBlocks: SdkPromptContentBlock[] } @@ -85,7 +85,7 @@ export type SdkPromptContentBlock = ContentBlock | SdkEncodedImageBlock /** Durable enqueue receipt for one prompt. */ export interface SessionPromptResult { - /** Identity of the queued prompt or retry control. */ + /** Identity of the queued user message. */ messageId: string } diff --git a/packages/sdk/server/README.i18n.yaml b/packages/sdk/server/README.i18n.yaml index 7ad56c42a9..b56838412f 100644 --- a/packages/sdk/server/README.i18n.yaml +++ b/packages/sdk/server/README.i18n.yaml @@ -27,8 +27,8 @@ en: 784b68b5a498f2d5 zh: 2c3c5a1490845179 /deepseek-ai-dsh-sdk-jsonrpc-server/use-this-package/what-sdk-clients-can-do: - en: 0936bb4ac5f59851 - zh: fb91df93242fc80e + en: 3e657fa4c9586454 + zh: 26d572f91e124cc5 /deepseek-ai-dsh-sdk-jsonrpc-server/use-this-package/native-user-questions: en: 5100c311c8476f93 zh: 60d4bc3165731246 @@ -60,8 +60,8 @@ en: 04d1b73cd8e06dfe zh: cd67fbbfdf39f776 /deepseek-ai-dsh-sdk-jsonrpc-server/model-experience/sdk-user-message/what-the-model-sees: - en: 57ed296ea4f0ec42 - zh: a0bf3746ce9bdf01 + en: 2b5a80ec4b4df11e + zh: e459fe77cc14d9b8 /deepseek-ai-dsh-sdk-jsonrpc-server/model-experience/sdk-user-message/token-effect: en: 372ed851986b76d2 zh: 075b94a565275368 diff --git a/packages/sdk/server/README.md b/packages/sdk/server/README.md index f3dcb00c29..e9007e593c 100644 --- a/packages/sdk/server/README.md +++ b/packages/sdk/server/README.md @@ -53,7 +53,7 @@ Stdout carries only JSON-RPC frames, so clients can parse every byte; diagnostic ### What SDK clients can do -`initialize` is the runtime-readiness boundary: when the server is mounted by a Loader composition, it waits for the current plugin tree to settle before replying, so async sibling capabilities such as initial MCP tool discovery are visible to the first prompt. The handshake returns the wire-stable identity `deepseek-harness-sdk-runtime`. The server validates the provider/model route and optional non-empty `reasoningEffort` through the selected adapter before it stores them; omission stores no effort, so the model retains its own default. An optional positive `maxTokens` becomes the request output cap of each SDK-created agent and its in-process descendants, while omission applies the selected adapter or provider route default. JSON-RPC requests may dispatch concurrently, so `session/prompt` rejects until one `initialize` has completed successfully; clients must await the handshake before sending prompts. An accepted prompt queues one identified input and immediately returns `{ messageId }`; the server then streams every durable fact as `session.event` and every whole-agent lifecycle transition as `session.status`. It does not assign an assistant message or `turn/end` to a prompt, and independent requests may enqueue more work on the same session. Persistence roots and persona come from the surrounding composition. +`initialize` is the runtime-readiness boundary: when the server is mounted by a Loader composition, it waits for the current plugin tree to settle before replying, so async sibling capabilities such as initial MCP tool discovery are visible to the first prompt. The handshake returns the wire-stable identity `deepseek-harness-sdk-runtime`. The server validates the provider/model route and optional non-empty `reasoningEffort` through the selected adapter before it stores them; omission stores no effort, so the model retains its own default. An optional positive `maxTokens` becomes the request output cap of each SDK-created agent and its in-process descendants, while omission applies the selected adapter or provider route default. JSON-RPC requests may dispatch concurrently, so `session/prompt` rejects until one `initialize` has completed successfully; clients must await the handshake before sending prompts. An accepted prompt queues one identified user message and immediately returns `{ messageId }`; the server then streams every durable fact as `session.event` and every whole-agent lifecycle transition as `session.status`. It does not assign an assistant message or `turn/end` to a prompt, and independent requests may enqueue more work on the same session. Persistence roots and persona come from the surrounding composition. `session/wait` observes an existing SDK-owned session through managed descendant completion and any resulting root turns. It rejects unknown or retired roots, never creates a session, and responds after the root stays idle across the descendant check. Idle children with parked inbox messages remain resident but do not delay this response; later waking input can resume them. Session notifications emitted before completion precede the response on the stdio transport. A live Agent failure remains associated with that exact session until a later committed `turn/end` accounts for the outcome; otherwise the wait rejects, including when the failure preceded the wait request. Errors already represented by a terminal remain in Session events, and later successful activity clears an earlier live failure. @@ -120,7 +120,7 @@ Read these pages when the plugin contract is not enough. They move from the wire #### What the model sees -For each non-empty accepted `session/prompt`, text and durable content references enter one user message verbatim. An empty content array queues a retry-source control; Loop admission records `agent/retry` without adding model-visible content. Inline `SdkEncodedImageBlock` values are validated and committed through the composition's attachment store first, so the session log retains content-addressed image references rather than base64 bytes. This package adds no system-prompt prose or tool schema; those come from the other plugins in the composition. +For each accepted `session/prompt`, text and durable content references enter one user message verbatim. Inline `SdkEncodedImageBlock` values are validated and committed through the composition's attachment store first, so the session log retains content-addressed image references rather than base64 bytes. This package adds no system-prompt prose or tool schema; those come from the other plugins in the composition. #### Token effect diff --git a/packages/sdk/server/README.zh.md b/packages/sdk/server/README.zh.md index 4d56722a7f..56ae2f8bdf 100644 --- a/packages/sdk/server/README.zh.md +++ b/packages/sdk/server/README.zh.md @@ -53,7 +53,7 @@ Stdout 只承载 JSON-RPC 帧,客户端可以逐字节解析;诊断信息应 ### SDK 客户端可以做什么 -`initialize` 是运行时就绪边界:服务器由 Loader 组合挂载时,会等待当前插件树完成所有加载任务后再响应,因此首次提示词能够看到 MCP 初始工具发现等异步同级能力。握手返回协议稳定标识 `deepseek-harness-sdk-runtime`。服务器会通过所选适配器校验提供方/模型路由与可选的非空 `reasoningEffort`,再保存这些值;省略时不会保存推理强度,因此模型保留自身默认值。可选的正整数 `maxTokens` 会成为每个 SDK 创建的 agent 及其进程内后代的请求输出上限,省略时则应用所选适配器或提供方路由的默认值。JSON-RPC 请求可能并发分派,因此在一次 `initialize` 成功完成之前,`session/prompt` 会拒绝;客户端必须等待握手完成后再发送提示词。已接受的提示词会把一条带标识的输入排入队列,并立即返回 `{ messageId }`;服务器随后把每个持久事实作为 `session.event`、把整个 agent 生命周期的每次状态转换作为 `session.status` 流式发出。它不会把某条助手消息或 `turn/end` 归属于某个提示词,同一会话上的独立请求可以继续排入更多工作。持久化根目录与 persona 来自外围组合。 +`initialize` 是运行时就绪边界:服务器由 Loader 组合挂载时,会等待当前插件树完成所有加载任务后再响应,因此首次提示词能够看到 MCP 初始工具发现等异步同级能力。握手返回协议稳定标识 `deepseek-harness-sdk-runtime`。服务器会通过所选适配器校验提供方/模型路由与可选的非空 `reasoningEffort`,再保存这些值;省略时不会保存推理强度,因此模型保留自身默认值。可选的正整数 `maxTokens` 会成为每个 SDK 创建的 agent 及其进程内后代的请求输出上限,省略时则应用所选适配器或提供方路由的默认值。JSON-RPC 请求可能并发分派,因此在一次 `initialize` 成功完成之前,`session/prompt` 会拒绝;客户端必须等待握手完成后再发送提示词。已接受的提示词会把一条带标识的用户消息排入队列,并立即返回 `{ messageId }`;服务器随后把每个持久事实作为 `session.event`、把整个 agent 生命周期的每次状态转换作为 `session.status` 流式发出。它不会把某条助手消息或 `turn/end` 归属于某个提示词,同一会话上的独立请求可以继续排入更多工作。持久化根目录与 persona 来自外围组合。 `session/wait` 观察已有 SDK 会话,直到受管理后代及其触发的根 Agent 后续轮次完成。它拒绝未知或已释放的根 Agent,不会创建会话,并在后代检查期间根 Agent 持续空闲后响应。收件箱消息处于停放状态的空闲子级继续驻留,但不延迟此响应;后续唤醒输入可使其继续运行。完成前发出的会话通知在 stdio 传输上先于响应到达。Agent 的运行时错误会保留在对应的确切会话中,直到后续提交的 `turn/end` 记录结果;否则等待请求会失败,即使错误发生在等待请求之前。终止事件已经记录的错误仍通过会话事件表达,后续成功的活动会清除较早的运行时错误。 @@ -120,7 +120,7 @@ Stdout 只承载 JSON-RPC 帧,客户端可以逐字节解析;诊断信息应 #### 模型看到什么 -对于每个非空且已接受的 `session/prompt`,文本和持久内容引用会原样进入一条用户消息。空内容数组将 retry 来源控制输入排入队列;Loop 准入记录 `agent/retry`,不增加模型可见内容。内联 `SdkEncodedImageBlock` 会先通过组合中的附件存储完成校验与提交,因此会话日志保留内容寻址的图片引用而不是 base64 字节。此包不会添加系统提示词文本或工具 schema;这些内容来自组合中的其他插件。 +对于每个已接受的 `session/prompt`,文本和持久内容引用会原样进入一条用户消息。内联 `SdkEncodedImageBlock` 会先通过组合中的附件存储完成校验与提交,因此会话日志保留内容寻址的图片引用而不是 base64 字节。此包不会添加系统提示词文本或工具 schema;这些内容来自组合中的其他插件。 #### Token 影响 diff --git a/packages/sdk/server/src/server.ts b/packages/sdk/server/src/server.ts index 8bb25cb1db..0dceccc59c 100644 --- a/packages/sdk/server/src/server.ts +++ b/packages/sdk/server/src/server.ts @@ -247,7 +247,7 @@ export class HarnessSdkJsonRpcServer { /** * Queue one identified prompt without assigning later activity to it. - * @param params - target session and user content; an empty array queues a retry control. + * @param params - target session and user content. * @returns the durable message identity. */ async prompt(params: SessionPromptParams): Promise { @@ -263,7 +263,7 @@ export class HarnessSdkJsonRpcServer { this.assertLiveAgent(rec, params.sessionId) const message = createUserMessage({ content, - source: { kind: content.length === 0 ? 'retry' : 'user' }, + source: { kind: 'user' }, }) rec.handle.agent.followup(message) return { messageId: message.id } diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py index 24bc9f15f1..3141b7bb24 100644 --- a/scripts/smoke-python-runtime.py +++ b/scripts/smoke-python-runtime.py @@ -113,6 +113,7 @@ RESTART_SECOND_SESSION_ID = "process-two" RESUME_PROMPT = "Continue the first Python SDK session after restarting the runtime." RESUME_TEXT = "RESUMED_PROCESS_OK" RECOVERY_PROMPT = "Run three todo updates; the scheduler will reject the second call." +RECOVERY_CONTINUE_PROMPT = "Continue after the scheduler failure without repeating any tools." RECOVERY_TEXT = "SCHEDULER_RECOVERY_OK" RECOVERY_SESSION_ID = "scheduler-recovery" RECOVERY_CALL_IDS = ("scheduler-complete", "scheduler-fail", "scheduler-unstarted") @@ -347,7 +348,7 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]: block.get("text") for block in latest.get("content", []) if isinstance(block, dict) and block.get("type") == "text" - and block.get("text") == RECOVERY_PROMPT + and block.get("text") in {RECOVERY_PROMPT, RECOVERY_CONTINUE_PROMPT} ), None) if recovery_prompt == RECOVERY_PROMPT: assert_advertised_tool(body, "todo_write") @@ -357,9 +358,7 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]: chunks.extend({**chunk, "index": index} for chunk in call[1:-2]) chunks.extend(call[-2:]) return chunks - if any(block.get("tool_use_id") == RECOVERY_CALL_IDS[-1] - for message in messages for block in message.get("content", []) - if isinstance(block, dict) and block.get("type") == "tool_result"): + if recovery_prompt == RECOVERY_CONTINUE_PROMPT: results = [ block for message in messages for block in message.get("content", []) @@ -1910,7 +1909,7 @@ def smoke_sdk_scheduler_recovery(base_url: str, executable: Path, update_snapsho with connect() as harness: failed = harness.run(RECOVERY_PROMPT, session_id=RECOVERY_SESSION_ID) - continued = harness.run([], session_id=RECOVERY_SESSION_ID) + continued = harness.run(RECOVERY_CONTINUE_PROMPT, session_id=RECOVERY_SESSION_ID) results = [failed, continued] if [result.finish_reason for result in results] != ["error", "completed"]: outcomes = [ @@ -1925,18 +1924,6 @@ def smoke_sdk_scheduler_recovery(base_url: str, executable: Path, update_snapsho raise AssertionError(f"scheduler recovery expected two serialized model requests: {requests}") logs = read_session_logs(sessions) records = logs[RECOVERY_SESSION_ID] - prompts = [record for record in records if record.get("type") == "user/message" - and record["data"]["source"].get("kind") == "user"] - retries = [record for record in records if record.get("type") == "agent/retry"] - if len(prompts) != 1 or len(retries) != 1: - raise AssertionError("scheduler recovery must record one original prompt and one retry control") - retry_id = retries[0]["data"]["inputId"] - queued_retry_ids = [message.get("id") for record in records - if record.get("type") == "agent/inbox/spliced" - for message in record["data"]["inserted"] - if message.get("source", {}).get("kind") == "retry"] - if not isinstance(retry_id, str) or queued_retry_ids != [retry_id]: - raise AssertionError("scheduler recovery lost the retry control's durable inbox identity") calls = [record["data"]["callId"] for record in records if record.get("type") == "tool/call"] if calls != list(RECOVERY_CALL_IDS[:2]): raise AssertionError(f"scheduler recovery dispatched the unstarted call: {calls}") @@ -1953,7 +1940,6 @@ def smoke_sdk_scheduler_recovery(base_url: str, executable: Path, update_snapsho replacements = [ *snapshot_directory_replacements(root, "{{cwd}}"), (RECOVERY_SESSION_ID, "{{parent}}"), - (retry_id, "{{messageId}}"), ] result_value = [{ "session_id": result.session_id, diff --git a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/requests.json b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/requests.json index 41cd02a726..f83fee366e 100644 --- a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/requests.json +++ b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/requests.json @@ -97,6 +97,10 @@ } ], "is_error": true + }, + { + "type": "text", + "text": "Continue after the scheduler failure without repeating any tools." } ] } diff --git a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json index e023466dea..46704a4ab7 100644 --- a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json +++ b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/result.json @@ -57,7 +57,7 @@ "turn/start", "agent/inbox/spliced", "step/start", - "agent/retry", + "user/message", "assistant/message", "step/end", "turn/end" diff --git a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl index acab2bcdb6..c679ff36b7 100644 --- a/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl +++ b/scripts/snapshots/python-sdk-single-exe/scheduler-recovery/session.v4.jsonl @@ -21,11 +21,11 @@ {"type":"tool/result","data":{"turn":1,"step":1,"message":{"id":"interrupted-tool-result-scheduler-unstarted-19","role":"tool","toolCallId":"scheduler-unstarted","isError":true,"source":{"kind":"tool","callId":"scheduler-unstarted"},"content":[{"type":"text","text":"The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed."}]},"error":{"name":"ToolNotStartedError","code":"TOOL_NOT_STARTED"}},"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"retry"},"role":"user","id":"{{messageId}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} -{"type":"agent/retry","data":{"turn":2,"step":1,"inputId":"{{messageId}}","source":{"kind":"retry"}}} +{"type":"user/message","data":{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"} {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/snapshots/sdk/sdk.snapshot.ts b/snapshots/sdk/sdk.snapshot.ts index c4c5a27aa7..1a687411fc 100644 --- a/snapshots/sdk/sdk.snapshot.ts +++ b/snapshots/sdk/sdk.snapshot.ts @@ -492,10 +492,6 @@ function turnActions(log: string): TurnAction[] { current = { turn: data.turn } continue } - if (record.type === 'agent/retry' && current !== undefined) { - current = { turn: current.turn, content: [] } - continue - } if (record.type === 'user/message' && current !== undefined && current.content === undefined) { const data = record.data as JsonObject | undefined const source = data?.source as JsonObject | undefined @@ -772,8 +768,7 @@ async function runScenario(scenario: CorpusScenario): Promise<{ session = await harness.resumeSession(sessionId) expect(session.id).toBe(sessionId) } - const input = materializeInput(action.content, scenario, cwd, liveSessions) - const result = await session.run(input, { + const result = await session.run(materializeInput(action.content, scenario, cwd, liveSessions), { onNotification: (notification) => { notifications.push(notification) observe(notification) @@ -1122,9 +1117,6 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => { } } }) } if (scenario.name === 'tool-scheduler-recovery') { - expect(records(ordered[0]!.content).filter(event => event.type === 'user/message' - && (event.data['source'] as { kind: string }).kind === 'user')).toHaveLength(1) - expect(records(ordered[0]!.content).filter(event => event.type === 'agent/retry')).toHaveLength(1) expect(results).toHaveLength(2) const events = results.flatMap(result => result.events) expect(results[1]?.finalResponse, JSON.stringify(events.filter(event => event.type === 'turn/end'))) diff --git a/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl b/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl index 01f324070c..885df800f9 100644 --- a/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl +++ b/snapshots/sdk/tool-scheduler-recovery/notifications.expected.jsonl @@ -19,12 +19,12 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":20,"time":0,"data":{"turn":1,"step":1}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":21,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":22,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"retry"},"role":"user","id":"{{sessionId}}"}]}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":22,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"running"}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":23,"time":0,"data":{"turn":2}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":24,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":25,"time":0,"data":{"turn":2,"step":1}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/retry","seq":26,"time":0,"data":{"turn":2,"step":1,"inputId":"{{sessionId}}","source":{"kind":"retry"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":26,"time":0,"data":{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":27,"time":0,"data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":64,"outputTokens":28},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":28}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":28,"time":0,"data":{"turn":2,"step":1}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":29,"time":0,"data":{"turn":2,"reason":{"kind":"completed"}}}}} diff --git a/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl b/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl index 704d83c721..1eb28fe0c1 100644 --- a/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl +++ b/snapshots/sdk/tool-scheduler-recovery/session.v4.jsonl @@ -21,11 +21,11 @@ {"type":"tool/result","data":{"turn":1,"step":1,"message":{"id":"interrupted-tool-result-scheduler-unstarted-19","role":"tool","toolCallId":"scheduler-unstarted","isError":true,"source":{"kind":"tool","callId":"scheduler-unstarted"},"content":[{"type":"text","text":"The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed."}]},"error":{"name":"ToolNotStartedError","code":"TOOL_NOT_STARTED"}},"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"error","error":{"message":"Snapshot scheduler preparation failed","code":"UNKNOWN"}}}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[],"source":{"kind":"retry"},"role":"user","id":"{{message:6}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:6}}"}]}} {"type":"turn/start","data":{"turn":2}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":1}} -{"type":"agent/retry","data":{"turn":2,"step":1,"inputId":"{{message:6}}","source":{"kind":"retry"}}} +{"type":"user/message","data":{"content":[{"type":"text","text":"Continue after the scheduler failure without repeating any tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:6}}"},"surfaceOp":"append"} {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"SCHEDULER_RECOVERY_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":64,"outputTokens":28},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["SCHEDULER_RECOVERY_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"SCHEDULER_RECOVERY_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":28}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} From 998ebe4e74aef20ee775c4f5e455988d0f1c5083 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 2 Nov 2026 16:09:19 +0800 Subject: [PATCH 14/29] fix(web): adapt retry rendering and projection tests to current types --- .../tests/session-projections.host.spec.ts | 15 +++++++++------ .../src/client/conversation-nodes/message.ts | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/api/session-controller/tests/session-projections.host.spec.ts b/packages/api/session-controller/tests/session-projections.host.spec.ts index 644af24d6a..2dc46a1ba2 100644 --- a/packages/api/session-controller/tests/session-projections.host.spec.ts +++ b/packages/api/session-controller/tests/session-projections.host.spec.ts @@ -416,14 +416,15 @@ describe('session.history projections block', () => { seedMessages(session, 1) let changed = await iterator.next() - while (!changed.done && changed.value.type === 'projection' && changed.value.key !== 'sessionListMetadata') { - expect(changed.value.key).not.toBe('test/internal-count') + while (!changed.done && changed.value.type === 'projection' + && !changed.value.changes.some(change => change.key === 'sessionListMetadata')) { + expect(changed.value.changes.map(change => change.key)).not.toContain('test/internal-count') changed = await iterator.next() } - expect(changed).toMatchObject({ - done: false, - value: { type: 'projection', changes: [{ key: 'sessionListMetadata' }] }, - }) + if (changed.done || changed.value.type !== 'projection') throw new Error('control stream ended before its projection') + const keys = changed.value.changes.map(change => change.key) + expect(keys).toContain('sessionListMetadata') + expect(keys).not.toContain('test/internal-count') abort.abort() await iterator.return?.() @@ -783,6 +784,7 @@ describe('Session control projection frames', () => { { type: 'projection', sessionId: session.id, seq: 0, changes: [ { key: 'test/last-user', val: { text: 'm0' } }, + { key: 'conversationContinuation', val: null }, { key: 'sessionListMetadata', val: { blank: true, lastPromptAt: 100 } }, ], }, @@ -823,6 +825,7 @@ describe('Session control projection frames', () => { value: { type: 'projection', seq: 0, changes: [ { key: 'test/last-user', val: { text: 'm0' } }, + { key: 'conversationContinuation', val: null }, { key: 'sessionListMetadata', val: { blank: true, lastPromptAt: 100 } }, ], }, diff --git a/packages/client/ui-chat/src/client/conversation-nodes/message.ts b/packages/client/ui-chat/src/client/conversation-nodes/message.ts index e7380a9c9c..34e1fcc863 100644 --- a/packages/client/ui-chat/src/client/conversation-nodes/message.ts +++ b/packages/client/ui-chat/src/client/conversation-nodes/message.ts @@ -137,7 +137,7 @@ export const messageDefinition: ConversationNodeDefinition = { buildViewNode: (context) => { if (context.state === undefined) return null if (context.start?.event.type === 'user/message' - && context.state.source.kind === 'retry' && context.state.content.length === 0) return null + && context.start.event.data.source.kind === 'retry' && context.start.event.data.content.length === 0) return null const waking = context.state.kind === 'context' && context.start?.event.type === 'user/message' && context.state.waking === true From 9ac67a545de4a416ee4311c340a88ccf60f3d1d8 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 2 Nov 2026 16:14:13 +0800 Subject: [PATCH 15/29] test(web): use the current subagent session fields --- .../client/ui-conversation/tests/apply-inject.client.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index 13b660b19f..d26866f463 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -309,7 +309,7 @@ describe('Conversation inject API', () => { expect(composer.addFiles?.([new File([Uint8Array.of(1)], 'draft.pdf', { type: 'application/pdf' })])).toBeNull() break case 'subagent': - await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.subagent = { address: { parentSessionId: ROOT, childSessionId: ROOT, mode: 'continuable' }, parentAvailable: true } }) + await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.subagent = { address: { parentSessionId: ROOT, childSessionId: ROOT, mode: 'continuable' } } }) break case 'removed': await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.removed = true }) From 309f4383541e63cc90a245bf6deceb1846f729b9 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 2 Nov 2026 16:26:58 +0800 Subject: [PATCH 16/29] docs(web): record empty retry message validation --- .../2026-10-27-retry-control-input.i18n.yaml | 4 ++-- docs/persistence-changes/2026-10-27-retry-control-input.md | 2 +- docs/persistence-changes/2026-10-27-retry-control-input.zh.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml b/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml index 3329a50ca3..c9979c895d 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml +++ b/docs/persistence-changes/2026-10-27-retry-control-input.i18n.yaml @@ -21,8 +21,8 @@ en: 6dee29fbf0f77087 zh: 72e0cfdef298a75e /2026-10-27-retry-control-input/verification: - en: fff40611ec2aea13 - zh: 4811164ae5c33b29 + en: 06eee3f214f278b8 + zh: d044457dd6e5b8c3 /2026-10-27-retry-control-input/dev-note: en: b75c8458928b72b9 zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.md b/docs/persistence-changes/2026-10-27-retry-control-input.md index 568db9731d..b97bfca4e8 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.md +++ b/docs/persistence-changes/2026-10-27-retry-control-input.md @@ -52,7 +52,7 @@ The retry source is producer-owned metadata on the existing empty UserMessage re ## Verification -Source review confirms AgentLoop and SDK files match the PR base. Regression cases cover consecutive failures, request deduplication, normal turn settlement, empty retry-message admission, unrelated empty messages, and replay/live/prepend Chat rendering. Updated behavior checks will run after publication as requested. +The Session API suite passed 1112 tests across 51 files; continuation.ts has 100% statements, branches, functions, and lines. Chat and conversation tests passed 1875 tests across 113 files; final focused Chat and input regressions passed 96 and 31 tests. The browser continuation replay passed with an empty retry user message retained in the log, no empty Chat bubble, and normally closed turns. Build passed all 10 gates, typed lint exited 0, and doc-sync passed all 43 gates. Git comparison against the PR base confirms no AgentLoop, Core Agent, Core Session, or SDK changes. ## Dev Note diff --git a/docs/persistence-changes/2026-10-27-retry-control-input.zh.md b/docs/persistence-changes/2026-10-27-retry-control-input.zh.md index a2016b43d3..119eab180a 100644 --- a/docs/persistence-changes/2026-10-27-retry-control-input.zh.md +++ b/docs/persistence-changes/2026-10-27-retry-control-input.zh.md @@ -52,7 +52,7 @@ retry 来源是现有空 UserMessage 表示中的生产方元数据。AgentLoop ## 验证 -源码审查确认 AgentLoop 与 SDK 文件和 PR 基线一致。回归用例覆盖连续失败、请求去重、正常轮次结算、空重试消息准入、无关空消息,以及回放、实时和前插历史三种 Chat 展示。按要求,更新后的行为检查将在发布后运行。 +Session API 测试通过:51 个文件、1112 个测试,continuation.ts 的语句、分支、函数和行覆盖率均为 100%。Chat 与 conversation 测试通过:113 个文件、1875 个测试;最终定向 Chat 与输入回归分别通过 96 和 31 个测试。浏览器继续回放通过,断言日志保留空 retry 用户消息、Chat 无空气泡且轮次正常闭合。构建 10 项全部通过,类型感知 lint 退出码为 0,doc-sync 的 43 项全部通过。与 PR 基线的 Git 比较确认 AgentLoop、Core Agent、Core Session 和 SDK 无改动。 ## 开发备注 From 9eee00fa7de7ad5ec6a9b0379b3fd85e5d16aeb7 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 3 Nov 2026 10:50:46 +0800 Subject: [PATCH 17/29] fix(web): show continuation request receipts --- ...10-26-manual-generation-continuation.i18n.yaml | 4 ++-- .../2026-10-26-manual-generation-continuation.md | 2 +- ...026-10-26-manual-generation-continuation.zh.md | 2 +- packages/client/ui-chat/README.i18n.yaml | 4 ++-- packages/client/ui-chat/README.md | 2 +- packages/client/ui-chat/README.zh.md | 2 +- .../client/chat/TurnTriggerNodeView.module.css | 11 +++++++++++ .../src/client/chat/TurnTriggerNodeView.tsx | 11 +++++++++-- .../ui-chat/src/client/chat/turn-trigger.ts | 8 ++++++-- .../src/client/conversation-nodes/message.ts | 10 ++++++---- packages/client/ui-chat/src/client/locale.ts | 8 ++++++++ .../conversation-node-definitions.client.spec.ts | 6 ++++-- .../ui-chat/tests/turn-trigger.client.spec.tsx | 15 +++++++++++++++ packages/client/ui-conversation/README.i18n.yaml | 4 ++-- packages/client/ui-conversation/README.md | 2 +- packages/client/ui-conversation/README.zh.md | 2 +- packages/client/ui-conversation/src/index.ts | 3 --- .../web/live-interactions/continued.expected.md | 1 + 18 files changed, 72 insertions(+), 25 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml index 0ed154a1c4..79b2e5d678 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.i18n.yaml @@ -9,8 +9,8 @@ en: f118844946afdbdc zh: f1fe37d042f88b24 /agent-note-manual-generation-continuation/decision: - en: b9860cafbc20e453 - zh: a274a3c7f33bc056 + en: 463db7deb7710e07 + zh: cfacb69cb58349a9 /agent-note-manual-generation-continuation/alternatives-considered: en: 58bb948d210d42ea zh: 5dc920d222d71218 diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md index 01f4a88314..dd7a0e24f1 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.md @@ -10,7 +10,7 @@ Users need another request after a model failure without inserting an artificial ## Decision -The play action submits `content: []` through the existing Session prompt API. The API assigns `source.kind: 'retry'`; unchanged AgentLoop admission records an empty `user/message` and opens a normally balanced new turn. Chat omits the empty retry node while the log retains the input. The original failed turn remains closed. +The play action submits `content: []` through the existing Session prompt API. The API assigns `source.kind: 'retry'`; unchanged AgentLoop admission records an empty `user/message` and opens a normally balanced new turn. Chat presents a compact continuation-request receipt while the log retains the empty input. The original failed turn remains closed. The Session API owns the fixed-size `conversationContinuation` projection and shares its value with the browser. Admission requires an eligible failure, true idle ownership, and no pending human input. An appended empty retry message admits the new turn, preserving eligibility if its request also fails. diff --git a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md index fc6fcf6bf2..dc23da7759 100644 --- a/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md +++ b/.agents/notes/implemented/feature/2026-10-26-manual-generation-continuation.zh.md @@ -10,7 +10,7 @@ Status: implemented ## 决策 -播放按钮通过现有 Session prompt API 提交 `content: []`。API 指定 `source.kind: 'retry'`;AgentLoop 沿用普通准入,记录空 `user/message` 并开启正常闭合的新轮次。Chat 不生成空重试节点,日志保留该输入。原失败轮次保持闭合。 +播放按钮通过现有 Session prompt API 提交 `content: []`。API 指定 `source.kind: 'retry'`;AgentLoop 沿用普通准入,记录空 `user/message` 并开启正常闭合的新轮次。Chat 显示轻量的继续请求记录,日志保留空输入。原失败轮次保持闭合。 Session API 拥有固定大小的 `conversationContinuation` 投影,向浏览器发布相同的值。准入要求失败符合条件、持有真正的空闲状态且没有待消费的用户输入。追加的空重试消息使新轮次获得准入,因此请求再次失败后仍保留继续资格。 diff --git a/packages/client/ui-chat/README.i18n.yaml b/packages/client/ui-chat/README.i18n.yaml index c5af804627..1bf89d2ed7 100644 --- a/packages/client/ui-chat/README.i18n.yaml +++ b/packages/client/ui-chat/README.i18n.yaml @@ -30,8 +30,8 @@ en: 382821ca3ffa60dd zh: 9840ffa07c13082a /deepseek-ai-dsh-client-ui-chat/turn-process-folding: - en: 0b1a8109ad0fd9bf - zh: f7274b42510550d0 + en: 7cc6692b879aa500 + zh: e924733c88b526c9 /deepseek-ai-dsh-client-ui-chat/grouped-rendering: en: b1630d93a52f483e zh: 59cb81ca50ecffc4 diff --git a/packages/client/ui-chat/README.md b/packages/client/ui-chat/README.md index 6bdad972c8..60840273c0 100644 --- a/packages/client/ui-chat/README.md +++ b/packages/client/ui-chat/README.md @@ -98,7 +98,7 @@ The completed-turn action footer follows the recorded Turn end. Its action row s ## Turn Process Folding -Empty `user/message` records with `source.kind: 'retry'` remain in Session history while Chat omits their view nodes. Ordinary empty user messages and retry messages containing content retain their normal presentation. +Empty `user/message` records with `source.kind: 'retry'` remain in Session history while Chat renders a compact continuation-request receipt instead of a user bubble. Ordinary empty user messages and retry messages containing content retain their normal presentation. During uninterrupted following, local transcript and steering echoes remain mounted through Inbox acceptance and claim until the durable message arrives, without triggering tail following twice. Pending steering follows Inbox order across clients, using matching local echoes in place. Admitted local steering also suppresses matching stale Inbox rows until the claim projection arrives; steering without a locally tracked submission continues to follow the Inbox projection. After reconnect, Host-owned rows replace receipt-confirmed local echoes; a claim awaiting admission may briefly have no bubble. diff --git a/packages/client/ui-chat/README.zh.md b/packages/client/ui-chat/README.zh.md index 568c82f47e..662603f847 100644 --- a/packages/client/ui-chat/README.zh.md +++ b/packages/client/ui-chat/README.zh.md @@ -98,7 +98,7 @@ Chat 在 `MarkdownDelegateProvider.getTriggerProps` 接入处适配 Markdown 目 ## 轮次过程折叠 -带 `source.kind: 'retry'` 的空 `user/message` 记录保留在 Session 历史中,Chat 不生成对应展示节点。普通空用户消息与包含内容的重试消息保持正常展示。 +带 `source.kind: 'retry'` 的空 `user/message` 记录保留在 Session 历史中,Chat 显示轻量的继续请求记录,不生成用户气泡。普通空用户消息与包含内容的重试消息保持正常展示。 正常在线时,本地 transcript 与 steering 回显在 Inbox 接受与领取期间保持挂载,直到持久消息到达,不会重复触发跟随底部。跨客户端的待处理 steering 遵循 Inbox 顺序,在匹配位置使用本地回显。已入档的本地 steering 还会排除匹配的旧 Inbox 行,直到领取投影到达;没有本地提交身份的 steering 仍按 Inbox 投影显示。重连后,Host 消息替代已有接收回执的本地回显;已领取但尚未入档时,气泡可能短暂消失。 diff --git a/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.module.css b/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.module.css index 4bf309274c..0d964a6790 100644 --- a/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.module.css +++ b/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.module.css @@ -26,6 +26,17 @@ .header:hover { color: var(--dsw-alias-label-secondary); } } .icon { display: inline-flex; flex: none; color: inherit; } +.continuation { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; + padding: 2px 0; + color: var(--dsw-alias-label-tertiary); + font-size: var(--dsh-content-font-size-secondary, 13px); + line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px)); + overflow-wrap: anywhere; +} .title { flex: 1; min-width: 0; overflow-wrap: anywhere; font-size: var(--dsh-content-font-size, 14px); } .time { flex: none; margin-left: auto; color: inherit; font: var(--dsw-font-xxs-12); } .chevron, .openChevron { flex: none; color: inherit; } diff --git a/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.tsx b/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.tsx index e6e366666a..70b4ded7bc 100644 --- a/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.tsx +++ b/packages/client/ui-chat/src/client/chat/TurnTriggerNodeView.tsx @@ -1,10 +1,10 @@ -/** An independent, expandable notice explaining a non-human Turn trigger. */ +/** Recorded Turn triggers and compact manual-continuation receipts. */ import { useId, useState, type ComponentType } from 'react' import { IconAgentPresetOutlineRegular, IconClockOutlineRegular, IconBranchOutlineRegular, IconChevronDownOutlineRegular, IconContextInjectionOutlineRegular, IconCordisPluginOutlineRegular, IconGoalOutlineRegular, IconGlobeOutlineRegular, IconPaperPlaneOutlineRegular, IconQueueOutlineRegular, - IconUsersOutlineRegular, type IconProps, + IconUsersOutlineRegular, IconPlayOutlineRegular, type IconProps, } from '@deepseek-ai/dsh-client-ui-primitives' import type { ChatNodeViewProps } from '../contract/slots.ts' import { formatMessageClock } from './message-chrome.ts' @@ -13,6 +13,7 @@ import { turnTriggerDetails, type TurnTriggerIcon } from './turn-trigger.ts' import css from './TurnTriggerNodeView.module.css' const TRIGGER_ICONS: Record> = { + continue: IconPlayOutlineRegular, request: IconContextInjectionOutlineRegular, goal: IconGoalOutlineRegular, agent: IconPaperPlaneOutlineRegular, @@ -31,6 +32,12 @@ export function TurnTriggerNodeView({ node, t }: Pick + + {t(details.title)} + + ) const date = new Date(node.data.time) const time = formatMessageClock(node.data.time, t) return ( diff --git a/packages/client/ui-chat/src/client/chat/turn-trigger.ts b/packages/client/ui-chat/src/client/chat/turn-trigger.ts index ade8093490..f1e5a2397e 100644 --- a/packages/client/ui-chat/src/client/chat/turn-trigger.ts +++ b/packages/client/ui-chat/src/client/chat/turn-trigger.ts @@ -3,7 +3,7 @@ import type { ContextMessageNode } from '../contract/snapshot.ts' import type { ChatKey } from '../locale.ts' /** Existing primitive glyph selected for a Turn trigger's source family. */ -export type TurnTriggerIcon = 'agent' | 'github' | 'goal' | 'job' | 'plugin' | 'request' | 'schedule' | 'subagent' | 'team' | 'webhook' +export type TurnTriggerIcon = 'agent' | 'continue' | 'github' | 'goal' | 'job' | 'plugin' | 'request' | 'schedule' | 'subagent' | 'team' | 'webhook' function record(value: unknown): Record { return typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record : {} @@ -30,17 +30,21 @@ function subagentTitle(reason: unknown): ChatKey { /** * Describe a waking message using its recorded source. * @param node - durable context, including the original notification body. - * @returns localized title key and source-family icon. + * @returns localized title, source-family icon, and compact presentation for continuation receipts. */ export function turnTriggerDetails(node: ContextMessageNode): { title: ChatKey icon: TurnTriggerIcon + compact?: true } { const source = record(node.source) const kind = field(source, 'kind') let title: ChatKey = 'message.trigger.request' let icon: TurnTriggerIcon = 'request' switch (kind) { + case 'retry': + if (node.content.length === 0) return { title: 'message.continuation.requested', icon: 'continue', compact: true } + break case 'goal': { title = 'message.trigger.goal' icon = 'goal' diff --git a/packages/client/ui-chat/src/client/conversation-nodes/message.ts b/packages/client/ui-chat/src/client/conversation-nodes/message.ts index 34e1fcc863..9120812197 100644 --- a/packages/client/ui-chat/src/client/conversation-nodes/message.ts +++ b/packages/client/ui-chat/src/client/conversation-nodes/message.ts @@ -38,7 +38,7 @@ declare module '../contract/chat-nodes.ts' { steering: ReferencedSteeringMessageNode /** Non-user context injected into model history. */ context: ContextMessageNode - /** Non-human input that starts a Turn. */ + /** Recorded notification or manual continuation that starts a Turn. */ 'turn-trigger': ContextMessageNode } } @@ -136,14 +136,16 @@ export const messageDefinition: ConversationNodeDefinition = { update: context => context.state, buildViewNode: (context) => { if (context.state === undefined) return null - if (context.start?.event.type === 'user/message' - && context.start.event.data.source.kind === 'retry' && context.start.event.data.content.length === 0) return null + const location = contextLocation(context) + if (context.state.kind === 'context' && context.start?.event.type === 'user/message' + && context.start.event.data.source.kind === 'retry' && context.start.event.data.content.length === 0) { + return chatNode(context, 'turn-trigger', context.state.seq, context.state, { location, groupTree: 'input' }) + } const waking = context.state.kind === 'context' && context.start?.event.type === 'user/message' && context.state.waking === true // Human input claimed for Step 1 opens an exchange; Notice-woken Turns and // later steering stay in the current exchange, so they never fold earlier Turns. - const location = contextLocation(context) const human = context.state.kind === 'user' || context.state.kind === 'steering' const opensExchange = human && location.kind === 'step' && location.step.step === 1 return chatNode( diff --git a/packages/client/ui-chat/src/client/locale.ts b/packages/client/ui-chat/src/client/locale.ts index 221ab256e8..4f6617ce85 100644 --- a/packages/client/ui-chat/src/client/locale.ts +++ b/packages/client/ui-chat/src/client/locale.ts @@ -170,6 +170,7 @@ export const zh = { 'message.retry.active': '正在重试模型请求', 'message.retry.cancelled': '模型请求重试已取消', 'message.retry.started': '已重试模型请求', + 'message.continuation.requested': '已请求继续生成', 'message.retry.scheduled': '等待重试模型请求', 'message.retry.status': '{label}({retry}/{maximum}) · {seconds}s', 'message.retry.delay': '重试延迟:', @@ -378,6 +379,7 @@ export const en = { 'message.retry.active': 'Retrying model request', 'message.retry.cancelled': 'Model request retry cancelled', 'message.retry.started': 'Retried model request', + 'message.continuation.requested': 'Requested to continue generation', 'message.retry.scheduled': 'Waiting to retry model request', 'message.retry.status': '{label} ({retry}/{maximum}) · {seconds}s', 'message.retry.delay': 'Retry delay: ', @@ -583,6 +585,7 @@ export const ja = { 'message.retry.active': 'モデルリクエストを再試行中', 'message.retry.cancelled': 'モデルリクエストの再試行をキャンセルしました', 'message.retry.started': 'モデルリクエストを再試行しました', + 'message.continuation.requested': '生成の続行をリクエストしました', 'message.retry.scheduled': 'モデルリクエストの再試行待ちです', 'message.retry.status': '{label} ({retry}/{maximum}) · {seconds}秒', 'message.retry.delay': '再試行までの待機時間: ', @@ -788,6 +791,7 @@ export const es = { 'message.retry.active': 'Reintentando solicitud de modelo', 'message.retry.cancelled': 'Reintento de solicitud de modelo cancelado', 'message.retry.started': 'Solicitud de modelo reintentada', + 'message.continuation.requested': 'Se ha solicitado continuar la generación', 'message.retry.scheduled': 'Esperando para volver a intentar la solicitud de modelo', 'message.retry.status': '{label} ({retry}/{maximum}) · {seconds}s', 'message.retry.delay': 'Retraso de reintento: ', @@ -993,6 +997,7 @@ export const fr = { 'message.retry.active': 'Nouvelle tentative de demande de modèle', 'message.retry.cancelled': 'Nouvelle tentative de demande de modèle annulée', 'message.retry.started': 'Demande de modèle réessayée', + 'message.continuation.requested': 'Suite de la génération demandée', 'message.retry.scheduled': 'En attente de réessayer la demande de modèle', 'message.retry.status': '{label} ({retry}/{maximum}) · {seconds}', 'message.retry.delay': 'Délai de nouvelle tentative : ', @@ -1198,6 +1203,7 @@ export const de = { 'message.retry.active': 'Modellanforderung wird erneut versucht', 'message.retry.cancelled': 'Wiederholungsversuch der Modellanforderung abgebrochen', 'message.retry.started': 'Modellanfrage erneut versucht', + 'message.continuation.requested': 'Fortsetzung der Generierung angefordert', 'message.retry.scheduled': 'Warten auf erneuten Versuch der Modellanforderung', 'message.retry.status': '{label} ({retry}/{maximum}) · {seconds}s', 'message.retry.delay': 'Wiederholungsverzögerung: ', @@ -1403,6 +1409,7 @@ export const ru = { 'message.retry.active': 'Повторный запрос модели', 'message.retry.cancelled': 'Повтор запроса модели отменен.', 'message.retry.started': 'Повторный запрос модели', + 'message.continuation.requested': 'Запрошено продолжение генерации', 'message.retry.scheduled': 'Ожидание повторного запроса модели', 'message.retry.status': '{label} ({retry}/{maximum}) · {seconds}s', 'message.retry.delay': 'Задержка повтора: ', @@ -1608,6 +1615,7 @@ export const pt = { 'message.retry.active': 'Tentando novamente a solicitação de modelo', 'message.retry.cancelled': 'Nova tentativa de solicitação de modelo cancelada', 'message.retry.started': 'Solicitação de modelo repetida', + 'message.continuation.requested': 'Continuação da geração solicitada', 'message.retry.scheduled': 'Aguardando para tentar novamente a solicitação de modelo', 'message.retry.status': '{label} ({retry}/{maximum}) · {seconds}s', 'message.retry.delay': 'Atraso na nova tentativa: ', diff --git a/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts b/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts index b7e2d74ba6..414dd6aaca 100644 --- a/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts +++ b/packages/client/ui-chat/tests/conversation-node-definitions.client.spec.ts @@ -626,7 +626,7 @@ describe('built-in conversation node Definitions', () => { }, ) - it.each(['replay', 'live', 'prepend'] as const)('hides empty retry bubbles through %s while retaining other input', (mode) => { + it.each(['replay', 'live', 'prepend'] as const)('presents empty retry receipts through %s while retaining other input', (mode) => { const retry = { id: 'empty-retry', role: 'user', content: [], source: { kind: 'retry' } } const before = [ at(1, 'agent/inbox/spliced', { target: 'next-turn', start: 0, inserted: [retry] }), @@ -640,7 +640,9 @@ describe('built-in conversation node Definitions', () => { if (mode === 'prepend') value.prepend(before, false) value.flush() const current = snapshot(value) - expect(current.nodes.values().filter(candidate => candidate.anchorSeq === 5)).toEqual([]) + const receipt = node(current, 'turn-trigger') + expect(receipt).toMatchObject({ anchorSeq: 5, groupTree: 'input', data: { content: [], source: { kind: 'retry' } } }) + expect(current.order).toContain(receipt?.key) expect(node(current, 'unknown')).toBeUndefined() value.append(at(6, 'user/message', { ...retry, id: 'retry-with-content', diff --git a/packages/client/ui-chat/tests/turn-trigger.client.spec.tsx b/packages/client/ui-chat/tests/turn-trigger.client.spec.tsx index bbb2999cc7..e12ca7973e 100644 --- a/packages/client/ui-chat/tests/turn-trigger.client.spec.tsx +++ b/packages/client/ui-chat/tests/turn-trigger.client.spec.tsx @@ -24,6 +24,20 @@ function trigger(source: unknown): ChatNode<'turn-trigger'> { } describe('Turn trigger notices', () => { + it.each([ + [en, 'Requested to continue generation'], + [zh, '已请求继续生成'], + ])('shows an empty retry as a compact continuation receipt', (locale, title) => { + const node = trigger({ kind: 'retry' }) + node.data.content = [] + expect(turnTriggerDetails(node.data)).toEqual({ title: 'message.continuation.requested', icon: 'continue', compact: true }) + const view = render() + expect(view.getByText(title)).toBeDefined() + expect(view.queryByRole('button')).toBeNull() + expect(view.container.querySelector('[data-generation-continuation] svg')).not.toBeNull() + expect(view.container.querySelector('time')).toBeNull() + }) + it.each([ [{ kind: 'schedule' }, 'schedule', 'Automation task'], [{ kind: 'tool-jobs' }, 'job', 'Background task updated'], @@ -35,6 +49,7 @@ describe('Turn trigger notices', () => { [{ kind: 'webhook', provider: 'github' }, 'github', 'GitHub event received'], [{ kind: 'webhook', provider: 'custom' }, 'webhook', 'External event received'], [{ kind: 'custom-extension' }, 'request', 'Execution requested'], + [{ kind: 'retry' }, 'request', 'Execution requested'], ] as const)('presents the recorded %j source', (source, icon, title) => { const node = trigger(source) const details = turnTriggerDetails(node.data) diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index ac9d5993cb..a579fc1027 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -33,8 +33,8 @@ en: 6a7ae99570c9c007 zh: 26e86063e97d82c7 /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/what-the-model-sees: - en: 6b815b6e27a8750b - zh: 87a08fc5b075e626 + en: 017391d7ad0f9eb7 + zh: 2693ed29d44b1164 /deepseek-ai-dsh-client-ui-conversation/model-experience/manual-continuation-request/token-effect: en: 517c56dbd522c929 zh: 55fa3a02d026e36a diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 0399a8c213..6a89c16706 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -197,7 +197,7 @@ Session title context gestures use the shared [context menu](../ui-context-menu/ #### What the model sees -The play action submits `content: []` through the existing Session prompt API. Ordinary Loop admission records an empty `user/message` with `source.kind: 'retry'`. Chat hides the empty retry bubble. The adapter applies its ordinary message serialization; the action adds no localized or hidden continuation text. Pending injected context follows ordinary admission. +The play action submits `content: []` through the existing Session prompt API. Ordinary Loop admission records an empty `user/message` with `source.kind: 'retry'`. Chat presents the empty retry as a compact continuation-request receipt. The adapter applies its ordinary message serialization; the action adds no localized or hidden continuation text. Pending injected context follows ordinary admission. #### Token effect diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index 02ba1a7f6a..3e4d8f942e 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -197,7 +197,7 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match #### 模型看到什么 -播放按钮通过现有 Session prompt API 提交 `content: []`。普通 Loop 准入记录带 `source.kind: 'retry'` 的空 `user/message`。Chat 隐藏空重试气泡。适配器按普通消息序列化处理;该操作不添加本地化或隐藏的继续文本。待注入上下文遵循普通准入流程。 +播放按钮通过现有 Session prompt API 提交 `content: []`。普通 Loop 准入记录带 `source.kind: 'retry'` 的空 `user/message`。Chat 将空重试消息显示为轻量的继续请求记录。适配器按普通消息序列化处理;该操作不添加本地化或隐藏的继续文本。待注入上下文遵循普通准入流程。 #### Token 影响 diff --git a/packages/client/ui-conversation/src/index.ts b/packages/client/ui-conversation/src/index.ts index 6ef779d42b..90c4366a1a 100644 --- a/packages/client/ui-conversation/src/index.ts +++ b/packages/client/ui-conversation/src/index.ts @@ -24,9 +24,6 @@ export const Config = z.object({ [BUSY_ENTER_FIELD]: ConversationSettingsFields[BUSY_ENTER_FIELD].volatile(), }) -/** Settings integration is optional and does not block activation. */ -export const inject = [] - /** Register browser conversation preferences. * @param ctx Plugin context used for optional settings presentation. */ diff --git a/snapshots/web/live-interactions/continued.expected.md b/snapshots/web/live-interactions/continued.expected.md index 16c21a89f1..d31d51fa11 100644 --- a/snapshots/web/live-interactions/continued.expected.md +++ b/snapshots/web/live-interactions/continued.expected.md @@ -20,6 +20,7 @@ - text: This turn failedupstream 503 - code: SERVER - text: plan Plan mode on. Use /plan off to leave. plan Plan mode off. +- text: Requested to continue generation - status: Completed - button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. From 3d470947027c349e282ed2fe1330477dc3f566ea Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 3 Nov 2026 10:53:03 +0800 Subject: [PATCH 18/29] test(web): edit continuation drafts through keyboard input --- apps/web/tests/live-interactions.e2e.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/tests/live-interactions.e2e.ts b/apps/web/tests/live-interactions.e2e.ts index ebb69fa246..aef7c3c699 100644 --- a/apps/web/tests/live-interactions.e2e.ts +++ b/apps/web/tests/live-interactions.e2e.ts @@ -27,7 +27,7 @@ import { compareOrRefreshGolden, fixtureUserPrompts, launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold, } from './scaffold.ts' -import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts' +import { connectFreshWorkspace, newEnglishPage, saveFailureShot, writeComposerDraft } from './support.ts' const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/live-interactions', import.meta.url)) const FIXTURE = join(SNAPSHOT_DIR, 'session.v3.jsonl') @@ -448,7 +448,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { const resume = page.getByRole('button', { name: 'Continue generating', exact: true }) await resume.waitFor() const input = page.locator('[data-composer-input]').first() - await input.fill('/plan ') + await writeComposerDraft(page, input, '/plan ') await input.press('Enter') const plan = page.getByRole('button', { name: 'Plan mode on, press to turn off', exact: true }) await plan.waitFor() @@ -458,10 +458,10 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => { await resume.waitFor() expect(await resume.isEnabled()).toBe(true) expect(turnEndReasons(sessionEvents)).toEqual(['error']) - await input.fill('A different instruction') + await writeComposerDraft(page, input, 'A different instruction') expect(await resume.count()).toBe(0) expect(await page.getByRole('button', { name: 'Send message', exact: true }).isEnabled()).toBe(true) - await input.fill('') + await writeComposerDraft(page, input, '') await resume.waitFor() await page.setViewportSize({ width: 390, height: 844 }) // ResizeObserver collapses the model label after the viewport changes. From 2ba385e9c9fa3aed9fd9bc31033290fbd896c6b4 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 3 Nov 2026 10:54:04 +0800 Subject: [PATCH 19/29] test(web): align continuation receipt accessibility snapshot --- snapshots/web/live-interactions/continued.expected.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/snapshots/web/live-interactions/continued.expected.md b/snapshots/web/live-interactions/continued.expected.md index d31d51fa11..063c0ced95 100644 --- a/snapshots/web/live-interactions/continued.expected.md +++ b/snapshots/web/live-interactions/continued.expected.md @@ -19,8 +19,7 @@ - status: - text: This turn failedupstream 503 - code: SERVER -- text: plan Plan mode on. Use /plan off to leave. plan Plan mode off. -- text: Requested to continue generation +- text: plan Plan mode on. Use /plan off to leave. plan Plan mode off. Requested to continue generation - status: Completed - button "Completed in {{duration}}" - paragraph: Event sourcing is a pattern where all changes to an application's state are stored as an immutable, append-only sequence of events, rather than persisting only the current state, enabling full auditability, temporal queries, and event-driven architectures. From c1ab3a24b0e995726e75389e4a653f14ae1a3721 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 3 Nov 2026 11:42:35 +0800 Subject: [PATCH 20/29] refactor(web): name failed generation recovery as retry --- .../client/ui-conversation/README.i18n.yaml | 4 +-- packages/client/ui-conversation/README.md | 4 ++- packages/client/ui-conversation/README.zh.md | 4 ++- .../ui-conversation/src/client/apply.ts | 10 +++---- .../src/client/contract/input.ts | 2 +- .../src/client/contract/slots.ts | 10 +++---- .../ui-conversation/src/client/input/hub.ts | 20 ++++++------- .../src/client/skeleton/InputBar.tsx | 20 ++++++------- .../tests/apply-inject.client.spec.tsx | 30 +++++++++---------- .../tests/input-bar.client.spec.tsx | 22 +++++++------- .../tests/input-matrix.client.spec.tsx | 4 +-- .../tests/input-scenarios.client.spec.tsx | 4 +-- .../tests/skeleton.client.spec.tsx | 4 +-- 13 files changed, 71 insertions(+), 67 deletions(-) diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index a579fc1027..5b2cf88b2b 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -21,8 +21,8 @@ en: cc4dafd1aae02468 zh: be6b67724b7ac4a3 /deepseek-ai-dsh-client-ui-conversation/shell-and-standard-props: - en: 4bf0671192af2d1e - zh: 54f47844e1d8621e + en: 98925230304e2815 + zh: b26aa383b89b0b18 /deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries: en: 59cd63057dfb6eeb zh: b2e2c86e810b7bb8 diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 6a89c16706..e8d5955f73 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -78,10 +78,12 @@ The composer measures its expanded control groups after size, content, visibilit The context meter follows the [statistics-control radius](../../../docs/ui-radius.md#radius-scale). -An empty, idle ordinary Session composer offers a play action after `RATE_LIMIT`, `SERVER`, `TIMEOUT`, `TRANSPORT`, `EMPTY_RESPONSE`, or `STREAM_CLOSED` ends its latest turn. The failed turn must include admitted ordinary user input and a settled failed model attempt. Clicking submits `content: []` through the existing Session prompt API; drafts and attachments keep the Send action. A new balanced turn uses committed history without adding a user message. Refusal or transport failure leaves the draft empty and reports the error through the ordinary submission lifecycle. +An empty, idle ordinary Session composer offers a play action after `RATE_LIMIT`, `SERVER`, `TIMEOUT`, `TRANSPORT`, `EMPTY_RESPONSE`, or `STREAM_CLOSED` ends its latest turn. The failed turn must include admitted ordinary user input and a settled failed model attempt. Clicking submits `content: []` through the existing Session prompt API; drafts and attachments keep the Send action. A new balanced turn uses committed history and records an empty `user/message` with `source.kind: 'retry'`. Refusal or transport failure leaves the draft empty and reports the error through the ordinary submission lifecycle. The Session API owns `conversationContinuation`, shared by Host admission and browser presentation. It restores eligibility from complete Session history and preserves it across repeated failed empty continuations, independently of loaded browser pages. +The composer exposes `retry(failureSeq)` for failed-generation recovery and `useRetryableFailure` for the latest eligible failed `turn/end` sequence or `undefined`. User-facing text calls the action Continue generating. + The composer uses the shared [text-input focus policy](../ui-primitives/README.md#use-this-package) on mount, Session switching, and unlock. Identified phones and tablets require tapping the input; desktop devices retain automatic focus. On these mobile devices, a submission that empties the focused draft moves focus to the non-text composer card, dismissing the keyboard while retaining its navigation position. Rejected submissions, retained newer draft text, and another control already holding focus preserve focus. The [Lexical patch](../../../patches/lexical@0.49.0.patch) keeps caret scrolling in the same coordinate system as DOM geometry when a mobile keyboard pans the page. The context-occupancy button shows a ring and percentage below the input card, after the Session statistics. Clicking it opens the token breakdown in a panel kept inside the viewport, including when no statistics are shown; the button stays hidden until context usage and capacity are available. Outside the Hero, that row keeps one reading's height while it is empty, so statistics and context usage arriving later do not change the composer's height; the Hero shows no empty row. diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index 3e4d8f942e..8a25eaa7a6 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -78,10 +78,12 @@ Queue 预览启用空白折叠和文档边缘 trim,保留 emoji,并使用 CS 上下文占用率入口遵循[统计控件圆角规则](../../../docs/ui-radius.zh.md#radius-scale)。 -空闲且草稿为空的普通 Session,在最新轮次以 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`、`EMPTY_RESPONSE` 或 `STREAM_CLOSED` 结束后显示播放按钮。失败工作必须有已准入的普通用户输入和失败请求尝试。点击通过现有 Session prompt API 提交 `content: []`;已有草稿或附件仍使用发送按钮。新轮次正常闭合,使用已提交历史而不新增用户消息。请求被拒绝或传输失败时,草稿保持为空,错误沿用普通提交流程展示。 +空闲且草稿为空的普通 Session,在最新轮次以 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`、`EMPTY_RESPONSE` 或 `STREAM_CLOSED` 结束后显示播放按钮。失败工作必须有已准入的普通用户输入和失败请求尝试。点击通过现有 Session prompt API 提交 `content: []`;已有草稿或附件仍使用发送按钮。新轮次正常闭合,使用已提交历史,并记录 `source.kind: 'retry'` 的空 `user/message`。请求被拒绝或传输失败时,草稿保持为空,错误沿用普通提交流程展示。 Session API 拥有 `conversationContinuation`,Host 准入和浏览器展示共用该投影。它从完整 Session 历史恢复资格,并在空输入继续再次失败时保留资格,不依赖浏览器已加载的历史分页。 +composer 提供 `retry(failureSeq)` 处理生成失败后的恢复,通过 `useRetryableFailure` 读取最新符合条件的失败 `turn/end` 序号或 `undefined`。面向用户的操作文案为“继续生成”。 + composer 在挂载、切换 Session 和解除锁定时使用共享的[文字输入框聚焦策略](../ui-primitives/README.zh.md#use-this-package)。识别出的手机和平板需要点击输入框;桌面设备保留自动聚焦。在这些移动设备上,提交清空正在聚焦的草稿后,焦点移到不接受文字输入的 composer 卡片,收起键盘并保留键盘导航位置。被拒绝的提交、保留的新草稿文字,以及已经聚焦的其他控件均保留原有焦点。移动端键盘平移页面时,[Lexical 补丁](../../../patches/lexical@0.49.0.patch)让光标滚动计算与 DOM 几何使用同一坐标系。 上下文占用按钮在输入卡片下方、会话统计右侧显示圆环和百分比。点击按钮可在视口内的面板查看 token 构成,没有统计项时面板也不会越界;上下文用量和容量尚不可用时,按钮保持隐藏。Hero 之外,这一行为空时仍保留一项读数的高度,因此会话统计和上下文用量稍后到达不会改变 composer 的高度;Hero 不显示空行。 diff --git a/packages/client/ui-conversation/src/client/apply.ts b/packages/client/ui-conversation/src/client/apply.ts index fee2a19e53..0c55b5c140 100644 --- a/packages/client/ui-conversation/src/client/apply.ts +++ b/packages/client/ui-conversation/src/client/apply.ts @@ -79,7 +79,7 @@ const ABSENT_NOTICES = { getSnapshot: (): InputNotice | null => null, subscribe: () => () => {}, } -const ABSENT_CONTINUATION = { +const ABSENT_RETRYABLE_FAILURE = { getSnapshot: () => undefined, subscribe: () => () => {}, } @@ -487,9 +487,9 @@ export function apply(ctx: Context, config: Config = Config({})): void { retryFileUpload: undefined, toggleCommandMenu: undefined, stop: undefined, - continueGeneration: undefined, + retry: undefined, hooks: { - continuation: ABSENT_CONTINUATION, + retryableFailure: ABSENT_RETRYABLE_FAILURE, stopShortcut, busyEnter: submissionPolicy.busyEnter, fileUploads: ABSENT_FILE_UPLOADS, @@ -568,9 +568,9 @@ export function apply(ctx: Context, config: Config = Config({})): void { }) }, stop: () => { stop(sessionId) }, - continueGeneration: (ending) => { inputHub.continueGeneration(sessionId, ending) }, + retry: (failureSeq) => { inputHub.retry(sessionId, failureSeq) }, hooks: { - continuation: inputHub.continuation(sessionId), + retryableFailure: inputHub.retryableFailure(sessionId), stopShortcut, busyEnter: submissionPolicy.busyEnter, fileUploads: conversation.fileUploads, diff --git a/packages/client/ui-conversation/src/client/contract/input.ts b/packages/client/ui-conversation/src/client/contract/input.ts index 9cc7e4b566..6f6b0f1436 100644 --- a/packages/client/ui-conversation/src/client/contract/input.ts +++ b/packages/client/ui-conversation/src/client/contract/input.ts @@ -352,7 +352,7 @@ export type InputEvent = /** Enter submission with the current clipboard projection, its reference chips, its quotes, and its attachments. */ | { readonly type: 'enter' - /** Explicit continuation may submit an empty draft through the default sink. */ + /** Explicit retry may submit an empty draft through the default sink. */ readonly allowEmpty?: boolean readonly mode: InputSubmitMode readonly draft: string diff --git a/packages/client/ui-conversation/src/client/contract/slots.ts b/packages/client/ui-conversation/src/client/contract/slots.ts index 65d3e2fd27..963f211b12 100644 --- a/packages/client/ui-conversation/src/client/contract/slots.ts +++ b/packages/client/ui-conversation/src/client/contract/slots.ts @@ -566,13 +566,13 @@ export interface ComposerBarInjected { toggleCommandMenu: ((selection: EditSelection) => void) | undefined stop: (() => void) | undefined /** - * Submit empty continuation input for the displayed failure. - * @param ending - sequence of the displayed failed turn/end. + * Submit empty input to retry the displayed failed generation. + * @param failureSeq - sequence of the displayed failed turn/end. */ - continueGeneration: ((ending: SessionSeq) => void) | undefined + retry: ((failureSeq: SessionSeq) => void) | undefined hooks: { - /** Host-projected latest eligible failure, excluding pending human steering. */ - continuation: ObservableSnapshot + /** Latest retryable failed turn/end sequence; undefined while unavailable or human steering is pending. */ + retryableFailure: ObservableSnapshot /** Readable sequence while the fixed Stop command is registered. */ stopShortcut: ObservableSnapshot /** diff --git a/packages/client/ui-conversation/src/client/input/hub.ts b/packages/client/ui-conversation/src/client/input/hub.ts index 16192af6ac..0c697803d0 100644 --- a/packages/client/ui-conversation/src/client/input/hub.ts +++ b/packages/client/ui-conversation/src/client/input/hub.ts @@ -57,7 +57,7 @@ interface ConversationAttachmentFace { /** Session-addressed input facade registry (SessionInputResolver face + composer-layer extras). */ export class InputHub implements SessionInputResolver { private readonly shells = new WeakMap() - private readonly continuations = new WeakMap>() + private readonly retryableFailures = new WeakMap>() /** * @param ctx - client root context (services resolved lazily per call — boot order stays free). @@ -209,14 +209,14 @@ export class InputHub implements SessionInputResolver { } /** - * Read Host-projected eligibility while excluding pending human steering. + * Read the Host-projected retryable failure while excluding pending human steering. * @param id - retained ordinary Session identity. - * @returns the identity-stable eligibility source. + * @returns an identity-stable source whose value is the failed turn/end sequence or undefined. */ - continuation(id: SessionId): ObservableSnapshot { + retryableFailure(id: SessionId): ObservableSnapshot { const binding = this.sessions().binding(id) if (binding === undefined) throw new Error(`conversation.input: session "${id}" resolved no binding`) - let source = this.continuations.get(binding) + let source = this.retryableFailures.get(binding) if (source === undefined) { const ending = binding.session.projections.faceOf('conversationContinuation') const inbox = binding.session.projections.faceOf('inbox') @@ -232,20 +232,20 @@ export class InputHub implements SessionInputResolver { return () => { unsubscribeInbox(); unsubscribeEnding() } }, } - this.continuations.set(binding, source) + this.retryableFailures.set(binding, source) } return source } /** - * Submit empty continuation input through the ordinary composer. Stale + * Submit empty input to retry a failed generation through the ordinary composer. Stale * failures, active work, and drafts keep their current input unchanged. * @param id - retained ordinary Session identity. - * @param ending - failure the displayed button addresses. + * @param failureSeq - failed turn/end sequence the displayed button addresses. */ - continueGeneration(id: SessionId, ending: SessionSeq): void { + retry(id: SessionId, failureSeq: SessionSeq): void { const binding = this.sessions().binding(id) - if (binding === undefined || this.continuation(id).getSnapshot() !== ending) return + if (binding === undefined || this.retryableFailure(id).getSnapshot() !== failureSeq) return const session = binding.session.getSnapshot() const shell = this.shellFor(binding) const input = shell.snapshot diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx index 351eb9acf9..f1680c7919 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx @@ -46,9 +46,9 @@ export type InputBarProps = ComposerBarProps export const InputBar = memo(function InputBar({ useSession, useInput, inputActions, keyboard, addFiles, removeAttachment, resolveDraftAttachments, retryFileUpload, - toggleCommandMenu, stop, continueGeneration, t, + toggleCommandMenu, stop, retry, t, renderSlot, useBusyEnter, useFileUploads, useNotices, useLexicon, useMenuLauncher, useStopShortcut, - useAccess, useContinuation, + useAccess, useRetryableFailure, useProjection, sessionId, variant, disabled: inert = false, blocked, workspacePickerOpen = false, onRequestWorkspace, placeholder, accessory, @@ -70,7 +70,7 @@ export const InputBar = memo(function InputBar({ useEffect(() => { setActivity(false) }, [sessionId]) const promptError = useSession(s => s.promptError) ?? null const running = useSession(s => s.running) ?? false - const continuation = useContinuation(value => value) + const retryableFailure = useRetryableFailure(value => value) const historyOpen = useSession(s => s.openState === 'open') ?? false const submissionPending = useSession(s => s.pendingSubmissions.length > 0) ?? false const subagent = useSession(s => s.subagent) ?? null @@ -358,12 +358,12 @@ export const InputBar = memo(function InputBar({ // exposes Stop independently, and so does a running session while a // message waits for the Host. const primaryStops = running && subagent === null && !sending && (empty || blocked !== undefined) - const primaryContinues = !running && subagent === null && empty && continuation !== undefined + const primaryRetries = !running && subagent === null && empty && retryableFailure !== undefined && historyOpen && !submissionPending && !disabled && input.queue.length === 0 - && input.phase === 'plain' && continueGeneration !== undefined + && input.phase === 'plain' && retry !== undefined // Disabled native buttons may omit mouseleave; their tooltip must close from state. const primaryDisabled = primaryStops ? stopAction === undefined - : (!primaryContinues && empty) || disabled || machineBusy || uploadsPending + : (!primaryRetries && empty) || disabled || machineBusy || uploadsPending const interruptible = running && (continuable || (sending && subagent === null)) const primarySubmitMode = resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable) const plainMessageDraft = !empty && input?.phase === 'plain' && !draft.trimStart().startsWith('/') @@ -371,7 +371,7 @@ export const InputBar = memo(function InputBar({ ? t('input.stop') : sending ? t('input.sending') - : primaryContinues + : primaryRetries ? t('input.continue') : running && steeringAvailable && !disabled && !uploadsPending && plainMessageDraft ? t(primarySubmitMode === 'steer' ? 'input.send.steer' : 'input.send.queue') @@ -381,8 +381,8 @@ export const InputBar = memo(function InputBar({ stopAction?.() return } - if (primaryContinues) { - continueGeneration(continuation) + if (primaryRetries) { + retry(retryableFailure) return } if (keyboard === undefined) return // absent machine: the button is disabled @@ -564,7 +564,7 @@ export const InputBar = memo(function InputBar({ - ) : primaryContinues ? ( + ) : primaryRetries ? ( ) : ( diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index d26866f463..a550553195 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -174,14 +174,14 @@ describe('Conversation inject API', () => { await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } })) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) const composer = b.composerApi(ROOT) - expect(b.composerApi(ROOT).hooks.continuation).toBe(composer.hooks.continuation) - expect(composer.hooks.continuation.getSnapshot()).toBe(5) + expect(b.composerApi(ROOT).hooks.retryableFailure).toBe(composer.hooks.retryableFailure) + expect(composer.hooks.retryableFailure.getSnapshot()).toBe(5) b.runtime.ctx.locale.setLocale(locale) const pending = Promise.withResolvers>>() onTestFinished(() => { pending.resolve({ ok: true, value: { accepted: true } }) }) b.sessionFake.prompt.mockImplementationOnce(() => pending.promise) - composer.continueGeneration!(SessionSeq(5)) - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) expect(composer.keyboard!.snapshot.phase).toBe('sending') await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce() }) expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([]) @@ -198,19 +198,19 @@ describe('Conversation inject API', () => { await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) const composer = b.composerApi(ROOT) const input = b.inputApi(ROOT) - composer.continueGeneration!(SessionSeq(1)) + composer.retry!(SessionSeq(1)) input.actions.setDraft('Keep my new instruction') - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) expect(input.state.getSnapshot().draft).toBe('Keep my new instruction') input.actions.setDraft('') await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.running = true }) - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) await b.runtime.sessions.updateSessionSnapshot(ROOT, (state) => { state.running = false }) await b.runtime.sessions.appendEvent(ROOT, { type: 'event', event: { type: 'turn/start', seq: SessionSeq(6), time: 1, data: { turn: 2 } }, }) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', null) - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) expect(input.state.getSnapshot().draft).toBe('') expect(b.sessionFake.prompt).not.toHaveBeenCalled() }) @@ -220,7 +220,7 @@ describe('Conversation inject API', () => { onTestFinished(() => b.runtime.dispose()) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) const composer = b.composerApi(ROOT) - const source = composer.hooks.continuation + const source = composer.hooks.retryableFailure const notified = vi.fn() const unsubscribe = source.subscribe(notified) onTestFinished(unsubscribe) @@ -231,11 +231,11 @@ describe('Conversation inject API', () => { const user = createUserMessage({ content: [{ type: 'text', text: 'new instruction' }], source: { kind: 'user' } }) await b.runtime.sessions.setProjection(ROOT, 'inbox', { ...inbox, 'next-step': [context, user] }) expect(source.getSnapshot()).toBeUndefined() - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) expect(b.sessionFake.prompt).not.toHaveBeenCalled() await b.runtime.sessions.setProjection(ROOT, 'inbox', inbox) expect(source.getSnapshot()).toBe(5) - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce() }) expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([]) expect(notified).toHaveBeenCalledTimes(3) @@ -246,7 +246,7 @@ describe('Conversation inject API', () => { onTestFinished(() => b.runtime.dispose()) await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } }).slice(1), true) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) - const source = b.composerApi(ROOT).hooks.continuation + const source = b.composerApi(ROOT).hooks.retryableFailure expect(source.getSnapshot()).toBe(5) const history = Array.from({ length: 1666 }, (_, index) => continuationTurn({ kind: 'completed' }, index + 1)).flat() history.push(...continuationTurn({ kind: 'completed' }, 1667).slice(0, 4)) @@ -319,7 +319,7 @@ describe('Conversation inject API', () => { break } const before = b.inputApi(ROOT).state.getSnapshot().draft - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) expect(b.inputApi(ROOT).state.getSnapshot().draft).toBe(before) expect(b.sessionFake.prompt).not.toHaveBeenCalled() }, @@ -336,12 +336,12 @@ describe('Conversation inject API', () => { return { ok: false, error } }) else b.sessionFake.prompt.mockRejectedValueOnce(new Error('connection lost')) - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce(); expect(composer.keyboard!.snapshot.phase).toBe('plain') }) expect(composer.keyboard!.snapshot.draft).toBe('') if (outcome === 'refused') expect(b.runtime.sessions.binding(ROOT)!.session.getSnapshot().promptError?.error.message).toBe('request refused') else expect(composer.hooks.notices.getSnapshot()).toMatchObject({ level: 'error', text: 'connection lost' }) - composer.continueGeneration!(SessionSeq(5)) + composer.retry!(SessionSeq(5)) await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledTimes(2) }) }) diff --git a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx index 3d7b8361a1..54989e8707 100644 --- a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx @@ -81,7 +81,7 @@ interface BenchOptions { } draft?: string running?: boolean - continuation?: SessionSeq + retryableFailure?: SessionSeq subagent?: Exclude disabled?: boolean inert?: boolean @@ -162,8 +162,8 @@ function bench(over?: BenchOptions) { if (over?.draft !== undefined && over.draft !== '') shell.setDraft(over.draft) if (over?.attachments !== undefined) shell.addAttachments(over.attachments.map(attachment => attachment.id)) const stop = vi.fn() - const continueGeneration = vi.fn() - const continuation = createSnapshotStore(over?.continuation) + const retry = vi.fn() + const retryableFailure = createSnapshotStore(over?.retryableFailure) const removeAttachment = vi.fn((id: DraftAttachmentId) => { shell.removeAttachment(id) }) const menuLauncher = createSnapshotStore(over?.commandMenuOpen === true ? 'command' : null) const busyEnter = createSnapshotStore<'queue' | 'steer'>(over?.busyEnter ?? 'queue') @@ -219,8 +219,8 @@ function bench(over?: BenchOptions) { }), toggleCommandMenu: over?.toggleCommandMenu ?? vi.fn(), useBusyEnter: bindSnapshotSelector(busyEnter), - continueGeneration, - useContinuation: bindSnapshotSelector(continuation), + retry, + useRetryableFailure: bindSnapshotSelector(retryableFailure), useStopShortcut: bindSnapshotSelector(stopShortcut), useNotices: bindSnapshotSelector(shell.notices), useLexicon: bindSnapshotSelector(shell.lexicon), @@ -257,7 +257,7 @@ function bench(over?: BenchOptions) { const plainMessageDraft = sendableDraft && !uploadsPending && !(over?.draft?.trimStart().startsWith('/') ?? false) const primaryLabel = primaryStops ? '停止生成' - : over?.continuation !== undefined && !sendableDraft && over.running !== true && over.subagent === undefined && !composerLocked + : over?.retryableFailure !== undefined && !sendableDraft && over.running !== true && over.subagent === undefined && !composerLocked ? '继续生成' : over?.running === true && steeringAvailable && !composerLocked && plainMessageDraft ? (over.busyEnter === 'steer' ? '插话发送' : '排队发送') @@ -266,7 +266,7 @@ function bench(over?: BenchOptions) { const interruptButton = view.container.querySelector('button[aria-label="停止生成"]') return { view, textarea, button, interruptButton, props, sink, shell, wiring: shell, session, stop, removeAttachment, slotCalls, - menuLauncher, busyEnter, stopShortcut, continuation, continueGeneration, + menuLauncher, busyEnter, stopShortcut, retryableFailure, retry, steerQueue: over?.steerQueue, get placeholder() { return placeholderOf(view.container) }, get inputDisabled() { return textarea.getAttribute('aria-disabled') === 'true' }, @@ -274,25 +274,25 @@ function bench(over?: BenchOptions) { } it('offers the play action only for an empty, idle composer with an eligible failure', async () => { - const b = bench({ continuation: 2 as SessionSeq }) + const b = bench({ retryableFailure: 2 as SessionSeq }) expect(b.button.disabled).toBe(false) expect(b.button.querySelector('svg')).not.toBeNull() fireEvent.click(b.button) - expect(b.continueGeneration).toHaveBeenCalledExactlyOnceWith(2) + expect(b.retry).toHaveBeenCalledExactlyOnceWith(2) expect(b.sink).not.toHaveBeenCalled() await act(async () => { b.shell.setDraft('A new instruction') }) const send = b.view.getByRole('button', { name: '发送消息' }) fireEvent.click(send) await vi.waitFor(() => { expect(b.sink).toHaveBeenCalledOnce() }) expect(b.sink.mock.calls[0]?.[0]).toBe('A new instruction') - expect(b.continueGeneration).toHaveBeenCalledOnce() + expect(b.retry).toHaveBeenCalledOnce() }) it.each([ { running: true }, { attachments: [{ kind: 'image' as const, id: 'image' as DraftAttachmentId, file: new File([], 'x.png'), previewUrl: 'blob:test' }] }, { blocked: { reason: 'Select a model' } }, { subagent: CONTINUABLE_CHILD }, { deniedEndpoints: ['session/prompt'] }, ])('keeps the continue action unavailable with %j', (options) => { - const b = bench({ ...options, continuation: 2 as SessionSeq }) + const b = bench({ ...options, retryableFailure: 2 as SessionSeq }) expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() }) diff --git a/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx b/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx index 1fbff8e852..7a29e6893b 100644 --- a/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx @@ -86,8 +86,8 @@ function mountBar(shell: SessionInputShell, over?: { running?: boolean; disabled })), toggleCommandMenu: vi.fn(), useBusyEnter: bindSnapshotSelector(createSnapshotStore<'queue' | 'steer'>('queue')), - continueGeneration: undefined, - useContinuation: bindSnapshotSelector(createSnapshotStore(undefined)), + retry: undefined, + useRetryableFailure: bindSnapshotSelector(createSnapshotStore(undefined)), useStopShortcut: bindSnapshotSelector(createSnapshotStore([])), useNotices: bindSnapshotSelector(shell.notices), useLexicon: bindSnapshotSelector(shell.lexicon), diff --git a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx index 9547120455..e9b4d81d54 100644 --- a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx @@ -202,8 +202,8 @@ async function scopedBench(register?: (inputTriggers: InputTriggerService) => vo useMenuLauncher: bindSnapshotSelector(controller.launcher), renderSlot: (() => null) as InputBarProps['renderSlot'], stop: vi.fn(), - continueGeneration: undefined, - useContinuation: bindSnapshotSelector(createSnapshotStore(undefined)), + retry: undefined, + useRetryableFailure: bindSnapshotSelector(createSnapshotStore(undefined)), useStopShortcut: bindSnapshotSelector(createSnapshotStore([])), t: makeTranslate(zh, commonZh), variant: 'composer', diff --git a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx index ab3dc07d76..711088df39 100644 --- a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx +++ b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx @@ -310,8 +310,8 @@ function mount( resolveDraftAttachments={() => []} toggleCommandMenu={vi.fn()} useBusyEnter={bindSnapshotSelector(createSnapshotStore<'queue' | 'steer'>('queue'))} - continueGeneration={undefined} - useContinuation={bindSnapshotSelector(createSnapshotStore(undefined))} + retry={undefined} + useRetryableFailure={bindSnapshotSelector(createSnapshotStore(undefined))} useStopShortcut={bindSnapshotSelector(createSnapshotStore([]))} useNotices={bindSnapshotSelector(wiring.notices)} useLexicon={bindSnapshotSelector(wiring.lexicon)} From bc98ceb69fe13ca45886c1deac99b9a797ac9222 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Tue, 3 Nov 2026 14:12:21 +0800 Subject: [PATCH 21/29] test(api): include retry eligibility in default session summaries --- .../tests/session-projections.host.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/api/session-controller/tests/session-projections.host.spec.ts b/packages/api/session-controller/tests/session-projections.host.spec.ts index 0138af80e7..aa4ae6a702 100644 --- a/packages/api/session-controller/tests/session-projections.host.spec.ts +++ b/packages/api/session-controller/tests/session-projections.host.spec.ts @@ -532,7 +532,7 @@ describe('session.list projections column', () => { expect(excludedApply).not.toHaveBeenCalled() }) - it('carries every registered summary key, which is only its own list metadata when no other unit is registered', async () => { + it('carries list metadata and unavailable retry eligibility through registered summary keys', async () => { const { ctx, session } = await harness(false) seedMessages(session, 1) const response = await (await remote(ctx)).list(request({})) @@ -540,7 +540,8 @@ describe('session.list projections column', () => { const row = response.value.items.find(item => item.sessionId === session.id) expect(row?.projections?.kind).toBe('sequenced') expect(Object.keys(row?.projections?.values ?? {})).toEqual([...ctx.sessionProjections.summaryKeys()]) - expect(Object.keys(row?.projections?.values ?? {})).toEqual(['sessionListMetadata']) + expect(Object.keys(row?.projections?.values ?? {})).toEqual(['conversationContinuation', 'sessionListMetadata']) + expect(row?.projections?.values.conversationContinuation).toBeNull() }) it('serves the summary projection hints from the cache with zero log loads', async () => { From ee20b6e9baa9d4989c0442632df9ac9a981d6600 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 4 Nov 2026 11:30:11 +0800 Subject: [PATCH 22/29] test(web): align continuation snapshot with context groups --- snapshots/web/live-interactions/continued.expected.md | 1 + 1 file changed, 1 insertion(+) diff --git a/snapshots/web/live-interactions/continued.expected.md b/snapshots/web/live-interactions/continued.expected.md index 063c0ced95..da8fc50a28 100644 --- a/snapshots/web/live-interactions/continued.expected.md +++ b/snapshots/web/live-interactions/continued.expected.md @@ -14,6 +14,7 @@ - button "Copy" - status: Failed - button "Failed" [disabled] [expanded] +- button "Injected context" - group: - status: Retried model request (2/2) · {{duration}} - status: From dde72c5af19dd54f01098cefd7a4532aa1f46a2d Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 4 Nov 2026 12:49:31 +0800 Subject: [PATCH 23/29] test: classify shared continuation fixture as client --- .../{continuation-fixture.ts => continuation-fixture.client.ts} | 0 packages/api/session-controller/tests/continuation.host.spec.ts | 2 +- .../client/ui-conversation/tests/apply-inject.client.spec.tsx | 2 +- tsconfig.client.json | 2 -- 4 files changed, 2 insertions(+), 4 deletions(-) rename packages/api/session-controller/tests/{continuation-fixture.ts => continuation-fixture.client.ts} (100%) diff --git a/packages/api/session-controller/tests/continuation-fixture.ts b/packages/api/session-controller/tests/continuation-fixture.client.ts similarity index 100% rename from packages/api/session-controller/tests/continuation-fixture.ts rename to packages/api/session-controller/tests/continuation-fixture.client.ts diff --git a/packages/api/session-controller/tests/continuation.host.spec.ts b/packages/api/session-controller/tests/continuation.host.spec.ts index 1ea9f649da..869ae86cfd 100644 --- a/packages/api/session-controller/tests/continuation.host.spec.ts +++ b/packages/api/session-controller/tests/continuation.host.spec.ts @@ -8,7 +8,7 @@ import { expect, it, onTestFinished, vi } from 'vitest' import { continuationProjectionDefinition as definition } from '../src/continuation.ts' import { paginate } from '../src/history.ts' -import { continuationTurn as turn } from './continuation-fixture.ts' +import { continuationTurn as turn } from './continuation-fixture.client.ts' const failed = (code: string): TurnEndReason => ({ kind: 'error', error: { code, message: code } }) function fold(events: readonly SessionEvent[]) { diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index a550553195..56935d0601 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -30,7 +30,7 @@ import { createUserMessage, LlmAttemptId } from '@deepseek-ai/dsh-llm' import { MutableSessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' import type { SessionEventWindow } from '@deepseek-ai/dsh-api-session-controller/client' import type { InboxWireState } from '@deepseek-ai/dsh-agent/types' -import { continuationTurn } from '../../../api/session-controller/tests/continuation-fixture.ts' +import { continuationTurn } from '../../../api/session-controller/tests/continuation-fixture.client.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap { diff --git a/tsconfig.client.json b/tsconfig.client.json index 9de5492df5..0075d223e4 100644 --- a/tsconfig.client.json +++ b/tsconfig.client.json @@ -29,8 +29,6 @@ "packages/experimental/*/src/css-modules.d.ts", "packages/client/*/tests/**/*.ts", "packages/client/*/tests/**/*.tsx", - // Shared Session event data has no Host Context augmentation. - "packages/api/session-controller/tests/continuation-fixture.ts", "benchmarks/**/*.client.ts", "benchmarks/**/*.client.tsx", "benchmarks/support/**/*.ts", From b0279fa8ae239d0a59289bddb87f967ea3d6adc9 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 4 Nov 2026 12:53:54 +0800 Subject: [PATCH 24/29] test: share continuation fixture across compiler faces --- packages/api/session-controller/tests/continuation.host.spec.ts | 2 +- .../client/ui-conversation/tests/apply-inject.client.spec.tsx | 2 +- .../ui-conversation/tests/continuation-fixture.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/{api/session-controller/tests/continuation-fixture.client.ts => client/ui-conversation/tests/continuation-fixture.ts} (100%) diff --git a/packages/api/session-controller/tests/continuation.host.spec.ts b/packages/api/session-controller/tests/continuation.host.spec.ts index 869ae86cfd..e7d339761d 100644 --- a/packages/api/session-controller/tests/continuation.host.spec.ts +++ b/packages/api/session-controller/tests/continuation.host.spec.ts @@ -8,7 +8,7 @@ import { expect, it, onTestFinished, vi } from 'vitest' import { continuationProjectionDefinition as definition } from '../src/continuation.ts' import { paginate } from '../src/history.ts' -import { continuationTurn as turn } from './continuation-fixture.client.ts' +import { continuationTurn as turn } from '../../../client/ui-conversation/tests/continuation-fixture.ts' const failed = (code: string): TurnEndReason => ({ kind: 'error', error: { code, message: code } }) function fold(events: readonly SessionEvent[]) { diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index 56935d0601..4fb13c4011 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -30,7 +30,7 @@ import { createUserMessage, LlmAttemptId } from '@deepseek-ai/dsh-llm' import { MutableSessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' import type { SessionEventWindow } from '@deepseek-ai/dsh-api-session-controller/client' import type { InboxWireState } from '@deepseek-ai/dsh-agent/types' -import { continuationTurn } from '../../../api/session-controller/tests/continuation-fixture.client.ts' +import { continuationTurn } from './continuation-fixture.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap { diff --git a/packages/api/session-controller/tests/continuation-fixture.client.ts b/packages/client/ui-conversation/tests/continuation-fixture.ts similarity index 100% rename from packages/api/session-controller/tests/continuation-fixture.client.ts rename to packages/client/ui-conversation/tests/continuation-fixture.ts From 8d8684dc0fe4010b233e36ad594f1c948a834480 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 4 Nov 2026 13:02:38 +0800 Subject: [PATCH 25/29] refactor(conversation): remove redundant retry source cache --- .../ui-conversation/src/client/input/hub.ts | 34 ++++++++----------- .../tests/apply-inject.client.spec.tsx | 1 - 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/packages/client/ui-conversation/src/client/input/hub.ts b/packages/client/ui-conversation/src/client/input/hub.ts index 0c697803d0..a43e5852b9 100644 --- a/packages/client/ui-conversation/src/client/input/hub.ts +++ b/packages/client/ui-conversation/src/client/input/hub.ts @@ -57,7 +57,6 @@ interface ConversationAttachmentFace { /** Session-addressed input facade registry (SessionInputResolver face + composer-layer extras). */ export class InputHub implements SessionInputResolver { private readonly shells = new WeakMap() - private readonly retryableFailures = new WeakMap>() /** * @param ctx - client root context (services resolved lazily per call — boot order stays free). @@ -211,30 +210,25 @@ export class InputHub implements SessionInputResolver { /** * Read the Host-projected retryable failure while excluding pending human steering. * @param id - retained ordinary Session identity. - * @returns an identity-stable source whose value is the failed turn/end sequence or undefined. + * @returns a source whose value is the failed turn/end sequence or undefined. */ retryableFailure(id: SessionId): ObservableSnapshot { const binding = this.sessions().binding(id) if (binding === undefined) throw new Error(`conversation.input: session "${id}" resolved no binding`) - let source = this.retryableFailures.get(binding) - if (source === undefined) { - const ending = binding.session.projections.faceOf('conversationContinuation') - const inbox = binding.session.projections.faceOf('inbox') - source = { - getSnapshot: () => { - const pending = inbox.getSnapshot() as InboxState | undefined - return pending?.['next-step'].some(message => message.source.kind === 'user') - ? undefined : (ending.getSnapshot() as SessionSeq | null | undefined) ?? undefined - }, - subscribe: (listener) => { - const unsubscribeEnding = ending.subscribe(listener) - const unsubscribeInbox = inbox.subscribe(listener) - return () => { unsubscribeInbox(); unsubscribeEnding() } - }, - } - this.retryableFailures.set(binding, source) + const ending = binding.session.projections.faceOf('conversationContinuation') + const inbox = binding.session.projections.faceOf('inbox') + return { + getSnapshot: () => { + const pending = inbox.getSnapshot() as InboxState | undefined + return pending?.['next-step'].some(message => message.source.kind === 'user') + ? undefined : (ending.getSnapshot() as SessionSeq | null | undefined) ?? undefined + }, + subscribe: (listener) => { + const unsubscribeEnding = ending.subscribe(listener) + const unsubscribeInbox = inbox.subscribe(listener) + return () => { unsubscribeInbox(); unsubscribeEnding() } + }, } - return source } /** diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index 4fb13c4011..d9fc076ef4 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -174,7 +174,6 @@ describe('Conversation inject API', () => { await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } })) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) const composer = b.composerApi(ROOT) - expect(b.composerApi(ROOT).hooks.retryableFailure).toBe(composer.hooks.retryableFailure) expect(composer.hooks.retryableFailure.getSnapshot()).toBe(5) b.runtime.ctx.locale.setLocale(locale) const pending = Promise.withResolvers>>() From 399fe74c1e041580dcb46f92080ede39bcd6b4d6 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 4 Nov 2026 13:27:41 +0800 Subject: [PATCH 26/29] refactor(conversation): read retry state through existing projections --- .../client/ui-conversation/README.i18n.yaml | 4 +- packages/client/ui-conversation/README.md | 2 +- packages/client/ui-conversation/README.zh.md | 2 +- .../ui-conversation/src/client/apply.ts | 6 --- .../src/client/contract/slots.ts | 2 - .../ui-conversation/src/client/input/hub.ts | 28 ++---------- .../src/client/skeleton/InputBar.tsx | 10 +++-- .../tests/apply-inject.client.spec.tsx | 13 +----- .../tests/input-bar.client.spec.tsx | 43 ++++++++++++++----- .../tests/input-matrix.client.spec.tsx | 1 - .../tests/input-scenarios.client.spec.tsx | 1 - .../tests/skeleton.client.spec.tsx | 1 - 12 files changed, 49 insertions(+), 64 deletions(-) diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index 3ff43c9220..f5821391a8 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -21,8 +21,8 @@ en: cc4dafd1aae02468 zh: be6b67724b7ac4a3 /deepseek-ai-dsh-client-ui-conversation/shell-and-standard-props: - en: cd2f687db450c567 - zh: 0de015eca3c97206 + en: 8994069b62a88825 + zh: 262ace7ad9bd35b7 /deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries: en: 59cd63057dfb6eeb zh: b2e2c86e810b7bb8 diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 6a172289eb..d9c67692b0 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -84,7 +84,7 @@ An empty, idle ordinary Session composer offers a play action after `RATE_LIMIT` The Session API owns `conversationContinuation`, shared by Host admission and browser presentation. It restores eligibility from complete Session history and preserves it across repeated failed empty continuations, independently of loaded browser pages. -The composer exposes `retry(failureSeq)` for failed-generation recovery and `useRetryableFailure` for the latest eligible failed `turn/end` sequence or `undefined`. User-facing text calls the action Continue generating. +The composer exposes `retry(failureSeq)` for failed-generation recovery. The input bar reads `conversationContinuation` and `inbox` through the standard `useProjection` prop. User-facing text calls the action Continue generating. The composer uses the shared [text-input focus policy](../ui-primitives/README.md#use-this-package) on mount, Session switching, and unlock. Identified phones and tablets require tapping the input; desktop devices retain automatic focus. On these mobile devices, a submission that empties the focused draft moves focus to the non-text composer card, dismissing the keyboard while retaining its navigation position. Rejected submissions, retained newer draft text, and another control already holding focus preserve focus. The [Lexical patch](../../../patches/lexical@0.49.0.patch) keeps caret scrolling in the same coordinate system as DOM geometry when a mobile keyboard pans the page. diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index 7ad492b084..c9d1b93019 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -84,7 +84,7 @@ root 作用域的 hoc Slot `conversation.hero.frame` 在所有阶段都包裹滚 Session API 拥有 `conversationContinuation`,Host 准入和浏览器展示共用该投影。它从完整 Session 历史恢复资格,并在空输入继续再次失败时保留资格,不依赖浏览器已加载的历史分页。 -composer 提供 `retry(failureSeq)` 处理生成失败后的恢复,通过 `useRetryableFailure` 读取最新符合条件的失败 `turn/end` 序号或 `undefined`。面向用户的操作文案为“继续生成”。 +composer 提供 `retry(failureSeq)` 处理生成失败后的恢复。输入栏通过标准 `useProjection` prop 读取 `conversationContinuation` 和 `inbox`。面向用户的操作文案为“继续生成”。 composer 在挂载、切换 Session 和解除锁定时使用共享的[文字输入框聚焦策略](../ui-primitives/README.zh.md#use-this-package)。识别出的手机和平板需要点击输入框;桌面设备保留自动聚焦。在这些移动设备上,提交清空正在聚焦的草稿后,焦点移到不接受文字输入的 composer 卡片,收起键盘并保留键盘导航位置。被拒绝的提交、保留的新草稿文字,以及已经聚焦的其他控件均保留原有焦点。移动端键盘平移页面时,[Lexical 补丁](../../../patches/lexical@0.49.0.patch)让光标滚动计算与 DOM 几何使用同一坐标系。 diff --git a/packages/client/ui-conversation/src/client/apply.ts b/packages/client/ui-conversation/src/client/apply.ts index 32c4b9511d..aae71928be 100644 --- a/packages/client/ui-conversation/src/client/apply.ts +++ b/packages/client/ui-conversation/src/client/apply.ts @@ -79,10 +79,6 @@ const ABSENT_NOTICES = { getSnapshot: (): InputNotice | null => null, subscribe: () => () => {}, } -const ABSENT_RETRYABLE_FAILURE = { - getSnapshot: () => undefined, - subscribe: () => () => {}, -} const ABSENT_BLOCK = { getSnapshot: (): ComposerBlock | undefined => undefined, subscribe: () => () => {}, @@ -490,7 +486,6 @@ export function apply(ctx: Context, config: Config = Config({})): void { stop: undefined, retry: undefined, hooks: { - retryableFailure: ABSENT_RETRYABLE_FAILURE, stopShortcut, busyEnter: submissionPolicy.busyEnter, fileUploads: ABSENT_FILE_UPLOADS, @@ -571,7 +566,6 @@ export function apply(ctx: Context, config: Config = Config({})): void { stop: () => { stop(sessionId) }, retry: (failureSeq) => { inputHub.retry(sessionId, failureSeq) }, hooks: { - retryableFailure: inputHub.retryableFailure(sessionId), stopShortcut, busyEnter: submissionPolicy.busyEnter, fileUploads: conversation.fileUploads, diff --git a/packages/client/ui-conversation/src/client/contract/slots.ts b/packages/client/ui-conversation/src/client/contract/slots.ts index fc5e2eb683..5ce7e7efb7 100644 --- a/packages/client/ui-conversation/src/client/contract/slots.ts +++ b/packages/client/ui-conversation/src/client/contract/slots.ts @@ -574,8 +574,6 @@ export interface ComposerBarInjected { */ retry: ((failureSeq: SessionSeq) => void) | undefined hooks: { - /** Latest retryable failed turn/end sequence; undefined while unavailable or human steering is pending. */ - retryableFailure: ObservableSnapshot /** Readable sequence while the fixed Stop command is registered. */ stopShortcut: ObservableSnapshot /** diff --git a/packages/client/ui-conversation/src/client/input/hub.ts b/packages/client/ui-conversation/src/client/input/hub.ts index a43e5852b9..f6e9ce4183 100644 --- a/packages/client/ui-conversation/src/client/input/hub.ts +++ b/packages/client/ui-conversation/src/client/input/hub.ts @@ -207,30 +207,6 @@ export class InputHub implements SessionInputResolver { return this.shell(id) } - /** - * Read the Host-projected retryable failure while excluding pending human steering. - * @param id - retained ordinary Session identity. - * @returns a source whose value is the failed turn/end sequence or undefined. - */ - retryableFailure(id: SessionId): ObservableSnapshot { - const binding = this.sessions().binding(id) - if (binding === undefined) throw new Error(`conversation.input: session "${id}" resolved no binding`) - const ending = binding.session.projections.faceOf('conversationContinuation') - const inbox = binding.session.projections.faceOf('inbox') - return { - getSnapshot: () => { - const pending = inbox.getSnapshot() as InboxState | undefined - return pending?.['next-step'].some(message => message.source.kind === 'user') - ? undefined : (ending.getSnapshot() as SessionSeq | null | undefined) ?? undefined - }, - subscribe: (listener) => { - const unsubscribeEnding = ending.subscribe(listener) - const unsubscribeInbox = inbox.subscribe(listener) - return () => { unsubscribeInbox(); unsubscribeEnding() } - }, - } - } - /** * Submit empty input to retry a failed generation through the ordinary composer. Stale * failures, active work, and drafts keep their current input unchanged. @@ -239,7 +215,8 @@ export class InputHub implements SessionInputResolver { */ retry(id: SessionId, failureSeq: SessionSeq): void { const binding = this.sessions().binding(id) - if (binding === undefined || this.retryableFailure(id).getSnapshot() !== failureSeq) return + if (binding === undefined + || binding.session.projections.faceOf('conversationContinuation').getSnapshot() !== failureSeq) return const session = binding.session.getSnapshot() const shell = this.shellFor(binding) const input = shell.snapshot @@ -248,6 +225,7 @@ export class InputHub implements SessionInputResolver { || session.pendingSubmissions.length > 0 || input.phase !== 'plain' || input.draft.trim() !== '' || input.attachmentIds.length > 0 || (inbox?.['next-turn'].length ?? 0) > 0 + || inbox?.['next-step'].some(message => message.source.kind === 'user') || this.conversation().blocks.storeFor(id).getSnapshot() !== undefined) return shell.submit('queue', 'click', true) } diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx index f1680c7919..859dabf79d 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx @@ -16,6 +16,7 @@ import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import type { ChangeEvent, KeyboardEvent, MouseEvent } from 'react' import clsx from 'clsx' +import { isRecord } from '@deepseek-ai/dsh-util-values' import { focusWithoutRing, IconPlusOutlineMedium, IconTriangleRightFillRegular, IconWarningOutlineRegular, isMobileDevice, Toast, Tooltip, } from '@deepseek-ai/dsh-client-ui-primitives' @@ -48,7 +49,7 @@ export const InputBar = memo(function InputBar({ retryFileUpload, toggleCommandMenu, stop, retry, t, renderSlot, useBusyEnter, useFileUploads, useNotices, useLexicon, useMenuLauncher, useStopShortcut, - useAccess, useRetryableFailure, + useAccess, useProjection, sessionId, variant, disabled: inert = false, blocked, workspacePickerOpen = false, onRequestWorkspace, placeholder, accessory, @@ -70,7 +71,10 @@ export const InputBar = memo(function InputBar({ useEffect(() => { setActivity(false) }, [sessionId]) const promptError = useSession(s => s.promptError) ?? null const running = useSession(s => s.running) ?? false - const retryableFailure = useRetryableFailure(value => value) + const retryableFailure = useProjection('conversationContinuation') ?? undefined + const pendingHumanInput = useProjection('inbox', inbox => + inbox?.['next-step'].some(message => + isRecord(message) && isRecord(message.source) && message.source.kind === 'user') ?? false) const historyOpen = useSession(s => s.openState === 'open') ?? false const submissionPending = useSession(s => s.pendingSubmissions.length > 0) ?? false const subagent = useSession(s => s.subagent) ?? null @@ -359,7 +363,7 @@ export const InputBar = memo(function InputBar({ // message waits for the Host. const primaryStops = running && subagent === null && !sending && (empty || blocked !== undefined) const primaryRetries = !running && subagent === null && empty && retryableFailure !== undefined - && historyOpen && !submissionPending && !disabled && input.queue.length === 0 + && !pendingHumanInput && historyOpen && !submissionPending && !disabled && input.queue.length === 0 && input.phase === 'plain' && retry !== undefined // Disabled native buttons may omit mouseleave; their tooltip must close from state. const primaryDisabled = primaryStops ? stopAction === undefined diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index d9fc076ef4..24d1ae466f 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -174,7 +174,6 @@ describe('Conversation inject API', () => { await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } })) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) const composer = b.composerApi(ROOT) - expect(composer.hooks.retryableFailure.getSnapshot()).toBe(5) b.runtime.ctx.locale.setLocale(locale) const pending = Promise.withResolvers>>() onTestFinished(() => { pending.resolve({ ok: true, value: { accepted: true } }) }) @@ -219,25 +218,17 @@ describe('Conversation inject API', () => { onTestFinished(() => b.runtime.dispose()) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) const composer = b.composerApi(ROOT) - const source = composer.hooks.retryableFailure - const notified = vi.fn() - const unsubscribe = source.subscribe(notified) - onTestFinished(unsubscribe) const context = { id: 'context', role: 'user', content: [{ type: 'text', text: 'approval policy context' }], source: { kind: 'user-approval' } } const inbox: InboxWireState = { 'next-turn': [], 'next-step': [context] } await b.runtime.sessions.setProjection(ROOT, 'inbox', inbox) - expect(source.getSnapshot()).toBe(5) const user = createUserMessage({ content: [{ type: 'text', text: 'new instruction' }], source: { kind: 'user' } }) await b.runtime.sessions.setProjection(ROOT, 'inbox', { ...inbox, 'next-step': [context, user] }) - expect(source.getSnapshot()).toBeUndefined() composer.retry!(SessionSeq(5)) expect(b.sessionFake.prompt).not.toHaveBeenCalled() await b.runtime.sessions.setProjection(ROOT, 'inbox', inbox) - expect(source.getSnapshot()).toBe(5) composer.retry!(SessionSeq(5)) await vi.waitFor(() => { expect(b.sessionFake.prompt).toHaveBeenCalledOnce() }) expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([]) - expect(notified).toHaveBeenCalledTimes(3) }) it('uses the Host projection on partial pages and does no history materialization for streaming chunks', async () => { @@ -245,7 +236,7 @@ describe('Conversation inject API', () => { onTestFinished(() => b.runtime.dispose()) await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } }).slice(1), true) await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) - const source = b.composerApi(ROOT).hooks.retryableFailure + const source = b.runtime.sessions.binding(ROOT)!.session.projections.faceOf('conversationContinuation') expect(source.getSnapshot()).toBe(5) const history = Array.from({ length: 1666 }, (_, index) => continuationTurn({ kind: 'completed' }, index + 1)).flat() history.push(...continuationTurn({ kind: 'completed' }, 1667).slice(0, 4)) @@ -276,7 +267,7 @@ describe('Conversation inject API', () => { feed.append({ type: 'transient', event: { type: 'assistant/live-chunk', seq: 9999.5, time: index, data: { attemptId: LlmAttemptId('streaming'), turn: 1667, step: 1, chunk: { type: 'text-delta', index: 0, text: 'x' } }, } }) - expect(source.getSnapshot()).toBeUndefined() + expect(source.getSnapshot()).toBeNull() } expect(materializations).toBe(0) expect(notified).not.toHaveBeenCalled() diff --git a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx index 54989e8707..1b239a058a 100644 --- a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx @@ -139,14 +139,15 @@ function bench(over?: BenchOptions) { promptError: over?.promptError ?? null, })) type ShellDeps = ConstructorParameters[0] + const inbox = createSnapshotStore({ + 'next-turn': over?.queue ?? [], + 'next-step': over?.nextStep ?? [], + }) const shell = new SessionInputShell({ actx: SCTX, defaultSink: sink, commandAttachments: { serialize: () => Promise.resolve([]), release: () => {}, unsupportedNotice: (token: string) => `${token.trim()} attachments-unsupported` }, - inbox: createSnapshotStore({ - 'next-turn': over?.queue ?? [], - 'next-step': over?.nextStep ?? [], - }), + inbox, ...(over?.steerQueue !== undefined ? { steerQueue: over.steerQueue } : {}), // Lexicon-only stub: adjudication untouched (undefined slash methods are // never reached — these benches drive plain-draft flows only). @@ -163,7 +164,9 @@ function bench(over?: BenchOptions) { if (over?.attachments !== undefined) shell.addAttachments(over.attachments.map(attachment => attachment.id)) const stop = vi.fn() const retry = vi.fn() - const retryableFailure = createSnapshotStore(over?.retryableFailure) + const continuation = createSnapshotStore(over?.retryableFailure ?? null) + const useContinuation = bindSnapshotSelector(continuation) + const useInbox = bindSnapshotSelector(inbox) const removeAttachment = vi.fn((id: DraftAttachmentId) => { shell.removeAttachment(id) }) const menuLauncher = createSnapshotStore(over?.commandMenuOpen === true ? 'command' : null) const busyEnter = createSnapshotStore<'queue' | 'steer'>(over?.busyEnter ?? 'queue') @@ -200,12 +203,15 @@ function bench(over?: BenchOptions) { ...allowAllAccess, useContextMenu: contextMenuHook(), useAccess: endpoint => !(over?.deniedEndpoints ?? []).includes(endpoint), - useProjection: ((key: string, selector?: (v: unknown) => unknown) => - (selector ?? (v => v))(key === 'plan' + useProjection: ((key: string, selector: (v: unknown) => unknown = v => v) => { + if (key === 'conversationContinuation') return useContinuation(selector) + if (key === 'inbox') return useInbox(selector) + return selector(key === 'plan' ? over?.plan : key === 'goal' ? over?.goal : key === 'imageLimits' ? over?.imageLimits - : key === 'contextPressure' ? over?.contextPressure : undefined)), + : key === 'contextPressure' ? over?.contextPressure : undefined) + }), useInput: bindSnapshotSelector(shell.state), inputActions: shell.actions, keyboard: shell, @@ -220,7 +226,6 @@ function bench(over?: BenchOptions) { toggleCommandMenu: over?.toggleCommandMenu ?? vi.fn(), useBusyEnter: bindSnapshotSelector(busyEnter), retry, - useRetryableFailure: bindSnapshotSelector(retryableFailure), useStopShortcut: bindSnapshotSelector(stopShortcut), useNotices: bindSnapshotSelector(shell.notices), useLexicon: bindSnapshotSelector(shell.lexicon), @@ -258,6 +263,7 @@ function bench(over?: BenchOptions) { const primaryLabel = primaryStops ? '停止生成' : over?.retryableFailure !== undefined && !sendableDraft && over.running !== true && over.subagent === undefined && !composerLocked + && !(over.nextStep ?? []).some(message => message.source.kind === 'user') ? '继续生成' : over?.running === true && steeringAvailable && !composerLocked && plainMessageDraft ? (over.busyEnter === 'steer' ? '插话发送' : '排队发送') @@ -266,7 +272,7 @@ function bench(over?: BenchOptions) { const interruptButton = view.container.querySelector('button[aria-label="停止生成"]') return { view, textarea, button, interruptButton, props, sink, shell, wiring: shell, session, stop, removeAttachment, slotCalls, - menuLauncher, busyEnter, stopShortcut, retryableFailure, retry, + menuLauncher, busyEnter, stopShortcut, continuation, inbox, retry, steerQueue: over?.steerQueue, get placeholder() { return placeholderOf(view.container) }, get inputDisabled() { return textarea.getAttribute('aria-disabled') === 'true' }, @@ -288,6 +294,23 @@ it('offers the play action only for an empty, idle composer with an eligible fai expect(b.retry).toHaveBeenCalledOnce() }) +it('updates the continue action from failure and pending-input projections', async () => { + const b = bench() + expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() + await act(async () => { b.continuation.set(2 as SessionSeq) }) + expect(b.view.getByRole('button', { name: '继续生成' }).getAttribute('disabled')).toBeNull() + const context = { ...row('context'), source: { kind: 'user-approval' as const } } + await act(async () => { b.inbox.set({ 'next-turn': [], 'next-step': [context] }) }) + expect(b.view.getByRole('button', { name: '继续生成' })).not.toBeNull() + await act(async () => { b.inbox.set({ 'next-turn': [], 'next-step': [context, row('new instruction')] }) }) + expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() + await act(async () => { b.inbox.set({ 'next-turn': [], 'next-step': [context] }) }) + fireEvent.click(b.view.getByRole('button', { name: '继续生成' })) + expect(b.retry).toHaveBeenCalledExactlyOnceWith(2) + await act(async () => { b.continuation.set(null) }) + expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() +}) + it.each([ { running: true }, { attachments: [{ kind: 'image' as const, id: 'image' as DraftAttachmentId, file: new File([], 'x.png'), previewUrl: 'blob:test' }] }, { blocked: { reason: 'Select a model' } }, { subagent: CONTINUABLE_CHILD }, { deniedEndpoints: ['session/prompt'] }, diff --git a/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx b/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx index 7a29e6893b..a4963d15ac 100644 --- a/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-matrix.client.spec.tsx @@ -87,7 +87,6 @@ function mountBar(shell: SessionInputShell, over?: { running?: boolean; disabled toggleCommandMenu: vi.fn(), useBusyEnter: bindSnapshotSelector(createSnapshotStore<'queue' | 'steer'>('queue')), retry: undefined, - useRetryableFailure: bindSnapshotSelector(createSnapshotStore(undefined)), useStopShortcut: bindSnapshotSelector(createSnapshotStore([])), useNotices: bindSnapshotSelector(shell.notices), useLexicon: bindSnapshotSelector(shell.lexicon), diff --git a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx index e9b4d81d54..82c9ba4d56 100644 --- a/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx @@ -203,7 +203,6 @@ async function scopedBench(register?: (inputTriggers: InputTriggerService) => vo renderSlot: (() => null) as InputBarProps['renderSlot'], stop: vi.fn(), retry: undefined, - useRetryableFailure: bindSnapshotSelector(createSnapshotStore(undefined)), useStopShortcut: bindSnapshotSelector(createSnapshotStore([])), t: makeTranslate(zh, commonZh), variant: 'composer', diff --git a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx index 4ed370f28e..ff9f49b18c 100644 --- a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx +++ b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx @@ -316,7 +316,6 @@ function mount( toggleCommandMenu={vi.fn()} useBusyEnter={bindSnapshotSelector(createSnapshotStore<'queue' | 'steer'>('queue'))} retry={undefined} - useRetryableFailure={bindSnapshotSelector(createSnapshotStore(undefined))} useStopShortcut={bindSnapshotSelector(createSnapshotStore([]))} useNotices={bindSnapshotSelector(wiring.notices)} useLexicon={bindSnapshotSelector(wiring.lexicon)} From ee28dc8d1cd29472ef424a46a352df702a0e7f24 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 4 Nov 2026 13:30:44 +0800 Subject: [PATCH 27/29] fix(conversation): narrow pending inbox message fields --- .../client/ui-conversation/src/client/skeleton/InputBar.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx index 859dabf79d..5b6407242d 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx @@ -16,7 +16,6 @@ import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import type { ChangeEvent, KeyboardEvent, MouseEvent } from 'react' import clsx from 'clsx' -import { isRecord } from '@deepseek-ai/dsh-util-values' import { focusWithoutRing, IconPlusOutlineMedium, IconTriangleRightFillRegular, IconWarningOutlineRegular, isMobileDevice, Toast, Tooltip, } from '@deepseek-ai/dsh-client-ui-primitives' @@ -74,7 +73,9 @@ export const InputBar = memo(function InputBar({ const retryableFailure = useProjection('conversationContinuation') ?? undefined const pendingHumanInput = useProjection('inbox', inbox => inbox?.['next-step'].some(message => - isRecord(message) && isRecord(message.source) && message.source.kind === 'user') ?? false) + typeof message === 'object' && message !== null && 'source' in message + && typeof message.source === 'object' && message.source !== null && 'kind' in message.source + && message.source.kind === 'user') ?? false) const historyOpen = useSession(s => s.openState === 'open') ?? false const submissionPending = useSession(s => s.pendingSubmissions.length > 0) ?? false const subagent = useSession(s => s.subagent) ?? null From 4e56134fec5b7c736b881a2c1742e847a18fab2a Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 4 Nov 2026 13:34:03 +0800 Subject: [PATCH 28/29] test(conversation): keep injected context fixture client-safe --- .../ui-conversation/tests/input-bar.client.spec.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx index 1b239a058a..edc59834b9 100644 --- a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx @@ -38,6 +38,12 @@ import { InputBar } from '../src/client/skeleton/InputBar.tsx' import type { InputBarProps } from '../src/client/skeleton/InputBar.tsx' import { en, zh } from '../src/client/locales.ts' +declare module '@deepseek-ai/dsh-llm' { + interface MessageSourceMap { + 'input-bar-context-fixture': { kind: 'input-bar-context-fixture' } + } +} + // Every fixture carries the resource hook the resources plugin merges into GlobalStandardProps. const useResource = (() => ({ status: 'none' as const, value: undefined, failure: undefined })) as GlobalStandardProps['useResource'] @@ -299,7 +305,7 @@ it('updates the continue action from failure and pending-input projections', asy expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() await act(async () => { b.continuation.set(2 as SessionSeq) }) expect(b.view.getByRole('button', { name: '继续生成' }).getAttribute('disabled')).toBeNull() - const context = { ...row('context'), source: { kind: 'user-approval' as const } } + const context = { ...row('context'), source: { kind: 'input-bar-context-fixture' as const } } await act(async () => { b.inbox.set({ 'next-turn': [], 'next-step': [context] }) }) expect(b.view.getByRole('button', { name: '继续生成' })).not.toBeNull() await act(async () => { b.inbox.set({ 'next-turn': [], 'next-step': [context, row('new instruction')] }) }) From 14d3a091b8896500707af4ab3d0966b902b04f9e Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 4 Nov 2026 13:49:59 +0800 Subject: [PATCH 29/29] test(conversation): remove redundant retry test scaffolding --- .../tests/apply-inject.client.spec.tsx | 52 +------------------ .../tests/input-bar.client.spec.tsx | 51 +++++------------- 2 files changed, 13 insertions(+), 90 deletions(-) diff --git a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx index 24d1ae466f..e6384343ef 100644 --- a/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.client.spec.tsx @@ -26,9 +26,7 @@ import { createConversationStore } from '../src/client/stores.ts' import type { SessionInputShell } from '../src/client/input/facade.ts' import { RemoteError } from '@deepseek-ai/dsh-client-test-runtime' import { SessionSeq } from '@deepseek-ai/dsh-session/types' -import { createUserMessage, LlmAttemptId } from '@deepseek-ai/dsh-llm' -import { MutableSessionEventSource } from '@deepseek-ai/dsh-api-session-controller/client' -import type { SessionEventWindow } from '@deepseek-ai/dsh-api-session-controller/client' +import { createUserMessage } from '@deepseek-ai/dsh-llm' import type { InboxWireState } from '@deepseek-ai/dsh-agent/types' import { continuationTurn } from './continuation-fixture.ts' @@ -231,54 +229,6 @@ describe('Conversation inject API', () => { expect(b.sessionFake.prompt.mock.calls[0]?.[0]).toEqual([]) }) - it('uses the Host projection on partial pages and does no history materialization for streaming chunks', async () => { - const b = await bench() - onTestFinished(() => b.runtime.dispose()) - await b.runtime.sessions.replaceEvents(ROOT, continuationTurn({ kind: 'error', error: { code: 'SERVER', message: 'busy' } }).slice(1), true) - await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(5)) - const source = b.runtime.sessions.binding(ROOT)!.session.projections.faceOf('conversationContinuation') - expect(source.getSnapshot()).toBe(5) - const history = Array.from({ length: 1666 }, (_, index) => continuationTurn({ kind: 'completed' }, index + 1)).flat() - history.push(...continuationTurn({ kind: 'completed' }, 1667).slice(0, 4)) - await b.runtime.sessions.replaceEvents(ROOT, history) - await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', null) - const feed = b.runtime.sessions.binding(ROOT)!.eventSource - if (!(feed instanceof MutableSessionEventSource)) throw new Error('expected mutable fixture feed') - const rawSnapshot = feed.getSnapshot.bind(feed) - const windows = new WeakMap() - let materializations = 0 - const read = vi.spyOn(feed, 'getSnapshot').mockImplementation(() => { - const window = rawSnapshot() - let observed = windows.get(window) - if (observed === undefined) { - observed = { - get entries() { materializations++; return window.entries }, - hasMore: window.hasMore, revision: window.revision, change: window.change, - } - windows.set(window, observed) - } - return observed - }) - onTestFinished(() => { read.mockRestore() }) - const notified = vi.fn() - const unsubscribe = source.subscribe(notified) - onTestFinished(unsubscribe) - for (let index = 0; index < 2000; index++) { - feed.append({ type: 'transient', event: { type: 'assistant/live-chunk', seq: 9999.5, time: index, - data: { attemptId: LlmAttemptId('streaming'), turn: 1667, step: 1, chunk: { type: 'text-delta', index: 0, text: 'x' } }, - } }) - expect(source.getSnapshot()).toBeNull() - } - expect(materializations).toBe(0) - expect(notified).not.toHaveBeenCalled() - await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', SessionSeq(10001)) - expect(notified).toHaveBeenCalledOnce() - expect(source.getSnapshot()).toBe(10001) - unsubscribe() - await b.runtime.sessions.setProjection(ROOT, 'conversationContinuation', null) - expect(notified).toHaveBeenCalledOnce() - }) - it.each(['next-turn', 'pending', 'block', 'attachment', 'subagent', 'removed', 'closed'] as const)( 'rejects a stale continuation click after %s changes', async (condition) => { const b = await bench() diff --git a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx index edc59834b9..69499cdff4 100644 --- a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx +++ b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx @@ -38,12 +38,6 @@ import { InputBar } from '../src/client/skeleton/InputBar.tsx' import type { InputBarProps } from '../src/client/skeleton/InputBar.tsx' import { en, zh } from '../src/client/locales.ts' -declare module '@deepseek-ai/dsh-llm' { - interface MessageSourceMap { - 'input-bar-context-fixture': { kind: 'input-bar-context-fixture' } - } -} - // Every fixture carries the resource hook the resources plugin merges into GlobalStandardProps. const useResource = (() => ({ status: 'none' as const, value: undefined, failure: undefined })) as GlobalStandardProps['useResource'] @@ -145,15 +139,14 @@ function bench(over?: BenchOptions) { promptError: over?.promptError ?? null, })) type ShellDeps = ConstructorParameters[0] - const inbox = createSnapshotStore({ - 'next-turn': over?.queue ?? [], - 'next-step': over?.nextStep ?? [], - }) const shell = new SessionInputShell({ actx: SCTX, defaultSink: sink, commandAttachments: { serialize: () => Promise.resolve([]), release: () => {}, unsupportedNotice: (token: string) => `${token.trim()} attachments-unsupported` }, - inbox, + inbox: createSnapshotStore({ + 'next-turn': over?.queue ?? [], + 'next-step': over?.nextStep ?? [], + }), ...(over?.steerQueue !== undefined ? { steerQueue: over.steerQueue } : {}), // Lexicon-only stub: adjudication untouched (undefined slash methods are // never reached — these benches drive plain-draft flows only). @@ -170,9 +163,6 @@ function bench(over?: BenchOptions) { if (over?.attachments !== undefined) shell.addAttachments(over.attachments.map(attachment => attachment.id)) const stop = vi.fn() const retry = vi.fn() - const continuation = createSnapshotStore(over?.retryableFailure ?? null) - const useContinuation = bindSnapshotSelector(continuation) - const useInbox = bindSnapshotSelector(inbox) const removeAttachment = vi.fn((id: DraftAttachmentId) => { shell.removeAttachment(id) }) const menuLauncher = createSnapshotStore(over?.commandMenuOpen === true ? 'command' : null) const busyEnter = createSnapshotStore<'queue' | 'steer'>(over?.busyEnter ?? 'queue') @@ -209,15 +199,14 @@ function bench(over?: BenchOptions) { ...allowAllAccess, useContextMenu: contextMenuHook(), useAccess: endpoint => !(over?.deniedEndpoints ?? []).includes(endpoint), - useProjection: ((key: string, selector: (v: unknown) => unknown = v => v) => { - if (key === 'conversationContinuation') return useContinuation(selector) - if (key === 'inbox') return useInbox(selector) - return selector(key === 'plan' + useProjection: ((key: string, selector: (v: unknown) => unknown = v => v) => + selector(key === 'plan' ? over?.plan : key === 'goal' ? over?.goal - : key === 'imageLimits' ? over?.imageLimits - : key === 'contextPressure' ? over?.contextPressure : undefined) - }), + : key === 'conversationContinuation' ? over?.retryableFailure + : key === 'inbox' ? { 'next-turn': over?.queue ?? [], 'next-step': over?.nextStep ?? [] } + : key === 'imageLimits' ? over?.imageLimits + : key === 'contextPressure' ? over?.contextPressure : undefined)), useInput: bindSnapshotSelector(shell.state), inputActions: shell.actions, keyboard: shell, @@ -278,7 +267,7 @@ function bench(over?: BenchOptions) { const interruptButton = view.container.querySelector('button[aria-label="停止生成"]') return { view, textarea, button, interruptButton, props, sink, shell, wiring: shell, session, stop, removeAttachment, slotCalls, - menuLauncher, busyEnter, stopShortcut, continuation, inbox, retry, + menuLauncher, busyEnter, stopShortcut, retry, steerQueue: over?.steerQueue, get placeholder() { return placeholderOf(view.container) }, get inputDisabled() { return textarea.getAttribute('aria-disabled') === 'true' }, @@ -300,26 +289,10 @@ it('offers the play action only for an empty, idle composer with an eligible fai expect(b.retry).toHaveBeenCalledOnce() }) -it('updates the continue action from failure and pending-input projections', async () => { - const b = bench() - expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() - await act(async () => { b.continuation.set(2 as SessionSeq) }) - expect(b.view.getByRole('button', { name: '继续生成' }).getAttribute('disabled')).toBeNull() - const context = { ...row('context'), source: { kind: 'input-bar-context-fixture' as const } } - await act(async () => { b.inbox.set({ 'next-turn': [], 'next-step': [context] }) }) - expect(b.view.getByRole('button', { name: '继续生成' })).not.toBeNull() - await act(async () => { b.inbox.set({ 'next-turn': [], 'next-step': [context, row('new instruction')] }) }) - expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() - await act(async () => { b.inbox.set({ 'next-turn': [], 'next-step': [context] }) }) - fireEvent.click(b.view.getByRole('button', { name: '继续生成' })) - expect(b.retry).toHaveBeenCalledExactlyOnceWith(2) - await act(async () => { b.continuation.set(null) }) - expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull() -}) - it.each([ { running: true }, { attachments: [{ kind: 'image' as const, id: 'image' as DraftAttachmentId, file: new File([], 'x.png'), previewUrl: 'blob:test' }] }, { blocked: { reason: 'Select a model' } }, { subagent: CONTINUABLE_CHILD }, { deniedEndpoints: ['session/prompt'] }, + { nextStep: [row('new instruction')] }, ])('keeps the continue action unavailable with %j', (options) => { const b = bench({ ...options, retryableFailure: 2 as SessionSeq }) expect(b.view.queryByRole('button', { name: '继续生成' })).toBeNull()