@downcity/agent 1.1.199 → 1.1.200
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/executor/Executor.d.ts +2 -2
- package/bin/executor/Executor.d.ts.map +1 -1
- package/bin/executor/Executor.js +23 -24
- package/bin/executor/Executor.js.map +1 -1
- package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts +5 -5
- package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts +2 -2
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +23 -30
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
- package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.d.ts.map +1 -1
- package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.js +4 -3
- package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.js.map +1 -1
- package/bin/executor/messages/SessionAttachmentMapper.js.map +1 -1
- package/bin/executor/messages/SessionMessageCodec.d.ts +1 -1
- package/bin/executor/messages/SessionMessageCodec.js +3 -3
- package/bin/executor/messages/SessionMessageCodec.js.map +1 -1
- package/bin/executor/messages/SessionStepEventMapper.d.ts +2 -2
- package/bin/executor/messages/SessionStepEventMapper.d.ts.map +1 -1
- package/bin/executor/messages/SessionStepEventMapper.js.map +1 -1
- package/bin/executor/services/ExecutorInflightService.d.ts +2 -2
- package/bin/executor/services/ExecutorInflightService.d.ts.map +1 -1
- package/bin/executor/services/ExecutorInflightService.js +9 -5
- package/bin/executor/services/ExecutorInflightService.js.map +1 -1
- package/bin/executor/store/history/SessionHistoryStore.d.ts +11 -11
- package/bin/executor/store/history/SessionHistoryStore.d.ts.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts +9 -7
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +97 -52
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
- package/bin/executor/types/SessionMessages.d.ts +41 -22
- package/bin/executor/types/SessionMessages.d.ts.map +1 -1
- package/bin/executor/types/SessionMessages.js +25 -7
- package/bin/executor/types/SessionMessages.js.map +1 -1
- package/bin/index.d.ts +1 -1
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/session/SessionTitle.d.ts.map +1 -1
- package/bin/session/SessionTitle.js +5 -0
- package/bin/session/SessionTitle.js.map +1 -1
- package/bin/session/browse/Browse.d.ts.map +1 -1
- package/bin/session/browse/Browse.js +35 -32
- package/bin/session/browse/Browse.js.map +1 -1
- package/bin/session/services/SessionStateService.d.ts +8 -8
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js +44 -54
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionTurnService.js +3 -3
- package/bin/session/services/SessionTurnService.js.map +1 -1
- package/bin/session/services/SessionViewService.d.ts.map +1 -1
- package/bin/session/services/SessionViewService.js +17 -27
- package/bin/session/services/SessionViewService.js.map +1 -1
- package/bin/types/agent/SessionTypes.d.ts +10 -10
- package/bin/types/agent/SessionTypes.d.ts.map +1 -1
- package/bin/types/executor/SessionRunContext.d.ts +6 -6
- package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
- package/bin/types/sdk/AgentSessionAction.d.ts +67 -0
- package/bin/types/sdk/AgentSessionAction.d.ts.map +1 -0
- package/bin/types/sdk/AgentSessionAction.js +10 -0
- package/bin/types/sdk/AgentSessionAction.js.map +1 -0
- package/bin/types/sdk/AgentSessionEvent.d.ts +2 -2
- package/bin/types/sdk/AgentSessionEvent.d.ts.map +1 -1
- package/package.json +3 -3
- package/scripts/{session-operation-message.test.mjs → session-action-message.test.mjs} +57 -55
- package/src/executor/Executor.ts +28 -29
- package/src/executor/composer/compaction/SessionCompactionComposer.ts +5 -5
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.ts +1 -1
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +32 -37
- package/src/executor/composer/history/jsonl/JsonlSessionHistoryComposer.ts +7 -4
- package/src/executor/messages/SessionAttachmentMapper.ts +2 -2
- package/src/executor/messages/SessionMessageCodec.ts +3 -3
- package/src/executor/messages/SessionStepEventMapper.ts +5 -5
- package/src/executor/services/ExecutorInflightService.ts +11 -7
- package/src/executor/store/history/SessionHistoryStore.ts +11 -10
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +118 -63
- package/src/executor/types/SessionMessages.ts +69 -31
- package/src/index.ts +5 -6
- package/src/session/SessionTitle.ts +3 -0
- package/src/session/browse/Browse.ts +42 -34
- package/src/session/services/SessionStateService.ts +54 -64
- package/src/session/services/SessionTurnService.ts +3 -3
- package/src/session/services/SessionViewService.ts +17 -27
- package/src/types/agent/SessionTypes.ts +10 -10
- package/src/types/executor/SessionRunContext.ts +11 -11
- package/src/types/sdk/AgentSessionAction.ts +79 -0
- package/src/types/sdk/AgentSessionEvent.ts +2 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/bin/types/sdk/AgentSessionOperation.d.ts +0 -99
- package/bin/types/sdk/AgentSessionOperation.d.ts.map +0 -1
- package/bin/types/sdk/AgentSessionOperation.js +0 -10
- package/bin/types/sdk/AgentSessionOperation.js.map +0 -1
- package/src/types/sdk/AgentSessionOperation.ts +0 -125
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import type { LanguageModel } from "ai";
|
|
11
11
|
import type {
|
|
12
|
+
SessionActionMessageV1,
|
|
12
13
|
SessionMessageV1,
|
|
13
14
|
SessionMetadataV1,
|
|
14
15
|
} from "@/executor/types/SessionMessages.js";
|
|
15
16
|
import type { SessionSystemMessage } from "@/executor/types/SessionPrompts.js";
|
|
16
|
-
import type {
|
|
17
|
+
import type { AgentSessionActionRecord } from "@/types/sdk/AgentSessionAction.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* compact 输入参数。
|
|
@@ -50,9 +51,9 @@ export type SessionHistoryCompactInput = {
|
|
|
50
51
|
compactRatio: number;
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
|
-
* 可选
|
|
54
|
+
* 可选 action 发布回调。
|
|
54
55
|
*/
|
|
55
|
-
|
|
56
|
+
onAction?: (action: AgentSessionActionRecord) => Promise<void>;
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
/**
|
|
@@ -165,23 +166,23 @@ export interface SessionHistoryStore {
|
|
|
165
166
|
}): SessionMessageV1;
|
|
166
167
|
|
|
167
168
|
/**
|
|
168
|
-
* 构造
|
|
169
|
+
* 构造 action 消息。
|
|
169
170
|
*/
|
|
170
|
-
|
|
171
|
+
action(input: {
|
|
171
172
|
/**
|
|
172
|
-
* 当前
|
|
173
|
+
* 当前 action 结构化记录。
|
|
173
174
|
*/
|
|
174
|
-
|
|
175
|
+
action: AgentSessionActionRecord;
|
|
175
176
|
|
|
176
177
|
/**
|
|
177
|
-
* 消息元信息(除 schema
|
|
178
|
+
* 消息元信息(除 schema 字段)。
|
|
178
179
|
*/
|
|
179
|
-
metadata:
|
|
180
|
+
metadata: Pick<SessionMetadataV1, "sessionId"> &
|
|
180
181
|
Partial<Pick<SessionMetadataV1, "ts">>;
|
|
181
182
|
|
|
182
183
|
/**
|
|
183
184
|
* 可选消息 ID(默认自动生成)。
|
|
184
185
|
*/
|
|
185
186
|
id?: string;
|
|
186
|
-
}):
|
|
187
|
+
}): SessionActionMessageV1;
|
|
187
188
|
}
|
|
@@ -18,10 +18,16 @@ import { generateId } from "@/utils/Id.js";
|
|
|
18
18
|
import { getLogger } from "@/utils/logger/Logger.js";
|
|
19
19
|
import { compactSessionMessagesIfNeeded } from "@executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js";
|
|
20
20
|
import type {
|
|
21
|
+
SessionActionMessageV1,
|
|
21
22
|
SessionMessageV1,
|
|
22
23
|
SessionMetadataV1,
|
|
23
24
|
} from "@/executor/types/SessionMessages.js";
|
|
24
|
-
import
|
|
25
|
+
import {
|
|
26
|
+
isSessionActionMessage,
|
|
27
|
+
isSessionModelMessage,
|
|
28
|
+
toSessionActionMessage,
|
|
29
|
+
} from "@/executor/types/SessionMessages.js";
|
|
30
|
+
import type { AgentSessionActionRecord } from "@/types/sdk/AgentSessionAction.js";
|
|
25
31
|
import type { SessionSystemMessage } from "@/executor/types/SessionPrompts.js";
|
|
26
32
|
import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
|
|
27
33
|
import type { SessionHistoryPathOverrides } from "@/executor/types/SessionHistoryPaths.js";
|
|
@@ -216,17 +222,60 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
216
222
|
input: unknown,
|
|
217
223
|
): SessionMessageV1 | null {
|
|
218
224
|
if (!input || typeof input !== "object") return null;
|
|
219
|
-
|
|
225
|
+
if ((input as { type?: unknown }).type === "action") {
|
|
226
|
+
return this.normalizeActionMessage(input);
|
|
227
|
+
}
|
|
228
|
+
const candidate = input as { role?: unknown; parts?: unknown };
|
|
220
229
|
const role = String(candidate.role || "");
|
|
221
|
-
if (role !== "user" && role !== "assistant"
|
|
230
|
+
if (role !== "user" && role !== "assistant") {
|
|
222
231
|
return null;
|
|
223
232
|
}
|
|
224
233
|
if (!Array.isArray(candidate.parts)) return null;
|
|
225
|
-
return
|
|
234
|
+
return input as SessionMessageV1;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private normalizeActionMessage(input: unknown): SessionActionMessageV1 | null {
|
|
238
|
+
if (!input || typeof input !== "object") return null;
|
|
239
|
+
const candidate = input as Partial<SessionActionMessageV1>;
|
|
240
|
+
const id = String(candidate.id || "").trim();
|
|
241
|
+
const title = String(candidate.title || "").trim();
|
|
242
|
+
const state = String(candidate.state || "").trim();
|
|
243
|
+
if (!id || !title) return null;
|
|
244
|
+
if (state !== "running" && state !== "completed" && state !== "failed") {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const metadata_input =
|
|
248
|
+
candidate.metadata && typeof candidate.metadata === "object"
|
|
249
|
+
? candidate.metadata
|
|
250
|
+
: {};
|
|
251
|
+
const metadata_candidate = metadata_input as Partial<SessionActionMessageV1["metadata"]>;
|
|
252
|
+
const session_id =
|
|
253
|
+
String(metadata_candidate.sessionId || "").trim() || this.sessionId;
|
|
254
|
+
const description = String(candidate.description || "").trim();
|
|
255
|
+
return {
|
|
256
|
+
type: "action",
|
|
257
|
+
id,
|
|
258
|
+
title,
|
|
259
|
+
...(description ? { description } : {}),
|
|
260
|
+
state,
|
|
261
|
+
metadata: {
|
|
262
|
+
v: 1,
|
|
263
|
+
ts:
|
|
264
|
+
typeof metadata_candidate.ts === "number"
|
|
265
|
+
? metadata_candidate.ts
|
|
266
|
+
: Date.now(),
|
|
267
|
+
sessionId: session_id,
|
|
268
|
+
...(metadata_candidate.turnId ? { turnId: metadata_candidate.turnId } : {}),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
226
271
|
}
|
|
227
272
|
|
|
228
273
|
private hasStructuredAssistantParts(message: SessionMessageV1 | null): boolean {
|
|
229
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
!isSessionModelMessage(message) ||
|
|
276
|
+
message.role !== "assistant" ||
|
|
277
|
+
!Array.isArray(message.parts)
|
|
278
|
+
) {
|
|
230
279
|
return false;
|
|
231
280
|
}
|
|
232
281
|
return message.parts.some((part) => {
|
|
@@ -240,6 +289,8 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
240
289
|
inflight: SessionMessageV1,
|
|
241
290
|
finalMessage: SessionMessageV1,
|
|
242
291
|
): SessionMessageV1 {
|
|
292
|
+
if (!isSessionModelMessage(finalMessage)) return inflight;
|
|
293
|
+
if (!isSessionModelMessage(inflight)) return finalMessage;
|
|
243
294
|
const final_parts = Array.isArray(finalMessage.parts)
|
|
244
295
|
? finalMessage.parts.filter((part) => part && typeof part === "object")
|
|
245
296
|
: [];
|
|
@@ -357,7 +408,10 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
357
408
|
|
|
358
409
|
private async writeInflightUnsafe(message: SessionMessageV1): Promise<void> {
|
|
359
410
|
const normalized = this.normalizePersistedMessage(message);
|
|
360
|
-
if (
|
|
411
|
+
if (
|
|
412
|
+
!isSessionModelMessage(normalized) ||
|
|
413
|
+
normalized.role !== "assistant"
|
|
414
|
+
) {
|
|
361
415
|
throw new Error("inflight assistant must be an assistant UIMessage");
|
|
362
416
|
}
|
|
363
417
|
const file = this.getInflightFilePath();
|
|
@@ -379,6 +433,44 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
379
433
|
);
|
|
380
434
|
}
|
|
381
435
|
|
|
436
|
+
private async rewriteMessagesUnsafe(messages: SessionMessageV1[]): Promise<void> {
|
|
437
|
+
const file = this.getMessagesFilePath();
|
|
438
|
+
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
439
|
+
const payload =
|
|
440
|
+
messages.length > 0
|
|
441
|
+
? messages.map((message) => JSON.stringify(message)).join("\n") + "\n"
|
|
442
|
+
: "";
|
|
443
|
+
await fs.writeFile(temp, payload, "utf8");
|
|
444
|
+
await fs.move(temp, file, { overwrite: true });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private async upsertActionMessageUnsafe(
|
|
448
|
+
message: SessionActionMessageV1,
|
|
449
|
+
): Promise<void> {
|
|
450
|
+
const file = this.getMessagesFilePath();
|
|
451
|
+
const raw = await fs.readFile(file, "utf8").catch(() => "");
|
|
452
|
+
const messages: SessionMessageV1[] = [];
|
|
453
|
+
let replaced = false;
|
|
454
|
+
|
|
455
|
+
for (const line of raw.split("\n").filter(Boolean)) {
|
|
456
|
+
try {
|
|
457
|
+
const current = this.normalizePersistedMessage(JSON.parse(line));
|
|
458
|
+
if (!current) continue;
|
|
459
|
+
if (isSessionActionMessage(current) && current.id === message.id) {
|
|
460
|
+
messages.push(message);
|
|
461
|
+
replaced = true;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
messages.push(current);
|
|
465
|
+
} catch {
|
|
466
|
+
// 关键点(中文):重写 action 时顺手跳过损坏行,避免坏 JSON 阻断状态更新。
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!replaced) messages.push(message);
|
|
471
|
+
await this.rewriteMessagesUnsafe(messages);
|
|
472
|
+
}
|
|
473
|
+
|
|
382
474
|
private async withWriteLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
383
475
|
await this.ensureLayout();
|
|
384
476
|
const lockPath = this.getLockFilePath();
|
|
@@ -438,44 +530,6 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
438
530
|
return system.filter((item) => item && typeof item === "object");
|
|
439
531
|
}
|
|
440
532
|
|
|
441
|
-
private normalizeOperation(
|
|
442
|
-
operation: AgentSessionOperationRecord,
|
|
443
|
-
): AgentSessionOperationRecord {
|
|
444
|
-
const operation_id = String(operation.operationId || "").trim() || generateId();
|
|
445
|
-
const name = String(operation.name || "").trim() || "operation";
|
|
446
|
-
const status = String(operation.status || "").trim();
|
|
447
|
-
const normalized_status: AgentSessionOperationRecord["status"] =
|
|
448
|
-
status === "started" ||
|
|
449
|
-
status === "progress" ||
|
|
450
|
-
status === "finished" ||
|
|
451
|
-
status === "skipped" ||
|
|
452
|
-
status === "failed"
|
|
453
|
-
? status
|
|
454
|
-
: "progress";
|
|
455
|
-
return {
|
|
456
|
-
operationId: operation_id,
|
|
457
|
-
name,
|
|
458
|
-
status: normalized_status,
|
|
459
|
-
...(typeof operation.turnId === "string" && operation.turnId.trim()
|
|
460
|
-
? { turnId: operation.turnId.trim() }
|
|
461
|
-
: {}),
|
|
462
|
-
...(typeof operation.label === "string" && operation.label.trim()
|
|
463
|
-
? { label: operation.label.trim() }
|
|
464
|
-
: {}),
|
|
465
|
-
...(typeof operation.reason === "string" && operation.reason.trim()
|
|
466
|
-
? { reason: operation.reason.trim() }
|
|
467
|
-
: {}),
|
|
468
|
-
...(typeof operation.progress === "number" && Number.isFinite(operation.progress)
|
|
469
|
-
? { progress: Math.max(0, Math.min(1, operation.progress)) }
|
|
470
|
-
: {}),
|
|
471
|
-
...(operation.result !== undefined ? { result: operation.result } : {}),
|
|
472
|
-
...(typeof operation.error === "string" && operation.error.trim()
|
|
473
|
-
? { error: operation.error.trim() }
|
|
474
|
-
: {}),
|
|
475
|
-
...(operation.visible === false ? { visible: false } : {}),
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
533
|
async compact(input: SessionHistoryCompactInput): Promise<{
|
|
480
534
|
compacted: boolean;
|
|
481
535
|
reason?: string;
|
|
@@ -512,7 +566,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
512
566
|
maxInputTokensApprox: input.maxInputTokensApprox,
|
|
513
567
|
archiveOnCompact: input.archiveOnCompact,
|
|
514
568
|
compactRatio: input.compactRatio,
|
|
515
|
-
...(input.
|
|
569
|
+
...(input.onAction ? { onAction: input.onAction } : {}),
|
|
516
570
|
},
|
|
517
571
|
);
|
|
518
572
|
}
|
|
@@ -521,10 +575,14 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
521
575
|
await this.withWriteLock(async () => {
|
|
522
576
|
const normalized = this.normalizePersistedMessage(message);
|
|
523
577
|
if (!normalized) return;
|
|
578
|
+
if (isSessionActionMessage(normalized)) {
|
|
579
|
+
await this.upsertActionMessageUnsafe(normalized);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
524
582
|
|
|
525
583
|
// 关键点(中文):若上一次 assistant 在运行中中断,新的 user 到来前先把残留快照收口到正式历史,
|
|
526
584
|
// 避免 `list()` 时旧 inflight 跑到新 user 后面,打乱时序。
|
|
527
|
-
if (normalized.role === "user") {
|
|
585
|
+
if (isSessionModelMessage(normalized) && normalized.role === "user") {
|
|
528
586
|
const current_inflight = await this.readInflightUnsafe();
|
|
529
587
|
if (current_inflight) {
|
|
530
588
|
await this.appendMessageUnsafe(current_inflight);
|
|
@@ -676,29 +734,26 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
676
734
|
};
|
|
677
735
|
}
|
|
678
736
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
metadata:
|
|
737
|
+
action(input: {
|
|
738
|
+
action: AgentSessionActionRecord;
|
|
739
|
+
metadata: Pick<SessionMetadataV1, "sessionId"> &
|
|
682
740
|
Partial<Pick<SessionMetadataV1, "ts">>;
|
|
683
741
|
id?: string;
|
|
684
|
-
}):
|
|
685
|
-
const operation = this.normalizeOperation(input.operation);
|
|
686
|
-
const label = String(
|
|
687
|
-
operation.label || `${operation.name}: ${operation.status}`,
|
|
688
|
-
).trim();
|
|
742
|
+
}): SessionActionMessageV1 {
|
|
689
743
|
const { ts, ...metadata } = input.metadata;
|
|
744
|
+
const message = toSessionActionMessage(
|
|
745
|
+
{
|
|
746
|
+
...input.action,
|
|
747
|
+
...(input.id ? { id: input.id } : {}),
|
|
748
|
+
},
|
|
749
|
+
metadata.sessionId || this.sessionId,
|
|
750
|
+
);
|
|
690
751
|
return {
|
|
691
|
-
|
|
692
|
-
role: "operation",
|
|
752
|
+
...message,
|
|
693
753
|
metadata: {
|
|
694
|
-
|
|
695
|
-
ts: typeof ts === "number" ? ts :
|
|
696
|
-
...metadata,
|
|
697
|
-
source: "operation",
|
|
698
|
-
kind: "operation",
|
|
699
|
-
operation,
|
|
754
|
+
...message.metadata,
|
|
755
|
+
ts: typeof ts === "number" ? ts : message.metadata.ts,
|
|
700
756
|
},
|
|
701
|
-
parts: [{ type: "text", text: label }],
|
|
702
757
|
};
|
|
703
758
|
}
|
|
704
759
|
}
|
|
@@ -9,18 +9,21 @@
|
|
|
9
9
|
|
|
10
10
|
import type { UIMessage } from "ai";
|
|
11
11
|
import type { JsonObject } from "@/types/common/Json.js";
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
AgentSessionActionRecord,
|
|
14
|
+
AgentSessionActionState,
|
|
15
|
+
} from "@/types/sdk/AgentSessionAction.js";
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Session 消息:以 UIMessage[] 作为唯一事实源。
|
|
16
19
|
*
|
|
17
20
|
* 关键点(中文)
|
|
18
21
|
* - 持久化存储在 `.downcity/agents/<encodedAgentId>/sessions/<encodedSessionId>/messages/messages.jsonl`
|
|
19
|
-
* -
|
|
22
|
+
* - 默认只存模型消息,或 `type=action` 的 UI 状态消息。
|
|
20
23
|
* - compact 会把更早消息压缩为一条 `assistant` 摘要消息
|
|
21
|
-
* -
|
|
24
|
+
* - action 只用于 UI timeline,不进入 LLM 输入
|
|
22
25
|
*/
|
|
23
|
-
export type SessionMessageKind = "normal" | "summary"
|
|
26
|
+
export type SessionMessageKind = "normal" | "summary";
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* Session 消息来源类型。
|
|
@@ -33,8 +36,7 @@ export type SessionMessageKind = "normal" | "summary" | "operation";
|
|
|
33
36
|
export type SessionMessageSource =
|
|
34
37
|
| "ingress"
|
|
35
38
|
| "egress"
|
|
36
|
-
| "compact"
|
|
37
|
-
| "operation";
|
|
39
|
+
| "compact";
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* 入站消息细分类型。
|
|
@@ -74,14 +76,6 @@ export type SessionMetadataV1 = {
|
|
|
74
76
|
archiveId?: string;
|
|
75
77
|
/** compact 摘要所覆盖的原始消息范围。 */
|
|
76
78
|
sourceRange?: SessionMessageSourceRangeV1;
|
|
77
|
-
/**
|
|
78
|
-
* operation message 对应的结构化操作记录。
|
|
79
|
-
*
|
|
80
|
-
* 说明(中文)
|
|
81
|
-
* - 仅当 `kind=operation` 或 `source=operation` 时存在。
|
|
82
|
-
* - 该字段供前端恢复时间线状态,不会进入 LLM 输入。
|
|
83
|
-
*/
|
|
84
|
-
operation?: AgentSessionOperationRecord;
|
|
85
79
|
/**
|
|
86
80
|
* 扩展元信息。
|
|
87
81
|
*
|
|
@@ -98,43 +92,87 @@ export type SessionMetadataV1 = {
|
|
|
98
92
|
export type SessionModelMessageV1 = UIMessage<SessionMetadataV1>;
|
|
99
93
|
|
|
100
94
|
/**
|
|
101
|
-
*
|
|
95
|
+
* action 消息元信息。
|
|
96
|
+
*/
|
|
97
|
+
export type SessionActionMetadataV1 = {
|
|
98
|
+
/** 元信息 schema 版本号。 */
|
|
99
|
+
v: 1;
|
|
100
|
+
/** 当前 action 写入时的毫秒时间戳。 */
|
|
101
|
+
ts: number;
|
|
102
|
+
/** 当前 action 所属的 session ID。 */
|
|
103
|
+
sessionId: string;
|
|
104
|
+
/** 当前 action 关联的 turn 标识。 */
|
|
105
|
+
turnId?: string;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* action 类型的 Session 消息结构。
|
|
102
110
|
*
|
|
103
111
|
* 关键点(中文)
|
|
104
|
-
* - `
|
|
112
|
+
* - `action` 不是 AI SDK 原生模型消息。
|
|
105
113
|
* - 它只存在于 JSONL history 与前端 timeline,进入 LLM 前必须过滤。
|
|
106
114
|
*/
|
|
107
|
-
export type
|
|
108
|
-
/**
|
|
109
|
-
|
|
115
|
+
export type SessionActionMessageV1 = {
|
|
116
|
+
/** item 类型固定为 `action`。 */
|
|
117
|
+
type: "action";
|
|
118
|
+
/** 同一个 action 生命周期内稳定复用的 ID。 */
|
|
119
|
+
id: string;
|
|
120
|
+
/** 当前 action 标题。 */
|
|
121
|
+
title: string;
|
|
122
|
+
/** 当前 action 描述。 */
|
|
123
|
+
description?: string;
|
|
124
|
+
/** 当前 action 状态。 */
|
|
125
|
+
state: AgentSessionActionState;
|
|
126
|
+
/** action 元信息。 */
|
|
127
|
+
metadata: SessionActionMetadataV1;
|
|
110
128
|
};
|
|
111
129
|
|
|
112
130
|
/**
|
|
113
131
|
* Session 持久化消息结构。
|
|
114
132
|
*/
|
|
115
|
-
export type SessionMessageV1 = SessionModelMessageV1 |
|
|
133
|
+
export type SessionMessageV1 = SessionModelMessageV1 | SessionActionMessageV1;
|
|
116
134
|
|
|
117
135
|
/**
|
|
118
136
|
* user 角色的 Session 消息结构。
|
|
119
137
|
*/
|
|
120
|
-
export type SessionUserMessageV1 =
|
|
138
|
+
export type SessionUserMessageV1 = SessionModelMessageV1 & {
|
|
121
139
|
/** 消息角色固定为 `user`。 */
|
|
122
140
|
role: "user";
|
|
123
141
|
};
|
|
124
142
|
|
|
125
143
|
/**
|
|
126
|
-
* 判断一条消息是否为
|
|
144
|
+
* 判断一条消息是否为 action message。
|
|
127
145
|
*/
|
|
128
|
-
export function
|
|
146
|
+
export function isSessionActionMessage(
|
|
129
147
|
message: SessionMessageV1 | null | undefined,
|
|
130
|
-
):
|
|
148
|
+
): message is SessionActionMessageV1 {
|
|
131
149
|
if (!message || typeof message !== "object") return false;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
return (message as { type?: unknown }).type === "action";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 从订阅事件构造 action message。
|
|
155
|
+
*/
|
|
156
|
+
export function toSessionActionMessage(
|
|
157
|
+
action: AgentSessionActionRecord,
|
|
158
|
+
session_id: string,
|
|
159
|
+
): SessionActionMessageV1 {
|
|
160
|
+
const title = String(action.title || "").trim() || "Action";
|
|
161
|
+
const description = String(action.description || "").trim();
|
|
162
|
+
const id = String(action.id || "").trim() || `action:${session_id}:${Date.now()}`;
|
|
163
|
+
return {
|
|
164
|
+
type: "action",
|
|
165
|
+
id,
|
|
166
|
+
title,
|
|
167
|
+
...(description ? { description } : {}),
|
|
168
|
+
state: action.state,
|
|
169
|
+
metadata: {
|
|
170
|
+
v: 1,
|
|
171
|
+
ts: Date.now(),
|
|
172
|
+
sessionId: session_id,
|
|
173
|
+
...(action.turnId ? { turnId: action.turnId } : {}),
|
|
174
|
+
},
|
|
175
|
+
};
|
|
138
176
|
}
|
|
139
177
|
|
|
140
178
|
/**
|
|
@@ -144,5 +182,5 @@ export function isSessionModelMessage(
|
|
|
144
182
|
message: SessionMessageV1 | null | undefined,
|
|
145
183
|
): message is SessionModelMessageV1 {
|
|
146
184
|
if (!message || typeof message !== "object") return false;
|
|
147
|
-
return
|
|
185
|
+
return !isSessionActionMessage(message);
|
|
148
186
|
}
|
package/src/index.ts
CHANGED
|
@@ -56,12 +56,11 @@ export type {
|
|
|
56
56
|
AgentSessionUnsubscribe,
|
|
57
57
|
} from "./types/sdk/AgentSessionEvent.js";
|
|
58
58
|
export type {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
} from "./types/sdk/AgentSessionOperation.js";
|
|
59
|
+
AgentSessionActionCallback,
|
|
60
|
+
AgentSessionActionEvent,
|
|
61
|
+
AgentSessionActionRecord,
|
|
62
|
+
AgentSessionActionState,
|
|
63
|
+
} from "./types/sdk/AgentSessionAction.js";
|
|
65
64
|
export type { AgentSessionPromptInput, SessionUserMessagePart } from "./types/sdk/AgentSessionPrompt.js";
|
|
66
65
|
export type { AgentSessionStopResult } from "./types/sdk/AgentSessionStop.js";
|
|
67
66
|
export type {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { generateText, type LanguageModel } from "ai";
|
|
11
11
|
import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
|
|
12
12
|
import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
13
|
+
import { isSessionModelMessage } from "@/executor/types/SessionMessages.js";
|
|
13
14
|
import {
|
|
14
15
|
normalizeSessionTitle,
|
|
15
16
|
readSessionMetadata,
|
|
@@ -61,6 +62,7 @@ function truncateTitle(input: string, maxChars: number): string {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
function extractTextFromMessage(message: SessionMessageV1): string {
|
|
65
|
+
if (!isSessionModelMessage(message)) return "";
|
|
64
66
|
if (!Array.isArray(message.parts)) return "";
|
|
65
67
|
const texts: string[] = [];
|
|
66
68
|
for (const part of message.parts) {
|
|
@@ -76,6 +78,7 @@ function extractTextFromMessage(message: SessionMessageV1): string {
|
|
|
76
78
|
|
|
77
79
|
function resolveFirstUserText(messages: SessionMessageV1[]): string {
|
|
78
80
|
for (const message of messages) {
|
|
81
|
+
if (!isSessionModelMessage(message)) continue;
|
|
79
82
|
if (message.role !== "user") continue;
|
|
80
83
|
const text = extractTextFromMessage(message);
|
|
81
84
|
if (text) return text;
|
|
@@ -26,10 +26,15 @@ import type {
|
|
|
26
26
|
AgentSessionTimelineEvent,
|
|
27
27
|
} from "@/types/agent/AgentTypes.js";
|
|
28
28
|
import type {
|
|
29
|
+
SessionActionMessageV1,
|
|
29
30
|
SessionMessageV1,
|
|
30
31
|
SessionMetadataV1,
|
|
32
|
+
SessionModelMessageV1,
|
|
33
|
+
} from "@/executor/types/SessionMessages.js";
|
|
34
|
+
import {
|
|
35
|
+
isSessionActionMessage,
|
|
36
|
+
isSessionModelMessage,
|
|
31
37
|
} from "@/executor/types/SessionMessages.js";
|
|
32
|
-
import { isSessionOperationMessage } from "@/executor/types/SessionMessages.js";
|
|
33
38
|
import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
|
|
34
39
|
import { pickLastSuccessfulChatSendText } from "@/executor/messages/UserVisibleText.js";
|
|
35
40
|
import { getSdkAgentSessionMessagesPath } from "@/session/storage/Paths.js";
|
|
@@ -172,7 +177,7 @@ function extractMessageText(parts: unknown): string {
|
|
|
172
177
|
return texts.join("\n").trim();
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
function extractAssistantToolSummary(message:
|
|
180
|
+
function extractAssistantToolSummary(message: SessionModelMessageV1): string {
|
|
176
181
|
if (!Array.isArray(message.parts)) return "";
|
|
177
182
|
const toolNames = new Set<string>();
|
|
178
183
|
for (const part of message.parts as AnyUiPart[]) {
|
|
@@ -189,6 +194,12 @@ function extractAssistantToolSummary(message: SessionMessageV1): string {
|
|
|
189
194
|
* 解析单条 session 消息的用户可见预览文本。
|
|
190
195
|
*/
|
|
191
196
|
export function resolveSessionMessagePreview(message: SessionMessageV1): string {
|
|
197
|
+
if (isSessionActionMessage(message)) {
|
|
198
|
+
return message.description
|
|
199
|
+
? `${message.title}\n${message.description}`
|
|
200
|
+
: message.title;
|
|
201
|
+
}
|
|
202
|
+
if (!isSessionModelMessage(message)) return "";
|
|
192
203
|
const plainText = extractMessageText(message.parts);
|
|
193
204
|
if (plainText) return plainText;
|
|
194
205
|
if (message.role !== "assistant") return "";
|
|
@@ -226,7 +237,7 @@ function extractToolResultOutput(part: ToolPartCompatShape): unknown {
|
|
|
226
237
|
}
|
|
227
238
|
|
|
228
239
|
function toTimelineEvent(params: {
|
|
229
|
-
message:
|
|
240
|
+
message: SessionModelMessageV1;
|
|
230
241
|
role: AgentSessionTimelineEvent["role"];
|
|
231
242
|
text: string;
|
|
232
243
|
sequence: number;
|
|
@@ -241,15 +252,21 @@ function toTimelineEvent(params: {
|
|
|
241
252
|
...(typeof metadata?.source === "string" ? { source: metadata.source } : {}),
|
|
242
253
|
text: params.text,
|
|
243
254
|
...(params.toolName ? { toolName: params.toolName } : {}),
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function toActionTimelineEvent(
|
|
259
|
+
message: SessionActionMessageV1,
|
|
260
|
+
): AgentSessionTimelineEvent {
|
|
261
|
+
const metadata = message.metadata || null;
|
|
262
|
+
return {
|
|
263
|
+
id: `${String(message.id || "")}:0`,
|
|
264
|
+
role: "action",
|
|
265
|
+
...(typeof metadata?.ts === "number" ? { ts: metadata.ts } : {}),
|
|
266
|
+
text: resolveSessionMessagePreview(message),
|
|
267
|
+
actionTitle: message.title,
|
|
268
|
+
...(message.description ? { actionDescription: message.description } : {}),
|
|
269
|
+
actionState: message.state,
|
|
253
270
|
};
|
|
254
271
|
}
|
|
255
272
|
|
|
@@ -259,21 +276,11 @@ function toTimelineEvent(params: {
|
|
|
259
276
|
export function toSessionTimelineEvents(
|
|
260
277
|
message: SessionMessageV1,
|
|
261
278
|
): AgentSessionTimelineEvent[] {
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
if (metadata?.operation?.status !== "finished") return [];
|
|
265
|
-
return [
|
|
266
|
-
toTimelineEvent({
|
|
267
|
-
message,
|
|
268
|
-
role: "operation",
|
|
269
|
-
text:
|
|
270
|
-
String(metadata?.operation?.label || "").trim() ||
|
|
271
|
-
resolveSessionMessagePreview(message),
|
|
272
|
-
sequence: 0,
|
|
273
|
-
}),
|
|
274
|
-
];
|
|
279
|
+
if (isSessionActionMessage(message)) {
|
|
280
|
+
return [toActionTimelineEvent(message)];
|
|
275
281
|
}
|
|
276
282
|
|
|
283
|
+
if (!isSessionModelMessage(message)) return [];
|
|
277
284
|
if (message.role !== "assistant") {
|
|
278
285
|
return [
|
|
279
286
|
toTimelineEvent({
|
|
@@ -368,10 +375,11 @@ export async function loadSessionMessagesFromPath(
|
|
|
368
375
|
try {
|
|
369
376
|
const parsed = JSON.parse(line) as SessionMessageV1;
|
|
370
377
|
if (!parsed || typeof parsed !== "object") continue;
|
|
378
|
+
const candidate = parsed as { type?: unknown; role?: unknown };
|
|
371
379
|
if (
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
380
|
+
candidate.type !== "action" &&
|
|
381
|
+
candidate.role !== "user" &&
|
|
382
|
+
candidate.role !== "assistant"
|
|
375
383
|
) {
|
|
376
384
|
continue;
|
|
377
385
|
}
|
|
@@ -387,8 +395,7 @@ export async function loadSessionMessagesFromPath(
|
|
|
387
395
|
try {
|
|
388
396
|
const parsed = (await fs.readJson(inflight_path)) as SessionMessageV1;
|
|
389
397
|
if (
|
|
390
|
-
parsed &&
|
|
391
|
-
typeof parsed === "object" &&
|
|
398
|
+
isSessionModelMessage(parsed) &&
|
|
392
399
|
parsed.role === "assistant" &&
|
|
393
400
|
Array.isArray(parsed.parts)
|
|
394
401
|
) {
|
|
@@ -417,14 +424,14 @@ export async function loadSessionArchiveMessagesFromPath(
|
|
|
417
424
|
const items = Array.isArray(raw?.messages) ? raw.messages : [];
|
|
418
425
|
return items.filter((item): item is SessionMessageV1 => {
|
|
419
426
|
if (!item || typeof item !== "object") return false;
|
|
420
|
-
const candidate = item as
|
|
427
|
+
const candidate = item as { type?: unknown; role?: unknown; parts?: unknown };
|
|
421
428
|
return (
|
|
422
429
|
(
|
|
430
|
+
candidate.type === "action" ||
|
|
423
431
|
candidate.role === "user" ||
|
|
424
|
-
candidate.role === "assistant"
|
|
425
|
-
candidate.role === "operation"
|
|
432
|
+
candidate.role === "assistant"
|
|
426
433
|
) &&
|
|
427
|
-
Array.isArray(candidate.parts)
|
|
434
|
+
(candidate.type === "action" || Array.isArray(candidate.parts))
|
|
428
435
|
);
|
|
429
436
|
});
|
|
430
437
|
} catch {
|
|
@@ -433,6 +440,7 @@ export async function loadSessionArchiveMessagesFromPath(
|
|
|
433
440
|
}
|
|
434
441
|
|
|
435
442
|
function isCompactSummaryMessage(message: SessionMessageV1): boolean {
|
|
443
|
+
if (!isSessionModelMessage(message)) return false;
|
|
436
444
|
const metadata = (message.metadata || null) as SessionMetadataV1 | null;
|
|
437
445
|
return metadata?.source === "compact" || metadata?.kind === "summary";
|
|
438
446
|
}
|