@downcity/agent 1.1.196 → 1.1.199

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 (77) hide show
  1. package/bin/executor/composer/context/LocalSessionContextComposer.d.ts +2 -2
  2. package/bin/executor/composer/context/LocalSessionContextComposer.d.ts.map +1 -1
  3. package/bin/executor/composer/context/LocalSessionContextComposer.js.map +1 -1
  4. package/bin/executor/composer/context/SessionContextComposer.d.ts +2 -2
  5. package/bin/executor/composer/context/SessionContextComposer.d.ts.map +1 -1
  6. package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
  7. package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
  8. package/bin/executor/core-engine/CoreEngineSignals.d.ts +4 -4
  9. package/bin/executor/core-engine/CoreEngineSignals.d.ts.map +1 -1
  10. package/bin/executor/core-engine/CoreEngineSignals.js.map +1 -1
  11. package/bin/executor/core-engine/CoreEngineUiStreamCollector.d.ts +3 -3
  12. package/bin/executor/core-engine/CoreEngineUiStreamCollector.d.ts.map +1 -1
  13. package/bin/executor/core-engine/CoreEngineUiStreamCollector.js.map +1 -1
  14. package/bin/executor/messages/AssistantResultPersistence.d.ts +3 -3
  15. package/bin/executor/messages/AssistantResultPersistence.d.ts.map +1 -1
  16. package/bin/executor/messages/AssistantResultPersistence.js.map +1 -1
  17. package/bin/executor/messages/SessionAttachmentMapper.d.ts +3 -3
  18. package/bin/executor/messages/SessionAttachmentMapper.d.ts.map +1 -1
  19. package/bin/executor/messages/SessionAttachmentMapper.js.map +1 -1
  20. package/bin/executor/messages/SessionMessageCodec.d.ts.map +1 -1
  21. package/bin/executor/messages/SessionMessageCodec.js +2 -2
  22. package/bin/executor/messages/SessionMessageCodec.js.map +1 -1
  23. package/bin/executor/messages/SessionMessageLog.d.ts +3 -3
  24. package/bin/executor/messages/SessionMessageLog.d.ts.map +1 -1
  25. package/bin/executor/messages/SessionMessageLog.js.map +1 -1
  26. package/bin/executor/messages/UserVisibleText.d.ts +2 -2
  27. package/bin/executor/messages/UserVisibleText.d.ts.map +1 -1
  28. package/bin/executor/messages/UserVisibleText.js +5 -0
  29. package/bin/executor/messages/UserVisibleText.js.map +1 -1
  30. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
  31. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +3 -2
  32. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
  33. package/bin/executor/types/SessionMessages.d.ts +24 -4
  34. package/bin/executor/types/SessionMessages.d.ts.map +1 -1
  35. package/bin/executor/types/SessionMessages.js +12 -2
  36. package/bin/executor/types/SessionMessages.js.map +1 -1
  37. package/bin/executor/types/SessionRun.d.ts +2 -3
  38. package/bin/executor/types/SessionRun.d.ts.map +1 -1
  39. package/bin/session/browse/Browse.d.ts.map +1 -1
  40. package/bin/session/browse/Browse.js +9 -2
  41. package/bin/session/browse/Browse.js.map +1 -1
  42. package/bin/session/runtime/SessionPromptRuntime.d.ts +2 -2
  43. package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
  44. package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
  45. package/bin/session/services/SessionStateService.d.ts +2 -2
  46. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  47. package/bin/session/services/SessionStateService.js +2 -1
  48. package/bin/session/services/SessionStateService.js.map +1 -1
  49. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  50. package/bin/session/services/SessionTurnService.js.map +1 -1
  51. package/bin/session/storage/Persistence.d.ts +2 -2
  52. package/bin/session/storage/Persistence.d.ts.map +1 -1
  53. package/bin/session/storage/Persistence.js.map +1 -1
  54. package/bin/types/sdk/AgentSessionTurn.d.ts +2 -2
  55. package/bin/types/sdk/AgentSessionTurn.d.ts.map +1 -1
  56. package/package.json +3 -3
  57. package/scripts/session-operation-message.test.mjs +23 -8
  58. package/src/executor/composer/context/LocalSessionContextComposer.ts +5 -2
  59. package/src/executor/composer/context/SessionContextComposer.ts +5 -2
  60. package/src/executor/core-engine/CoreEngineRunner.ts +7 -4
  61. package/src/executor/core-engine/CoreEngineSignals.ts +7 -7
  62. package/src/executor/core-engine/CoreEngineUiStreamCollector.ts +5 -5
  63. package/src/executor/messages/AssistantResultPersistence.ts +6 -3
  64. package/src/executor/messages/SessionAttachmentMapper.ts +10 -7
  65. package/src/executor/messages/SessionMessageCodec.ts +2 -4
  66. package/src/executor/messages/SessionMessageLog.ts +5 -3
  67. package/src/executor/messages/UserVisibleText.ts +8 -2
  68. package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +4 -2
  69. package/src/executor/types/SessionMessages.ts +37 -5
  70. package/src/executor/types/SessionRun.ts +6 -3
  71. package/src/session/browse/Browse.ts +13 -2
  72. package/src/session/runtime/SessionPromptRuntime.ts +6 -3
  73. package/src/session/services/SessionStateService.ts +3 -2
  74. package/src/session/services/SessionTurnService.ts +2 -1
  75. package/src/session/storage/Persistence.ts +5 -2
  76. package/src/types/sdk/AgentSessionTurn.ts +2 -2
  77. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Persistence.d.ts","sourceRoot":"","sources":["../../../src/session/storage/Persistence.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAM9E;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,aAAa,EAAE,0BAA0B,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,+BACf,SAAQ,0BAA0B;IAClC;;OAEG;IACH,QAAQ,EAAE;QACR,sBAAsB,CAAC,MAAM,EAAE;YAC7B;;eAEG;YACH,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;YAClC;;eAEG;YACH,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACnB,CAAC;IACF;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,+BAA+B,GACtC,OAAO,CAAC,IAAI,CAAC,CASf"}
1
+ {"version":3,"file":"Persistence.d.ts","sourceRoot":"","sources":["../../../src/session/storage/Persistence.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAM9E;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,aAAa,EAAE,0BAA0B,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,+BACf,SAAQ,0BAA0B;IAClC;;OAEG;IACH,QAAQ,EAAE;QACR,sBAAsB,CAAC,MAAM,EAAE;YAC7B;;eAEG;YACH,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;YAClC;;eAEG;YACH,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACnB,CAAC;IACF;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACjD;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,+BAA+B,GACtC,OAAO,CAAC,IAAI,CAAC,CASf"}
@@ -1 +1 @@
1
- {"version":3,"file":"Persistence.js","sourceRoot":"","sources":["../../../src/session/storage/Persistence.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAC;AAGvF,OAAO,EAAE,sBAAsB,EAAE,MAAM,mDAAmD,CAAC;AAE3F,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,+BAA+B,CAAC;AAsDvC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAkC;IAElC,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC;QACxC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC,CAAC;IACH,MAAM,IAAI,GAAyB;QACjC,GAAG,OAAO;QACV,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EACP,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;QACxE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU;YACjC,CAAC,CAAC;gBACE,UAAU,EAAE,MAAM,CAAC,aAAa,CAAC,UAAU;aAC5C;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;IACF,MAAM,oBAAoB,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAAuC;IAEvC,MAAM,sBAAsB,CAAC;QAC3B,MAAM,EAAE,MAAM,CAAC,QAAQ;QACvB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,YAAY,EAAE,MAAM,CAAC,gBAAgB;YACnC,CAAC,CAAC,wBAAwB,CAAC,MAAM,CAAC,gBAAgB,CAAC;YACnD,CAAC,CAAC,SAAS;KACd,CAAC,CAAC;IACH,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"Persistence.js","sourceRoot":"","sources":["../../../src/session/storage/Persistence.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAC;AAMvF,OAAO,EAAE,sBAAsB,EAAE,MAAM,mDAAmD,CAAC;AAE3F,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,+BAA+B,CAAC;AAsDvC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAkC;IAElC,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC;QACxC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC,CAAC;IACH,MAAM,IAAI,GAAyB;QACjC,GAAG,OAAO;QACV,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EACP,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;QACxE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU;YACjC,CAAC,CAAC;gBACE,UAAU,EAAE,MAAM,CAAC,aAAa,CAAC,UAAU;aAC5C;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;IACF,MAAM,oBAAoB,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,IAAI,EAAE,IAAI;KACX,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAAuC;IAEvC,MAAM,sBAAsB,CAAC;QAC3B,MAAM,EAAE,MAAM,CAAC,QAAQ;QACvB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,YAAY,EAAE,MAAM,CAAC,gBAAgB;YACnC,CAAC,CAAC,wBAAwB,CAAC,MAAM,CAAC,gBAAgB,CAAC;YACnD,CAAC,CAAC,SAAS;KACd,CAAC,CAAC;IACH,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
@@ -6,7 +6,7 @@
6
6
  * - 同一个 turn 可以吸收多条 prompt,因此多个 prompt 可能返回相同 `id` 的 turn handle。
7
7
  * - 增量过程继续通过 `session.subscribe()` 观察;turn handle 只负责等待最终完成结果。
8
8
  */
9
- import type { SessionMessageV1 } from "../../executor/types/SessionMessages.js";
9
+ import type { SessionModelMessageV1 } from "../../executor/types/SessionMessages.js";
10
10
  /**
11
11
  * 单个 Session turn 的最终结果。
12
12
  */
@@ -30,7 +30,7 @@ export interface AgentSessionTurnResult {
30
30
  * - `turn.finished` 返回的结果以这条消息作为最终事实源。
31
31
  * - transport / runtime service 若需要读取最终结构化输出,应优先消费这里。
32
32
  */
33
- assistantMessage?: SessionMessageV1;
33
+ assistantMessage?: SessionModelMessageV1;
34
34
  /**
35
35
  * 当前 turn 失败时的错误文本。
36
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"AgentSessionTurn.d.ts","sourceRoot":"","sources":["../../../src/types/sdk/AgentSessionTurn.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAEpC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;;;;OAMG;IACH,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAEtC;;;;;;OAMG;IACH,QAAQ,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC3C"}
1
+ {"version":3,"file":"AgentSessionTurn.d.ts","sourceRoot":"","sources":["../../../src/types/sdk/AgentSessionTurn.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IAEzC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;;;;OAMG;IACH,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAEtC;;;;;;OAMG;IACH,QAAQ,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC3C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@downcity/agent",
3
- "version": "1.1.196",
3
+ "version": "1.1.199",
4
4
  "type": "module",
5
5
  "description": "Downcity Agent 运行时 — 单 Agent 执行壳与本机 RPC 能力",
6
6
  "main": "./bin/index.js",
@@ -17,8 +17,8 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@ai-sdk/openai-compatible": "^2.0.48",
20
- "@downcity/shell": "^0.1.66",
21
- "@downcity/type": "0.1.99",
20
+ "@downcity/shell": "^0.1.68",
21
+ "@downcity/type": "0.1.101",
22
22
  "ai": "^6.0.193",
23
23
  "commander": "^15.0.0",
24
24
  "dotenv": "^17.4.2",
@@ -2,7 +2,7 @@
2
2
  * @file 验证 operation message 的持久化、模型输入过滤与 timeline 投影。
3
3
  *
4
4
  * 关键点(中文)
5
- * - operation message 会作为 UIMessage 写入 JSONL。
5
+ * - operation message 会作为 operation role 消息写入 JSONL。
6
6
  * - history composer 组装 LLM 输入时必须过滤 operation message。
7
7
  * - timeline view 可以把 operation message 投影成前端可识别的 operation 事件。
8
8
  */
@@ -42,8 +42,8 @@ test("operation message is persisted but filtered from LLM history", async () =>
42
42
  operation: {
43
43
  operationId: "op-1",
44
44
  name: "compacting",
45
- status: "started",
46
- label: "Compacting session history",
45
+ status: "finished",
46
+ label: "Session history compacted",
47
47
  turnId: "turn-1",
48
48
  },
49
49
  metadata: { sessionId: session_id },
@@ -60,6 +60,7 @@ test("operation message is persisted but filtered from LLM history", async () =>
60
60
 
61
61
  const persisted_messages = await store.list();
62
62
  assert.equal(persisted_messages.length, 3);
63
+ assert.equal(persisted_messages[1].role, "operation");
63
64
  assert.equal(persisted_messages[1].metadata.kind, "operation");
64
65
  assert.equal(persisted_messages[1].metadata.operation.operationId, "op-1");
65
66
 
@@ -80,8 +81,22 @@ test("operation message is persisted but filtered from LLM history", async () =>
80
81
  assert.equal(timeline_events[0].role, "operation");
81
82
  assert.equal(timeline_events[0].operationId, "op-1");
82
83
  assert.equal(timeline_events[0].operationName, "compacting");
83
- assert.equal(timeline_events[0].operationStatus, "started");
84
- assert.equal(timeline_events[0].text, "Compacting session history");
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
+ }),
98
+ );
99
+ assert.deepEqual(hidden_started_events, []);
85
100
  });
86
101
 
87
102
  test("session.set persists and publishes model-switching operations", async () => {
@@ -120,7 +135,7 @@ test("session.set persists and publishes model-switching operations", async () =
120
135
  );
121
136
  assert.deepEqual(
122
137
  operation_events.map((event) => `${event.operationName}:${event.operationStatus}`),
123
- ["model-switching:started", "model-switching:finished"],
138
+ ["model-switching:finished"],
124
139
  );
125
140
  } finally {
126
141
  await agent.dispose();
@@ -168,14 +183,14 @@ test("session.fork persists and publishes history-forking operations on source s
168
183
  );
169
184
  assert.deepEqual(
170
185
  source_fork_operations.map((event) => event.operationStatus),
171
- ["started", "finished"],
186
+ ["finished"],
172
187
  );
173
188
 
174
189
  const forked_history = await forked.history({ view: "timeline" });
175
190
  const forked_model_operations = forked_history.items.filter(
176
191
  (item) => item.operationName === "model-switching",
177
192
  );
178
- assert.equal(forked_model_operations.length, 2);
193
+ assert.equal(forked_model_operations.length, 1);
179
194
  } finally {
180
195
  await agent.dispose();
181
196
  }
@@ -12,7 +12,10 @@ import type {
12
12
  SessionContextComposer,
13
13
  SessionContextComposeResult,
14
14
  } from "@executor/composer/context/SessionContextComposer.js";
15
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
15
+ import type {
16
+ SessionMessageV1,
17
+ SessionModelMessageV1,
18
+ } from "@/executor/types/SessionMessages.js";
16
19
  import type { SessionSystemMessage } from "@/executor/types/SessionPrompts.js";
17
20
  import type { Tool } from "ai";
18
21
  import type { SessionRunContext } from "@/types/executor/SessionRunContext.js";
@@ -147,7 +150,7 @@ export class LocalSessionContextComposer implements SessionContextComposer {
147
150
  buildFallbackAssistantMessage(
148
151
  text: string,
149
152
  run_context: SessionRunContext,
150
- ): SessionMessageV1 {
153
+ ): SessionModelMessageV1 {
151
154
  const sessionId = String(
152
155
  run_context.sessionId || this.sessionId || "",
153
156
  ).trim();
@@ -7,7 +7,10 @@
7
7
  */
8
8
 
9
9
  import type { ModelMessage, Tool } from "ai";
10
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
10
+ import type {
11
+ SessionMessageV1,
12
+ SessionModelMessageV1,
13
+ } from "@/executor/types/SessionMessages.js";
11
14
  import type { SessionSystemMessage } from "@/executor/types/SessionPrompts.js";
12
15
  import type { SessionRunContext } from "@/types/executor/SessionRunContext.js";
13
16
 
@@ -76,5 +79,5 @@ export interface SessionContextComposer {
76
79
  buildFallbackAssistantMessage(
77
80
  text: string,
78
81
  run_context: SessionRunContext,
79
- ): SessionMessageV1;
82
+ ): SessionModelMessageV1;
80
83
  }
@@ -41,7 +41,10 @@ import type {
41
41
  SessionExecuteInput,
42
42
  SessionRunResult,
43
43
  } from "@/executor/types/SessionRun.js";
44
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
44
+ import type {
45
+ SessionMessageV1,
46
+ SessionModelMessageV1,
47
+ } from "@/executor/types/SessionMessages.js";
45
48
 
46
49
  const TURN_STOPPED_MESSAGE = "Turn stopped";
47
50
 
@@ -61,9 +64,9 @@ function build_file_part_key(part: FileUIPart): string {
61
64
  * 把 tool/plugin 运行期产生的 file parts 并入最终 assistant UIMessage。
62
65
  */
63
66
  function mergePendingAssistantFileParts(
64
- message: SessionMessageV1,
67
+ message: SessionModelMessageV1,
65
68
  parts: FileUIPart[],
66
- ): SessionMessageV1 {
69
+ ): SessionModelMessageV1 {
67
70
  if (!Array.isArray(parts) || parts.length === 0) return message;
68
71
  const current_parts = Array.isArray(message.parts) ? message.parts : [];
69
72
  const seen = new Set<string>();
@@ -151,7 +154,7 @@ export class CoreEngineRunner {
151
154
  : [];
152
155
  const tools = input.execute_input.tools;
153
156
  let last_observed_stream_error: unknown = undefined;
154
- let final_assistant_ui_message: SessionMessageV1 | null = null;
157
+ let final_assistant_ui_message: SessionModelMessageV1 | null = null;
155
158
 
156
159
  try {
157
160
  const message_state = await CoreEngineMessageState.create({
@@ -12,7 +12,7 @@ import {
12
12
  isTextUIPart,
13
13
  isToolUIPart,
14
14
  } from "ai";
15
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
15
+ import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
16
16
  import type { JsonObject } from "@/types/common/Json.js";
17
17
 
18
18
  /**
@@ -192,7 +192,7 @@ export function summarizeStepForDebug(stepResult: unknown): JsonObject {
192
192
  * 汇总最终 assistant UI 消息的调试摘要。
193
193
  */
194
194
  export function summarizeUiMessageForDebug(
195
- message: SessionMessageV1 | null | undefined,
195
+ message: SessionModelMessageV1 | null | undefined,
196
196
  ): JsonObject {
197
197
  const parts = Array.isArray(message?.parts) ? message.parts : [];
198
198
  const text = parts
@@ -230,9 +230,9 @@ export function summarizeUiMessageForDebug(
230
230
  * - 多 step 场景下,最终 assistant message 需要把各 step 的 UI part 串起来。
231
231
  */
232
232
  export function mergeAssistantUiMessages(
233
- base: SessionMessageV1 | null,
234
- incoming: SessionMessageV1,
235
- ): SessionMessageV1 {
233
+ base: SessionModelMessageV1 | null,
234
+ incoming: SessionModelMessageV1,
235
+ ): SessionModelMessageV1 {
236
236
  if (!base) return incoming;
237
237
  const baseMetadata = base.metadata;
238
238
  const incomingMetadata = incoming.metadata;
@@ -254,7 +254,7 @@ export function mergeAssistantUiMessages(
254
254
  }
255
255
 
256
256
  function pickIncompleteToolParts(
257
- message: SessionMessageV1 | null | undefined,
257
+ message: SessionModelMessageV1 | null | undefined,
258
258
  ): Array<{
259
259
  toolName: string;
260
260
  state: string;
@@ -304,7 +304,7 @@ export function buildIncompleteResponseRecoveryNudge(
304
304
  */
305
305
  export function detectIncompleteResponse(params: {
306
306
  stepResult: unknown;
307
- assistantMessage: SessionMessageV1 | null | undefined;
307
+ assistantMessage: SessionModelMessageV1 | null | undefined;
308
308
  }): {
309
309
  reason: string;
310
310
  details: JsonObject;
@@ -10,7 +10,7 @@
10
10
  import type { streamText } from "ai";
11
11
  import type { Logger } from "@/utils/logger/Logger.js";
12
12
  import type { JsonObject } from "@/types/common/Json.js";
13
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
13
+ import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
14
14
  import type { SessionUiMessageChunkCallback } from "@/executor/types/SessionRun.js";
15
15
  import { generateId } from "@/utils/Id.js";
16
16
  import {
@@ -37,7 +37,7 @@ export async function collectFinalAssistantMessageFromUiStream(params: {
37
37
  /**
38
38
  * 构造 fallback assistant 消息的工厂函数。
39
39
  */
40
- buildFallbackAssistantMessage: (text: string) => SessionMessageV1;
40
+ buildFallbackAssistantMessage: (text: string) => SessionModelMessageV1;
41
41
  /**
42
42
  * UI stream chunk 回调。
43
43
  */
@@ -50,12 +50,12 @@ export async function collectFinalAssistantMessageFromUiStream(params: {
50
50
  * - 此时仍应尽量用已经收到的 text delta 构造可持久化 assistant 消息。
51
51
  */
52
52
  abortSignal?: AbortSignal;
53
- }): Promise<SessionMessageV1> {
54
- let streamedAssistantMessage: SessionMessageV1 | null = null;
53
+ }): Promise<SessionModelMessageV1> {
54
+ let streamedAssistantMessage: SessionModelMessageV1 | null = null;
55
55
  let uiFinishSummary: JsonObject | null = null;
56
56
  let streamed_text = "";
57
57
 
58
- const uiStream = params.result.toUIMessageStream<SessionMessageV1>({
58
+ const uiStream = params.result.toUIMessageStream<SessionModelMessageV1>({
59
59
  // 关键点(中文):SDK stream 需要 reasoning 旁路事件时可直接消费;最终落盘仍由 responseMessage 收敛。
60
60
  originalMessages: [],
61
61
  generateMessageId: () => `a:${params.sessionId}:${generateId()}`,
@@ -7,7 +7,10 @@
7
7
  * - 运行中 step/tool 的持久化由 inflight 快照承担;run 结束时只关心最终 assistant 如何收口。
8
8
  */
9
9
 
10
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
10
+ import type {
11
+ SessionMessageV1,
12
+ SessionModelMessageV1,
13
+ } from "@/executor/types/SessionMessages.js";
11
14
  import { resolveAssistantMessageForPersistence } from "@/executor/messages/UserVisibleText.js";
12
15
 
13
16
  /**
@@ -40,7 +43,7 @@ export interface PersistAssistantResultParams {
40
43
  /**
41
44
  * 本轮执行得到的 assistant message。
42
45
  */
43
- assistantMessage: SessionMessageV1 | null | undefined;
46
+ assistantMessage: SessionModelMessageV1 | null | undefined;
44
47
  /**
45
48
  * 可选兜底文本。
46
49
  *
@@ -65,7 +68,7 @@ export function buildAssistantResultPersistencePayload(
65
68
  /**
66
69
  * 已构造好的完整 assistant message。
67
70
  */
68
- message?: SessionMessageV1 | null;
71
+ message?: SessionModelMessageV1 | null;
69
72
  /**
70
73
  * 允许写入的兜底文本。
71
74
  */
@@ -17,7 +17,10 @@ import {
17
17
  type FileUIPart,
18
18
  } from "ai";
19
19
  import type { SessionUserMessagePart } from "@/types/sdk/AgentSessionPrompt.js";
20
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
20
+ import type {
21
+ SessionMessageV1,
22
+ SessionModelMessageV1,
23
+ } from "@/executor/types/SessionMessages.js";
21
24
  import { parseChatMessageMarkup } from "@/executor/messages/ChatMessageMarkup.js";
22
25
 
23
26
  /**
@@ -171,12 +174,12 @@ export async function hydrateUserPromptFileParts(
171
174
  * - 新历史保留 Agent 根目录相对路径,旧历史的 `file://` 仍继续兼容。
172
175
  */
173
176
  export async function hydrateFileUrlPartsForModel(
174
- messages: SessionMessageV1[],
177
+ messages: SessionModelMessageV1[],
175
178
  projectRoot?: string,
176
- ): Promise<SessionMessageV1[]> {
179
+ ): Promise<SessionModelMessageV1[]> {
177
180
  if (!Array.isArray(messages) || messages.length === 0) return messages;
178
181
 
179
- const out: SessionMessageV1[] = [];
182
+ const out: SessionModelMessageV1[] = [];
180
183
  for (const message of messages) {
181
184
  const parts = Array.isArray(message?.parts) ? message.parts : [];
182
185
  if (!parts.some((part) => isFileUIPart(part as FileUIPart))) {
@@ -206,13 +209,13 @@ export async function hydrateFileUrlPartsForModel(
206
209
  * 在 user 消息上注入 FileUIPart,以便多模态模型直接消费本地附件。
207
210
  */
208
211
  export async function injectFilePartsFromAttachments(
209
- messages: SessionMessageV1[],
212
+ messages: SessionModelMessageV1[],
210
213
  projectRoot?: string,
211
- ): Promise<SessionMessageV1[]> {
214
+ ): Promise<SessionModelMessageV1[]> {
212
215
  if (!Array.isArray(messages) || messages.length === 0) return messages;
213
216
 
214
217
  const root = path.resolve(String(projectRoot || "").trim() || process.cwd());
215
- const out: SessionMessageV1[] = [];
218
+ const out: SessionModelMessageV1[] = [];
216
219
 
217
220
  for (const message of messages) {
218
221
  if (!message || typeof message !== "object" || message.role !== "user") {
@@ -15,7 +15,7 @@ import {
15
15
  type ToolSet,
16
16
  } from "ai";
17
17
  import {
18
- isSessionOperationMessage,
18
+ isSessionModelMessage,
19
19
  type SessionMessageV1,
20
20
  } from "@/executor/types/SessionMessages.js";
21
21
  import {
@@ -81,9 +81,7 @@ export async function toModelMessages(
81
81
  if (!Array.isArray(messages) || messages.length === 0) return [];
82
82
 
83
83
  // operation message 只服务前端时间线,不能传入模型。
84
- const model_messages = messages.filter(
85
- (message) => !isSessionOperationMessage(message),
86
- );
84
+ const model_messages = messages.filter(isSessionModelMessage);
87
85
  if (model_messages.length === 0) return [];
88
86
 
89
87
  // 第一步(中文):在 user 消息上注入 file parts(多模态附件)。
@@ -9,12 +9,14 @@
9
9
 
10
10
  import { isTextUIPart } from "ai";
11
11
  import type { Logger } from "@/utils/logger/Logger.js";
12
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
12
+ import type { SessionModelMessageV1 } from "@/executor/types/SessionMessages.js";
13
13
 
14
14
  /**
15
15
  * 从 UI message 中提取 assistant 文本部分。
16
16
  */
17
- export function extractAssistantTextForLog(message: SessionMessageV1): string {
17
+ export function extractAssistantTextForLog(
18
+ message: SessionModelMessageV1,
19
+ ): string {
18
20
  if (!Array.isArray(message.parts)) return "";
19
21
  return message.parts
20
22
  .filter(isTextUIPart)
@@ -28,7 +30,7 @@ export function extractAssistantTextForLog(message: SessionMessageV1): string {
28
30
  */
29
31
  export async function logAssistantMessageNow(
30
32
  logger: Logger,
31
- message: SessionMessageV1,
33
+ message: SessionModelMessageV1,
32
34
  ): Promise<void> {
33
35
  const text = extractAssistantTextForLog(message) || "-";
34
36
  const normalized = text.replace(/\r\n/g, "\n");
@@ -7,7 +7,11 @@
7
7
  * - 因此需要一个稳定的、与 Agent 解耦的提取逻辑(属于 chat/egress 语义)
8
8
  */
9
9
 
10
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
10
+ import {
11
+ isSessionModelMessage,
12
+ type SessionMessageV1,
13
+ type SessionModelMessageV1,
14
+ } from "@/executor/types/SessionMessages.js";
11
15
  import {
12
16
  extractTextFromUiMessage,
13
17
  extractToolCallsFromUiMessage,
@@ -22,8 +26,9 @@ import {
22
26
  */
23
27
  export function resolveAssistantMessageForPersistence(
24
28
  message: SessionMessageV1 | null | undefined,
25
- ): SessionMessageV1 | null {
29
+ ): SessionModelMessageV1 | null {
26
30
  if (!message || typeof message !== "object") return null;
31
+ if (!isSessionModelMessage(message)) return null;
27
32
  return message;
28
33
  }
29
34
 
@@ -37,6 +42,7 @@ export function resolveAssistantMessageForPersistence(
37
42
  export function pickLastSuccessfulChatSendText(
38
43
  message: SessionMessageV1 | null | undefined,
39
44
  ): string {
45
+ if (!isSessionModelMessage(message)) return "";
40
46
  const toolCalls = extractToolCallsFromUiMessage(message);
41
47
  // 关键点(中文):优先从 chat_send 的 input.text 还原"用户可见回复"。
42
48
  for (let i = toolCalls.length - 1; i >= 0; i -= 1) {
@@ -218,7 +218,9 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
218
218
  if (!input || typeof input !== "object") return null;
219
219
  const candidate = input as Partial<SessionMessageV1>;
220
220
  const role = String(candidate.role || "");
221
- if (role !== "user" && role !== "assistant") return null;
221
+ if (role !== "user" && role !== "assistant" && role !== "operation") {
222
+ return null;
223
+ }
222
224
  if (!Array.isArray(candidate.parts)) return null;
223
225
  return candidate as SessionMessageV1;
224
226
  }
@@ -687,7 +689,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
687
689
  const { ts, ...metadata } = input.metadata;
688
690
  return {
689
691
  id: input.id || `op:${this.sessionId}:${operation.operationId}:${generateId()}`,
690
- role: "assistant",
692
+ role: "operation",
691
693
  metadata: {
692
694
  v: 1,
693
695
  ts: typeof ts === "number" ? ts : Date.now(),
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 关键点(中文)
5
5
  * - 这里统一描述 session 消息落盘格式与元信息结构。
6
- * - session 的唯一事实源是 `UIMessage[]`,不会再维护第二套平行消息结构。
6
+ * - session 的唯一事实源是消息 JSONL,模型消息继续使用 AI SDK `UIMessage`。
7
7
  * - 这些类型会被 history Store、history Composer、compact、control UI、task runtime 共同复用。
8
8
  */
9
9
 
@@ -16,8 +16,9 @@ import type { AgentSessionOperationRecord } from "@/types/sdk/AgentSessionOperat
16
16
  *
17
17
  * 关键点(中文)
18
18
  * - 持久化存储在 `.downcity/agents/<encodedAgentId>/sessions/<encodedSessionId>/messages/messages.jsonl`
19
- * - 默认只存 `role=user|assistant`
19
+ * - 默认只存 `role=user|assistant|operation`
20
20
  * - compact 会把更早消息压缩为一条 `assistant` 摘要消息
21
+ * - operation 只用于 UI timeline,不进入 LLM 输入
21
22
  */
22
23
  export type SessionMessageKind = "normal" | "summary" | "operation";
23
24
 
@@ -92,9 +93,26 @@ export type SessionMetadataV1 = {
92
93
  };
93
94
 
94
95
  /**
95
- * Session UI 消息结构。
96
+ * 模型可消费的 Session UI 消息结构。
96
97
  */
97
- export type SessionMessageV1 = UIMessage<SessionMetadataV1>;
98
+ export type SessionModelMessageV1 = UIMessage<SessionMetadataV1>;
99
+
100
+ /**
101
+ * operation 角色的 Session 消息结构。
102
+ *
103
+ * 关键点(中文)
104
+ * - `operation` 不是 AI SDK 原生模型 role。
105
+ * - 它只存在于 JSONL history 与前端 timeline,进入 LLM 前必须过滤。
106
+ */
107
+ export type SessionOperationMessageV1 = Omit<SessionModelMessageV1, "role"> & {
108
+ /** 消息角色固定为 `operation`。 */
109
+ role: "operation";
110
+ };
111
+
112
+ /**
113
+ * Session 持久化消息结构。
114
+ */
115
+ export type SessionMessageV1 = SessionModelMessageV1 | SessionOperationMessageV1;
98
116
 
99
117
  /**
100
118
  * user 角色的 Session 消息结构。
@@ -112,5 +130,19 @@ export function isSessionOperationMessage(
112
130
  ): boolean {
113
131
  if (!message || typeof message !== "object") return false;
114
132
  const metadata = (message.metadata || null) as SessionMetadataV1 | null;
115
- return metadata?.kind === "operation" || metadata?.source === "operation";
133
+ return (
134
+ message.role === "operation" ||
135
+ metadata?.kind === "operation" ||
136
+ metadata?.source === "operation"
137
+ );
138
+ }
139
+
140
+ /**
141
+ * 判断一条消息是否为模型可消费的 UIMessage。
142
+ */
143
+ export function isSessionModelMessage(
144
+ message: SessionMessageV1 | null | undefined,
145
+ ): message is SessionModelMessageV1 {
146
+ if (!message || typeof message !== "object") return false;
147
+ return message.role !== "operation" && !isSessionOperationMessage(message);
116
148
  }
@@ -8,8 +8,11 @@
8
8
  */
9
9
 
10
10
  import type { Tool, UIMessageChunk } from "ai";
11
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
12
- import type { SessionUserMessageV1 } from "@/executor/types/SessionMessages.js";
11
+ import type {
12
+ SessionMessageV1,
13
+ SessionModelMessageV1,
14
+ SessionUserMessageV1,
15
+ } from "@/executor/types/SessionMessages.js";
13
16
  import type { SessionSystemMessage } from "@/executor/types/SessionPrompts.js";
14
17
  import type { SessionRunContext } from "@/types/executor/SessionRunContext.js";
15
18
 
@@ -99,7 +102,7 @@ export interface SessionRunResult {
99
102
  * - stop/abort 且没有任何 assistant 内容时可以为空。
100
103
  * - turn 状态通过 `success` / `error` 表达,不应伪造成 assistant 正文。
101
104
  */
102
- assistantMessage?: SessionMessageV1 | null;
105
+ assistantMessage?: SessionModelMessageV1 | null;
103
106
 
104
107
  /**
105
108
  * 本轮执行结束后待写入长期历史的 user 消息。
@@ -261,6 +261,7 @@ export function toSessionTimelineEvents(
261
261
  ): AgentSessionTimelineEvent[] {
262
262
  if (isSessionOperationMessage(message)) {
263
263
  const metadata = (message.metadata || null) as SessionMetadataV1 | null;
264
+ if (metadata?.operation?.status !== "finished") return [];
264
265
  return [
265
266
  toTimelineEvent({
266
267
  message,
@@ -367,7 +368,13 @@ export async function loadSessionMessagesFromPath(
367
368
  try {
368
369
  const parsed = JSON.parse(line) as SessionMessageV1;
369
370
  if (!parsed || typeof parsed !== "object") continue;
370
- if (parsed.role !== "user" && parsed.role !== "assistant") continue;
371
+ if (
372
+ parsed.role !== "user" &&
373
+ parsed.role !== "assistant" &&
374
+ parsed.role !== "operation"
375
+ ) {
376
+ continue;
377
+ }
371
378
  messages.push(parsed);
372
379
  } catch {
373
380
  // 关键点(中文):单行损坏不影响整个 session 的可读性。
@@ -412,7 +419,11 @@ export async function loadSessionArchiveMessagesFromPath(
412
419
  if (!item || typeof item !== "object") return false;
413
420
  const candidate = item as Partial<SessionMessageV1>;
414
421
  return (
415
- (candidate.role === "user" || candidate.role === "assistant") &&
422
+ (
423
+ candidate.role === "user" ||
424
+ candidate.role === "assistant" ||
425
+ candidate.role === "operation"
426
+ ) &&
416
427
  Array.isArray(candidate.parts)
417
428
  );
418
429
  });
@@ -9,7 +9,10 @@
9
9
 
10
10
  import { nanoid } from "nanoid";
11
11
  import type { SessionUserMessageV1 } from "@/executor/types/SessionMessages.js";
12
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
12
+ import type {
13
+ SessionMessageV1,
14
+ SessionModelMessageV1,
15
+ } from "@/executor/types/SessionMessages.js";
13
16
  import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
14
17
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
15
18
  import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
@@ -106,7 +109,7 @@ export interface SessionPromptRuntimeOptions {
106
109
  }) => Promise<{
107
110
  text: string;
108
111
  success: boolean;
109
- assistantMessage?: SessionMessageV1 | null;
112
+ assistantMessage?: SessionModelMessageV1 | null;
110
113
  error?: string;
111
114
  }>;
112
115
 
@@ -346,7 +349,7 @@ export class SessionPromptRuntime {
346
349
  function buildPromptRuntimeErrorAssistantMessage(input: {
347
350
  sessionId: string;
348
351
  message: string;
349
- }): SessionMessageV1 {
352
+ }): SessionModelMessageV1 {
350
353
  return {
351
354
  id: `a:${input.sessionId}:${Date.now()}:${nanoid(6)}`,
352
355
  role: "assistant",