@downcity/agent 1.1.317 → 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 +7 -2
- package/bin/session/SessionMessages.d.ts.map +1 -1
- package/bin/session/SessionMessages.js +21 -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/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/package.json +6 -6
- package/scripts/remote-session-reconnect.test.mjs +25 -0
- package/scripts/session-config-turn-boundary.test.mjs +185 -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 +44 -13
- package/src/session/SessionState.ts +39 -5
- 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/tsconfig.tsbuildinfo +1 -1
|
@@ -600,6 +600,18 @@ test("running session approval mode changes stay queued until the next Session s
|
|
|
600
600
|
},
|
|
601
601
|
});
|
|
602
602
|
|
|
603
|
+
const cached_session = await agent.sessions.get(session.id);
|
|
604
|
+
assert.equal(cached_session, session);
|
|
605
|
+
assert.deepEqual(await cached_session.status(), {
|
|
606
|
+
session_id: session.id,
|
|
607
|
+
state: "running",
|
|
608
|
+
active_turn_id: first_turn.id,
|
|
609
|
+
security: {
|
|
610
|
+
approval_mode: "always-allow",
|
|
611
|
+
effective_approval_mode: "ask",
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
|
|
603
615
|
release_first_provider_request.resolve();
|
|
604
616
|
assert.equal((await first_turn.finished).success, true);
|
|
605
617
|
assert.deepEqual(await session.status(), {
|
|
@@ -639,6 +651,179 @@ test("running session approval mode changes stay queued until the next Session s
|
|
|
639
651
|
}
|
|
640
652
|
});
|
|
641
653
|
|
|
654
|
+
test("session set options independently control Action persistence and Mutation publication", async () => {
|
|
655
|
+
const agent_path = await fs.mkdtemp(
|
|
656
|
+
path.join(os.tmpdir(), "downcity-session-set-options-"),
|
|
657
|
+
);
|
|
658
|
+
const first_model = new MockLanguageModelV3({
|
|
659
|
+
modelId: "set-options-first-model",
|
|
660
|
+
doStream: async () => create_stream_text_result("first"),
|
|
661
|
+
});
|
|
662
|
+
const second_model = new MockLanguageModelV3({
|
|
663
|
+
modelId: "set-options-second-model",
|
|
664
|
+
doStream: async () => create_stream_text_result("second"),
|
|
665
|
+
});
|
|
666
|
+
const agent = new Agent({
|
|
667
|
+
id: "session_set_options_agent",
|
|
668
|
+
workspace: new Workspace({ path: agent_path }),
|
|
669
|
+
model: first_model,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
const session = await agent.sessions.create({
|
|
674
|
+
session_id: "session_set_options_session",
|
|
675
|
+
});
|
|
676
|
+
const mutations = [];
|
|
677
|
+
const unsubscribe = session.subscribe((mutation) => {
|
|
678
|
+
mutations.push(mutation);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
await session.set(
|
|
682
|
+
{ model: first_model },
|
|
683
|
+
{ persist_action: false, publish_mutation: false },
|
|
684
|
+
);
|
|
685
|
+
assert.equal((await (await session.prompt({ query: "first" })).finished).success, true);
|
|
686
|
+
assert.equal(
|
|
687
|
+
(await session.messages()).items.some(
|
|
688
|
+
(message) =>
|
|
689
|
+
message.type === "action" &&
|
|
690
|
+
message.title === "Session configuration updated",
|
|
691
|
+
),
|
|
692
|
+
false,
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
mutations.splice(0, mutations.length);
|
|
696
|
+
await session.set(
|
|
697
|
+
{ model: second_model },
|
|
698
|
+
{ publish_mutation: false },
|
|
699
|
+
);
|
|
700
|
+
assert.equal((await (await session.prompt({ query: "second" })).finished).success, true);
|
|
701
|
+
assert.equal(
|
|
702
|
+
(await session.messages()).items.some(
|
|
703
|
+
(message) =>
|
|
704
|
+
message.type === "action" &&
|
|
705
|
+
message.description === "model: set-options-second-model",
|
|
706
|
+
),
|
|
707
|
+
true,
|
|
708
|
+
);
|
|
709
|
+
assert.equal(
|
|
710
|
+
mutations.some(
|
|
711
|
+
(mutation) =>
|
|
712
|
+
mutation.variant === "message" &&
|
|
713
|
+
mutation.type === "action",
|
|
714
|
+
),
|
|
715
|
+
false,
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
const before_duplicate_count = (await session.messages()).items.filter(
|
|
719
|
+
(message) =>
|
|
720
|
+
message.type === "action" &&
|
|
721
|
+
message.title === "Session configuration updated",
|
|
722
|
+
).length;
|
|
723
|
+
await session.set({ model: second_model });
|
|
724
|
+
assert.equal((await (await session.prompt({ query: "duplicate" })).finished).success, true);
|
|
725
|
+
assert.equal(
|
|
726
|
+
(await session.messages()).items.filter(
|
|
727
|
+
(message) =>
|
|
728
|
+
message.type === "action" &&
|
|
729
|
+
message.title === "Session configuration updated",
|
|
730
|
+
).length,
|
|
731
|
+
before_duplicate_count,
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
await assert.rejects(
|
|
735
|
+
session.set(
|
|
736
|
+
{ security: { approval_mode: "always-allow" } },
|
|
737
|
+
{ persist_action: false, publish_mutation: true },
|
|
738
|
+
),
|
|
739
|
+
/publish_mutation requires persist_action/,
|
|
740
|
+
);
|
|
741
|
+
unsubscribe();
|
|
742
|
+
} finally {
|
|
743
|
+
await agent.dispose();
|
|
744
|
+
await fs.rm(agent_path, { recursive: true, force: true });
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test("restored Session rebinds the same model without emitting a configuration Mutation", async () => {
|
|
749
|
+
const agent_path = await fs.mkdtemp(
|
|
750
|
+
path.join(os.tmpdir(), "downcity-session-config-restore-"),
|
|
751
|
+
);
|
|
752
|
+
const create_model = () => new MockLanguageModelV3({
|
|
753
|
+
modelId: "restored-session-model",
|
|
754
|
+
doStream: async () => create_stream_text_result("restored"),
|
|
755
|
+
});
|
|
756
|
+
const first_agent = new Agent({
|
|
757
|
+
id: "session_config_restore_agent",
|
|
758
|
+
workspace: new Workspace({ path: agent_path }),
|
|
759
|
+
model: create_model(),
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
try {
|
|
763
|
+
const session = await first_agent.sessions.create({
|
|
764
|
+
session_id: "session_config_restore_session",
|
|
765
|
+
});
|
|
766
|
+
await session.set({
|
|
767
|
+
model: create_model(),
|
|
768
|
+
security: { approval_mode: "always-allow" },
|
|
769
|
+
});
|
|
770
|
+
assert.equal((await (await session.prompt({ query: "persist" })).finished).success, true);
|
|
771
|
+
} finally {
|
|
772
|
+
await first_agent.dispose();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const restored_agent = new Agent({
|
|
776
|
+
id: "session_config_restore_agent",
|
|
777
|
+
workspace: new Workspace({ path: agent_path }),
|
|
778
|
+
model: create_model(),
|
|
779
|
+
});
|
|
780
|
+
try {
|
|
781
|
+
const session = await restored_agent.sessions.get(
|
|
782
|
+
"session_config_restore_session",
|
|
783
|
+
);
|
|
784
|
+
assert.deepEqual((await session.status()).security, {
|
|
785
|
+
approval_mode: "always-allow",
|
|
786
|
+
effective_approval_mode: "always-allow",
|
|
787
|
+
});
|
|
788
|
+
const before_action_count = (await session.messages()).items.filter(
|
|
789
|
+
(message) =>
|
|
790
|
+
message.type === "action" &&
|
|
791
|
+
message.title === "Session configuration updated",
|
|
792
|
+
).length;
|
|
793
|
+
const mutations = [];
|
|
794
|
+
const unsubscribe = session.subscribe((mutation) => {
|
|
795
|
+
mutations.push(mutation);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
await session.set({
|
|
799
|
+
model: create_model(),
|
|
800
|
+
security: { approval_mode: "always-allow" },
|
|
801
|
+
});
|
|
802
|
+
assert.equal((await (await session.prompt({ query: "restore" })).finished).success, true);
|
|
803
|
+
assert.equal(
|
|
804
|
+
(await session.messages()).items.filter(
|
|
805
|
+
(message) =>
|
|
806
|
+
message.type === "action" &&
|
|
807
|
+
message.title === "Session configuration updated",
|
|
808
|
+
).length,
|
|
809
|
+
before_action_count,
|
|
810
|
+
);
|
|
811
|
+
assert.equal(
|
|
812
|
+
mutations.some(
|
|
813
|
+
(mutation) =>
|
|
814
|
+
mutation.variant === "message" &&
|
|
815
|
+
mutation.type === "action" &&
|
|
816
|
+
mutation.message.title === "Session configuration updated",
|
|
817
|
+
),
|
|
818
|
+
false,
|
|
819
|
+
);
|
|
820
|
+
unsubscribe();
|
|
821
|
+
} finally {
|
|
822
|
+
await restored_agent.dispose();
|
|
823
|
+
await fs.rm(agent_path, { recursive: true, force: true });
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
|
|
642
827
|
test("config remains effective when its action message cannot be persisted", async () => {
|
|
643
828
|
const agent_path = await fs.mkdtemp(
|
|
644
829
|
path.join(os.tmpdir(), "downcity-config-action-observability-"),
|
|
@@ -23,6 +23,8 @@ export type SessionHistoryMetaV1 = {
|
|
|
23
23
|
title?: string;
|
|
24
24
|
/** 当前 session 绑定模型的可读标签。 */
|
|
25
25
|
model_label?: string;
|
|
26
|
+
/** 当前 Session 持久化的 Shell 审批模式。 */
|
|
27
|
+
approval_mode?: "ask" | "always-allow";
|
|
26
28
|
/** 当前 session 已持久化记录数量,用于列表查询避免扫描完整历史。 */
|
|
27
29
|
message_count?: number;
|
|
28
30
|
/** 当前 session 最后一条记录的用户可见摘要,用于列表预览。 */
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type {
|
|
8
8
|
AgentSessionConfigSnapshot,
|
|
9
|
+
AgentSessionSetOptions,
|
|
9
10
|
AgentSessionForkInput,
|
|
10
11
|
AgentSessionInfo,
|
|
11
12
|
AgentSessionStatus,
|
|
@@ -159,8 +160,11 @@ export class RemoteSession implements RemoteAgentSession {
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
/** 更新当前远程 Session 的可序列化动态配置。 */
|
|
162
|
-
async set(
|
|
163
|
-
|
|
163
|
+
async set(
|
|
164
|
+
input: RemoteSessionSetInput,
|
|
165
|
+
options?: AgentSessionSetOptions,
|
|
166
|
+
): Promise<void> {
|
|
167
|
+
await this.transport.set(this.id, input, options);
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
/** 提交当前远程 Session 的 Interaction 用户响应。 */
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
AgentCleanArchiveResult,
|
|
17
17
|
AgentSessionForkInput,
|
|
18
18
|
AgentSessionInfo,
|
|
19
|
+
AgentSessionSetOptions,
|
|
19
20
|
AgentSessionStatus,
|
|
20
21
|
AgentSessionSummaryPage,
|
|
21
22
|
AgentSessionSystemSnapshot,
|
|
@@ -86,7 +87,11 @@ export type RemoteSessionTransport = {
|
|
|
86
87
|
/** 读取指定 Session 的运行与安全状态。 */
|
|
87
88
|
status(session_id: string): Promise<AgentSessionStatus>;
|
|
88
89
|
/** 更新指定 Session 的可序列化动态配置。 */
|
|
89
|
-
set(
|
|
90
|
+
set(
|
|
91
|
+
session_id: string,
|
|
92
|
+
input: RemoteSessionSetInput,
|
|
93
|
+
options?: AgentSessionSetOptions,
|
|
94
|
+
): Promise<void>;
|
|
90
95
|
/** 提交指定 Session 的 Interaction 用户响应。 */
|
|
91
96
|
respond(
|
|
92
97
|
session_id: string,
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
AgentCleanArchiveResult,
|
|
17
17
|
AgentSessionForkInput,
|
|
18
18
|
AgentSessionInfo,
|
|
19
|
+
AgentSessionSetOptions,
|
|
19
20
|
AgentSessionStatus,
|
|
20
21
|
AgentSessionSummaryPage,
|
|
21
22
|
AgentSessionSystemSnapshot,
|
|
@@ -427,6 +428,7 @@ export class HttpRemoteAgentTransport implements RemoteAgentTransport {
|
|
|
427
428
|
async set(
|
|
428
429
|
session_id: string,
|
|
429
430
|
input: RemoteSessionSetInput,
|
|
431
|
+
options?: AgentSessionSetOptions,
|
|
430
432
|
): Promise<void> {
|
|
431
433
|
const payload = await read_http_json<{
|
|
432
434
|
success?: boolean;
|
|
@@ -437,7 +439,7 @@ export class HttpRemoteAgentTransport implements RemoteAgentTransport {
|
|
|
437
439
|
headers: this.headers({
|
|
438
440
|
"Content-Type": "application/json",
|
|
439
441
|
}),
|
|
440
|
-
body: JSON.stringify(input),
|
|
442
|
+
body: JSON.stringify({ ...input, ...(options ? { options } : {}) }),
|
|
441
443
|
});
|
|
442
444
|
if (payload.success !== true || payload.queued !== true) {
|
|
443
445
|
throw new Error(String(payload.error || "Remote session set failed"));
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
AgentArchiveSessionsResult,
|
|
18
18
|
AgentCleanArchiveResult,
|
|
19
19
|
AgentSessionInfo,
|
|
20
|
+
AgentSessionSetOptions,
|
|
20
21
|
AgentSessionStatus,
|
|
21
22
|
AgentSessionSummaryPage,
|
|
22
23
|
AgentSessionSystemSnapshot,
|
|
@@ -257,10 +258,11 @@ export class RpcClient {
|
|
|
257
258
|
async set_session(
|
|
258
259
|
session_id: string,
|
|
259
260
|
input: RemoteSessionSetInput,
|
|
261
|
+
options?: AgentSessionSetOptions,
|
|
260
262
|
): Promise<void> {
|
|
261
263
|
await this.request<{ queued: true }>({
|
|
262
264
|
method: "sdk.sessions.set",
|
|
263
|
-
params: { session_id: session_id, input },
|
|
265
|
+
params: { session_id: session_id, input, ...(options ? { options } : {}) },
|
|
264
266
|
});
|
|
265
267
|
}
|
|
266
268
|
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
AgentCleanArchiveResult,
|
|
17
17
|
AgentSessionForkInput,
|
|
18
18
|
AgentSessionInfo,
|
|
19
|
+
AgentSessionSetOptions,
|
|
19
20
|
AgentSessionStatus,
|
|
20
21
|
AgentSessionSummaryPage,
|
|
21
22
|
AgentSessionSystemSnapshot,
|
|
@@ -167,8 +168,9 @@ export class RpcRemoteAgentTransport implements RemoteAgentTransport {
|
|
|
167
168
|
async set(
|
|
168
169
|
session_id: string,
|
|
169
170
|
input: RemoteSessionSetInput,
|
|
171
|
+
options?: AgentSessionSetOptions,
|
|
170
172
|
): Promise<void> {
|
|
171
|
-
await this.client.set_session(session_id, input);
|
|
173
|
+
await this.client.set_session(session_id, input, options);
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
async respond(
|
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
|
|
|
@@ -776,6 +786,7 @@ export class SessionMessages {
|
|
|
776
786
|
private async create_message(
|
|
777
787
|
factory: (sequence: number, created_at: number) => SessionMessage,
|
|
778
788
|
draft = false,
|
|
789
|
+
publish_mutation = true,
|
|
779
790
|
): Promise<SessionMessage> {
|
|
780
791
|
await this.ensure_initialized();
|
|
781
792
|
if (draft) {
|
|
@@ -786,13 +797,13 @@ export class SessionMessages {
|
|
|
786
797
|
}
|
|
787
798
|
return candidate;
|
|
788
799
|
});
|
|
789
|
-
this.accept_message(message);
|
|
800
|
+
this.accept_message(message, publish_mutation);
|
|
790
801
|
return message;
|
|
791
802
|
}
|
|
792
803
|
const message = await this.store.append_message((state) =>
|
|
793
804
|
factory(state.message_sequence, Date.now()),
|
|
794
805
|
);
|
|
795
|
-
this.accept_message(message);
|
|
806
|
+
this.accept_message(message, publish_mutation);
|
|
796
807
|
return message;
|
|
797
808
|
}
|
|
798
809
|
|
|
@@ -923,7 +934,11 @@ export class SessionMessages {
|
|
|
923
934
|
} as SessionMessageSnapshotMutation;
|
|
924
935
|
}
|
|
925
936
|
|
|
926
|
-
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
|
+
}
|
|
927
942
|
this.accept_mutation(this.build_message_mutation(message), message);
|
|
928
943
|
}
|
|
929
944
|
|
|
@@ -941,26 +956,42 @@ export class SessionMessages {
|
|
|
941
956
|
export class SessionActionMessageWriter {
|
|
942
957
|
readonly message_id: string;
|
|
943
958
|
private readonly messages: SessionMessages;
|
|
959
|
+
private readonly publish_mutation: boolean;
|
|
944
960
|
private closed = false;
|
|
945
961
|
|
|
946
|
-
constructor(
|
|
962
|
+
constructor(
|
|
963
|
+
messages: SessionMessages,
|
|
964
|
+
message_id: string,
|
|
965
|
+
publish_mutation = true,
|
|
966
|
+
) {
|
|
947
967
|
this.messages = messages;
|
|
948
968
|
this.message_id = message_id;
|
|
969
|
+
this.publish_mutation = publish_mutation;
|
|
949
970
|
}
|
|
950
971
|
|
|
951
972
|
/** 把 Action 更新为 completed。 */
|
|
952
973
|
async complete(input?: { title?: string; description?: string; data?: JsonObject }): Promise<void> {
|
|
953
974
|
if (this.closed) return;
|
|
954
|
-
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
|
+
);
|
|
955
981
|
this.closed = true;
|
|
956
982
|
}
|
|
957
983
|
|
|
958
984
|
/** 把 Action 更新为 failed。 */
|
|
959
985
|
async fail(error: unknown): Promise<void> {
|
|
960
986
|
if (this.closed) return;
|
|
961
|
-
await this.messages.update_action_message(
|
|
962
|
-
|
|
963
|
-
|
|
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
|
+
);
|
|
964
995
|
this.closed = true;
|
|
965
996
|
}
|
|
966
997
|
}
|