Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/core/src/__tests__/runtime-event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = {}): RuntimeEvent {
Expand Down Expand Up @@ -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);
});
Comment thread
Astro-Han marked this conversation as resolved.

describe('continuation-start protocol', () => {
test('accepts only the replay projection version defined by v2', () => {
const continuationStart = {
Expand Down
33 changes: 1 addition & 32 deletions packages/core/src/runtime-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 (
Expand Down
17 changes: 3 additions & 14 deletions packages/core/src/runtime-inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand Down
75 changes: 5 additions & 70 deletions packages/core/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -960,25 +957,6 @@ const ASSISTANT_THINKING_SHAPE = defineObjectShape<AssistantThinking>()(
['text'],
['signature', 'providerOptions', 'parts'],
);
type MessageOrigin = NonNullable<UserMessage['origin']>;
type ScheduledTaskOrigin = Extract<MessageOrigin, { kind: 'scheduled_task' }>;
type LegacyAutomationOrigin = Extract<MessageOrigin, { kind: 'legacy_automation' }>;
type GoalOrigin = Extract<MessageOrigin, { kind: 'goal' }>;
type AgentGraphOrigin = Extract<MessageOrigin, { kind: 'agent_graph' }>;
const SCHEDULED_TASK_ORIGIN_SHAPE = defineObjectShape<ScheduledTaskOrigin>()(
['kind', 'scheduledTaskId'],
[],
);
const LEGACY_AUTOMATION_ORIGIN_SHAPE = defineObjectShape<LegacyAutomationOrigin>()(
['kind', 'automationId'],
[],
);
const GOAL_ORIGIN_SHAPE = defineObjectShape<GoalOrigin>()(['kind', 'goalId'], []);
const AGENT_GRAPH_ORIGIN_SHAPE = defineObjectShape<AgentGraphOrigin>()(
['kind', 'graphId', 'wakeId', 'attemptId'],
[],
);

const SYSTEM_NOTE_KINDS = new Set([
'session_start',
'session_resume',
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down
75 changes: 75 additions & 0 deletions packages/core/src/turn-origin.ts
Original file line number Diff line number Diff line change
@@ -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<TurnOrigin, { kind: 'scheduled_task' }>;
type LegacyAutomationOrigin = Extract<TurnOrigin, { kind: 'legacy_automation' }>;
type GoalOrigin = Extract<TurnOrigin, { kind: 'goal' }>;
type AgentGraphOrigin = Extract<TurnOrigin, { kind: 'agent_graph' }>;

const SCHEDULED_TASK_ORIGIN_SHAPE = defineObjectShape<ScheduledTaskOrigin>()(
['kind', 'scheduledTaskId'],
[],
);
const LEGACY_AUTOMATION_ORIGIN_SHAPE = defineObjectShape<LegacyAutomationOrigin>()(
['kind', 'automationId'],
[],
);
const GOAL_ORIGIN_SHAPE = defineObjectShape<GoalOrigin>()(['kind', 'goalId'], []);
const AGENT_GRAPH_ORIGIN_SHAPE = defineObjectShape<AgentGraphOrigin>()(
['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;
}
Loading