From 54dc873998f97fa2002217cf9d8065b25a5f60dd Mon Sep 17 00:00:00 2001 From: Hsin <2129830748@qq.com> Date: Sun, 16 Aug 2026 05:06:42 +0800 Subject: [PATCH] refactor(core): make TurnOrigin single-authority Move the shared TurnOrigin type and decoder into a leaf module and use it from stored-message and RuntimeEvent decoding. Generated-by: Codex --- .../core/src/__tests__/runtime-event.test.ts | 15 ++++ packages/core/src/runtime-event.ts | 33 +------- packages/core/src/runtime-inputs.ts | 17 +---- packages/core/src/session.ts | 75 ++----------------- packages/core/src/turn-origin.ts | 75 +++++++++++++++++++ 5 files changed, 99 insertions(+), 116 deletions(-) create mode 100644 packages/core/src/turn-origin.ts diff --git a/packages/core/src/__tests__/runtime-event.test.ts b/packages/core/src/__tests__/runtime-event.test.ts index 7c17dd8bb8..5a1b321a47 100644 --- a/packages/core/src/__tests__/runtime-event.test.ts +++ b/packages/core/src/__tests__/runtime-event.test.ts @@ -16,6 +16,7 @@ import { type RuntimeEventActions, } from '../runtime-event.js'; import { decodeStoredMessage } from '../session.js'; +import { decodeTurnOrigin } from '../turn-origin.js'; /** Minimal valid RuntimeEvent; callers spread overrides on top. */ function baseEvent(overrides: Partial = {}): RuntimeEvent { @@ -92,6 +93,20 @@ test('decodes released Automation origins as read-only legacy provenance', () => }); }); +test('shares one decoder across all TurnOrigin variants', () => { + const origins = [ + { kind: 'scheduled_task', scheduledTaskId: 'task-1' }, + { kind: 'goal', goalId: 'goal-1' }, + { kind: 'agent_graph', graphId: 'graph-1', wakeId: 'wake-1', attemptId: 'attempt-1' }, + ] as const; + for (const origin of origins) assert.deepEqual(decodeTurnOrigin(origin), origin); + assert.deepEqual(decodeTurnOrigin({ kind: 'automation', automationId: 'automation-1' }), { + kind: 'legacy_automation', + automationId: 'automation-1', + }); + assert.equal(decodeTurnOrigin({ kind: 'goal', goalId: 'goal-1', extra: true }), undefined); +}); + describe('continuation-start protocol', () => { test('accepts only the replay projection version defined by v2', () => { const continuationStart = { diff --git a/packages/core/src/runtime-event.ts b/packages/core/src/runtime-event.ts index fbb9229f0c..ec866a1a37 100644 --- a/packages/core/src/runtime-event.ts +++ b/packages/core/src/runtime-event.ts @@ -22,7 +22,7 @@ import { } from './events.js'; import { INTERACTION_ID_MAX_BYTES, INTERACTION_TOOL_NAME_MAX_BYTES } from './interaction.js'; import type { PermissionRequestPayload, PermissionResponse } from './permission.js'; -import type { TurnOrigin } from './runtime-inputs.js'; +import { decodeTurnOrigin, type TurnOrigin } from './turn-origin.js'; import type { UserQuestionRequest } from './user-question.js'; import { defineObjectShape, @@ -696,37 +696,6 @@ function isTurnOrigin(value: unknown): value is TurnOrigin { return decodeTurnOrigin(value) !== undefined; } -function decodeTurnOrigin(value: unknown): TurnOrigin | undefined { - if (!isRecord(value)) return undefined; - if (value.kind === 'scheduled_task') { - return Object.keys(value).length === 2 && typeof value.scheduledTaskId === 'string' - ? { kind: 'scheduled_task', scheduledTaskId: value.scheduledTaskId } - : undefined; - } - if (value.kind === 'automation' || value.kind === 'legacy_automation') { - return Object.keys(value).length === 2 && typeof value.automationId === 'string' - ? { kind: 'legacy_automation', automationId: value.automationId } - : undefined; - } - if (value.kind === 'goal') { - return Object.keys(value).length === 2 && typeof value.goalId === 'string' - ? { kind: 'goal', goalId: value.goalId } - : undefined; - } - return value.kind === 'agent_graph' && - Object.keys(value).length === 4 && - typeof value.graphId === 'string' && - typeof value.wakeId === 'string' && - typeof value.attemptId === 'string' - ? { - kind: 'agent_graph', - graphId: value.graphId, - wakeId: value.wakeId, - attemptId: value.attemptId, - } - : undefined; -} - function isRuntimeEventActions(value: unknown): value is RuntimeEventActions { if (!isRecord(value) || !hasExactShape(value, RUNTIME_ACTIONS_SHAPE)) return false; if ( diff --git a/packages/core/src/runtime-inputs.ts b/packages/core/src/runtime-inputs.ts index 6f48113f54..ece4766e9f 100644 --- a/packages/core/src/runtime-inputs.ts +++ b/packages/core/src/runtime-inputs.ts @@ -19,6 +19,9 @@ import type { OrchestrationMode, TurnOrchestration } from './orchestration.js'; import type { SessionStartMode } from './explore-agent.js'; import type { SubagentWorkspaceBinding } from './subagent-workspace.js'; import type { ToolMode } from './tool-mode.js'; +import type { TurnOrigin } from './turn-origin.js'; + +export type { TurnOrigin } from './turn-origin.js'; export type { TurnOrchestration } from './orchestration.js'; @@ -103,20 +106,6 @@ export interface UserMessageInput extends MessageContent { origin?: TurnOrigin; } -/** Non-user trigger source for a turn. */ -export type TurnOrigin = - | { kind: 'scheduled_task'; scheduledTaskId: string } - | { kind: 'legacy_automation'; automationId: string } - | { kind: 'goal'; goalId: string } - | { - kind: 'agent_graph'; - graphId: string; - /** Durable, graph-snapshot-scoped idempotency key for this supervisor wake. */ - wakeId: string; - /** Durable identity of one delivery attempt for the wake. */ - attemptId: string; - }; - export interface AgentSpec { id: string; name: string; diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index e9dc928dbd..77c357275b 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -25,6 +25,7 @@ import { isPermissionDecisionFields } from './interaction-record-schema.js'; import { isTokenUsageFields, type TokenUsageFields } from './usage-record-schema.js'; import { decodeCanonicalToolResultContent } from './tool-result-record-schema.js'; import type { SubagentWorkspaceBinding } from './subagent-workspace.js'; +import { decodeTurnOrigin, type TurnOrigin } from './turn-origin.js'; export { DEEP_RESEARCH_SESSION_LABEL, isDeepResearchSession } from './explore-agent.js'; @@ -680,12 +681,8 @@ export interface UserMessage extends MessageContent { /** Canonical RuntimeEvent that materialized this mid-Turn steering projection. */ steeringEventId?: string; /** Non-user trigger source. Lets the chat mark turns the user did not - * hand-type. Mirrors TurnOrigin in runtime-inputs. */ - origin?: - | { kind: 'scheduled_task'; scheduledTaskId: string } - | { kind: 'legacy_automation'; automationId: string } - | { kind: 'goal'; goalId: string } - | { kind: 'agent_graph'; graphId: string; wakeId: string; attemptId: string }; + * hand-type. */ + origin?: TurnOrigin; } /** Prefer the human-facing view of a user message when one was stored. */ @@ -960,25 +957,6 @@ const ASSISTANT_THINKING_SHAPE = defineObjectShape()( ['text'], ['signature', 'providerOptions', 'parts'], ); -type MessageOrigin = NonNullable; -type ScheduledTaskOrigin = Extract; -type LegacyAutomationOrigin = Extract; -type GoalOrigin = Extract; -type AgentGraphOrigin = Extract; -const SCHEDULED_TASK_ORIGIN_SHAPE = defineObjectShape()( - ['kind', 'scheduledTaskId'], - [], -); -const LEGACY_AUTOMATION_ORIGIN_SHAPE = defineObjectShape()( - ['kind', 'automationId'], - [], -); -const GOAL_ORIGIN_SHAPE = defineObjectShape()(['kind', 'goalId'], []); -const AGENT_GRAPH_ORIGIN_SHAPE = defineObjectShape()( - ['kind', 'graphId', 'wakeId', 'attemptId'], - [], -); - const SYSTEM_NOTE_KINDS = new Set([ 'session_start', 'session_resume', @@ -999,10 +977,10 @@ export function decodeStoredMessage(value: unknown): StoredMessage { if ( hasExactShape(message, USER_MESSAGE_SHAPE) && hasMessageEnvelope(message, true) && - (message.origin === undefined || decodeMessageOrigin(message.origin) !== undefined) + (message.origin === undefined || decodeTurnOrigin(message.origin) !== undefined) ) { const { displayText, attachments, quotes, inlineReferences, origin, ...envelope } = message; - const decodedOrigin = origin === undefined ? undefined : decodeMessageOrigin(origin); + const decodedOrigin = origin === undefined ? undefined : decodeTurnOrigin(origin); try { return { ...envelope, @@ -1157,49 +1135,6 @@ function isAssistantThinking(value: unknown): value is AssistantThinking { ); } -function isGoalOrigin(value: unknown): value is GoalOrigin { - return ( - isRecord(value) && - hasExactShape(value, GOAL_ORIGIN_SHAPE) && - value.kind === 'goal' && - typeof value.goalId === 'string' - ); -} - -function isAgentGraphOrigin(value: unknown): value is AgentGraphOrigin { - return ( - isRecord(value) && - hasExactShape(value, AGENT_GRAPH_ORIGIN_SHAPE) && - value.kind === 'agent_graph' && - typeof value.graphId === 'string' && - typeof value.wakeId === 'string' && - typeof value.attemptId === 'string' - ); -} - -function isScheduledTaskOrigin(value: unknown): value is ScheduledTaskOrigin { - return ( - isRecord(value) && - hasExactShape(value, SCHEDULED_TASK_ORIGIN_SHAPE) && - value.kind === 'scheduled_task' && - typeof value.scheduledTaskId === 'string' - ); -} - -function decodeMessageOrigin(value: unknown): MessageOrigin | undefined { - if (isScheduledTaskOrigin(value) || isGoalOrigin(value) || isAgentGraphOrigin(value)) - return value; - if ( - isRecord(value) && - hasExactShape(value, LEGACY_AUTOMATION_ORIGIN_SHAPE) && - (value.kind === 'automation' || value.kind === 'legacy_automation') && - typeof value.automationId === 'string' - ) { - return { kind: 'legacy_automation', automationId: value.automationId }; - } - return undefined; -} - function isOptionalFiniteDuration(value: unknown): boolean { return value === undefined || isFiniteNumber(value); } diff --git a/packages/core/src/turn-origin.ts b/packages/core/src/turn-origin.ts new file mode 100644 index 0000000000..d5edd5ccb1 --- /dev/null +++ b/packages/core/src/turn-origin.ts @@ -0,0 +1,75 @@ +import { defineObjectShape, hasExactShape, isRecord } from './record-schema.js'; + +/** Non-user trigger source for a turn. */ +export type TurnOrigin = + | { kind: 'scheduled_task'; scheduledTaskId: string } + | { kind: 'legacy_automation'; automationId: string } + | { kind: 'goal'; goalId: string } + | { + kind: 'agent_graph'; + graphId: string; + /** Durable, graph-snapshot-scoped idempotency key for this supervisor wake. */ + wakeId: string; + /** Durable identity of one delivery attempt for the wake. */ + attemptId: string; + }; + +type ScheduledTaskOrigin = Extract; +type LegacyAutomationOrigin = Extract; +type GoalOrigin = Extract; +type AgentGraphOrigin = Extract; + +const SCHEDULED_TASK_ORIGIN_SHAPE = defineObjectShape()( + ['kind', 'scheduledTaskId'], + [], +); +const LEGACY_AUTOMATION_ORIGIN_SHAPE = defineObjectShape()( + ['kind', 'automationId'], + [], +); +const GOAL_ORIGIN_SHAPE = defineObjectShape()(['kind', 'goalId'], []); +const AGENT_GRAPH_ORIGIN_SHAPE = defineObjectShape()( + ['kind', 'graphId', 'wakeId', 'attemptId'], + [], +); + +/** Decode a persisted or runtime turn origin, normalizing released Automation rows. */ +export function decodeTurnOrigin(value: unknown): TurnOrigin | undefined { + if (!isRecord(value)) return undefined; + if ( + hasExactShape(value, SCHEDULED_TASK_ORIGIN_SHAPE) && + value.kind === 'scheduled_task' && + typeof value.scheduledTaskId === 'string' + ) { + return { kind: 'scheduled_task', scheduledTaskId: value.scheduledTaskId }; + } + if ( + hasExactShape(value, LEGACY_AUTOMATION_ORIGIN_SHAPE) && + (value.kind === 'automation' || value.kind === 'legacy_automation') && + typeof value.automationId === 'string' + ) { + return { kind: 'legacy_automation', automationId: value.automationId }; + } + if ( + hasExactShape(value, GOAL_ORIGIN_SHAPE) && + value.kind === 'goal' && + typeof value.goalId === 'string' + ) { + return { kind: 'goal', goalId: value.goalId }; + } + if ( + hasExactShape(value, AGENT_GRAPH_ORIGIN_SHAPE) && + value.kind === 'agent_graph' && + typeof value.graphId === 'string' && + typeof value.wakeId === 'string' && + typeof value.attemptId === 'string' + ) { + return { + kind: 'agent_graph', + graphId: value.graphId, + wakeId: value.wakeId, + attemptId: value.attemptId, + }; + } + return undefined; +}