@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
|
@@ -3,23 +3,27 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - 这里统一描述 session 消息落盘格式与元信息结构。
|
|
6
|
-
* - session
|
|
6
|
+
* - session 的唯一事实源是消息 JSONL,模型消息继续使用 AI SDK `UIMessage`。
|
|
7
7
|
* - 这些类型会被 history Store、history Composer、compact、control UI、task runtime 共同复用。
|
|
8
8
|
*/
|
|
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` 摘要消息
|
|
24
|
+
* - action 只用于 UI timeline,不进入 LLM 输入
|
|
21
25
|
*/
|
|
22
|
-
export type SessionMessageKind = "normal" | "summary"
|
|
26
|
+
export type SessionMessageKind = "normal" | "summary";
|
|
23
27
|
|
|
24
28
|
/**
|
|
25
29
|
* Session 消息来源类型。
|
|
@@ -32,8 +36,7 @@ export type SessionMessageKind = "normal" | "summary" | "operation";
|
|
|
32
36
|
export type SessionMessageSource =
|
|
33
37
|
| "ingress"
|
|
34
38
|
| "egress"
|
|
35
|
-
| "compact"
|
|
36
|
-
| "operation";
|
|
39
|
+
| "compact";
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
42
|
* 入站消息细分类型。
|
|
@@ -73,14 +76,6 @@ export type SessionMetadataV1 = {
|
|
|
73
76
|
archiveId?: string;
|
|
74
77
|
/** compact 摘要所覆盖的原始消息范围。 */
|
|
75
78
|
sourceRange?: SessionMessageSourceRangeV1;
|
|
76
|
-
/**
|
|
77
|
-
* operation message 对应的结构化操作记录。
|
|
78
|
-
*
|
|
79
|
-
* 说明(中文)
|
|
80
|
-
* - 仅当 `kind=operation` 或 `source=operation` 时存在。
|
|
81
|
-
* - 该字段供前端恢复时间线状态,不会进入 LLM 输入。
|
|
82
|
-
*/
|
|
83
|
-
operation?: AgentSessionOperationRecord;
|
|
84
79
|
/**
|
|
85
80
|
* 扩展元信息。
|
|
86
81
|
*
|
|
@@ -92,25 +87,100 @@ export type SessionMetadataV1 = {
|
|
|
92
87
|
};
|
|
93
88
|
|
|
94
89
|
/**
|
|
95
|
-
* Session UI 消息结构。
|
|
90
|
+
* 模型可消费的 Session UI 消息结构。
|
|
91
|
+
*/
|
|
92
|
+
export type SessionModelMessageV1 = UIMessage<SessionMetadataV1>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
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 消息结构。
|
|
110
|
+
*
|
|
111
|
+
* 关键点(中文)
|
|
112
|
+
* - `action` 不是 AI SDK 原生模型消息。
|
|
113
|
+
* - 它只存在于 JSONL history 与前端 timeline,进入 LLM 前必须过滤。
|
|
114
|
+
*/
|
|
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;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Session 持久化消息结构。
|
|
96
132
|
*/
|
|
97
|
-
export type SessionMessageV1 =
|
|
133
|
+
export type SessionMessageV1 = SessionModelMessageV1 | SessionActionMessageV1;
|
|
98
134
|
|
|
99
135
|
/**
|
|
100
136
|
* user 角色的 Session 消息结构。
|
|
101
137
|
*/
|
|
102
|
-
export type SessionUserMessageV1 =
|
|
138
|
+
export type SessionUserMessageV1 = SessionModelMessageV1 & {
|
|
103
139
|
/** 消息角色固定为 `user`。 */
|
|
104
140
|
role: "user";
|
|
105
141
|
};
|
|
106
142
|
|
|
107
143
|
/**
|
|
108
|
-
* 判断一条消息是否为
|
|
144
|
+
* 判断一条消息是否为 action message。
|
|
145
|
+
*/
|
|
146
|
+
export function isSessionActionMessage(
|
|
147
|
+
message: SessionMessageV1 | null | undefined,
|
|
148
|
+
): message is SessionActionMessageV1 {
|
|
149
|
+
if (!message || typeof message !== "object") return false;
|
|
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
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 判断一条消息是否为模型可消费的 UIMessage。
|
|
109
180
|
*/
|
|
110
|
-
export function
|
|
181
|
+
export function isSessionModelMessage(
|
|
111
182
|
message: SessionMessageV1 | null | undefined,
|
|
112
|
-
):
|
|
183
|
+
): message is SessionModelMessageV1 {
|
|
113
184
|
if (!message || typeof message !== "object") return false;
|
|
114
|
-
|
|
115
|
-
return metadata?.kind === "operation" || metadata?.source === "operation";
|
|
185
|
+
return !isSessionActionMessage(message);
|
|
116
186
|
}
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Tool, UIMessageChunk } from "ai";
|
|
11
|
-
import type {
|
|
12
|
-
|
|
11
|
+
import type {
|
|
12
|
+
SessionMessageV1,
|
|
13
|
+
SessionModelMessageV1,
|
|
14
|
+
SessionUserMessageV1,
|
|
15
|
+
} from "@/executor/types/SessionMessages.js";
|
|
13
16
|
import type { SessionSystemMessage } from "@/executor/types/SessionPrompts.js";
|
|
14
17
|
import type { SessionRunContext } from "@/types/executor/SessionRunContext.js";
|
|
15
18
|
|
|
@@ -99,7 +102,7 @@ export interface SessionRunResult {
|
|
|
99
102
|
* - stop/abort 且没有任何 assistant 内容时可以为空。
|
|
100
103
|
* - turn 状态通过 `success` / `error` 表达,不应伪造成 assistant 正文。
|
|
101
104
|
*/
|
|
102
|
-
assistantMessage?:
|
|
105
|
+
assistantMessage?: SessionModelMessageV1 | null;
|
|
103
106
|
|
|
104
107
|
/**
|
|
105
108
|
* 本轮执行结束后待写入长期历史的 user 消息。
|
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,20 +276,11 @@ function toTimelineEvent(params: {
|
|
|
259
276
|
export function toSessionTimelineEvents(
|
|
260
277
|
message: SessionMessageV1,
|
|
261
278
|
): AgentSessionTimelineEvent[] {
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
return [
|
|
265
|
-
toTimelineEvent({
|
|
266
|
-
message,
|
|
267
|
-
role: "operation",
|
|
268
|
-
text:
|
|
269
|
-
String(metadata?.operation?.label || "").trim() ||
|
|
270
|
-
resolveSessionMessagePreview(message),
|
|
271
|
-
sequence: 0,
|
|
272
|
-
}),
|
|
273
|
-
];
|
|
279
|
+
if (isSessionActionMessage(message)) {
|
|
280
|
+
return [toActionTimelineEvent(message)];
|
|
274
281
|
}
|
|
275
282
|
|
|
283
|
+
if (!isSessionModelMessage(message)) return [];
|
|
276
284
|
if (message.role !== "assistant") {
|
|
277
285
|
return [
|
|
278
286
|
toTimelineEvent({
|
|
@@ -367,7 +375,14 @@ export async function loadSessionMessagesFromPath(
|
|
|
367
375
|
try {
|
|
368
376
|
const parsed = JSON.parse(line) as SessionMessageV1;
|
|
369
377
|
if (!parsed || typeof parsed !== "object") continue;
|
|
370
|
-
|
|
378
|
+
const candidate = parsed as { type?: unknown; role?: unknown };
|
|
379
|
+
if (
|
|
380
|
+
candidate.type !== "action" &&
|
|
381
|
+
candidate.role !== "user" &&
|
|
382
|
+
candidate.role !== "assistant"
|
|
383
|
+
) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
371
386
|
messages.push(parsed);
|
|
372
387
|
} catch {
|
|
373
388
|
// 关键点(中文):单行损坏不影响整个 session 的可读性。
|
|
@@ -380,8 +395,7 @@ export async function loadSessionMessagesFromPath(
|
|
|
380
395
|
try {
|
|
381
396
|
const parsed = (await fs.readJson(inflight_path)) as SessionMessageV1;
|
|
382
397
|
if (
|
|
383
|
-
parsed &&
|
|
384
|
-
typeof parsed === "object" &&
|
|
398
|
+
isSessionModelMessage(parsed) &&
|
|
385
399
|
parsed.role === "assistant" &&
|
|
386
400
|
Array.isArray(parsed.parts)
|
|
387
401
|
) {
|
|
@@ -410,10 +424,14 @@ export async function loadSessionArchiveMessagesFromPath(
|
|
|
410
424
|
const items = Array.isArray(raw?.messages) ? raw.messages : [];
|
|
411
425
|
return items.filter((item): item is SessionMessageV1 => {
|
|
412
426
|
if (!item || typeof item !== "object") return false;
|
|
413
|
-
const candidate = item as
|
|
427
|
+
const candidate = item as { type?: unknown; role?: unknown; parts?: unknown };
|
|
414
428
|
return (
|
|
415
|
-
(
|
|
416
|
-
|
|
429
|
+
(
|
|
430
|
+
candidate.type === "action" ||
|
|
431
|
+
candidate.role === "user" ||
|
|
432
|
+
candidate.role === "assistant"
|
|
433
|
+
) &&
|
|
434
|
+
(candidate.type === "action" || Array.isArray(candidate.parts))
|
|
417
435
|
);
|
|
418
436
|
});
|
|
419
437
|
} catch {
|
|
@@ -422,6 +440,7 @@ export async function loadSessionArchiveMessagesFromPath(
|
|
|
422
440
|
}
|
|
423
441
|
|
|
424
442
|
function isCompactSummaryMessage(message: SessionMessageV1): boolean {
|
|
443
|
+
if (!isSessionModelMessage(message)) return false;
|
|
425
444
|
const metadata = (message.metadata || null) as SessionMetadataV1 | null;
|
|
426
445
|
return metadata?.source === "compact" || metadata?.kind === "summary";
|
|
427
446
|
}
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import { nanoid } from "nanoid";
|
|
11
11
|
import type { SessionUserMessageV1 } from "@/executor/types/SessionMessages.js";
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
SessionMessageV1,
|
|
14
|
+
SessionModelMessageV1,
|
|
15
|
+
} from "@/executor/types/SessionMessages.js";
|
|
13
16
|
import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
|
|
14
17
|
import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
|
|
15
18
|
import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
|
|
@@ -106,7 +109,7 @@ export interface SessionPromptRuntimeOptions {
|
|
|
106
109
|
}) => Promise<{
|
|
107
110
|
text: string;
|
|
108
111
|
success: boolean;
|
|
109
|
-
assistantMessage?:
|
|
112
|
+
assistantMessage?: SessionModelMessageV1 | null;
|
|
110
113
|
error?: string;
|
|
111
114
|
}>;
|
|
112
115
|
|
|
@@ -346,7 +349,7 @@ export class SessionPromptRuntime {
|
|
|
346
349
|
function buildPromptRuntimeErrorAssistantMessage(input: {
|
|
347
350
|
sessionId: string;
|
|
348
351
|
message: string;
|
|
349
|
-
}):
|
|
352
|
+
}): SessionModelMessageV1 {
|
|
350
353
|
return {
|
|
351
354
|
id: `a:${input.sessionId}:${Date.now()}:${nanoid(6)}`,
|
|
352
355
|
role: "assistant",
|
|
@@ -31,12 +31,13 @@ import type {
|
|
|
31
31
|
AgentSessionEvent,
|
|
32
32
|
} from "@/types/sdk/AgentSessionEvent.js";
|
|
33
33
|
import type {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} from "@/types/sdk/
|
|
34
|
+
AgentSessionActionEvent,
|
|
35
|
+
AgentSessionActionRecord,
|
|
36
|
+
} from "@/types/sdk/AgentSessionAction.js";
|
|
37
37
|
import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
|
|
38
38
|
import type {
|
|
39
39
|
SessionMessageV1,
|
|
40
|
+
SessionModelMessageV1,
|
|
40
41
|
SessionUserMessageV1,
|
|
41
42
|
} from "@/executor/types/SessionMessages.js";
|
|
42
43
|
import type { SessionLocalState } from "@/types/session/SessionLocalState.js";
|
|
@@ -86,17 +87,17 @@ type SessionStateServiceOptions = {
|
|
|
86
87
|
|
|
87
88
|
type SessionSetOptions = {
|
|
88
89
|
/**
|
|
89
|
-
* 是否写入并发布 model-switching
|
|
90
|
+
* 是否写入并发布 model-switching action。
|
|
90
91
|
*
|
|
91
92
|
* 说明(中文)
|
|
92
93
|
* - 公开 `session.set()` 应保持默认开启。
|
|
93
94
|
* - fork 内部复制模型配置时关闭,避免污染 forked session 历史。
|
|
94
95
|
*/
|
|
95
|
-
|
|
96
|
+
emit_action?: boolean;
|
|
96
97
|
};
|
|
97
98
|
|
|
98
|
-
type
|
|
99
|
-
Partial<Pick<
|
|
99
|
+
type EmitActionInput = Omit<AgentSessionActionRecord, "id"> &
|
|
100
|
+
Partial<Pick<AgentSessionActionRecord, "id">>;
|
|
100
101
|
|
|
101
102
|
/**
|
|
102
103
|
* 本地 Session 状态与持久化服务。
|
|
@@ -222,23 +223,21 @@ export class SessionStateService {
|
|
|
222
223
|
* 写入当前 session 配置。
|
|
223
224
|
*/
|
|
224
225
|
async set(input: AgentSessionSetInput, options?: SessionSetOptions): Promise<void> {
|
|
225
|
-
const
|
|
226
|
+
const should_emit_action = options?.emit_action !== false;
|
|
226
227
|
const previous_model_label = this.state.sessionConfig.modelLabel;
|
|
227
228
|
const next_model_label = input.model
|
|
228
229
|
? inferAgentModelLabel(input.model)
|
|
229
230
|
: undefined;
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
if (input.model &&
|
|
233
|
-
await this.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
...(next_model_label ? { modelLabel: next_model_label } : {}),
|
|
241
|
-
},
|
|
231
|
+
const action_id = `model-switching:${this.session_id}:${Date.now()}:${generateId()}`;
|
|
232
|
+
|
|
233
|
+
if (input.model && should_emit_action) {
|
|
234
|
+
await this.emit_action_event({
|
|
235
|
+
id: action_id,
|
|
236
|
+
title: "Switching session model",
|
|
237
|
+
description: next_model_label
|
|
238
|
+
? `Switching to ${next_model_label}.`
|
|
239
|
+
: undefined,
|
|
240
|
+
state: "running",
|
|
242
241
|
});
|
|
243
242
|
}
|
|
244
243
|
|
|
@@ -255,30 +254,25 @@ export class SessionStateService {
|
|
|
255
254
|
model: this.state.sessionConfig.model,
|
|
256
255
|
});
|
|
257
256
|
} catch (error) {
|
|
258
|
-
if (input.model &&
|
|
259
|
-
await this.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
error: error instanceof Error ? error.message : String(error),
|
|
257
|
+
if (input.model && should_emit_action) {
|
|
258
|
+
await this.emit_action_event({
|
|
259
|
+
id: action_id,
|
|
260
|
+
title: "Session model switch failed",
|
|
261
|
+
description: error instanceof Error ? error.message : String(error),
|
|
262
|
+
state: "failed",
|
|
265
263
|
});
|
|
266
264
|
}
|
|
267
265
|
throw error;
|
|
268
266
|
}
|
|
269
267
|
|
|
270
|
-
if (input.model &&
|
|
271
|
-
await this.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
...(this.state.sessionConfig.modelLabel
|
|
279
|
-
? { modelLabel: this.state.sessionConfig.modelLabel }
|
|
280
|
-
: {}),
|
|
281
|
-
},
|
|
268
|
+
if (input.model && should_emit_action) {
|
|
269
|
+
await this.emit_action_event({
|
|
270
|
+
id: action_id,
|
|
271
|
+
title: "Session model switched",
|
|
272
|
+
description: this.state.sessionConfig.modelLabel
|
|
273
|
+
? `Using ${this.state.sessionConfig.modelLabel}.`
|
|
274
|
+
: undefined,
|
|
275
|
+
state: "completed",
|
|
282
276
|
});
|
|
283
277
|
}
|
|
284
278
|
}
|
|
@@ -355,7 +349,7 @@ export class SessionStateService {
|
|
|
355
349
|
* 持久化最终 assistant 结果。
|
|
356
350
|
*/
|
|
357
351
|
async persist_assistant_result(
|
|
358
|
-
assistant_message?:
|
|
352
|
+
assistant_message?: SessionModelMessageV1 | null,
|
|
359
353
|
): Promise<void> {
|
|
360
354
|
await persistSdkAssistantResult({
|
|
361
355
|
projectRoot: this.project_root,
|
|
@@ -368,23 +362,18 @@ export class SessionStateService {
|
|
|
368
362
|
}
|
|
369
363
|
|
|
370
364
|
/**
|
|
371
|
-
* 持久化一条
|
|
365
|
+
* 持久化一条 action message。
|
|
372
366
|
*/
|
|
373
|
-
async
|
|
374
|
-
event:
|
|
367
|
+
async persist_action_event(
|
|
368
|
+
event: AgentSessionActionEvent,
|
|
375
369
|
): Promise<void> {
|
|
376
|
-
const message = this.history_store.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
370
|
+
const message = this.history_store.action({
|
|
371
|
+
action: {
|
|
372
|
+
id: event.id,
|
|
373
|
+
title: event.title,
|
|
374
|
+
state: event.state,
|
|
375
|
+
...(event.description ? { description: event.description } : {}),
|
|
381
376
|
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
382
|
-
...(event.label ? { label: event.label } : {}),
|
|
383
|
-
...(event.reason ? { reason: event.reason } : {}),
|
|
384
|
-
...(typeof event.progress === "number" ? { progress: event.progress } : {}),
|
|
385
|
-
...(event.result !== undefined ? { result: event.result } : {}),
|
|
386
|
-
...(event.error ? { error: event.error } : {}),
|
|
387
|
-
...(event.visible === false ? { visible: false } : {}),
|
|
388
377
|
},
|
|
389
378
|
metadata: {
|
|
390
379
|
sessionId: this.session_id,
|
|
@@ -395,22 +384,24 @@ export class SessionStateService {
|
|
|
395
384
|
}
|
|
396
385
|
|
|
397
386
|
/**
|
|
398
|
-
* 写入并发布一条
|
|
387
|
+
* 写入并发布一条 action。
|
|
399
388
|
*/
|
|
400
|
-
async
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
389
|
+
async emit_action_event(input: EmitActionInput): Promise<void> {
|
|
390
|
+
if (input.visible === false) return;
|
|
391
|
+
const action_id =
|
|
392
|
+
String(input.id || "").trim() ||
|
|
393
|
+
`action:${this.session_id}:${Date.now()}:${generateId()}`;
|
|
394
|
+
const { visible: _visible, ...public_input } = input;
|
|
395
|
+
const event: AgentSessionActionEvent = {
|
|
396
|
+
type: "action",
|
|
406
397
|
sessionId: this.session_id,
|
|
407
|
-
...
|
|
408
|
-
|
|
398
|
+
...public_input,
|
|
399
|
+
id: action_id,
|
|
409
400
|
};
|
|
410
401
|
try {
|
|
411
|
-
await this.
|
|
402
|
+
await this.persist_action_event(event);
|
|
412
403
|
} catch {
|
|
413
|
-
//
|
|
404
|
+
// action 持久化失败不应阻断宿主操作。
|
|
414
405
|
}
|
|
415
406
|
this.publish_event(event);
|
|
416
407
|
}
|
|
@@ -20,6 +20,7 @@ import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
|
|
|
20
20
|
import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
|
|
21
21
|
import type {
|
|
22
22
|
SessionMessageV1,
|
|
23
|
+
SessionModelMessageV1,
|
|
23
24
|
SessionUserMessageV1,
|
|
24
25
|
} from "@/executor/types/SessionMessages.js";
|
|
25
26
|
import { mapAgentEventToSessionEvent, mapUiMessageChunkToAgentEvent } from "@/session/SessionEventMapper.js";
|
|
@@ -144,7 +145,7 @@ export class SessionTurnService {
|
|
|
144
145
|
}): Promise<{
|
|
145
146
|
text: string;
|
|
146
147
|
success: boolean;
|
|
147
|
-
assistantMessage?:
|
|
148
|
+
assistantMessage?: SessionModelMessageV1 | null;
|
|
148
149
|
error?: string;
|
|
149
150
|
}> {
|
|
150
151
|
const tool_name_by_call_id = new Map<string, string>();
|
|
@@ -198,11 +199,11 @@ export class SessionTurnService {
|
|
|
198
199
|
this.publish_event(session_event);
|
|
199
200
|
}
|
|
200
201
|
},
|
|
201
|
-
|
|
202
|
+
onActionCallback: async (event) => {
|
|
202
203
|
try {
|
|
203
|
-
await this.state_service.
|
|
204
|
+
await this.state_service.persist_action_event(event);
|
|
204
205
|
} catch {
|
|
205
|
-
//
|
|
206
|
+
// action 持久化失败不应阻断当前 turn。
|
|
206
207
|
}
|
|
207
208
|
this.publish_event(event);
|
|
208
209
|
},
|