@downcity/agent 1.1.232 → 1.1.236
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent/local/Agent.d.ts +1 -1
- package/bin/agent/local/Agent.js +2 -2
- package/bin/agent/local/Agent.js.map +1 -1
- package/bin/agent/local/services/AgentSessions.d.ts +4 -4
- package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
- package/bin/agent/local/services/AgentSessions.js +8 -8
- package/bin/agent/local/services/AgentSessions.js.map +1 -1
- package/bin/agent/remote/RemoteSession.d.ts +2 -0
- package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
- package/bin/agent/remote/RemoteSession.js +4 -0
- package/bin/agent/remote/RemoteSession.js.map +1 -1
- package/bin/agent/remote/RemoteTransport.d.ts +2 -0
- package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +1 -0
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +11 -0
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +1 -0
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +3 -0
- package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
- package/bin/executor/Executor.d.ts +20 -2
- package/bin/executor/Executor.d.ts.map +1 -1
- package/bin/executor/Executor.js +102 -18
- package/bin/executor/Executor.js.map +1 -1
- package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts +6 -0
- package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts +0 -9
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js +4 -38
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts +1 -3
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +7 -59
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
- package/bin/executor/core-engine/CoreEngineContextCompaction.d.ts +38 -0
- package/bin/executor/core-engine/CoreEngineContextCompaction.d.ts.map +1 -0
- package/bin/executor/core-engine/CoreEngineContextCompaction.js +392 -0
- package/bin/executor/core-engine/CoreEngineContextCompaction.js.map +1 -0
- package/bin/executor/core-engine/CoreEngineMessageState.d.ts +15 -0
- package/bin/executor/core-engine/CoreEngineMessageState.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineMessageState.js +20 -0
- package/bin/executor/core-engine/CoreEngineMessageState.js.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.d.ts +9 -5
- package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.js +146 -35
- package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
- package/bin/executor/store/history/SessionHistoryStore.d.ts +4 -10
- package/bin/executor/store/history/SessionHistoryStore.d.ts.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +1 -3
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
- package/bin/executor/types/SessionRun.d.ts +8 -0
- 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 +1 -1
- package/bin/index.js.map +1 -1
- package/bin/plugin/core/PluginLocalExecution.js +3 -3
- package/bin/plugin/core/PluginLocalExecution.js.map +1 -1
- package/bin/plugin/core/PluginRegistry.d.ts +20 -2
- package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
- package/bin/plugin/core/PluginRegistry.js +100 -3
- package/bin/plugin/core/PluginRegistry.js.map +1 -1
- package/bin/rpc/Client.d.ts +4 -0
- package/bin/rpc/Client.d.ts.map +1 -1
- package/bin/rpc/Client.js +11 -0
- package/bin/rpc/Client.js.map +1 -1
- package/bin/session/Session.d.ts +7 -3
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +26 -19
- package/bin/session/Session.js.map +1 -1
- package/bin/session/SessionSystemBuilder.d.ts.map +1 -1
- package/bin/session/SessionSystemBuilder.js +3 -1
- package/bin/session/SessionSystemBuilder.js.map +1 -1
- package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -1
- package/bin/session/recorder/SessionRecorderCompaction.js +102 -60
- package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.d.ts +11 -7
- package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.js +22 -24
- package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
- package/bin/session/services/SessionStateService.d.ts +10 -6
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js +29 -20
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionTurnService.d.ts +13 -3
- package/bin/session/services/SessionTurnService.d.ts.map +1 -1
- package/bin/session/services/SessionTurnService.js +90 -7
- package/bin/session/services/SessionTurnService.js.map +1 -1
- package/bin/types/agent/SessionActor.d.ts +8 -0
- package/bin/types/agent/SessionActor.d.ts.map +1 -1
- package/bin/types/executor/SessionRunContext.d.ts +16 -8
- package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
- package/bin/types/plugin/PluginRuntime.d.ts +36 -4
- package/bin/types/plugin/PluginRuntime.d.ts.map +1 -1
- package/bin/types/plugin/PluginState.d.ts +10 -0
- package/bin/types/plugin/PluginState.d.ts.map +1 -1
- package/bin/types/rpc/RpcProtocol.d.ts +10 -0
- package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.d.ts +2 -2
- package/bin/types/runtime/agent/AgentContext.js +1 -1
- package/bin/types/session/SessionLocalState.d.ts +2 -2
- package/bin/types/session/SessionOptions.d.ts +5 -5
- package/bin/types/session/SessionOptions.d.ts.map +1 -1
- package/bin/types/session/SessionQueueCommand.d.ts +68 -0
- package/bin/types/session/SessionQueueCommand.d.ts.map +1 -0
- package/bin/types/session/SessionQueueCommand.js +10 -0
- package/bin/types/session/SessionQueueCommand.js.map +1 -0
- package/bin/utils/logger/Logger.d.ts +7 -9
- package/bin/utils/logger/Logger.d.ts.map +1 -1
- package/bin/utils/logger/Logger.js +11 -9
- package/bin/utils/logger/Logger.js.map +1 -1
- package/package.json +3 -3
- package/scripts/core-engine-context-compaction.test.mjs +411 -0
- package/scripts/logger-isolation.test.mjs +55 -0
- package/scripts/multi-agent-plugin-isolation.test.mjs +289 -0
- package/scripts/plugin-tool-bridge.test.mjs +52 -0
- package/scripts/remote-session-reconnect.test.mjs +18 -0
- package/scripts/session-compaction-context-window.test.mjs +27 -26
- package/scripts/session-config-turn-boundary.test.mjs +80 -26
- package/scripts/session-prompt-runtime.test.mjs +23 -15
- package/scripts/session-recorder.test.mjs +15 -24
- package/src/agent/local/Agent.ts +2 -2
- package/src/agent/local/services/AgentSessions.ts +10 -10
- package/src/agent/remote/RemoteSession.ts +5 -0
- package/src/agent/remote/RemoteTransport.ts +2 -0
- package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +15 -0
- package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +4 -0
- package/src/executor/Executor.ts +111 -19
- package/src/executor/composer/compaction/SessionCompactionComposer.ts +7 -0
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.ts +4 -57
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +8 -66
- package/src/executor/core-engine/CoreEngineContextCompaction.ts +477 -0
- package/src/executor/core-engine/CoreEngineMessageState.ts +29 -0
- package/src/executor/core-engine/CoreEngineRunner.ts +200 -48
- package/src/executor/store/history/SessionHistoryStore.ts +4 -12
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +1 -3
- package/src/executor/types/SessionRun.ts +9 -0
- package/src/index.ts +1 -1
- package/src/plugin/core/PluginLocalExecution.ts +3 -3
- package/src/plugin/core/PluginRegistry.ts +116 -3
- package/src/rpc/Client.ts +12 -0
- package/src/session/Session.ts +28 -20
- package/src/session/SessionSystemBuilder.ts +4 -1
- package/src/session/recorder/SessionRecorderCompaction.ts +113 -65
- package/src/session/runtime/SessionPromptRuntime.ts +31 -46
- package/src/session/services/SessionStateService.ts +34 -25
- package/src/session/services/SessionTurnService.ts +97 -8
- package/src/types/agent/SessionActor.ts +9 -0
- package/src/types/executor/SessionRunContext.ts +17 -8
- package/src/types/plugin/PluginRuntime.ts +38 -4
- package/src/types/plugin/PluginState.ts +10 -0
- package/src/types/rpc/RpcProtocol.ts +11 -0
- package/src/types/runtime/agent/AgentContext.ts +2 -2
- package/src/types/session/SessionLocalState.ts +2 -2
- package/src/types/session/SessionOptions.ts +6 -6
- package/src/types/session/SessionQueueCommand.ts +78 -0
- package/src/utils/logger/Logger.ts +10 -10
- package/tsconfig.tsbuildinfo +1 -1
- package/bin/types/session/SessionConfigMutation.d.ts +0 -68
- package/bin/types/session/SessionConfigMutation.d.ts.map +0 -1
- package/bin/types/session/SessionConfigMutation.js +0 -10
- package/bin/types/session/SessionConfigMutation.js.map +0 -1
- package/src/types/session/SessionConfigMutation.ts +0 -78
package/src/session/Session.ts
CHANGED
|
@@ -55,7 +55,7 @@ import { SessionStateService } from "@/session/services/SessionStateService.js";
|
|
|
55
55
|
import { SessionTurnService } from "@/session/services/SessionTurnService.js";
|
|
56
56
|
import { SessionViewService } from "@/session/services/SessionViewService.js";
|
|
57
57
|
import type { SessionLocalState } from "@/types/session/SessionLocalState.js";
|
|
58
|
-
import type {
|
|
58
|
+
import type { AgentSessionCommand } from "@/types/session/SessionQueueCommand.js";
|
|
59
59
|
import type {
|
|
60
60
|
SessionComposerFactoryContext,
|
|
61
61
|
SessionComposerInput,
|
|
@@ -243,8 +243,8 @@ export class Session implements AgentSession {
|
|
|
243
243
|
*/
|
|
244
244
|
async set(input: AgentSessionSetInput): Promise<void> {
|
|
245
245
|
const configured = await this.stateService.set(input);
|
|
246
|
-
if (configured.
|
|
247
|
-
this.turnService.
|
|
246
|
+
if (configured.command) {
|
|
247
|
+
this.turnService.enqueue_command(configured.command);
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -263,39 +263,47 @@ export class Session implements AgentSession {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
/**
|
|
266
|
-
*
|
|
266
|
+
* 把一次显式历史压缩加入当前 Session 的有序输入队列。
|
|
267
267
|
*/
|
|
268
|
-
|
|
269
|
-
this.turnService.
|
|
270
|
-
|
|
268
|
+
async compact(): Promise<void> {
|
|
269
|
+
await this.turnService.compact();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 把 Agent configured state command 加入当前 Session 的统一输入队列。
|
|
274
|
+
*/
|
|
275
|
+
enqueue_agent_command(command: AgentSessionCommand): void {
|
|
276
|
+
this.turnService.enqueue_command({
|
|
277
|
+
type: "command",
|
|
278
|
+
command_id: command.command_id,
|
|
271
279
|
scope: "agent",
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
this.effective_instruction_system_blocks =
|
|
280
|
+
execute: async ({ turn_id }) => {
|
|
281
|
+
if (command.type === "instruction") {
|
|
282
|
+
this.effective_instruction_system_blocks = command
|
|
275
283
|
.instruction_blocks
|
|
276
284
|
.map((block) => ({ ...block }));
|
|
277
|
-
await this.stateService.
|
|
278
|
-
id: `agent-instruction:${this.id}:${
|
|
285
|
+
await this.stateService.emit_config_action_event({
|
|
286
|
+
id: `agent-instruction:${this.id}:${command.command_id}`,
|
|
279
287
|
title: "Agent instruction updated",
|
|
280
288
|
state: "completed",
|
|
281
289
|
turnId: turn_id,
|
|
282
290
|
});
|
|
283
291
|
return;
|
|
284
292
|
}
|
|
285
|
-
if (
|
|
286
|
-
this.effective_agent_env = { ...
|
|
287
|
-
await this.stateService.
|
|
288
|
-
id: `agent-env:${this.id}:${
|
|
293
|
+
if (command.type === "env") {
|
|
294
|
+
this.effective_agent_env = { ...command.env };
|
|
295
|
+
await this.stateService.emit_config_action_event({
|
|
296
|
+
id: `agent-env:${this.id}:${command.command_id}`,
|
|
289
297
|
title: "Agent environment updated",
|
|
290
298
|
state: "completed",
|
|
291
299
|
turnId: turn_id,
|
|
292
300
|
});
|
|
293
301
|
return;
|
|
294
302
|
}
|
|
295
|
-
this.effective_agent_plugins =
|
|
296
|
-
await this.stateService.
|
|
297
|
-
id: `agent-plugins:${this.id}:${
|
|
298
|
-
title:
|
|
303
|
+
this.effective_agent_plugins = command.plugins;
|
|
304
|
+
await this.stateService.emit_config_action_event({
|
|
305
|
+
id: `agent-plugins:${this.id}:${command.command_id}`,
|
|
306
|
+
title: command.title,
|
|
299
307
|
state: "completed",
|
|
300
308
|
turnId: turn_id,
|
|
301
309
|
});
|
|
@@ -253,7 +253,10 @@ export class SessionSystemBuilder implements SessionSystemComposer {
|
|
|
253
253
|
timezone: this.getSessionTimezone(),
|
|
254
254
|
getInstructionSystemBlocks: this.getInstructionSystemBlocks,
|
|
255
255
|
getManagedPluginSystemBlocks: this.getManagedPluginSystemBlocks,
|
|
256
|
-
getPluginSystemBlocks:
|
|
256
|
+
getPluginSystemBlocks: async () =>
|
|
257
|
+
run_context.agentPlugins
|
|
258
|
+
? await run_context.agentPlugins.systemBlocks()
|
|
259
|
+
: await this.getPluginSystemBlocks(),
|
|
257
260
|
});
|
|
258
261
|
}
|
|
259
262
|
}
|
|
@@ -13,10 +13,23 @@ import {
|
|
|
13
13
|
build_update_session_compaction_prompt,
|
|
14
14
|
SESSION_COMPACTION_SYSTEM_PROMPT,
|
|
15
15
|
} from "@executor/composer/compaction/jsonl/JsonlSessionCompactionPrompts.js";
|
|
16
|
+
import { fold_compacted_text } from "@executor/core-engine/CoreEngineContextCompaction.js";
|
|
16
17
|
import { SessionRecorder } from "@/session/recorder/SessionRecorder.js";
|
|
17
18
|
import { to_executor_ui_message } from "@/session/recorder/SessionMessageCodec.js";
|
|
18
19
|
import type { SessionMessage } from "@/types/session/SessionMessage.js";
|
|
19
20
|
|
|
21
|
+
/** 单次摘要请求中 conversation 文本的最大字符数。 */
|
|
22
|
+
const SUMMARY_CHUNK_MAX_CHARS = 20_000;
|
|
23
|
+
|
|
24
|
+
/** 单条 canonical Message 投影到摘要输入后的最大字符数。 */
|
|
25
|
+
const SUMMARY_MESSAGE_MAX_CHARS = 12_000;
|
|
26
|
+
|
|
27
|
+
/** 上一轮累计 Summary 进入下一次 reduce 请求时的最大字符数。 */
|
|
28
|
+
const PREVIOUS_SUMMARY_MAX_CHARS = 12_000;
|
|
29
|
+
|
|
30
|
+
/** 摘要模型的最大输出 token,用于避免 Summary 自身无限增长。 */
|
|
31
|
+
const SUMMARY_MAX_OUTPUT_TOKENS = 4_000;
|
|
32
|
+
|
|
20
33
|
/** 必要时把 Active 前缀关闭为带累计 Summary 的 Segment。 */
|
|
21
34
|
export async function compact_session_recorder_messages(input: {
|
|
22
35
|
/** 当前 Session Recorder。 */
|
|
@@ -28,31 +41,10 @@ export async function compact_session_recorder_messages(input: {
|
|
|
28
41
|
const context_messages = snapshot.messages.filter(
|
|
29
42
|
(message) => message.type === "user" || message.type === "assistant",
|
|
30
43
|
);
|
|
31
|
-
if (
|
|
32
|
-
return { compacted: false, reason: "
|
|
33
|
-
}
|
|
34
|
-
const system_text = input.compact_input.system
|
|
35
|
-
.map((message) => String(message.content || ""))
|
|
36
|
-
.join("\n\n");
|
|
37
|
-
const estimated_tokens = Math.ceil(
|
|
38
|
-
`${system_text}\n${snapshot.summary?.text || ""}\n${JSON.stringify(context_messages)}`.length / 3,
|
|
39
|
-
);
|
|
40
|
-
if (estimated_tokens <= input.compact_input.maxInputTokensApprox) {
|
|
41
|
-
return { compacted: false, reason: "under_budget" };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const ratio = Math.max(0.1, Math.min(0.9, input.compact_input.compactRatio));
|
|
45
|
-
const keep_last_messages = Math.max(1, input.compact_input.keepLastMessages);
|
|
46
|
-
const max_compact_count = context_messages.length - keep_last_messages;
|
|
47
|
-
if (max_compact_count < 2) {
|
|
48
|
-
return { compacted: false, reason: "keep_last_messages" };
|
|
44
|
+
if (!input.compact_input.force) {
|
|
45
|
+
return { compacted: false, reason: "not_requested" };
|
|
49
46
|
}
|
|
50
|
-
const
|
|
51
|
-
max_compact_count,
|
|
52
|
-
Math.max(2, Math.floor(context_messages.length * ratio)),
|
|
53
|
-
);
|
|
54
|
-
const older = context_messages.slice(0, compact_count);
|
|
55
|
-
const boundary = older.at(-1);
|
|
47
|
+
const boundary = context_messages.at(-1);
|
|
56
48
|
if (!boundary) return { compacted: false, reason: "nothing_to_compact" };
|
|
57
49
|
|
|
58
50
|
const action_id = `compacting:${input.recorder.session_id}:${generateId()}`;
|
|
@@ -63,47 +55,42 @@ export async function compact_session_recorder_messages(input: {
|
|
|
63
55
|
state: "running",
|
|
64
56
|
});
|
|
65
57
|
|
|
66
|
-
const
|
|
58
|
+
const conversation_messages = context_messages
|
|
67
59
|
.map((message) => message_to_compaction_text(message))
|
|
68
|
-
.filter(Boolean)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
60
|
+
.filter(Boolean);
|
|
61
|
+
const chunks = build_summary_chunks(conversation_messages);
|
|
62
|
+
let summary = String(snapshot.summary?.text || "").trim();
|
|
63
|
+
let used_fallback = false;
|
|
64
|
+
for (const chunk of chunks) {
|
|
65
|
+
const prompt = summary
|
|
66
|
+
? build_update_session_compaction_prompt({
|
|
67
|
+
previous_summary: fold_compacted_text(
|
|
68
|
+
summary,
|
|
69
|
+
PREVIOUS_SUMMARY_MAX_CHARS,
|
|
70
|
+
),
|
|
71
|
+
new_conversation_text: chunk || "(none)",
|
|
72
|
+
})
|
|
73
|
+
: build_initial_session_compaction_prompt({
|
|
74
|
+
conversation_text: chunk || "(none)",
|
|
75
|
+
});
|
|
76
|
+
try {
|
|
77
|
+
const result = await generateText({
|
|
78
|
+
model: input.compact_input.model,
|
|
79
|
+
system: [{ role: "system", content: SESSION_COMPACTION_SYSTEM_PROMPT }],
|
|
80
|
+
prompt,
|
|
81
|
+
maxOutputTokens: SUMMARY_MAX_OUTPUT_TOKENS,
|
|
78
82
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
summary = String(result.text || "").trim();
|
|
88
|
-
} catch (error) {
|
|
89
|
-
await publish_action(input.compact_input.onAction, {
|
|
90
|
-
id: action_id,
|
|
91
|
-
session_id: input.recorder.session_id,
|
|
92
|
-
title: "Session message compaction failed",
|
|
93
|
-
description: error instanceof Error ? error.message : String(error),
|
|
94
|
-
state: "failed",
|
|
95
|
-
});
|
|
96
|
-
return { compacted: false, reason: "summary_failed" };
|
|
83
|
+
const next_summary = String(result.text || "").trim();
|
|
84
|
+
if (!next_summary) throw new Error("Compaction model returned an empty Summary");
|
|
85
|
+
summary = next_summary;
|
|
86
|
+
} catch {
|
|
87
|
+
used_fallback = true;
|
|
88
|
+
summary = build_deterministic_summary(summary, chunk);
|
|
89
|
+
}
|
|
97
90
|
}
|
|
98
91
|
if (!summary) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
session_id: input.recorder.session_id,
|
|
102
|
-
title: "Session message compaction failed",
|
|
103
|
-
description: "The compaction model returned an empty Summary.",
|
|
104
|
-
state: "failed",
|
|
105
|
-
});
|
|
106
|
-
return { compacted: false, reason: "summary_failed" };
|
|
92
|
+
used_fallback = true;
|
|
93
|
+
summary = build_deterministic_summary("", "(no textual context)");
|
|
107
94
|
}
|
|
108
95
|
|
|
109
96
|
await input.recorder.compact_active({
|
|
@@ -121,7 +108,9 @@ export async function compact_session_recorder_messages(input: {
|
|
|
121
108
|
id: action_id,
|
|
122
109
|
session_id: input.recorder.session_id,
|
|
123
110
|
title: "Session messages compacted",
|
|
124
|
-
description:
|
|
111
|
+
description: used_fallback
|
|
112
|
+
? `Closed Active through Message ${boundary.message_id} with deterministic fallback Summary.`
|
|
113
|
+
: `Closed Active through Message ${boundary.message_id}.`,
|
|
125
114
|
state: "completed",
|
|
126
115
|
});
|
|
127
116
|
return { compacted: true };
|
|
@@ -130,10 +119,69 @@ export async function compact_session_recorder_messages(input: {
|
|
|
130
119
|
function message_to_compaction_text(message: SessionMessage): string {
|
|
131
120
|
const projected = to_executor_ui_message(message);
|
|
132
121
|
if (!projected) return "";
|
|
133
|
-
|
|
122
|
+
const parts = projected.parts
|
|
123
|
+
.filter((part) => part.type !== "reasoning")
|
|
124
|
+
.map((part) => {
|
|
125
|
+
const serialized = safe_stringify(part);
|
|
126
|
+
return serialized.length <= SUMMARY_MESSAGE_MAX_CHARS
|
|
127
|
+
? part
|
|
128
|
+
: {
|
|
129
|
+
type: part.type,
|
|
130
|
+
compacted_preview: fold_compacted_text(
|
|
131
|
+
serialized,
|
|
132
|
+
SUMMARY_MESSAGE_MAX_CHARS,
|
|
133
|
+
),
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
return fold_compacted_text(safe_stringify({
|
|
134
137
|
role: projected.role,
|
|
135
|
-
parts
|
|
136
|
-
});
|
|
138
|
+
parts,
|
|
139
|
+
}), SUMMARY_MESSAGE_MAX_CHARS);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 按 Message 边界把摘要输入拆为固定字符上限的 chunk。 */
|
|
143
|
+
function build_summary_chunks(messages: string[]): string[] {
|
|
144
|
+
const chunks: string[] = [];
|
|
145
|
+
let current = "";
|
|
146
|
+
for (const message of messages) {
|
|
147
|
+
const value = fold_compacted_text(message, SUMMARY_CHUNK_MAX_CHARS);
|
|
148
|
+
const candidate = current ? `${current}\n${value}` : value;
|
|
149
|
+
if (current && candidate.length > SUMMARY_CHUNK_MAX_CHARS) {
|
|
150
|
+
chunks.push(current);
|
|
151
|
+
current = value;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
current = candidate;
|
|
155
|
+
}
|
|
156
|
+
if (current) chunks.push(current);
|
|
157
|
+
return chunks;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** 摘要模型不可用时仍可完成归档的确定性 checkpoint。 */
|
|
161
|
+
function build_deterministic_summary(
|
|
162
|
+
previous_summary: string,
|
|
163
|
+
new_context: string,
|
|
164
|
+
): string {
|
|
165
|
+
const body = [
|
|
166
|
+
String(previous_summary || "").trim(),
|
|
167
|
+
String(new_context || "").trim(),
|
|
168
|
+
]
|
|
169
|
+
.filter(Boolean)
|
|
170
|
+
.join("\n\n");
|
|
171
|
+
return [
|
|
172
|
+
"## Compacted Context",
|
|
173
|
+
"The summary model was unavailable. The following deterministic checkpoint preserves the beginning and latest context.",
|
|
174
|
+
"",
|
|
175
|
+
fold_compacted_text(body || "(none)", PREVIOUS_SUMMARY_MAX_CHARS),
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function safe_stringify(value: unknown): string {
|
|
180
|
+
try {
|
|
181
|
+
return JSON.stringify(value) || String(value || "");
|
|
182
|
+
} catch {
|
|
183
|
+
return String(value || "");
|
|
184
|
+
}
|
|
137
185
|
}
|
|
138
186
|
|
|
139
187
|
async function publish_action(
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
import type { SessionMutation } from "@/types/session/SessionMutation.js";
|
|
17
17
|
import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
|
|
18
18
|
import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
|
|
19
|
-
import type {
|
|
19
|
+
import type { SessionQueueCommand } from "@/types/session/SessionQueueCommand.js";
|
|
20
20
|
import type {
|
|
21
21
|
AgentSessionTurnHandle,
|
|
22
22
|
AgentSessionTurnResult,
|
|
@@ -26,7 +26,7 @@ const TURN_STOPPED_MESSAGE = "Turn stopped";
|
|
|
26
26
|
const QUEUED_PROMPT_CANCELLED_MESSAGE =
|
|
27
27
|
"Prompt cancelled because session was stopped";
|
|
28
28
|
|
|
29
|
-
type
|
|
29
|
+
type SessionQueuePrompt = {
|
|
30
30
|
/** 当前队列项固定为 prompt。 */
|
|
31
31
|
type: "prompt";
|
|
32
32
|
|
|
@@ -42,20 +42,9 @@ type QueuedPrompt = {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* Session queue 的统一有序输入。
|
|
46
46
|
*/
|
|
47
|
-
type
|
|
48
|
-
/** 当前队列项固定为配置 mutation。 */
|
|
49
|
-
type: "config";
|
|
50
|
-
|
|
51
|
-
/** 当前待提交的配置 mutation。 */
|
|
52
|
-
mutation: SessionRuntimeConfigMutation;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Session actor 的统一有序输入。
|
|
57
|
-
*/
|
|
58
|
-
type QueuedSessionInput = QueuedPrompt | QueuedConfigMutation;
|
|
47
|
+
type SessionQueueInput = SessionQueuePrompt | SessionQueueCommand;
|
|
59
48
|
|
|
60
49
|
/**
|
|
61
50
|
* Promise 延迟控制器。
|
|
@@ -95,9 +84,6 @@ interface ActiveTurnState {
|
|
|
95
84
|
* 当前 turn 的取消控制器。
|
|
96
85
|
*/
|
|
97
86
|
abortController: AbortController;
|
|
98
|
-
|
|
99
|
-
/** 当前公开 turn 已开始的模型 turn 数量。 */
|
|
100
|
-
model_turn_index: number;
|
|
101
87
|
}
|
|
102
88
|
|
|
103
89
|
/**
|
|
@@ -162,7 +148,7 @@ export class SessionPromptRuntime {
|
|
|
162
148
|
private readonly appendErrorMessage: SessionPromptRuntimeOptions["appendErrorMessage"];
|
|
163
149
|
private readonly executeTurn: SessionPromptRuntimeOptions["executeTurn"];
|
|
164
150
|
private readonly stopTurn: SessionPromptRuntimeOptions["stopTurn"];
|
|
165
|
-
private readonly queue:
|
|
151
|
+
private readonly queue: SessionQueueInput[] = [];
|
|
166
152
|
private processingPromise: Promise<void> | null = null;
|
|
167
153
|
private activeTurn: ActiveTurnState | null = null;
|
|
168
154
|
|
|
@@ -193,22 +179,26 @@ export class SessionPromptRuntime {
|
|
|
193
179
|
}
|
|
194
180
|
|
|
195
181
|
/**
|
|
196
|
-
*
|
|
182
|
+
* 把一次已成功创建的 command 加入统一输入队列。
|
|
197
183
|
*/
|
|
198
|
-
|
|
199
|
-
this.queue.push(
|
|
200
|
-
type: "config",
|
|
201
|
-
mutation,
|
|
202
|
-
});
|
|
184
|
+
enqueue_command(command: SessionQueueCommand): void {
|
|
185
|
+
this.queue.push(command);
|
|
203
186
|
}
|
|
204
187
|
|
|
205
188
|
/**
|
|
206
|
-
*
|
|
189
|
+
* 判断是否存在等待并入下一 Session step 的 prompt。
|
|
207
190
|
*/
|
|
208
191
|
has_pending_prompt(): boolean {
|
|
209
192
|
return this.queue.some((item) => item.type === "prompt");
|
|
210
193
|
}
|
|
211
194
|
|
|
195
|
+
/**
|
|
196
|
+
* 判断是否存在等待在下一 Session step 检查点执行的 command。
|
|
197
|
+
*/
|
|
198
|
+
has_pending_command(): boolean {
|
|
199
|
+
return this.queue.some((item) => item.type === "command");
|
|
200
|
+
}
|
|
201
|
+
|
|
212
202
|
/**
|
|
213
203
|
* 返回当前 actor prompt 调度器是否仍处于活跃态。
|
|
214
204
|
*
|
|
@@ -281,9 +271,9 @@ export class SessionPromptRuntime {
|
|
|
281
271
|
current.deferredHandle.resolve(createTurnHandle(activeTurn));
|
|
282
272
|
|
|
283
273
|
try {
|
|
284
|
-
await this.
|
|
274
|
+
await this.execute_commands(
|
|
285
275
|
queued_before_prompt.filter(
|
|
286
|
-
(item): item is
|
|
276
|
+
(item): item is SessionQueueCommand => item.type === "command",
|
|
287
277
|
),
|
|
288
278
|
activeTurn,
|
|
289
279
|
);
|
|
@@ -368,10 +358,10 @@ export class SessionPromptRuntime {
|
|
|
368
358
|
|
|
369
359
|
private cancelQueuedPrompts(): number {
|
|
370
360
|
const cancelled = this.queue.filter(
|
|
371
|
-
(item): item is
|
|
361
|
+
(item): item is SessionQueuePrompt => item.type === "prompt",
|
|
372
362
|
);
|
|
373
363
|
if (cancelled.length <= 0) return 0;
|
|
374
|
-
const retained = this.queue.filter((item) => item.type === "
|
|
364
|
+
const retained = this.queue.filter((item) => item.type === "command");
|
|
375
365
|
this.queue.splice(0, this.queue.length, ...retained);
|
|
376
366
|
for (const item of cancelled) {
|
|
377
367
|
const turnId = `turn:${this.sessionId}:cancelled:${Date.now()}:${nanoid(6)}`;
|
|
@@ -414,26 +404,24 @@ export class SessionPromptRuntime {
|
|
|
414
404
|
}
|
|
415
405
|
|
|
416
406
|
/**
|
|
417
|
-
*
|
|
407
|
+
* 在下一 Session step 检查点按入队顺序提交配置并持久化 steer。
|
|
418
408
|
*/
|
|
419
409
|
private async drain_queued_inputs(
|
|
420
410
|
activeTurn: ActiveTurnState,
|
|
421
411
|
): Promise<SessionUserMessageV1[]> {
|
|
422
|
-
activeTurn.model_turn_index += 1;
|
|
423
412
|
if (this.queue.length <= 0) return [];
|
|
424
413
|
const drained = this.queue.splice(0, this.queue.length);
|
|
425
414
|
const merged: SessionUserMessageV1[] = [];
|
|
426
415
|
|
|
427
416
|
for (let index = 0; index < drained.length; index += 1) {
|
|
428
417
|
const item = drained[index];
|
|
429
|
-
if (item.type === "
|
|
418
|
+
if (item.type === "command") {
|
|
430
419
|
try {
|
|
431
|
-
await item.
|
|
420
|
+
await item.execute({
|
|
432
421
|
turn_id: activeTurn.turnId,
|
|
433
|
-
model_turn_index: activeTurn.model_turn_index,
|
|
434
422
|
});
|
|
435
423
|
} catch {
|
|
436
|
-
//
|
|
424
|
+
// command 自己负责失败观测;单条失败不能吞掉后续 steer 或 command。
|
|
437
425
|
}
|
|
438
426
|
continue;
|
|
439
427
|
}
|
|
@@ -457,22 +445,20 @@ export class SessionPromptRuntime {
|
|
|
457
445
|
}
|
|
458
446
|
|
|
459
447
|
/**
|
|
460
|
-
*
|
|
448
|
+
* 执行一组已经从统一队列截取的 command。
|
|
461
449
|
*/
|
|
462
|
-
private async
|
|
463
|
-
|
|
450
|
+
private async execute_commands(
|
|
451
|
+
commands: SessionQueueCommand[],
|
|
464
452
|
active_turn: ActiveTurnState,
|
|
465
453
|
): Promise<void> {
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
for (const item of mutations) {
|
|
454
|
+
if (commands.length <= 0) return;
|
|
455
|
+
for (const command of commands) {
|
|
469
456
|
try {
|
|
470
|
-
await
|
|
457
|
+
await command.execute({
|
|
471
458
|
turn_id: active_turn.turnId,
|
|
472
|
-
model_turn_index: active_turn.model_turn_index,
|
|
473
459
|
});
|
|
474
460
|
} catch {
|
|
475
|
-
//
|
|
461
|
+
// command 自己负责失败观测;单条失败不能阻断当前 prompt。
|
|
476
462
|
}
|
|
477
463
|
}
|
|
478
464
|
}
|
|
@@ -516,7 +502,6 @@ function createActiveTurnState(turnId: string): ActiveTurnState {
|
|
|
516
502
|
result: null,
|
|
517
503
|
deferredFinished: createDeferred<AgentSessionTurnResult>(),
|
|
518
504
|
abortController: new AbortController(),
|
|
519
|
-
model_turn_index: 0,
|
|
520
505
|
};
|
|
521
506
|
}
|
|
522
507
|
|
|
@@ -37,7 +37,7 @@ import type {
|
|
|
37
37
|
SessionUserMessageV1,
|
|
38
38
|
} from "@/executor/types/SessionRecords.js";
|
|
39
39
|
import type { SessionLocalState } from "@/types/session/SessionLocalState.js";
|
|
40
|
-
import type {
|
|
40
|
+
import type { SessionQueueCommand } from "@/types/session/SessionQueueCommand.js";
|
|
41
41
|
import { generateId } from "@/utils/Id.js";
|
|
42
42
|
import type { Logger } from "@/utils/logger/Logger.js";
|
|
43
43
|
import type { AgentModel } from "@/model/CityModelAdapter.js";
|
|
@@ -119,15 +119,15 @@ type EmitActionInput = SessionActionRecordInputV1 | SessionActionRecordV1;
|
|
|
119
119
|
/**
|
|
120
120
|
* Session 配置成功写入后的队列提交结果。
|
|
121
121
|
*/
|
|
122
|
-
export interface
|
|
122
|
+
export interface SessionConfiguredCommandResult {
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* 等待在下一 Session step 检查点执行的 command。
|
|
125
125
|
*
|
|
126
126
|
* 说明(中文)
|
|
127
127
|
* - 输入未产生实际配置变化时为空。
|
|
128
|
-
* - fork 等内部初始化路径可以选择立即提交而不返回
|
|
128
|
+
* - fork 等内部初始化路径可以选择立即提交而不返回 command。
|
|
129
129
|
*/
|
|
130
|
-
|
|
130
|
+
command?: SessionQueueCommand;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
@@ -267,7 +267,7 @@ export class SessionStateService {
|
|
|
267
267
|
async set(
|
|
268
268
|
input: AgentSessionSetInput,
|
|
269
269
|
options?: SessionSetOptions,
|
|
270
|
-
): Promise<
|
|
270
|
+
): Promise<SessionConfiguredCommandResult> {
|
|
271
271
|
const should_emit_action = options?.emit_action !== false;
|
|
272
272
|
const previous_model_label = this.state.sessionConfig.modelLabel;
|
|
273
273
|
const previous_model_id = String(
|
|
@@ -320,7 +320,7 @@ export class SessionStateService {
|
|
|
320
320
|
...next_config,
|
|
321
321
|
};
|
|
322
322
|
if (!should_emit_model_switch_action) return;
|
|
323
|
-
await this.
|
|
323
|
+
await this.emit_config_action_event({
|
|
324
324
|
id: action_id,
|
|
325
325
|
title: `Session model switched from ${previous_model_name} to ${next_model_name}`,
|
|
326
326
|
state: "completed",
|
|
@@ -333,26 +333,14 @@ export class SessionStateService {
|
|
|
333
333
|
return {};
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
const
|
|
336
|
+
const command_id = generateId();
|
|
337
337
|
return {
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
command: {
|
|
339
|
+
type: "command",
|
|
340
|
+
command_id,
|
|
340
341
|
scope: "session",
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
await apply_effective_config(turn_id);
|
|
344
|
-
} catch (error) {
|
|
345
|
-
if (should_emit_model_switch_action) {
|
|
346
|
-
await this.emit_action_event({
|
|
347
|
-
id: action_id,
|
|
348
|
-
title: `Session model switch from ${previous_model_name} to ${next_model_name} failed`,
|
|
349
|
-
description: error instanceof Error ? error.message : String(error),
|
|
350
|
-
state: "failed",
|
|
351
|
-
turnId: turn_id,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
throw error;
|
|
355
|
-
}
|
|
342
|
+
execute: async ({ turn_id }) => {
|
|
343
|
+
await apply_effective_config(turn_id);
|
|
356
344
|
},
|
|
357
345
|
},
|
|
358
346
|
};
|
|
@@ -547,6 +535,27 @@ export class SessionStateService {
|
|
|
547
535
|
});
|
|
548
536
|
}
|
|
549
537
|
|
|
538
|
+
/**
|
|
539
|
+
* 尽力写入配置生效 action,不让 timeline 故障改变 effective state。
|
|
540
|
+
*/
|
|
541
|
+
async emit_config_action_event(input: EmitActionInput): Promise<boolean> {
|
|
542
|
+
try {
|
|
543
|
+
await this.emit_action_event(input);
|
|
544
|
+
return true;
|
|
545
|
+
} catch (error) {
|
|
546
|
+
try {
|
|
547
|
+
await this.logger.log("warn", "[agent] config action persistence failed", {
|
|
548
|
+
sessionId: this.session_id,
|
|
549
|
+
actionId: String(input.id || ""),
|
|
550
|
+
error: error instanceof Error ? error.message : String(error),
|
|
551
|
+
});
|
|
552
|
+
} catch {
|
|
553
|
+
// 配置已经提交,日志失败也不能反向改变 effective state。
|
|
554
|
+
}
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
550
559
|
/**
|
|
551
560
|
* 构造并持久化一条 prompt user 消息。
|
|
552
561
|
*/
|