@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.
Files changed (157) hide show
  1. package/bin/agent/local/Agent.d.ts +6 -1
  2. package/bin/agent/local/Agent.d.ts.map +1 -1
  3. package/bin/agent/local/Agent.js +30 -3
  4. package/bin/agent/local/Agent.js.map +1 -1
  5. package/bin/agent/local/services/AgentSessions.d.ts +23 -0
  6. package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
  7. package/bin/agent/local/services/AgentSessions.js +44 -0
  8. package/bin/agent/local/services/AgentSessions.js.map +1 -1
  9. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  10. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +0 -7
  11. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  12. package/bin/executor/Executor.d.ts +25 -0
  13. package/bin/executor/Executor.d.ts.map +1 -1
  14. package/bin/executor/Executor.js +36 -0
  15. package/bin/executor/Executor.js.map +1 -1
  16. package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts +4 -0
  17. package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts.map +1 -1
  18. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts +0 -1
  19. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts.map +1 -1
  20. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js +15 -11
  21. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js.map +1 -1
  22. package/bin/executor/core-engine/CoreEngineRunner.d.ts +11 -0
  23. package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
  24. package/bin/executor/core-engine/CoreEngineRunner.js +12 -8
  25. package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
  26. package/bin/executor/store/history/SessionHistoryStore.d.ts +3 -13
  27. package/bin/executor/store/history/SessionHistoryStore.d.ts.map +1 -1
  28. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +1 -1
  29. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
  30. package/bin/executor/tools/plugin/PluginToolBridge.d.ts.map +1 -1
  31. package/bin/executor/tools/plugin/PluginToolBridge.js +4 -2
  32. package/bin/executor/tools/plugin/PluginToolBridge.js.map +1 -1
  33. package/bin/index.d.ts +2 -1
  34. package/bin/index.d.ts.map +1 -1
  35. package/bin/index.js +1 -1
  36. package/bin/index.js.map +1 -1
  37. package/bin/model/CityModelAdapter.d.ts +6 -0
  38. package/bin/model/CityModelAdapter.d.ts.map +1 -1
  39. package/bin/model/CityModelAdapter.js +13 -0
  40. package/bin/model/CityModelAdapter.js.map +1 -1
  41. package/bin/plugin/core/PluginRegistry.d.ts +22 -1
  42. package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
  43. package/bin/plugin/core/PluginRegistry.js +46 -4
  44. package/bin/plugin/core/PluginRegistry.js.map +1 -1
  45. package/bin/session/Session.d.ts +11 -1
  46. package/bin/session/Session.d.ts.map +1 -1
  47. package/bin/session/Session.js +76 -11
  48. package/bin/session/Session.js.map +1 -1
  49. package/bin/session/browse/Browse.d.ts +0 -8
  50. package/bin/session/browse/Browse.d.ts.map +1 -1
  51. package/bin/session/browse/Browse.js +72 -57
  52. package/bin/session/browse/Browse.js.map +1 -1
  53. package/bin/session/recorder/JsonlSessionMessageStore.d.ts +69 -16
  54. package/bin/session/recorder/JsonlSessionMessageStore.d.ts.map +1 -1
  55. package/bin/session/recorder/JsonlSessionMessageStore.js +283 -60
  56. package/bin/session/recorder/JsonlSessionMessageStore.js.map +1 -1
  57. package/bin/session/recorder/SessionRecorder.d.ts +20 -2
  58. package/bin/session/recorder/SessionRecorder.d.ts.map +1 -1
  59. package/bin/session/recorder/SessionRecorder.js +91 -46
  60. package/bin/session/recorder/SessionRecorder.js.map +1 -1
  61. package/bin/session/recorder/SessionRecorderCompaction.d.ts +3 -3
  62. package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -1
  63. package/bin/session/recorder/SessionRecorderCompaction.js +50 -62
  64. package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -1
  65. package/bin/session/recorder/SessionRecorderHistoryStore.d.ts +2 -2
  66. package/bin/session/recorder/SessionRecorderHistoryStore.d.ts.map +1 -1
  67. package/bin/session/recorder/SessionRecorderHistoryStore.js +31 -36
  68. package/bin/session/recorder/SessionRecorderHistoryStore.js.map +1 -1
  69. package/bin/session/runtime/SessionPromptRuntime.d.ts +17 -1
  70. package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
  71. package/bin/session/runtime/SessionPromptRuntime.js +67 -8
  72. package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
  73. package/bin/session/services/SessionStateService.d.ts +15 -1
  74. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  75. package/bin/session/services/SessionStateService.js +60 -39
  76. package/bin/session/services/SessionStateService.js.map +1 -1
  77. package/bin/session/services/SessionTurnService.d.ts +5 -0
  78. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  79. package/bin/session/services/SessionTurnService.js +17 -3
  80. package/bin/session/services/SessionTurnService.js.map +1 -1
  81. package/bin/session/services/SessionViewService.d.ts +1 -5
  82. package/bin/session/services/SessionViewService.d.ts.map +1 -1
  83. package/bin/session/services/SessionViewService.js +4 -34
  84. package/bin/session/services/SessionViewService.js.map +1 -1
  85. package/bin/session/storage/Paths.d.ts +3 -15
  86. package/bin/session/storage/Paths.d.ts.map +1 -1
  87. package/bin/session/storage/Paths.js +5 -21
  88. package/bin/session/storage/Paths.js.map +1 -1
  89. package/bin/types/agent/SessionTypes.d.ts +2 -24
  90. package/bin/types/agent/SessionTypes.d.ts.map +1 -1
  91. package/bin/types/executor/SessionRunContext.d.ts +21 -0
  92. package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
  93. package/bin/types/plugin/PluginRuntime.d.ts +25 -0
  94. package/bin/types/plugin/PluginRuntime.d.ts.map +1 -1
  95. package/bin/types/runtime/agent/AgentContext.d.ts +17 -5
  96. package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
  97. package/bin/types/runtime/agent/AgentContext.js +29 -6
  98. package/bin/types/runtime/agent/AgentContext.js.map +1 -1
  99. package/bin/types/session/SessionConfigMutation.d.ts +68 -0
  100. package/bin/types/session/SessionConfigMutation.d.ts.map +1 -0
  101. package/bin/types/session/SessionConfigMutation.js +10 -0
  102. package/bin/types/session/SessionConfigMutation.js.map +1 -0
  103. package/bin/types/session/SessionLocalState.d.ts +8 -0
  104. package/bin/types/session/SessionLocalState.d.ts.map +1 -1
  105. package/bin/types/session/SessionMessage.d.ts +16 -12
  106. package/bin/types/session/SessionMessage.d.ts.map +1 -1
  107. package/bin/types/session/SessionMutation.d.ts +1 -1
  108. package/bin/types/session/SessionMutation.js +1 -1
  109. package/bin/types/session/SessionOptions.d.ts +16 -0
  110. package/bin/types/session/SessionOptions.d.ts.map +1 -1
  111. package/bin/types/session/SessionSegment.d.ts +57 -0
  112. package/bin/types/session/SessionSegment.d.ts.map +1 -0
  113. package/bin/types/session/SessionSegment.js +8 -0
  114. package/bin/types/session/SessionSegment.js.map +1 -0
  115. package/package.json +3 -3
  116. package/scripts/city-model-tool-loop.test.mjs +32 -0
  117. package/scripts/session-action-message.test.mjs +59 -13
  118. package/scripts/session-compaction-context-window.test.mjs +95 -0
  119. package/scripts/session-config-turn-boundary.test.mjs +224 -0
  120. package/scripts/session-list-title.test.mjs +21 -3
  121. package/scripts/session-prompt-runtime.test.mjs +85 -0
  122. package/scripts/session-recorder.test.mjs +340 -11
  123. package/src/agent/local/Agent.ts +34 -3
  124. package/src/agent/local/services/AgentSessions.ts +61 -0
  125. package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +0 -5
  126. package/src/executor/Executor.ts +62 -0
  127. package/src/executor/composer/compaction/SessionCompactionComposer.ts +5 -0
  128. package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.ts +16 -14
  129. package/src/executor/core-engine/CoreEngineRunner.ts +24 -8
  130. package/src/executor/store/history/SessionHistoryStore.ts +3 -14
  131. package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +1 -1
  132. package/src/executor/tools/plugin/PluginToolBridge.ts +4 -2
  133. package/src/index.ts +8 -0
  134. package/src/model/CityModelAdapter.ts +15 -0
  135. package/src/plugin/core/PluginRegistry.ts +72 -4
  136. package/src/session/Session.ts +89 -11
  137. package/src/session/browse/Browse.ts +73 -69
  138. package/src/session/recorder/JsonlSessionMessageStore.ts +378 -60
  139. package/src/session/recorder/SessionRecorder.ts +116 -49
  140. package/src/session/recorder/SessionRecorderCompaction.ts +55 -80
  141. package/src/session/recorder/SessionRecorderHistoryStore.ts +29 -49
  142. package/src/session/runtime/SessionPromptRuntime.ts +100 -9
  143. package/src/session/services/SessionStateService.ts +79 -44
  144. package/src/session/services/SessionTurnService.ts +19 -3
  145. package/src/session/services/SessionViewService.ts +4 -50
  146. package/src/session/storage/Paths.ts +5 -38
  147. package/src/types/agent/SessionTypes.ts +2 -24
  148. package/src/types/executor/SessionRunContext.ts +25 -0
  149. package/src/types/plugin/PluginRuntime.ts +27 -0
  150. package/src/types/runtime/agent/AgentContext.ts +32 -7
  151. package/src/types/session/SessionConfigMutation.ts +78 -0
  152. package/src/types/session/SessionLocalState.ts +9 -0
  153. package/src/types/session/SessionMessage.ts +16 -12
  154. package/src/types/session/SessionMutation.ts +1 -1
  155. package/src/types/session/SessionOptions.ts +21 -0
  156. package/src/types/session/SessionSegment.ts +62 -0
  157. package/tsconfig.tsbuildinfo +1 -1
@@ -4,7 +4,7 @@
4
4
  * 关键点(中文)
5
5
  * - 面向 `new Agent(...)` 的本地会话使用场景。
6
6
  * - 对外保留稳定 Session facade,把状态、turn、view 逻辑下沉到独立 service。
7
- * - 内部继续复用 `Executor` / `JsonlSessionHistoryStore` / Composer 体系。
7
+ * - 内部使用 `SessionRecorder` 统一管理 Active、Segment 与流式 Assistant 草稿。
8
8
  */
9
9
 
10
10
  import { Executor } from "@executor/Executor.js";
@@ -55,6 +55,7 @@ import { SessionStateService } from "@/session/services/SessionStateService.js";
55
55
  import { SessionTurnService } from "@/session/services/SessionTurnService.js";
56
56
  import { SessionViewService } from "@/session/services/SessionViewService.js";
57
57
  import type { SessionLocalState } from "@/types/session/SessionLocalState.js";
58
+ import type { AgentSessionConfigMutation } from "@/types/session/SessionConfigMutation.js";
58
59
  import type {
59
60
  SessionComposerFactoryContext,
60
61
  SessionComposerInput,
@@ -65,6 +66,7 @@ import type { SessionContextComposer } from "@/executor/composer/context/Session
65
66
  import type { SessionHistoryComposer } from "@/executor/composer/history/SessionHistoryComposer.js";
66
67
  import type { SessionSystemComposer } from "@/executor/composer/system/SessionSystemComposer.js";
67
68
  import type { SessionOptions } from "@/types/session/SessionOptions.js";
69
+ import type { AgentPluginExecutionRuntime } from "@/types/plugin/PluginRuntime.js";
68
70
 
69
71
  /**
70
72
  * SDK 本地 Session。
@@ -92,6 +94,11 @@ export class Session implements AgentSession {
92
94
  private readonly set_approval_mode_hook: SessionOptions["set_approval_mode"];
93
95
  private readonly resolve_approval_hook: SessionOptions["resolve_approval"];
94
96
  private readonly localState: SessionLocalState;
97
+ private readonly getAgentEnv: SessionOptions["getAgentEnv"];
98
+ private readonly get_agent_plugins: SessionOptions["get_agent_plugins"];
99
+ private effective_instruction_system_blocks: AgentSessionSystemBlock[];
100
+ private effective_agent_env: Record<string, string>;
101
+ private effective_agent_plugins: AgentPluginExecutionRuntime;
95
102
  private readonly stateService: SessionStateService;
96
103
  private readonly turnService: SessionTurnService;
97
104
  private readonly viewService: SessionViewService<this>;
@@ -104,6 +111,13 @@ export class Session implements AgentSession {
104
111
  this.tools = options.tools;
105
112
  this.logger = options.logger;
106
113
  this.getInstructionSystemBlocks = options.getInstructionSystemBlocks;
114
+ this.getAgentEnv = options.getAgentEnv;
115
+ this.get_agent_plugins = options.get_agent_plugins;
116
+ this.effective_instruction_system_blocks = options
117
+ .getInstructionSystemBlocks()
118
+ .map((block) => ({ ...block }));
119
+ this.effective_agent_env = { ...options.getAgentEnv() };
120
+ this.effective_agent_plugins = options.get_agent_plugins();
107
121
  this.getManagedPluginSystemBlocks = options.getManagedPluginSystemBlocks;
108
122
  this.getPluginSystemBlocks = options.getPluginSystemBlocks;
109
123
  this.ensureConfiguredHook = options.ensureConfigured;
@@ -187,9 +201,11 @@ export class Session implements AgentSession {
187
201
  state_service: this.stateService,
188
202
  logger: this.logger,
189
203
  is_executing: () => this.isExecuting(),
190
- get_instruction_system_blocks: this.getInstructionSystemBlocks,
204
+ get_instruction_system_blocks: () =>
205
+ this.effective_instruction_system_blocks.map((block) => ({ ...block })),
191
206
  get_managed_plugin_system_blocks: this.getManagedPluginSystemBlocks,
192
- get_plugin_system_blocks: this.getPluginSystemBlocks,
207
+ get_plugin_system_blocks: async () =>
208
+ await this.effective_agent_plugins.systemBlocks(),
193
209
  ...(this.composers?.systemComposer
194
210
  ? { custom_system_composer: system_composer }
195
211
  : {}),
@@ -226,7 +242,10 @@ export class Session implements AgentSession {
226
242
  * 写入当前 session 默认配置。
227
243
  */
228
244
  async set(input: AgentSessionSetInput): Promise<void> {
229
- await this.stateService.set(input);
245
+ const configured = await this.stateService.set(input);
246
+ if (configured.mutation) {
247
+ this.turnService.enqueue_config(configured.mutation);
248
+ }
230
249
  }
231
250
 
232
251
  /**
@@ -243,6 +262,47 @@ export class Session implements AgentSession {
243
262
  return await this.turnService.stop();
244
263
  }
245
264
 
265
+ /**
266
+ * 把 Agent configured state 修改加入当前 Session 的统一输入队列。
267
+ */
268
+ enqueue_agent_config(mutation: AgentSessionConfigMutation): void {
269
+ this.turnService.enqueue_config({
270
+ mutation_id: mutation.mutation_id,
271
+ scope: "agent",
272
+ apply: async ({ turn_id }) => {
273
+ if (mutation.type === "instruction") {
274
+ this.effective_instruction_system_blocks = mutation
275
+ .instruction_blocks
276
+ .map((block) => ({ ...block }));
277
+ await this.stateService.emit_action_event({
278
+ id: `agent-instruction:${this.id}:${mutation.mutation_id}`,
279
+ title: "Agent instruction updated",
280
+ state: "completed",
281
+ turnId: turn_id,
282
+ });
283
+ return;
284
+ }
285
+ if (mutation.type === "env") {
286
+ this.effective_agent_env = { ...mutation.env };
287
+ await this.stateService.emit_action_event({
288
+ id: `agent-env:${this.id}:${mutation.mutation_id}`,
289
+ title: "Agent environment updated",
290
+ state: "completed",
291
+ turnId: turn_id,
292
+ });
293
+ return;
294
+ }
295
+ this.effective_agent_plugins = mutation.plugins;
296
+ await this.stateService.emit_action_event({
297
+ id: `agent-plugins:${this.id}:${mutation.mutation_id}`,
298
+ title: mutation.title,
299
+ state: "completed",
300
+ turnId: turn_id,
301
+ });
302
+ },
303
+ });
304
+ }
305
+
246
306
  /**
247
307
  * 订阅当前 Session 的未来事件。
248
308
  */
@@ -388,8 +448,11 @@ export class Session implements AgentSession {
388
448
  tools: this.tools,
389
449
  logger: this.logger,
390
450
  getInstructionSystemBlocks: this.getInstructionSystemBlocks,
451
+ getAgentEnv: this.getAgentEnv,
452
+ get_agent_plugins: this.get_agent_plugins,
391
453
  getManagedPluginSystemBlocks: this.getManagedPluginSystemBlocks,
392
- getPluginSystemBlocks: this.getPluginSystemBlocks,
454
+ getPluginSystemBlocks: async () =>
455
+ await this.effective_agent_plugins.systemBlocks(),
393
456
  ensureConfigured: this.ensureConfiguredHook,
394
457
  composers: this.composers,
395
458
  list_approvals: this.list_approvals,
@@ -422,7 +485,7 @@ export class Session implements AgentSession {
422
485
  const messages_dir_path = `${session_dir_path}/messages`;
423
486
  return new JsonlSessionMessageStore({
424
487
  session_id: this.id,
425
- file_path: `${messages_dir_path}/messages.jsonl`,
488
+ file_path: `${messages_dir_path}/active.jsonl`,
426
489
  assistant_message_file_path: getSdkAgentSessionAssistantMessagePath(
427
490
  this.projectRoot,
428
491
  this.agentId,
@@ -441,6 +504,7 @@ export class Session implements AgentSession {
441
504
  private create_local_state(): SessionLocalState {
442
505
  return {
443
506
  sessionConfig: {},
507
+ effective_session_config: {},
444
508
  createdAt: Date.now(),
445
509
  timezone: resolveSystemTimezone(),
446
510
  initializePromise: null,
@@ -455,9 +519,11 @@ export class Session implements AgentSession {
455
519
  sessionId: this.id,
456
520
  historyStore: this.historyStore,
457
521
  getTools: () => this.tools,
458
- getInstructionSystemBlocks: this.getInstructionSystemBlocks,
522
+ getInstructionSystemBlocks: () =>
523
+ this.effective_instruction_system_blocks.map((block) => ({ ...block })),
459
524
  getManagedPluginSystemBlocks: this.getManagedPluginSystemBlocks,
460
- getPluginSystemBlocks: this.getPluginSystemBlocks,
525
+ getPluginSystemBlocks: async () =>
526
+ await this.effective_agent_plugins.systemBlocks(),
461
527
  getSessionCreatedAt: () => this.localState.createdAt,
462
528
  getSessionTimezone: () => this.localState.timezone,
463
529
  };
@@ -482,9 +548,11 @@ export class Session implements AgentSession {
482
548
  projectRoot: this.projectRoot,
483
549
  getSessionCreatedAt: () => this.localState.createdAt,
484
550
  getSessionTimezone: () => this.localState.timezone,
485
- getInstructionSystemBlocks: this.getInstructionSystemBlocks,
551
+ getInstructionSystemBlocks: () =>
552
+ this.effective_instruction_system_blocks.map((block) => ({ ...block })),
486
553
  getManagedPluginSystemBlocks: this.getManagedPluginSystemBlocks,
487
- getPluginSystemBlocks: this.getPluginSystemBlocks,
554
+ getPluginSystemBlocks: async () =>
555
+ await this.effective_agent_plugins.systemBlocks(),
488
556
  }),
489
557
  );
490
558
  }
@@ -516,10 +584,20 @@ export class Session implements AgentSession {
516
584
  sessionId: this.id,
517
585
  historyStore: this.historyStore,
518
586
  historyComposer: this.historyComposer,
519
- getModel: () => this.localState.sessionConfig.model,
587
+ getModel: () =>
588
+ this.localState.effective_session_config.model ||
589
+ this.localState.sessionConfig.model,
590
+ get_model_context_window: () =>
591
+ this.localState.effective_session_config.model
592
+ ? this.localState.effective_session_config.model_context_window
593
+ : this.localState.sessionConfig.model_context_window,
520
594
  logger: this.logger,
521
595
  systemComposer: system_composer,
522
596
  getTools: () => this.tools,
597
+ getEnv: () => ({ ...this.effective_agent_env }),
598
+ get_systems: () =>
599
+ this.effective_instruction_system_blocks.map((block) => block.content),
600
+ get_plugins: () => this.effective_agent_plugins,
523
601
  ...(context_composer ? { contextComposer: context_composer } : {}),
524
602
  ...(compaction_composer
525
603
  ? { compactionComposer: compaction_composer }
@@ -94,13 +94,6 @@ type SessionBrowseBaseInput = {
94
94
  executing?: boolean;
95
95
  };
96
96
 
97
- type SessionArchiveFileV1 = {
98
- v?: unknown;
99
- sessionId?: unknown;
100
- archivedAt?: unknown;
101
- messages?: unknown;
102
- };
103
-
104
97
  function decodeMaybe(input: string): string {
105
98
  try {
106
99
  return decodeURIComponent(input);
@@ -140,11 +133,6 @@ function encodeCursor(offset: number): string | undefined {
140
133
  return String(Math.floor(offset));
141
134
  }
142
135
 
143
- function normalizeArchiveId(input: unknown): string | undefined {
144
- const value = String(input || "").trim();
145
- return value || undefined;
146
- }
147
-
148
136
  function stringifyForDisplay(input: unknown, maxChars = 2400): string {
149
137
  if (input === undefined) return "";
150
138
  if (input === null) return "null";
@@ -344,7 +332,7 @@ export async function loadSessionMessagesFromPath(
344
332
  }
345
333
  }
346
334
 
347
- const inflight_path = filePath.replace(/messages\.jsonl$/, "assistant_message.json");
335
+ const inflight_path = path.join(path.dirname(filePath), "assistant_message.json");
348
336
  if (await fs.pathExists(inflight_path)) {
349
337
  try {
350
338
  const message = (await fs.readJson(inflight_path)) as SessionMessage;
@@ -395,52 +383,12 @@ function project_canonical_message_record(message: SessionMessage): SessionRecor
395
383
  }];
396
384
  }
397
385
 
398
- /**
399
- * 读取 compact archive 文件中的消息。
400
- *
401
- * 关键点(中文)
402
- * - archive 是 compact 时被覆盖的“上一层历史”,不是当前运行态事实源。
403
- * - 这里不读取 inflight,因为 archive 层永远是已完成的历史快照。
404
- */
405
- export async function loadSessionArchiveMessagesFromPath(
406
- filePath: string,
407
- ): Promise<SessionRecordV1[]> {
408
- try {
409
- const raw = (await fs.readJson(filePath)) as SessionArchiveFileV1 | null;
410
- const items = Array.isArray(raw?.messages) ? raw.messages : [];
411
- return items.filter((item): item is SessionRecordV1 => {
412
- if (!item || typeof item !== "object") return false;
413
- const candidate = item as { type?: unknown; role?: unknown; parts?: unknown };
414
- return (
415
- (
416
- candidate.type === "action" ||
417
- candidate.role === "user" ||
418
- candidate.role === "assistant"
419
- ) &&
420
- (candidate.type === "action" || Array.isArray(candidate.parts))
421
- );
422
- });
423
- } catch {
424
- return [];
425
- }
426
- }
427
-
428
386
  function isCompactSummaryMessage(message: SessionRecordV1): boolean {
429
387
  if (!is_session_message_record(message)) return false;
430
388
  const metadata = (message.metadata || null) as SessionMetadataV1 | null;
431
389
  return metadata?.source === "compact" || metadata?.kind === "summary";
432
390
  }
433
391
 
434
- function resolvePreviousArchiveId(messages: SessionRecordV1[]): string | undefined {
435
- for (const message of messages) {
436
- if (!isCompactSummaryMessage(message)) continue;
437
- const metadata = (message.metadata || null) as SessionMetadataV1 | null;
438
- const archiveId = normalizeArchiveId(metadata?.archiveId);
439
- if (archiveId) return archiveId;
440
- }
441
- return undefined;
442
- }
443
-
444
392
  function filterUserVisibleHistoryMessages(
445
393
  messages: SessionRecordV1[],
446
394
  ): SessionRecordV1[] {
@@ -460,9 +408,11 @@ export function buildSessionInfo(
460
408
  180,
461
409
  )
462
410
  : input.metadata.previewText;
463
- const message_count = messages
464
- ? messages.length
465
- : input.metadata.messageCount || 0;
411
+ const message_count = typeof input.metadata.messageCount === "number"
412
+ ? input.metadata.messageCount
413
+ : messages
414
+ ? filterUserVisibleHistoryMessages(messages).length
415
+ : 0;
466
416
  const title =
467
417
  typeof input.metadata.title === "string" && input.metadata.title.trim()
468
418
  ? input.metadata.title.trim()
@@ -494,7 +444,7 @@ export function buildSessionInfo(
494
444
  * 读取列表所需的轻量 session 摘要。
495
445
  *
496
446
  * 关键点(中文)
497
- * - 新记录直接使用 metadata 摘要,不扫描 messages.jsonl。
447
+ * - 新记录直接使用 metadata 摘要,不扫描 active.jsonl。
498
448
  * - 旧记录或执行中记录回退读取一次完整历史,并把摘要补写回 metadata。
499
449
  */
500
450
  async function resolve_session_summary_metadata(input: {
@@ -507,10 +457,9 @@ async function resolve_session_summary_metadata(input: {
507
457
  /** 是否强制刷新摘要。 */
508
458
  refresh: boolean;
509
459
  }): Promise<SessionHistoryMetaV1> {
510
- const history_bytes = await fs.stat(input.messagesPath)
511
- .then((file_stat) => file_stat.size)
512
- .catch(() => 0);
513
- const inflight_path = `${input.messagesPath.slice(0, -"messages.jsonl".length)}assistant_message.json`;
460
+ const storage_stats = await resolve_session_disk_stats(input.messagesPath);
461
+ const history_bytes = storage_stats.history_bytes;
462
+ const inflight_path = path.join(path.dirname(input.messagesPath), "assistant_message.json");
514
463
  const has_inflight = await fs.pathExists(inflight_path);
515
464
  if (
516
465
  !input.refresh &&
@@ -529,9 +478,11 @@ async function resolve_session_summary_metadata(input: {
529
478
  void _previous_preview;
530
479
  const next_metadata: SessionHistoryMetaV1 = {
531
480
  ...metadata_without_preview,
532
- messageCount: messages.length,
481
+ messageCount: storage_stats.message_count,
533
482
  historyBytes: history_bytes,
534
- ...(preview_text ? { previewText: preview_text } : {}),
483
+ ...(preview_text || input.metadata.previewText
484
+ ? { previewText: preview_text || input.metadata.previewText }
485
+ : {}),
535
486
  };
536
487
  const temp_path = `${input.metaPath}.${process.pid}.${Date.now()}.tmp`;
537
488
  await fs.ensureDir(path.dirname(input.metaPath));
@@ -540,6 +491,65 @@ async function resolve_session_summary_metadata(input: {
540
491
  return next_metadata;
541
492
  }
542
493
 
494
+ /**
495
+ * 只读取 Active 和 Segment 文件索引,计算列表所需的持久化统计。
496
+ *
497
+ * 关键点(中文)
498
+ * - Segment 的结束 sequence 直接来自文件名,不解析历史正文。
499
+ * - Active 需要逐行读取,以同时覆盖 revision 行与运行中 Assistant sequence。
500
+ */
501
+ async function resolve_session_disk_stats(messages_path: string) {
502
+ const messages_dir_path = path.dirname(messages_path);
503
+ const segments_dir_path = path.join(messages_dir_path, "segments");
504
+ const segment_entries = await fs.readdir(segments_dir_path, { withFileTypes: true })
505
+ .catch(() => []);
506
+ const segment_files = segment_entries.flatMap((entry) => {
507
+ if (!entry.isFile()) return [];
508
+ const match = /^(\d+)-(\d+)\.jsonl$/.exec(entry.name);
509
+ if (!match) return [];
510
+ return [{
511
+ file_path: path.join(segments_dir_path, entry.name),
512
+ end_sequence: Number(match[2]),
513
+ }];
514
+ });
515
+ const active_raw = await fs.readFile(messages_path, "utf8").catch(() => "");
516
+ let latest_active_sequence = 0;
517
+ for (const line of active_raw.split("\n")) {
518
+ if (!line.trim()) continue;
519
+ try {
520
+ const message = JSON.parse(line) as Partial<SessionMessage>;
521
+ if (Number.isInteger(message.sequence)) {
522
+ latest_active_sequence = Math.max(latest_active_sequence, Number(message.sequence));
523
+ }
524
+ } catch {
525
+ // 单行损坏不阻断 Session 列表,其正文读取时会按既有规则忽略。
526
+ }
527
+ }
528
+ const inflight_path = path.join(messages_dir_path, "assistant_message.json");
529
+ try {
530
+ const inflight = await fs.readJson(inflight_path) as Partial<SessionMessage>;
531
+ if (Number.isInteger(inflight.sequence)) {
532
+ latest_active_sequence = Math.max(latest_active_sequence, Number(inflight.sequence));
533
+ }
534
+ } catch {
535
+ // 草稿不存在或损坏时只统计已完成历史。
536
+ }
537
+ const segment_sizes = await Promise.all(
538
+ segment_files.map(({ file_path }) => fs.stat(file_path)
539
+ .then((file_stat) => file_stat.size)
540
+ .catch(() => 0)),
541
+ );
542
+ const latest_segment_sequence = segment_files.reduce(
543
+ (latest, segment) => Math.max(latest, segment.end_sequence),
544
+ 0,
545
+ );
546
+ return {
547
+ history_bytes: Buffer.byteLength(active_raw, "utf8") +
548
+ segment_sizes.reduce((total, size) => total + size, 0),
549
+ message_count: Math.max(latest_segment_sequence, latest_active_sequence),
550
+ };
551
+ }
552
+
543
553
  /**
544
554
  * 基于完整消息列表构建 session records 分页结果。
545
555
  */
@@ -552,8 +562,6 @@ export function buildSessionRecordsPage(params: {
552
562
  const order = params.input?.order || "asc";
553
563
  const limit = normalizeLimit(params.input?.limit, 50, 500);
554
564
  const cursor = normalizeCursor(params.input?.cursor);
555
- const archive_id = normalizeArchiveId(params.input?.archive_id);
556
- const previous_archive_id = resolvePreviousArchiveId(params.messages);
557
565
  const visibleMessages = filterUserVisibleHistoryMessages(params.messages);
558
566
 
559
567
  if (view === "timeline") {
@@ -570,8 +578,6 @@ export function buildSessionRecordsPage(params: {
570
578
  ? { next_cursor: encodeCursor(nextOffset) }
571
579
  : {}),
572
580
  has_more: nextOffset < orderedEvents.length,
573
- ...(archive_id ? { archive_id } : {}),
574
- ...(previous_archive_id ? { previous_archive_id } : {}),
575
581
  };
576
582
  }
577
583
 
@@ -588,8 +594,6 @@ export function buildSessionRecordsPage(params: {
588
594
  ? { next_cursor: encodeCursor(nextOffset) }
589
595
  : {}),
590
596
  has_more: nextOffset < orderedMessages.length,
591
- ...(archive_id ? { archive_id } : {}),
592
- ...(previous_archive_id ? { previous_archive_id } : {}),
593
597
  };
594
598
  }
595
599