@downcity/agent 1.1.276 → 1.1.278

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.
Files changed (56) hide show
  1. package/bin/agent/Agent.d.ts.map +1 -1
  2. package/bin/agent/Agent.js +0 -1
  3. package/bin/agent/Agent.js.map +1 -1
  4. package/bin/agent/AgentSessions.d.ts +0 -4
  5. package/bin/agent/AgentSessions.d.ts.map +1 -1
  6. package/bin/agent/AgentSessions.js +2 -17
  7. package/bin/agent/AgentSessions.js.map +1 -1
  8. package/bin/session/Session.d.ts +11 -1
  9. package/bin/session/Session.d.ts.map +1 -1
  10. package/bin/session/Session.js +61 -24
  11. package/bin/session/Session.js.map +1 -1
  12. package/bin/session/messages/SessionAssistantMessageWriter.d.ts +6 -1
  13. package/bin/session/messages/SessionAssistantMessageWriter.d.ts.map +1 -1
  14. package/bin/session/messages/SessionAssistantMessageWriter.js +52 -15
  15. package/bin/session/messages/SessionAssistantMessageWriter.js.map +1 -1
  16. package/bin/session/messages/SessionToolPartGate.d.ts +23 -0
  17. package/bin/session/messages/SessionToolPartGate.d.ts.map +1 -0
  18. package/bin/session/messages/SessionToolPartGate.js +64 -0
  19. package/bin/session/messages/SessionToolPartGate.js.map +1 -0
  20. package/bin/session/storage/Instruction.d.ts +14 -0
  21. package/bin/session/storage/Instruction.d.ts.map +1 -0
  22. package/bin/session/storage/Instruction.js +44 -0
  23. package/bin/session/storage/Instruction.js.map +1 -0
  24. package/bin/session/storage/Paths.d.ts +8 -0
  25. package/bin/session/storage/Paths.d.ts.map +1 -1
  26. package/bin/session/storage/Paths.js +10 -0
  27. package/bin/session/storage/Paths.js.map +1 -1
  28. package/bin/types/agent/SessionActor.d.ts +2 -0
  29. package/bin/types/agent/SessionActor.d.ts.map +1 -1
  30. package/bin/types/session/SessionInstruction.d.ts +22 -0
  31. package/bin/types/session/SessionInstruction.d.ts.map +1 -0
  32. package/bin/types/session/SessionInstruction.js +9 -0
  33. package/bin/types/session/SessionInstruction.js.map +1 -0
  34. package/bin/types/session/SessionOptions.d.ts +6 -2
  35. package/bin/types/session/SessionOptions.d.ts.map +1 -1
  36. package/bin/types/session/SessionQueue.d.ts +2 -18
  37. package/bin/types/session/SessionQueue.d.ts.map +1 -1
  38. package/bin/types/session/SessionTool.d.ts +9 -0
  39. package/bin/types/session/SessionTool.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/scripts/session-config-turn-boundary.test.mjs +156 -5
  42. package/scripts/session-messages.test.mjs +180 -4
  43. package/scripts/session-tool-part-gate.test.mjs +58 -0
  44. package/src/agent/Agent.ts +0 -1
  45. package/src/agent/AgentSessions.ts +2 -21
  46. package/src/session/Session.ts +68 -26
  47. package/src/session/messages/SessionAssistantMessageWriter.ts +65 -14
  48. package/src/session/messages/SessionToolPartGate.ts +67 -0
  49. package/src/session/storage/Instruction.ts +59 -0
  50. package/src/session/storage/Paths.ts +18 -0
  51. package/src/types/agent/SessionActor.ts +3 -0
  52. package/src/types/session/SessionInstruction.ts +24 -0
  53. package/src/types/session/SessionOptions.ts +6 -2
  54. package/src/types/session/SessionQueue.ts +9 -29
  55. package/src/types/session/SessionTool.ts +10 -0
  56. package/tsconfig.tsbuildinfo +1 -1
@@ -74,6 +74,10 @@ import { nanoid } from "nanoid";
74
74
  import { buildSessionInfo } from "@/session/browse/Browse.js";
75
75
  import { ensureSessionTitle } from "@/session/SessionTitle.js";
76
76
  import { readSessionMetadata } from "@/session/storage/Metadata.js";
77
+ import {
78
+ read_session_instruction,
79
+ write_session_instruction,
80
+ } from "@/session/storage/Instruction.js";
77
81
  import { to_executor_history } from "@/session/messages/SessionMessageCodec.js";
78
82
  import type { SessionMessage } from "@/types/session/SessionMessage.js";
79
83
  import type {
@@ -91,7 +95,6 @@ export class Session implements AgentSession {
91
95
  private readonly project_root: string;
92
96
  private readonly tools: Record<string, Tool>;
93
97
  private readonly logger: SessionOptions["logger"];
94
- private readonly get_instruction_system_blocks: SessionOptions["getInstructionSystemBlocks"];
95
98
  private readonly get_managed_plugin_system_blocks: SessionOptions["getManagedPluginSystemBlocks"];
96
99
  private readonly ensure_configured_hook?: SessionOptions["ensureConfigured"];
97
100
  private readonly composer: SessionComposer;
@@ -105,6 +108,7 @@ export class Session implements AgentSession {
105
108
  private readonly get_agent_model: SessionOptions["getAgentModel"];
106
109
  private readonly get_agent_plugins: SessionOptions["get_agent_plugins"];
107
110
  private effective_instruction_system_blocks: AgentSessionSystemBlock[];
111
+ private instruction_initialize_promise: Promise<void> | null = null;
108
112
  private effective_agent_env: Record<string, string>;
109
113
  private effective_agent_plugins: AgentPluginExecutionRuntime;
110
114
  private readonly state: SessionState;
@@ -117,12 +121,11 @@ export class Session implements AgentSession {
117
121
  this.project_root = String(options.projectRoot || "").trim();
118
122
  this.tools = options.tools;
119
123
  this.logger = options.logger;
120
- this.get_instruction_system_blocks = options.getInstructionSystemBlocks;
121
124
  this.get_agent_env = options.getAgentEnv;
122
125
  this.get_agent_model = options.getAgentModel;
123
126
  this.get_agent_plugins = options.get_agent_plugins;
124
127
  this.effective_instruction_system_blocks = options
125
- .getInstructionSystemBlocks()
128
+ .instruction_system_blocks
126
129
  .map((block) => ({ ...block }));
127
130
  this.effective_agent_env = { ...options.getAgentEnv() };
128
131
  this.effective_agent_plugins = options.get_agent_plugins();
@@ -188,11 +191,35 @@ export class Session implements AgentSession {
188
191
  * 初始化当前 session。
189
192
  */
190
193
  async initialize(): Promise<this> {
191
- await this.session_messages.initialize();
192
- await this.state.initialize();
194
+ await Promise.all([
195
+ this.initialize_instruction(),
196
+ this.session_messages.initialize(),
197
+ this.state.initialize(),
198
+ ]);
193
199
  return this;
194
200
  }
195
201
 
202
+ /**
203
+ * 把当前 Session 生效的自定义 instruction 显式固化到 instruction.md。
204
+ *
205
+ * 关键点(中文)
206
+ * - 只写入 `instruction` 来源的 block,不包含 SDK core 与 plugin system。
207
+ * - 多个 instruction block 按原顺序合并为一个 Markdown 文档。
208
+ */
209
+ async snapshot(): Promise<void> {
210
+ await this.initialize_instruction();
211
+ const instruction = this.effective_instruction_system_blocks
212
+ .filter((block) => block.source === "instruction")
213
+ .map((block) => block.content)
214
+ .join("\n\n");
215
+ await write_session_instruction({
216
+ project_root: this.project_root,
217
+ agent_id: this.agentId,
218
+ session_id: this.id,
219
+ instruction,
220
+ });
221
+ }
222
+
196
223
  /**
197
224
  * 读取当前 session 配置快照。
198
225
  */
@@ -214,6 +241,7 @@ export class Session implements AgentSession {
214
241
  * 追加一条新的 Session prompt。
215
242
  */
216
243
  async prompt(input: AgentSessionPromptInput): Promise<AgentSessionTurnHandle> {
244
+ await this.initialize_instruction();
217
245
  return await this.session_turn.prompt(input);
218
246
  }
219
247
 
@@ -236,14 +264,6 @@ export class Session implements AgentSession {
236
264
  * 把 Agent configured state command 加入当前 Session 的统一输入队列。
237
265
  */
238
266
  enqueue_agent_command(command: AgentSessionCommand): void {
239
- if (command.type === "instruction") {
240
- this.session_turn.enqueue_command({
241
- type: "agent_instruction",
242
- command_id: command.command_id,
243
- instruction_blocks: command.instruction_blocks,
244
- });
245
- return;
246
- }
247
267
  if (command.type === "env") {
248
268
  this.session_turn.enqueue_command({
249
269
  type: "agent_env",
@@ -277,18 +297,6 @@ export class Session implements AgentSession {
277
297
  }
278
298
  return;
279
299
  }
280
- if (command.type === "agent_instruction") {
281
- this.effective_instruction_system_blocks = command.instruction_blocks.map(
282
- (block) => ({ ...block }),
283
- );
284
- await this.emit_config_action_event({
285
- id: `agent-instruction:${this.id}:${command.command_id}`,
286
- title: "Agent instruction updated",
287
- state: "completed",
288
- turnId: turn_id,
289
- });
290
- return;
291
- }
292
300
  if (command.type === "agent_env") {
293
301
  this.effective_agent_env = { ...command.env };
294
302
  await this.emit_config_action_event({
@@ -406,6 +414,7 @@ export class Session implements AgentSession {
406
414
  * 读取当前 session 生效的 system 快照。
407
415
  */
408
416
  async system(): Promise<AgentSessionSystemSnapshot> {
417
+ await this.initialize_instruction();
409
418
  const composed = await this.compose_for_view();
410
419
  const blocks = resolve_composed_system_blocks(composed);
411
420
  return {
@@ -533,6 +542,7 @@ export class Session implements AgentSession {
533
542
  * 在执行前确保 session 已完成初始化与宿主装配。
534
543
  */
535
544
  async ensureReadyForExecution(): Promise<void> {
545
+ await this.initialize_instruction();
536
546
  await this.state.ensure_ready_for_execution();
537
547
  }
538
548
 
@@ -543,7 +553,9 @@ export class Session implements AgentSession {
543
553
  sessionId: session_id,
544
554
  tools: this.tools,
545
555
  logger: this.logger,
546
- getInstructionSystemBlocks: this.get_instruction_system_blocks,
556
+ instruction_system_blocks: this.effective_instruction_system_blocks.map(
557
+ (block) => ({ ...block }),
558
+ ),
547
559
  getAgentEnv: this.get_agent_env,
548
560
  get_agent_plugins: this.get_agent_plugins,
549
561
  getManagedPluginSystemBlocks: this.get_managed_plugin_system_blocks,
@@ -567,6 +579,36 @@ export class Session implements AgentSession {
567
579
  return new SessionClass(options) as this;
568
580
  }
569
581
 
582
+ /** 恢复显式固化的 instruction.md;文件不存在时保留创建时 instruction。 */
583
+ private async initialize_instruction(): Promise<void> {
584
+ if (!this.instruction_initialize_promise) {
585
+ this.instruction_initialize_promise = (async () => {
586
+ const persisted_instruction = await read_session_instruction({
587
+ project_root: this.project_root,
588
+ agent_id: this.agentId,
589
+ session_id: this.id,
590
+ });
591
+ if (persisted_instruction === null) return;
592
+
593
+ const instruction = persisted_instruction.trim();
594
+ const stable_system_blocks = this.effective_instruction_system_blocks
595
+ .filter((block) => block.source !== "instruction")
596
+ .map((block) => ({ ...block }));
597
+ this.effective_instruction_system_blocks = [
598
+ ...(instruction
599
+ ? [{
600
+ source: "instruction" as const,
601
+ name: "agent",
602
+ content: instruction,
603
+ }]
604
+ : []),
605
+ ...stable_system_blocks,
606
+ ];
607
+ })();
608
+ }
609
+ await this.instruction_initialize_promise;
610
+ }
611
+
570
612
  private create_message_store(): JsonlSessionMessageStore {
571
613
  const session_dir_path = getSdkAgentSessionDirPath(
572
614
  this.project_root,
@@ -12,6 +12,7 @@ import {
12
12
  to_session_json_value,
13
13
  to_session_provider_metadata,
14
14
  } from "@/session/messages/SessionJsonValue.js";
15
+ import { SessionToolPartGate } from "@/session/messages/SessionToolPartGate.js";
15
16
  import type {
16
17
  SessionAssistantFilePart,
17
18
  SessionAssistantMessage,
@@ -32,6 +33,7 @@ export class SessionAssistantMessageWriter {
32
33
  >();
33
34
  private readonly active_text_part_ids = new Map<string, string>();
34
35
  private readonly current_step_part_ids = new Set<string>();
36
+ private readonly tool_part_gate = new SessionToolPartGate();
35
37
  private write_chain: Promise<void> = Promise.resolve();
36
38
  private step_index = 0;
37
39
  private step_active = false;
@@ -108,8 +110,8 @@ export class SessionAssistantMessageWriter {
108
110
  /** 释放异常结束的 step 作用域并保留已经写入的 canonical Parts。 */
109
111
  async abort_step(): Promise<void> {
110
112
  await this.enqueue_write(async () => {
111
- if (!this.step_active) return;
112
- this.reset_step_state();
113
+ this.tool_part_gate.reject_pending("Assistant canonical step was aborted");
114
+ if (this.step_active) this.reset_step_state();
113
115
  });
114
116
  }
115
117
 
@@ -196,7 +198,7 @@ export class SessionAssistantMessageWriter {
196
198
  });
197
199
  return;
198
200
  }
199
- await this.upsert_tool(chunk.toolCallId, {
201
+ await this.create_tool(chunk.toolCallId, {
200
202
  tool_name: chunk.toolName,
201
203
  state: "input-streaming",
202
204
  input_text: "",
@@ -210,6 +212,7 @@ export class SessionAssistantMessageWriter {
210
212
  ...(tool_metadata !== undefined ? { tool_metadata } : {}),
211
213
  ...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
212
214
  });
215
+ this.tool_part_gate.mark_available(chunk.toolCallId);
213
216
  return;
214
217
  }
215
218
  case "tool-input-delta": {
@@ -251,7 +254,7 @@ export class SessionAssistantMessageWriter {
251
254
  });
252
255
  return;
253
256
  }
254
- await this.upsert_tool(chunk.toolCallId, {
257
+ const changes = {
255
258
  tool_name: chunk.toolName,
256
259
  state: "ready",
257
260
  input: to_session_json_value(chunk.input),
@@ -264,7 +267,13 @@ export class SessionAssistantMessageWriter {
264
267
  ...(chunk.title !== undefined ? { title: chunk.title } : {}),
265
268
  ...(tool_metadata !== undefined ? { tool_metadata } : {}),
266
269
  ...(chunk.dynamic !== undefined ? { dynamic: chunk.dynamic } : {}),
267
- });
270
+ } as const;
271
+ if (tool) {
272
+ await this.upsert_tool(chunk.toolCallId, changes);
273
+ } else {
274
+ await this.create_tool(chunk.toolCallId, changes);
275
+ this.tool_part_gate.mark_available(chunk.toolCallId);
276
+ }
268
277
  return;
269
278
  }
270
279
  case "tool-input-error": {
@@ -449,18 +458,21 @@ export class SessionAssistantMessageWriter {
449
458
 
450
459
  /** Executor 在调用 Tool 实现前写入完整输入。 */
451
460
  async prepare_tool_input(input: SessionToolInputReady): Promise<void> {
461
+ await this.tool_part_gate.wait_until_available(input.tool_call_id);
452
462
  await this.enqueue_write(async () => {
463
+ if (this.closed) throw new Error("Assistant Message writer is closed");
453
464
  const current = this.find_tool(input.tool_call_id);
454
- if (current && current.state !== "input-streaming" && current.state !== "ready") {
465
+ if (!current) {
466
+ throw new Error(
467
+ `Assistant canonical Tool Part not found: ${input.tool_call_id}`,
468
+ );
469
+ }
470
+ if (current.state !== "input-streaming" && current.state !== "ready") {
455
471
  throw new Error(
456
472
  `Tool input cannot be prepared from ${current.state}: ${input.tool_call_id}`,
457
473
  );
458
474
  }
459
- await this.upsert_tool(input.tool_call_id, {
460
- tool_name: input.tool_name,
461
- state: "ready",
462
- input: to_session_json_value(input.input),
463
- });
475
+ await this.write_prepared_tool_input(input);
464
476
  });
465
477
  }
466
478
 
@@ -692,23 +704,59 @@ export class SessionAssistantMessageWriter {
692
704
  this.pending_text_parts.delete(part_id);
693
705
  }
694
706
 
695
- /** 创建或更新 Tool Part 完整快照。 */
707
+ /** 更新已经由 canonical stream 创建的 Tool Part */
696
708
  private async upsert_tool(
697
709
  tool_call_id: string,
698
710
  changes: Pick<SessionAssistantToolPart, "tool_name" | "state"> &
699
711
  Partial<Omit<SessionAssistantToolPart, "part_id" | "type" | "tool_call_id" | "tool_name" | "state">>,
700
712
  ): Promise<void> {
701
713
  const current = this.find_tool(tool_call_id);
714
+ if (!current) {
715
+ throw new Error(
716
+ `Assistant canonical Tool Part not found: ${tool_call_id}`,
717
+ );
718
+ }
702
719
  await this.upsert_part({
703
- ...(current || {}),
720
+ ...current,
704
721
  part_id: `tool:${tool_call_id}`,
705
- sequence: current?.sequence || this.next_part_sequence(),
722
+ sequence: current.sequence,
706
723
  type: "tool",
707
724
  tool_call_id,
708
725
  ...changes,
709
726
  });
710
727
  }
711
728
 
729
+ /** 仅由 Tool 输入 stream chunk 创建 canonical Tool Part。 */
730
+ private async create_tool(
731
+ tool_call_id: string,
732
+ changes: Pick<SessionAssistantToolPart, "tool_name" | "state"> &
733
+ Partial<Omit<SessionAssistantToolPart, "part_id" | "type" | "tool_call_id" | "tool_name" | "state">>,
734
+ ): Promise<void> {
735
+ if (this.find_tool(tool_call_id)) {
736
+ throw new Error(
737
+ `Assistant canonical Tool Part already exists: ${tool_call_id}`,
738
+ );
739
+ }
740
+ await this.upsert_part({
741
+ part_id: `tool:${tool_call_id}`,
742
+ sequence: this.next_part_sequence(),
743
+ type: "tool",
744
+ tool_call_id,
745
+ ...changes,
746
+ });
747
+ }
748
+
749
+ /** 将 Executor 完整输入写入已经由 stream 创建的 Tool Part。 */
750
+ private async write_prepared_tool_input(
751
+ input: SessionToolInputReady,
752
+ ): Promise<void> {
753
+ await this.upsert_tool(input.tool_call_id, {
754
+ tool_name: input.tool_name,
755
+ state: "ready",
756
+ input: to_session_json_value(input.input),
757
+ });
758
+ }
759
+
712
760
  /** 串行执行对当前 Assistant Message 的全部写操作。 */
713
761
  private async enqueue_write(operation: () => Promise<void>): Promise<void> {
714
762
  const current = this.write_chain.then(operation, operation);
@@ -721,6 +769,9 @@ export class SessionAssistantMessageWriter {
721
769
  status: "completed" | "stopped" | "failed",
722
770
  ): Promise<void> {
723
771
  if (this.closed) return;
772
+ this.tool_part_gate.close(
773
+ `Assistant Message writer closed with status ${status}`,
774
+ );
724
775
  this.reset_step_state();
725
776
  await this.recorder.complete_assistant_message(this.message_id, status);
726
777
  this.closed = true;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * canonical Tool Part 的并发等待门。
3
+ *
4
+ * Gate 只协调单个 `tool_call_id` 的可用状态,不接触 Message、Recorder 或
5
+ * Tool 输入。不同 Tool 使用独立 Promise,因此不会形成全局执行锁。
6
+ */
7
+
8
+ import type { SessionToolPartWaiter } from "@/types/session/SessionTool.js";
9
+
10
+ /** 按 Tool Call 隔离的 canonical Part 等待门。 */
11
+ export class SessionToolPartGate {
12
+ private readonly available_tool_call_ids = new Set<string>();
13
+ private readonly pending_waiters = new Map<string, SessionToolPartWaiter>();
14
+ private closed_error: Error | null = null;
15
+
16
+ /** 等待指定 Tool Part 成功持久化;已经可用时立即完成。 */
17
+ wait_until_available(tool_call_id: string): Promise<void> {
18
+ if (this.closed_error) return Promise.reject(this.closed_error);
19
+ if (this.available_tool_call_ids.has(tool_call_id)) return Promise.resolve();
20
+
21
+ const current = this.pending_waiters.get(tool_call_id);
22
+ if (current) return current.promise;
23
+
24
+ const waiter = this.create_waiter();
25
+ this.pending_waiters.set(tool_call_id, waiter);
26
+ return waiter.promise;
27
+ }
28
+
29
+ /** 标记指定 Tool Part 已持久化,并只释放该 Tool 的等待者。 */
30
+ mark_available(tool_call_id: string): void {
31
+ if (this.closed_error) throw this.closed_error;
32
+ if (this.available_tool_call_ids.has(tool_call_id)) return;
33
+
34
+ this.available_tool_call_ids.add(tool_call_id);
35
+ const waiter = this.pending_waiters.get(tool_call_id);
36
+ if (!waiter) return;
37
+ this.pending_waiters.delete(tool_call_id);
38
+ waiter.resolve();
39
+ }
40
+
41
+ /** 拒绝当前尚未出现 canonical Part 的全部等待者。 */
42
+ reject_pending(reason: string): void {
43
+ for (const [tool_call_id, waiter] of this.pending_waiters) {
44
+ waiter.reject(new Error(`${reason}: ${tool_call_id}`));
45
+ }
46
+ this.pending_waiters.clear();
47
+ }
48
+
49
+ /** 永久关闭 Gate,并拒绝当前及后续等待。 */
50
+ close(reason: string): void {
51
+ if (this.closed_error) return;
52
+ this.closed_error = new Error(reason);
53
+ this.reject_pending(reason);
54
+ this.available_tool_call_ids.clear();
55
+ }
56
+
57
+ /** 创建单个 Tool Call 使用的异步等待句柄。 */
58
+ private create_waiter(): SessionToolPartWaiter {
59
+ let resolve!: () => void;
60
+ let reject!: (error: Error) => void;
61
+ const promise = new Promise<void>((resolve_promise, reject_promise) => {
62
+ resolve = resolve_promise;
63
+ reject = reject_promise;
64
+ });
65
+ return { promise, resolve, reject };
66
+ }
67
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Session instruction 显式快照存储。
3
+ *
4
+ * 关键点(中文)
5
+ * - instruction.md 是可选文件,不存在时由 Session 使用 Agent 当前 instruction。
6
+ * - 空文件也是有效快照,用于显式固化没有自定义 instruction 的状态。
7
+ * - 写入使用同目录临时文件替换,避免进程中断留下半份内容。
8
+ */
9
+
10
+ import { randomUUID } from "node:crypto";
11
+ import path from "node:path";
12
+ import fs from "fs-extra";
13
+ import { getSdkAgentSessionInstructionPath } from "@/session/storage/Paths.js";
14
+ import type {
15
+ SessionInstructionStorageLocation,
16
+ WriteSessionInstructionInput,
17
+ } from "@/types/session/SessionInstruction.js";
18
+
19
+ /** 读取 Session 显式固化的 instruction;文件不存在时返回 null。 */
20
+ export async function read_session_instruction(
21
+ input: SessionInstructionStorageLocation,
22
+ ): Promise<string | null> {
23
+ const instruction_path = getSdkAgentSessionInstructionPath(
24
+ input.project_root,
25
+ input.agent_id,
26
+ input.session_id,
27
+ );
28
+ try {
29
+ return await fs.readFile(instruction_path, "utf8");
30
+ } catch (error) {
31
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ /** 原子覆盖当前 Session 的 instruction.md。 */
37
+ export async function write_session_instruction(
38
+ input: WriteSessionInstructionInput,
39
+ ): Promise<void> {
40
+ const instruction_path = getSdkAgentSessionInstructionPath(
41
+ input.project_root,
42
+ input.agent_id,
43
+ input.session_id,
44
+ );
45
+ const temporary_path = [
46
+ instruction_path,
47
+ process.pid,
48
+ Date.now(),
49
+ randomUUID(),
50
+ "tmp",
51
+ ].join(".");
52
+ await fs.ensureDir(path.dirname(instruction_path));
53
+ try {
54
+ await fs.writeFile(temporary_path, input.instruction, "utf8");
55
+ await fs.move(temporary_path, instruction_path, { overwrite: true });
56
+ } finally {
57
+ await fs.remove(temporary_path);
58
+ }
59
+ }
@@ -129,6 +129,24 @@ export function getSdkAgentSessionDirPath(
129
129
  );
130
130
  }
131
131
 
132
+ /**
133
+ * 单个 session 显式固化的 instruction.md 路径。
134
+ *
135
+ * 关键点(中文)
136
+ * - 文件不存在表示 Session 恢复时继续采用 Agent 当前 instruction。
137
+ * - 空文件表示调用方显式固化了空 instruction。
138
+ */
139
+ export function getSdkAgentSessionInstructionPath(
140
+ projectRoot: string,
141
+ agentId: string,
142
+ sessionId: string,
143
+ ): string {
144
+ return path.join(
145
+ getSdkAgentSessionDirPath(projectRoot, agentId, sessionId),
146
+ "instruction.md",
147
+ );
148
+ }
149
+
132
150
  /**
133
151
  * 单个 session 的消息目录路径。
134
152
  */
@@ -125,6 +125,9 @@ export interface AgentSession extends AgentSessionActor {
125
125
  /** 写入当前 session 默认配置。 */
126
126
  set(input: AgentSessionSetInput): Promise<void>;
127
127
 
128
+ /** 把当前 Session 生效的自定义 instruction 显式固化到 instruction.md。 */
129
+ snapshot(): Promise<void>;
130
+
128
131
  /** 从当前 session 创建一个分叉会话。 */
129
132
  fork(input?: AgentSessionForkInput | string): Promise<AgentSession>;
130
133
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Session instruction 显式快照存储类型。
3
+ *
4
+ * 关键点(中文)
5
+ * - 这里只描述 instruction.md 的定位与写入输入。
6
+ * - 文件存在性本身用于表达 Session 是否启用显式 instruction 快照。
7
+ */
8
+
9
+ /** Session instruction.md 的稳定定位信息。 */
10
+ export interface SessionInstructionStorageLocation {
11
+ /** 当前项目根目录。 */
12
+ project_root: string;
13
+ /** 当前 Agent 稳定标识。 */
14
+ agent_id: string;
15
+ /** 当前 Session 稳定标识。 */
16
+ session_id: string;
17
+ }
18
+
19
+ /** 原子写入 Session instruction.md 使用的完整输入。 */
20
+ export interface WriteSessionInstructionInput
21
+ extends SessionInstructionStorageLocation {
22
+ /** 当前 Session 生效的自定义 instruction Markdown。 */
23
+ instruction: string;
24
+ }
@@ -74,9 +74,13 @@ export interface SessionOptions {
74
74
  logger: Logger;
75
75
 
76
76
  /**
77
- * 读取当前 SDK 调用方传入的 instruction system blocks。
77
+ * 当前 Session 创建时绑定的 instruction system blocks。
78
+ *
79
+ * 关键点(中文)
80
+ * - Session 创建后不再动态读取 Agent instruction。
81
+ * - `session.snapshot()` 只会把其中 `instruction` 来源的内容显式写入本地文件。
78
82
  */
79
- getInstructionSystemBlocks: () => AgentSessionSystemBlock[];
83
+ instruction_system_blocks: AgentSessionSystemBlock[];
80
84
 
81
85
  /**
82
86
  * 读取当前 Agent configured env。
@@ -4,7 +4,7 @@
4
4
  * 队列只保存明确的输入事实,不保存可执行闭包。SessionTurn 是这些输入的唯一解释者。
5
5
  */
6
6
 
7
- import type { AgentSessionConfigSnapshot, AgentSessionSystemBlock } from "@/types/agent/SessionTypes.js";
7
+ import type { AgentSessionConfigSnapshot } from "@/types/agent/SessionTypes.js";
8
8
  import type { AgentPluginExecutionRuntime } from "@/types/plugin/PluginRuntime.js";
9
9
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
10
10
  import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
@@ -23,16 +23,6 @@ export interface SessionModelQueueCommand {
23
23
  action_title?: string;
24
24
  }
25
25
 
26
- /** Agent instruction 在 Session Step 检查点生效的命令。 */
27
- export interface SessionInstructionQueueCommand {
28
- /** 命令种类固定为 Agent instruction 更新。 */
29
- type: "agent_instruction";
30
- /** 当前命令的稳定唯一标识。 */
31
- command_id: string;
32
- /** 下一 Step 使用的完整 instruction blocks。 */
33
- instruction_blocks: AgentSessionSystemBlock[];
34
- }
35
-
36
26
  /** Agent env 在 Session Step 检查点生效的命令。 */
37
27
  export interface SessionEnvQueueCommand {
38
28
  /** 命令种类固定为 Agent env 更新。 */
@@ -66,29 +56,19 @@ export interface SessionCompactQueueCommand {
66
56
  /** Session FIFO 中允许出现的领域命令。 */
67
57
  export type SessionQueueCommand =
68
58
  | SessionModelQueueCommand
69
- | SessionInstructionQueueCommand
70
59
  | SessionEnvQueueCommand
71
60
  | SessionPluginsQueueCommand
72
61
  | SessionCompactQueueCommand;
73
62
 
74
63
  /** Agent configured state 广播给既有 Session 的输入。 */
75
- export type AgentSessionCommand =
76
- | {
77
- /** 当前修改固定为 instruction。 */
78
- type: "instruction";
79
- /** 当前命令唯一标识。 */
80
- command_id: string;
81
- /** 下一 Session Step 使用的 instruction blocks。 */
82
- instruction_blocks: AgentSessionSystemBlock[];
83
- }
84
- | {
85
- /** 当前修改固定为 env。 */
86
- type: "env";
87
- /** 当前命令唯一标识。 */
88
- command_id: string;
89
- /** 下一 Session Step 使用的完整 Agent env。 */
90
- env: Record<string, string>;
91
- }
64
+ export type AgentSessionCommand = {
65
+ /** 当前修改固定为 env。 */
66
+ type: "env";
67
+ /** 当前命令唯一标识。 */
68
+ command_id: string;
69
+ /** 下一 Session Step 使用的完整 Agent env。 */
70
+ env: Record<string, string>;
71
+ }
92
72
  | {
93
73
  /** 当前修改固定为 plugins。 */
94
74
  type: "plugins";
@@ -23,3 +23,13 @@ export interface SessionToolInputReady {
23
23
  /** 已完成解析的 Tool 输入。 */
24
24
  input: unknown;
25
25
  }
26
+
27
+ /** 等待单个 canonical Tool Part 到达的异步句柄。 */
28
+ export interface SessionToolPartWaiter {
29
+ /** Tool Part 成功持久化后完成,异常结束时拒绝。 */
30
+ promise: Promise<void>;
31
+ /** Tool Part 已按 canonical 顺序持久化时释放等待。 */
32
+ resolve: () => void;
33
+ /** 当前 step 或 Gate 异常结束时拒绝等待。 */
34
+ reject: (error: Error) => void;
35
+ }