diff --git a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md index f9768a652c..9952f8fac0 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md +++ b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md @@ -16,7 +16,7 @@ Business events also use different correlation models. Tool has call IDs, Assist Client Runtime provides a target-neutral Conversation Node assembly engine. Business plugins register Event Definitions, and view plugins register per-Session View Builders. `ui-conversation` registers the first built-in Definitions and the `chat` builder; Session only submits the current contiguous Event window to the engine and publishes its snapshot instead of interpreting individual conversation businesses. -The complete derivation, business-by-business validation, and file-level implementation plan remain in [`business-node assembler version one`](../../../../docs/client-conversation-node-engine-rfc.md), [`follow-up design differences`](../../../../docs/client-conversation-node-engine-follow-up-differences.md), [`business-node and dual-view adaptation analysis`](../../../../docs/client-conversation-node-adaptation-analysis.md), and the [`Chat implementation design`](../../../../docs/client-conversation-node-chat-implementation-plan.md). Those design documents retain the full discussion; this Note fixes the responsibilities, algorithms, and trade-offs that remain relevant after implementation. +This Note retains the derivation, business-by-business validation, responsibilities, algorithms, and trade-offs that remain relevant after implementation. ### Responsibility layers diff --git a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md index 408d61f73b..f27a82459c 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md @@ -16,7 +16,7 @@ Client Session 既维护传输窗口、连接状态和待处理交互,也在 Client Runtime 提供 target-neutral 的 Conversation Node 组装引擎,业务插件注册 Event Definition,视图插件注册 per-Session View Builder。`ui-conversation` 注册第一批内建 Definition 和 `chat` builder;Session 只负责把当前连续事件窗口送入引擎并发布它的 snapshot,不再解释具体 conversation 业务。 -详细的方案推导、逐业务适配和逐文件实施设计保留在 [`业务节点组装器第一版`](../../../../docs/client-conversation-node-engine-rfc.md)、[`后续方案差异`](../../../../docs/client-conversation-node-engine-follow-up-differences.md)、[`业务节点与双视图适配论证`](../../../../docs/client-conversation-node-adaptation-analysis.md) 和 [`Chat 链路工程实施设计`](../../../../docs/client-conversation-node-chat-implementation-plan.md)。这些设计稿保留完整讨论过程;本 Note 固定实现后仍需长期维护的职责、算法和取舍。 +本 Note 保留实现后仍有价值的方案推导、逐业务适配、职责、算法和取舍。 ### 责任分层 diff --git a/packages/client/runtime/package.json b/packages/client/runtime/package.json index 3176428b36..f5a9a06a60 100644 --- a/packages/client/runtime/package.json +++ b/packages/client/runtime/package.json @@ -32,6 +32,7 @@ }, "license": "BSD-3-Clause", "dependencies": { + "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-client-connection": "workspace:^", "@deepseek-ai/dsh-commands": "workspace:^", "@deepseek-ai/dsh-client-ui-slots": "workspace:^", @@ -41,6 +42,7 @@ "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-projection": "workspace:^", "@deepseek-ai/dsh-session-title": "workspace:^", + "@deepseek-ai/dsh-tools": "workspace:^", "immer": "^10.1.1", "react": "^18.2.0", "zustand": "~4.4.7" diff --git a/packages/client/runtime/src/client/sessions/conversation-location-index.ts b/packages/client/runtime/src/client/sessions/conversation-location-index.ts index 20fe3efdff..c842a230a9 100644 --- a/packages/client/runtime/src/client/sessions/conversation-location-index.ts +++ b/packages/client/runtime/src/client/sessions/conversation-location-index.ts @@ -139,7 +139,11 @@ export class ConversationLocationIndex { return this.timeline } - /** Replace all Definition-owned Location values while preserving reader identities. */ + /** + * Replace all Definition-owned Location values while preserving reader identities. + * @param entries - complete current set of Definition-owned Location values. + * @returns whether any published Location data changed. + */ replaceData(entries: readonly { readonly owner: string; readonly data: ConversationLocationData }[]): boolean { const turns = new Map>() const steps = new Map>() @@ -165,7 +169,11 @@ export class ConversationLocationIndex { return changed } - /** Apply changed Context publications without rebuilding Turn/Step membership. */ + /** + * Apply changed Context publications without rebuilding Turn/Step membership. + * @param changes - incremental removals and replacements from published Contexts. + * @returns whether any published Location data changed. + */ applyData(changes: readonly ConversationLocationDataChange[]): boolean { let changed = false for (const change of changes) { diff --git a/packages/client/runtime/src/client/sessions/request-inspection.ts b/packages/client/runtime/src/client/sessions/request-inspection.ts index 36ae79eebe..162f34d5ff 100644 --- a/packages/client/runtime/src/client/sessions/request-inspection.ts +++ b/packages/client/runtime/src/client/sessions/request-inspection.ts @@ -5,6 +5,9 @@ import type { ContentBlock, TokenUsage, ToolSchema } from '@deepseek-ai/dsh-llm/types' import type { HistoryEntry } from '@deepseek-ai/dsh-client-connection/client' import type { SessionEvent } from '@deepseek-ai/dsh-session/types' +import type {} from '@deepseek-ai/dsh-compact/types' +import type {} from '@deepseek-ai/dsh-llm-retry/types' +import type {} from '@deepseek-ai/dsh-tools/types' import type { AssistantProvenanceView, AssistantRequestConfig, } from './conversation.ts' @@ -109,48 +112,6 @@ export function inspectRequests( } } -interface RetryEvent { - type: 'llm/retry' - seq: number - time: number - data: { - turn: number - step: number - retry: number - maxRetries: number - delayMs: number - failure: { message: string } - } -} - -interface CompactionStartEvent { - type: 'compact/start' - seq: number - time: number - data: { turn: number | null } -} - -interface CompactionSummaryEvent { - type: 'compact/summary' - seq: number - time: number - data: { - summary: readonly ContentBlock[] - rawOutput?: readonly ContentBlock[] - provider: string - model: string - maxTokens?: number - usage?: unknown - } -} - -interface CompactionEndEvent { - type: 'compact/end' - seq: number - time: number - data: { turn: number | null; error?: string } -} - function requestKey(turn: number, step: number): string { return `${turn}\u0000${step}` } @@ -205,10 +166,8 @@ function deriveCallSchemas( capture(String(event.data.callId), event.data.name) continue } - const type = event.type as string - if (type === 'tool/code-dispatch-start' || type === 'tool/code-dispatch') { - const data = event.data as unknown as { subCallId: string; name: string } - capture(data.subCallId, data.name) + if (event.type === 'tool/code-dispatch-start' || event.type === 'tool/code-dispatch') { + capture(String(event.data.subCallId), event.data.name) } } return calls @@ -351,14 +310,14 @@ function deriveRequests(events: readonly SessionEvent[]): readonly RequestView[] if (activeStep === key) activeStep = undefined continue } - if ((sourceEvent.type as string) === 'llm/retry') { - const event = sourceEvent as unknown as RetryEvent - updateAssistant(ordinaryByStep.get(requestKey(event.data.turn, event.data.step)), { + if (sourceEvent.type === 'llm/retry') { + const data = sourceEvent.data + updateAssistant(ordinaryByStep.get(requestKey(data.turn, data.step)), { status: 'error', - error: displayFailureMessage(event.data.failure), - retry: event.data.retry, - maxRetries: event.data.maxRetries, - retryDelayMs: event.data.delayMs, + error: displayFailureMessage(data.failure), + retry: data.retry, + ...data.mode === 'normal' ? { maxRetries: data.maxRetries } : {}, + retryDelayMs: data.delayMs, }) continue } @@ -374,8 +333,7 @@ function deriveRequests(events: readonly SessionEvent[]): readonly RequestView[] continue } - const type = sourceEvent.type as string - if (type === 'session/end-seed' && activeCompaction !== undefined) { + if (sourceEvent.type === 'session/end-seed' && activeCompaction !== undefined) { updateCompaction(activeCompaction, { completedAt: sourceEvent.time, status: 'error', @@ -384,37 +342,36 @@ function deriveRequests(events: readonly SessionEvent[]): readonly RequestView[] activeCompaction = undefined continue } - if (type === 'compact/start') { - const event = sourceEvent as unknown as CompactionStartEvent + if (sourceEvent.type === 'compact/start') { activeCompaction = requests.length requests.push({ purpose: 'compaction', - startSeq: event.seq, - turn: event.data.turn, + startSeq: sourceEvent.seq, + turn: sourceEvent.data.turn, step: 0, - startedAt: event.time, + startedAt: sourceEvent.time, completedAt: null, status: 'running', }) continue } - if (type === 'compact/summary' && activeCompaction !== undefined) { - const event = sourceEvent as unknown as CompactionSummaryEvent + if (sourceEvent.type === 'compact/summary' && activeCompaction !== undefined) { + const data = sourceEvent.data updateCompaction(activeCompaction, { - resultSeq: event.seq, - summary: event.data.summary, - ...(event.data.rawOutput === undefined ? {} : { rawOutput: event.data.rawOutput }), + resultSeq: sourceEvent.seq, + summary: data.summary, + ...(data.rawOutput === undefined ? {} : { rawOutput: data.rawOutput }), provenance: { - provider: event.data.provider, - model: event.data.model, + provider: data.provider, + model: data.model, }, requestConfig: { - provider: event.data.provider, - model: event.data.model, + provider: data.provider, + model: data.model, purpose: 'compaction', - ...(event.data.maxTokens === undefined ? {} : { maxTokens: event.data.maxTokens }), + ...(data.maxTokens === undefined ? {} : { maxTokens: data.maxTokens }), }, - ...(event.data.usage === undefined ? {} : { usage: event.data.usage }), + ...(data.usage === undefined ? {} : { usage: data.usage }), }) continue } @@ -426,12 +383,11 @@ function deriveRequests(events: readonly SessionEvent[]): readonly RequestView[] updateCompaction(activeCompaction, { replacementSeq: sourceEvent.seq }) continue } - if (type !== 'compact/end' || activeCompaction === undefined) continue - const event = sourceEvent as unknown as CompactionEndEvent + if (sourceEvent.type !== 'compact/end' || activeCompaction === undefined) continue updateCompaction(activeCompaction, { - completedAt: event.time, - status: event.data.error === undefined ? 'complete' : 'error', - ...(event.data.error === undefined ? {} : { error: event.data.error }), + completedAt: sourceEvent.time, + status: sourceEvent.data.error === undefined ? 'complete' : 'error', + ...(sourceEvent.data.error === undefined ? {} : { error: sourceEvent.data.error }), }) activeCompaction = undefined } diff --git a/packages/client/runtime/src/client/sessions/steering-history.ts b/packages/client/runtime/src/client/sessions/steering-history.ts index 0f66025e16..4220a769ff 100644 --- a/packages/client/runtime/src/client/sessions/steering-history.ts +++ b/packages/client/runtime/src/client/sessions/steering-history.ts @@ -1,6 +1,7 @@ /** Reconstruct durable steering identity from the event-sourced agent inbox. */ import type { SessionEvent } from '@deepseek-ai/dsh-session/types' +import type {} from '@deepseek-ai/dsh-agent/types' type InboxTarget = 'next-turn' | 'next-step' @@ -45,8 +46,8 @@ export class SteeringHistory { * @returns true only for a user-origin message previously claimed from `next-step`. */ apply(event: SessionEvent): boolean { - if ((event.type as string) === 'agent/inbox/spliced') { - this.applySplice(event.data as unknown as InboxSplice) + if (event.type === 'agent/inbox/spliced') { + this.applySplice(event.data) return false } if (event.type !== 'user/message') return false diff --git a/packages/client/runtime/src/client/sessions/tool-call-tree.ts b/packages/client/runtime/src/client/sessions/tool-call-tree.ts index fbc32e0cef..4507b7a947 100644 --- a/packages/client/runtime/src/client/sessions/tool-call-tree.ts +++ b/packages/client/runtime/src/client/sessions/tool-call-tree.ts @@ -1,5 +1,5 @@ -import type { ContentBlock } from '@deepseek-ai/dsh-llm/types' import type { SessionEvent } from '@deepseek-ai/dsh-session/types' +import type {} from '@deepseek-ai/dsh-tools/types' import type { ConversationNode, RunningToolCall, ToolCallBlock, ToolResultNode, } from './conversation.ts' @@ -55,13 +55,8 @@ export class ToolCallTree { * @returns Whether the event was consumed as a child-call lifecycle event. */ apply(event: SessionEvent): boolean { - if ((event.type as string) === 'tool/code-dispatch-start') { - const data = event.data as unknown as { - parentCallId: string - subCallId: string - name: string - arguments: unknown - } + if (event.type === 'tool/code-dispatch-start') { + const data = event.data const running: RunningToolCall = { callId: data.subCallId, name: data.name, @@ -78,15 +73,8 @@ export class ToolCallTree { this.revision++ return true } - if ((event.type as string) !== 'tool/code-dispatch') return false - const data = event.data as unknown as { - parentCallId: string - subCallId: string - name: string - arguments: unknown - isError: boolean - content: ContentBlock[] - } + if (event.type !== 'tool/code-dispatch') return false + const data = event.data const siblings = this.childrenByParent.get(data.parentCallId) ?? [] const at = siblings.findIndex(sub => sub.callId === data.subCallId) if (at === -1 && !this.acceptEdge(data.parentCallId, data.subCallId)) return true diff --git a/packages/client/runtime/tsconfig.json b/packages/client/runtime/tsconfig.json index 867b2f588c..b8949fc6db 100644 --- a/packages/client/runtime/tsconfig.json +++ b/packages/client/runtime/tsconfig.json @@ -26,6 +26,12 @@ { "path": "../../interaction/commands" }, + { + "path": "../../core/agent" + }, + { + "path": "../../core/tools" + }, { "path": "../../compact/compact" }, diff --git a/packages/client/ui-conversation/package.json b/packages/client/ui-conversation/package.json index 3f5bd8364c..e78567cbf7 100644 --- a/packages/client/ui-conversation/package.json +++ b/packages/client/ui-conversation/package.json @@ -39,22 +39,28 @@ "clsx": "^2.0.0" }, "peerDependencies": { + "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-client-locale": "^0.0.1", "@deepseek-ai/dsh-client-runtime": "^0.0.1", "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1", "@deepseek-ai/dsh-client-ui-slash": "^0.0.1", "@deepseek-ai/dsh-client-ui-slots": "^0.0.1", "@deepseek-ai/dsh-compact": "^0.0.1", + "@deepseek-ai/dsh-commands": "^0.0.1", "@deepseek-ai/dsh-invariants": "^0.0.1", + "@deepseek-ai/dsh-llm-retry": "^0.0.1", "@deepseek-ai/dsh-token-meter": "^0.0.1", + "@deepseek-ai/dsh-tools": "^0.0.1", "cordis": "^4.0.0-rc.7", "react": "^18.2.0" }, "devDependencies": { + "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-client-locale": "workspace:^", "@deepseek-ai/dsh-client-runtime": "workspace:^", "@deepseek-ai/dsh-client-test-runtime": "workspace:^", "@deepseek-ai/dsh-compact": "workspace:^", + "@deepseek-ai/dsh-commands": "workspace:^", "@deepseek-ai/dsh-goal": "workspace:^", "@deepseek-ai/dsh-plan-mode": "workspace:^", "@deepseek-ai/dsh-client-ui-layout": "workspace:^", @@ -62,9 +68,11 @@ "@deepseek-ai/dsh-client-ui-slash": "workspace:^", "@deepseek-ai/dsh-client-ui-slots": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-llm-retry": "workspace:^", "@deepseek-ai/dsh-permission": "workspace:^", "@deepseek-ai/dsh-session-projection": "workspace:^", "@deepseek-ai/dsh-token-meter": "workspace:^", + "@deepseek-ai/dsh-tools": "workspace:^", "@deepseek-ai/dsh-tool-todo": "workspace:^", "@types/react": "~18.3.1", "cordis": "^4.0.0-rc.7", diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts b/packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts index 8a487c89ed..83df1e0df6 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts @@ -6,6 +6,7 @@ import type { import { emptyAssistantBlock, isAppendSurfaceEvent, isTokenDelta, toAssistantBlock, toAssistantBlocks, } from '@deepseek-ai/dsh-client-runtime/client' +import type {} from '@deepseek-ai/dsh-llm-retry/types' import type { AssistantChatData } from '../contract/chat-nodes.ts' import { CHAT_SYNTHETIC_SEQ_OFFSETS, chatNode } from './common.ts' @@ -197,7 +198,7 @@ function fallbackState(context: ConversationNodeContext): Assist } continue } - if ((match.event.type as string) === 'llm/retry' && state !== undefined) { + if (match.event.type === 'llm/retry' && state !== undefined) { state = resetForRetry(state) } } @@ -247,9 +248,8 @@ export const assistantDefinition: ConversationNodeDefinition = { || (event.type === 'assistant/message' && isAppendSurfaceEvent(event))) { return { id: `${event.data.turn}:${event.data.step}`, role: 'update' } } - if ((event.type as string) === 'llm/retry') { - const data = event.data as unknown as { turn: number; step: number } - return { id: `${data.turn}:${data.step}`, role: 'update' } + if (event.type === 'llm/retry') { + return { id: `${event.data.turn}:${event.data.step}`, role: 'update' } } return null }, @@ -268,7 +268,7 @@ export const assistantDefinition: ConversationNodeDefinition = { usage: match.event.data.usage, } } - if ((match.event.type as string) === 'llm/retry') { + if (match.event.type === 'llm/retry') { return resetForRetry(context.state) } return context.state diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/command.ts b/packages/client/ui-conversation/src/client/conversation-nodes/command.ts index 3ca7976e32..38cb85ed87 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/command.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/command.ts @@ -5,6 +5,8 @@ import type { } from '@deepseek-ai/dsh-client-runtime/client' import { isReplacementSurfaceEvent } from '@deepseek-ai/dsh-client-runtime/client' import type { COMPACT_CHECKPOINT_SOURCE } from '@deepseek-ai/dsh-compact/checkpoint' +import type {} from '@deepseek-ai/dsh-compact/types' +import type {} from '@deepseek-ai/dsh-commands/types' import type { ManualCompactionChatData } from '../contract/chat-nodes.ts' import { chatNode } from './common.ts' @@ -32,21 +34,9 @@ interface CompactionEvidence { readonly checkpoint?: ConversationMatch } -interface CommandRunData { - readonly commandId: CommandId - readonly name: string - readonly args?: string -} - -interface CommandDoneData { - readonly commandId: CommandId - readonly kind: 'success' | 'error' - readonly text?: string - readonly sourceEventSeq?: number -} - function commandFromRun(match: ConversationMatch): CommandNode { - const data = match.event.data as unknown as CommandRunData + if (match.event.type !== 'command/run') throw new Error('command start requires command/run') + const data = match.event.data return { kind: 'command', seq: match.event.seq, @@ -59,10 +49,12 @@ function commandFromRun(match: ConversationMatch): CommandNode { } function commandFromDone(match: ConversationMatch, previous?: CommandNode): CommandNode { - const data = match.event.data as unknown as CommandDoneData + if (match.event.type !== 'command/done') throw new Error('command update requires command/done') + const data = match.event.data const sourceEventSeq = data.kind === 'success' - && Number.isSafeInteger(data.sourceEventSeq) && (data.sourceEventSeq as number) >= 0 - ? data.sourceEventSeq as number + && data.sourceEventSeq !== undefined + && Number.isSafeInteger(data.sourceEventSeq) && data.sourceEventSeq >= 0 + ? data.sourceEventSeq : undefined return { kind: 'command', @@ -112,28 +104,21 @@ function compactSummary(match: ConversationMatch | undefined, checkpoint: Conver let summary: string | null = null let shadowedItemCount: number | null = null let shadowedTokenCount: number | null = null - if (match !== undefined) { - const data = match.event.data as unknown as { - summary?: unknown - shadowedSeqs?: unknown - shadowedTokenCount?: unknown - } + if (match?.event.type === 'compact/summary') { + const data = match.event.data if (Array.isArray(data.summary)) { const text = data.summary - .map((block: unknown) => { - const value = block as { type?: unknown; text?: unknown } - return value.type === 'text' && typeof value.text === 'string' ? value.text : '' - }) + .map(block => block.type === 'text' ? block.text : '') .join('') summary = text.trim() === '' ? null : text } shadowedItemCount = Array.isArray(data.shadowedSeqs) - && data.shadowedSeqs.every(seq => Number.isSafeInteger(seq) && (seq as number) >= 0) + && data.shadowedSeqs.every(seq => Number.isSafeInteger(seq) && seq >= 0) ? data.shadowedSeqs.length : null shadowedTokenCount = Number.isSafeInteger(data.shadowedTokenCount) - && (data.shadowedTokenCount as number) >= 0 - ? data.shadowedTokenCount as number + && data.shadowedTokenCount >= 0 + ? data.shadowedTokenCount : null } return { @@ -148,9 +133,9 @@ function compactSummary(match: ConversationMatch | undefined, checkpoint: Conver } function fallbackState(context: ConversationNodeContext): CommandState | undefined { - const done = context.matches.find(match => (match.event.type as string) === 'command/done') + const done = context.matches.find(match => match.event.type === 'command/done') const checkpoint = context.matches.find(match => compactSource(match.event) !== undefined) - const summary = context.matches.find(match => (match.event.type as string) === 'compact/summary') + const summary = context.matches.find(match => match.event.type === 'compact/summary') if (checkpoint === undefined) return done === undefined ? undefined : { command: commandFromDone(done) } const source = compactSource(checkpoint.event) if (source?.sourceCommandId === undefined) return done === undefined ? undefined : { command: commandFromDone(done) } @@ -182,7 +167,7 @@ export function updateCompactionState( state: State, match: ConversationMatch, ): State { - if ((match.event.type as string) === 'compact/summary') return { ...state, summary: match } + if (match.event.type === 'compact/summary') return { ...state, summary: match } if (compactSource(match.event) !== undefined) return { ...state, checkpoint: match } return state } @@ -191,27 +176,28 @@ export function updateCompactionState( export const commandDefinition: ConversationNodeDefinition = { kind: 'command', match: (event) => { - if ((event.type as string) === 'command/run') { - return { id: String((event.data as unknown as CommandRunData).commandId), role: 'start' } + if (event.type === 'command/run') { + return { id: String(event.data.commandId), role: 'start' } } - if ((event.type as string) === 'command/done') { - return { id: String((event.data as unknown as CommandDoneData).commandId), role: 'update' } + if (event.type === 'command/done') { + return { id: String(event.data.commandId), role: 'update' } } const checkpoint = compactSource(event) if (checkpoint?.sourceCommandId !== undefined) { return { id: String(checkpoint.sourceCommandId), role: 'update' } } - if ((event.type as string) === 'compact/start' - || (event.type as string) === 'compact/summary' - || (event.type as string) === 'compact/end') { - const data = event.data as unknown as { sourceCommandId?: CommandId } - if (data.sourceCommandId !== undefined) return { id: String(data.sourceCommandId), role: 'update' } + if (event.type === 'compact/start' + || event.type === 'compact/summary' + || event.type === 'compact/end') { + if (event.data.sourceCommandId !== undefined) { + return { id: String(event.data.sourceCommandId), role: 'update' } + } } return null }, start: (_context, match) => ({ command: commandFromRun(match) }), update: (context, match) => { - if ((match.event.type as string) === 'command/done') { + if (match.event.type === 'command/done') { return { ...context.state, command: commandFromDone(match, context.state.command) } } return updateCompactionState(context.state, match) diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/compaction.ts b/packages/client/ui-conversation/src/client/conversation-nodes/compaction.ts index 0742ab6539..04eace8038 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/compaction.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/compaction.ts @@ -2,6 +2,7 @@ import type { Context } from 'cordis' import type { CompactionSummaryNode, ConversationMatch, ConversationNodeContext, ConversationNodeDefinition, } from '@deepseek-ai/dsh-client-runtime/client' +import type {} from '@deepseek-ai/dsh-compact/types' import { chatNode } from './common.ts' import { compactSource, compactSummary, updateCompactionState } from './command.ts' @@ -18,7 +19,7 @@ interface CompactionState { } function fallbackState(context: ConversationNodeContext): CompactionState { - const summary = context.matches.find(match => (match.event.type as string) === 'compact/summary') + const summary = context.matches.find(match => match.event.type === 'compact/summary') const checkpoint = context.matches.find(match => compactSource(match.event) !== undefined) return { ...summary === undefined ? {} : { summary }, @@ -34,12 +35,11 @@ export const compactionDefinition: ConversationNodeDefinition = if (checkpoint !== undefined && checkpoint.sourceCommandId === undefined) { return { id: checkpoint.compactionId, role: 'update' } } - if ((event.type as string) === 'compact/start' - || (event.type as string) === 'compact/summary' - || (event.type as string) === 'compact/end') { - const data = event.data as unknown as { compactionId?: unknown; sourceCommandId?: unknown } - if (typeof data.compactionId !== 'string' || data.sourceCommandId !== undefined) return null - return { id: data.compactionId, role: (event.type as string) === 'compact/start' ? 'start' : 'update' } + if (event.type === 'compact/start' + || event.type === 'compact/summary' + || event.type === 'compact/end') { + if (event.data.sourceCommandId !== undefined) return null + return { id: String(event.data.compactionId), role: event.type === 'compact/start' ? 'start' : 'update' } } return null }, diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/inbox.ts b/packages/client/ui-conversation/src/client/conversation-nodes/inbox.ts index 092bcaaa59..74406c324a 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/inbox.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/inbox.ts @@ -2,6 +2,7 @@ import type { Context } from 'cordis' import type { ConversationNodeDefinition, ConversationPreviousContext, } from '@deepseek-ai/dsh-client-runtime/client' +import type {} from '@deepseek-ai/dsh-agent/types' type InboxTarget = 'next-turn' | 'next-step' @@ -41,14 +42,14 @@ function inboxDefinition(target: InboxTarget): ConversationNodeDefinition (event.type as string) === 'agent/inbox/spliced' - && (event.data as unknown as { target?: unknown }).target === target + match: event => event.type === 'agent/inbox/spliced' + && event.data.target === target ? { id: String(event.seq), role: 'start' } : null, - start: (_context, match, reader) => applySplice( - reader.previous(kind), - match.event.data as unknown as InboxSplice, - ), + start: (_context, match, reader) => { + if (match.event.type !== 'agent/inbox/spliced') throw new Error(`${kind} start requires agent/inbox/spliced`) + return applySplice(reader.previous(kind), match.event.data) + }, update: context => context.state, publication: () => 'none', buildViewNode: () => null, diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/retry.ts b/packages/client/ui-conversation/src/client/conversation-nodes/retry.ts index fe95c0052d..f383bc46d8 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/retry.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/retry.ts @@ -2,6 +2,7 @@ import type { Context } from 'cordis' import type { ConversationLocation, ConversationNodeDefinition, ModelRetryNode, } from '@deepseek-ai/dsh-client-runtime/client' +import type {} from '@deepseek-ai/dsh-llm-retry/types' import type { RetryChatData } from '../contract/chat-nodes.ts' import { chatNode } from './common.ts' @@ -12,11 +13,6 @@ declare module '@deepseek-ai/dsh-client-ui-conversation/client' { } } -type WithoutRetryProjection = Node extends unknown - ? Omit - : never -type RetryEventData = WithoutRetryProjection - /** Accumulated retry attempts sharing one producer-owned RetryId. */ export interface RetryState { readonly turn: number @@ -24,31 +20,14 @@ export interface RetryState { readonly attempts: readonly ModelRetryNode[] } -function retryData(value: unknown): RetryEventData | undefined { - if (value === null || typeof value !== 'object') return undefined - const data = value as Record - if (typeof data.retryId !== 'string' || data.retryId === '' - || !Number.isSafeInteger(data.turn) || (data.turn as number) < 0 - || !Number.isSafeInteger(data.step) || (data.step as number) < 0 - || !Number.isSafeInteger(data.retry) || (data.retry as number) <= 0 - || typeof data.delayMs !== 'number' || !Number.isFinite(data.delayMs) || data.delayMs < 0 - || typeof data.provider !== 'string' || typeof data.policyKey !== 'string' - || (data.mode !== 'normal' && data.mode !== 'always') - || data.failure === null || typeof data.failure !== 'object') return undefined - if (data.mode === 'normal' && (!Number.isSafeInteger(data.maxRetries) || (data.maxRetries as number) <= 0)) { - return undefined - } - return data as unknown as RetryEventData -} - -function scheduledNode(event: { seq: number; time: number; data: unknown }): ModelRetryNode | undefined { - const data = retryData(event.data) - return data === undefined ? undefined : { +function scheduledNode(match: Parameters[1]): ModelRetryNode | undefined { + if (match.event.type !== 'llm/retry') return undefined + return { kind: 'model-retry', - seq: event.seq, - time: event.time, + seq: match.event.seq, + time: match.event.time, retryState: 'scheduled', - ...data, + ...match.event.data, } } @@ -61,33 +40,30 @@ function isClosed(location: ConversationLocation): boolean { export const retryDefinition: ConversationNodeDefinition = { kind: 'model-retry', match: (event) => { - if ((event.type as string) === 'llm/retry') { - const data = retryData(event.data) - if (data === undefined) return null - return { id: String(data.retryId), role: data.retry === 1 ? 'start' : 'update' } + if (event.type === 'llm/retry') { + return { id: String(event.data.retryId), role: event.data.retry === 1 ? 'start' : 'update' } } - if ((event.type as string) === 'llm/retry-started') { - const data = event.data as unknown as { retryId?: unknown } - return typeof data.retryId === 'string' ? { id: data.retryId, role: 'update' } : null + if (event.type === 'llm/retry-started') { + return { id: String(event.data.retryId), role: 'update' } } return null }, start: (_context, match) => { - const node = scheduledNode(match.event) + const node = scheduledNode(match) if (node === undefined) throw new Error('model-retry start requires a valid llm/retry event') return { turn: node.turn, step: node.step, attempts: [node] } }, update: (context, match) => { - if ((match.event.type as string) === 'llm/retry') { - const node = scheduledNode(match.event) + if (match.event.type === 'llm/retry') { + const node = scheduledNode(match) return node === undefined ? context.state : { ...context.state, attempts: [...context.state.attempts, node] } } - if ((match.event.type as string) !== 'llm/retry-started') return context.state - const data = match.event.data as unknown as { retry: number } + if (match.event.type !== 'llm/retry-started') return context.state + const retry = match.event.data.retry return { ...context.state, attempts: context.state.attempts.map(attempt => - attempt.retry === data.retry ? { ...attempt, retryState: 'started' } : attempt), + attempt.retry === retry ? { ...attempt, retryState: 'started' } : attempt), } }, buildViewNode: (context, target) => { diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/tool.ts b/packages/client/ui-conversation/src/client/conversation-nodes/tool.ts index 23201fec3e..dc9e996dcc 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/tool.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/tool.ts @@ -4,6 +4,7 @@ import type { RunningToolCall, ToolCallBlock, ToolResultNode, } from '@deepseek-ai/dsh-client-runtime/client' import { isAppendSurfaceEvent } from '@deepseek-ai/dsh-client-runtime/client' +import type {} from '@deepseek-ai/dsh-tools/types' import type { ToolChatData } from '../contract/chat-nodes.ts' import { CHAT_SYNTHETIC_SEQ_OFFSETS, chatNode } from './common.ts' @@ -141,27 +142,30 @@ function acceptsEdge(state: ToolState, parent: string, child: string): boolean { } function updateDispatch(state: ToolState, match: ConversationMatch): ToolState { - const data = match.event.data as unknown as DispatchData - const siblings = state.children.get(data.parentCallId) ?? [] - const index = siblings.findIndex(candidate => candidate.callId === data.subCallId) - if ((match.event.type as string) === 'tool/code-dispatch-start') { - if (index >= 0 || !acceptsEdge(state, data.parentCallId, data.subCallId)) return state + const event = match.event + if (event.type !== 'tool/code-dispatch-start' && event.type !== 'tool/code-dispatch') return state + const data = event.data + const parentCallId = String(data.parentCallId) + const subCallId = String(data.subCallId) + const siblings = state.children.get(parentCallId) ?? [] + const index = siblings.findIndex(candidate => candidate.callId === subCallId) + if (event.type === 'tool/code-dispatch-start') { + if (index >= 0 || !acceptsEdge(state, parentCallId, subCallId)) return state const children = new Map(state.children) - children.set(data.parentCallId, [...siblings, childCall(match, data)]) + children.set(parentCallId, [...siblings, childCall(match, data)]) const parents = new Map(state.parents) - parents.set(data.subCallId, data.parentCallId) + parents.set(subCallId, parentCallId) return { ...state, children, parents } } - if ((match.event.type as string) !== 'tool/code-dispatch') return state - if (index < 0 && !acceptsEdge(state, data.parentCallId, data.subCallId)) return state + if (index < 0 && !acceptsEdge(state, parentCallId, subCallId)) return state const previous = index < 0 ? undefined : siblings[index] const settled = childResult(match, data, previous) const children = new Map(state.children) - children.set(data.parentCallId, index < 0 + children.set(parentCallId, index < 0 ? [...siblings, settled] : siblings.map((child, at) => at === index ? settled : child)) const parents = new Map(state.parents) - if (index < 0) parents.set(data.subCallId, data.parentCallId) + if (index < 0) parents.set(subCallId, parentCallId) return { ...state, children, parents } } @@ -236,9 +240,8 @@ export const toolDefinition: ConversationNodeDefinition = { if (event.type === 'tool/result' && isAppendSurfaceEvent(event)) { return { id: String(event.data.message.source.callId), role: 'update' } } - if ((event.type as string) === 'tool/code-dispatch-start' || (event.type as string) === 'tool/code-dispatch') { - const data = event.data as unknown as { rootCallId: string } - return { id: data.rootCallId, role: 'update' } + if (event.type === 'tool/code-dispatch-start' || event.type === 'tool/code-dispatch') { + return { id: String(event.data.rootCallId), role: 'update' } } return null }, diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/turn-error.ts b/packages/client/ui-conversation/src/client/conversation-nodes/turn-error.ts index 58725d8723..60b2fca087 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/turn-error.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/turn-error.ts @@ -3,6 +3,7 @@ import type { ConversationMatch, ConversationNodeContext, ConversationNodeDefinition, TurnErrorNode, } from '@deepseek-ai/dsh-client-runtime/client' import { displayFailureMessage } from '@deepseek-ai/dsh-client-runtime/client' +import type {} from '@deepseek-ai/dsh-llm-retry/types' import { chatNode } from './common.ts' declare module '@deepseek-ai/dsh-client-ui-conversation/client' { @@ -30,9 +31,9 @@ function lastStep(context: ConversationNodeContext): number { } function retryTurn(event: Parameters[0]): number | undefined { - if ((event.type as string) !== 'llm/retry' && (event.type as string) !== 'llm/retry-started') return undefined - const turn = (event.data as unknown as { turn?: unknown }).turn - return Number.isSafeInteger(turn) && (turn as number) >= 0 ? turn as number : undefined + return event.type === 'llm/retry' || event.type === 'llm/retry-started' + ? event.data.turn + : undefined } function failureFrom(match: ConversationMatch): TurnErrorState['failure'] | undefined { diff --git a/packages/client/ui-conversation/src/client/conversation-nodes/turn-tail.ts b/packages/client/ui-conversation/src/client/conversation-nodes/turn-tail.ts index 51cb348e6a..2a1b4d2b13 100644 --- a/packages/client/ui-conversation/src/client/conversation-nodes/turn-tail.ts +++ b/packages/client/ui-conversation/src/client/conversation-nodes/turn-tail.ts @@ -3,6 +3,7 @@ import type { ConversationMatch, ConversationNodeContext, ConversationNodeDefinition, TurnLocation, } from '@deepseek-ai/dsh-client-runtime/client' import { isAppendSurfaceEvent, toAssistantBlocks } from '@deepseek-ai/dsh-client-runtime/client' +import type {} from '@deepseek-ai/dsh-llm-retry/types' import type { AssistantChatData, FinalAssistantChatData, TurnTailChatData, } from '../contract/chat-nodes.ts' @@ -58,9 +59,7 @@ function turnCoordinates(event: Parameters[ || event.type === 'step/end') { return { turn: event.data.turn, step: event.data.step } } - if ((event.type as string) === 'llm/retry') { - return event.data as unknown as { turn: number; step: number } - } + if (event.type === 'llm/retry') return { turn: event.data.turn, step: event.data.step } return undefined } @@ -90,7 +89,7 @@ function closingAnchor(context: ConversationNodeContext): number } continue } - if ((event.type as string) === 'llm/retry') { + if (event.type === 'llm/retry') { steps.set(coordinates.step, { streamedText: false, finalized: false }) continue } @@ -130,7 +129,7 @@ function tailData(context: ConversationNodeContext): TurnTailChat const candidate = event.type === 'tool/call' || (event.type === 'tool/result' && isAppendSurfaceEvent(event)) || (event.type === 'turn/end' && event.data.reason.kind === 'error') - || (event.type as string) === 'llm/retry' + || event.type === 'llm/retry' ? event.seq : undefined if (candidate !== undefined && (latestTranscriptSeq === undefined || candidate > latestTranscriptSeq)) { diff --git a/packages/client/ui-conversation/tests/chat-apply.spec.tsx b/packages/client/ui-conversation/tests/chat-apply.spec.tsx index 3481ee758e..41ffd329b8 100644 --- a/packages/client/ui-conversation/tests/chat-apply.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-apply.spec.tsx @@ -62,7 +62,9 @@ describe('apply wiring', () => { expect(entries[0]?.options.order).toBe(0) // Declaring is claiming: the chat entry's registration put the hole on // the ledger with the contract's kind/scope. - expect(b.slots.spec('conversation.chat.node')).toEqual({ kind: 'keyed', scope: 'session' }) + const nodeSlot = b.slots.spec('conversation.chat.node') + expect(nodeSlot).toMatchObject({ kind: 'keyed', scope: 'session' }) + expect(nodeSlot?.inject?.hooks?.turnData).toBeTypeOf('function') await b.runtime.dispose() }) diff --git a/packages/client/ui-conversation/tests/conversation-node-definitions.spec.ts b/packages/client/ui-conversation/tests/conversation-node-definitions.spec.ts index 5346ad57f9..3e5dcccff3 100644 --- a/packages/client/ui-conversation/tests/conversation-node-definitions.spec.ts +++ b/packages/client/ui-conversation/tests/conversation-node-definitions.spec.ts @@ -644,6 +644,28 @@ describe('built-in conversation node Definitions', () => { }) }) + it('renders a historical compaction when its start remains outside the loaded window', () => { + const value = assembler([ + at(10, 'compact/summary', { + compactionId: 'compact-windowed', + summary: [{ type: 'text', text: 'loaded summary' }], + shadowedSeqs: [1, 2, 3], + shadowedTokenCount: 42, + }), + at(11, 'user/message', { + ...textMessage('checkpoint-windowed', 'checkpoint'), + source: { kind: 'plugin', plugin: 'compact', compactionId: 'compact-windowed' }, + }, { surfaceOp: { op: 'replace', start: 1, end: 3 } }), + ], true) + + expect(node(snapshot(value), 'compaction')?.data).toMatchObject({ + summary: 'loaded summary', + summaryEventSeq: 10, + shadowedItemCount: 3, + shadowedTokenCount: 42, + }) + }) + it('suppresses a turn error when the loaded tail contains only a later retry attempt', () => { const value = assembler([ at(5, 'llm/retry', { diff --git a/packages/client/ui-conversation/tsconfig.json b/packages/client/ui-conversation/tsconfig.json index 89d0067caf..0d6f250441 100644 --- a/packages/client/ui-conversation/tsconfig.json +++ b/packages/client/ui-conversation/tsconfig.json @@ -23,12 +23,24 @@ { "path": "../runtime" }, + { + "path": "../../core/agent" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../interaction/commands" + }, { "path": "../../session/session-projection" }, { "path": "../../llm/token-meter" }, + { + "path": "../../llm/llm-retry" + }, { "path": "../../plan/plan-mode" }, diff --git a/packages/compact/compact/package.json b/packages/compact/compact/package.json index bb4150ee32..860145e542 100644 --- a/packages/compact/compact/package.json +++ b/packages/compact/compact/package.json @@ -15,14 +15,14 @@ "types": "./lib/types/invariant.d.ts", "default": "./lib/invariant.js" }, + "./types": { + "types": "./lib/types/types.d.ts", + "default": "./lib/types/types.js" + }, "./checkpoint": { "types": "./lib/types/checkpoint.d.ts", "default": "./lib/types/checkpoint.js" }, - "./brand": { - "types": "./lib/types/brand.d.ts", - "default": "./lib/types/brand.js" - }, "./src/*": "./src/*", "./package.json": "./package.json" }, diff --git a/packages/compact/compact/src/types.ts b/packages/compact/compact/src/types.ts index df5e749cd8..53c3baf05f 100644 --- a/packages/compact/compact/src/types.ts +++ b/packages/compact/compact/src/types.ts @@ -9,7 +9,9 @@ import type { ContentBlock, TokenUsage } from '@deepseek-ai/dsh-llm' import type { CommandId } from '@deepseek-ai/dsh-commands/brand' -import type { CompactionId } from './brand.ts' +import { CompactionId } from './brand.ts' + +export { CompactionId } declare module '@deepseek-ai/dsh-session/types' { interface SessionEventMap { diff --git a/packages/core/agent/package.json b/packages/core/agent/package.json index 9f64d33e75..d186f32d03 100644 --- a/packages/core/agent/package.json +++ b/packages/core/agent/package.json @@ -16,8 +16,8 @@ "default": "./lib/invariant.js" }, "./types": { - "types": "./lib/types/types.d.ts", - "default": "./lib/types/types.js" + "types": "./lib/types/session-types.d.ts", + "default": "./lib/types/session-types.js" }, "./src/*": "./src/*", "./package.json": "./package.json" diff --git a/packages/core/agent/src/dispatch.ts b/packages/core/agent/src/dispatch.ts index cf07f24ecf..7eff09b01d 100644 --- a/packages/core/agent/src/dispatch.ts +++ b/packages/core/agent/src/dispatch.ts @@ -10,7 +10,7 @@ import type { Context, Events } from 'cordis' import { scopeTarget } from '@deepseek-ai/dsh-scope' import type { Scoped } from '@deepseek-ai/dsh-scope' import type { AssembleContext } from '@deepseek-ai/dsh-system-prompt' -import type { Agent } from './types.ts' +import type { Agent } from './runtime-types.ts' /** Extract the parameter tuple from an event handler type (its `this` is not part of the tuple). */ type Params = F extends (...args: infer P) => unknown ? P : never diff --git a/packages/core/agent/src/inbox.ts b/packages/core/agent/src/inbox.ts index d457277035..2db3c4b580 100644 --- a/packages/core/agent/src/inbox.ts +++ b/packages/core/agent/src/inbox.ts @@ -6,9 +6,7 @@ import type { MessageId } from '@deepseek-ai/dsh-llm' import type { Session, SessionEventMap, UserMessage } from '@deepseek-ai/dsh-session' - -/** One of the two ordered pending-message lists owned by an agent. */ -export type InboxTarget = 'next-turn' | 'next-step' +import type { InboxTarget } from './session-types.ts' /** Mutable state privately owned by an {@link Inbox}. */ type InboxState = Record diff --git a/packages/core/agent/src/index.ts b/packages/core/agent/src/index.ts index bedfc819dc..e35bc06211 100644 --- a/packages/core/agent/src/index.ts +++ b/packages/core/agent/src/index.ts @@ -13,9 +13,10 @@ import { scopeTarget } from '@deepseek-ai/dsh-scope' import type { Scoped } from '@deepseek-ai/dsh-scope' import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session' import type { TypeRTContext, TypeRTLookup } from '@deepseek-ai/dsh-type-meta' -import type { Agent, AgentOptions } from './types.ts' +import type { Agent, AgentOptions } from './runtime-types.ts' -export * from './types.ts' +export * from './runtime-types.ts' +export * from './session-types.ts' export * from './inbox.ts' export * from './model-selection.ts' export { agentCarrier, agentEvents, assembleContextFor, emitAgentEvent } from './dispatch.ts' diff --git a/packages/core/agent/src/types.ts b/packages/core/agent/src/runtime-types.ts similarity index 96% rename from packages/core/agent/src/types.ts rename to packages/core/agent/src/runtime-types.ts index 8decb9ba3f..072f509f8f 100644 --- a/packages/core/agent/src/types.ts +++ b/packages/core/agent/src/runtime-types.ts @@ -2,7 +2,7 @@ * Public agent types and live-runtime events. Durable transcript facts and * turn/step boundaries remain `@deepseek-ai/dsh-session` events. * - * @module @deepseek-ai/dsh-agent/types + * @module @deepseek-ai/dsh-agent */ import type { Context } from 'cordis' @@ -10,7 +10,8 @@ import type { Scoped } from '@deepseek-ai/dsh-scope' import type { LlmCallConfig, LlmFailure, ResolvedRetryPolicy } from '@deepseek-ai/dsh-llm' import type { AgentCancelCause, Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session' export type { AgentCancelCause } from '@deepseek-ai/dsh-session' -import type { Inbox, InboxTarget } from './inbox.ts' +import type { Inbox } from './inbox.ts' +import type { InboxTarget } from './session-types.ts' import type {} from '@deepseek-ai/dsh-system-prompt' declare module '@deepseek-ai/dsh-system-prompt' { interface AssembleContext { @@ -289,20 +290,3 @@ declare module 'cordis' { 'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void } } - -declare module '@deepseek-ai/dsh-session/types' { - interface SessionEventMap { - /** - * One normalized mutation of an agent's durable pending-message lists. - * Live dispatch precedes projection mutation, so synchronous observers may - * read the pre-splice inbox to recover the removed messages. - */ - 'agent/inbox/spliced': { - target: InboxTarget - start: number - removedCount?: number - inserted: UserMessage[] - outcome?: 'canceled' - } - } -} diff --git a/packages/core/agent/src/session-types.ts b/packages/core/agent/src/session-types.ts new file mode 100644 index 0000000000..b54e56ea8f --- /dev/null +++ b/packages/core/agent/src/session-types.ts @@ -0,0 +1,27 @@ +/** + * Durable agent session-event vocabulary shared with type-only consumers. + * + * @module @deepseek-ai/dsh-agent/types + */ + +import type { UserMessage } from '@deepseek-ai/dsh-llm/types' + +/** One of the two ordered pending-message lists owned by an agent. */ +export type InboxTarget = 'next-turn' | 'next-step' + +declare module '@deepseek-ai/dsh-session/types' { + interface SessionEventMap { + /** + * One normalized mutation of an agent's durable pending-message lists. + * Live dispatch precedes projection mutation, so synchronous observers may + * read the pre-splice inbox to recover the removed messages. + */ + 'agent/inbox/spliced': { + target: InboxTarget + start: number + removedCount?: number + inserted: UserMessage[] + outcome?: 'canceled' + } + } +} diff --git a/packages/core/session/tests/gen-persistence-catalog.spec.ts b/packages/core/session/tests/gen-persistence-catalog.spec.ts index 22bc09835f..4fee74793f 100644 --- a/packages/core/session/tests/gen-persistence-catalog.spec.ts +++ b/packages/core/session/tests/gen-persistence-catalog.spec.ts @@ -89,7 +89,7 @@ describe('gen-persistence-catalog collectLogEvents', () => { it('hard-errors on an extends clause (inherited keys would escape the catalog)', () => { expect(() => collectLogEvents(make({ 'packages/group/fix/src/types.ts': - 'interface Extra { \'fix/hidden\': { turn: number } }\ndeclare module \'@deepseek-ai/dsh-session\' {\n interface SessionEventMap extends Extra {\n /** Declared directly. */\n \'fix/direct\': { turn: number }\n }\n}\n', + 'interface Extra { \'fix/hidden\': { turn: number } }\ndeclare module \'@deepseek-ai/dsh-session/types\' {\n interface SessionEventMap extends Extra {\n /** Declared directly. */\n \'fix/direct\': { turn: number }\n }\n}\n', }))).toThrow(/uses extends; inherited keys would join keyof SessionEventMap without a catalog row/) }) diff --git a/packages/core/tools/package.json b/packages/core/tools/package.json index 3279f17729..0fcc66b497 100644 --- a/packages/core/tools/package.json +++ b/packages/core/tools/package.json @@ -15,6 +15,10 @@ "types": "./lib/types/invariant.d.ts", "default": "./lib/invariant.js" }, + "./types": { + "types": "./lib/types/types.d.ts", + "default": "./lib/types/types.js" + }, "./presentation": { "types": "./lib/types/presentation.d.ts", "default": "./lib/types/presentation.js" diff --git a/packages/core/tools/src/code-mode.ts b/packages/core/tools/src/code-mode.ts index 984fecab88..f5c5809544 100644 --- a/packages/core/tools/src/code-mode.ts +++ b/packages/core/tools/src/code-mode.ts @@ -14,41 +14,7 @@ import type { JsonValue } from '@deepseek-ai/dsh-session' import { defineTool, parameterSchemaSpecToJsonSchema } from './schema.ts' import { TOOL_REGISTRY_SCHEDULER } from './index.ts' import type { CodeDispatchLog, ToolDefinition, ToolExecutionResult, ToolRegistry, ToolRunContext } from './index.ts' - -declare module '@deepseek-ai/dsh-session/types' { - interface SessionEventMap { - /** - * One sub-dispatch STARTING inside a `run_code` program: the parent - * `run_code` call id, the deterministic sub-call id (`:code:`, - * numbered in submission order), and the tool `name` with its - * JSON-normalized `arguments` — the exact value dispatched, normalized - * BEFORE dispatch, so this append can never fail on payload shape. - * Appended when the scheduler actually starts the call (not at - * submission), so a start means the tool body pipeline was entered; a - * call abandoned in the queue logs nothing. Log-only: `deriveMessages()` - * ignores it; UIs use it for live per-sub-call running state and pair it - * with `tool/code-dispatch` by `subCallId` (timing = the two events' - * `time` fields). - */ - 'tool/code-dispatch-start': { rootCallId: CallId; parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown } - /** - * One bridged sub-dispatch SETTLING: the pairing ids (matching the - * `tool/code-dispatch-start` with the same `subCallId`), the tool `name` - * with the same JSON-normalized `arguments`, and the sub-call's complete - * model-facing outcome in `tool/result`'s own vocabulary - * (`content` + `isError`), so UIs render a sub-call through the exact - * code path that renders a native call. Every started sub-call settles - * with exactly one of these (abort included: the aborted pipeline result - * is an `isError` outcome). - * Log-only: `deriveMessages()` ignores it, so sub-calls never re-enter - * model context; persistence and UIs get every call. Appended inside the - * parent `run_code`'s execution (the bridge drains in-flight dispatches - * before returning), so its execution-enclosure relation holds by - * construction. - */ - 'tool/code-dispatch': { rootCallId: CallId; parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown; isError: boolean; content: ContentBlock[] } - } -} +import type {} from './types.ts' /** The model-facing name of the Code Mode tool. */ export const RUN_CODE_NAME = 'run_code' diff --git a/packages/core/tools/src/index.ts b/packages/core/tools/src/index.ts index 6338e54501..1a5f6a05eb 100644 --- a/packages/core/tools/src/index.ts +++ b/packages/core/tools/src/index.ts @@ -85,6 +85,7 @@ export { } from './json-schema.ts' export type { JsonValue } from '@deepseek-ai/dsh-session' +export type { CodeDispatchEventData, CodeDispatchStartEventData } from './types.ts' export { CodeRunFailedError, RUN_CODE_NAME } from './code-mode.ts' export { jsonSchemaToTs, renderToolsSdk } from './ts-types.ts' diff --git a/packages/core/tools/src/types.ts b/packages/core/tools/src/types.ts new file mode 100644 index 0000000000..4dd6f7a9a8 --- /dev/null +++ b/packages/core/tools/src/types.ts @@ -0,0 +1,58 @@ +/** + * Durable Tool event vocabulary shared with type-only consumers. + * + * @module @deepseek-ai/dsh-tools/types + */ + +import type { CallId } from '@deepseek-ai/dsh-llm/brand' +import type { ContentBlock } from '@deepseek-ai/dsh-llm/types' + +/** Payload recorded when one nested Code Mode Tool dispatch starts. */ +export interface CodeDispatchStartEventData { + rootCallId: CallId + parentCallId: CallId + subCallId: CallId + name: string + arguments: unknown +} + +/** Payload recorded when one nested Code Mode Tool dispatch settles. */ +export interface CodeDispatchEventData extends CodeDispatchStartEventData { + isError: boolean + content: ContentBlock[] +} + +declare module '@deepseek-ai/dsh-session/types' { + interface SessionEventMap { + /** + * One sub-dispatch STARTING inside a `run_code` program: the parent + * `run_code` call id, the deterministic sub-call id (`:code:`, + * numbered in submission order), and the tool `name` with its + * JSON-normalized `arguments` — the exact value dispatched, normalized + * BEFORE dispatch, so this append can never fail on payload shape. + * Appended when the scheduler actually starts the call (not at + * submission), so a start means the tool body pipeline was entered; a + * call abandoned in the queue logs nothing. Log-only: `deriveMessages()` + * ignores it; UIs use it for live per-sub-call running state and pair it + * with `tool/code-dispatch` by `subCallId` (timing = the two events' + * `time` fields). + */ + 'tool/code-dispatch-start': CodeDispatchStartEventData + /** + * One bridged sub-dispatch SETTLING: the pairing ids (matching the + * `tool/code-dispatch-start` with the same `subCallId`), the tool `name` + * with the same JSON-normalized `arguments`, and the sub-call's complete + * model-facing outcome in `tool/result`'s own vocabulary + * (`content` + `isError`), so UIs render a sub-call through the exact + * code path that renders a native call. Every started sub-call settles + * with exactly one of these (abort included: the aborted pipeline result + * is an `isError` outcome). + * Log-only: `deriveMessages()` ignores it, so sub-calls never re-enter + * model context; persistence and UIs get every call. Appended inside the + * parent `run_code`'s execution (the bridge drains in-flight dispatches + * before returning), so its execution-enclosure relation holds by + * construction. + */ + 'tool/code-dispatch': CodeDispatchEventData + } +} diff --git a/packages/interaction/commands/package.json b/packages/interaction/commands/package.json index 7d35603a79..cd62942a5f 100644 --- a/packages/interaction/commands/package.json +++ b/packages/interaction/commands/package.json @@ -15,6 +15,10 @@ "types": "./lib/types/invariant.d.ts", "default": "./lib/invariant.js" }, + "./types": { + "types": "./lib/types/types.d.ts", + "default": "./lib/types/types.js" + }, "./brand": { "types": "./lib/types/brand.d.ts", "default": "./lib/types/brand.js" diff --git a/packages/interaction/commands/src/index.ts b/packages/interaction/commands/src/index.ts index ba7b449228..9f4110568e 100644 --- a/packages/interaction/commands/src/index.ts +++ b/packages/interaction/commands/src/index.ts @@ -11,24 +11,12 @@ import type { Session, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-se import { CommandId } from './brand.ts' export { CommandId } from './brand.ts' +export type { CommandSource, CommandSourceMap } from './types.ts' export const name = 'commands' const COMMAND_NAME = /^[a-z][a-z0-9_-]*$/u -/** - * Producer record for one command invocation (the `command/run` event's - * source slot). Merge-extensible sum type mirroring `MessageSourceMap`'s - * shape; minimal today because every executor caller is a human-facing UI - * surface dispatching a human-typed line, so the sole variant is `user`. - */ -export interface CommandSourceMap { - user: { kind: 'user' } -} - -/** The union over {@link CommandSourceMap} — who issued a command line. */ -export type CommandSource = CommandSourceMap[keyof CommandSourceMap] - /** Immutable metadata for a command's optional unstructured input. */ export interface CommandInputDescriptor { /** Placeholder shown before the user supplies free-form input. */ @@ -131,34 +119,6 @@ class CommandLayer implements ScopeLayer { } } -declare module '@deepseek-ai/dsh-session/types' { - interface SessionEventMap { - /** - * A resolved slash command entered its handler. Log-only (never model - * surface); paired with `command/done` by `commandId`, mirroring the - * `tool/call`↔`tool/result` pairing. The payload is structured — `name` - * and `args` are `parseCommand`'s own split (name and verbatim rawInput, - * separator whitespace included), so a consumer (a projection unit - * folding its own command records, a rich command card) never re-parses - * a line. `args` is absent when the definition sets `recordInput: false` - * because an authoritative domain event owns the input payload. - */ - 'command/run': { commandId: CommandId; name: string; args?: string; source: CommandSource } - /** - * The paired command settled. `kind`/`text` carry the handler's verbatim - * outcome (a thrown/aborted handler settles as `kind: 'error'` with the - * rendered failure). A successful command may identify the earlier - * authoritative domain event for a richer client-computed presentation. - */ - 'command/done': { - commandId: CommandId - kind: 'success' | 'error' - text?: string - sourceEventSeq?: number - } - } -} - declare module 'cordis' { interface Context { commands: CommandService diff --git a/packages/interaction/commands/src/types.ts b/packages/interaction/commands/src/types.ts new file mode 100644 index 0000000000..2b81071d37 --- /dev/null +++ b/packages/interaction/commands/src/types.ts @@ -0,0 +1,48 @@ +/** + * Durable command event vocabulary shared with type-only consumers. + * + * @module @deepseek-ai/dsh-commands/types + */ + +import type { CommandId } from './brand.ts' + +/** + * Producer record for one command invocation (the `command/run` event's + * source slot). Merge-extensible sum type mirroring `MessageSourceMap`'s + * shape; minimal today because every executor caller is a human-facing UI + * surface dispatching a human-typed line, so the sole variant is `user`. + */ +export interface CommandSourceMap { + user: { kind: 'user' } +} + +/** The union over {@link CommandSourceMap} — who issued a command line. */ +export type CommandSource = CommandSourceMap[keyof CommandSourceMap] + +declare module '@deepseek-ai/dsh-session/types' { + interface SessionEventMap { + /** + * A resolved slash command entered its handler. Log-only (never model + * surface); paired with `command/done` by `commandId`, mirroring the + * `tool/call`↔`tool/result` pairing. The payload is structured — `name` + * and `args` are `parseCommand`'s own split (name and verbatim rawInput, + * separator whitespace included), so a consumer (a projection unit + * folding its own command records, a rich command card) never re-parses + * a line. `args` is absent when the definition sets `recordInput: false` + * because an authoritative domain event owns the input payload. + */ + 'command/run': { commandId: CommandId; name: string; args?: string; source: CommandSource } + /** + * The paired command settled. `kind`/`text` carry the handler's verbatim + * outcome (a thrown/aborted handler settles as `kind: 'error'` with the + * rendered failure). A successful command may identify the earlier + * authoritative domain event for a richer client-computed presentation. + */ + 'command/done': { + commandId: CommandId + kind: 'success' | 'error' + text?: string + sourceEventSeq?: number + } + } +} diff --git a/packages/llm/llm-retry/package.json b/packages/llm/llm-retry/package.json index 1685d0e552..809b5d14e5 100644 --- a/packages/llm/llm-retry/package.json +++ b/packages/llm/llm-retry/package.json @@ -19,10 +19,6 @@ "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" }, - "./brand": { - "types": "./lib/types/brand.d.ts", - "default": "./lib/types/brand.js" - }, "./package.json": "./package.json" }, "files": [ diff --git a/packages/llm/llm-retry/src/types.ts b/packages/llm/llm-retry/src/types.ts index c698b68446..5144939289 100644 --- a/packages/llm/llm-retry/src/types.ts +++ b/packages/llm/llm-retry/src/types.ts @@ -1,5 +1,7 @@ import type { LlmFailure } from '@deepseek-ai/dsh-llm/types' -import type { RetryId } from './brand.ts' +import { RetryId } from './brand.ts' + +export { RetryId } declare module '@deepseek-ai/dsh-session/types' { interface SessionEventMap { diff --git a/packages/llm/llm-retry/tests/invariant.spec.ts b/packages/llm/llm-retry/tests/invariant.spec.ts index 33ac3a19fa..917a6d03f8 100644 --- a/packages/llm/llm-retry/tests/invariant.spec.ts +++ b/packages/llm/llm-retry/tests/invariant.spec.ts @@ -5,7 +5,7 @@ import { createUserMessage, ProviderRequestId } from '@deepseek-ai/dsh-llm' import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout' import InvariantService from '@deepseek-ai/dsh-invariants' import * as RetryInvariant from '@deepseek-ai/dsh-llm-retry/invariant' -import { RetryId } from '@deepseek-ai/dsh-llm-retry/brand' +import { RetryId } from '@deepseek-ai/dsh-llm-retry/types' import { providerForOpenStep } from '../src/history.ts' async function setup(): Promise { diff --git a/packages/llm/llm-retry/tests/persistence.spec.ts b/packages/llm/llm-retry/tests/persistence.spec.ts index 3a605bd40e..9e17290b43 100644 --- a/packages/llm/llm-retry/tests/persistence.spec.ts +++ b/packages/llm/llm-retry/tests/persistence.spec.ts @@ -6,7 +6,7 @@ import { Context } from 'cordis' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl' import SessionPersistenceSqlite from '@deepseek-ai/dsh-session-persistence-sqlite' -import { RetryId } from '@deepseek-ai/dsh-llm-retry/brand' +import { RetryId } from '@deepseek-ai/dsh-llm-retry/types' import type {} from '../src/index.ts' const dirs: string[] = [] diff --git a/packages/llm/token-meter/tests/context-breakdown-projection.spec.ts b/packages/llm/token-meter/tests/context-breakdown-projection.spec.ts index 9251eef2c0..bfbf6a89ee 100644 --- a/packages/llm/token-meter/tests/context-breakdown-projection.spec.ts +++ b/packages/llm/token-meter/tests/context-breakdown-projection.spec.ts @@ -10,7 +10,7 @@ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import TokenMeterService from '@deepseek-ai/dsh-token-meter' import type { ContextBreakdownProjection } from '@deepseek-ai/dsh-token-meter/client' -import { CompactionId } from '@deepseek-ai/dsh-compact/brand' +import { CompactionId } from '@deepseek-ai/dsh-compact/types' import { contextBreakdownProjectionDefinition } from '../src/breakdown-projection.ts' import { estimateContent, diff --git a/packages/llm/token-meter/tests/token-usage-projection.spec.ts b/packages/llm/token-meter/tests/token-usage-projection.spec.ts index 65771fc877..3f66e0564b 100644 --- a/packages/llm/token-meter/tests/token-usage-projection.spec.ts +++ b/packages/llm/token-meter/tests/token-usage-projection.spec.ts @@ -7,7 +7,7 @@ import type { Session } from '@deepseek-ai/dsh-session' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import TokenMeterService from '@deepseek-ai/dsh-token-meter' import type { ContextPressureProjection, TokenUsageProjection } from '@deepseek-ai/dsh-token-meter/client' -import { CompactionId } from '@deepseek-ai/dsh-compact/brand' +import { CompactionId } from '@deepseek-ai/dsh-compact/types' const ZERO: TokenUsageProjection = { uncachedInputTokens: 0, diff --git a/packages/support/llm-replay/tests/llm-replay.spec.ts b/packages/support/llm-replay/tests/llm-replay.spec.ts index 0276199e3e..db60b695b1 100644 --- a/packages/support/llm-replay/tests/llm-replay.spec.ts +++ b/packages/support/llm-replay/tests/llm-replay.spec.ts @@ -4,7 +4,7 @@ import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { Context } from 'cordis' import type { SessionEvent } from '@deepseek-ai/dsh-session' -import { CompactionId } from '@deepseek-ai/dsh-compact/brand' +import { CompactionId } from '@deepseek-ai/dsh-compact/types' import LlmService, { CallId, createUserMessage, GenerateOptions, LlmAdapter, StreamChunk } from '@deepseek-ai/dsh-llm' import { type ReplayEntry, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1ee272761..6fb20b9718 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1495,6 +1495,9 @@ importers: packages/client/runtime: dependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent '@deepseek-ai/dsh-client-connection': specifier: workspace:^ version: link:../connection @@ -1522,6 +1525,9 @@ importers: '@deepseek-ai/dsh-session-title': specifier: workspace:^ version: link:../../session/session-title + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools immer: specifier: ^10.1.1 version: 10.2.0 @@ -1656,6 +1662,9 @@ importers: specifier: ^2.0.0 version: 2.1.1 devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent '@deepseek-ai/dsh-client-locale': specifier: workspace:^ version: link:../locale @@ -1677,6 +1686,9 @@ importers: '@deepseek-ai/dsh-client-ui-slots': specifier: workspace:^ version: link:../ui-slots + '@deepseek-ai/dsh-commands': + specifier: workspace:^ + version: link:../../interaction/commands '@deepseek-ai/dsh-compact': specifier: workspace:^ version: link:../../compact/compact @@ -1686,6 +1698,9 @@ importers: '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants + '@deepseek-ai/dsh-llm-retry': + specifier: workspace:^ + version: link:../../llm/llm-retry '@deepseek-ai/dsh-permission': specifier: workspace:^ version: link:../../interaction/permission @@ -1701,6 +1716,9 @@ importers: '@deepseek-ai/dsh-tool-todo': specifier: workspace:^ version: link:../../todo/tool-todo + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools '@types/react': specifier: ~18.3.1 version: 18.3.31 diff --git a/scripts/gen-persistence-catalog.ts b/scripts/gen-persistence-catalog.ts index 5294242e27..173d4222cb 100644 --- a/scripts/gen-persistence-catalog.ts +++ b/scripts/gen-persistence-catalog.ts @@ -18,8 +18,11 @@ const OUT = 'docs/persistence-catalog.md' * doc-typecheck, since their imported types are not standalone-compilable). */ const FENCE = 'ts persistence-catalog' -/** The package whose module id plugin merges augment (`declare module '…'`). */ -const SESSION_MODULE = '@deepseek-ai/dsh-session' +/** The package that owns the durable event vocabulary. */ +const SESSION_PACKAGE = '@deepseek-ai/dsh-session' + +/** The type-only module that plugin declaration merges augment. */ +const SESSION_TYPES_MODULE = '@deepseek-ai/dsh-session/types' /** Event-envelope declarations rendered before the per-event vocabulary. */ const EVENT_ENVELOPE_TYPE_NAMES = [ @@ -115,7 +118,7 @@ function declarationText(text: string, sf: ts.SourceFile, node: ts.Node): string /** * Every `interface SessionEventMap` declaration in a source file: the owning * top-level declaration (in `@deepseek-ai/dsh-session`) and any declaration - * merge inside a `declare module '@deepseek-ai/dsh-session'` block. Both forms + * merge inside a `declare module '@deepseek-ai/dsh-session/types'` block. Both forms * declare members of the SAME merged interface, so both are catalogued * uniformly. `topLevel` distinguishes the owning form so the caller can verify * it actually lives in the owning package — an unrelated local interface that @@ -125,7 +128,7 @@ function sessionEventMapDecls(sf: ts.SourceFile): { decl: ts.InterfaceDeclaratio const decls: { decl: ts.InterfaceDeclaration; topLevel: boolean }[] = [] for (const stmt of sf.statements) { if (ts.isInterfaceDeclaration(stmt) && stmt.name.text === 'SessionEventMap') decls.push({ decl: stmt, topLevel: true }) - if (ts.isModuleDeclaration(stmt) && ts.isStringLiteral(stmt.name) && stmt.name.text === SESSION_MODULE + if (ts.isModuleDeclaration(stmt) && ts.isStringLiteral(stmt.name) && stmt.name.text === SESSION_TYPES_MODULE && stmt.body && ts.isModuleBlock(stmt.body)) { for (const inner of stmt.body.statements) { if (ts.isInterfaceDeclaration(inner) && inner.name.text === 'SessionEventMap') decls.push({ decl: inner, topLevel: false }) @@ -174,8 +177,8 @@ export function collectLogEvents(scanRoot: string = root): LogEventEntry[] { // the owning package. Same-named interfaces elsewhere are different // types and must not enter the on-disk catalog. const pkg = packageNameFor(rel, scanRoot) - if (pkg !== SESSION_MODULE) { - violations.push(`top-level interface SessionEventMap (${declSrc}) is outside ${SESSION_MODULE} (package ${pkg ?? 'unknown'}). Rename the interface, or contribute events via declare module '${SESSION_MODULE}'.`) + if (pkg !== SESSION_PACKAGE) { + violations.push(`top-level interface SessionEventMap (${declSrc}) is outside ${SESSION_PACKAGE} (package ${pkg ?? 'unknown'}). Rename the interface, or contribute events via declare module '${SESSION_TYPES_MODULE}'.`) continue } const exported = decl.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false @@ -243,7 +246,7 @@ export function collectEventEnvelopeTypes(scanRoot: string = root): EventEnvelop const abs = resolve(scanRoot, rel) const text = readFileSync(abs, 'utf8') if (!EVENT_ENVELOPE_TYPE_NAMES.some(name => text.includes(name))) continue - if (packageNameFor(rel, scanRoot) !== SESSION_MODULE) continue + if (packageNameFor(rel, scanRoot) !== SESSION_PACKAGE) continue const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true) for (const stmt of sf.statements) { if (!ts.isTypeAliasDeclaration(stmt) || !wanted.has(stmt.name.text)) continue @@ -352,7 +355,7 @@ export function render(events: AnnotatedLogEventEntry[], envelopeTypes: EventEnv '', '# Session Persistence Event Catalog', '', - 'Every event type that can appear in a session\'s durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the generated region of [session.md](subsystems/session.md#cordis-surface) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', + 'Every event type that can appear in a session\'s durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge into `@deepseek-ai/dsh-session/types` in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the generated region of [session.md](subsystems/session.md#cordis-surface) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', '', 'This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. See [the persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md).', '', diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index cd9d020779..73431101da 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -114,12 +114,12 @@ { "doc": "docs/subsystems/core.md", "symbol": "InboxTarget", - "source": "packages/core/agent/src/inbox.ts" + "source": "packages/core/agent/src/session-types.ts" }, { "doc": "docs/subsystems/core.md", "symbol": "CancelOptions", - "source": "packages/core/agent/src/types.ts" + "source": "packages/core/agent/src/runtime-types.ts" }, { "doc": "docs/subsystems/core.md", @@ -129,22 +129,22 @@ { "doc": "docs/subsystems/core.md", "symbol": "Agent", - "source": "packages/core/agent/src/types.ts" + "source": "packages/core/agent/src/runtime-types.ts" }, { "doc": "docs/subsystems/core.md", "symbol": "PreStepDecision", - "source": "packages/core/agent/src/types.ts" + "source": "packages/core/agent/src/runtime-types.ts" }, { "doc": "docs/subsystems/core.md", "symbol": "RequestErrorAction", - "source": "packages/core/agent/src/types.ts" + "source": "packages/core/agent/src/runtime-types.ts" }, { "doc": "docs/subsystems/core.md", "symbol": "SessionStartSource", - "source": "packages/core/agent/src/types.ts" + "source": "packages/core/agent/src/runtime-types.ts" }, { "doc": "docs/subsystems/scope.md", @@ -1698,12 +1698,12 @@ { "doc": "docs/subsystems/core.md", "symbol": "AgentStatus", - "source": "packages/core/agent/src/types.ts" + "source": "packages/core/agent/src/runtime-types.ts" }, { "doc": "docs/subsystems/core.md", "symbol": "AgentOptions", - "source": "packages/core/agent/src/types.ts" + "source": "packages/core/agent/src/runtime-types.ts" } ] } diff --git a/tsconfig.base.json b/tsconfig.base.json index d8b777a2ad..c7be2d4925 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -71,8 +71,11 @@ "@deepseek-ai/dsh-llm-retry/types": ["./packages/llm/llm-retry/src/types.ts"], "@deepseek-ai/dsh-llm/message": ["./packages/llm/llm/src/message.ts"], "@deepseek-ai/dsh-commands/brand": ["./packages/interaction/commands/src/brand.ts"], + "@deepseek-ai/dsh-commands/types": ["./packages/interaction/commands/src/types.ts"], "@deepseek-ai/dsh-compact/checkpoint": ["./packages/compact/compact/src/checkpoint.ts"], + "@deepseek-ai/dsh-compact/types": ["./packages/compact/compact/src/types.ts"], "@deepseek-ai/dsh-tools/presentation": ["./packages/core/tools/src/presentation.ts"], + "@deepseek-ai/dsh-tools/types": ["./packages/core/tools/src/types.ts"], "@deepseek-ai/dsh-tool-subagent-control/list-agents": ["./packages/subagent/tool-subagent-control/src/list-agents.ts"], "@deepseek-ai/dsh-user-approval/types": ["./packages/interaction/user-approval/src/types.ts"], "@deepseek-ai/dsh-user-interaction/types": ["./packages/interaction/user-interaction/src/types.ts"],