@downcity/agent 1.1.228 → 1.1.232
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/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +0 -7
- package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
- package/bin/executor/Executor.d.ts +25 -0
- package/bin/executor/Executor.d.ts.map +1 -1
- package/bin/executor/Executor.js +36 -0
- package/bin/executor/Executor.js.map +1 -1
- package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts +4 -0
- package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts +0 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts.map +1 -1
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js +15 -11
- package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.d.ts +11 -0
- package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
- package/bin/executor/core-engine/CoreEngineRunner.js +12 -8
- package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
- package/bin/executor/store/history/SessionHistoryStore.d.ts +3 -13
- package/bin/executor/store/history/SessionHistoryStore.d.ts.map +1 -1
- package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +1 -1
- 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/index.d.ts +2 -1
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +1 -1
- 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/PluginRegistry.d.ts +22 -1
- package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
- package/bin/plugin/core/PluginRegistry.js +46 -4
- package/bin/plugin/core/PluginRegistry.js.map +1 -1
- package/bin/session/Session.d.ts +11 -1
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +76 -11
- package/bin/session/Session.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 +50 -62
- 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 +17 -1
- package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
- package/bin/session/runtime/SessionPromptRuntime.js +67 -8
- package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
- package/bin/session/services/SessionStateService.d.ts +15 -1
- package/bin/session/services/SessionStateService.d.ts.map +1 -1
- package/bin/session/services/SessionStateService.js +60 -39
- package/bin/session/services/SessionStateService.js.map +1 -1
- package/bin/session/services/SessionTurnService.d.ts +5 -0
- package/bin/session/services/SessionTurnService.d.ts.map +1 -1
- package/bin/session/services/SessionTurnService.js +17 -3
- 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/SessionTypes.d.ts +2 -24
- package/bin/types/agent/SessionTypes.d.ts.map +1 -1
- package/bin/types/executor/SessionRunContext.d.ts +21 -0
- package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
- package/bin/types/plugin/PluginRuntime.d.ts +25 -0
- package/bin/types/plugin/PluginRuntime.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/SessionConfigMutation.d.ts +68 -0
- package/bin/types/session/SessionConfigMutation.d.ts.map +1 -0
- package/bin/types/session/SessionConfigMutation.js +10 -0
- package/bin/types/session/SessionConfigMutation.js.map +1 -0
- 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/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/package.json +3 -3
- package/scripts/city-model-tool-loop.test.mjs +32 -0
- package/scripts/session-action-message.test.mjs +59 -13
- package/scripts/session-compaction-context-window.test.mjs +95 -0
- package/scripts/session-config-turn-boundary.test.mjs +224 -0
- package/scripts/session-list-title.test.mjs +21 -3
- package/scripts/session-prompt-runtime.test.mjs +85 -0
- package/scripts/session-recorder.test.mjs +340 -11
- package/src/agent/local/Agent.ts +34 -3
- package/src/agent/local/services/AgentSessions.ts +61 -0
- package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +0 -5
- package/src/executor/Executor.ts +62 -0
- package/src/executor/composer/compaction/SessionCompactionComposer.ts +5 -0
- package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.ts +16 -14
- package/src/executor/core-engine/CoreEngineRunner.ts +24 -8
- package/src/executor/store/history/SessionHistoryStore.ts +3 -14
- package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +1 -1
- package/src/executor/tools/plugin/PluginToolBridge.ts +4 -2
- package/src/index.ts +8 -0
- package/src/model/CityModelAdapter.ts +15 -0
- package/src/plugin/core/PluginRegistry.ts +72 -4
- package/src/session/Session.ts +89 -11
- 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 +55 -80
- package/src/session/recorder/SessionRecorderHistoryStore.ts +29 -49
- package/src/session/runtime/SessionPromptRuntime.ts +100 -9
- package/src/session/services/SessionStateService.ts +79 -44
- package/src/session/services/SessionTurnService.ts +19 -3
- package/src/session/services/SessionViewService.ts +4 -50
- package/src/session/storage/Paths.ts +5 -38
- package/src/types/agent/SessionTypes.ts +2 -24
- package/src/types/executor/SessionRunContext.ts +25 -0
- package/src/types/plugin/PluginRuntime.ts +27 -0
- package/src/types/runtime/agent/AgentContext.ts +32 -7
- package/src/types/session/SessionConfigMutation.ts +78 -0
- 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/SessionSegment.ts +62 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -16,6 +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 { SessionRuntimeConfigMutation } from "@/types/session/SessionConfigMutation.js";
|
|
19
20
|
import type {
|
|
20
21
|
AgentSessionTurnHandle,
|
|
21
22
|
AgentSessionTurnResult,
|
|
@@ -26,6 +27,9 @@ const QUEUED_PROMPT_CANCELLED_MESSAGE =
|
|
|
26
27
|
"Prompt cancelled because session was stopped";
|
|
27
28
|
|
|
28
29
|
type QueuedPrompt = {
|
|
30
|
+
/** 当前队列项固定为 prompt。 */
|
|
31
|
+
type: "prompt";
|
|
32
|
+
|
|
29
33
|
/**
|
|
30
34
|
* 当前排队中的 prompt 输入。
|
|
31
35
|
*/
|
|
@@ -37,6 +41,22 @@ type QueuedPrompt = {
|
|
|
37
41
|
deferredHandle: Deferred<AgentSessionTurnHandle>;
|
|
38
42
|
};
|
|
39
43
|
|
|
44
|
+
/**
|
|
45
|
+
* 等待在模型 turn 边界提交的配置队列项。
|
|
46
|
+
*/
|
|
47
|
+
type QueuedConfigMutation = {
|
|
48
|
+
/** 当前队列项固定为配置 mutation。 */
|
|
49
|
+
type: "config";
|
|
50
|
+
|
|
51
|
+
/** 当前待提交的配置 mutation。 */
|
|
52
|
+
mutation: SessionRuntimeConfigMutation;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Session actor 的统一有序输入。
|
|
57
|
+
*/
|
|
58
|
+
type QueuedSessionInput = QueuedPrompt | QueuedConfigMutation;
|
|
59
|
+
|
|
40
60
|
/**
|
|
41
61
|
* Promise 延迟控制器。
|
|
42
62
|
*/
|
|
@@ -75,6 +95,9 @@ interface ActiveTurnState {
|
|
|
75
95
|
* 当前 turn 的取消控制器。
|
|
76
96
|
*/
|
|
77
97
|
abortController: AbortController;
|
|
98
|
+
|
|
99
|
+
/** 当前公开 turn 已开始的模型 turn 数量。 */
|
|
100
|
+
model_turn_index: number;
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
/**
|
|
@@ -139,7 +162,7 @@ export class SessionPromptRuntime {
|
|
|
139
162
|
private readonly appendErrorMessage: SessionPromptRuntimeOptions["appendErrorMessage"];
|
|
140
163
|
private readonly executeTurn: SessionPromptRuntimeOptions["executeTurn"];
|
|
141
164
|
private readonly stopTurn: SessionPromptRuntimeOptions["stopTurn"];
|
|
142
|
-
private readonly queue:
|
|
165
|
+
private readonly queue: QueuedSessionInput[] = [];
|
|
143
166
|
private processingPromise: Promise<void> | null = null;
|
|
144
167
|
private activeTurn: ActiveTurnState | null = null;
|
|
145
168
|
|
|
@@ -161,6 +184,7 @@ export class SessionPromptRuntime {
|
|
|
161
184
|
prompt(input: AgentSessionPromptInput): Promise<AgentSessionTurnHandle> {
|
|
162
185
|
const deferredHandle = createDeferred<AgentSessionTurnHandle>();
|
|
163
186
|
this.queue.push({
|
|
187
|
+
type: "prompt",
|
|
164
188
|
input,
|
|
165
189
|
deferredHandle,
|
|
166
190
|
});
|
|
@@ -168,6 +192,23 @@ export class SessionPromptRuntime {
|
|
|
168
192
|
return deferredHandle.promise;
|
|
169
193
|
}
|
|
170
194
|
|
|
195
|
+
/**
|
|
196
|
+
* 把一次已成功写入 configured state 的配置修改加入统一输入队列。
|
|
197
|
+
*/
|
|
198
|
+
enqueue_config(mutation: SessionRuntimeConfigMutation): void {
|
|
199
|
+
this.queue.push({
|
|
200
|
+
type: "config",
|
|
201
|
+
mutation,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 判断是否存在等待并入模型 turn 的 prompt。
|
|
207
|
+
*/
|
|
208
|
+
has_pending_prompt(): boolean {
|
|
209
|
+
return this.queue.some((item) => item.type === "prompt");
|
|
210
|
+
}
|
|
211
|
+
|
|
171
212
|
/**
|
|
172
213
|
* 返回当前 actor prompt 调度器是否仍处于活跃态。
|
|
173
214
|
*
|
|
@@ -176,7 +217,7 @@ export class SessionPromptRuntime {
|
|
|
176
217
|
* - Session 会用它阻止内部 direct execution 与 actor 模式并发混用。
|
|
177
218
|
*/
|
|
178
219
|
isActive(): boolean {
|
|
179
|
-
return this.processingPromise !== null || this.
|
|
220
|
+
return this.processingPromise !== null || this.has_pending_prompt();
|
|
180
221
|
}
|
|
181
222
|
|
|
182
223
|
/**
|
|
@@ -211,16 +252,19 @@ export class SessionPromptRuntime {
|
|
|
211
252
|
if (this.processingPromise) return;
|
|
212
253
|
this.processingPromise = this.processLoop().finally(() => {
|
|
213
254
|
this.processingPromise = null;
|
|
214
|
-
if (this.
|
|
255
|
+
if (this.has_pending_prompt()) {
|
|
215
256
|
this.ensureProcessing();
|
|
216
257
|
}
|
|
217
258
|
});
|
|
218
259
|
}
|
|
219
260
|
|
|
220
261
|
private async processLoop(): Promise<void> {
|
|
221
|
-
while (this.
|
|
262
|
+
while (this.has_pending_prompt()) {
|
|
263
|
+
const prompt_index = this.queue.findIndex((item) => item.type === "prompt");
|
|
264
|
+
if (prompt_index < 0) break;
|
|
265
|
+
const queued_before_prompt = this.queue.splice(0, prompt_index);
|
|
222
266
|
const current = this.queue.shift();
|
|
223
|
-
if (!current) break;
|
|
267
|
+
if (!current || current.type !== "prompt") break;
|
|
224
268
|
|
|
225
269
|
const turnId = `turn:${this.sessionId}:${Date.now()}:${nanoid(6)}`;
|
|
226
270
|
const activeTurn = createActiveTurnState(turnId);
|
|
@@ -237,12 +281,18 @@ export class SessionPromptRuntime {
|
|
|
237
281
|
current.deferredHandle.resolve(createTurnHandle(activeTurn));
|
|
238
282
|
|
|
239
283
|
try {
|
|
284
|
+
await this.apply_config_mutations(
|
|
285
|
+
queued_before_prompt.filter(
|
|
286
|
+
(item): item is QueuedConfigMutation => item.type === "config",
|
|
287
|
+
),
|
|
288
|
+
activeTurn,
|
|
289
|
+
);
|
|
240
290
|
await this.createAndPersistUserMessage(current.input, turnId, "prompt");
|
|
241
291
|
const result = await this.executeTurn({
|
|
242
292
|
turnId,
|
|
243
293
|
promptInput: current.input,
|
|
244
294
|
onStepMerge: async () => {
|
|
245
|
-
return await this.
|
|
295
|
+
return await this.drain_queued_inputs(activeTurn);
|
|
246
296
|
},
|
|
247
297
|
abortSignal: activeTurn.abortController.signal,
|
|
248
298
|
});
|
|
@@ -317,8 +367,12 @@ export class SessionPromptRuntime {
|
|
|
317
367
|
}
|
|
318
368
|
|
|
319
369
|
private cancelQueuedPrompts(): number {
|
|
320
|
-
|
|
321
|
-
|
|
370
|
+
const cancelled = this.queue.filter(
|
|
371
|
+
(item): item is QueuedPrompt => item.type === "prompt",
|
|
372
|
+
);
|
|
373
|
+
if (cancelled.length <= 0) return 0;
|
|
374
|
+
const retained = this.queue.filter((item) => item.type === "config");
|
|
375
|
+
this.queue.splice(0, this.queue.length, ...retained);
|
|
322
376
|
for (const item of cancelled) {
|
|
323
377
|
const turnId = `turn:${this.sessionId}:cancelled:${Date.now()}:${nanoid(6)}`;
|
|
324
378
|
const cancelledTurn = createActiveTurnState(turnId);
|
|
@@ -359,15 +413,30 @@ export class SessionPromptRuntime {
|
|
|
359
413
|
return cancelled.length;
|
|
360
414
|
}
|
|
361
415
|
|
|
362
|
-
|
|
416
|
+
/**
|
|
417
|
+
* 在下一模型 turn 开始前按入队顺序提交配置并持久化 steer。
|
|
418
|
+
*/
|
|
419
|
+
private async drain_queued_inputs(
|
|
363
420
|
activeTurn: ActiveTurnState,
|
|
364
421
|
): Promise<SessionUserMessageV1[]> {
|
|
422
|
+
activeTurn.model_turn_index += 1;
|
|
365
423
|
if (this.queue.length <= 0) return [];
|
|
366
424
|
const drained = this.queue.splice(0, this.queue.length);
|
|
367
425
|
const merged: SessionUserMessageV1[] = [];
|
|
368
426
|
|
|
369
427
|
for (let index = 0; index < drained.length; index += 1) {
|
|
370
428
|
const item = drained[index];
|
|
429
|
+
if (item.type === "config") {
|
|
430
|
+
try {
|
|
431
|
+
await item.mutation.apply({
|
|
432
|
+
turn_id: activeTurn.turnId,
|
|
433
|
+
model_turn_index: activeTurn.model_turn_index,
|
|
434
|
+
});
|
|
435
|
+
} catch {
|
|
436
|
+
// 配置实现负责写 failed action;单条失败不能吞掉后续 steer 或配置。
|
|
437
|
+
}
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
371
440
|
try {
|
|
372
441
|
const message = await this.createAndPersistUserMessage(
|
|
373
442
|
item.input,
|
|
@@ -386,6 +455,27 @@ export class SessionPromptRuntime {
|
|
|
386
455
|
|
|
387
456
|
return merged;
|
|
388
457
|
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 提交一组已经从统一队列截取的配置 mutation。
|
|
461
|
+
*/
|
|
462
|
+
private async apply_config_mutations(
|
|
463
|
+
mutations: QueuedConfigMutation[],
|
|
464
|
+
active_turn: ActiveTurnState,
|
|
465
|
+
): Promise<void> {
|
|
466
|
+
if (mutations.length <= 0) return;
|
|
467
|
+
active_turn.model_turn_index += 1;
|
|
468
|
+
for (const item of mutations) {
|
|
469
|
+
try {
|
|
470
|
+
await item.mutation.apply({
|
|
471
|
+
turn_id: active_turn.turnId,
|
|
472
|
+
model_turn_index: active_turn.model_turn_index,
|
|
473
|
+
});
|
|
474
|
+
} catch {
|
|
475
|
+
// 配置实现负责写 failed action;单条失败不能阻断当前 prompt。
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
389
479
|
}
|
|
390
480
|
|
|
391
481
|
function buildPromptRuntimeErrorAssistantMessage(input: {
|
|
@@ -426,6 +516,7 @@ function createActiveTurnState(turnId: string): ActiveTurnState {
|
|
|
426
516
|
result: null,
|
|
427
517
|
deferredFinished: createDeferred<AgentSessionTurnResult>(),
|
|
428
518
|
abortController: new AbortController(),
|
|
519
|
+
model_turn_index: 0,
|
|
429
520
|
};
|
|
430
521
|
}
|
|
431
522
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import {
|
|
11
11
|
inferAgentModelLabel,
|
|
12
12
|
normalizeAgentModel,
|
|
13
|
+
read_agent_model_context_window,
|
|
13
14
|
} from "@/model/CityModelAdapter.js";
|
|
14
15
|
import {
|
|
15
16
|
patchSessionModelLabel,
|
|
@@ -36,6 +37,7 @@ import type {
|
|
|
36
37
|
SessionUserMessageV1,
|
|
37
38
|
} from "@/executor/types/SessionRecords.js";
|
|
38
39
|
import type { SessionLocalState } from "@/types/session/SessionLocalState.js";
|
|
40
|
+
import type { SessionRuntimeConfigMutation } from "@/types/session/SessionConfigMutation.js";
|
|
39
41
|
import { generateId } from "@/utils/Id.js";
|
|
40
42
|
import type { Logger } from "@/utils/logger/Logger.js";
|
|
41
43
|
import type { AgentModel } from "@/model/CityModelAdapter.js";
|
|
@@ -46,8 +48,6 @@ import {
|
|
|
46
48
|
from_ui_user_parts,
|
|
47
49
|
to_executor_ui_message,
|
|
48
50
|
} from "@/session/recorder/SessionMessageCodec.js";
|
|
49
|
-
import fs from "fs-extra";
|
|
50
|
-
import { getSdkAgentSessionMessagesPath } from "@/session/storage/Paths.js";
|
|
51
51
|
import type { SessionMessage } from "@/types/session/SessionMessage.js";
|
|
52
52
|
|
|
53
53
|
type SessionStateServiceOptions = {
|
|
@@ -116,6 +116,20 @@ type SessionSetOptions = {
|
|
|
116
116
|
|
|
117
117
|
type EmitActionInput = SessionActionRecordInputV1 | SessionActionRecordV1;
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Session 配置成功写入后的队列提交结果。
|
|
121
|
+
*/
|
|
122
|
+
export interface SessionConfiguredMutationResult {
|
|
123
|
+
/**
|
|
124
|
+
* 等待在下一模型 turn 提交的 mutation。
|
|
125
|
+
*
|
|
126
|
+
* 说明(中文)
|
|
127
|
+
* - 输入未产生实际配置变化时为空。
|
|
128
|
+
* - fork 等内部初始化路径可以选择立即提交而不返回 mutation。
|
|
129
|
+
*/
|
|
130
|
+
mutation?: SessionRuntimeConfigMutation;
|
|
131
|
+
}
|
|
132
|
+
|
|
119
133
|
/**
|
|
120
134
|
* 本地 Session 状态与持久化服务。
|
|
121
135
|
*/
|
|
@@ -209,6 +223,9 @@ export class SessionStateService {
|
|
|
209
223
|
...(metadata.modelId ? { modelId: metadata.modelId } : {}),
|
|
210
224
|
};
|
|
211
225
|
await this.restore_persisted_model();
|
|
226
|
+
this.state.effective_session_config = {
|
|
227
|
+
...this.state.sessionConfig,
|
|
228
|
+
};
|
|
212
229
|
})();
|
|
213
230
|
await this.state.initializePromise;
|
|
214
231
|
}
|
|
@@ -247,7 +264,10 @@ export class SessionStateService {
|
|
|
247
264
|
/**
|
|
248
265
|
* 写入当前 session 配置。
|
|
249
266
|
*/
|
|
250
|
-
async set(
|
|
267
|
+
async set(
|
|
268
|
+
input: AgentSessionSetInput,
|
|
269
|
+
options?: SessionSetOptions,
|
|
270
|
+
): Promise<SessionConfiguredMutationResult> {
|
|
251
271
|
const should_emit_action = options?.emit_action !== false;
|
|
252
272
|
const previous_model_label = this.state.sessionConfig.modelLabel;
|
|
253
273
|
const previous_model_id = String(
|
|
@@ -273,47 +293,71 @@ export class SessionStateService {
|
|
|
273
293
|
const next_model_name = next_model_label || next_model_id;
|
|
274
294
|
const action_id = `model-switching:${this.session_id}:${Date.now()}:${generateId()}`;
|
|
275
295
|
|
|
276
|
-
if (should_emit_model_switch_action) {
|
|
277
|
-
await this.emit_action_event({
|
|
278
|
-
id: action_id,
|
|
279
|
-
title: `Switching session model from ${previous_model_name} to ${next_model_name}`,
|
|
280
|
-
state: "running",
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
296
|
try {
|
|
297
|
+
const next_config: AgentSessionConfigSnapshot = {
|
|
298
|
+
...this.state.sessionConfig,
|
|
299
|
+
};
|
|
285
300
|
if (next_model) {
|
|
286
|
-
|
|
287
|
-
|
|
301
|
+
next_config.model = normalizeAgentModel(next_model);
|
|
302
|
+
next_config.modelLabel = next_model_label;
|
|
303
|
+
next_config.model_context_window =
|
|
304
|
+
read_agent_model_context_window(next_model);
|
|
288
305
|
}
|
|
289
306
|
if (next_model_id) {
|
|
290
|
-
|
|
307
|
+
next_config.modelId = next_model_id;
|
|
291
308
|
}
|
|
292
309
|
await patchSessionModelLabel({
|
|
293
310
|
projectRoot: this.project_root,
|
|
294
311
|
agentId: this.agent_id,
|
|
295
312
|
sessionId: this.session_id,
|
|
296
|
-
model:
|
|
297
|
-
modelId:
|
|
313
|
+
model: next_config.model,
|
|
314
|
+
modelId: next_config.modelId,
|
|
298
315
|
});
|
|
299
|
-
|
|
300
|
-
|
|
316
|
+
this.state.sessionConfig = next_config;
|
|
317
|
+
|
|
318
|
+
const apply_effective_config = async (turn_id?: string): Promise<void> => {
|
|
319
|
+
this.state.effective_session_config = {
|
|
320
|
+
...next_config,
|
|
321
|
+
};
|
|
322
|
+
if (!should_emit_model_switch_action) return;
|
|
301
323
|
await this.emit_action_event({
|
|
302
324
|
id: action_id,
|
|
303
|
-
title: `Session model
|
|
304
|
-
|
|
305
|
-
|
|
325
|
+
title: `Session model switched from ${previous_model_name} to ${next_model_name}`,
|
|
326
|
+
state: "completed",
|
|
327
|
+
...(turn_id ? { turnId: turn_id } : {}),
|
|
306
328
|
});
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
if (options?.emit_action === false) {
|
|
332
|
+
await apply_effective_config();
|
|
333
|
+
return {};
|
|
307
334
|
}
|
|
308
|
-
throw error;
|
|
309
|
-
}
|
|
310
335
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
336
|
+
const mutation_id = generateId();
|
|
337
|
+
return {
|
|
338
|
+
mutation: {
|
|
339
|
+
mutation_id,
|
|
340
|
+
scope: "session",
|
|
341
|
+
apply: async ({ turn_id }) => {
|
|
342
|
+
try {
|
|
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
|
+
}
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
} catch (error) {
|
|
360
|
+
throw error;
|
|
317
361
|
}
|
|
318
362
|
}
|
|
319
363
|
|
|
@@ -324,6 +368,8 @@ export class SessionStateService {
|
|
|
324
368
|
const model = await this.resolve_model_by_id(model_id);
|
|
325
369
|
this.state.sessionConfig.model = normalizeAgentModel(model);
|
|
326
370
|
this.state.sessionConfig.modelLabel = inferAgentModelLabel(model);
|
|
371
|
+
this.state.sessionConfig.model_context_window =
|
|
372
|
+
read_agent_model_context_window(model);
|
|
327
373
|
}
|
|
328
374
|
|
|
329
375
|
/** 使用宿主 resolver 解析稳定模型 ID。 */
|
|
@@ -384,28 +430,17 @@ export class SessionStateService {
|
|
|
384
430
|
* 仅刷新当前 session metadata。
|
|
385
431
|
*/
|
|
386
432
|
async touch_metadata(): Promise<void> {
|
|
387
|
-
const
|
|
388
|
-
limit: 500,
|
|
389
|
-
include_internal: true,
|
|
390
|
-
});
|
|
391
|
-
const messages_path = getSdkAgentSessionMessagesPath(
|
|
392
|
-
this.project_root,
|
|
393
|
-
this.agent_id,
|
|
394
|
-
this.session_id,
|
|
395
|
-
);
|
|
396
|
-
const history_bytes = await fs.stat(messages_path)
|
|
397
|
-
.then((file_stat) => file_stat.size)
|
|
398
|
-
.catch(() => 0);
|
|
433
|
+
const stats = await this.recorder.storage_stats();
|
|
399
434
|
const preview_text = resolve_message_preview(
|
|
400
|
-
|
|
435
|
+
stats.latest_message || undefined,
|
|
401
436
|
).slice(0, 180);
|
|
402
437
|
await touchSessionMetadata({
|
|
403
438
|
projectRoot: this.project_root,
|
|
404
439
|
agentId: this.agent_id,
|
|
405
440
|
sessionId: this.session_id,
|
|
406
441
|
sessionConfig: this.state.sessionConfig,
|
|
407
|
-
message_count:
|
|
408
|
-
history_bytes,
|
|
442
|
+
message_count: stats.message_count,
|
|
443
|
+
history_bytes: stats.history_bytes,
|
|
409
444
|
...(preview_text ? { preview_text } : {}),
|
|
410
445
|
});
|
|
411
446
|
}
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
import { SessionEventHub } from "@/session/runtime/SessionEventHub.js";
|
|
23
23
|
import { SessionPromptRuntime } from "@/session/runtime/SessionPromptRuntime.js";
|
|
24
24
|
import type { SessionRunContext } from "@/types/executor/SessionRunContext.js";
|
|
25
|
+
import type { SessionRuntimeConfigMutation } from "@/types/session/SessionConfigMutation.js";
|
|
25
26
|
import { SessionStateService } from "@/session/services/SessionStateService.js";
|
|
26
27
|
import {
|
|
27
28
|
SessionAssistantMessageWriter,
|
|
@@ -136,6 +137,13 @@ export class SessionTurnService {
|
|
|
136
137
|
return await this.prompt_runtime.prompt(input);
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
/**
|
|
141
|
+
* 把 configured state 修改加入当前 Session 的统一输入队列。
|
|
142
|
+
*/
|
|
143
|
+
enqueue_config(mutation: SessionRuntimeConfigMutation): void {
|
|
144
|
+
this.prompt_runtime.enqueue_config(mutation);
|
|
145
|
+
}
|
|
146
|
+
|
|
139
147
|
/**
|
|
140
148
|
* 停止当前 turn,并取消尚未被吸收的排队 prompt。
|
|
141
149
|
*/
|
|
@@ -182,6 +190,7 @@ export class SessionTurnService {
|
|
|
182
190
|
}
|
|
183
191
|
return merged;
|
|
184
192
|
},
|
|
193
|
+
hasPendingStepInput: () => this.prompt_runtime.has_pending_prompt(),
|
|
185
194
|
onUiMessageChunkCallback: async (chunk) => {
|
|
186
195
|
if (!is_assistant_content_chunk(chunk.type)) return;
|
|
187
196
|
const writer = await ensure_assistant_writer();
|
|
@@ -201,8 +210,16 @@ export class SessionTurnService {
|
|
|
201
210
|
query: executor_query,
|
|
202
211
|
runContext: run_context,
|
|
203
212
|
});
|
|
213
|
+
const final_assistant_parts = result.assistantMessage
|
|
214
|
+
? from_ui_assistant_parts(result.assistantMessage.parts)
|
|
215
|
+
: [];
|
|
204
216
|
const final_assistant_writer = assistant_writer_ref.current;
|
|
205
217
|
if (final_assistant_writer) {
|
|
218
|
+
for (const part of final_assistant_parts) {
|
|
219
|
+
if (part.type === "file") {
|
|
220
|
+
await final_assistant_writer.append_file_part(part);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
206
223
|
if (input.abortSignal?.aborted) {
|
|
207
224
|
await final_assistant_writer.stop();
|
|
208
225
|
} else if (result.success) {
|
|
@@ -211,14 +228,13 @@ export class SessionTurnService {
|
|
|
211
228
|
await final_assistant_writer.fail(result.error);
|
|
212
229
|
}
|
|
213
230
|
} else if (result.assistantMessage) {
|
|
214
|
-
|
|
215
|
-
if (parts.length > 0) {
|
|
231
|
+
if (final_assistant_parts.length > 0) {
|
|
216
232
|
assistant_segment_index += 1;
|
|
217
233
|
const fallback_writer = await this.recorder.open_assistant_message({
|
|
218
234
|
turn_id: input.turnId,
|
|
219
235
|
segment_index: assistant_segment_index,
|
|
220
236
|
});
|
|
221
|
-
for (const part of
|
|
237
|
+
for (const part of final_assistant_parts) await fallback_writer.upsert_part(part);
|
|
222
238
|
if (result.success) await fallback_writer.complete();
|
|
223
239
|
else await fallback_writer.fail(result.error);
|
|
224
240
|
}
|
|
@@ -8,12 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { nanoid } from "nanoid";
|
|
11
|
-
import {
|
|
12
|
-
buildSessionRecordsPage,
|
|
13
|
-
buildSessionInfo,
|
|
14
|
-
loadSessionArchiveMessagesFromPath,
|
|
15
|
-
} from "@/session/browse/Browse.js";
|
|
16
|
-
import { getSdkAgentSessionArchiveFilePath } from "@/session/storage/Paths.js";
|
|
11
|
+
import { buildSessionInfo } from "@/session/browse/Browse.js";
|
|
17
12
|
import { ensureSessionTitle } from "@/session/SessionTitle.js";
|
|
18
13
|
import { readSessionMetadata } from "@/session/storage/Metadata.js";
|
|
19
14
|
import { buildSessionSystemBlocks } from "@/session/SessionSystemBuilder.js";
|
|
@@ -21,8 +16,6 @@ import type { SessionHistoryStore } from "@/executor/store/history/SessionHistor
|
|
|
21
16
|
import type { SessionSystemComposer } from "@/executor/composer/system/SessionSystemComposer.js";
|
|
22
17
|
import type {
|
|
23
18
|
AgentSessionForkInput,
|
|
24
|
-
AgentSessionRecordsInput,
|
|
25
|
-
AgentSessionRecordsPage,
|
|
26
19
|
AgentSessionInfo,
|
|
27
20
|
AgentSessionSystemBlock,
|
|
28
21
|
AgentSessionSystemSnapshot,
|
|
@@ -192,42 +185,6 @@ export class SessionViewService<TSession extends Pick<AgentSession, "set">> {
|
|
|
192
185
|
});
|
|
193
186
|
}
|
|
194
187
|
|
|
195
|
-
/**
|
|
196
|
-
* 读取当前 session records 分页。
|
|
197
|
-
*/
|
|
198
|
-
async records(
|
|
199
|
-
input?: AgentSessionRecordsInput,
|
|
200
|
-
): Promise<AgentSessionRecordsPage> {
|
|
201
|
-
const archive_id = String(input?.archive_id || "").trim();
|
|
202
|
-
const [metadata, current_messages] = await Promise.all([
|
|
203
|
-
readSessionMetadata({
|
|
204
|
-
projectRoot: this.project_root,
|
|
205
|
-
agentId: this.agent_id,
|
|
206
|
-
sessionId: this.session_id,
|
|
207
|
-
}),
|
|
208
|
-
this.history_store.list_records(),
|
|
209
|
-
]);
|
|
210
|
-
const page_messages = archive_id
|
|
211
|
-
? await loadSessionArchiveMessagesFromPath(
|
|
212
|
-
getSdkAgentSessionArchiveFilePath(
|
|
213
|
-
this.project_root,
|
|
214
|
-
this.agent_id,
|
|
215
|
-
this.session_id,
|
|
216
|
-
archive_id,
|
|
217
|
-
),
|
|
218
|
-
)
|
|
219
|
-
: current_messages;
|
|
220
|
-
const session = await this.build_info({
|
|
221
|
-
metadata,
|
|
222
|
-
messages: current_messages,
|
|
223
|
-
});
|
|
224
|
-
return buildSessionRecordsPage({
|
|
225
|
-
session,
|
|
226
|
-
messages: page_messages,
|
|
227
|
-
input,
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
188
|
/**
|
|
232
189
|
* 读取当前 session 生效的 system 快照。
|
|
233
190
|
*/
|
|
@@ -290,14 +247,11 @@ export class SessionViewService<TSession extends Pick<AgentSession, "set">> {
|
|
|
290
247
|
typeof input === "string"
|
|
291
248
|
? String(input || "").trim() || undefined
|
|
292
249
|
: String(input?.messageId || "").trim() || undefined;
|
|
293
|
-
const
|
|
294
|
-
limit: 500,
|
|
295
|
-
include_internal: true,
|
|
296
|
-
});
|
|
250
|
+
const messages = await this.recorder.list_history_messages();
|
|
297
251
|
const fork_messages =
|
|
298
252
|
!message_id
|
|
299
|
-
?
|
|
300
|
-
: this.resolve_fork_messages(
|
|
253
|
+
? messages
|
|
254
|
+
: this.resolve_fork_messages(messages, message_id);
|
|
301
255
|
const action_id = `history-forking:${this.session_id}:${Date.now()}:${nanoid(8)}`;
|
|
302
256
|
|
|
303
257
|
await this.state_service.emit_action_event({
|
|
@@ -88,7 +88,7 @@ export function getSdkAgentArchivedSessionMessagesDirPath(
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* 单个已归档 session
|
|
91
|
+
* 单个已归档 session 的 Active JSONL 文件路径。
|
|
92
92
|
*/
|
|
93
93
|
export function getSdkAgentArchivedSessionMessagesPath(
|
|
94
94
|
projectRoot: string,
|
|
@@ -97,7 +97,7 @@ export function getSdkAgentArchivedSessionMessagesPath(
|
|
|
97
97
|
): string {
|
|
98
98
|
return path.join(
|
|
99
99
|
getSdkAgentArchivedSessionMessagesDirPath(projectRoot, agentId, sessionId),
|
|
100
|
-
"
|
|
100
|
+
"active.jsonl",
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -144,7 +144,7 @@ export function getSdkAgentSessionMessagesDirPath(
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
|
-
* 单个 session
|
|
147
|
+
* 单个 session 的 Active JSONL 文件路径。
|
|
148
148
|
*/
|
|
149
149
|
export function getSdkAgentSessionMessagesPath(
|
|
150
150
|
projectRoot: string,
|
|
@@ -153,7 +153,7 @@ export function getSdkAgentSessionMessagesPath(
|
|
|
153
153
|
): string {
|
|
154
154
|
return path.join(
|
|
155
155
|
getSdkAgentSessionMessagesDirPath(projectRoot, agentId, sessionId),
|
|
156
|
-
"
|
|
156
|
+
"active.jsonl",
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -171,46 +171,13 @@ export function getSdkAgentSessionMetaPath(
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
/**
|
|
175
|
-
* 单个 session 的 archive 目录路径。
|
|
176
|
-
*/
|
|
177
|
-
export function getSdkAgentSessionArchiveDirPath(
|
|
178
|
-
projectRoot: string,
|
|
179
|
-
agentId: string,
|
|
180
|
-
sessionId: string,
|
|
181
|
-
): string {
|
|
182
|
-
return path.join(
|
|
183
|
-
getSdkAgentSessionMessagesDirPath(projectRoot, agentId, sessionId),
|
|
184
|
-
"archive",
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* 单个 session 的 compact archive JSON 文件路径。
|
|
190
|
-
*
|
|
191
|
-
* 关键点(中文)
|
|
192
|
-
* - `archiveId` 来自 compact summary metadata,不由调用方拼接扩展名。
|
|
193
|
-
* - 这里统一 encode,避免 archiveId 中的特殊字符影响文件路径。
|
|
194
|
-
*/
|
|
195
|
-
export function getSdkAgentSessionArchiveFilePath(
|
|
196
|
-
projectRoot: string,
|
|
197
|
-
agentId: string,
|
|
198
|
-
sessionId: string,
|
|
199
|
-
archiveId: string,
|
|
200
|
-
): string {
|
|
201
|
-
return path.join(
|
|
202
|
-
getSdkAgentSessionArchiveDirPath(projectRoot, agentId, sessionId),
|
|
203
|
-
`${encodeURIComponent(String(archiveId || "").trim())}.json`,
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
174
|
/**
|
|
208
175
|
* 单个 session 的 inflight assistant 路径。
|
|
209
176
|
*
|
|
210
177
|
* 关键点(中文)
|
|
211
178
|
* - 运行中的 assistant 只保留一份增量快照。
|
|
212
179
|
* - step / tool 过程会持续重写这个文件,避免中途中断后过程完全丢失。
|
|
213
|
-
* - 完成后再把最终 assistant 合并进 `
|
|
180
|
+
* - 完成后再把最终 assistant 合并进 `active.jsonl`,并清理该文件。
|
|
214
181
|
*/
|
|
215
182
|
export function getSdkAgentSessionAssistantMessagePath(
|
|
216
183
|
projectRoot: string,
|