@downcity/agent 1.1.315 → 1.1.320
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/executor/types/SessionHistoryMeta.d.ts +2 -0
- package/bin/executor/types/SessionHistoryMeta.d.ts.map +1 -1
- package/bin/index.d.ts +1 -1
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/remote/RemoteSession.d.ts +2 -2
- package/bin/remote/RemoteSession.d.ts.map +1 -1
- package/bin/remote/RemoteSession.js +2 -2
- package/bin/remote/RemoteSession.js.map +1 -1
- package/bin/remote/RemoteTransport.d.ts +2 -2
- package/bin/remote/RemoteTransport.d.ts.map +1 -1
- package/bin/remote/transports/HttpRemoteAgentTransport.d.ts +2 -2
- package/bin/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
- package/bin/remote/transports/HttpRemoteAgentTransport.js +2 -2
- package/bin/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
- package/bin/remote/transports/RpcClient.d.ts +2 -2
- package/bin/remote/transports/RpcClient.d.ts.map +1 -1
- package/bin/remote/transports/RpcClient.js +2 -2
- package/bin/remote/transports/RpcClient.js.map +1 -1
- package/bin/remote/transports/RpcRemoteAgentTransport.d.ts +2 -2
- package/bin/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
- package/bin/remote/transports/RpcRemoteAgentTransport.js +2 -2
- package/bin/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
- package/bin/session/Session.d.ts +4 -4
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +47 -27
- package/bin/session/Session.js.map +1 -1
- package/bin/session/SessionLoop.d.ts.map +1 -1
- package/bin/session/SessionLoop.js +4 -2
- package/bin/session/SessionLoop.js.map +1 -1
- package/bin/session/SessionMessages.d.ts +9 -2
- package/bin/session/SessionMessages.d.ts.map +1 -1
- package/bin/session/SessionMessages.js +63 -13
- package/bin/session/SessionMessages.js.map +1 -1
- package/bin/session/SessionState.d.ts +13 -1
- package/bin/session/SessionState.d.ts.map +1 -1
- package/bin/session/SessionState.js +28 -4
- package/bin/session/SessionState.js.map +1 -1
- package/bin/session/messages/SessionAssistantMessageWriter.d.ts.map +1 -1
- package/bin/session/messages/SessionAssistantMessageWriter.js +7 -6
- package/bin/session/messages/SessionAssistantMessageWriter.js.map +1 -1
- package/bin/session/storage/Metadata.d.ts.map +1 -1
- package/bin/session/storage/Metadata.js +7 -0
- package/bin/session/storage/Metadata.js.map +1 -1
- package/bin/types/agent/SessionActor.d.ts +3 -3
- package/bin/types/agent/SessionActor.d.ts.map +1 -1
- package/bin/types/agent/SessionTypes.d.ts +20 -0
- package/bin/types/agent/SessionTypes.d.ts.map +1 -1
- package/bin/types/rpc/RpcProtocol.d.ts +2 -1
- package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
- package/bin/types/session/SessionCommand.d.ts +2 -0
- package/bin/types/session/SessionCommand.d.ts.map +1 -1
- package/bin/types/session/SessionLocalState.d.ts +3 -0
- package/bin/types/session/SessionLocalState.d.ts.map +1 -1
- package/bin/types/session/SessionMessages.d.ts +2 -0
- package/bin/types/session/SessionMessages.d.ts.map +1 -1
- package/bin/types/session/SessionMutation.d.ts +17 -4
- package/bin/types/session/SessionMutation.d.ts.map +1 -1
- package/bin/types/session/SessionMutation.js.map +1 -1
- package/package.json +6 -6
- package/scripts/remote-session-reconnect.test.mjs +25 -0
- package/scripts/session-config-turn-boundary.test.mjs +185 -0
- package/scripts/session-messages.test.mjs +20 -0
- package/src/executor/types/SessionHistoryMeta.ts +2 -0
- package/src/index.ts +1 -0
- package/src/remote/RemoteSession.ts +6 -2
- package/src/remote/RemoteTransport.ts +6 -1
- package/src/remote/transports/HttpRemoteAgentTransport.ts +3 -1
- package/src/remote/transports/RpcClient.ts +3 -1
- package/src/remote/transports/RpcRemoteAgentTransport.ts +3 -1
- package/src/session/Session.ts +55 -28
- package/src/session/SessionLoop.ts +4 -1
- package/src/session/SessionMessages.ts +97 -13
- package/src/session/SessionState.ts +39 -5
- package/src/session/messages/SessionAssistantMessageWriter.ts +13 -6
- package/src/session/storage/Metadata.ts +10 -0
- package/src/types/agent/SessionActor.ts +3 -2
- package/src/types/agent/SessionTypes.ts +22 -0
- package/src/types/rpc/RpcProtocol.ts +6 -1
- package/src/types/session/SessionCommand.ts +2 -0
- package/src/types/session/SessionLocalState.ts +4 -0
- package/src/types/session/SessionMessages.ts +2 -0
- package/src/types/session/SessionMutation.ts +21 -4
- package/tsconfig.tsbuildinfo +1 -1
package/src/session/Session.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
AgentSessionInfo,
|
|
22
22
|
AgentSessionStatus,
|
|
23
23
|
AgentSessionSetInput,
|
|
24
|
+
AgentSessionSetOptions,
|
|
24
25
|
AgentSessionSystemBlock,
|
|
25
26
|
AgentSessionSystemSnapshot,
|
|
26
27
|
} from "@/types/agent/SessionTypes.js";
|
|
@@ -93,8 +94,6 @@ export class Session implements AgentSession {
|
|
|
93
94
|
private readonly events: SessionEventHub;
|
|
94
95
|
private readonly session_interactions: SessionInteractions;
|
|
95
96
|
private readonly shell_approval_adapter: SessionShellApprovalAdapter;
|
|
96
|
-
/** 已被 Session 接受、等待或已经在 Step 检查点提交的审批模式。 */
|
|
97
|
-
private configured_approval_mode: SessionApprovalMode = "ask";
|
|
98
97
|
private readonly local_state: SessionLocalState;
|
|
99
98
|
private readonly get_workspace_env: SessionOptions["get_workspace_env"];
|
|
100
99
|
private readonly get_agent_model: SessionOptions["get_agent_model"];
|
|
@@ -102,6 +101,8 @@ export class Session implements AgentSession {
|
|
|
102
101
|
private readonly get_instruction_system_blocks:
|
|
103
102
|
SessionOptions["get_instruction_system_blocks"];
|
|
104
103
|
private effective_instruction_system_blocks: AgentSessionSystemBlock[];
|
|
104
|
+
/** 当前 Session 的一次性初始化任务,避免缓存实例被重复恢复运行时状态。 */
|
|
105
|
+
private initialize_promise: Promise<void> | null = null;
|
|
105
106
|
private instruction_initialize_promise: Promise<void> | null = null;
|
|
106
107
|
private effective_workspace_env: Record<string, string>;
|
|
107
108
|
private effective_agent_plugins: AgentPluginExecutionRuntime;
|
|
@@ -199,11 +200,19 @@ export class Session implements AgentSession {
|
|
|
199
200
|
* 初始化当前 session。
|
|
200
201
|
*/
|
|
201
202
|
async initialize(): Promise<this> {
|
|
202
|
-
|
|
203
|
-
this.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
if (!this.initialize_promise) {
|
|
204
|
+
this.initialize_promise = (async () => {
|
|
205
|
+
await Promise.all([
|
|
206
|
+
this.initialize_instruction(),
|
|
207
|
+
this.session_messages.initialize(),
|
|
208
|
+
this.state.initialize(),
|
|
209
|
+
]);
|
|
210
|
+
this.shell_approval_adapter.set_effective_mode(
|
|
211
|
+
this.state.get_approval_mode(),
|
|
212
|
+
);
|
|
213
|
+
})();
|
|
214
|
+
}
|
|
215
|
+
await this.initialize_promise;
|
|
207
216
|
return this;
|
|
208
217
|
}
|
|
209
218
|
|
|
@@ -257,7 +266,10 @@ export class Session implements AgentSession {
|
|
|
257
266
|
/**
|
|
258
267
|
* 写入当前 session 默认配置。
|
|
259
268
|
*/
|
|
260
|
-
async set(
|
|
269
|
+
async set(
|
|
270
|
+
input: AgentSessionSetInput,
|
|
271
|
+
options?: AgentSessionSetOptions,
|
|
272
|
+
): Promise<void> {
|
|
261
273
|
if (!input.model && !input.security) {
|
|
262
274
|
throw new Error("session.set requires model or security");
|
|
263
275
|
}
|
|
@@ -269,38 +281,49 @@ export class Session implements AgentSession {
|
|
|
269
281
|
) {
|
|
270
282
|
throw new Error("security.approval_mode must be ask or always-allow");
|
|
271
283
|
}
|
|
272
|
-
const
|
|
284
|
+
const persist_action = options?.persist_action !== false;
|
|
285
|
+
const publish_mutation =
|
|
286
|
+
options?.publish_mutation === undefined
|
|
287
|
+
? persist_action
|
|
288
|
+
: options.publish_mutation;
|
|
289
|
+
if (!persist_action && publish_mutation) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
"session.set publish_mutation requires persist_action",
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
const model_result = input.model
|
|
273
295
|
? await this.state.set_model(input.model)
|
|
274
296
|
: undefined;
|
|
275
297
|
const next_approval_mode = requested_approval_mode;
|
|
276
|
-
const security_changed =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
this.configured_approval_mode = next_approval_mode;
|
|
281
|
-
}
|
|
282
|
-
if (!model_config && !security_changed) return;
|
|
298
|
+
const security_changed = next_approval_mode !== undefined
|
|
299
|
+
? await this.state.set_approval_mode(next_approval_mode)
|
|
300
|
+
: false;
|
|
301
|
+
if (!model_result && !security_changed) return;
|
|
283
302
|
const changed_fields = [
|
|
284
|
-
...(
|
|
285
|
-
? [`model: ${String(
|
|
303
|
+
...(model_result?.changed
|
|
304
|
+
? [`model: ${String(model_result.config.model_label || "configured")}`]
|
|
286
305
|
: []),
|
|
287
306
|
...(security_changed
|
|
288
307
|
? [`security.approval_mode: ${String(next_approval_mode)}`]
|
|
289
308
|
: []),
|
|
290
309
|
];
|
|
310
|
+
const completion = changed_fields.length > 0 && persist_action
|
|
311
|
+
? {
|
|
312
|
+
type: "action" as const,
|
|
313
|
+
id: `session-config:${this.id}:${Date.now()}:${generate_id()}`,
|
|
314
|
+
title: "Session configuration updated",
|
|
315
|
+
description: changed_fields.join("; "),
|
|
316
|
+
publish_mutation,
|
|
317
|
+
}
|
|
318
|
+
: undefined;
|
|
291
319
|
this.enqueue_command({
|
|
292
320
|
execute: async () => {
|
|
293
|
-
if (
|
|
321
|
+
if (model_result) this.state.apply_model_config(model_result.config);
|
|
294
322
|
if (security_changed && next_approval_mode) {
|
|
295
323
|
this.shell_approval_adapter.set_effective_mode(next_approval_mode);
|
|
296
324
|
}
|
|
297
325
|
},
|
|
298
|
-
completion: {
|
|
299
|
-
type: "action",
|
|
300
|
-
id: `session-config:${this.id}:${Date.now()}:${generate_id()}`,
|
|
301
|
-
title: "Session configuration updated",
|
|
302
|
-
description: changed_fields.join("; "),
|
|
303
|
-
},
|
|
326
|
+
...(completion ? { completion } : {}),
|
|
304
327
|
});
|
|
305
328
|
}
|
|
306
329
|
|
|
@@ -432,7 +455,7 @@ export class Session implements AgentSession {
|
|
|
432
455
|
state: active_turn_id || this.executor.is_executing() ? "running" : "idle",
|
|
433
456
|
...(active_turn_id ? { active_turn_id } : {}),
|
|
434
457
|
security: {
|
|
435
|
-
approval_mode: this.
|
|
458
|
+
approval_mode: this.state.get_approval_mode(),
|
|
436
459
|
effective_approval_mode: this.shell_approval_adapter.get_effective_mode(),
|
|
437
460
|
},
|
|
438
461
|
};
|
|
@@ -555,9 +578,12 @@ export class Session implements AgentSession {
|
|
|
555
578
|
await forked.initialize();
|
|
556
579
|
const session_config = this.state.get_config();
|
|
557
580
|
if (session_config.model) {
|
|
558
|
-
const
|
|
559
|
-
forked.state.apply_model_config(
|
|
581
|
+
const forked_model = await forked.state.set_model(session_config.model);
|
|
582
|
+
forked.state.apply_model_config(forked_model.config);
|
|
560
583
|
}
|
|
584
|
+
const approval_mode = this.state.get_approval_mode();
|
|
585
|
+
await forked.state.set_approval_mode(approval_mode);
|
|
586
|
+
forked.shell_approval_adapter.set_effective_mode(approval_mode);
|
|
561
587
|
await forked.session_messages.import_messages(fork_messages);
|
|
562
588
|
await this.emit_action_event({
|
|
563
589
|
id: action_id,
|
|
@@ -710,6 +736,7 @@ export class Session implements AgentSession {
|
|
|
710
736
|
return {
|
|
711
737
|
session_config: {},
|
|
712
738
|
effective_session_config: {},
|
|
739
|
+
configured_approval_mode: "ask",
|
|
713
740
|
created_at: Date.now(),
|
|
714
741
|
timezone: resolve_system_timezone(),
|
|
715
742
|
initialize_promise: null,
|
|
@@ -279,6 +279,8 @@ export class SessionLoop {
|
|
|
279
279
|
session_id: this.session_id,
|
|
280
280
|
turn_id,
|
|
281
281
|
},
|
|
282
|
+
}, {
|
|
283
|
+
publish_mutation: completion.publish_mutation !== false,
|
|
282
284
|
});
|
|
283
285
|
} catch (error) {
|
|
284
286
|
try {
|
|
@@ -599,8 +601,9 @@ export class SessionLoop {
|
|
|
599
601
|
/** 持久化 Executor Action,并同步刷新 Session metadata。 */
|
|
600
602
|
private async persist_action_event(
|
|
601
603
|
event: SessionActionRecordV1,
|
|
604
|
+
options?: { publish_mutation?: boolean },
|
|
602
605
|
): Promise<void> {
|
|
603
|
-
await this.messages.persist_action_record(event);
|
|
606
|
+
await this.messages.persist_action_record(event, options);
|
|
604
607
|
await this.state.touch_metadata();
|
|
605
608
|
}
|
|
606
609
|
|
|
@@ -315,7 +315,11 @@ export class SessionMessages {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
/** 按稳定 Action ID 创建或更新 canonical Action Message。 */
|
|
318
|
-
async persist_action_record(
|
|
318
|
+
async persist_action_record(
|
|
319
|
+
event: SessionActionRecordV1,
|
|
320
|
+
options?: { publish_mutation?: boolean },
|
|
321
|
+
): Promise<void> {
|
|
322
|
+
const publish_mutation = options?.publish_mutation !== false;
|
|
319
323
|
const existing = this.get_message(event.id);
|
|
320
324
|
if (!existing) {
|
|
321
325
|
const writer = await this.open_action_message({
|
|
@@ -324,6 +328,7 @@ export class SessionMessages {
|
|
|
324
328
|
action_type: infer_action_type(event.id),
|
|
325
329
|
title: event.title,
|
|
326
330
|
description: event.description,
|
|
331
|
+
publish_mutation,
|
|
327
332
|
});
|
|
328
333
|
if (event.state === "completed") await writer.complete();
|
|
329
334
|
if (event.state === "failed") {
|
|
@@ -335,7 +340,7 @@ export class SessionMessages {
|
|
|
335
340
|
await this.update_action_message(event.id, event.state, {
|
|
336
341
|
title: event.title,
|
|
337
342
|
description: event.description,
|
|
338
|
-
});
|
|
343
|
+
}, { publish_mutation });
|
|
339
344
|
}
|
|
340
345
|
}
|
|
341
346
|
|
|
@@ -360,8 +365,12 @@ export class SessionMessages {
|
|
|
360
365
|
title: input.title,
|
|
361
366
|
...(input.description ? { description: input.description } : {}),
|
|
362
367
|
...(input.data ? { data: structuredClone(input.data) } : {}),
|
|
363
|
-
}))) as SessionActionMessage;
|
|
364
|
-
return new SessionActionMessageWriter(
|
|
368
|
+
}), false, input.publish_mutation !== false)) as SessionActionMessage;
|
|
369
|
+
return new SessionActionMessageWriter(
|
|
370
|
+
this,
|
|
371
|
+
message.message_id,
|
|
372
|
+
input.publish_mutation !== false,
|
|
373
|
+
);
|
|
365
374
|
}
|
|
366
375
|
|
|
367
376
|
/** 更新 Action 状态,同时保持 message_id 与 sequence 不变。 */
|
|
@@ -369,6 +378,7 @@ export class SessionMessages {
|
|
|
369
378
|
message_id: string,
|
|
370
379
|
status: "running" | "completed" | "failed",
|
|
371
380
|
changes?: { title?: string; description?: string; data?: JsonObject },
|
|
381
|
+
options?: { publish_mutation?: boolean },
|
|
372
382
|
): Promise<SessionActionMessage> {
|
|
373
383
|
const message = await this.store.append_message((state) => {
|
|
374
384
|
const current = require_message(state.messages, message_id, "action");
|
|
@@ -385,7 +395,7 @@ export class SessionMessages {
|
|
|
385
395
|
updated_at: created_at,
|
|
386
396
|
} satisfies SessionActionMessage;
|
|
387
397
|
});
|
|
388
|
-
this.accept_message(message);
|
|
398
|
+
this.accept_message(message, options?.publish_mutation !== false);
|
|
389
399
|
return message as SessionActionMessage;
|
|
390
400
|
}
|
|
391
401
|
|
|
@@ -576,6 +586,59 @@ export class SessionMessages {
|
|
|
576
586
|
}, message);
|
|
577
587
|
}
|
|
578
588
|
|
|
589
|
+
/** @internal 写入 Assistant Tool 输入原始 delta。 */
|
|
590
|
+
async append_assistant_tool_input_delta(
|
|
591
|
+
message_id: string,
|
|
592
|
+
part_id: string,
|
|
593
|
+
tool_call_id: string,
|
|
594
|
+
delta: string,
|
|
595
|
+
): Promise<void> {
|
|
596
|
+
if (!delta) return;
|
|
597
|
+
await this.enqueue_assistant_write(message_id, async () => {
|
|
598
|
+
const current = require_message(
|
|
599
|
+
[...this.messages_by_id.values()],
|
|
600
|
+
message_id,
|
|
601
|
+
"assistant",
|
|
602
|
+
);
|
|
603
|
+
require_streaming_assistant(current);
|
|
604
|
+
const part = current.parts.find((item) => item.part_id === part_id);
|
|
605
|
+
if (!part || part.type !== "tool") {
|
|
606
|
+
throw new Error(`Tool input Delta target Part does not exist: ${part_id}`);
|
|
607
|
+
}
|
|
608
|
+
if (part.tool_call_id !== tool_call_id) {
|
|
609
|
+
throw new Error(`Tool input Delta tool_call_id changed for Part: ${part_id}`);
|
|
610
|
+
}
|
|
611
|
+
if (part.state !== "input-streaming") {
|
|
612
|
+
throw new Error(`Tool input Delta cannot update ${part.state} Part: ${part_id}`);
|
|
613
|
+
}
|
|
614
|
+
const created_at = Date.now();
|
|
615
|
+
const message: SessionAssistantMessage = {
|
|
616
|
+
...current,
|
|
617
|
+
revision: current.revision + 1,
|
|
618
|
+
updated_at: created_at,
|
|
619
|
+
parts: current.parts.map((item) =>
|
|
620
|
+
item.part_id === part_id && item.type === "tool"
|
|
621
|
+
? { ...item, input_text: `${item.input_text || ""}${delta}` }
|
|
622
|
+
: item,
|
|
623
|
+
),
|
|
624
|
+
};
|
|
625
|
+
await this.store.write_assistant_message(message);
|
|
626
|
+
this.accept_mutation({
|
|
627
|
+
mutation_id: generate_id(),
|
|
628
|
+
variant: "delta",
|
|
629
|
+
type: "tool_input",
|
|
630
|
+
message_id,
|
|
631
|
+
revision: message.revision,
|
|
632
|
+
session_id: this.session_id,
|
|
633
|
+
turn_id: message.turn_id,
|
|
634
|
+
created_at,
|
|
635
|
+
part_id,
|
|
636
|
+
tool_call_id,
|
|
637
|
+
delta,
|
|
638
|
+
}, message);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
579
642
|
/** @internal 写入 Assistant 完整 part。 */
|
|
580
643
|
async update_assistant_part(
|
|
581
644
|
message_id: string,
|
|
@@ -723,6 +786,7 @@ export class SessionMessages {
|
|
|
723
786
|
private async create_message(
|
|
724
787
|
factory: (sequence: number, created_at: number) => SessionMessage,
|
|
725
788
|
draft = false,
|
|
789
|
+
publish_mutation = true,
|
|
726
790
|
): Promise<SessionMessage> {
|
|
727
791
|
await this.ensure_initialized();
|
|
728
792
|
if (draft) {
|
|
@@ -733,13 +797,13 @@ export class SessionMessages {
|
|
|
733
797
|
}
|
|
734
798
|
return candidate;
|
|
735
799
|
});
|
|
736
|
-
this.accept_message(message);
|
|
800
|
+
this.accept_message(message, publish_mutation);
|
|
737
801
|
return message;
|
|
738
802
|
}
|
|
739
803
|
const message = await this.store.append_message((state) =>
|
|
740
804
|
factory(state.message_sequence, Date.now()),
|
|
741
805
|
);
|
|
742
|
-
this.accept_message(message);
|
|
806
|
+
this.accept_message(message, publish_mutation);
|
|
743
807
|
return message;
|
|
744
808
|
}
|
|
745
809
|
|
|
@@ -870,7 +934,11 @@ export class SessionMessages {
|
|
|
870
934
|
} as SessionMessageSnapshotMutation;
|
|
871
935
|
}
|
|
872
936
|
|
|
873
|
-
private accept_message(message: SessionMessage): void {
|
|
937
|
+
private accept_message(message: SessionMessage, publish_mutation = true): void {
|
|
938
|
+
if (!publish_mutation) {
|
|
939
|
+
this.messages_by_id.set(message.message_id, structuredClone(message));
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
874
942
|
this.accept_mutation(this.build_message_mutation(message), message);
|
|
875
943
|
}
|
|
876
944
|
|
|
@@ -888,26 +956,42 @@ export class SessionMessages {
|
|
|
888
956
|
export class SessionActionMessageWriter {
|
|
889
957
|
readonly message_id: string;
|
|
890
958
|
private readonly messages: SessionMessages;
|
|
959
|
+
private readonly publish_mutation: boolean;
|
|
891
960
|
private closed = false;
|
|
892
961
|
|
|
893
|
-
constructor(
|
|
962
|
+
constructor(
|
|
963
|
+
messages: SessionMessages,
|
|
964
|
+
message_id: string,
|
|
965
|
+
publish_mutation = true,
|
|
966
|
+
) {
|
|
894
967
|
this.messages = messages;
|
|
895
968
|
this.message_id = message_id;
|
|
969
|
+
this.publish_mutation = publish_mutation;
|
|
896
970
|
}
|
|
897
971
|
|
|
898
972
|
/** 把 Action 更新为 completed。 */
|
|
899
973
|
async complete(input?: { title?: string; description?: string; data?: JsonObject }): Promise<void> {
|
|
900
974
|
if (this.closed) return;
|
|
901
|
-
await this.messages.update_action_message(
|
|
975
|
+
await this.messages.update_action_message(
|
|
976
|
+
this.message_id,
|
|
977
|
+
"completed",
|
|
978
|
+
input,
|
|
979
|
+
{ publish_mutation: this.publish_mutation },
|
|
980
|
+
);
|
|
902
981
|
this.closed = true;
|
|
903
982
|
}
|
|
904
983
|
|
|
905
984
|
/** 把 Action 更新为 failed。 */
|
|
906
985
|
async fail(error: unknown): Promise<void> {
|
|
907
986
|
if (this.closed) return;
|
|
908
|
-
await this.messages.update_action_message(
|
|
909
|
-
|
|
910
|
-
|
|
987
|
+
await this.messages.update_action_message(
|
|
988
|
+
this.message_id,
|
|
989
|
+
"failed",
|
|
990
|
+
{
|
|
991
|
+
description: error instanceof Error ? error.message : String(error),
|
|
992
|
+
},
|
|
993
|
+
{ publish_mutation: this.publish_mutation },
|
|
994
|
+
);
|
|
911
995
|
this.closed = true;
|
|
912
996
|
}
|
|
913
997
|
}
|
|
@@ -25,6 +25,15 @@ import { to_executor_history } from "@/session/messages/SessionMessageCodec.js";
|
|
|
25
25
|
import type { SessionMessage } from "@/types/session/SessionMessage.js";
|
|
26
26
|
import type { SessionStateOptions } from "@/types/session/SessionState.js";
|
|
27
27
|
import type { SessionStore } from "@/types/store/SessionStore.js";
|
|
28
|
+
import type { SessionApprovalMode } from "@/types/session/SessionInteraction.js";
|
|
29
|
+
|
|
30
|
+
/** Session 模型配置写入结果。 */
|
|
31
|
+
export interface SessionModelSetResult {
|
|
32
|
+
/** 已包含最新运行时模型实例的 configured 快照。 */
|
|
33
|
+
config: AgentSessionConfigSnapshot;
|
|
34
|
+
/** 持久化模型身份是否发生真实变化。 */
|
|
35
|
+
changed: boolean;
|
|
36
|
+
}
|
|
28
37
|
|
|
29
38
|
/**
|
|
30
39
|
* 本地 Session 配置与 Metadata 状态管理器。
|
|
@@ -61,6 +70,11 @@ export class SessionState {
|
|
|
61
70
|
};
|
|
62
71
|
}
|
|
63
72
|
|
|
73
|
+
/** 读取当前 Session 已接受的 Shell 审批模式。 */
|
|
74
|
+
get_approval_mode(): SessionApprovalMode {
|
|
75
|
+
return this.state.configured_approval_mode;
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
/**
|
|
65
79
|
* 读取当前 session 创建时间。
|
|
66
80
|
*/
|
|
@@ -99,10 +113,13 @@ export class SessionState {
|
|
|
99
113
|
});
|
|
100
114
|
this.state.created_at = created_at;
|
|
101
115
|
this.state.timezone = timezone;
|
|
102
|
-
this.state.session_config = {
|
|
116
|
+
this.state.session_config = {
|
|
117
|
+
...(metadata.model_label ? { model_label: metadata.model_label } : {}),
|
|
118
|
+
};
|
|
103
119
|
this.state.effective_session_config = {
|
|
104
120
|
...this.state.session_config,
|
|
105
121
|
};
|
|
122
|
+
this.state.configured_approval_mode = metadata.approval_mode || "ask";
|
|
106
123
|
})();
|
|
107
124
|
await this.state.initialize_promise;
|
|
108
125
|
}
|
|
@@ -141,23 +158,40 @@ export class SessionState {
|
|
|
141
158
|
/**
|
|
142
159
|
* 写入当前 session 配置。
|
|
143
160
|
*/
|
|
144
|
-
async set_model(model: AgentModel): Promise<
|
|
161
|
+
async set_model(model: AgentModel): Promise<SessionModelSetResult> {
|
|
145
162
|
const next_model_label = infer_agent_model_label(model);
|
|
163
|
+
const changed = next_model_label !== this.state.session_config.model_label;
|
|
146
164
|
const next_config: AgentSessionConfigSnapshot = {
|
|
147
165
|
...this.state.session_config,
|
|
148
166
|
model,
|
|
149
167
|
model_label: next_model_label,
|
|
150
168
|
model_context_window: read_agent_model_context_window(model),
|
|
151
169
|
};
|
|
170
|
+
if (changed) {
|
|
171
|
+
const metadata = await this.store.read_metadata();
|
|
172
|
+
await this.store.write_metadata({
|
|
173
|
+
...metadata,
|
|
174
|
+
agent_id: this.agent_id,
|
|
175
|
+
updated_at: Date.now(),
|
|
176
|
+
...(next_model_label ? { model_label: next_model_label } : {}),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
this.state.session_config = next_config;
|
|
180
|
+
return { config: next_config, changed };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 接受并持久化当前 Session 的 Shell 审批模式。 */
|
|
184
|
+
async set_approval_mode(mode: SessionApprovalMode): Promise<boolean> {
|
|
185
|
+
if (mode === this.state.configured_approval_mode) return false;
|
|
152
186
|
const metadata = await this.store.read_metadata();
|
|
153
187
|
await this.store.write_metadata({
|
|
154
188
|
...metadata,
|
|
155
189
|
agent_id: this.agent_id,
|
|
156
190
|
updated_at: Date.now(),
|
|
157
|
-
|
|
191
|
+
approval_mode: mode,
|
|
158
192
|
});
|
|
159
|
-
this.state.
|
|
160
|
-
return
|
|
193
|
+
this.state.configured_approval_mode = mode;
|
|
194
|
+
return true;
|
|
161
195
|
}
|
|
162
196
|
|
|
163
197
|
/** 在 Session Step 检查点提交模型配置。 */
|
|
@@ -217,13 +217,20 @@ export class SessionAssistantMessageWriter {
|
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
219
|
case "tool-input-delta": {
|
|
220
|
+
if (chunk.inputTextDelta.length === 0) return;
|
|
220
221
|
const tool = this.find_tool(chunk.toolCallId);
|
|
221
|
-
if (tool
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
if (!tool) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Assistant canonical Tool Part not found: ${chunk.toolCallId}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (tool.state !== "input-streaming") return;
|
|
228
|
+
await this.recorder.append_assistant_tool_input_delta(
|
|
229
|
+
this.message_id,
|
|
230
|
+
tool.part_id,
|
|
231
|
+
chunk.toolCallId,
|
|
232
|
+
chunk.inputTextDelta,
|
|
233
|
+
);
|
|
227
234
|
return;
|
|
228
235
|
}
|
|
229
236
|
case "tool-input-available": {
|
|
@@ -14,6 +14,13 @@ function normalizeModelLabel(input: unknown): string | undefined {
|
|
|
14
14
|
return label || undefined;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** 归一化 Session Shell 审批模式。 */
|
|
18
|
+
function normalize_approval_mode(
|
|
19
|
+
input: unknown,
|
|
20
|
+
): "ask" | "always-allow" | undefined {
|
|
21
|
+
return input === "ask" || input === "always-allow" ? input : undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* 归一化 session 标题。
|
|
19
26
|
*/
|
|
@@ -106,6 +113,9 @@ export function normalize_session_metadata(
|
|
|
106
113
|
...(normalizeModelLabel(raw.model_label)
|
|
107
114
|
? { model_label: normalizeModelLabel(raw.model_label) }
|
|
108
115
|
: {}),
|
|
116
|
+
...(normalize_approval_mode(raw.approval_mode)
|
|
117
|
+
? { approval_mode: normalize_approval_mode(raw.approval_mode) }
|
|
118
|
+
: {}),
|
|
109
119
|
...(normalize_message_count(raw.message_count) !== undefined
|
|
110
120
|
? { message_count: normalize_message_count(raw.message_count) }
|
|
111
121
|
: {}),
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
AgentSessionForkInput,
|
|
19
19
|
AgentSessionInfo,
|
|
20
20
|
AgentSessionSetInput,
|
|
21
|
+
AgentSessionSetOptions,
|
|
21
22
|
AgentSessionStatus,
|
|
22
23
|
AgentSessionSummaryPage,
|
|
23
24
|
AgentSessionSystemSnapshot,
|
|
@@ -121,7 +122,7 @@ export interface AgentSession extends AgentSessionActor {
|
|
|
121
122
|
readonly config: AgentSessionConfigSnapshot;
|
|
122
123
|
|
|
123
124
|
/** 写入当前 session 默认配置。 */
|
|
124
|
-
set(input: AgentSessionSetInput): Promise<void>;
|
|
125
|
+
set(input: AgentSessionSetInput, options?: AgentSessionSetOptions): Promise<void>;
|
|
125
126
|
|
|
126
127
|
/** 把当前 Session 首次生成后固定的完整 system 显式固化到 instruction.md。 */
|
|
127
128
|
snapshot(): Promise<void>;
|
|
@@ -138,7 +139,7 @@ export interface AgentSession extends AgentSessionActor {
|
|
|
138
139
|
*/
|
|
139
140
|
export interface RemoteAgentSession extends AgentSessionActor {
|
|
140
141
|
/** 写入当前远程 Session 的可序列化动态配置。 */
|
|
141
|
-
set(input: RemoteSessionSetInput): Promise<void>;
|
|
142
|
+
set(input: RemoteSessionSetInput, options?: AgentSessionSetOptions): Promise<void>;
|
|
142
143
|
|
|
143
144
|
/** 从当前远程 session 创建一个分叉会话。 */
|
|
144
145
|
fork(input?: AgentSessionForkInput | string): Promise<RemoteAgentSession>;
|
|
@@ -80,6 +80,28 @@ export interface AgentSessionSetInput {
|
|
|
80
80
|
security?: AgentSessionSecurityConfig;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/** Session 配置提交后的 Action 可观测性选项。 */
|
|
84
|
+
export interface AgentSessionSetOptions {
|
|
85
|
+
/**
|
|
86
|
+
* 是否把真实配置变化持久化为 completed Action Message。
|
|
87
|
+
*
|
|
88
|
+
* 关键点(中文)
|
|
89
|
+
* - 默认值为 true。
|
|
90
|
+
* - 设为 false 只关闭 Action Message,不影响配置写入与检查点提交。
|
|
91
|
+
* - 适用于 Session 初始化、恢复与其他不应进入时间线的宿主装配流程。
|
|
92
|
+
*/
|
|
93
|
+
persist_action?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 是否在 Action Message 持久化成功后发布对应 Session Mutation。
|
|
97
|
+
*
|
|
98
|
+
* 关键点(中文)
|
|
99
|
+
* - 默认跟随 `persist_action`。
|
|
100
|
+
* - 只有 `persist_action=true` 时才允许设为 true,避免发布不存在于 canonical Message 历史中的事件。
|
|
101
|
+
*/
|
|
102
|
+
publish_mutation?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
83
105
|
/** 可通过本地或远程 Session 动态更新的安全策略。 */
|
|
84
106
|
export interface AgentSessionSecurityConfig {
|
|
85
107
|
/** 高风险操作是否需要用户逐次审批。 */
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
AgentArchiveSessionResult,
|
|
16
16
|
AgentArchiveSessionsResult,
|
|
17
17
|
AgentCleanArchiveResult,
|
|
18
|
+
AgentSessionSetOptions,
|
|
18
19
|
RemoteSessionSetInput,
|
|
19
20
|
AgentSessionSystemSnapshot,
|
|
20
21
|
} from "@/types/agent/SessionTypes.js";
|
|
@@ -155,7 +156,11 @@ export type RpcRequest =
|
|
|
155
156
|
/** 更新指定 Session 的可序列化动态配置。 */
|
|
156
157
|
method: "sdk.sessions.set";
|
|
157
158
|
/** Session 与动态配置参数。 */
|
|
158
|
-
params: {
|
|
159
|
+
params: {
|
|
160
|
+
session_id: string;
|
|
161
|
+
input: RemoteSessionSetInput;
|
|
162
|
+
options?: AgentSessionSetOptions;
|
|
163
|
+
};
|
|
159
164
|
}
|
|
160
165
|
| {
|
|
161
166
|
/** 请求 id,用于匹配响应。 */
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { AgentSessionConfigSnapshot } from "@/types/agent/SessionTypes.js";
|
|
11
|
+
import type { SessionApprovalMode } from "@/types/session/SessionInteraction.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* 本地 Session 内存状态。
|
|
@@ -27,6 +28,9 @@ export interface SessionLocalState {
|
|
|
27
28
|
*/
|
|
28
29
|
effective_session_config: AgentSessionConfigSnapshot;
|
|
29
30
|
|
|
31
|
+
/** 当前 Session 已接受并持久化的 Shell 审批模式。 */
|
|
32
|
+
configured_approval_mode: SessionApprovalMode;
|
|
33
|
+
|
|
30
34
|
/**
|
|
31
35
|
* 当前 session 创建时间(毫秒时间戳)。
|
|
32
36
|
*/
|
|
@@ -56,12 +56,10 @@ export type SessionPartMutation = {
|
|
|
56
56
|
};
|
|
57
57
|
}[SessionAssistantMessagePart["type"]];
|
|
58
58
|
|
|
59
|
-
/** Assistant
|
|
60
|
-
|
|
59
|
+
/** Assistant Part 原始增量 Mutation 的公共字段。 */
|
|
60
|
+
interface SessionDeltaMutationBase extends SessionMutationBase {
|
|
61
61
|
/** Mutation 层级固定为 delta。 */
|
|
62
62
|
variant: "delta";
|
|
63
|
-
/** Delta 只允许可见文本或推理文本。 */
|
|
64
|
-
type: "text" | "reasoning";
|
|
65
63
|
/** 当前 Delta 所属 Message 标识。 */
|
|
66
64
|
message_id: string;
|
|
67
65
|
/** 当前 Mutation 所属 Turn 标识。 */
|
|
@@ -74,6 +72,25 @@ export interface SessionDeltaMutation extends SessionMutationBase {
|
|
|
74
72
|
delta: string;
|
|
75
73
|
}
|
|
76
74
|
|
|
75
|
+
/** Assistant 文本或推理的原始增量 Mutation。 */
|
|
76
|
+
interface SessionTextDeltaMutation extends SessionDeltaMutationBase {
|
|
77
|
+
/** Delta 属于可见文本或推理文本。 */
|
|
78
|
+
type: "text" | "reasoning";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Assistant Tool 输入的原始增量 Mutation。 */
|
|
82
|
+
interface SessionToolInputDeltaMutation extends SessionDeltaMutationBase {
|
|
83
|
+
/** Delta 属于 Tool 输入参数原文。 */
|
|
84
|
+
type: "tool_input";
|
|
85
|
+
/** 当前 Delta 所属 Tool Call 标识。 */
|
|
86
|
+
tool_call_id: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Assistant 文本、推理或 Tool 输入的原始增量 Mutation。 */
|
|
90
|
+
export type SessionDeltaMutation =
|
|
91
|
+
| SessionTextDeltaMutation
|
|
92
|
+
| SessionToolInputDeltaMutation;
|
|
93
|
+
|
|
77
94
|
/** Turn 生命周期 Mutation。 */
|
|
78
95
|
export type SessionTurnMutation = SessionMutationBase & {
|
|
79
96
|
/** Mutation 层级固定为 turn。 */
|