@downcity/agent 1.1.222 → 1.1.224
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/bin/agent/local/Agent.d.ts +1 -34
- package/bin/agent/local/Agent.d.ts.map +1 -1
- package/bin/agent/local/Agent.js +1 -44
- package/bin/agent/local/Agent.js.map +1 -1
- package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
- package/bin/agent/local/services/AgentAssemblyService.js +35 -2
- package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
- package/bin/agent/local/services/AgentSessions.d.ts +6 -0
- package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
- package/bin/agent/local/services/AgentSessions.js +42 -0
- package/bin/agent/local/services/AgentSessions.js.map +1 -1
- package/bin/agent/remote/RemoteAgent.d.ts +1 -35
- package/bin/agent/remote/RemoteAgent.d.ts.map +1 -1
- package/bin/agent/remote/RemoteAgent.js +1 -37
- package/bin/agent/remote/RemoteAgent.js.map +1 -1
- package/bin/agent/remote/RemoteSession.d.ts +24 -45
- package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
- package/bin/agent/remote/RemoteSession.js +74 -121
- package/bin/agent/remote/RemoteSession.js.map +1 -1
- package/bin/agent/remote/RemoteTransport.d.ts +11 -27
- package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +7 -21
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +11 -47
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +7 -20
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +8 -17
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +2 -2
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
- package/bin/executor/types/SessionHistoryPaths.d.ts +1 -1
- package/bin/index.d.ts +3 -3
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +1 -1
- package/bin/index.js.map +1 -1
- package/bin/rpc/Client.d.ts +7 -36
- package/bin/rpc/Client.d.ts.map +1 -1
- package/bin/rpc/Client.js +28 -78
- package/bin/rpc/Client.js.map +1 -1
- package/bin/session/Session.d.ts +16 -8
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +66 -21
- package/bin/session/Session.js.map +1 -1
- package/bin/session/browse/Browse.d.ts.map +1 -1
- package/bin/session/browse/Browse.js +49 -18
- package/bin/session/browse/Browse.js.map +1 -1
- package/bin/session/recorder/JsonlSessionMessageStore.d.ts +30 -24
- package/bin/session/recorder/JsonlSessionMessageStore.d.ts.map +1 -1
- package/bin/session/recorder/JsonlSessionMessageStore.js +163 -59
- package/bin/session/recorder/JsonlSessionMessageStore.js.map +1 -1
- package/bin/session/recorder/SessionMessageCodec.d.ts.map +1 -1
- package/bin/session/recorder/SessionMessageCodec.js +6 -1
- package/bin/session/recorder/SessionMessageCodec.js.map +1 -1
- package/bin/session/recorder/SessionRecorder.d.ts +20 -11
- package/bin/session/recorder/SessionRecorder.d.ts.map +1 -1
- package/bin/session/recorder/SessionRecorder.js +148 -103
- package/bin/session/recorder/SessionRecorder.js.map +1 -1
- package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -1
- package/bin/session/recorder/SessionRecorderCompaction.js +1 -0
- package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -1
- package/bin/session/recorder/SessionRecorderHistoryStore.d.ts +5 -5
- package/bin/session/recorder/SessionRecorderHistoryStore.d.ts.map +1 -1
- package/bin/session/recorder/SessionRecorderHistoryStore.js +5 -5
- package/bin/session/recorder/SessionRecorderHistoryStore.js.map +1 -1
- package/bin/session/runtime/SessionEventHub.d.ts +12 -5
- package/bin/session/runtime/SessionEventHub.d.ts.map +1 -1
- package/bin/session/runtime/SessionEventHub.js +11 -6
- package/bin/session/runtime/SessionEventHub.js.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.d.ts +2 -2
- package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.js +35 -17
- package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
- package/bin/session/services/SessionStateService.d.ts +2 -2
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js +6 -2
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionTurnService.d.ts +2 -7
- package/bin/session/services/SessionTurnService.d.ts.map +1 -1
- package/bin/session/services/SessionTurnService.js +2 -8
- package/bin/session/services/SessionTurnService.js.map +1 -1
- package/bin/session/storage/Paths.d.ts +1 -1
- package/bin/session/storage/Paths.d.ts.map +1 -1
- package/bin/session/storage/Paths.js +2 -2
- package/bin/session/storage/Paths.js.map +1 -1
- package/bin/session/storage/RuntimeSessionPort.d.ts +3 -4
- package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
- package/bin/session/storage/RuntimeSessionPort.js +2 -2
- package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
- package/bin/types/agent/SessionActor.d.ts +11 -7
- package/bin/types/agent/SessionActor.d.ts.map +1 -1
- package/bin/types/rpc/RpcProtocol.d.ts +35 -62
- package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.d.ts +4 -4
- package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.js.map +1 -1
- package/bin/types/session/SessionApproval.d.ts +97 -0
- package/bin/types/session/SessionApproval.d.ts.map +1 -0
- package/bin/types/session/SessionApproval.js +7 -0
- package/bin/types/session/SessionApproval.js.map +1 -0
- package/bin/types/session/SessionMessage.d.ts +10 -4
- package/bin/types/session/SessionMessage.d.ts.map +1 -1
- package/bin/types/session/SessionMutation.d.ts +116 -0
- package/bin/types/session/SessionMutation.d.ts.map +1 -0
- package/bin/types/session/SessionMutation.js +19 -0
- package/bin/types/session/SessionMutation.js.map +1 -0
- package/bin/types/session/SessionOptions.d.ts +9 -0
- package/bin/types/session/SessionOptions.d.ts.map +1 -1
- package/package.json +3 -3
- package/scripts/remote-session-reconnect.test.mjs +7 -3
- package/scripts/session-history-inflight.test.mjs +2 -2
- package/scripts/session-prompt-runtime.test.mjs +3 -3
- package/scripts/session-recorder.test.mjs +100 -146
- package/src/agent/local/Agent.ts +1 -55
- package/src/agent/local/services/AgentAssemblyService.ts +39 -3
- package/src/agent/local/services/AgentSessions.ts +47 -0
- package/src/agent/remote/RemoteAgent.ts +1 -54
- package/src/agent/remote/RemoteSession.ts +105 -163
- package/src/agent/remote/RemoteTransport.ts +23 -34
- package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +33 -90
- package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +22 -39
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +2 -2
- package/src/executor/types/SessionHistoryPaths.ts +1 -1
- package/src/index.ts +20 -12
- package/src/rpc/Client.ts +48 -106
- package/src/session/Session.ts +91 -34
- package/src/session/browse/Browse.ts +51 -22
- package/src/session/recorder/JsonlSessionMessageStore.ts +170 -86
- package/src/session/recorder/SessionMessageCodec.ts +6 -1
- package/src/session/recorder/SessionRecorder.ts +183 -124
- package/src/session/recorder/SessionRecorderCompaction.ts +1 -0
- package/src/session/recorder/SessionRecorderHistoryStore.ts +5 -5
- package/src/session/runtime/SessionEventHub.ts +25 -12
- package/src/session/runtime/SessionPromptRuntime.ts +37 -19
- package/src/session/services/SessionStateService.ts +8 -6
- package/src/session/services/SessionTurnService.ts +3 -14
- package/src/session/storage/Paths.ts +2 -2
- package/src/session/storage/RuntimeSessionPort.ts +8 -11
- package/src/types/agent/SessionActor.ts +23 -20
- package/src/types/rpc/RpcProtocol.ts +34 -71
- package/src/types/runtime/agent/AgentContext.ts +7 -11
- package/src/types/session/SessionApproval.ts +108 -0
- package/src/types/session/SessionMessage.ts +10 -4
- package/src/types/session/SessionMutation.ts +141 -0
- package/src/types/session/SessionOptions.ts +25 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/bin/session/recorder/SessionMessageReducer.d.ts +0 -12
- package/bin/session/recorder/SessionMessageReducer.d.ts.map +0 -1
- package/bin/session/recorder/SessionMessageReducer.js +0 -109
- package/bin/session/recorder/SessionMessageReducer.js.map +0 -1
- package/bin/session/runtime/SessionMessageEventHub.d.ts +0 -15
- package/bin/session/runtime/SessionMessageEventHub.d.ts.map +0 -1
- package/bin/session/runtime/SessionMessageEventHub.js +0 -28
- package/bin/session/runtime/SessionMessageEventHub.js.map +0 -1
- package/bin/types/sdk/AgentSessionEvent.d.ts +0 -303
- package/bin/types/sdk/AgentSessionEvent.d.ts.map +0 -1
- package/bin/types/sdk/AgentSessionEvent.js +0 -9
- package/bin/types/sdk/AgentSessionEvent.js.map +0 -1
- package/bin/types/session/SessionMessageMutation.d.ts +0 -92
- package/bin/types/session/SessionMessageMutation.d.ts.map +0 -1
- package/bin/types/session/SessionMessageMutation.js +0 -21
- package/bin/types/session/SessionMessageMutation.js.map +0 -1
- package/src/session/recorder/SessionMessageReducer.ts +0 -155
- package/src/session/runtime/SessionMessageEventHub.ts +0 -35
- package/src/types/sdk/AgentSessionEvent.ts +0 -372
- package/src/types/session/SessionMessageMutation.ts +0 -121
|
@@ -109,6 +109,7 @@ export function from_ui_assistant_parts(
|
|
|
109
109
|
if (type === "text" || type === "reasoning") {
|
|
110
110
|
return [{
|
|
111
111
|
part_id: `${type}:${index + 1}`,
|
|
112
|
+
sequence: index + 1,
|
|
112
113
|
type: type as "text" | "reasoning",
|
|
113
114
|
text: String(candidate.text || ""),
|
|
114
115
|
state: "done" as const,
|
|
@@ -117,6 +118,7 @@ export function from_ui_assistant_parts(
|
|
|
117
118
|
if (type === "file") {
|
|
118
119
|
return [{
|
|
119
120
|
part_id: `file:${index + 1}`,
|
|
121
|
+
sequence: index + 1,
|
|
120
122
|
type: "file" as const,
|
|
121
123
|
url: String(candidate.url || ""),
|
|
122
124
|
media_type: String(candidate.mediaType || "application/octet-stream"),
|
|
@@ -127,6 +129,7 @@ export function from_ui_assistant_parts(
|
|
|
127
129
|
const state = String(candidate.state || "");
|
|
128
130
|
return [{
|
|
129
131
|
part_id: String(candidate.toolCallId || `tool:${index + 1}`),
|
|
132
|
+
sequence: index + 1,
|
|
130
133
|
type: "tool" as const,
|
|
131
134
|
tool_call_id: String(candidate.toolCallId || `tool:${index + 1}`),
|
|
132
135
|
tool_name:
|
|
@@ -142,7 +145,9 @@ export function from_ui_assistant_parts(
|
|
|
142
145
|
? "approval-required" as const
|
|
143
146
|
: state === "input-streaming"
|
|
144
147
|
? "input-streaming" as const
|
|
145
|
-
: "
|
|
148
|
+
: state === "input-available"
|
|
149
|
+
? "ready" as const
|
|
150
|
+
: "running" as const,
|
|
146
151
|
...(candidate.input !== undefined
|
|
147
152
|
? { input: to_json_value(candidate.input) }
|
|
148
153
|
: {}),
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SessionRecorder:Session Message 的唯一持久化与发布入口。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 完整 Message 与 Assistant 草稿先持久化,成功后再发布实时 Mutation。
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { UIMessage, UIMessageChunk } from "ai";
|
|
8
8
|
import { generateId } from "@/utils/Id.js";
|
|
9
9
|
import { JsonlSessionMessageStore } from "@/session/recorder/JsonlSessionMessageStore.js";
|
|
10
|
-
import { reduce_session_message } from "@/session/recorder/SessionMessageReducer.js";
|
|
11
10
|
import type { JsonObject, JsonValue } from "@/types/common/Json.js";
|
|
12
11
|
import type {
|
|
13
12
|
ListSessionMessagesInput,
|
|
@@ -22,23 +21,18 @@ import type {
|
|
|
22
21
|
SessionUserMessagePart,
|
|
23
22
|
} from "@/types/session/SessionMessage.js";
|
|
24
23
|
import type {
|
|
25
|
-
|
|
26
|
-
SessionMessageMutation,
|
|
27
|
-
|
|
28
|
-
SessionMessageMutationSubscriber,
|
|
29
|
-
SessionMessageMutationUnsubscribe,
|
|
30
|
-
SessionMessageSnapshotMutation,
|
|
31
|
-
SessionPartSnapshotMutation,
|
|
32
|
-
} from "@/types/session/SessionMessageMutation.js";
|
|
24
|
+
SessionMutation,
|
|
25
|
+
SessionMessageMutation as SessionMessageSnapshotMutation,
|
|
26
|
+
} from "@/types/session/SessionMutation.js";
|
|
33
27
|
|
|
34
28
|
/** SessionRecorder 构造参数。 */
|
|
35
29
|
export interface SessionRecorderOptions {
|
|
36
30
|
/** 当前 Session 标识。 */
|
|
37
31
|
session_id: string;
|
|
38
|
-
/**
|
|
32
|
+
/** Message 快照 store。 */
|
|
39
33
|
store: JsonlSessionMessageStore;
|
|
40
|
-
/** Mutation
|
|
41
|
-
publish: (mutation:
|
|
34
|
+
/** 持久化成功后的实时 Mutation 发布函数。 */
|
|
35
|
+
publish: (mutation: SessionMutation) => void;
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
/** User Message 创建参数。 */
|
|
@@ -120,7 +114,6 @@ export class SessionRecorder {
|
|
|
120
114
|
readonly session_id: string;
|
|
121
115
|
private readonly store: JsonlSessionMessageStore;
|
|
122
116
|
private readonly publish: SessionRecorderOptions["publish"];
|
|
123
|
-
private readonly subscribers = new Set<SessionMessageMutationSubscriber>();
|
|
124
117
|
private readonly messages_by_id = new Map<string, SessionMessage>();
|
|
125
118
|
private initialized = false;
|
|
126
119
|
|
|
@@ -131,7 +124,7 @@ export class SessionRecorder {
|
|
|
131
124
|
if (!this.session_id) throw new Error("SessionRecorder requires session_id");
|
|
132
125
|
}
|
|
133
126
|
|
|
134
|
-
/**
|
|
127
|
+
/** 恢复已有 Message,并收口进程中断遗留的运行状态。 */
|
|
135
128
|
async initialize(): Promise<void> {
|
|
136
129
|
if (this.initialized) return;
|
|
137
130
|
await this.store.initialize();
|
|
@@ -202,7 +195,7 @@ export class SessionRecorder {
|
|
|
202
195
|
...(input.summary_through_message_id
|
|
203
196
|
? { summary_through_message_id: input.summary_through_message_id }
|
|
204
197
|
: {}),
|
|
205
|
-
}))) as SessionAssistantMessage;
|
|
198
|
+
}), true)) as SessionAssistantMessage;
|
|
206
199
|
return new SessionAssistantMessageWriter(this, message.message_id);
|
|
207
200
|
}
|
|
208
201
|
|
|
@@ -256,10 +249,10 @@ export class SessionRecorder {
|
|
|
256
249
|
status: "running" | "completed" | "failed",
|
|
257
250
|
changes?: { title?: string; description?: string; data?: JsonObject },
|
|
258
251
|
): Promise<SessionActionMessage> {
|
|
259
|
-
const
|
|
252
|
+
const message = await this.store.append_message((state) => {
|
|
260
253
|
const current = require_message(state.messages, message_id, "action");
|
|
261
254
|
const created_at = Date.now();
|
|
262
|
-
|
|
255
|
+
return {
|
|
263
256
|
...current,
|
|
264
257
|
status,
|
|
265
258
|
...(changes?.title ? { title: changes.title } : {}),
|
|
@@ -269,11 +262,10 @@ export class SessionRecorder {
|
|
|
269
262
|
...(changes?.data ? { data: structuredClone(changes.data) } : {}),
|
|
270
263
|
revision: current.revision + 1,
|
|
271
264
|
updated_at: created_at,
|
|
272
|
-
};
|
|
273
|
-
return this.build_message_mutation(state.commit_sequence, message);
|
|
265
|
+
} satisfies SessionActionMessage;
|
|
274
266
|
});
|
|
275
|
-
this.
|
|
276
|
-
return
|
|
267
|
+
this.accept_message(message);
|
|
268
|
+
return message as SessionActionMessage;
|
|
277
269
|
}
|
|
278
270
|
|
|
279
271
|
/** 创建用户可见 Error Message。 */
|
|
@@ -302,9 +294,6 @@ export class SessionRecorder {
|
|
|
302
294
|
input?: ListSessionMessagesInput,
|
|
303
295
|
): Promise<SessionMessagePage> {
|
|
304
296
|
await this.ensure_initialized();
|
|
305
|
-
const mutations = await this.store.list_mutations();
|
|
306
|
-
const latest_commit_sequence =
|
|
307
|
-
mutations[mutations.length - 1]?.commit_sequence || 0;
|
|
308
297
|
const include_internal = input?.include_internal === true;
|
|
309
298
|
const through_sequence = Number.isFinite(input?.through_sequence)
|
|
310
299
|
? Number(input?.through_sequence)
|
|
@@ -329,38 +318,9 @@ export class SessionRecorder {
|
|
|
329
318
|
total: all.length,
|
|
330
319
|
...(next_offset < all.length ? { next_cursor: encode_cursor(next_offset) } : {}),
|
|
331
320
|
has_more: next_offset < all.length,
|
|
332
|
-
latest_commit_sequence,
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/** 从指定 commit_sequence 增量读取 Mutation。 */
|
|
337
|
-
async list_message_changes(
|
|
338
|
-
input: ListSessionMessageChangesInput,
|
|
339
|
-
): Promise<SessionMessageMutationPage> {
|
|
340
|
-
await this.ensure_initialized();
|
|
341
|
-
const after = Math.max(0, Math.floor(input.after_commit_sequence || 0));
|
|
342
|
-
const limit = normalize_limit(input.limit, 200, 1000);
|
|
343
|
-
const all = await this.store.list_mutations();
|
|
344
|
-
const matching = all.filter((item) => item.commit_sequence > after);
|
|
345
|
-
const items = matching.slice(0, limit);
|
|
346
|
-
const latest_commit_sequence = all[all.length - 1]?.commit_sequence || 0;
|
|
347
|
-
return {
|
|
348
|
-
items,
|
|
349
|
-
has_more: matching.length > items.length,
|
|
350
|
-
next_commit_sequence:
|
|
351
|
-
items[items.length - 1]?.commit_sequence || after,
|
|
352
|
-
latest_commit_sequence,
|
|
353
321
|
};
|
|
354
322
|
}
|
|
355
323
|
|
|
356
|
-
/** 订阅已成功持久化的未来 Mutation。 */
|
|
357
|
-
subscribe(
|
|
358
|
-
subscriber: SessionMessageMutationSubscriber,
|
|
359
|
-
): SessionMessageMutationUnsubscribe {
|
|
360
|
-
this.subscribers.add(subscriber);
|
|
361
|
-
return () => this.subscribers.delete(subscriber);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
324
|
/** 向当前 Session 导入 fork 来源 Message,并重新分配全部身份和顺序。 */
|
|
365
325
|
async import_messages(messages: SessionMessage[]): Promise<void> {
|
|
366
326
|
const turn_ids = new Map<string, string>();
|
|
@@ -400,23 +360,37 @@ export class SessionRecorder {
|
|
|
400
360
|
delta: string,
|
|
401
361
|
): Promise<void> {
|
|
402
362
|
if (!delta) return;
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
363
|
+
const current = require_message([...this.messages_by_id.values()], message_id, "assistant");
|
|
364
|
+
require_streaming_assistant(current);
|
|
365
|
+
const part = current.parts.find((item) => item.part_id === part_id);
|
|
366
|
+
if (!part || (part.type !== "text" && part.type !== "reasoning")) {
|
|
367
|
+
throw new Error(`Delta target Part does not exist: ${part_id}`);
|
|
368
|
+
}
|
|
369
|
+
if (part.type !== type) throw new Error(`Delta type changed for Part: ${part_id}`);
|
|
370
|
+
const created_at = Date.now();
|
|
371
|
+
const message: SessionAssistantMessage = {
|
|
372
|
+
...current,
|
|
373
|
+
revision: current.revision + 1,
|
|
374
|
+
updated_at: created_at,
|
|
375
|
+
parts: current.parts.map((item) =>
|
|
376
|
+
item.part_id === part_id && (item.type === "text" || item.type === "reasoning")
|
|
377
|
+
? { ...item, text: item.text + delta }
|
|
378
|
+
: item,
|
|
379
|
+
),
|
|
380
|
+
};
|
|
381
|
+
await this.store.write_assistant_message(message);
|
|
382
|
+
this.accept_mutation({
|
|
383
|
+
mutation_id: generateId(),
|
|
384
|
+
variant: "delta",
|
|
385
|
+
type,
|
|
386
|
+
message_id,
|
|
387
|
+
revision: message.revision,
|
|
388
|
+
session_id: this.session_id,
|
|
389
|
+
turn_id: message.turn_id,
|
|
390
|
+
created_at,
|
|
391
|
+
part_id,
|
|
392
|
+
delta,
|
|
393
|
+
}, message);
|
|
420
394
|
}
|
|
421
395
|
|
|
422
396
|
/** @internal 写入 Assistant 完整 part。 */
|
|
@@ -424,23 +398,36 @@ export class SessionRecorder {
|
|
|
424
398
|
message_id: string,
|
|
425
399
|
part: SessionAssistantMessagePart,
|
|
426
400
|
): Promise<void> {
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
part:
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
401
|
+
const current = require_message([...this.messages_by_id.values()], message_id, "assistant");
|
|
402
|
+
require_streaming_assistant(current);
|
|
403
|
+
const existing = current.parts.find((item) => item.part_id === part.part_id);
|
|
404
|
+
if (existing && existing.sequence !== part.sequence) {
|
|
405
|
+
throw new Error(`Assistant Part sequence changed: ${part.part_id}`);
|
|
406
|
+
}
|
|
407
|
+
const created_at = Date.now();
|
|
408
|
+
const next_part = structuredClone(part);
|
|
409
|
+
const message: SessionAssistantMessage = {
|
|
410
|
+
...current,
|
|
411
|
+
revision: current.revision + 1,
|
|
412
|
+
updated_at: created_at,
|
|
413
|
+
parts: (existing
|
|
414
|
+
? current.parts.map((item) => item.part_id === part.part_id ? next_part : item)
|
|
415
|
+
: [...current.parts, next_part]
|
|
416
|
+
).sort((left, right) => left.sequence - right.sequence),
|
|
417
|
+
};
|
|
418
|
+
await this.store.write_assistant_message(message);
|
|
419
|
+
this.accept_mutation({
|
|
420
|
+
mutation_id: generateId(),
|
|
421
|
+
variant: "part",
|
|
422
|
+
type: next_part.type,
|
|
423
|
+
message_id,
|
|
424
|
+
revision: message.revision,
|
|
425
|
+
session_id: this.session_id,
|
|
426
|
+
turn_id: message.turn_id,
|
|
427
|
+
created_at,
|
|
428
|
+
part_id: next_part.part_id,
|
|
429
|
+
part: next_part,
|
|
430
|
+
} as SessionMutation, message);
|
|
444
431
|
}
|
|
445
432
|
|
|
446
433
|
/** @internal 收口 Assistant Message。 */
|
|
@@ -448,45 +435,100 @@ export class SessionRecorder {
|
|
|
448
435
|
message_id: string,
|
|
449
436
|
status: "completed" | "stopped" | "failed",
|
|
450
437
|
): Promise<void> {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
438
|
+
const current = require_message([...this.messages_by_id.values()], message_id, "assistant");
|
|
439
|
+
require_streaming_assistant(current);
|
|
440
|
+
const created_at = Date.now();
|
|
441
|
+
const message: SessionAssistantMessage = {
|
|
442
|
+
...current,
|
|
443
|
+
revision: current.revision + 1,
|
|
444
|
+
status,
|
|
445
|
+
updated_at: created_at,
|
|
446
|
+
parts: current.parts.map((part) =>
|
|
447
|
+
part.type === "text" || part.type === "reasoning"
|
|
448
|
+
? { ...part, state: "done" as const }
|
|
449
|
+
: part,
|
|
450
|
+
),
|
|
451
|
+
};
|
|
452
|
+
await this.store.finalize_assistant_message(message);
|
|
453
|
+
this.accept_message(message);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** @internal 将 Shell Runtime 审批请求更新到当前 Tool Part。 */
|
|
457
|
+
async require_tool_approval(input: {
|
|
458
|
+
tool_call_id: string;
|
|
459
|
+
approval_id: string;
|
|
460
|
+
}): Promise<void> {
|
|
461
|
+
const tool = this.find_streaming_tool(input.tool_call_id);
|
|
462
|
+
await this.update_assistant_part(tool.message_id, {
|
|
463
|
+
...tool.part,
|
|
464
|
+
state: "approval-required",
|
|
465
|
+
approval_id: input.approval_id,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** @internal 将审批结果更新到当前 Tool Part。 */
|
|
470
|
+
async resolve_tool_approval(input: {
|
|
471
|
+
tool_call_id: string;
|
|
472
|
+
approval_id: string;
|
|
473
|
+
decision: "approved" | "denied" | "expired";
|
|
474
|
+
}): Promise<void> {
|
|
475
|
+
const tool = this.find_streaming_tool(input.tool_call_id);
|
|
476
|
+
if (tool.part.approval_id && tool.part.approval_id !== input.approval_id) {
|
|
477
|
+
throw new Error(`Tool approval identity mismatch: ${input.approval_id}`);
|
|
478
|
+
}
|
|
479
|
+
await this.update_assistant_part(tool.message_id, {
|
|
480
|
+
...tool.part,
|
|
481
|
+
state: input.decision === "approved" ? "running" : "failed",
|
|
482
|
+
...(input.decision === "approved"
|
|
483
|
+
? {}
|
|
484
|
+
: { error: input.decision === "expired" ? "Approval expired" : "Approval denied" }),
|
|
466
485
|
});
|
|
467
|
-
this.accept_mutation(mutation);
|
|
468
486
|
}
|
|
469
487
|
|
|
470
488
|
private async create_message(
|
|
471
489
|
factory: (sequence: number, created_at: number) => SessionMessage,
|
|
490
|
+
draft = false,
|
|
472
491
|
): Promise<SessionMessage> {
|
|
473
|
-
|
|
474
|
-
const message =
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
492
|
+
if (draft) {
|
|
493
|
+
const message = await this.store.create_assistant_message((state) => {
|
|
494
|
+
const candidate = factory(state.message_sequence, Date.now());
|
|
495
|
+
if (candidate.type !== "assistant" || candidate.status !== "streaming") {
|
|
496
|
+
throw new Error("Draft Message must be a streaming Assistant");
|
|
497
|
+
}
|
|
498
|
+
return candidate;
|
|
499
|
+
});
|
|
500
|
+
this.accept_message(message);
|
|
501
|
+
return message;
|
|
502
|
+
}
|
|
503
|
+
const message = await this.store.append_message((state) =>
|
|
504
|
+
factory(state.message_sequence, Date.now()),
|
|
505
|
+
);
|
|
506
|
+
this.accept_message(message);
|
|
507
|
+
return message;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
private find_streaming_tool(tool_call_id: string): {
|
|
511
|
+
message_id: string;
|
|
512
|
+
part: SessionAssistantToolPart;
|
|
513
|
+
} {
|
|
514
|
+
for (const message of this.messages_by_id.values()) {
|
|
515
|
+
if (message.type !== "assistant" || message.status !== "streaming") continue;
|
|
516
|
+
const part = message.parts.find(
|
|
517
|
+
(item): item is SessionAssistantToolPart =>
|
|
518
|
+
item.type === "tool" && item.tool_call_id === tool_call_id,
|
|
519
|
+
);
|
|
520
|
+
if (part) return { message_id: message.message_id, part };
|
|
521
|
+
}
|
|
522
|
+
throw new Error(`Streaming Tool Part not found: ${tool_call_id}`);
|
|
479
523
|
}
|
|
480
524
|
|
|
481
525
|
private build_message_mutation(
|
|
482
|
-
commit_sequence: number,
|
|
483
526
|
message: SessionMessage,
|
|
484
527
|
): SessionMessageSnapshotMutation {
|
|
485
528
|
return {
|
|
486
529
|
mutation_id: generateId(),
|
|
487
530
|
variant: "message",
|
|
488
531
|
type: message.type,
|
|
489
|
-
commit_sequence,
|
|
490
532
|
message_id: message.message_id,
|
|
491
533
|
sequence: message.sequence,
|
|
492
534
|
revision: message.revision,
|
|
@@ -497,18 +539,13 @@ export class SessionRecorder {
|
|
|
497
539
|
} as SessionMessageSnapshotMutation;
|
|
498
540
|
}
|
|
499
541
|
|
|
500
|
-
private
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
542
|
+
private accept_message(message: SessionMessage): void {
|
|
543
|
+
this.accept_mutation(this.build_message_mutation(message), message);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private accept_mutation(mutation: SessionMutation, message: SessionMessage): void {
|
|
547
|
+
this.messages_by_id.set(message.message_id, structuredClone(message));
|
|
504
548
|
this.publish(mutation);
|
|
505
|
-
for (const subscriber of this.subscribers) {
|
|
506
|
-
try {
|
|
507
|
-
subscriber(mutation);
|
|
508
|
-
} catch {
|
|
509
|
-
// 单个订阅者异常不能影响已经完成的提交。
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
549
|
}
|
|
513
550
|
|
|
514
551
|
private async ensure_initialized(): Promise<void> {
|
|
@@ -536,6 +573,7 @@ export class SessionAssistantMessageWriter {
|
|
|
536
573
|
case "reasoning-start":
|
|
537
574
|
await this.upsert_part({
|
|
538
575
|
part_id: `${chunk.type === "text-start" ? "text" : "reasoning"}:${chunk.id}`,
|
|
576
|
+
sequence: this.next_part_sequence(),
|
|
539
577
|
type: chunk.type === "text-start" ? "text" : "reasoning",
|
|
540
578
|
text: "",
|
|
541
579
|
state: "streaming",
|
|
@@ -578,7 +616,7 @@ export class SessionAssistantMessageWriter {
|
|
|
578
616
|
case "tool-input-available":
|
|
579
617
|
await this.upsert_tool(chunk.toolCallId, {
|
|
580
618
|
tool_name: chunk.toolName,
|
|
581
|
-
state: "
|
|
619
|
+
state: "ready",
|
|
582
620
|
input: to_json_value(chunk.input),
|
|
583
621
|
});
|
|
584
622
|
return;
|
|
@@ -624,6 +662,7 @@ export class SessionAssistantMessageWriter {
|
|
|
624
662
|
case "file":
|
|
625
663
|
await this.upsert_part({
|
|
626
664
|
part_id: `file:${generateId()}`,
|
|
665
|
+
sequence: this.next_part_sequence(),
|
|
627
666
|
type: "file",
|
|
628
667
|
media_type: chunk.mediaType,
|
|
629
668
|
url: chunk.url,
|
|
@@ -672,6 +711,13 @@ export class SessionAssistantMessageWriter {
|
|
|
672
711
|
);
|
|
673
712
|
}
|
|
674
713
|
|
|
714
|
+
private next_part_sequence(): number {
|
|
715
|
+
return this.current_message().parts.reduce(
|
|
716
|
+
(value, part) => Math.max(value, part.sequence + 1),
|
|
717
|
+
1,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
|
|
675
721
|
private async upsert_tool(
|
|
676
722
|
tool_call_id: string,
|
|
677
723
|
changes: Pick<SessionAssistantToolPart, "tool_name" | "state"> &
|
|
@@ -681,6 +727,7 @@ export class SessionAssistantMessageWriter {
|
|
|
681
727
|
await this.upsert_part({
|
|
682
728
|
...(current || {}),
|
|
683
729
|
part_id: `tool:${tool_call_id}`,
|
|
730
|
+
sequence: current?.sequence || this.next_part_sequence(),
|
|
684
731
|
type: "tool",
|
|
685
732
|
tool_call_id,
|
|
686
733
|
...changes,
|
|
@@ -771,6 +818,18 @@ function require_message<TType extends SessionMessage["type"]>(
|
|
|
771
818
|
return message as Extract<SessionMessage, { type: TType }>;
|
|
772
819
|
}
|
|
773
820
|
|
|
821
|
+
function require_streaming_assistant(
|
|
822
|
+
message: SessionMessage,
|
|
823
|
+
): SessionAssistantMessage {
|
|
824
|
+
if (message.type !== "assistant") {
|
|
825
|
+
throw new Error(`Session Message is not assistant: ${message.message_id}`);
|
|
826
|
+
}
|
|
827
|
+
if (message.status !== "streaming") {
|
|
828
|
+
throw new Error(`Assistant Message is already closed: ${message.message_id}`);
|
|
829
|
+
}
|
|
830
|
+
return message;
|
|
831
|
+
}
|
|
832
|
+
|
|
774
833
|
function to_json_value(input: unknown): JsonValue {
|
|
775
834
|
if (input === undefined || input === null) return null;
|
|
776
835
|
if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") return input;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* SessionRecorder 到 Executor SessionHistoryStore 的内部投影适配器。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* - Recorder messages.jsonl
|
|
5
|
+
* - Recorder 的 messages.jsonl 与 assistant_message.json 共同组成持久化事实源。
|
|
6
6
|
* - Executor 继续读取临时 UIMessage 投影,不直接拥有文件写入权。
|
|
7
|
-
* -
|
|
7
|
+
* - 流式草稿写入由 AssistantMessageWriter 负责,Executor 的旧接口只提供投影兼容。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { nanoid } from "nanoid";
|
|
@@ -109,7 +109,7 @@ export class SessionRecorderHistoryStore implements SessionHistoryStore {
|
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/** Recorder
|
|
112
|
+
/** 从 Recorder 当前快照投影运行中的 Assistant。 */
|
|
113
113
|
async read_inflight(): Promise<SessionRecordV1 | null> {
|
|
114
114
|
const page = await this.recorder.list_messages({
|
|
115
115
|
limit: 500,
|
|
@@ -124,10 +124,10 @@ export class SessionRecorderHistoryStore implements SessionHistoryStore {
|
|
|
124
124
|
return assistant ? to_executor_ui_message(assistant) : null;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
/**
|
|
127
|
+
/** Executor 草稿写入口不持久化;流式内容由 Recorder writer 负责。 */
|
|
128
128
|
async write_inflight(_message: SessionRecordV1): Promise<void> {}
|
|
129
129
|
|
|
130
|
-
/**
|
|
130
|
+
/** Executor 草稿收口入口不持久化;Assistant writer 负责 complete。 */
|
|
131
131
|
async finalize_inflight(_message?: SessionRecordV1 | null): Promise<void> {}
|
|
132
132
|
|
|
133
133
|
/** 返回 Context Composer 使用的 UIMessage 投影。 */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SessionEventHub
|
|
2
|
+
* SessionEventHub:统一 Session Mutation 分发器。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - 收口 `Session.subscribe()` 的订阅/取消订阅能力。
|
|
@@ -7,24 +7,36 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
SessionMutation,
|
|
11
|
+
SessionMutationReply,
|
|
12
|
+
SessionMutationSubscriber,
|
|
13
|
+
SessionMutationUnsubscribe,
|
|
14
|
+
} from "@/types/session/SessionMutation.js";
|
|
15
|
+
|
|
16
|
+
/** SessionEventHub 构造参数。 */
|
|
17
|
+
export interface SessionEventHubOptions {
|
|
18
|
+
/** 为当前 Mutation 创建绑定 Session 和 Tool Part 的回复入口。 */
|
|
19
|
+
create_reply: (mutation: SessionMutation) => SessionMutationReply;
|
|
20
|
+
}
|
|
14
21
|
|
|
15
22
|
/**
|
|
16
23
|
* SessionEventHub:最小事件总线实现。
|
|
17
24
|
*/
|
|
18
25
|
export class SessionEventHub {
|
|
19
|
-
private readonly subscribers = new Map<number,
|
|
20
|
-
private
|
|
26
|
+
private readonly subscribers = new Map<number, SessionMutationSubscriber>();
|
|
27
|
+
private readonly create_reply: SessionEventHubOptions["create_reply"];
|
|
28
|
+
private next_subscriber_id = 1;
|
|
29
|
+
|
|
30
|
+
constructor(options: SessionEventHubOptions) {
|
|
31
|
+
this.create_reply = options.create_reply;
|
|
32
|
+
}
|
|
21
33
|
|
|
22
34
|
/**
|
|
23
35
|
* 注册一个新的 Session 事件订阅者。
|
|
24
36
|
*/
|
|
25
|
-
subscribe(subscriber:
|
|
26
|
-
const id = this.
|
|
27
|
-
this.
|
|
37
|
+
subscribe(subscriber: SessionMutationSubscriber): SessionMutationUnsubscribe {
|
|
38
|
+
const id = this.next_subscriber_id;
|
|
39
|
+
this.next_subscriber_id += 1;
|
|
28
40
|
this.subscribers.set(id, subscriber);
|
|
29
41
|
|
|
30
42
|
return () => {
|
|
@@ -35,10 +47,11 @@ export class SessionEventHub {
|
|
|
35
47
|
/**
|
|
36
48
|
* 广播一条 Session 事件。
|
|
37
49
|
*/
|
|
38
|
-
publish(
|
|
50
|
+
publish(mutation: SessionMutation): void {
|
|
51
|
+
const reply = this.create_reply(mutation);
|
|
39
52
|
for (const subscriber of this.subscribers.values()) {
|
|
40
53
|
try {
|
|
41
|
-
subscriber(
|
|
54
|
+
Promise.resolve(subscriber(mutation, reply)).catch(() => undefined);
|
|
42
55
|
} catch {
|
|
43
56
|
// ignore single subscriber failures
|
|
44
57
|
}
|