@ct-agents/protocol 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/memory.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 通用 Agent Memory 契约。
3
+ *
4
+ * Memory 在协议层只是一组按实现访问范围隔离的 Markdown 文档;访问范围由实现包在构造期绑定,
5
+ * Harness、工具和 Orchestrator 只依赖这里的 scope-free KV 接口。
6
+ *
7
+ * 目录召回模型:MemoryStore.list() 返回所有存活条目的 {name, description, updatedAt},
8
+ * load-memory 工具在每次 wake 时自动生成目录注入自身 description,无需手工维护根索引。
9
+ */
10
+ export type MemoryRecord = {
11
+ name: string;
12
+ description: string;
13
+ content: string;
14
+ version: number;
15
+ deleted: boolean;
16
+ source?: string;
17
+ idempotencyKey?: string;
18
+ createdAt: string;
19
+ updatedAt: string;
20
+ };
21
+
22
+ export type AddMemoryInput = {
23
+ name: string;
24
+ description: string;
25
+ content: string;
26
+ source?: string;
27
+ idempotencyKey?: string;
28
+ };
29
+
30
+ export type UpdateMemoryInput = {
31
+ name: string;
32
+ description?: string;
33
+ content: string;
34
+ baseVersion: number;
35
+ source?: string;
36
+ };
37
+
38
+ export type DeleteMemoryInput = {
39
+ name: string;
40
+ baseVersion: number;
41
+ };
42
+
43
+ /** list() 返回条目,仅存活项(deleted=false)。 */
44
+ export type MemoryListItem = {
45
+ name: string;
46
+ description: string;
47
+ updatedAt: string;
48
+ };
49
+
50
+ export interface MemoryStore {
51
+ get(name: string): Promise<MemoryRecord | null>;
52
+ add(input: AddMemoryInput): Promise<MemoryRecord>;
53
+ update(input: UpdateMemoryInput): Promise<MemoryRecord>;
54
+ delete(input: DeleteMemoryInput): Promise<MemoryRecord>;
55
+ /** 返回当前所有存活 memory 的目录项,供 load-memory 自动生成目录。 */
56
+ list(): Promise<MemoryListItem[]>;
57
+ }
@@ -0,0 +1,128 @@
1
+ import type {
2
+ DerivedSessionState,
3
+ HarnessTrigger,
4
+ SessionEvent,
5
+ SessionEventPayload,
6
+ SessionRecord,
7
+ ExecutionScope,
8
+ } from './session/index.js';
9
+ import type { SessionStore } from './session/store.js';
10
+ import type {
11
+ FailOpenToolCallsInput as EnvironmentFailOpenToolCallsInput,
12
+ EnvironmentConfigStore,
13
+ ResourceRegistry,
14
+ } from './environment.js';
15
+ import type { PromptRegistry } from './prompt.js';
16
+ import type { ToolRegistry } from './tool.js';
17
+ import type { Store } from './store.js';
18
+
19
+ export type WakeSessionResult = {
20
+ accepted: boolean;
21
+ reason?: string;
22
+ trigger?: HarnessTrigger;
23
+ };
24
+
25
+ export type InterruptSessionResult = {
26
+ accepted: boolean;
27
+ reason?: string;
28
+ };
29
+
30
+ export type SessionWakeHandle = {
31
+ sessionId: string;
32
+ load(): Promise<LoadedSessionExecutionContext | null>;
33
+ };
34
+
35
+ export interface Orchestrator {
36
+ wakeSession(handle: SessionWakeHandle): Promise<WakeSessionResult>;
37
+ interruptSession(sessionId: string): Promise<InterruptSessionResult>;
38
+ reconcileStaleSessions(): Promise<void>;
39
+ start(): void;
40
+ stop(): Promise<void>;
41
+ }
42
+
43
+ export type HarnessConfig = {
44
+ appId?: string;
45
+ id: string;
46
+ version: number;
47
+ name: string;
48
+ description?: string;
49
+ model?: string;
50
+ promptFragmentIds: string[];
51
+ toolIds: string[];
52
+ subAgentHarnessIds: string[];
53
+ skills: Array<Record<string, unknown>>;
54
+ metadata: Record<string, unknown>;
55
+ maxModelTurns?: number;
56
+ contextResolve?: {
57
+ keys: string[];
58
+ };
59
+ createdAt?: string;
60
+ };
61
+
62
+ export type HarnessConfigLatestKey = string | { appId?: string; id: string };
63
+ export type HarnessConfigListQuery = { appId?: string };
64
+
65
+ export interface HarnessConfigStore extends Store<HarnessConfig, { appId?: string; id: string; version: number }> {
66
+ getLatest(key: HarnessConfigLatestKey): Promise<HarnessConfig | null>;
67
+ list(query?: HarnessConfigListQuery): Promise<HarnessConfig[]>;
68
+ listVersions(key: HarnessConfigLatestKey): Promise<HarnessConfig[]>;
69
+ save(input: HarnessConfig): Promise<HarnessConfig>;
70
+ publish(input: Omit<HarnessConfig, 'version' | 'createdAt'> & { baseVersion: number }): Promise<HarnessConfig>;
71
+ }
72
+
73
+ export type LoadedSessionExecutionContext = {
74
+ session: SessionRecord;
75
+ state: DerivedSessionState;
76
+ events: SessionEvent[];
77
+ sessionBackend: SessionStore;
78
+ };
79
+
80
+ export interface SessionExecutionContextLoader {
81
+ load(
82
+ sessionId: string,
83
+ scope?: ExecutionScope,
84
+ ): Promise<LoadedSessionExecutionContext | null>;
85
+ }
86
+
87
+ export interface ToolLifecycleReconciler {
88
+ failOpenToolCalls(input: EnvironmentFailOpenToolCallsInput): Promise<void>;
89
+ }
90
+
91
+ export type StaleRunningSessionCandidate = SessionWakeHandle;
92
+
93
+ export interface StaleSessionScanner {
94
+ findStaleRunningSessions(input: {
95
+ observedAt: string;
96
+ staleRunningThresholdMs: number;
97
+ }): Promise<StaleRunningSessionCandidate[]>;
98
+ }
99
+
100
+ export type RetrySessionCandidate = SessionWakeHandle;
101
+
102
+ export interface RetrySessionScanner {
103
+ findDueRetrySessions(input: {
104
+ observedAt: string;
105
+ }): Promise<RetrySessionCandidate[]>;
106
+ }
107
+
108
+ export type QueuedToolWakeSessionCandidate = SessionWakeHandle;
109
+
110
+ export interface QueuedToolWakeScanner {
111
+ findQueuedToolWakeSessions(input: {
112
+ observedAt: string;
113
+ }): Promise<QueuedToolWakeSessionCandidate[]>;
114
+ }
115
+
116
+ export type ContextResolveWakeSessionCandidate = SessionWakeHandle;
117
+
118
+ export interface ContextResolveWakeScanner {
119
+ findContextResolveWakeSessions(input: {
120
+ observedAt: string;
121
+ }): Promise<ContextResolveWakeSessionCandidate[]>;
122
+ }
123
+
124
+ export type OrchestratorRetryPolicy = {
125
+ maxAttempts?: number;
126
+ baseDelayMs?: number;
127
+ maxDelayMs?: number;
128
+ };
package/src/prompt.ts ADDED
@@ -0,0 +1,122 @@
1
+ import type { ResourceSlots } from './environment.js';
2
+ import type { Store } from './store.js';
3
+
4
+ export type PromptFragmentSourceKind = 'static' | 'persisted';
5
+
6
+ export type PromptFragmentView = {
7
+ appId?: string | null;
8
+ id: string;
9
+ version: number;
10
+ name: string;
11
+ description?: string;
12
+ content: string;
13
+ enabled: boolean;
14
+ source: {
15
+ kind: PromptFragmentSourceKind;
16
+ sourcePath?: string;
17
+ };
18
+ createdAt?: string;
19
+ updatedAt?: string;
20
+ };
21
+
22
+ export type CreatePromptFragmentInput = {
23
+ appId?: string | null;
24
+ name: string;
25
+ description?: string;
26
+ content: string;
27
+ enabled?: boolean;
28
+ };
29
+
30
+ export type UpdatePromptFragmentInput = {
31
+ id: string;
32
+ baseVersion: number;
33
+ name?: string;
34
+ description?: string;
35
+ content?: string;
36
+ enabled?: boolean;
37
+ };
38
+
39
+ export type PromptFragmentListQuery = {
40
+ appId?: string;
41
+ includeGlobal?: boolean;
42
+ };
43
+
44
+ /**
45
+ * 组装系统提示词的输入参数。
46
+ *
47
+ * orchestrator 在每次 session wake 时调用 `PromptRegistry.assemblePrompt()`,
48
+ * 将 harness 配置中的 fragment ID 列表、metadata 和已实例化的 resources 传入,
49
+ * 最终拼接出完整的系统提示词。
50
+ *
51
+ * @template TMetadata - metadata 的具体 shape,默认为宽泛的 Record
52
+ * @template TResources - resources 的具体 shape,默认为宽泛的 Record
53
+ */
54
+ export type AssemblePromptInput<
55
+ TMetadata extends Record<string, unknown> = Record<string, unknown>,
56
+ TResources extends object = object,
57
+ > = {
58
+ /** 当前 session 所属 app;用于只装配该 app 可见的 fragment 与全局 fragment */
59
+ appId?: string;
60
+ /** harness 配置中声明的 prompt fragment ID 列表,按顺序拼接 */
61
+ promptFragmentIds?: string[];
62
+ /** 来自 harness 配置的静态 metadata(如行为规则开关) */
63
+ harnessMetadata?: TMetadata;
64
+ /** 来自 session 的动态 metadata(如 subject.id、applicationId) */
65
+ sessionMetadata?: TMetadata;
66
+ /** 已实例化且绑定了 scope 的 resource 集合,供 generator 异步获取数据 */
67
+ resources?: ResourceSlots & TResources;
68
+ /** 运行时动态追加的 prompt 片段(不经过 fragment store) */
69
+ dynamicFragments?: string[];
70
+ };
71
+
72
+ export interface PromptFragmentStore extends Store<PromptFragmentView> {
73
+ list(query?: PromptFragmentListQuery): Promise<PromptFragmentView[]>;
74
+ create(input: CreatePromptFragmentInput): Promise<PromptFragmentView>;
75
+ update(input: UpdatePromptFragmentInput): Promise<PromptFragmentView>;
76
+ delete(id: string): Promise<void>;
77
+ }
78
+
79
+ /**
80
+ * 提示词注册中心,管理 prompt fragments 的 CRUD 和系统提示词组装。
81
+ *
82
+ * orchestrator 在每次 session wake 时调用 `assemblePrompt()`,
83
+ * 按 harness 配置中声明的 fragment ID 顺序拼接最终系统提示词。
84
+ */
85
+ export interface PromptRegistry {
86
+ assemblePrompt(input: AssemblePromptInput): Promise<string>;
87
+ listPromptFragments(query?: PromptFragmentListQuery): Promise<PromptFragmentView[]>;
88
+ getPromptFragment(id: string): Promise<PromptFragmentView | null>;
89
+ createPromptFragment(input: CreatePromptFragmentInput): Promise<PromptFragmentView>;
90
+ updatePromptFragment(input: UpdatePromptFragmentInput): Promise<PromptFragmentView>;
91
+ deletePromptFragment(id: string): Promise<void>;
92
+ }
93
+
94
+ /**
95
+ * 动态提示词生成器,通过 `generator:<id>` fragment ID 引用。
96
+ *
97
+ * 与静态 fragment 不同,generator 可以在运行时异步获取数据(通过 resources),
98
+ * 并根据 session metadata 动态生成提示词段落。
99
+ *
100
+ * @template TMetadata - generate() 接收的 metadata shape,默认宽泛
101
+ * @template TResources - generate() 接收的 resources shape,默认宽泛
102
+ */
103
+ export interface PromptGenerator<
104
+ TMetadata extends Record<string, unknown> = Record<string, unknown>,
105
+ TResources extends object = object,
106
+ > {
107
+ id: string;
108
+ generate(input: {
109
+ /** harnessMetadata 与 sessionMetadata 合并后的上下文 */
110
+ metadata: TMetadata;
111
+ /** 已实例化且绑定了 scope 的 resource 集合 */
112
+ resources: ResourceSlots & TResources;
113
+ }): Promise<string> | string;
114
+ }
115
+
116
+ /**
117
+ * generator 注册表,通过 ID 查找和注册 PromptGenerator 实例。
118
+ */
119
+ export interface GeneratorRegistry {
120
+ get(id: string): PromptGenerator | undefined;
121
+ register(generator: PromptGenerator): void;
122
+ }
@@ -0,0 +1,20 @@
1
+ import type { StopReason } from './events.js';
2
+ import type { SessionEvent } from './events.js';
3
+
4
+ export type DerivedSessionStatus = 'idle' | 'running' | 'rescheduled' | 'terminated';
5
+
6
+ export interface DerivedSessionState {
7
+ status: DerivedSessionStatus;
8
+ stopReason?: StopReason;
9
+ lastSeq: number;
10
+ handledInputSeq: number;
11
+ activeInputSeqMax?: number;
12
+ pendingUserMessageEventIds: string[];
13
+ pendingActionEventIds: string[];
14
+ openToolCallEventIds: string[];
15
+ orphanedToolCallEventIds: string[];
16
+ }
17
+
18
+ export type SessionStreamEnvelope =
19
+ | { kind: 'event'; event: SessionEvent }
20
+ | { kind: 'heartbeat'; emittedAt: string };
@@ -0,0 +1,298 @@
1
+ import type { SessionId } from './ids.js';
2
+ import type { ExecutionScope } from './scope.js';
3
+
4
+ // ─── 用户侧 payload ────────────────────────────────────────────────────
5
+
6
+ /** user.message 内容 part:文本 / 图片引用 / 文档引用。 */
7
+ export type UserMessageContent =
8
+ | { type: 'text'; text: string }
9
+ | { type: 'image'; mimeType: string; dataRef: string }
10
+ | { type: 'document'; mimeType: string; dataRef: string; title?: string };
11
+
12
+ export type UserMessagePayload = {
13
+ // 内容 part 列表;运行时「至少一条」由 HTTP 边界 zod schema 的 `.min(1)` 保证,
14
+ // 类型层用普通数组以与 zod array 推断一致(zod 的 min 不影响类型)。
15
+ content: UserMessageContent[];
16
+ };
17
+
18
+ export type UserInterruptPayload = {
19
+ reason?: string;
20
+ };
21
+
22
+ export type UserActionResolvedPayload = {
23
+ kind: 'ask_user' | 'tool_confirmation' | 'custom_tool_result';
24
+ result: unknown;
25
+ };
26
+
27
+ // ─── assistant 侧 payload ───────────────────────────────────────────────
28
+
29
+ export type AssistantOutputCompletedPayload = {
30
+ text: string;
31
+ };
32
+
33
+ export type AssistantReasoningCompletedPayload = {
34
+ text: string;
35
+ displayMode?: 'summary' | 'full';
36
+ // provider 原生续传状态。协议层不解释其内部结构,只负责随事件落库并在后续模型回放时原样交还。
37
+ providerState?: Record<string, unknown>;
38
+ };
39
+
40
+ // ─── 工具生命周期 payload ───────────────────────────────────────────────
41
+
42
+ export type ToolCalledPayload = {
43
+ toolName: string;
44
+ // 模型 provider 返回的原生工具调用 id。Anthropic extended thinking 会校验同轮
45
+ // thinking + tool_use 的原始结构,后续回放必须优先使用该 id,而不是本地事件 id。
46
+ providerToolCallId?: string;
47
+ input: unknown;
48
+ };
49
+
50
+ export type ToolCompletedPayload = {
51
+ toolName: string;
52
+ result: unknown;
53
+ };
54
+
55
+ export type ToolErrorPayload = {
56
+ code?: string;
57
+ message: string;
58
+ retryable?: boolean;
59
+ details?: unknown;
60
+ };
61
+
62
+ export type ToolFailedPayload = {
63
+ toolName: string;
64
+ error: ToolErrorPayload;
65
+ };
66
+
67
+ export type ToolInterruptedPayload = {
68
+ toolName: string;
69
+ reason: 'user_interrupt' | 'abort_signal';
70
+ };
71
+
72
+ /**
73
+ * 工具调用未写入终态(进程崩溃 / stale running 收口)时,盖在 `tool.failed.error.code` 上的固定字面值。
74
+ *
75
+ * 这是 protocol 的**契约常量**:写方(`@ct-agents/environment`)与读方(`@ct-agents/session` 的派生状态)
76
+ * 必须对该字面值达成一致才能互操作,且无任何依赖——符合 protocol「零依赖契约字面量」判据。
77
+ */
78
+ export const TOOL_ORPHANED_ERROR_CODE = 'TOOL_ORPHANED' as const;
79
+
80
+ export type ToolOrphanedReason = 'process_crash' | 'stale_running_reconcile' | 'unknown';
81
+
82
+ export type ToolOrphanedErrorDetails = {
83
+ reason: ToolOrphanedReason;
84
+ observedAt: string;
85
+ };
86
+
87
+ // ─── 子 Agent / action payload ──────────────────────────────────────────
88
+
89
+ export type SessionChildSpawnedPayload = {
90
+ childSessionId: string;
91
+ agentId: string;
92
+ task: string;
93
+ };
94
+
95
+ export type AgentActionRequestedPayload = {
96
+ kind: 'ask_user' | 'tool_confirmation' | 'custom_tool_result';
97
+ prompt?: string;
98
+ input?: unknown;
99
+ allowedResults?: unknown[];
100
+ resultSchema?: Record<string, unknown>;
101
+ display?: {
102
+ title?: string;
103
+ summary?: string;
104
+ };
105
+ expiresAt?: string;
106
+ };
107
+
108
+ // ─── context resolve payload ───────────────────────────────────────────
109
+
110
+ export type ContextResolveRequestPayload = {
111
+ kind: 'prompt_context';
112
+ keys: string[];
113
+ };
114
+
115
+ export type AgentContextResolvedPayload = {
116
+ workId: string;
117
+ request: ContextResolveRequestPayload;
118
+ snapshot: Record<string, unknown>;
119
+ };
120
+
121
+ export type AgentContextFailedPayload = {
122
+ workId: string;
123
+ request: ContextResolveRequestPayload;
124
+ error: ToolErrorPayload;
125
+ };
126
+
127
+ // ─── session 状态机 payload ─────────────────────────────────────────────
128
+
129
+ export type HarnessTrigger =
130
+ | { type: 'user_message'; inputSeqMax: number }
131
+ | { type: 'action_resolved' }
132
+ | { type: 'resume_running' }
133
+ | { type: 'retry' };
134
+
135
+ export type SessionStatusRunningPayload = {
136
+ inputSeqMax: number;
137
+ trigger: HarnessTrigger;
138
+ };
139
+
140
+ export type StopReason =
141
+ | { type: 'end_turn' }
142
+ | { type: 'requires_action'; eventIds: string[] }
143
+ | { type: 'queued_tool'; workIds: string[] }
144
+ | { type: 'context_resolve'; workIds: string[] }
145
+ | { type: 'interrupted'; eventId: string }
146
+ | { type: 'retries_exhausted' }
147
+ | { type: 'error'; message: string };
148
+
149
+ export type SessionStatusIdlePayload = {
150
+ handledInputSeq: number;
151
+ stopReason: StopReason;
152
+ };
153
+
154
+ export type SessionStatusRescheduledPayload = {
155
+ retryAt?: string;
156
+ attempt?: number;
157
+ reason?: string;
158
+ };
159
+
160
+ export type SessionStatusTerminatedPayload = {
161
+ reason?: string;
162
+ };
163
+
164
+ export type SessionErrorPayload = {
165
+ message: string;
166
+ retryable?: boolean;
167
+ details?: unknown;
168
+ };
169
+
170
+ // ─── 事件类型登记表 ─────────────────────────────────────────────────────
171
+
172
+ export const sessionEventTypes = [
173
+ 'user.message',
174
+ 'user.action.resolved',
175
+ 'user.interrupt',
176
+ 'assistant.output.started',
177
+ 'assistant.output.completed',
178
+ 'assistant.reasoning.started',
179
+ 'assistant.reasoning.completed',
180
+ 'tool.called',
181
+ 'tool.completed',
182
+ 'tool.failed',
183
+ 'tool.interrupted',
184
+ 'agent.action.requested',
185
+ 'agent.context.resolved',
186
+ 'agent.context.failed',
187
+ 'session.child.spawned',
188
+ 'session.status.running',
189
+ 'session.status.idle',
190
+ 'session.status.rescheduled',
191
+ 'session.status.terminated',
192
+ 'session.error',
193
+ 'session.deleted',
194
+ ] as const;
195
+
196
+ export type CoreSessionEventType = (typeof sessionEventTypes)[number];
197
+
198
+ const sessionEventTypeSet = new Set<string>(sessionEventTypes);
199
+
200
+ export function isSessionEventType(value: string): value is SessionEventType {
201
+ return sessionEventTypeSet.has(value);
202
+ }
203
+
204
+ export interface CoreSessionEventPayloadByType {
205
+ 'user.message': UserMessagePayload;
206
+ 'user.action.resolved': UserActionResolvedPayload;
207
+ 'user.interrupt': UserInterruptPayload;
208
+ 'assistant.output.started': Record<string, never>;
209
+ 'assistant.output.completed': AssistantOutputCompletedPayload;
210
+ 'assistant.reasoning.started': Record<string, never>;
211
+ 'assistant.reasoning.completed': AssistantReasoningCompletedPayload;
212
+ 'tool.called': ToolCalledPayload;
213
+ 'tool.completed': ToolCompletedPayload;
214
+ 'tool.failed': ToolFailedPayload;
215
+ 'tool.interrupted': ToolInterruptedPayload;
216
+ 'agent.action.requested': AgentActionRequestedPayload;
217
+ 'agent.context.resolved': AgentContextResolvedPayload;
218
+ 'agent.context.failed': AgentContextFailedPayload;
219
+ 'session.child.spawned': SessionChildSpawnedPayload;
220
+ 'session.status.running': SessionStatusRunningPayload;
221
+ 'session.status.idle': SessionStatusIdlePayload;
222
+ 'session.status.rescheduled': SessionStatusRescheduledPayload;
223
+ 'session.status.terminated': SessionStatusTerminatedPayload;
224
+ 'session.error': SessionErrorPayload;
225
+ 'session.deleted': Record<string, never>;
226
+ }
227
+
228
+ /**
229
+ * 应用事件 payload 的扩展点。
230
+ *
231
+ * 业务事件(如 `workspace.*`)通过 module augmentation 注入本接口:
232
+ * `declare module '@ct-agents/protocol' { interface AppSessionEventPayloadByType {...} }`。
233
+ * protocol 本身保持业务无关,只提供这个空扩展点。
234
+ */
235
+ export interface AppSessionEventPayloadByType {}
236
+
237
+ export type SessionEventPayloadByType =
238
+ CoreSessionEventPayloadByType
239
+ & AppSessionEventPayloadByType;
240
+
241
+ export type SessionEventType = keyof SessionEventPayloadByType & string;
242
+
243
+ export type SessionEventPayload<TType extends SessionEventType = SessionEventType> =
244
+ SessionEventPayloadByType[TType];
245
+
246
+ // ─── 事件结构(协议权威类型,手写定义,不从 schema 反推) ───────────────
247
+
248
+ export type SessionEventActor = {
249
+ type: 'user' | 'assistant' | 'tool' | 'system';
250
+ id?: string;
251
+ };
252
+
253
+ /**
254
+ * 事件核心字段——不含数据面 scope 与存储关注点。
255
+ *
256
+ * `id` / `turnId` / `parentEventId` 用裸 `string`(忠实原 schema 语义),
257
+ * 持久化层的 branded 字段见 {@link PersistedSessionEvent}。
258
+ */
259
+ export interface SessionEventCoreFields {
260
+ id: string;
261
+ seq: number;
262
+ globalSeq?: number;
263
+ turnId?: string;
264
+ parentEventId?: string;
265
+ actor: SessionEventActor;
266
+ createdAt: string;
267
+ }
268
+
269
+ /**
270
+ * 领域事件——harness / orchestrator / tools / frontend 使用。
271
+ *
272
+ * 这是 protocol 的权威事件类型,手写定义;不再由 zod schema 反推。
273
+ *
274
+ * 使用 distributive 条件类型是有意为之:当 `TType` 取默认值(全联合)时,`SessionEvent`
275
+ * 展开成「每个事件类型一个成员」的 discriminated union,使得下游 `switch (event.type)`
276
+ * 能正确收窄 `event.payload`。若改成普通 interface,`type` / `payload` 会退化为
277
+ * 「同对象内的两个独立联合字段」,丢失判别关联,TS 无法在 switch 中收窄 payload。
278
+ */
279
+ export type SessionEvent<TType extends SessionEventType = SessionEventType> =
280
+ TType extends SessionEventType
281
+ ? SessionEventCoreFields & {
282
+ type: TType;
283
+ payload: SessionEventPayloadByType[TType];
284
+ }
285
+ : never;
286
+
287
+ /**
288
+ * 存储事件——SessionStore / DB 层使用,在核心字段上叠加不透明 scope。
289
+ */
290
+ export type PersistedSessionEvent<TType extends SessionEventType = SessionEventType> =
291
+ TType extends SessionEventType
292
+ ? SessionEvent<TType> & {
293
+ sessionId: SessionId;
294
+ rootSessionId?: SessionId;
295
+ scope: ExecutionScope;
296
+ idempotencyKey?: string;
297
+ }
298
+ : never;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 品牌类型工具:给原始类型打上 nominal 标记,使不同语义的字符串 id 在类型层不可互换。
3
+ * 运行时零开销(brand 标记字段编译期擦除)。
4
+ */
5
+ type Brand<TValue, TBrand extends string> = TValue & { readonly __brand: TBrand };
6
+
7
+ export type SessionId = Brand<string, 'SessionId'>;
8
+ export type EventId = Brand<string, 'EventId'>;
9
+ export type UserId = Brand<string, 'UserId'>;
10
+ export type HarnessId = Brand<string, 'HarnessId'>;
11
+ export type EnvironmentId = Brand<string, 'EnvironmentId'>;
12
+ export type TurnId = Brand<string, 'TurnId'>;
@@ -0,0 +1,6 @@
1
+ export * from './ids.js';
2
+ export * from './scope.js';
3
+ export * from './record.js';
4
+ export * from './events.js';
5
+ export * from './derived-state.js';
6
+ export * from './store.js';
@@ -0,0 +1,32 @@
1
+ import type {
2
+ EnvironmentId,
3
+ EventId,
4
+ HarnessId,
5
+ SessionId,
6
+ } from './ids.js';
7
+ import type { ExecutionScope } from './scope.js';
8
+
9
+ /**
10
+ * 会话记录:一次 Agent 对话的元数据。
11
+ *
12
+ * harness / environment 以 pin(`harnessId` + `harnessVersion` / `environmentId` +
13
+ * `environmentUpdatedAt`)固化会话使用的配置版本,配置变更不影响在跑会话;
14
+ * `appId` 是应用路由键;业务信息统一进 `metadata`,协议不解释其结构。
15
+ */
16
+ export interface SessionRecord {
17
+ id: SessionId;
18
+ appId: string;
19
+ scope: ExecutionScope;
20
+ parentSessionId?: SessionId;
21
+ rootSessionId?: SessionId;
22
+ spawnedByEventId?: EventId;
23
+ harnessId: HarnessId;
24
+ harnessVersion: number;
25
+ environmentId: EnvironmentId;
26
+ environmentUpdatedAt: string;
27
+ title: string;
28
+ metadata: Record<string, unknown>;
29
+ createdAt: string;
30
+ updatedAt: string;
31
+ deletedAt?: string;
32
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 跨进程 / 再入边界携带的隔离句柄。
3
+ *
4
+ * 协议只把它当作不透明字符串字典;具体键集由实现层约定和解释。
5
+ */
6
+ export type ExecutionScope = Readonly<Record<string, string>>;