@downcity/agent 1.1.199 → 1.1.200

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 (94) hide show
  1. package/bin/executor/Executor.d.ts +2 -2
  2. package/bin/executor/Executor.d.ts.map +1 -1
  3. package/bin/executor/Executor.js +23 -24
  4. package/bin/executor/Executor.js.map +1 -1
  5. package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts +5 -5
  6. package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts.map +1 -1
  7. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js +1 -1
  8. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js.map +1 -1
  9. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts +2 -2
  10. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
  11. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +23 -30
  12. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
  13. package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.d.ts.map +1 -1
  14. package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.js +4 -3
  15. package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.js.map +1 -1
  16. package/bin/executor/messages/SessionAttachmentMapper.js.map +1 -1
  17. package/bin/executor/messages/SessionMessageCodec.d.ts +1 -1
  18. package/bin/executor/messages/SessionMessageCodec.js +3 -3
  19. package/bin/executor/messages/SessionMessageCodec.js.map +1 -1
  20. package/bin/executor/messages/SessionStepEventMapper.d.ts +2 -2
  21. package/bin/executor/messages/SessionStepEventMapper.d.ts.map +1 -1
  22. package/bin/executor/messages/SessionStepEventMapper.js.map +1 -1
  23. package/bin/executor/services/ExecutorInflightService.d.ts +2 -2
  24. package/bin/executor/services/ExecutorInflightService.d.ts.map +1 -1
  25. package/bin/executor/services/ExecutorInflightService.js +9 -5
  26. package/bin/executor/services/ExecutorInflightService.js.map +1 -1
  27. package/bin/executor/store/history/SessionHistoryStore.d.ts +11 -11
  28. package/bin/executor/store/history/SessionHistoryStore.d.ts.map +1 -1
  29. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts +9 -7
  30. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
  31. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +97 -52
  32. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
  33. package/bin/executor/types/SessionMessages.d.ts +41 -22
  34. package/bin/executor/types/SessionMessages.d.ts.map +1 -1
  35. package/bin/executor/types/SessionMessages.js +25 -7
  36. package/bin/executor/types/SessionMessages.js.map +1 -1
  37. package/bin/index.d.ts +1 -1
  38. package/bin/index.d.ts.map +1 -1
  39. package/bin/index.js.map +1 -1
  40. package/bin/session/SessionTitle.d.ts.map +1 -1
  41. package/bin/session/SessionTitle.js +5 -0
  42. package/bin/session/SessionTitle.js.map +1 -1
  43. package/bin/session/browse/Browse.d.ts.map +1 -1
  44. package/bin/session/browse/Browse.js +35 -32
  45. package/bin/session/browse/Browse.js.map +1 -1
  46. package/bin/session/services/SessionStateService.d.ts +8 -8
  47. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  48. package/bin/session/services/SessionStateService.js +44 -54
  49. package/bin/session/services/SessionStateService.js.map +1 -1
  50. package/bin/session/services/SessionTurnService.js +3 -3
  51. package/bin/session/services/SessionTurnService.js.map +1 -1
  52. package/bin/session/services/SessionViewService.d.ts.map +1 -1
  53. package/bin/session/services/SessionViewService.js +17 -27
  54. package/bin/session/services/SessionViewService.js.map +1 -1
  55. package/bin/types/agent/SessionTypes.d.ts +10 -10
  56. package/bin/types/agent/SessionTypes.d.ts.map +1 -1
  57. package/bin/types/executor/SessionRunContext.d.ts +6 -6
  58. package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
  59. package/bin/types/sdk/AgentSessionAction.d.ts +67 -0
  60. package/bin/types/sdk/AgentSessionAction.d.ts.map +1 -0
  61. package/bin/types/sdk/AgentSessionAction.js +10 -0
  62. package/bin/types/sdk/AgentSessionAction.js.map +1 -0
  63. package/bin/types/sdk/AgentSessionEvent.d.ts +2 -2
  64. package/bin/types/sdk/AgentSessionEvent.d.ts.map +1 -1
  65. package/package.json +3 -3
  66. package/scripts/{session-operation-message.test.mjs → session-action-message.test.mjs} +57 -55
  67. package/src/executor/Executor.ts +28 -29
  68. package/src/executor/composer/compaction/SessionCompactionComposer.ts +5 -5
  69. package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.ts +1 -1
  70. package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +32 -37
  71. package/src/executor/composer/history/jsonl/JsonlSessionHistoryComposer.ts +7 -4
  72. package/src/executor/messages/SessionAttachmentMapper.ts +2 -2
  73. package/src/executor/messages/SessionMessageCodec.ts +3 -3
  74. package/src/executor/messages/SessionStepEventMapper.ts +5 -5
  75. package/src/executor/services/ExecutorInflightService.ts +11 -7
  76. package/src/executor/store/history/SessionHistoryStore.ts +11 -10
  77. package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +118 -63
  78. package/src/executor/types/SessionMessages.ts +69 -31
  79. package/src/index.ts +5 -6
  80. package/src/session/SessionTitle.ts +3 -0
  81. package/src/session/browse/Browse.ts +42 -34
  82. package/src/session/services/SessionStateService.ts +54 -64
  83. package/src/session/services/SessionTurnService.ts +3 -3
  84. package/src/session/services/SessionViewService.ts +17 -27
  85. package/src/types/agent/SessionTypes.ts +10 -10
  86. package/src/types/executor/SessionRunContext.ts +11 -11
  87. package/src/types/sdk/AgentSessionAction.ts +79 -0
  88. package/src/types/sdk/AgentSessionEvent.ts +2 -2
  89. package/tsconfig.tsbuildinfo +1 -1
  90. package/bin/types/sdk/AgentSessionOperation.d.ts +0 -99
  91. package/bin/types/sdk/AgentSessionOperation.d.ts.map +0 -1
  92. package/bin/types/sdk/AgentSessionOperation.js +0 -10
  93. package/bin/types/sdk/AgentSessionOperation.js.map +0 -1
  94. package/src/types/sdk/AgentSessionOperation.ts +0 -125
@@ -1,10 +1,10 @@
1
1
  /**
2
- * @file 验证 operation message 的持久化、模型输入过滤与 timeline 投影。
2
+ * @file 验证 action message 的持久化、模型输入过滤与 timeline 投影。
3
3
  *
4
4
  * 关键点(中文)
5
- * - operation message 会作为 operation role 消息写入 JSONL。
6
- * - history composer 组装 LLM 输入时必须过滤 operation message。
7
- * - timeline view 可以把 operation message 投影成前端可识别的 operation 事件。
5
+ * - action message 会作为 `type=action` item 写入 JSONL。
6
+ * - 同一个 action id 会被更新为一条历史记录,不拆成 running/completed 多条。
7
+ * - history composer 组装 LLM 输入时必须过滤 action message。
8
8
  */
9
9
 
10
10
  import test from "node:test";
@@ -18,14 +18,14 @@ import { JsonlSessionHistoryComposer } from "../bin/executor/composer/history/js
18
18
  import { toSessionTimelineEvents } from "../bin/session/browse/Browse.js";
19
19
  import { Agent } from "../bin/index.js";
20
20
 
21
- test("operation message is persisted but filtered from LLM history", async () => {
21
+ test("action message is upserted but filtered from LLM history", async () => {
22
22
  const root_path = await fs.mkdtemp(
23
- path.join(os.tmpdir(), "downcity-agent-operation-message-"),
23
+ path.join(os.tmpdir(), "downcity-agent-action-message-"),
24
24
  );
25
- const session_id = "operation_session";
25
+ const session_id = "action_session";
26
26
  const store = new JsonlSessionHistoryStore({
27
27
  rootPath: root_path,
28
- agentId: "operation_agent",
28
+ agentId: "action_agent",
29
29
  sessionId: session_id,
30
30
  });
31
31
  const composer = new JsonlSessionHistoryComposer({ store });
@@ -38,16 +38,26 @@ test("operation message is persisted but filtered from LLM history", async () =>
38
38
  }),
39
39
  );
40
40
  await store.append(
41
- store.operation({
42
- operation: {
43
- operationId: "op-1",
44
- name: "compacting",
45
- status: "finished",
46
- label: "Session history compacted",
41
+ store.action({
42
+ action: {
43
+ id: "action:1",
44
+ title: "Compacting session history",
45
+ state: "running",
46
+ turnId: "turn-1",
47
+ },
48
+ metadata: { sessionId: session_id },
49
+ }),
50
+ );
51
+ await store.append(
52
+ store.action({
53
+ action: {
54
+ id: "action:1",
55
+ title: "Session history compacted",
56
+ description: "Compacted earlier messages.",
57
+ state: "completed",
47
58
  turnId: "turn-1",
48
59
  },
49
60
  metadata: { sessionId: session_id },
50
- id: "op:1",
51
61
  }),
52
62
  );
53
63
  await store.append(
@@ -60,9 +70,10 @@ test("operation message is persisted but filtered from LLM history", async () =>
60
70
 
61
71
  const persisted_messages = await store.list();
62
72
  assert.equal(persisted_messages.length, 3);
63
- assert.equal(persisted_messages[1].role, "operation");
64
- assert.equal(persisted_messages[1].metadata.kind, "operation");
65
- assert.equal(persisted_messages[1].metadata.operation.operationId, "op-1");
73
+ assert.equal(persisted_messages[1].type, "action");
74
+ assert.equal(persisted_messages[1].title, "Session history compacted");
75
+ assert.equal(persisted_messages[1].description, "Compacted earlier messages.");
76
+ assert.equal(persisted_messages[1].state, "completed");
66
77
 
67
78
  const model_messages = await composer.prepare({
68
79
  query: "",
@@ -78,28 +89,17 @@ test("operation message is persisted but filtered from LLM history", async () =>
78
89
 
79
90
  const timeline_events = toSessionTimelineEvents(persisted_messages[1]);
80
91
  assert.equal(timeline_events.length, 1);
81
- assert.equal(timeline_events[0].role, "operation");
82
- assert.equal(timeline_events[0].operationId, "op-1");
83
- assert.equal(timeline_events[0].operationName, "compacting");
84
- assert.equal(timeline_events[0].operationStatus, "finished");
85
- assert.equal(timeline_events[0].text, "Session history compacted");
86
-
87
- const hidden_started_events = toSessionTimelineEvents(
88
- store.operation({
89
- operation: {
90
- operationId: "op-started",
91
- name: "compacting",
92
- status: "started",
93
- label: "Compacting session history",
94
- },
95
- metadata: { sessionId: session_id },
96
- id: "op:started",
97
- }),
92
+ assert.equal(timeline_events[0].role, "action");
93
+ assert.equal(timeline_events[0].actionTitle, "Session history compacted");
94
+ assert.equal(timeline_events[0].actionDescription, "Compacted earlier messages.");
95
+ assert.equal(timeline_events[0].actionState, "completed");
96
+ assert.equal(
97
+ timeline_events[0].text,
98
+ "Session history compacted\nCompacted earlier messages.",
98
99
  );
99
- assert.deepEqual(hidden_started_events, []);
100
100
  });
101
101
 
102
- test("session.set persists and publishes model-switching operations", async () => {
102
+ test("session.set persists and publishes model-switching actions", async () => {
103
103
  const agent_path = await fs.mkdtemp(
104
104
  path.join(os.tmpdir(), "downcity-agent-model-switching-"),
105
105
  );
@@ -113,7 +113,7 @@ test("session.set persists and publishes model-switching operations", async () =
113
113
  });
114
114
  const events = [];
115
115
  const unsubscribe = session.subscribe((event) => {
116
- if (event.type === "operation") events.push(event);
116
+ if (event.type === "action") events.push(event);
117
117
  });
118
118
 
119
119
  await session.set({
@@ -125,24 +125,25 @@ test("session.set persists and publishes model-switching operations", async () =
125
125
  unsubscribe();
126
126
 
127
127
  assert.deepEqual(
128
- events.map((event) => `${event.name}:${event.status}`),
129
- ["model-switching:started", "model-switching:finished"],
128
+ events.map((event) => `${event.title}:${event.state}`),
129
+ ["Switching session model:running", "Session model switched:completed"],
130
130
  );
131
+ assert.equal(new Set(events.map((event) => event.id)).size, 1);
131
132
 
132
133
  const history = await session.history({ view: "timeline" });
133
- const operation_events = history.items.filter(
134
- (item) => item.role === "operation",
134
+ const action_events = history.items.filter(
135
+ (item) => item.role === "action",
135
136
  );
136
137
  assert.deepEqual(
137
- operation_events.map((event) => `${event.operationName}:${event.operationStatus}`),
138
- ["model-switching:finished"],
138
+ action_events.map((event) => `${event.actionTitle}:${event.actionState}`),
139
+ ["Session model switched:completed"],
139
140
  );
140
141
  } finally {
141
142
  await agent.dispose();
142
143
  }
143
144
  });
144
145
 
145
- test("session.fork persists and publishes history-forking operations on source session", async () => {
146
+ test("session.fork persists and publishes history-forking actions on source session", async () => {
146
147
  const agent_path = await fs.mkdtemp(
147
148
  path.join(os.tmpdir(), "downcity-agent-history-forking-"),
148
149
  );
@@ -164,7 +165,7 @@ test("session.fork persists and publishes history-forking operations on source s
164
165
 
165
166
  const events = [];
166
167
  const unsubscribe = session.subscribe((event) => {
167
- if (event.type === "operation" && event.name === "history-forking") {
168
+ if (event.type === "action") {
168
169
  events.push(event);
169
170
  }
170
171
  });
@@ -172,25 +173,26 @@ test("session.fork persists and publishes history-forking operations on source s
172
173
  unsubscribe();
173
174
 
174
175
  assert.deepEqual(
175
- events.map((event) => `${event.name}:${event.status}`),
176
- ["history-forking:started", "history-forking:finished"],
176
+ events.map((event) => `${event.title}:${event.state}`),
177
+ ["Forking session history:running", "Session history forked:completed"],
177
178
  );
179
+ assert.equal(new Set(events.map((event) => event.id)).size, 1);
178
180
  assert.notEqual(forked.id, session.id);
179
181
 
180
182
  const source_history = await session.history({ view: "timeline" });
181
- const source_fork_operations = source_history.items.filter(
182
- (item) => item.operationName === "history-forking",
183
+ const source_fork_actions = source_history.items.filter(
184
+ (item) => item.role === "action" && item.actionTitle === "Session history forked",
183
185
  );
184
186
  assert.deepEqual(
185
- source_fork_operations.map((event) => event.operationStatus),
186
- ["finished"],
187
+ source_fork_actions.map((event) => event.actionState),
188
+ ["completed"],
187
189
  );
188
190
 
189
191
  const forked_history = await forked.history({ view: "timeline" });
190
- const forked_model_operations = forked_history.items.filter(
191
- (item) => item.operationName === "model-switching",
192
+ const forked_model_actions = forked_history.items.filter(
193
+ (item) => item.role === "action" && item.actionTitle === "Session model switched",
192
194
  );
193
- assert.equal(forked_model_operations.length, 1);
195
+ assert.equal(forked_model_actions.length, 1);
194
196
  } finally {
195
197
  await agent.dispose();
196
198
  }
@@ -28,7 +28,7 @@ import type { JsonObject } from "@/types/common/Json.js";
28
28
  import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
29
29
  import type { SessionExecutor } from "@/executor/types/SessionExecutor.js";
30
30
  import type { SessionRunContext } from "@/types/executor/SessionRunContext.js";
31
- import type { AgentSessionOperationRecord } from "@/types/sdk/AgentSessionOperation.js";
31
+ import type { AgentSessionActionRecord } from "@/types/sdk/AgentSessionAction.js";
32
32
  import type {
33
33
  SessionExecuteInput,
34
34
  SessionRunResult,
@@ -362,7 +362,7 @@ export class Executor implements SessionExecutor {
362
362
  const composed_context = await this.contextComposer.compose(run_context);
363
363
  const tools = this.bind_run_scope_to_tools(composed_context.tools, run_context);
364
364
  const system = await this.systemComposer.resolve(run_context);
365
- let compaction_operation_id = "";
365
+ let compaction_action_id = "";
366
366
 
367
367
  try {
368
368
  if (retry_count > 0) {
@@ -371,13 +371,13 @@ export class Executor implements SessionExecutor {
371
371
  });
372
372
  }
373
373
 
374
- const emit_compaction_operation = async (
375
- operation: AgentSessionOperationRecord,
374
+ const emit_compaction_action = async (
375
+ action: AgentSessionActionRecord,
376
376
  ): Promise<void> => {
377
- if (operation.status === "started") {
378
- compaction_operation_id = operation.operationId;
377
+ if (action.state === "running") {
378
+ compaction_action_id = action.id;
379
379
  }
380
- await this.emitOperation(run_context, operation);
380
+ await this.emitAction(run_context, action);
381
381
  };
382
382
 
383
383
  await this.compactionComposer.run({
@@ -385,17 +385,16 @@ export class Executor implements SessionExecutor {
385
385
  model,
386
386
  system,
387
387
  retryCount: retry_count,
388
- onOperation: emit_compaction_operation,
388
+ onAction: emit_compaction_action,
389
389
  });
390
390
  } catch (error) {
391
- await this.emitOperation(run_context, {
392
- operationId:
393
- compaction_operation_id ||
391
+ await this.emitAction(run_context, {
392
+ id:
393
+ compaction_action_id ||
394
394
  `compacting:${this.sessionId}:failed:${Date.now()}:${generateId()}`,
395
- name: "compacting",
396
- status: "failed",
397
- label: "Session history compact failed",
398
- error: error instanceof Error ? error.message : String(error),
395
+ title: "Session history compact failed",
396
+ description: error instanceof Error ? error.message : String(error),
397
+ state: "failed",
399
398
  visible: false,
400
399
  });
401
400
  // 压缩失败不阻断主流程,继续使用当前历史消息执行。
@@ -492,8 +491,8 @@ export class Executor implements SessionExecutor {
492
491
  ...(typeof input?.onUiMessageChunkCallback === "function"
493
492
  ? { onUiMessageChunkCallback: input.onUiMessageChunkCallback }
494
493
  : {}),
495
- ...(typeof input?.onOperationCallback === "function"
496
- ? { onOperationCallback: input.onOperationCallback }
494
+ ...(typeof input?.onActionCallback === "function"
495
+ ? { onActionCallback: input.onActionCallback }
497
496
  : {}),
498
497
  ...(input?.abortSignal ? { abortSignal: input.abortSignal } : {}),
499
498
  injectedUserMessages: Array.isArray(input?.injectedUserMessages)
@@ -511,22 +510,22 @@ export class Executor implements SessionExecutor {
511
510
  }
512
511
 
513
512
  /**
514
- * 发布一次 session operation
513
+ * 发布一次 session action
515
514
  */
516
- private async emitOperation(
515
+ private async emitAction(
517
516
  run_context: SessionRunContext,
518
- operation: AgentSessionOperationRecord,
517
+ action: AgentSessionActionRecord,
519
518
  ): Promise<void> {
520
- if (typeof run_context.onOperationCallback !== "function") return;
521
- const operation_id =
522
- String(operation.operationId || "").trim() ||
523
- `operation:${this.sessionId}:${Date.now()}:${generateId()}`;
524
- await run_context.onOperationCallback({
525
- type: "operation",
519
+ if (typeof run_context.onActionCallback !== "function") return;
520
+ const action_id =
521
+ String(action.id || "").trim() ||
522
+ `action:${this.sessionId}:${Date.now()}:${generateId()}`;
523
+ await run_context.onActionCallback({
524
+ type: "action",
526
525
  sessionId: run_context.sessionId || this.sessionId,
527
- ...operation,
528
- operationId: operation_id,
529
- ...(run_context.turnId && !operation.turnId
526
+ ...action,
527
+ id: action_id,
528
+ ...(run_context.turnId && !action.turnId
530
529
  ? { turnId: run_context.turnId }
531
530
  : {}),
532
531
  });
@@ -8,7 +8,7 @@
8
8
 
9
9
  import type { LanguageModel, SystemModelMessage } from "ai";
10
10
  import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
11
- import type { AgentSessionOperationRecord } from "@/types/sdk/AgentSessionOperation.js";
11
+ import type { AgentSessionActionRecord } from "@/types/sdk/AgentSessionAction.js";
12
12
 
13
13
  /**
14
14
  * compaction Composer 执行输入。
@@ -35,13 +35,13 @@ export type SessionCompactionInput = {
35
35
  retryCount: number;
36
36
 
37
37
  /**
38
- * 可选 operation 发布回调。
38
+ * 可选 action 发布回调。
39
39
  *
40
40
  * 说明(中文)
41
- * - 默认 compaction Composer 可在真正开始压缩时发布 `compacting`。
42
- * - 回调由上层 Session 负责转成订阅事件与 operation message。
41
+ * - 默认 compaction Composer 可在真正开始压缩时发布 action。
42
+ * - 回调由上层 Session 负责转成订阅事件与 action message。
43
43
  */
44
- onOperation?: (operation: AgentSessionOperationRecord) => Promise<void>;
44
+ onAction?: (action: AgentSessionActionRecord) => Promise<void>;
45
45
  };
46
46
 
47
47
  /**
@@ -79,7 +79,7 @@ export class JsonlSessionCompactionComposer implements SessionCompactionComposer
79
79
  maxInputTokensApprox: policy.maxInputTokensApprox,
80
80
  archiveOnCompact: policy.archiveOnCompact,
81
81
  compactRatio: policy.compactRatio,
82
- ...(input.onOperation ? { onOperation: input.onOperation } : {}),
82
+ ...(input.onAction ? { onAction: input.onAction } : {}),
83
83
  });
84
84
  }
85
85
 
@@ -20,10 +20,14 @@ import { getLogger } from "@/utils/logger/Logger.js";
20
20
  import type {
21
21
  SessionMessageV1,
22
22
  SessionMetadataV1,
23
+ SessionModelMessageV1,
24
+ } from "@/executor/types/SessionMessages.js";
25
+ import {
26
+ isSessionActionMessage,
27
+ isSessionModelMessage,
23
28
  } from "@/executor/types/SessionMessages.js";
24
- import { isSessionOperationMessage } from "@/executor/types/SessionMessages.js";
25
29
  import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
26
- import type { AgentSessionOperationRecord } from "@/types/sdk/AgentSessionOperation.js";
30
+ import type { AgentSessionActionRecord } from "@/types/sdk/AgentSessionAction.js";
27
31
 
28
32
  export type SessionCompactParams = {
29
33
  model: LanguageModel;
@@ -32,7 +36,7 @@ export type SessionCompactParams = {
32
36
  maxInputTokensApprox: number;
33
37
  archiveOnCompact: boolean;
34
38
  compactRatio: number;
35
- onOperation?: (operation: AgentSessionOperationRecord) => Promise<void>;
39
+ onAction?: (action: AgentSessionActionRecord) => Promise<void>;
36
40
  };
37
41
 
38
42
  type SessionCompactDeps = {
@@ -63,17 +67,16 @@ export async function compactSessionMessagesIfNeeded(
63
67
  params: SessionCompactParams,
64
68
  ): Promise<{ compacted: boolean; reason?: string }> {
65
69
  const logger = getLogger(deps.rootPath, "info");
66
- const operation_id = `compacting:${deps.sessionId}:${Date.now()}:${generateId()}`;
67
- let operation_started = false;
70
+ const action_id = `compacting:${deps.sessionId}:${Date.now()}:${generateId()}`;
71
+ let action_started = false;
68
72
 
69
- const publish_operation = async (
70
- operation: Omit<AgentSessionOperationRecord, "operationId" | "name">,
73
+ const publish_action = async (
74
+ action: Omit<AgentSessionActionRecord, "id">,
71
75
  ): Promise<void> => {
72
- if (typeof params.onOperation !== "function") return;
73
- await params.onOperation({
74
- operationId: operation_id,
75
- name: "compacting",
76
- ...operation,
76
+ if (typeof params.onAction !== "function") return;
77
+ await params.onAction({
78
+ id: action_id,
79
+ ...action,
77
80
  });
78
81
  };
79
82
 
@@ -81,12 +84,10 @@ export async function compactSessionMessagesIfNeeded(
81
84
  // phase 1:snapshot(短锁)
82
85
  // - 仅负责拿一致性快照,不做耗时的模型调用。
83
86
  // - 目的是把锁持有时间降到最低。
84
- let snapshot: SessionMessageV1[] = [];
87
+ let snapshot: SessionModelMessageV1[] = [];
85
88
  let snapshotTailId = "";
86
89
  await deps.withWriteLock(async () => {
87
- snapshot = (await deps.loadAll()).filter(
88
- (message) => !isSessionOperationMessage(message),
89
- );
90
+ snapshot = (await deps.loadAll()).filter(isSessionModelMessage);
90
91
  snapshotTailId =
91
92
  snapshot.length > 0
92
93
  ? String(snapshot[snapshot.length - 1].id || "")
@@ -118,11 +119,10 @@ export async function compactSessionMessagesIfNeeded(
118
119
  const older = snapshot.slice(0, compactCount);
119
120
  if (older.length === 0) return { compacted: false, reason: "nothing_to_compact" };
120
121
 
121
- operation_started = true;
122
- await publish_operation({
123
- status: "started",
124
- label: "Compacting session history",
125
- progress: 0,
122
+ action_started = true;
123
+ await publish_action({
124
+ title: "Compacting session history",
125
+ state: "running",
126
126
  });
127
127
 
128
128
  const olderTextAll = extractPlainTextFromMessages(older);
@@ -163,11 +163,10 @@ export async function compactSessionMessagesIfNeeded(
163
163
  },
164
164
  );
165
165
  summary = "(系统自动压缩:摘要生成失败,已丢弃更早历史,仅保留最近对话。)";
166
- await publish_operation({
167
- status: "progress",
168
- label: "Compacting session history",
169
- reason: "summary_fallback",
170
- progress: 0.5,
166
+ await publish_action({
167
+ title: "Compacting session history",
168
+ description: "Summary generation failed; using a fallback summary.",
169
+ state: "running",
171
170
  });
172
171
  }
173
172
 
@@ -191,7 +190,7 @@ export async function compactSessionMessagesIfNeeded(
191
190
  void snapshotTailId;
192
191
 
193
192
  const current_model_messages = current.filter(
194
- (message) => !isSessionOperationMessage(message),
193
+ isSessionModelMessage,
195
194
  );
196
195
  const currentCompactCount = resolveCompactCount(
197
196
  current_model_messages.length,
@@ -242,15 +241,11 @@ export async function compactSessionMessagesIfNeeded(
242
241
  });
243
242
  });
244
243
 
245
- if (operation_started) {
246
- await publish_operation({
247
- status: "finished",
248
- label: "Session history compacted",
249
- progress: 1,
250
- result: {
251
- archiveId,
252
- compactedCount: older.length,
253
- },
244
+ if (action_started) {
245
+ await publish_action({
246
+ title: "Session history compacted",
247
+ description: `Compacted ${String(older.length)} earlier messages into ${archiveId}.`,
248
+ state: "completed",
254
249
  });
255
250
  }
256
251
 
@@ -302,7 +297,7 @@ function estimateTokensApproxFromText(text: string): number {
302
297
  * - 统一把 user / assistant 内容线性化,作为 compact 摘要输入。
303
298
  * - tool 原始结构不会原样输出,避免把噪声日志喂给摘要模型。
304
299
  */
305
- function extractPlainTextFromMessages(messages: SessionMessageV1[]): string {
300
+ function extractPlainTextFromMessages(messages: SessionModelMessageV1[]): string {
306
301
  const lines: string[] = [];
307
302
  for (const m of messages) {
308
303
  if (!m || typeof m !== "object") continue;
@@ -9,8 +9,10 @@
9
9
 
10
10
  import type { Tool } from "ai";
11
11
  import {
12
- isSessionOperationMessage,
12
+ isSessionActionMessage,
13
+ isSessionModelMessage,
13
14
  type SessionMessageV1,
15
+ type SessionModelMessageV1,
14
16
  } from "@/executor/types/SessionMessages.js";
15
17
  import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
16
18
  import type {
@@ -49,7 +51,7 @@ export class JsonlSessionHistoryComposer implements SessionHistoryComposer {
49
51
  return tools && typeof tools === "object" ? { ...tools } : {};
50
52
  }
51
53
 
52
- private readUserSessionMessageText(message: SessionMessageV1): string {
54
+ private readUserSessionMessageText(message: SessionModelMessageV1): string {
53
55
  if (!message || typeof message !== "object" || message.role !== "user") {
54
56
  return "";
55
57
  }
@@ -76,7 +78,7 @@ export class JsonlSessionHistoryComposer implements SessionHistoryComposer {
76
78
  for (let index = messages.length - 1; index >= 0; index -= 1) {
77
79
  const item = messages[index];
78
80
  if (!item || typeof item !== "object") continue;
79
- if (item.role !== "user") continue;
81
+ if (!isSessionModelMessage(item) || item.role !== "user") continue;
80
82
  return this.readUserSessionMessageText(item) === target;
81
83
  }
82
84
  return false;
@@ -85,6 +87,8 @@ export class JsonlSessionHistoryComposer implements SessionHistoryComposer {
85
87
  private sanitizeMessages(messages: SessionMessageV1[]): SessionMessageV1[] {
86
88
  if (!Array.isArray(messages)) return [];
87
89
  return messages
90
+ .filter((message) => !isSessionActionMessage(message))
91
+ .filter(isSessionModelMessage)
88
92
  .map((message) => {
89
93
  const parts = Array.isArray(message.parts)
90
94
  ? message.parts.filter((part) => part && typeof part === "object")
@@ -94,7 +98,6 @@ export class JsonlSessionHistoryComposer implements SessionHistoryComposer {
94
98
  parts,
95
99
  };
96
100
  })
97
- .filter((message) => !isSessionOperationMessage(message))
98
101
  .filter((message) => Array.isArray(message.parts) && message.parts.length > 0);
99
102
  }
100
103
 
@@ -187,7 +187,7 @@ export async function hydrateFileUrlPartsForModel(
187
187
  continue;
188
188
  }
189
189
 
190
- const nextParts: SessionMessageV1["parts"] = [];
190
+ const nextParts: SessionModelMessageV1["parts"] = [];
191
191
  let changed = false;
192
192
  for (const part of parts) {
193
193
  if (!isFileUIPart(part as FileUIPart)) {
@@ -196,7 +196,7 @@ export async function hydrateFileUrlPartsForModel(
196
196
  }
197
197
  const nextPart = await hydrateFileUrlPart(part as FileUIPart, projectRoot);
198
198
  if (nextPart !== part) changed = true;
199
- nextParts.push(nextPart as SessionMessageV1["parts"][number]);
199
+ nextParts.push(nextPart as SessionModelMessageV1["parts"][number]);
200
200
  }
201
201
 
202
202
  out.push(changed ? { ...message, parts: nextParts } : message);
@@ -28,7 +28,7 @@ import {
28
28
  *
29
29
  * 关键点(中文)
30
30
  * - 用途:从 onStepCallback 返回的消息里挑出可并入推理上下文的 user 文本。
31
- * - 输入:任意 SessionMessageV1[](可能混有 assistant/tool/空消息)。
31
+ * - 输入:任意 SessionMessageV1[](可能混有 assistant/tool/action/空消息)。
32
32
  * - 输出:只包含“非空 user 文本”的消息数组。
33
33
  */
34
34
  export function pickMergedUserMessages(
@@ -40,7 +40,7 @@ export function pickMergedUserMessages(
40
40
  // 逐条过滤消息。
41
41
  return messages.filter((message) => {
42
42
  // 防御 1:消息必须是对象。
43
- if (!message || typeof message !== "object") return false;
43
+ if (!isSessionModelMessage(message)) return false;
44
44
 
45
45
  // 防御 2:只接受 user 角色。
46
46
  if (message.role !== "user") return false;
@@ -80,7 +80,7 @@ export async function toModelMessages(
80
80
  // 空输入快速返回,避免调用转换器的额外开销。
81
81
  if (!Array.isArray(messages) || messages.length === 0) return [];
82
82
 
83
- // operation message 只服务前端时间线,不能传入模型。
83
+ // action message 只服务前端时间线,不能传入模型。
84
84
  const model_messages = messages.filter(isSessionModelMessage);
85
85
  if (model_messages.length === 0) return [];
86
86
 
@@ -7,7 +7,7 @@
7
7
  * - 每个 step 前插入 `step-start`,便于在 UI 中保留多 step 边界。
8
8
  */
9
9
 
10
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
10
+ import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
11
11
 
12
12
  type UnknownRecord = Record<string, unknown>;
13
13
 
@@ -112,8 +112,8 @@ export function buildSessionStepParts(params: {
112
112
  stepResult?: unknown;
113
113
  text: string;
114
114
  visibility?: "visible" | "internal";
115
- }): SessionMessageV1["parts"] {
116
- const out: SessionMessageV1["parts"] = [];
115
+ }): SessionModelMessageV1["parts"] {
116
+ const out: SessionModelMessageV1["parts"] = [];
117
117
  const stepRecord = toRecord(params.stepResult) || {};
118
118
  const toolCalls = Array.isArray(stepRecord.toolCalls) ? stepRecord.toolCalls : [];
119
119
  const toolResults = Array.isArray(stepRecord.toolResults)
@@ -171,7 +171,7 @@ export function buildSessionStepParts(params: {
171
171
  state: "output-available",
172
172
  output,
173
173
  }),
174
- } as SessionMessageV1["parts"][number],
174
+ } as SessionModelMessageV1["parts"][number],
175
175
  );
176
176
  }
177
177
 
@@ -183,7 +183,7 @@ export function buildSessionStepParts(params: {
183
183
  {
184
184
  type: toDataPartType(type),
185
185
  data: resolveAcpEventData(acpEvent),
186
- } as SessionMessageV1["parts"][number],
186
+ } as SessionModelMessageV1["parts"][number],
187
187
  );
188
188
  }
189
189
 
@@ -9,7 +9,8 @@
9
9
 
10
10
  import { generateId } from "@/utils/Id.js";
11
11
  import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
12
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
12
+ import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
13
+ import { isSessionModelMessage } from "@/executor/types/SessionMessages.js";
13
14
 
14
15
  interface ExecutorInflightServiceOptions {
15
16
  /**
@@ -51,7 +52,7 @@ export class ExecutorInflightService {
51
52
  * 把 step/tool 过程增量写入当前运行中的 assistant 快照。
52
53
  */
53
54
  async append_assistant_step_parts(
54
- parts: SessionMessageV1["parts"],
55
+ parts: SessionModelMessageV1["parts"],
55
56
  ): Promise<void> {
56
57
  const normalized_parts = Array.isArray(parts)
57
58
  ? parts.filter((part) => part && typeof part === "object")
@@ -59,11 +60,14 @@ export class ExecutorInflightService {
59
60
  if (normalized_parts.length === 0) return;
60
61
 
61
62
  const current_inflight = await this.history_store.readInflight();
62
- const next_message: SessionMessageV1 = current_inflight
63
+ const normalized_inflight = isSessionModelMessage(current_inflight)
64
+ ? current_inflight
65
+ : null;
66
+ const next_message: SessionModelMessageV1 = normalized_inflight
63
67
  ? {
64
- ...current_inflight,
68
+ ...normalized_inflight,
65
69
  metadata: {
66
- ...(current_inflight.metadata || {
70
+ ...(normalized_inflight.metadata || {
67
71
  v: 1 as const,
68
72
  ts: Date.now(),
69
73
  sessionId: this.session_id,
@@ -74,8 +78,8 @@ export class ExecutorInflightService {
74
78
  kind: "normal",
75
79
  },
76
80
  parts: [
77
- ...(Array.isArray(current_inflight.parts)
78
- ? current_inflight.parts
81
+ ...(Array.isArray(normalized_inflight.parts)
82
+ ? normalized_inflight.parts
79
83
  : []),
80
84
  ...normalized_parts,
81
85
  ],