@downcity/agent 1.1.228 → 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 +6 -1
- package/bin/agent/local/Agent.d.ts.map +1 -1
- package/bin/agent/local/Agent.js +30 -3
- package/bin/agent/local/Agent.js.map +1 -1
- package/bin/agent/local/services/AgentSessions.d.ts +23 -0
- package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
- package/bin/agent/local/services/AgentSessions.js +44 -0
- 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 -7
- 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 +43 -0
- package/bin/executor/Executor.d.ts.map +1 -1
- package/bin/executor/Executor.js +127 -7
- package/bin/executor/Executor.js.map +1 -1
- package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts +10 -0
- package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts +0 -10
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js +4 -34
- 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 +15 -0
- package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.js +150 -35
- package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
- package/bin/executor/store/history/SessionHistoryStore.d.ts +7 -23
- 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 +2 -4
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.d.ts.map +1 -1
- package/bin/executor/tools/plugin/PluginToolBridge.js +4 -2
- package/bin/executor/tools/plugin/PluginToolBridge.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 +3 -2
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +2 -2
- package/bin/index.js.map +1 -1
- package/bin/model/CityModelAdapter.d.ts +6 -0
- package/bin/model/CityModelAdapter.d.ts.map +1 -1
- package/bin/model/CityModelAdapter.js +13 -0
- package/bin/model/CityModelAdapter.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 +41 -2
- package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
- package/bin/plugin/core/PluginRegistry.js +145 -6
- 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 +15 -1
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +83 -11
- 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/browse/Browse.d.ts +0 -8
- package/bin/session/browse/Browse.d.ts.map +1 -1
- package/bin/session/browse/Browse.js +72 -57
- package/bin/session/browse/Browse.js.map +1 -1
- package/bin/session/recorder/JsonlSessionMessageStore.d.ts +69 -16
- package/bin/session/recorder/JsonlSessionMessageStore.d.ts.map +1 -1
- package/bin/session/recorder/JsonlSessionMessageStore.js +283 -60
- package/bin/session/recorder/JsonlSessionMessageStore.js.map +1 -1
- package/bin/session/recorder/SessionRecorder.d.ts +20 -2
- package/bin/session/recorder/SessionRecorder.d.ts.map +1 -1
- package/bin/session/recorder/SessionRecorder.js +91 -46
- package/bin/session/recorder/SessionRecorder.js.map +1 -1
- package/bin/session/recorder/SessionRecorderCompaction.d.ts +3 -3
- package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -1
- package/bin/session/recorder/SessionRecorderCompaction.js +118 -88
- package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -1
- package/bin/session/recorder/SessionRecorderHistoryStore.d.ts +2 -2
- package/bin/session/recorder/SessionRecorderHistoryStore.d.ts.map +1 -1
- package/bin/session/recorder/SessionRecorderHistoryStore.js +31 -36
- package/bin/session/recorder/SessionRecorderHistoryStore.js.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.d.ts +21 -1
- package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.js +65 -8
- package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
- package/bin/session/services/SessionStateService.d.ts +19 -1
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js +70 -40
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionTurnService.d.ts +15 -0
- package/bin/session/services/SessionTurnService.d.ts.map +1 -1
- package/bin/session/services/SessionTurnService.js +104 -7
- package/bin/session/services/SessionTurnService.js.map +1 -1
- package/bin/session/services/SessionViewService.d.ts +1 -5
- package/bin/session/services/SessionViewService.d.ts.map +1 -1
- package/bin/session/services/SessionViewService.js +4 -34
- package/bin/session/services/SessionViewService.js.map +1 -1
- package/bin/session/storage/Paths.d.ts +3 -15
- package/bin/session/storage/Paths.d.ts.map +1 -1
- package/bin/session/storage/Paths.js +5 -21
- package/bin/session/storage/Paths.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/agent/SessionTypes.d.ts +2 -24
- package/bin/types/agent/SessionTypes.d.ts.map +1 -1
- package/bin/types/executor/SessionRunContext.d.ts +29 -0
- package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
- package/bin/types/plugin/PluginRuntime.d.ts +59 -2
- 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 +17 -5
- package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
- package/bin/types/runtime/agent/AgentContext.js +29 -6
- package/bin/types/runtime/agent/AgentContext.js.map +1 -1
- package/bin/types/session/SessionLocalState.d.ts +8 -0
- package/bin/types/session/SessionLocalState.d.ts.map +1 -1
- package/bin/types/session/SessionMessage.d.ts +16 -12
- package/bin/types/session/SessionMessage.d.ts.map +1 -1
- package/bin/types/session/SessionMutation.d.ts +1 -1
- package/bin/types/session/SessionMutation.js +1 -1
- package/bin/types/session/SessionOptions.d.ts +16 -0
- 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/types/session/SessionSegment.d.ts +57 -0
- package/bin/types/session/SessionSegment.d.ts.map +1 -0
- package/bin/types/session/SessionSegment.js +8 -0
- package/bin/types/session/SessionSegment.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/city-model-tool-loop.test.mjs +32 -0
- 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-action-message.test.mjs +59 -13
- package/scripts/session-compaction-context-window.test.mjs +96 -0
- package/scripts/session-config-turn-boundary.test.mjs +278 -0
- package/scripts/session-list-title.test.mjs +21 -3
- package/scripts/session-prompt-runtime.test.mjs +93 -0
- package/scripts/session-recorder.test.mjs +335 -15
- package/src/agent/local/Agent.ts +34 -3
- package/src/agent/local/services/AgentSessions.ts +61 -0
- package/src/agent/remote/RemoteSession.ts +5 -0
- package/src/agent/remote/RemoteTransport.ts +2 -0
- package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +15 -5
- package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +4 -0
- package/src/executor/Executor.ts +161 -7
- package/src/executor/composer/compaction/SessionCompactionComposer.ts +12 -0
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.ts +4 -55
- 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 +211 -43
- package/src/executor/store/history/SessionHistoryStore.ts +7 -26
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +2 -4
- package/src/executor/tools/plugin/PluginToolBridge.ts +4 -2
- package/src/executor/types/SessionRun.ts +9 -0
- package/src/index.ts +9 -1
- package/src/model/CityModelAdapter.ts +15 -0
- package/src/plugin/core/PluginLocalExecution.ts +3 -3
- package/src/plugin/core/PluginRegistry.ts +187 -6
- package/src/rpc/Client.ts +12 -0
- package/src/session/Session.ts +97 -11
- package/src/session/SessionSystemBuilder.ts +4 -1
- package/src/session/browse/Browse.ts +73 -69
- package/src/session/recorder/JsonlSessionMessageStore.ts +378 -60
- package/src/session/recorder/SessionRecorder.ts +116 -49
- package/src/session/recorder/SessionRecorderCompaction.ts +131 -108
- package/src/session/recorder/SessionRecorderHistoryStore.ts +29 -49
- package/src/session/runtime/SessionPromptRuntime.ts +86 -10
- package/src/session/services/SessionStateService.ts +89 -45
- package/src/session/services/SessionTurnService.ts +112 -7
- package/src/session/services/SessionViewService.ts +4 -50
- package/src/session/storage/Paths.ts +5 -38
- package/src/types/agent/SessionActor.ts +9 -0
- package/src/types/agent/SessionTypes.ts +2 -24
- package/src/types/executor/SessionRunContext.ts +34 -0
- package/src/types/plugin/PluginRuntime.ts +63 -2
- package/src/types/plugin/PluginState.ts +10 -0
- package/src/types/rpc/RpcProtocol.ts +11 -0
- package/src/types/runtime/agent/AgentContext.ts +32 -7
- package/src/types/session/SessionLocalState.ts +9 -0
- package/src/types/session/SessionMessage.ts +16 -12
- package/src/types/session/SessionMutation.ts +1 -1
- package/src/types/session/SessionOptions.ts +21 -0
- package/src/types/session/SessionQueueCommand.ts +78 -0
- package/src/types/session/SessionSegment.ts +62 -0
- package/src/utils/logger/Logger.ts +10 -10
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 验证 Agent 配置与 steer 在 Session step 检查点统一生效。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 第一次 provider 请求保持阻塞,用来制造真实的运行中配置修改窗口。
|
|
6
|
+
* - instruction、env 与 plugin registry 修改不能改变已经开始的请求。
|
|
7
|
+
* - config 与 steer 在同一个 Session step 检查点提交,并继续使用同一个 turn id。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import fs from "node:fs/promises";
|
|
15
|
+
import { MockLanguageModelV3 } from "ai/test";
|
|
16
|
+
import { Agent } from "../bin/index.js";
|
|
17
|
+
import {
|
|
18
|
+
createAction,
|
|
19
|
+
createPlugin,
|
|
20
|
+
} from "../bin/plugin/core/PluginActionFactory.js";
|
|
21
|
+
|
|
22
|
+
function create_deferred() {
|
|
23
|
+
let resolve;
|
|
24
|
+
const promise = new Promise((inner_resolve) => {
|
|
25
|
+
resolve = inner_resolve;
|
|
26
|
+
});
|
|
27
|
+
return { promise, resolve };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function create_stream_text_result(text) {
|
|
31
|
+
return {
|
|
32
|
+
stream: new ReadableStream({
|
|
33
|
+
start(controller) {
|
|
34
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
35
|
+
controller.enqueue({ type: "text-start", id: "text_1" });
|
|
36
|
+
controller.enqueue({ type: "text-delta", id: "text_1", delta: text });
|
|
37
|
+
controller.enqueue({ type: "text-end", id: "text_1" });
|
|
38
|
+
controller.enqueue({
|
|
39
|
+
type: "finish",
|
|
40
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
41
|
+
usage: {
|
|
42
|
+
inputTokens: {
|
|
43
|
+
total: 0,
|
|
44
|
+
noCache: 0,
|
|
45
|
+
cacheRead: 0,
|
|
46
|
+
cacheWrite: 0,
|
|
47
|
+
},
|
|
48
|
+
outputTokens: {
|
|
49
|
+
total: 0,
|
|
50
|
+
text: 0,
|
|
51
|
+
reasoning: 0,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
controller.close();
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test("running Agent config changes and steer apply at the same Session step checkpoint", async () => {
|
|
62
|
+
const agent_path = await fs.mkdtemp(
|
|
63
|
+
path.join(os.tmpdir(), "downcity-agent-config-turn-boundary-"),
|
|
64
|
+
);
|
|
65
|
+
const first_provider_request_started = create_deferred();
|
|
66
|
+
const release_first_provider_request = create_deferred();
|
|
67
|
+
const provider_prompts = [];
|
|
68
|
+
let provider_request_count = 0;
|
|
69
|
+
let plugin_stop_count = 0;
|
|
70
|
+
|
|
71
|
+
const model = new MockLanguageModelV3({
|
|
72
|
+
modelId: "config-turn-boundary-model",
|
|
73
|
+
doStream: async (options) => {
|
|
74
|
+
const has_tools = Array.isArray(options.tools) && options.tools.length > 0;
|
|
75
|
+
if (!has_tools) return create_stream_text_result("Session title");
|
|
76
|
+
provider_request_count += 1;
|
|
77
|
+
provider_prompts.push(JSON.stringify(options.prompt));
|
|
78
|
+
if (provider_request_count === 1) {
|
|
79
|
+
first_provider_request_started.resolve();
|
|
80
|
+
await release_first_provider_request.promise;
|
|
81
|
+
}
|
|
82
|
+
return create_stream_text_result(`done:${String(provider_request_count)}`);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const runtime_plugin = createPlugin({
|
|
86
|
+
name: "runtime-config",
|
|
87
|
+
title: "Runtime Config",
|
|
88
|
+
description: "Provides a system block for turn-boundary tests",
|
|
89
|
+
lifecycle: {
|
|
90
|
+
stop: async () => {
|
|
91
|
+
plugin_stop_count += 1;
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
system: (context) => `plugin-env:${context.env.TURN_ENV || "missing"}`,
|
|
95
|
+
actions: {
|
|
96
|
+
ping: createAction({
|
|
97
|
+
description: "Ping",
|
|
98
|
+
execute: async () => ({ success: true, data: { ok: true } }),
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const agent = new Agent({
|
|
103
|
+
id: "config_turn_boundary_agent",
|
|
104
|
+
path: agent_path,
|
|
105
|
+
model,
|
|
106
|
+
instruction: ["instruction:old"],
|
|
107
|
+
env: { TURN_ENV: "old" },
|
|
108
|
+
plugins: [runtime_plugin],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const session = await agent.sessions.create({
|
|
113
|
+
sessionId: "config_turn_boundary_session",
|
|
114
|
+
});
|
|
115
|
+
const first_turn = await session.prompt({ query: "first" });
|
|
116
|
+
await first_provider_request_started.promise;
|
|
117
|
+
|
|
118
|
+
agent.setInstruction(["instruction:new"]);
|
|
119
|
+
agent.patchEnv({ TURN_ENV: "new" });
|
|
120
|
+
await agent.plugins.unregister("runtime-config");
|
|
121
|
+
const steer_turn_promise = session.prompt({ query: "steer" });
|
|
122
|
+
|
|
123
|
+
assert.equal(plugin_stop_count, 0);
|
|
124
|
+
assert.equal(provider_prompts.length, 1);
|
|
125
|
+
assert.match(provider_prompts[0], /instruction:old/);
|
|
126
|
+
assert.match(provider_prompts[0], /plugin-env:old/);
|
|
127
|
+
assert.doesNotMatch(provider_prompts[0], /instruction:new/);
|
|
128
|
+
|
|
129
|
+
release_first_provider_request.resolve();
|
|
130
|
+
const steer_turn = await steer_turn_promise;
|
|
131
|
+
assert.equal((await first_turn.finished).success, true);
|
|
132
|
+
assert.equal(steer_turn.id, first_turn.id);
|
|
133
|
+
assert.equal((await steer_turn.finished).success, true);
|
|
134
|
+
assert.equal(plugin_stop_count, 1);
|
|
135
|
+
assert.equal(provider_prompts.length, 2);
|
|
136
|
+
assert.match(provider_prompts[1], /instruction:new/);
|
|
137
|
+
assert.doesNotMatch(provider_prompts[1], /instruction:old/);
|
|
138
|
+
assert.doesNotMatch(provider_prompts[1], /plugin-env:old/);
|
|
139
|
+
|
|
140
|
+
const messages = await session.messages();
|
|
141
|
+
const completed_actions = messages.items
|
|
142
|
+
.filter((message) => message.type === "action" && message.status === "completed")
|
|
143
|
+
.map((message) => message.title);
|
|
144
|
+
assert.deepEqual(completed_actions, [
|
|
145
|
+
"Agent instruction updated",
|
|
146
|
+
"Agent environment updated",
|
|
147
|
+
"Agent plugin runtime-config unregistered",
|
|
148
|
+
]);
|
|
149
|
+
} finally {
|
|
150
|
+
release_first_provider_request.resolve();
|
|
151
|
+
await agent.dispose();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("running session model changes apply with steer at the next Session step", async () => {
|
|
156
|
+
const agent_path = await fs.mkdtemp(
|
|
157
|
+
path.join(os.tmpdir(), "downcity-session-step-boundary-"),
|
|
158
|
+
);
|
|
159
|
+
const old_model_started = create_deferred();
|
|
160
|
+
const release_old_model = create_deferred();
|
|
161
|
+
const model_calls = [];
|
|
162
|
+
|
|
163
|
+
const old_model = new MockLanguageModelV3({
|
|
164
|
+
modelId: "old-model",
|
|
165
|
+
doStream: async (options) => {
|
|
166
|
+
const has_tools = Array.isArray(options.tools) && options.tools.length > 0;
|
|
167
|
+
if (!has_tools) return create_stream_text_result("Old title");
|
|
168
|
+
model_calls.push("old-model");
|
|
169
|
+
old_model_started.resolve();
|
|
170
|
+
await release_old_model.promise;
|
|
171
|
+
return create_stream_text_result("old response");
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
const new_model = new MockLanguageModelV3({
|
|
175
|
+
modelId: "new-model",
|
|
176
|
+
doStream: async (options) => {
|
|
177
|
+
const has_tools = Array.isArray(options.tools) && options.tools.length > 0;
|
|
178
|
+
if (!has_tools) return create_stream_text_result("New title");
|
|
179
|
+
model_calls.push("new-model");
|
|
180
|
+
return create_stream_text_result("new response");
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
const runtime_plugin = createPlugin({
|
|
184
|
+
name: "model-boundary",
|
|
185
|
+
title: "Model Boundary",
|
|
186
|
+
description: "Ensures the main provider request has tools",
|
|
187
|
+
actions: {
|
|
188
|
+
ping: createAction({
|
|
189
|
+
description: "Ping",
|
|
190
|
+
execute: async () => ({ success: true, data: { ok: true } }),
|
|
191
|
+
}),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
const agent = new Agent({
|
|
195
|
+
id: "session_step_boundary_agent",
|
|
196
|
+
path: agent_path,
|
|
197
|
+
model: old_model,
|
|
198
|
+
plugins: [runtime_plugin],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const session = await agent.sessions.create({
|
|
203
|
+
sessionId: "session_step_boundary_session",
|
|
204
|
+
});
|
|
205
|
+
const first_turn = await session.prompt({ query: "first" });
|
|
206
|
+
await old_model_started.promise;
|
|
207
|
+
|
|
208
|
+
await session.set({ model: new_model });
|
|
209
|
+
const steer_turn_promise = session.prompt({ query: "continue" });
|
|
210
|
+
assert.deepEqual(model_calls, ["old-model"]);
|
|
211
|
+
|
|
212
|
+
release_old_model.resolve();
|
|
213
|
+
const steer_turn = await steer_turn_promise;
|
|
214
|
+
assert.equal((await first_turn.finished).success, true);
|
|
215
|
+
assert.equal(steer_turn.id, first_turn.id);
|
|
216
|
+
assert.equal((await steer_turn.finished).success, true);
|
|
217
|
+
assert.deepEqual(model_calls, ["old-model", "new-model"]);
|
|
218
|
+
|
|
219
|
+
const messages = await session.messages();
|
|
220
|
+
const model_actions = messages.items.filter(
|
|
221
|
+
(message) =>
|
|
222
|
+
message.type === "action" &&
|
|
223
|
+
message.title === "Session model switched from old-model to new-model",
|
|
224
|
+
);
|
|
225
|
+
assert.deepEqual(
|
|
226
|
+
model_actions.map((message) => message.status),
|
|
227
|
+
["completed"],
|
|
228
|
+
);
|
|
229
|
+
} finally {
|
|
230
|
+
release_old_model.resolve();
|
|
231
|
+
await agent.dispose();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("config remains effective when its action message cannot be persisted", async () => {
|
|
236
|
+
const agent_path = await fs.mkdtemp(
|
|
237
|
+
path.join(os.tmpdir(), "downcity-config-action-observability-"),
|
|
238
|
+
);
|
|
239
|
+
const model_calls = [];
|
|
240
|
+
const old_model = new MockLanguageModelV3({
|
|
241
|
+
modelId: "old-observability-model",
|
|
242
|
+
doStream: async () => {
|
|
243
|
+
model_calls.push("old");
|
|
244
|
+
return create_stream_text_result("old");
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
const new_model = new MockLanguageModelV3({
|
|
248
|
+
modelId: "new-observability-model",
|
|
249
|
+
doStream: async () => {
|
|
250
|
+
model_calls.push("new");
|
|
251
|
+
return create_stream_text_result("new");
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
const agent = new Agent({
|
|
255
|
+
id: "config_action_observability_agent",
|
|
256
|
+
path: agent_path,
|
|
257
|
+
model: old_model,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const session = await agent.sessions.create({
|
|
262
|
+
sessionId: "config_action_observability_session",
|
|
263
|
+
});
|
|
264
|
+
session.stateService.emit_action_event = async () => {
|
|
265
|
+
throw new Error("action store unavailable");
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
await session.set({ model: new_model });
|
|
269
|
+
const turn = await session.prompt({ query: "use configured model" });
|
|
270
|
+
assert.equal((await turn.finished).success, true);
|
|
271
|
+
assert.deepEqual(model_calls, ["new", "new"]);
|
|
272
|
+
|
|
273
|
+
const messages = await session.messages();
|
|
274
|
+
assert.equal(messages.items.some((message) => message.type === "action"), false);
|
|
275
|
+
} finally {
|
|
276
|
+
await agent.dispose();
|
|
277
|
+
}
|
|
278
|
+
});
|
|
@@ -118,7 +118,7 @@ test("list_sessions returns persisted title from active session metadata", async
|
|
|
118
118
|
"sessions",
|
|
119
119
|
encodeURIComponent(session.id),
|
|
120
120
|
"messages",
|
|
121
|
-
"
|
|
121
|
+
"active.jsonl",
|
|
122
122
|
),
|
|
123
123
|
);
|
|
124
124
|
assert.equal(meta.historyBytes, history_stat.size);
|
|
@@ -158,23 +158,41 @@ test("list_sessions reflects canonical Recorder history changes", async () => {
|
|
|
158
158
|
encodeURIComponent(session.id),
|
|
159
159
|
"messages",
|
|
160
160
|
);
|
|
161
|
-
const messages_path = path.join(messages_dir, "
|
|
161
|
+
const messages_path = path.join(messages_dir, "active.jsonl");
|
|
162
162
|
const meta_path = path.join(messages_dir, "meta.json");
|
|
163
163
|
|
|
164
164
|
try {
|
|
165
165
|
await session.append_assistant_message({
|
|
166
166
|
text: "Recorder appended history",
|
|
167
167
|
});
|
|
168
|
+
await session.recorder.compact_active({
|
|
169
|
+
through_sequence: 1,
|
|
170
|
+
summary: {
|
|
171
|
+
record_type: "summary",
|
|
172
|
+
session_id: session.id,
|
|
173
|
+
summary_id: "summary-through-1",
|
|
174
|
+
through_sequence: 1,
|
|
175
|
+
text: "Initial history was compacted.",
|
|
176
|
+
created_at: Date.now(),
|
|
177
|
+
},
|
|
178
|
+
});
|
|
168
179
|
|
|
169
180
|
const page = await collection.list();
|
|
181
|
+
const info = await session.get_info();
|
|
170
182
|
const repaired_meta = JSON.parse(await fs.readFile(meta_path, "utf8"));
|
|
171
183
|
const history_stat = await fs.stat(messages_path);
|
|
184
|
+
const segment_stat = await fs.stat(path.join(
|
|
185
|
+
messages_dir,
|
|
186
|
+
"segments",
|
|
187
|
+
"000000000001-000000000001.jsonl",
|
|
188
|
+
));
|
|
172
189
|
|
|
173
190
|
assert.equal(page.items[0].messageCount, 2);
|
|
191
|
+
assert.equal(info.messageCount, 2);
|
|
174
192
|
assert.equal(page.items[0].previewText, "Recorder appended history");
|
|
175
193
|
assert.equal(repaired_meta.messageCount, 2);
|
|
176
194
|
assert.equal(repaired_meta.previewText, "Recorder appended history");
|
|
177
|
-
assert.equal(repaired_meta.historyBytes, history_stat.size);
|
|
195
|
+
assert.equal(repaired_meta.historyBytes, history_stat.size + segment_stat.size);
|
|
178
196
|
} finally {
|
|
179
197
|
await agent.dispose();
|
|
180
198
|
}
|
|
@@ -139,6 +139,99 @@ test("SessionPromptRuntime merges queued prompts at the next step boundary", asy
|
|
|
139
139
|
);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
test("SessionPromptRuntime applies config and steer in queue order at the same step checkpoint", async () => {
|
|
143
|
+
const lifecycle = [];
|
|
144
|
+
const applied_turn_ids = [];
|
|
145
|
+
const execution_finished = createDeferred();
|
|
146
|
+
let step_merge = null;
|
|
147
|
+
|
|
148
|
+
const runtime = new SessionPromptRuntime({
|
|
149
|
+
sessionId: "test",
|
|
150
|
+
publish: () => {},
|
|
151
|
+
createAndPersistUserMessage: async (input, _turn_id, input_type) => {
|
|
152
|
+
lifecycle.push(`${input_type}:${input.query}`);
|
|
153
|
+
return createUserMessage(input.query, lifecycle.length);
|
|
154
|
+
},
|
|
155
|
+
appendErrorMessage: async () => {},
|
|
156
|
+
executeTurn: async (input) => {
|
|
157
|
+
step_merge = input.onStepMerge;
|
|
158
|
+
await execution_finished.promise;
|
|
159
|
+
return {
|
|
160
|
+
text: "done",
|
|
161
|
+
success: true,
|
|
162
|
+
assistantMessage: createAssistantMessage("done", 1),
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
stopTurn: () => false,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
runtime.enqueue_command({
|
|
169
|
+
type: "command",
|
|
170
|
+
command_id: "config-1",
|
|
171
|
+
scope: "agent",
|
|
172
|
+
execute: async ({ turn_id }) => {
|
|
173
|
+
applied_turn_ids.push(turn_id);
|
|
174
|
+
lifecycle.push("config:1");
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const first_turn = await runtime.prompt({ query: "first" });
|
|
178
|
+
const merge = await waitUntil(() => step_merge);
|
|
179
|
+
runtime.enqueue_command({
|
|
180
|
+
type: "command",
|
|
181
|
+
command_id: "config-2",
|
|
182
|
+
scope: "session",
|
|
183
|
+
execute: async ({ turn_id }) => {
|
|
184
|
+
applied_turn_ids.push(turn_id);
|
|
185
|
+
lifecycle.push("config:2");
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
const second_turn_promise = runtime.prompt({ query: "second" });
|
|
189
|
+
|
|
190
|
+
const messages = await merge();
|
|
191
|
+
const second_turn = await second_turn_promise;
|
|
192
|
+
|
|
193
|
+
assert.equal(second_turn.id, first_turn.id);
|
|
194
|
+
assert.deepEqual(
|
|
195
|
+
messages.map((message) => message.parts[0]?.text),
|
|
196
|
+
["second"],
|
|
197
|
+
);
|
|
198
|
+
assert.deepEqual(lifecycle, [
|
|
199
|
+
"config:1",
|
|
200
|
+
"prompt:first",
|
|
201
|
+
"config:2",
|
|
202
|
+
"steer:second",
|
|
203
|
+
]);
|
|
204
|
+
assert.deepEqual(applied_turn_ids, [first_turn.id, first_turn.id]);
|
|
205
|
+
|
|
206
|
+
execution_finished.resolve();
|
|
207
|
+
await first_turn.finished;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("SessionPromptRuntime keeps command queued without starting a turn", async () => {
|
|
211
|
+
let apply_count = 0;
|
|
212
|
+
const runtime = new SessionPromptRuntime({
|
|
213
|
+
sessionId: "test",
|
|
214
|
+
publish: () => {},
|
|
215
|
+
createAndPersistUserMessage: async (input) => createUserMessage(input.query, 1),
|
|
216
|
+
appendErrorMessage: async () => {},
|
|
217
|
+
executeTurn: async () => ({ text: "done", success: true }),
|
|
218
|
+
stopTurn: () => false,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
runtime.enqueue_command({
|
|
222
|
+
type: "command",
|
|
223
|
+
command_id: "config-only",
|
|
224
|
+
scope: "agent",
|
|
225
|
+
execute: async () => {
|
|
226
|
+
apply_count += 1;
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
assert.equal(runtime.isActive(), false);
|
|
231
|
+
assert.equal(runtime.has_pending_command(), true);
|
|
232
|
+
assert.equal(apply_count, 0);
|
|
233
|
+
});
|
|
234
|
+
|
|
142
235
|
test("SessionPromptRuntime moves unmerged prompts into the next turn", async () => {
|
|
143
236
|
const events = [];
|
|
144
237
|
const finishQueue = [];
|