@downcity/agent 1.1.217 → 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 +43 -4
- 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 +26 -41
- package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
- package/bin/agent/remote/RemoteSession.js +75 -107
- package/bin/agent/remote/RemoteSession.js.map +1 -1
- package/bin/agent/remote/RemoteTransport.d.ts +15 -27
- package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +10 -21
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +24 -46
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +10 -20
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +10 -16
- 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/executor/types/SessionRecords.d.ts +2 -0
- package/bin/executor/types/SessionRecords.d.ts.map +1 -1
- package/bin/executor/types/SessionRecords.js.map +1 -1
- package/bin/index.d.ts +6 -3
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +1 -0
- package/bin/index.js.map +1 -1
- package/bin/rpc/Client.d.ts +12 -34
- package/bin/rpc/Client.d.ts.map +1 -1
- package/bin/rpc/Client.js +31 -70
- package/bin/rpc/Client.js.map +1 -1
- package/bin/session/Session.d.ts +22 -4
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +95 -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 +51 -0
- package/bin/session/recorder/JsonlSessionMessageStore.d.ts.map +1 -0
- package/bin/session/recorder/JsonlSessionMessageStore.js +231 -0
- package/bin/session/recorder/JsonlSessionMessageStore.js.map +1 -0
- package/bin/session/recorder/SessionMessageCodec.d.ts +20 -0
- package/bin/session/recorder/SessionMessageCodec.d.ts.map +1 -0
- package/bin/session/recorder/SessionMessageCodec.js +257 -0
- package/bin/session/recorder/SessionMessageCodec.js.map +1 -0
- package/bin/session/recorder/SessionRecorder.d.ts +186 -0
- package/bin/session/recorder/SessionRecorder.d.ts.map +1 -0
- package/bin/session/recorder/SessionRecorder.js +655 -0
- package/bin/session/recorder/SessionRecorder.js.map +1 -0
- package/bin/session/recorder/SessionRecorderCompaction.d.ts +18 -0
- package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -0
- package/bin/session/recorder/SessionRecorderCompaction.js +142 -0
- package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -0
- package/bin/session/recorder/SessionRecorderHistoryStore.d.ts +66 -0
- package/bin/session/recorder/SessionRecorderHistoryStore.d.ts.map +1 -0
- package/bin/session/recorder/SessionRecorderHistoryStore.js +195 -0
- package/bin/session/recorder/SessionRecorderHistoryStore.js.map +1 -0
- 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 +9 -15
- package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.js +48 -30
- package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
- package/bin/session/services/SessionStateService.d.ts +9 -5
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js +138 -74
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionTurnService.d.ts +6 -7
- package/bin/session/services/SessionTurnService.d.ts.map +1 -1
- package/bin/session/services/SessionTurnService.js +84 -60
- package/bin/session/services/SessionTurnService.js.map +1 -1
- package/bin/session/services/SessionViewService.d.ts +8 -3
- package/bin/session/services/SessionViewService.d.ts.map +1 -1
- package/bin/session/services/SessionViewService.js +10 -5
- package/bin/session/services/SessionViewService.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/Persistence.d.ts +6 -0
- package/bin/session/storage/Persistence.d.ts.map +1 -1
- package/bin/session/storage/Persistence.js +7 -0
- package/bin/session/storage/Persistence.js.map +1 -1
- package/bin/session/storage/RuntimeSessionPort.d.ts +3 -3
- 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 +15 -5
- package/bin/types/agent/SessionActor.d.ts.map +1 -1
- package/bin/types/rpc/RpcProtocol.d.ts +48 -61
- package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.d.ts +5 -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 +223 -0
- package/bin/types/session/SessionMessage.d.ts.map +1 -0
- package/bin/types/session/SessionMessage.js +8 -0
- package/bin/types/session/SessionMessage.js.map +1 -0
- 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/agent-env.test.mjs +4 -4
- package/scripts/remote-session-reconnect.test.mjs +7 -3
- package/scripts/session-action-message.test.mjs +98 -27
- package/scripts/session-history-inflight.test.mjs +2 -2
- package/scripts/session-list-title.test.mjs +6 -19
- package/scripts/session-prompt-runtime.test.mjs +7 -19
- package/scripts/session-recorder.test.mjs +199 -0
- package/scripts/session-title-event.test.mjs +10 -18
- package/src/agent/local/Agent.ts +1 -55
- package/src/agent/local/services/AgentAssemblyService.ts +39 -3
- package/src/agent/local/services/AgentSessions.ts +48 -3
- package/src/agent/remote/RemoteAgent.ts +1 -54
- package/src/agent/remote/RemoteSession.ts +106 -136
- package/src/agent/remote/RemoteTransport.ts +30 -30
- package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +52 -77
- package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +30 -34
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +2 -2
- package/src/executor/types/SessionHistoryPaths.ts +1 -1
- package/src/executor/types/SessionRecords.ts +2 -0
- package/src/index.ts +36 -10
- package/src/rpc/Client.ts +58 -95
- package/src/session/Session.ts +128 -37
- package/src/session/browse/Browse.ts +51 -22
- package/src/session/recorder/JsonlSessionMessageStore.ts +246 -0
- package/src/session/recorder/SessionMessageCodec.ts +284 -0
- package/src/session/recorder/SessionRecorder.ts +871 -0
- package/src/session/recorder/SessionRecorderCompaction.ts +188 -0
- package/src/session/recorder/SessionRecorderHistoryStore.ts +276 -0
- package/src/session/runtime/SessionEventHub.ts +25 -12
- package/src/session/runtime/SessionPromptRuntime.ts +60 -43
- package/src/session/services/SessionStateService.ts +159 -86
- package/src/session/services/SessionTurnService.ts +91 -73
- package/src/session/services/SessionViewService.ts +25 -13
- package/src/session/storage/Paths.ts +2 -2
- package/src/session/storage/Persistence.ts +13 -0
- package/src/session/storage/RuntimeSessionPort.ts +8 -9
- package/src/types/agent/SessionActor.ts +31 -8
- package/src/types/rpc/RpcProtocol.ts +44 -67
- package/src/types/runtime/agent/AgentContext.ts +9 -7
- package/src/types/session/SessionApproval.ts +108 -0
- package/src/types/session/SessionMessage.ts +253 -0
- package/src/types/session/SessionMutation.ts +141 -0
- package/src/types/session/SessionOptions.ts +25 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/bin/session/SessionEventMapper.d.ts +0 -32
- package/bin/session/SessionEventMapper.d.ts.map +0 -1
- package/bin/session/SessionEventMapper.js +0 -131
- package/bin/session/SessionEventMapper.js.map +0 -1
- package/bin/types/sdk/AgentSessionEvent.d.ts +0 -302
- 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/sdk/InternalUiChunkEvent.d.ts +0 -100
- package/bin/types/sdk/InternalUiChunkEvent.d.ts.map +0 -1
- package/bin/types/sdk/InternalUiChunkEvent.js +0 -9
- package/bin/types/sdk/InternalUiChunkEvent.js.map +0 -1
- package/scripts/session-history-archive.test.mjs +0 -188
- package/src/session/SessionEventMapper.ts +0 -148
- package/src/types/sdk/AgentSessionEvent.ts +0 -370
- package/src/types/sdk/InternalUiChunkEvent.ts +0 -108
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
SessionRecordV1,
|
|
14
14
|
SessionMessageRecordV1,
|
|
15
15
|
} from "@/executor/types/SessionRecords.js";
|
|
16
|
-
import type {
|
|
16
|
+
import type { SessionMutation } from "@/types/session/SessionMutation.js";
|
|
17
17
|
import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
|
|
18
18
|
import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
|
|
19
19
|
import type {
|
|
@@ -89,28 +89,23 @@ export interface SessionPromptRuntimeOptions {
|
|
|
89
89
|
/**
|
|
90
90
|
* 广播 session 事件。
|
|
91
91
|
*/
|
|
92
|
-
publish: (
|
|
92
|
+
publish: (mutation: SessionMutation) => void;
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* 构造并持久化一条 user 消息。
|
|
96
96
|
*/
|
|
97
97
|
createAndPersistUserMessage: (
|
|
98
98
|
input: AgentSessionPromptInput,
|
|
99
|
+
turn_id: string,
|
|
100
|
+
input_type: "prompt" | "steer",
|
|
99
101
|
) => Promise<SessionUserMessageV1>;
|
|
100
102
|
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
emit_steer_action: (input: {
|
|
105
|
-
/**
|
|
106
|
-
* steer message 所属的当前 turn 标识。
|
|
107
|
-
*/
|
|
103
|
+
/** 持久化一条用户可见的 turn 错误 Message。 */
|
|
104
|
+
appendErrorMessage: (input: {
|
|
105
|
+
/** 错误所属 turn。 */
|
|
108
106
|
turn_id: string;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
* 已成功持久化的 steer message。
|
|
112
|
-
*/
|
|
113
|
-
message: SessionUserMessageV1;
|
|
107
|
+
/** 用户可见错误文本。 */
|
|
108
|
+
message: string;
|
|
114
109
|
}) => Promise<void>;
|
|
115
110
|
|
|
116
111
|
/**
|
|
@@ -141,7 +136,7 @@ export class SessionPromptRuntime {
|
|
|
141
136
|
private readonly sessionId: string;
|
|
142
137
|
private readonly publish: SessionPromptRuntimeOptions["publish"];
|
|
143
138
|
private readonly createAndPersistUserMessage: SessionPromptRuntimeOptions["createAndPersistUserMessage"];
|
|
144
|
-
private readonly
|
|
139
|
+
private readonly appendErrorMessage: SessionPromptRuntimeOptions["appendErrorMessage"];
|
|
145
140
|
private readonly executeTurn: SessionPromptRuntimeOptions["executeTurn"];
|
|
146
141
|
private readonly stopTurn: SessionPromptRuntimeOptions["stopTurn"];
|
|
147
142
|
private readonly queue: QueuedPrompt[] = [];
|
|
@@ -152,7 +147,7 @@ export class SessionPromptRuntime {
|
|
|
152
147
|
this.sessionId = String(options.sessionId || "").trim();
|
|
153
148
|
this.publish = options.publish;
|
|
154
149
|
this.createAndPersistUserMessage = options.createAndPersistUserMessage;
|
|
155
|
-
this.
|
|
150
|
+
this.appendErrorMessage = options.appendErrorMessage;
|
|
156
151
|
this.executeTurn = options.executeTurn;
|
|
157
152
|
this.stopTurn = options.stopTurn;
|
|
158
153
|
if (!this.sessionId) {
|
|
@@ -231,13 +226,18 @@ export class SessionPromptRuntime {
|
|
|
231
226
|
const activeTurn = createActiveTurnState(turnId);
|
|
232
227
|
this.activeTurn = activeTurn;
|
|
233
228
|
this.publish({
|
|
234
|
-
|
|
235
|
-
|
|
229
|
+
mutation_id: nanoid(),
|
|
230
|
+
variant: "turn",
|
|
231
|
+
type: "start",
|
|
232
|
+
session_id: this.sessionId,
|
|
233
|
+
turn_id: turnId,
|
|
234
|
+
status: "running",
|
|
235
|
+
created_at: Date.now(),
|
|
236
236
|
});
|
|
237
237
|
current.deferredHandle.resolve(createTurnHandle(activeTurn));
|
|
238
238
|
|
|
239
239
|
try {
|
|
240
|
-
await this.createAndPersistUserMessage(current.input);
|
|
240
|
+
await this.createAndPersistUserMessage(current.input, turnId, "prompt");
|
|
241
241
|
const result = await this.executeTurn({
|
|
242
242
|
turnId,
|
|
243
243
|
promptInput: current.input,
|
|
@@ -260,10 +260,14 @@ export class SessionPromptRuntime {
|
|
|
260
260
|
};
|
|
261
261
|
activeTurn.result = finalResult;
|
|
262
262
|
this.publish({
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
mutation_id: nanoid(),
|
|
264
|
+
variant: "turn",
|
|
265
|
+
type: "finish",
|
|
266
|
+
session_id: this.sessionId,
|
|
267
|
+
turn_id: turnId,
|
|
268
|
+
status: stopped ? "stopped" : finalResult.success ? "completed" : "failed",
|
|
269
|
+
created_at: Date.now(),
|
|
265
270
|
text: finalResult.text,
|
|
266
|
-
success: finalResult.success,
|
|
267
271
|
...(finalResult.error ? { error: finalResult.error } : {}),
|
|
268
272
|
});
|
|
269
273
|
activeTurn.deferredFinished.resolve(finalResult);
|
|
@@ -283,16 +287,24 @@ export class SessionPromptRuntime {
|
|
|
283
287
|
};
|
|
284
288
|
activeTurn.result = finalResult;
|
|
285
289
|
if (message !== TURN_STOPPED_MESSAGE) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
+
try {
|
|
291
|
+
await this.appendErrorMessage({
|
|
292
|
+
turn_id: turnId,
|
|
293
|
+
message,
|
|
294
|
+
});
|
|
295
|
+
} catch {
|
|
296
|
+
// Error Message 写入失败不能阻止 turn handle 收口。
|
|
297
|
+
}
|
|
290
298
|
}
|
|
291
299
|
this.publish({
|
|
292
|
-
|
|
293
|
-
|
|
300
|
+
mutation_id: nanoid(),
|
|
301
|
+
variant: "turn",
|
|
302
|
+
type: "finish",
|
|
303
|
+
session_id: this.sessionId,
|
|
304
|
+
turn_id: turnId,
|
|
305
|
+
status: activeTurn.abortController.signal.aborted ? "stopped" : "failed",
|
|
306
|
+
created_at: Date.now(),
|
|
294
307
|
text: "",
|
|
295
|
-
success: false,
|
|
296
308
|
error: message,
|
|
297
309
|
});
|
|
298
310
|
activeTurn.deferredFinished.resolve(finalResult);
|
|
@@ -323,14 +335,23 @@ export class SessionPromptRuntime {
|
|
|
323
335
|
cancelledTurn.result = finalResult;
|
|
324
336
|
cancelledTurn.deferredFinished.resolve(finalResult);
|
|
325
337
|
this.publish({
|
|
326
|
-
|
|
327
|
-
|
|
338
|
+
mutation_id: nanoid(),
|
|
339
|
+
variant: "turn",
|
|
340
|
+
type: "start",
|
|
341
|
+
session_id: this.sessionId,
|
|
342
|
+
turn_id: turnId,
|
|
343
|
+
status: "running",
|
|
344
|
+
created_at: Date.now(),
|
|
328
345
|
});
|
|
329
346
|
this.publish({
|
|
330
|
-
|
|
331
|
-
|
|
347
|
+
mutation_id: nanoid(),
|
|
348
|
+
variant: "turn",
|
|
349
|
+
type: "finish",
|
|
350
|
+
session_id: this.sessionId,
|
|
351
|
+
turn_id: turnId,
|
|
352
|
+
status: "failed",
|
|
353
|
+
created_at: Date.now(),
|
|
332
354
|
text: "",
|
|
333
|
-
success: false,
|
|
334
355
|
error: QUEUED_PROMPT_CANCELLED_MESSAGE,
|
|
335
356
|
});
|
|
336
357
|
item.deferredHandle.resolve(createTurnHandle(cancelledTurn));
|
|
@@ -348,17 +369,13 @@ export class SessionPromptRuntime {
|
|
|
348
369
|
for (let index = 0; index < drained.length; index += 1) {
|
|
349
370
|
const item = drained[index];
|
|
350
371
|
try {
|
|
351
|
-
const message = await this.createAndPersistUserMessage(
|
|
372
|
+
const message = await this.createAndPersistUserMessage(
|
|
373
|
+
item.input,
|
|
374
|
+
activeTurn.turnId,
|
|
375
|
+
"steer",
|
|
376
|
+
);
|
|
352
377
|
item.deferredHandle.resolve(createTurnHandle(activeTurn));
|
|
353
378
|
merged.push(message);
|
|
354
|
-
try {
|
|
355
|
-
await this.emit_steer_action({
|
|
356
|
-
turn_id: activeTurn.turnId,
|
|
357
|
-
message,
|
|
358
|
-
});
|
|
359
|
-
} catch {
|
|
360
|
-
// action 发送失败不应阻断已经持久化的 steer message。
|
|
361
|
-
}
|
|
362
379
|
} catch {
|
|
363
380
|
// 关键点(中文):若某条消息持久化失败,把未处理部分重新放回队列头部,避免静默丢失。
|
|
364
381
|
const remaining = drained.slice(index);
|
|
@@ -19,17 +19,14 @@ import {
|
|
|
19
19
|
} from "@/session/storage/Metadata.js";
|
|
20
20
|
import { touchSessionMetadata } from "@/session/storage/Persistence.js";
|
|
21
21
|
import { ensureSessionTitle } from "@/session/SessionTitle.js";
|
|
22
|
-
import { persistSdkAssistantResult } from "@/session/storage/Persistence.js";
|
|
23
22
|
import { hydrateUserPromptFileParts } from "@executor/messages/SessionAttachmentMapper.js";
|
|
24
23
|
import type { Executor } from "@executor/Executor.js";
|
|
25
|
-
import type {
|
|
24
|
+
import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
|
|
26
25
|
import type {
|
|
27
26
|
AgentSessionConfigSnapshot,
|
|
28
27
|
AgentSessionSetInput,
|
|
29
28
|
} from "@/types/agent/SessionTypes.js";
|
|
30
|
-
import type {
|
|
31
|
-
AgentSessionEvent,
|
|
32
|
-
} from "@/types/sdk/AgentSessionEvent.js";
|
|
29
|
+
import type { SessionMutation } from "@/types/session/SessionMutation.js";
|
|
33
30
|
import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
|
|
34
31
|
import type {
|
|
35
32
|
SessionActionRecordInputV1,
|
|
@@ -38,11 +35,20 @@ import type {
|
|
|
38
35
|
SessionMessageRecordV1,
|
|
39
36
|
SessionUserMessageV1,
|
|
40
37
|
} from "@/executor/types/SessionRecords.js";
|
|
41
|
-
import { to_session_action_record } from "@/executor/types/SessionRecords.js";
|
|
42
38
|
import type { SessionLocalState } from "@/types/session/SessionLocalState.js";
|
|
43
39
|
import { generateId } from "@/utils/Id.js";
|
|
44
40
|
import type { Logger } from "@/utils/logger/Logger.js";
|
|
45
41
|
import type { AgentModel } from "@/model/CityModelAdapter.js";
|
|
42
|
+
import { SessionRecorder } from "@/session/recorder/SessionRecorder.js";
|
|
43
|
+
import { normalize_session_user_parts } from "@/session/recorder/SessionRecorder.js";
|
|
44
|
+
import {
|
|
45
|
+
from_ui_assistant_parts,
|
|
46
|
+
from_ui_user_parts,
|
|
47
|
+
to_executor_ui_message,
|
|
48
|
+
} from "@/session/recorder/SessionMessageCodec.js";
|
|
49
|
+
import fs from "fs-extra";
|
|
50
|
+
import { getSdkAgentSessionMessagesPath } from "@/session/storage/Paths.js";
|
|
51
|
+
import type { SessionMessage } from "@/types/session/SessionMessage.js";
|
|
46
52
|
|
|
47
53
|
type SessionStateServiceOptions = {
|
|
48
54
|
/**
|
|
@@ -63,7 +69,10 @@ type SessionStateServiceOptions = {
|
|
|
63
69
|
/**
|
|
64
70
|
* 当前 session 历史事实源。
|
|
65
71
|
*/
|
|
66
|
-
history_store:
|
|
72
|
+
history_store: SessionHistoryStore;
|
|
73
|
+
|
|
74
|
+
/** 当前 Session Message Recorder。 */
|
|
75
|
+
recorder: SessionRecorder;
|
|
67
76
|
|
|
68
77
|
/**
|
|
69
78
|
* 当前 session 执行器。
|
|
@@ -91,7 +100,7 @@ type SessionStateServiceOptions = {
|
|
|
91
100
|
/**
|
|
92
101
|
* 发布 session 事件。
|
|
93
102
|
*/
|
|
94
|
-
publish_event: (
|
|
103
|
+
publish_event: (mutation: SessionMutation) => void;
|
|
95
104
|
};
|
|
96
105
|
|
|
97
106
|
type SessionSetOptions = {
|
|
@@ -114,7 +123,8 @@ export class SessionStateService {
|
|
|
114
123
|
private readonly agent_id: string;
|
|
115
124
|
private readonly project_root: string;
|
|
116
125
|
private readonly session_id: string;
|
|
117
|
-
private readonly history_store:
|
|
126
|
+
private readonly history_store: SessionHistoryStore;
|
|
127
|
+
private readonly recorder: SessionRecorder;
|
|
118
128
|
private readonly executor: Executor;
|
|
119
129
|
private readonly state: SessionLocalState;
|
|
120
130
|
private readonly logger: Logger;
|
|
@@ -127,6 +137,7 @@ export class SessionStateService {
|
|
|
127
137
|
this.project_root = options.project_root;
|
|
128
138
|
this.session_id = options.session_id;
|
|
129
139
|
this.history_store = options.history_store;
|
|
140
|
+
this.recorder = options.recorder;
|
|
130
141
|
this.executor = options.executor;
|
|
131
142
|
this.state = options.state;
|
|
132
143
|
this.logger = options.logger;
|
|
@@ -197,6 +208,7 @@ export class SessionStateService {
|
|
|
197
208
|
: {}),
|
|
198
209
|
...(metadata.modelId ? { modelId: metadata.modelId } : {}),
|
|
199
210
|
};
|
|
211
|
+
await this.restore_persisted_model();
|
|
200
212
|
})();
|
|
201
213
|
await this.state.initializePromise;
|
|
202
214
|
}
|
|
@@ -206,7 +218,6 @@ export class SessionStateService {
|
|
|
206
218
|
*/
|
|
207
219
|
async ensure_ready_for_execution(): Promise<void> {
|
|
208
220
|
await this.initialize();
|
|
209
|
-
await this.restore_persisted_model();
|
|
210
221
|
if (this.state.ensureConfiguredPromise) {
|
|
211
222
|
await this.state.ensureConfiguredPromise;
|
|
212
223
|
return;
|
|
@@ -239,6 +250,9 @@ export class SessionStateService {
|
|
|
239
250
|
async set(input: AgentSessionSetInput, options?: SessionSetOptions): Promise<void> {
|
|
240
251
|
const should_emit_action = options?.emit_action !== false;
|
|
241
252
|
const previous_model_label = this.state.sessionConfig.modelLabel;
|
|
253
|
+
const previous_model_id = String(
|
|
254
|
+
this.state.sessionConfig.modelId || previous_model_label || "",
|
|
255
|
+
).trim();
|
|
242
256
|
const requested_model_id = String(input.modelId || "").trim();
|
|
243
257
|
const next_model = input.model || (requested_model_id
|
|
244
258
|
? await this.resolve_model_by_id(requested_model_id)
|
|
@@ -251,19 +265,18 @@ export class SessionStateService {
|
|
|
251
265
|
next_model &&
|
|
252
266
|
should_emit_action &&
|
|
253
267
|
this.state.sessionConfig.model &&
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
268
|
+
previous_model_id &&
|
|
269
|
+
next_model_id &&
|
|
270
|
+
previous_model_id !== next_model_id,
|
|
257
271
|
);
|
|
272
|
+
const previous_model_name = previous_model_label || previous_model_id;
|
|
273
|
+
const next_model_name = next_model_label || next_model_id;
|
|
258
274
|
const action_id = `model-switching:${this.session_id}:${Date.now()}:${generateId()}`;
|
|
259
275
|
|
|
260
276
|
if (should_emit_model_switch_action) {
|
|
261
277
|
await this.emit_action_event({
|
|
262
278
|
id: action_id,
|
|
263
|
-
title:
|
|
264
|
-
description: next_model_label
|
|
265
|
-
? `Switching to ${next_model_label}.`
|
|
266
|
-
: undefined,
|
|
279
|
+
title: `Switching session model from ${previous_model_name} to ${next_model_name}`,
|
|
267
280
|
state: "running",
|
|
268
281
|
});
|
|
269
282
|
}
|
|
@@ -287,7 +300,7 @@ export class SessionStateService {
|
|
|
287
300
|
if (should_emit_model_switch_action) {
|
|
288
301
|
await this.emit_action_event({
|
|
289
302
|
id: action_id,
|
|
290
|
-
title:
|
|
303
|
+
title: `Session model switch from ${previous_model_name} to ${next_model_name} failed`,
|
|
291
304
|
description: error instanceof Error ? error.message : String(error),
|
|
292
305
|
state: "failed",
|
|
293
306
|
});
|
|
@@ -298,10 +311,7 @@ export class SessionStateService {
|
|
|
298
311
|
if (should_emit_model_switch_action) {
|
|
299
312
|
await this.emit_action_event({
|
|
300
313
|
id: action_id,
|
|
301
|
-
title:
|
|
302
|
-
description: this.state.sessionConfig.modelLabel
|
|
303
|
-
? `Using ${this.state.sessionConfig.modelLabel}.`
|
|
304
|
-
: undefined,
|
|
314
|
+
title: `Session model switched from ${previous_model_name} to ${next_model_name}`,
|
|
305
315
|
state: "completed",
|
|
306
316
|
});
|
|
307
317
|
}
|
|
@@ -331,7 +341,20 @@ export class SessionStateService {
|
|
|
331
341
|
message?: SessionRecordV1 | null;
|
|
332
342
|
text?: string;
|
|
333
343
|
}): Promise<void> {
|
|
334
|
-
|
|
344
|
+
const parts = params.message && "role" in params.message
|
|
345
|
+
? from_ui_user_parts(params.message.parts)
|
|
346
|
+
: [{
|
|
347
|
+
part_id: `external-user-text:${Date.now()}`,
|
|
348
|
+
type: "text" as const,
|
|
349
|
+
text: String(params.text || "").trim(),
|
|
350
|
+
state: "done" as const,
|
|
351
|
+
}];
|
|
352
|
+
if (parts.length === 0) return;
|
|
353
|
+
await this.recorder.append_user_message({
|
|
354
|
+
turn_id: `external:${this.session_id}:${Date.now()}`,
|
|
355
|
+
input_type: "prompt",
|
|
356
|
+
parts,
|
|
357
|
+
});
|
|
335
358
|
await this.ensure_title_from_history({ generate: true });
|
|
336
359
|
await this.touch_metadata();
|
|
337
360
|
}
|
|
@@ -343,7 +366,17 @@ export class SessionStateService {
|
|
|
343
366
|
message?: SessionRecordV1 | null;
|
|
344
367
|
fallbackText?: string;
|
|
345
368
|
}): Promise<void> {
|
|
346
|
-
|
|
369
|
+
const parts = params.message && "role" in params.message
|
|
370
|
+
? from_ui_assistant_parts(params.message.parts)
|
|
371
|
+
: [{
|
|
372
|
+
part_id: `external-assistant-text:${Date.now()}`,
|
|
373
|
+
sequence: 1,
|
|
374
|
+
type: "text" as const,
|
|
375
|
+
text: String(params.fallbackText || "").trim(),
|
|
376
|
+
state: "done" as const,
|
|
377
|
+
}];
|
|
378
|
+
if (parts.length === 0) return;
|
|
379
|
+
await this.recorder.append_completed_assistant_message({ parts });
|
|
347
380
|
await this.touch_metadata();
|
|
348
381
|
}
|
|
349
382
|
|
|
@@ -351,11 +384,29 @@ export class SessionStateService {
|
|
|
351
384
|
* 仅刷新当前 session metadata。
|
|
352
385
|
*/
|
|
353
386
|
async touch_metadata(): Promise<void> {
|
|
387
|
+
const message_page = await this.recorder.list_messages({
|
|
388
|
+
limit: 500,
|
|
389
|
+
include_internal: true,
|
|
390
|
+
});
|
|
391
|
+
const messages_path = getSdkAgentSessionMessagesPath(
|
|
392
|
+
this.project_root,
|
|
393
|
+
this.agent_id,
|
|
394
|
+
this.session_id,
|
|
395
|
+
);
|
|
396
|
+
const history_bytes = await fs.stat(messages_path)
|
|
397
|
+
.then((file_stat) => file_stat.size)
|
|
398
|
+
.catch(() => 0);
|
|
399
|
+
const preview_text = resolve_message_preview(
|
|
400
|
+
message_page.items[message_page.items.length - 1],
|
|
401
|
+
).slice(0, 180);
|
|
354
402
|
await touchSessionMetadata({
|
|
355
403
|
projectRoot: this.project_root,
|
|
356
404
|
agentId: this.agent_id,
|
|
357
405
|
sessionId: this.session_id,
|
|
358
406
|
sessionConfig: this.state.sessionConfig,
|
|
407
|
+
message_count: message_page.total,
|
|
408
|
+
history_bytes,
|
|
409
|
+
...(preview_text ? { preview_text } : {}),
|
|
359
410
|
});
|
|
360
411
|
}
|
|
361
412
|
|
|
@@ -390,8 +441,11 @@ export class SessionStateService {
|
|
|
390
441
|
const next_title = String(next_metadata.title || "").trim();
|
|
391
442
|
if (!next_title || next_title === before_title) return;
|
|
392
443
|
this.publish_event({
|
|
393
|
-
|
|
394
|
-
|
|
444
|
+
mutation_id: generateId(),
|
|
445
|
+
variant: "session",
|
|
446
|
+
type: "title",
|
|
447
|
+
session_id: this.session_id,
|
|
448
|
+
created_at: Date.now(),
|
|
395
449
|
title: next_title,
|
|
396
450
|
});
|
|
397
451
|
}
|
|
@@ -402,14 +456,8 @@ export class SessionStateService {
|
|
|
402
456
|
async persist_assistant_result(
|
|
403
457
|
assistant_message?: SessionMessageRecordV1 | null,
|
|
404
458
|
): Promise<void> {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
agentId: this.agent_id,
|
|
408
|
-
sessionId: this.session_id,
|
|
409
|
-
sessionConfig: this.state.sessionConfig,
|
|
410
|
-
executor: this.executor,
|
|
411
|
-
assistantMessage: assistant_message,
|
|
412
|
-
});
|
|
459
|
+
void assistant_message;
|
|
460
|
+
await this.touch_metadata();
|
|
413
461
|
}
|
|
414
462
|
|
|
415
463
|
/**
|
|
@@ -418,8 +466,23 @@ export class SessionStateService {
|
|
|
418
466
|
async persist_action_event(
|
|
419
467
|
event: SessionActionRecordV1,
|
|
420
468
|
): Promise<void> {
|
|
421
|
-
const
|
|
422
|
-
|
|
469
|
+
const existing = this.recorder.get_message(event.id);
|
|
470
|
+
if (!existing) {
|
|
471
|
+
const writer = await this.recorder.open_action_message({
|
|
472
|
+
message_id: event.id,
|
|
473
|
+
turn_id: event.metadata.turnId,
|
|
474
|
+
action_type: infer_action_type(event.id),
|
|
475
|
+
title: event.title,
|
|
476
|
+
description: event.description,
|
|
477
|
+
});
|
|
478
|
+
if (event.state === "completed") await writer.complete();
|
|
479
|
+
if (event.state === "failed") await writer.fail(event.description || event.title);
|
|
480
|
+
} else if (existing.type === "action" && event.state !== "running") {
|
|
481
|
+
await this.recorder.update_action_message(event.id, event.state, {
|
|
482
|
+
title: event.title,
|
|
483
|
+
description: event.description,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
423
486
|
await this.touch_metadata();
|
|
424
487
|
}
|
|
425
488
|
|
|
@@ -427,21 +490,26 @@ export class SessionStateService {
|
|
|
427
490
|
* 写入并发布一条 action。
|
|
428
491
|
*/
|
|
429
492
|
async emit_action_event(input: EmitActionInput): Promise<void> {
|
|
430
|
-
const
|
|
431
|
-
{
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
493
|
+
const action_id = String(input.id || "").trim() ||
|
|
494
|
+
`action:${this.session_id}:${Date.now()}`;
|
|
495
|
+
const turn_id = "turnId" in input
|
|
496
|
+
? input.turnId
|
|
497
|
+
: input.metadata?.turnId;
|
|
498
|
+
await this.persist_action_event({
|
|
499
|
+
type: "action",
|
|
500
|
+
id: action_id,
|
|
501
|
+
title: input.title,
|
|
502
|
+
...(input.description ? { description: input.description } : {}),
|
|
503
|
+
state: input.state,
|
|
504
|
+
metadata: {
|
|
505
|
+
v: 1,
|
|
506
|
+
ts: input.metadata?.ts || Date.now(),
|
|
507
|
+
sessionId: this.session_id,
|
|
508
|
+
...(turn_id
|
|
509
|
+
? { turnId: turn_id }
|
|
510
|
+
: {}),
|
|
436
511
|
},
|
|
437
|
-
|
|
438
|
-
);
|
|
439
|
-
try {
|
|
440
|
-
await this.persist_action_event(event);
|
|
441
|
-
} catch {
|
|
442
|
-
// action 持久化失败不应阻断宿主操作。
|
|
443
|
-
}
|
|
444
|
-
this.publish_event(event);
|
|
512
|
+
});
|
|
445
513
|
}
|
|
446
514
|
|
|
447
515
|
/**
|
|
@@ -449,43 +517,22 @@ export class SessionStateService {
|
|
|
449
517
|
*/
|
|
450
518
|
async create_and_persist_user_prompt_message(
|
|
451
519
|
input: AgentSessionPromptInput,
|
|
520
|
+
turn_id: string,
|
|
521
|
+
input_type: "prompt" | "steer" = "prompt",
|
|
452
522
|
): Promise<SessionUserMessageV1> {
|
|
453
523
|
const query = input.query;
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
await this.ensure_title_from_history({ generate: true });
|
|
465
|
-
await this.touch_metadata();
|
|
466
|
-
return message;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// query 是 parts 数组,先把本地图片附件转换为模型可消费的 data URL。
|
|
470
|
-
const parts = await hydrateUserPromptFileParts(
|
|
471
|
-
Array.isArray(query) ? query : [],
|
|
472
|
-
this.project_root,
|
|
473
|
-
);
|
|
474
|
-
const message: SessionUserMessageV1 = {
|
|
475
|
-
id: `u:${this.session_id}:${generateId()}`,
|
|
476
|
-
role: "user",
|
|
477
|
-
parts,
|
|
478
|
-
metadata: {
|
|
479
|
-
v: 1,
|
|
480
|
-
ts: Date.now(),
|
|
481
|
-
sessionId: this.session_id,
|
|
482
|
-
source: "ingress",
|
|
483
|
-
kind: "normal",
|
|
484
|
-
},
|
|
485
|
-
};
|
|
486
|
-
await this.executor.append_user_message({
|
|
487
|
-
message,
|
|
524
|
+
const ui_parts = typeof query === "string"
|
|
525
|
+
? [{ type: "text" as const, text: query.trim() }]
|
|
526
|
+
: await hydrateUserPromptFileParts(
|
|
527
|
+
Array.isArray(query) ? query : [],
|
|
528
|
+
this.project_root,
|
|
529
|
+
);
|
|
530
|
+
const canonical = await this.recorder.append_user_message({
|
|
531
|
+
turn_id,
|
|
532
|
+
input_type,
|
|
533
|
+
parts: normalize_session_user_parts(ui_parts),
|
|
488
534
|
});
|
|
535
|
+
const message = to_executor_ui_message(canonical) as SessionUserMessageV1;
|
|
489
536
|
await this.ensure_title_from_history({ generate: true });
|
|
490
537
|
await this.touch_metadata();
|
|
491
538
|
return message;
|
|
@@ -502,10 +549,36 @@ export class SessionStateService {
|
|
|
502
549
|
: [];
|
|
503
550
|
if (normalized_messages.length <= 0) return;
|
|
504
551
|
for (const message of normalized_messages) {
|
|
505
|
-
await this.
|
|
506
|
-
message,
|
|
552
|
+
await this.recorder.append_user_message({
|
|
553
|
+
turn_id: String(message.metadata?.turnId || `deferred:${this.session_id}:${Date.now()}`),
|
|
554
|
+
input_type: "steer",
|
|
555
|
+
parts: from_ui_user_parts(message.parts),
|
|
507
556
|
});
|
|
508
557
|
}
|
|
509
558
|
await this.touch_metadata();
|
|
510
559
|
}
|
|
511
560
|
}
|
|
561
|
+
|
|
562
|
+
function resolve_message_preview(message: SessionMessage | undefined): string {
|
|
563
|
+
if (!message) return "";
|
|
564
|
+
if (message.type === "user") {
|
|
565
|
+
return message.parts
|
|
566
|
+
.flatMap((part) => part.type === "text" ? [part.text] : [])
|
|
567
|
+
.join("")
|
|
568
|
+
.trim();
|
|
569
|
+
}
|
|
570
|
+
if (message.type === "assistant") {
|
|
571
|
+
return message.parts
|
|
572
|
+
.flatMap((part) => part.type === "text" ? [part.text] : [])
|
|
573
|
+
.join("")
|
|
574
|
+
.trim();
|
|
575
|
+
}
|
|
576
|
+
if (message.type === "action") {
|
|
577
|
+
return [message.title, message.description].filter(Boolean).join("\n");
|
|
578
|
+
}
|
|
579
|
+
return message.message.trim();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function infer_action_type(message_id: string): string {
|
|
583
|
+
return String(message_id || "").split(":")[0] || "action";
|
|
584
|
+
}
|