@downcity/agent 1.1.198 → 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/context/LocalSessionContextComposer.d.ts +2 -2
- package/bin/executor/composer/context/LocalSessionContextComposer.d.ts.map +1 -1
- package/bin/executor/composer/context/LocalSessionContextComposer.js.map +1 -1
- package/bin/executor/composer/context/SessionContextComposer.d.ts +2 -2
- package/bin/executor/composer/context/SessionContextComposer.d.ts.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/core-engine/CoreEngineRunner.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
- package/bin/executor/core-engine/CoreEngineSignals.d.ts +4 -4
- package/bin/executor/core-engine/CoreEngineSignals.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineSignals.js.map +1 -1
- package/bin/executor/core-engine/CoreEngineUiStreamCollector.d.ts +3 -3
- package/bin/executor/core-engine/CoreEngineUiStreamCollector.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineUiStreamCollector.js.map +1 -1
- package/bin/executor/messages/AssistantResultPersistence.d.ts +3 -3
- package/bin/executor/messages/AssistantResultPersistence.d.ts.map +1 -1
- package/bin/executor/messages/AssistantResultPersistence.js.map +1 -1
- package/bin/executor/messages/SessionAttachmentMapper.d.ts +3 -3
- package/bin/executor/messages/SessionAttachmentMapper.d.ts.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.d.ts.map +1 -1
- package/bin/executor/messages/SessionMessageCodec.js +5 -5
- package/bin/executor/messages/SessionMessageCodec.js.map +1 -1
- package/bin/executor/messages/SessionMessageLog.d.ts +3 -3
- package/bin/executor/messages/SessionMessageLog.d.ts.map +1 -1
- package/bin/executor/messages/SessionMessageLog.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/messages/UserVisibleText.d.ts +2 -2
- package/bin/executor/messages/UserVisibleText.d.ts.map +1 -1
- package/bin/executor/messages/UserVisibleText.js +5 -0
- package/bin/executor/messages/UserVisibleText.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 +98 -52
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
- package/bin/executor/types/SessionMessages.d.ts +57 -18
- package/bin/executor/types/SessionMessages.d.ts.map +1 -1
- package/bin/executor/types/SessionMessages.js +33 -5
- package/bin/executor/types/SessionMessages.js.map +1 -1
- package/bin/executor/types/SessionRun.d.ts +2 -3
- package/bin/executor/types/SessionRun.d.ts.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 +36 -26
- package/bin/session/browse/Browse.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.map +1 -1
- package/bin/session/services/SessionStateService.d.ts +10 -10
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js +44 -53
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionTurnService.d.ts.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/session/storage/Persistence.d.ts +2 -2
- package/bin/session/storage/Persistence.d.ts.map +1 -1
- package/bin/session/storage/Persistence.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/bin/types/sdk/AgentSessionTurn.d.ts +2 -2
- package/bin/types/sdk/AgentSessionTurn.d.ts.map +1 -1
- package/package.json +3 -3
- package/scripts/{session-operation-message.test.mjs → session-action-message.test.mjs} +58 -41
- 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/context/LocalSessionContextComposer.ts +5 -2
- package/src/executor/composer/context/SessionContextComposer.ts +5 -2
- package/src/executor/composer/history/jsonl/JsonlSessionHistoryComposer.ts +7 -4
- package/src/executor/core-engine/CoreEngineRunner.ts +7 -4
- package/src/executor/core-engine/CoreEngineSignals.ts +7 -7
- package/src/executor/core-engine/CoreEngineUiStreamCollector.ts +5 -5
- package/src/executor/messages/AssistantResultPersistence.ts +6 -3
- package/src/executor/messages/SessionAttachmentMapper.ts +12 -9
- package/src/executor/messages/SessionMessageCodec.ts +5 -7
- package/src/executor/messages/SessionMessageLog.ts +5 -3
- package/src/executor/messages/SessionStepEventMapper.ts +5 -5
- package/src/executor/messages/UserVisibleText.ts +8 -2
- 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 +120 -63
- package/src/executor/types/SessionMessages.ts +92 -22
- package/src/executor/types/SessionRun.ts +6 -3
- package/src/index.ts +5 -6
- package/src/session/SessionTitle.ts +3 -0
- package/src/session/browse/Browse.ts +49 -30
- package/src/session/runtime/SessionPromptRuntime.ts +6 -3
- package/src/session/services/SessionStateService.ts +56 -65
- package/src/session/services/SessionTurnService.ts +5 -4
- package/src/session/services/SessionViewService.ts +17 -27
- package/src/session/storage/Persistence.ts +5 -2
- 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/src/types/sdk/AgentSessionTurn.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
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isTextUIPart,
|
|
13
13
|
isToolUIPart,
|
|
14
14
|
} from "ai";
|
|
15
|
-
import type {
|
|
15
|
+
import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
16
16
|
import type { JsonObject } from "@/types/common/Json.js";
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -192,7 +192,7 @@ export function summarizeStepForDebug(stepResult: unknown): JsonObject {
|
|
|
192
192
|
* 汇总最终 assistant UI 消息的调试摘要。
|
|
193
193
|
*/
|
|
194
194
|
export function summarizeUiMessageForDebug(
|
|
195
|
-
message:
|
|
195
|
+
message: SessionModelMessageV1 | null | undefined,
|
|
196
196
|
): JsonObject {
|
|
197
197
|
const parts = Array.isArray(message?.parts) ? message.parts : [];
|
|
198
198
|
const text = parts
|
|
@@ -230,9 +230,9 @@ export function summarizeUiMessageForDebug(
|
|
|
230
230
|
* - 多 step 场景下,最终 assistant message 需要把各 step 的 UI part 串起来。
|
|
231
231
|
*/
|
|
232
232
|
export function mergeAssistantUiMessages(
|
|
233
|
-
base:
|
|
234
|
-
incoming:
|
|
235
|
-
):
|
|
233
|
+
base: SessionModelMessageV1 | null,
|
|
234
|
+
incoming: SessionModelMessageV1,
|
|
235
|
+
): SessionModelMessageV1 {
|
|
236
236
|
if (!base) return incoming;
|
|
237
237
|
const baseMetadata = base.metadata;
|
|
238
238
|
const incomingMetadata = incoming.metadata;
|
|
@@ -254,7 +254,7 @@ export function mergeAssistantUiMessages(
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
function pickIncompleteToolParts(
|
|
257
|
-
message:
|
|
257
|
+
message: SessionModelMessageV1 | null | undefined,
|
|
258
258
|
): Array<{
|
|
259
259
|
toolName: string;
|
|
260
260
|
state: string;
|
|
@@ -304,7 +304,7 @@ export function buildIncompleteResponseRecoveryNudge(
|
|
|
304
304
|
*/
|
|
305
305
|
export function detectIncompleteResponse(params: {
|
|
306
306
|
stepResult: unknown;
|
|
307
|
-
assistantMessage:
|
|
307
|
+
assistantMessage: SessionModelMessageV1 | null | undefined;
|
|
308
308
|
}): {
|
|
309
309
|
reason: string;
|
|
310
310
|
details: JsonObject;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { streamText } from "ai";
|
|
11
11
|
import type { Logger } from "@/utils/logger/Logger.js";
|
|
12
12
|
import type { JsonObject } from "@/types/common/Json.js";
|
|
13
|
-
import type {
|
|
13
|
+
import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
14
14
|
import type { SessionUiMessageChunkCallback } from "@/executor/types/SessionRun.js";
|
|
15
15
|
import { generateId } from "@/utils/Id.js";
|
|
16
16
|
import {
|
|
@@ -37,7 +37,7 @@ export async function collectFinalAssistantMessageFromUiStream(params: {
|
|
|
37
37
|
/**
|
|
38
38
|
* 构造 fallback assistant 消息的工厂函数。
|
|
39
39
|
*/
|
|
40
|
-
buildFallbackAssistantMessage: (text: string) =>
|
|
40
|
+
buildFallbackAssistantMessage: (text: string) => SessionModelMessageV1;
|
|
41
41
|
/**
|
|
42
42
|
* UI stream chunk 回调。
|
|
43
43
|
*/
|
|
@@ -50,12 +50,12 @@ export async function collectFinalAssistantMessageFromUiStream(params: {
|
|
|
50
50
|
* - 此时仍应尽量用已经收到的 text delta 构造可持久化 assistant 消息。
|
|
51
51
|
*/
|
|
52
52
|
abortSignal?: AbortSignal;
|
|
53
|
-
}): Promise<
|
|
54
|
-
let streamedAssistantMessage:
|
|
53
|
+
}): Promise<SessionModelMessageV1> {
|
|
54
|
+
let streamedAssistantMessage: SessionModelMessageV1 | null = null;
|
|
55
55
|
let uiFinishSummary: JsonObject | null = null;
|
|
56
56
|
let streamed_text = "";
|
|
57
57
|
|
|
58
|
-
const uiStream = params.result.toUIMessageStream<
|
|
58
|
+
const uiStream = params.result.toUIMessageStream<SessionModelMessageV1>({
|
|
59
59
|
// 关键点(中文):SDK stream 需要 reasoning 旁路事件时可直接消费;最终落盘仍由 responseMessage 收敛。
|
|
60
60
|
originalMessages: [],
|
|
61
61
|
generateMessageId: () => `a:${params.sessionId}:${generateId()}`,
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* - 运行中 step/tool 的持久化由 inflight 快照承担;run 结束时只关心最终 assistant 如何收口。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
SessionMessageV1,
|
|
12
|
+
SessionModelMessageV1,
|
|
13
|
+
} from "@/executor/types/SessionMessages.js";
|
|
11
14
|
import { resolveAssistantMessageForPersistence } from "@/executor/messages/UserVisibleText.js";
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -40,7 +43,7 @@ export interface PersistAssistantResultParams {
|
|
|
40
43
|
/**
|
|
41
44
|
* 本轮执行得到的 assistant message。
|
|
42
45
|
*/
|
|
43
|
-
assistantMessage:
|
|
46
|
+
assistantMessage: SessionModelMessageV1 | null | undefined;
|
|
44
47
|
/**
|
|
45
48
|
* 可选兜底文本。
|
|
46
49
|
*
|
|
@@ -65,7 +68,7 @@ export function buildAssistantResultPersistencePayload(
|
|
|
65
68
|
/**
|
|
66
69
|
* 已构造好的完整 assistant message。
|
|
67
70
|
*/
|
|
68
|
-
message?:
|
|
71
|
+
message?: SessionModelMessageV1 | null;
|
|
69
72
|
/**
|
|
70
73
|
* 允许写入的兜底文本。
|
|
71
74
|
*/
|
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
type FileUIPart,
|
|
18
18
|
} from "ai";
|
|
19
19
|
import type { SessionUserMessagePart } from "@/types/sdk/AgentSessionPrompt.js";
|
|
20
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
SessionMessageV1,
|
|
22
|
+
SessionModelMessageV1,
|
|
23
|
+
} from "@/executor/types/SessionMessages.js";
|
|
21
24
|
import { parseChatMessageMarkup } from "@/executor/messages/ChatMessageMarkup.js";
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -171,12 +174,12 @@ export async function hydrateUserPromptFileParts(
|
|
|
171
174
|
* - 新历史保留 Agent 根目录相对路径,旧历史的 `file://` 仍继续兼容。
|
|
172
175
|
*/
|
|
173
176
|
export async function hydrateFileUrlPartsForModel(
|
|
174
|
-
messages:
|
|
177
|
+
messages: SessionModelMessageV1[],
|
|
175
178
|
projectRoot?: string,
|
|
176
|
-
): Promise<
|
|
179
|
+
): Promise<SessionModelMessageV1[]> {
|
|
177
180
|
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
178
181
|
|
|
179
|
-
const out:
|
|
182
|
+
const out: SessionModelMessageV1[] = [];
|
|
180
183
|
for (const message of messages) {
|
|
181
184
|
const parts = Array.isArray(message?.parts) ? message.parts : [];
|
|
182
185
|
if (!parts.some((part) => isFileUIPart(part as FileUIPart))) {
|
|
@@ -184,7 +187,7 @@ export async function hydrateFileUrlPartsForModel(
|
|
|
184
187
|
continue;
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
const nextParts:
|
|
190
|
+
const nextParts: SessionModelMessageV1["parts"] = [];
|
|
188
191
|
let changed = false;
|
|
189
192
|
for (const part of parts) {
|
|
190
193
|
if (!isFileUIPart(part as FileUIPart)) {
|
|
@@ -193,7 +196,7 @@ export async function hydrateFileUrlPartsForModel(
|
|
|
193
196
|
}
|
|
194
197
|
const nextPart = await hydrateFileUrlPart(part as FileUIPart, projectRoot);
|
|
195
198
|
if (nextPart !== part) changed = true;
|
|
196
|
-
nextParts.push(nextPart as
|
|
199
|
+
nextParts.push(nextPart as SessionModelMessageV1["parts"][number]);
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
out.push(changed ? { ...message, parts: nextParts } : message);
|
|
@@ -206,13 +209,13 @@ export async function hydrateFileUrlPartsForModel(
|
|
|
206
209
|
* 在 user 消息上注入 FileUIPart,以便多模态模型直接消费本地附件。
|
|
207
210
|
*/
|
|
208
211
|
export async function injectFilePartsFromAttachments(
|
|
209
|
-
messages:
|
|
212
|
+
messages: SessionModelMessageV1[],
|
|
210
213
|
projectRoot?: string,
|
|
211
|
-
): Promise<
|
|
214
|
+
): Promise<SessionModelMessageV1[]> {
|
|
212
215
|
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
213
216
|
|
|
214
217
|
const root = path.resolve(String(projectRoot || "").trim() || process.cwd());
|
|
215
|
-
const out:
|
|
218
|
+
const out: SessionModelMessageV1[] = [];
|
|
216
219
|
|
|
217
220
|
for (const message of messages) {
|
|
218
221
|
if (!message || typeof message !== "object" || message.role !== "user") {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type ToolSet,
|
|
16
16
|
} from "ai";
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
isSessionModelMessage,
|
|
19
19
|
type SessionMessageV1,
|
|
20
20
|
} from "@/executor/types/SessionMessages.js";
|
|
21
21
|
import {
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
*
|
|
29
29
|
* 关键点(中文)
|
|
30
30
|
* - 用途:从 onStepCallback 返回的消息里挑出可并入推理上下文的 user 文本。
|
|
31
|
-
* - 输入:任意 SessionMessageV1[](可能混有 assistant/tool/空消息)。
|
|
31
|
+
* - 输入:任意 SessionMessageV1[](可能混有 assistant/tool/action/空消息)。
|
|
32
32
|
* - 输出:只包含“非空 user 文本”的消息数组。
|
|
33
33
|
*/
|
|
34
34
|
export function pickMergedUserMessages(
|
|
@@ -40,7 +40,7 @@ export function pickMergedUserMessages(
|
|
|
40
40
|
// 逐条过滤消息。
|
|
41
41
|
return messages.filter((message) => {
|
|
42
42
|
// 防御 1:消息必须是对象。
|
|
43
|
-
if (!message
|
|
43
|
+
if (!isSessionModelMessage(message)) return false;
|
|
44
44
|
|
|
45
45
|
// 防御 2:只接受 user 角色。
|
|
46
46
|
if (message.role !== "user") return false;
|
|
@@ -80,10 +80,8 @@ export async function toModelMessages(
|
|
|
80
80
|
// 空输入快速返回,避免调用转换器的额外开销。
|
|
81
81
|
if (!Array.isArray(messages) || messages.length === 0) return [];
|
|
82
82
|
|
|
83
|
-
//
|
|
84
|
-
const model_messages = messages.filter(
|
|
85
|
-
(message) => !isSessionOperationMessage(message),
|
|
86
|
-
);
|
|
83
|
+
// action message 只服务前端时间线,不能传入模型。
|
|
84
|
+
const model_messages = messages.filter(isSessionModelMessage);
|
|
87
85
|
if (model_messages.length === 0) return [];
|
|
88
86
|
|
|
89
87
|
// 第一步(中文):在 user 消息上注入 file parts(多模态附件)。
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
|
|
10
10
|
import { isTextUIPart } from "ai";
|
|
11
11
|
import type { Logger } from "@/utils/logger/Logger.js";
|
|
12
|
-
import type {
|
|
12
|
+
import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* 从 UI message 中提取 assistant 文本部分。
|
|
16
16
|
*/
|
|
17
|
-
export function extractAssistantTextForLog(
|
|
17
|
+
export function extractAssistantTextForLog(
|
|
18
|
+
message: SessionModelMessageV1,
|
|
19
|
+
): string {
|
|
18
20
|
if (!Array.isArray(message.parts)) return "";
|
|
19
21
|
return message.parts
|
|
20
22
|
.filter(isTextUIPart)
|
|
@@ -28,7 +30,7 @@ export function extractAssistantTextForLog(message: SessionMessageV1): string {
|
|
|
28
30
|
*/
|
|
29
31
|
export async function logAssistantMessageNow(
|
|
30
32
|
logger: Logger,
|
|
31
|
-
message:
|
|
33
|
+
message: SessionModelMessageV1,
|
|
32
34
|
): Promise<void> {
|
|
33
35
|
const text = extractAssistantTextForLog(message) || "-";
|
|
34
36
|
const normalized = text.replace(/\r\n/g, "\n");
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - 每个 step 前插入 `step-start`,便于在 UI 中保留多 step 边界。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
11
11
|
|
|
12
12
|
type UnknownRecord = Record<string, unknown>;
|
|
13
13
|
|
|
@@ -112,8 +112,8 @@ export function buildSessionStepParts(params: {
|
|
|
112
112
|
stepResult?: unknown;
|
|
113
113
|
text: string;
|
|
114
114
|
visibility?: "visible" | "internal";
|
|
115
|
-
}):
|
|
116
|
-
const out:
|
|
115
|
+
}): SessionModelMessageV1["parts"] {
|
|
116
|
+
const out: SessionModelMessageV1["parts"] = [];
|
|
117
117
|
const stepRecord = toRecord(params.stepResult) || {};
|
|
118
118
|
const toolCalls = Array.isArray(stepRecord.toolCalls) ? stepRecord.toolCalls : [];
|
|
119
119
|
const toolResults = Array.isArray(stepRecord.toolResults)
|
|
@@ -171,7 +171,7 @@ export function buildSessionStepParts(params: {
|
|
|
171
171
|
state: "output-available",
|
|
172
172
|
output,
|
|
173
173
|
}),
|
|
174
|
-
} as
|
|
174
|
+
} as SessionModelMessageV1["parts"][number],
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -183,7 +183,7 @@ export function buildSessionStepParts(params: {
|
|
|
183
183
|
{
|
|
184
184
|
type: toDataPartType(type),
|
|
185
185
|
data: resolveAcpEventData(acpEvent),
|
|
186
|
-
} as
|
|
186
|
+
} as SessionModelMessageV1["parts"][number],
|
|
187
187
|
);
|
|
188
188
|
}
|
|
189
189
|
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
* - 因此需要一个稳定的、与 Agent 解耦的提取逻辑(属于 chat/egress 语义)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
isSessionModelMessage,
|
|
12
|
+
type SessionMessageV1,
|
|
13
|
+
type SessionModelMessageV1,
|
|
14
|
+
} from "@/executor/types/SessionMessages.js";
|
|
11
15
|
import {
|
|
12
16
|
extractTextFromUiMessage,
|
|
13
17
|
extractToolCallsFromUiMessage,
|
|
@@ -22,8 +26,9 @@ import {
|
|
|
22
26
|
*/
|
|
23
27
|
export function resolveAssistantMessageForPersistence(
|
|
24
28
|
message: SessionMessageV1 | null | undefined,
|
|
25
|
-
):
|
|
29
|
+
): SessionModelMessageV1 | null {
|
|
26
30
|
if (!message || typeof message !== "object") return null;
|
|
31
|
+
if (!isSessionModelMessage(message)) return null;
|
|
27
32
|
return message;
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -37,6 +42,7 @@ export function resolveAssistantMessageForPersistence(
|
|
|
37
42
|
export function pickLastSuccessfulChatSendText(
|
|
38
43
|
message: SessionMessageV1 | null | undefined,
|
|
39
44
|
): string {
|
|
45
|
+
if (!isSessionModelMessage(message)) return "";
|
|
40
46
|
const toolCalls = extractToolCallsFromUiMessage(message);
|
|
41
47
|
// 关键点(中文):优先从 chat_send 的 input.text 还原"用户可见回复"。
|
|
42
48
|
for (let i = toolCalls.length - 1; i >= 0; i -= 1) {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { generateId } from "@/utils/Id.js";
|
|
11
11
|
import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
|
|
12
|
-
import type {
|
|
12
|
+
import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
13
|
+
import { isSessionModelMessage } from "@/executor/types/SessionMessages.js";
|
|
13
14
|
|
|
14
15
|
interface ExecutorInflightServiceOptions {
|
|
15
16
|
/**
|
|
@@ -51,7 +52,7 @@ export class ExecutorInflightService {
|
|
|
51
52
|
* 把 step/tool 过程增量写入当前运行中的 assistant 快照。
|
|
52
53
|
*/
|
|
53
54
|
async append_assistant_step_parts(
|
|
54
|
-
parts:
|
|
55
|
+
parts: SessionModelMessageV1["parts"],
|
|
55
56
|
): Promise<void> {
|
|
56
57
|
const normalized_parts = Array.isArray(parts)
|
|
57
58
|
? parts.filter((part) => part && typeof part === "object")
|
|
@@ -59,11 +60,14 @@ export class ExecutorInflightService {
|
|
|
59
60
|
if (normalized_parts.length === 0) return;
|
|
60
61
|
|
|
61
62
|
const current_inflight = await this.history_store.readInflight();
|
|
62
|
-
const
|
|
63
|
+
const normalized_inflight = isSessionModelMessage(current_inflight)
|
|
64
|
+
? current_inflight
|
|
65
|
+
: null;
|
|
66
|
+
const next_message: SessionModelMessageV1 = normalized_inflight
|
|
63
67
|
? {
|
|
64
|
-
...
|
|
68
|
+
...normalized_inflight,
|
|
65
69
|
metadata: {
|
|
66
|
-
...(
|
|
70
|
+
...(normalized_inflight.metadata || {
|
|
67
71
|
v: 1 as const,
|
|
68
72
|
ts: Date.now(),
|
|
69
73
|
sessionId: this.session_id,
|
|
@@ -74,8 +78,8 @@ export class ExecutorInflightService {
|
|
|
74
78
|
kind: "normal",
|
|
75
79
|
},
|
|
76
80
|
parts: [
|
|
77
|
-
...(Array.isArray(
|
|
78
|
-
?
|
|
81
|
+
...(Array.isArray(normalized_inflight.parts)
|
|
82
|
+
? normalized_inflight.parts
|
|
79
83
|
: []),
|
|
80
84
|
...normalized_parts,
|
|
81
85
|
],
|
|
@@ -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,15 +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") {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
222
233
|
if (!Array.isArray(candidate.parts)) return null;
|
|
223
|
-
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
|
+
};
|
|
224
271
|
}
|
|
225
272
|
|
|
226
273
|
private hasStructuredAssistantParts(message: SessionMessageV1 | null): boolean {
|
|
227
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
!isSessionModelMessage(message) ||
|
|
276
|
+
message.role !== "assistant" ||
|
|
277
|
+
!Array.isArray(message.parts)
|
|
278
|
+
) {
|
|
228
279
|
return false;
|
|
229
280
|
}
|
|
230
281
|
return message.parts.some((part) => {
|
|
@@ -238,6 +289,8 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
238
289
|
inflight: SessionMessageV1,
|
|
239
290
|
finalMessage: SessionMessageV1,
|
|
240
291
|
): SessionMessageV1 {
|
|
292
|
+
if (!isSessionModelMessage(finalMessage)) return inflight;
|
|
293
|
+
if (!isSessionModelMessage(inflight)) return finalMessage;
|
|
241
294
|
const final_parts = Array.isArray(finalMessage.parts)
|
|
242
295
|
? finalMessage.parts.filter((part) => part && typeof part === "object")
|
|
243
296
|
: [];
|
|
@@ -355,7 +408,10 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
355
408
|
|
|
356
409
|
private async writeInflightUnsafe(message: SessionMessageV1): Promise<void> {
|
|
357
410
|
const normalized = this.normalizePersistedMessage(message);
|
|
358
|
-
if (
|
|
411
|
+
if (
|
|
412
|
+
!isSessionModelMessage(normalized) ||
|
|
413
|
+
normalized.role !== "assistant"
|
|
414
|
+
) {
|
|
359
415
|
throw new Error("inflight assistant must be an assistant UIMessage");
|
|
360
416
|
}
|
|
361
417
|
const file = this.getInflightFilePath();
|
|
@@ -377,6 +433,44 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
377
433
|
);
|
|
378
434
|
}
|
|
379
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
|
+
|
|
380
474
|
private async withWriteLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
381
475
|
await this.ensureLayout();
|
|
382
476
|
const lockPath = this.getLockFilePath();
|
|
@@ -436,44 +530,6 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
436
530
|
return system.filter((item) => item && typeof item === "object");
|
|
437
531
|
}
|
|
438
532
|
|
|
439
|
-
private normalizeOperation(
|
|
440
|
-
operation: AgentSessionOperationRecord,
|
|
441
|
-
): AgentSessionOperationRecord {
|
|
442
|
-
const operation_id = String(operation.operationId || "").trim() || generateId();
|
|
443
|
-
const name = String(operation.name || "").trim() || "operation";
|
|
444
|
-
const status = String(operation.status || "").trim();
|
|
445
|
-
const normalized_status: AgentSessionOperationRecord["status"] =
|
|
446
|
-
status === "started" ||
|
|
447
|
-
status === "progress" ||
|
|
448
|
-
status === "finished" ||
|
|
449
|
-
status === "skipped" ||
|
|
450
|
-
status === "failed"
|
|
451
|
-
? status
|
|
452
|
-
: "progress";
|
|
453
|
-
return {
|
|
454
|
-
operationId: operation_id,
|
|
455
|
-
name,
|
|
456
|
-
status: normalized_status,
|
|
457
|
-
...(typeof operation.turnId === "string" && operation.turnId.trim()
|
|
458
|
-
? { turnId: operation.turnId.trim() }
|
|
459
|
-
: {}),
|
|
460
|
-
...(typeof operation.label === "string" && operation.label.trim()
|
|
461
|
-
? { label: operation.label.trim() }
|
|
462
|
-
: {}),
|
|
463
|
-
...(typeof operation.reason === "string" && operation.reason.trim()
|
|
464
|
-
? { reason: operation.reason.trim() }
|
|
465
|
-
: {}),
|
|
466
|
-
...(typeof operation.progress === "number" && Number.isFinite(operation.progress)
|
|
467
|
-
? { progress: Math.max(0, Math.min(1, operation.progress)) }
|
|
468
|
-
: {}),
|
|
469
|
-
...(operation.result !== undefined ? { result: operation.result } : {}),
|
|
470
|
-
...(typeof operation.error === "string" && operation.error.trim()
|
|
471
|
-
? { error: operation.error.trim() }
|
|
472
|
-
: {}),
|
|
473
|
-
...(operation.visible === false ? { visible: false } : {}),
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
533
|
async compact(input: SessionHistoryCompactInput): Promise<{
|
|
478
534
|
compacted: boolean;
|
|
479
535
|
reason?: string;
|
|
@@ -510,7 +566,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
510
566
|
maxInputTokensApprox: input.maxInputTokensApprox,
|
|
511
567
|
archiveOnCompact: input.archiveOnCompact,
|
|
512
568
|
compactRatio: input.compactRatio,
|
|
513
|
-
...(input.
|
|
569
|
+
...(input.onAction ? { onAction: input.onAction } : {}),
|
|
514
570
|
},
|
|
515
571
|
);
|
|
516
572
|
}
|
|
@@ -519,10 +575,14 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
519
575
|
await this.withWriteLock(async () => {
|
|
520
576
|
const normalized = this.normalizePersistedMessage(message);
|
|
521
577
|
if (!normalized) return;
|
|
578
|
+
if (isSessionActionMessage(normalized)) {
|
|
579
|
+
await this.upsertActionMessageUnsafe(normalized);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
522
582
|
|
|
523
583
|
// 关键点(中文):若上一次 assistant 在运行中中断,新的 user 到来前先把残留快照收口到正式历史,
|
|
524
584
|
// 避免 `list()` 时旧 inflight 跑到新 user 后面,打乱时序。
|
|
525
|
-
if (normalized.role === "user") {
|
|
585
|
+
if (isSessionModelMessage(normalized) && normalized.role === "user") {
|
|
526
586
|
const current_inflight = await this.readInflightUnsafe();
|
|
527
587
|
if (current_inflight) {
|
|
528
588
|
await this.appendMessageUnsafe(current_inflight);
|
|
@@ -674,29 +734,26 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
|
|
|
674
734
|
};
|
|
675
735
|
}
|
|
676
736
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
metadata:
|
|
737
|
+
action(input: {
|
|
738
|
+
action: AgentSessionActionRecord;
|
|
739
|
+
metadata: Pick<SessionMetadataV1, "sessionId"> &
|
|
680
740
|
Partial<Pick<SessionMetadataV1, "ts">>;
|
|
681
741
|
id?: string;
|
|
682
|
-
}):
|
|
683
|
-
const operation = this.normalizeOperation(input.operation);
|
|
684
|
-
const label = String(
|
|
685
|
-
operation.label || `${operation.name}: ${operation.status}`,
|
|
686
|
-
).trim();
|
|
742
|
+
}): SessionActionMessageV1 {
|
|
687
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
|
+
);
|
|
688
751
|
return {
|
|
689
|
-
|
|
690
|
-
role: "assistant",
|
|
752
|
+
...message,
|
|
691
753
|
metadata: {
|
|
692
|
-
|
|
693
|
-
ts: typeof ts === "number" ? ts :
|
|
694
|
-
...metadata,
|
|
695
|
-
source: "operation",
|
|
696
|
-
kind: "operation",
|
|
697
|
-
operation,
|
|
754
|
+
...message.metadata,
|
|
755
|
+
ts: typeof ts === "number" ? ts : message.metadata.ts,
|
|
698
756
|
},
|
|
699
|
-
parts: [{ type: "text", text: label }],
|
|
700
757
|
};
|
|
701
758
|
}
|
|
702
759
|
}
|