@downcity/agent 1.1.189 → 1.1.196

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 (112) hide show
  1. package/bin/agent/local/Agent.d.ts.map +1 -1
  2. package/bin/agent/local/Agent.js +5 -2
  3. package/bin/agent/local/Agent.js.map +1 -1
  4. package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
  5. package/bin/agent/local/services/AgentAssemblyService.js +2 -2
  6. package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
  7. package/bin/agent/local/services/AgentSessionManager.d.ts +5 -0
  8. package/bin/agent/local/services/AgentSessionManager.d.ts.map +1 -1
  9. package/bin/agent/local/services/AgentSessionManager.js +3 -0
  10. package/bin/agent/local/services/AgentSessionManager.js.map +1 -1
  11. package/bin/executor/Executor.d.ts +4 -0
  12. package/bin/executor/Executor.d.ts.map +1 -1
  13. package/bin/executor/Executor.js +40 -1
  14. package/bin/executor/Executor.js.map +1 -1
  15. package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts +9 -0
  16. package/bin/executor/composer/compaction/SessionCompactionComposer.d.ts.map +1 -1
  17. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.d.ts.map +1 -1
  18. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js +1 -0
  19. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.js.map +1 -1
  20. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts +2 -0
  21. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.d.ts.map +1 -1
  22. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js +49 -5
  23. package/bin/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.js.map +1 -1
  24. package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.d.ts +1 -1
  25. package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.d.ts.map +1 -1
  26. package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.js +2 -0
  27. package/bin/executor/composer/history/jsonl/JsonlSessionHistoryComposer.js.map +1 -1
  28. package/bin/executor/messages/SessionMessageCodec.d.ts +1 -1
  29. package/bin/executor/messages/SessionMessageCodec.d.ts.map +1 -1
  30. package/bin/executor/messages/SessionMessageCodec.js +6 -1
  31. package/bin/executor/messages/SessionMessageCodec.js.map +1 -1
  32. package/bin/executor/store/history/SessionHistoryStore.d.ts +22 -0
  33. package/bin/executor/store/history/SessionHistoryStore.d.ts.map +1 -1
  34. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts +7 -0
  35. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.d.ts.map +1 -1
  36. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +53 -0
  37. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
  38. package/bin/executor/tools/plugin/PluginToolBridge.d.ts +4 -8
  39. package/bin/executor/tools/plugin/PluginToolBridge.d.ts.map +1 -1
  40. package/bin/executor/tools/plugin/PluginToolBridge.js +6 -21
  41. package/bin/executor/tools/plugin/PluginToolBridge.js.map +1 -1
  42. package/bin/executor/tools/plugin/PluginToolDefinition.d.ts +7 -19
  43. package/bin/executor/tools/plugin/PluginToolDefinition.d.ts.map +1 -1
  44. package/bin/executor/tools/plugin/PluginToolDefinition.js +29 -18
  45. package/bin/executor/tools/plugin/PluginToolDefinition.js.map +1 -1
  46. package/bin/executor/types/SessionMessages.d.ts +15 -2
  47. package/bin/executor/types/SessionMessages.d.ts.map +1 -1
  48. package/bin/executor/types/SessionMessages.js +9 -1
  49. package/bin/executor/types/SessionMessages.js.map +1 -1
  50. package/bin/index.d.ts +1 -0
  51. package/bin/index.d.ts.map +1 -1
  52. package/bin/index.js.map +1 -1
  53. package/bin/plugin/core/PluginRegistry.d.ts.map +1 -1
  54. package/bin/plugin/core/PluginRegistry.js +0 -4
  55. package/bin/plugin/core/PluginRegistry.js.map +1 -1
  56. package/bin/session/browse/Browse.d.ts.map +1 -1
  57. package/bin/session/browse/Browse.js +22 -0
  58. package/bin/session/browse/Browse.js.map +1 -1
  59. package/bin/session/services/SessionStateService.d.ts +21 -1
  60. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  61. package/bin/session/services/SessionStateService.js +101 -11
  62. package/bin/session/services/SessionStateService.js.map +1 -1
  63. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  64. package/bin/session/services/SessionTurnService.js +9 -0
  65. package/bin/session/services/SessionTurnService.js.map +1 -1
  66. package/bin/session/services/SessionViewService.d.ts.map +1 -1
  67. package/bin/session/services/SessionViewService.js +42 -8
  68. package/bin/session/services/SessionViewService.js.map +1 -1
  69. package/bin/types/agent/SessionTypes.d.ts +22 -1
  70. package/bin/types/agent/SessionTypes.d.ts.map +1 -1
  71. package/bin/types/executor/SessionRunContext.d.ts +10 -0
  72. package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
  73. package/bin/types/plugin/PluginToolRuntime.d.ts +75 -0
  74. package/bin/types/plugin/PluginToolRuntime.d.ts.map +1 -0
  75. package/bin/types/plugin/PluginToolRuntime.js +9 -0
  76. package/bin/types/plugin/PluginToolRuntime.js.map +1 -0
  77. package/bin/types/sdk/AgentSessionEvent.d.ts +2 -1
  78. package/bin/types/sdk/AgentSessionEvent.d.ts.map +1 -1
  79. package/bin/types/sdk/AgentSessionOperation.d.ts +99 -0
  80. package/bin/types/sdk/AgentSessionOperation.d.ts.map +1 -0
  81. package/bin/types/sdk/AgentSessionOperation.js +10 -0
  82. package/bin/types/sdk/AgentSessionOperation.js.map +1 -0
  83. package/package.json +3 -3
  84. package/scripts/agent-ready.test.mjs +168 -0
  85. package/scripts/plugin-tool-bridge.test.mjs +114 -10
  86. package/scripts/session-operation-message.test.mjs +182 -0
  87. package/src/agent/local/Agent.ts +5 -2
  88. package/src/agent/local/services/AgentAssemblyService.ts +2 -3
  89. package/src/agent/local/services/AgentSessionManager.ts +8 -0
  90. package/src/executor/Executor.ts +49 -1
  91. package/src/executor/composer/compaction/SessionCompactionComposer.ts +10 -0
  92. package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionComposer.ts +1 -0
  93. package/src/executor/composer/compaction/jsonl/JsonlSessionCompactionExecutor.ts +64 -5
  94. package/src/executor/composer/history/jsonl/JsonlSessionHistoryComposer.ts +5 -1
  95. package/src/executor/messages/SessionMessageCodec.ts +11 -2
  96. package/src/executor/store/history/SessionHistoryStore.ts +27 -0
  97. package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +66 -0
  98. package/src/executor/tools/plugin/PluginToolBridge.ts +10 -26
  99. package/src/executor/tools/plugin/PluginToolDefinition.ts +39 -22
  100. package/src/executor/types/SessionMessages.ts +26 -2
  101. package/src/index.ts +7 -0
  102. package/src/plugin/core/PluginRegistry.ts +0 -4
  103. package/src/session/browse/Browse.ts +24 -0
  104. package/src/session/services/SessionStateService.ts +125 -11
  105. package/src/session/services/SessionTurnService.ts +8 -0
  106. package/src/session/services/SessionViewService.ts +47 -10
  107. package/src/types/agent/SessionTypes.ts +22 -1
  108. package/src/types/executor/SessionRunContext.ts +20 -0
  109. package/src/types/plugin/PluginToolRuntime.ts +86 -0
  110. package/src/types/sdk/AgentSessionEvent.ts +2 -0
  111. package/src/types/sdk/AgentSessionOperation.ts +125 -0
  112. package/tsconfig.tsbuildinfo +1 -1
@@ -28,8 +28,7 @@ import {
28
28
  createAgentPluginConfigRuntime,
29
29
  } from "@/agent/local/AgentRuntimeAssembly.js";
30
30
  import {
31
- plugin_tools,
32
- setPluginToolRuntime,
31
+ createPluginTools,
33
32
  } from "@executor/tools/plugin/PluginToolDefinition.js";
34
33
  import type { AgentManagedSession } from "@/types/agent/AgentTypes.js";
35
34
  import type { SessionPort } from "@/types/runtime/agent/AgentContext.js";
@@ -183,6 +182,7 @@ export class AgentAssemblyService {
183
182
  });
184
183
  const plugins = plugin_registry;
185
184
  if (this.should_register_plugin_call_tool(plugin_instances)) {
185
+ const plugin_tools = createPluginTools({ plugins });
186
186
  tools.plugin_read = tools.plugin_read || plugin_tools.plugin_read;
187
187
  tools.plugin_call = tools.plugin_call || plugin_tools.plugin_call;
188
188
  }
@@ -228,7 +228,6 @@ export class AgentAssemblyService {
228
228
  });
229
229
  Object.assign(tools, shell.tools);
230
230
  }
231
- setPluginToolRuntime(plugins);
232
231
 
233
232
  return {
234
233
  id,
@@ -87,6 +87,11 @@ type AgentSessionManagerOptions = {
87
87
  */
88
88
  plugin_instances: Map<string, Plugin>;
89
89
 
90
+ /**
91
+ * 等待当前 Agent 后台能力启动完成。
92
+ */
93
+ ensure_agent_ready: () => Promise<void>;
94
+
90
95
  /**
91
96
  * 当前默认模型实例。
92
97
  */
@@ -109,6 +114,7 @@ export class AgentSessionManager {
109
114
  private readonly get_agent_context: AgentSessionManagerOptions["get_agent_context"];
110
115
  private readonly get_instruction: AgentSessionManagerOptions["get_instruction"];
111
116
  private readonly plugin_instances: Map<string, Plugin>;
117
+ private readonly ensure_agent_ready: AgentSessionManagerOptions["ensure_agent_ready"];
112
118
  private readonly default_model?: AgentModel;
113
119
  private readonly SessionClass: AgentSessionConstructor;
114
120
  private readonly sessions_by_id = new Map<string, AgentManagedSession>();
@@ -123,6 +129,7 @@ export class AgentSessionManager {
123
129
  this.get_agent_context = options.get_agent_context;
124
130
  this.get_instruction = options.get_instruction;
125
131
  this.plugin_instances = options.plugin_instances;
132
+ this.ensure_agent_ready = options.ensure_agent_ready;
126
133
  this.default_model = options.default_model;
127
134
  this.SessionClass = options.SessionClass || Session;
128
135
  this.session_collection = {
@@ -350,6 +357,7 @@ export class AgentSessionManager {
350
357
  getManagedPluginSystemBlocks: async () => [],
351
358
  getPluginSystemBlocks: async () => await this.load_plugin_system_blocks(),
352
359
  ensureConfigured: async (session) => {
360
+ await this.ensure_agent_ready();
353
361
  await this.apply_session_defaults(session);
354
362
  },
355
363
  });
@@ -22,11 +22,13 @@ import type { SessionContextComposer } from "@executor/composer/context/SessionC
22
22
  import type { SessionSystemComposer } from "@executor/composer/system/SessionSystemComposer.js";
23
23
  import { ExecutorInflightService } from "@executor/services/ExecutorInflightService.js";
24
24
  import { ExecutorRecoveryPolicy } from "@executor/services/ExecutorRecoveryPolicy.js";
25
+ import { generateId } from "@/utils/Id.js";
25
26
  import type { Logger } from "@/utils/logger/Logger.js";
26
27
  import type { JsonObject } from "@/types/common/Json.js";
27
28
  import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
28
29
  import type { SessionExecutor } from "@/executor/types/SessionExecutor.js";
29
30
  import type { SessionRunContext } from "@/types/executor/SessionRunContext.js";
31
+ import type { AgentSessionOperationRecord } from "@/types/sdk/AgentSessionOperation.js";
30
32
  import type {
31
33
  SessionExecuteInput,
32
34
  SessionRunResult,
@@ -360,6 +362,7 @@ export class Executor implements SessionExecutor {
360
362
  const composed_context = await this.contextComposer.compose(run_context);
361
363
  const tools = this.bind_run_scope_to_tools(composed_context.tools, run_context);
362
364
  const system = await this.systemComposer.resolve(run_context);
365
+ let compaction_operation_id = "";
363
366
 
364
367
  try {
365
368
  if (retry_count > 0) {
@@ -368,13 +371,33 @@ export class Executor implements SessionExecutor {
368
371
  });
369
372
  }
370
373
 
374
+ const emit_compaction_operation = async (
375
+ operation: AgentSessionOperationRecord,
376
+ ): Promise<void> => {
377
+ if (operation.status === "started") {
378
+ compaction_operation_id = operation.operationId;
379
+ }
380
+ await this.emitOperation(run_context, operation);
381
+ };
382
+
371
383
  await this.compactionComposer.run({
372
384
  historyStore: this.historyStore,
373
385
  model,
374
386
  system,
375
387
  retryCount: retry_count,
388
+ onOperation: emit_compaction_operation,
389
+ });
390
+ } catch (error) {
391
+ await this.emitOperation(run_context, {
392
+ operationId:
393
+ compaction_operation_id ||
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),
399
+ visible: false,
376
400
  });
377
- } catch {
378
401
  // 压缩失败不阻断主流程,继续使用当前历史消息执行。
379
402
  }
380
403
 
@@ -469,6 +492,9 @@ export class Executor implements SessionExecutor {
469
492
  ...(typeof input?.onUiMessageChunkCallback === "function"
470
493
  ? { onUiMessageChunkCallback: input.onUiMessageChunkCallback }
471
494
  : {}),
495
+ ...(typeof input?.onOperationCallback === "function"
496
+ ? { onOperationCallback: input.onOperationCallback }
497
+ : {}),
472
498
  ...(input?.abortSignal ? { abortSignal: input.abortSignal } : {}),
473
499
  injectedUserMessages: Array.isArray(input?.injectedUserMessages)
474
500
  ? [...input.injectedUserMessages]
@@ -484,6 +510,28 @@ export class Executor implements SessionExecutor {
484
510
  };
485
511
  }
486
512
 
513
+ /**
514
+ * 发布一次 session operation。
515
+ */
516
+ private async emitOperation(
517
+ run_context: SessionRunContext,
518
+ operation: AgentSessionOperationRecord,
519
+ ): 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",
526
+ sessionId: run_context.sessionId || this.sessionId,
527
+ ...operation,
528
+ operationId: operation_id,
529
+ ...(run_context.turnId && !operation.turnId
530
+ ? { turnId: run_context.turnId }
531
+ : {}),
532
+ });
533
+ }
534
+
487
535
  /**
488
536
  * 读取当前 session 模型。
489
537
  */
@@ -8,6 +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
12
 
12
13
  /**
13
14
  * compaction Composer 执行输入。
@@ -32,6 +33,15 @@ export type SessionCompactionInput = {
32
33
  * 当前重试次数(由 Executor 递增)。
33
34
  */
34
35
  retryCount: number;
36
+
37
+ /**
38
+ * 可选 operation 发布回调。
39
+ *
40
+ * 说明(中文)
41
+ * - 默认 compaction Composer 可在真正开始压缩时发布 `compacting`。
42
+ * - 回调由上层 Session 负责转成订阅事件与 operation message。
43
+ */
44
+ onOperation?: (operation: AgentSessionOperationRecord) => Promise<void>;
35
45
  };
36
46
 
37
47
  /**
@@ -79,6 +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
83
  });
83
84
  }
84
85
 
@@ -21,7 +21,9 @@ import type {
21
21
  SessionMessageV1,
22
22
  SessionMetadataV1,
23
23
  } from "@/executor/types/SessionMessages.js";
24
+ import { isSessionOperationMessage } from "@/executor/types/SessionMessages.js";
24
25
  import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
26
+ import type { AgentSessionOperationRecord } from "@/types/sdk/AgentSessionOperation.js";
25
27
 
26
28
  export type SessionCompactParams = {
27
29
  model: LanguageModel;
@@ -30,6 +32,7 @@ export type SessionCompactParams = {
30
32
  maxInputTokensApprox: number;
31
33
  archiveOnCompact: boolean;
32
34
  compactRatio: number;
35
+ onOperation?: (operation: AgentSessionOperationRecord) => Promise<void>;
33
36
  };
34
37
 
35
38
  type SessionCompactDeps = {
@@ -60,6 +63,19 @@ export async function compactSessionMessagesIfNeeded(
60
63
  params: SessionCompactParams,
61
64
  ): Promise<{ compacted: boolean; reason?: string }> {
62
65
  const logger = getLogger(deps.rootPath, "info");
66
+ const operation_id = `compacting:${deps.sessionId}:${Date.now()}:${generateId()}`;
67
+ let operation_started = false;
68
+
69
+ const publish_operation = async (
70
+ operation: Omit<AgentSessionOperationRecord, "operationId" | "name">,
71
+ ): Promise<void> => {
72
+ if (typeof params.onOperation !== "function") return;
73
+ await params.onOperation({
74
+ operationId: operation_id,
75
+ name: "compacting",
76
+ ...operation,
77
+ });
78
+ };
63
79
 
64
80
  // 算法阶段(中文)
65
81
  // phase 1:snapshot(短锁)
@@ -68,7 +84,9 @@ export async function compactSessionMessagesIfNeeded(
68
84
  let snapshot: SessionMessageV1[] = [];
69
85
  let snapshotTailId = "";
70
86
  await deps.withWriteLock(async () => {
71
- snapshot = await deps.loadAll();
87
+ snapshot = (await deps.loadAll()).filter(
88
+ (message) => !isSessionOperationMessage(message),
89
+ );
72
90
  snapshotTailId =
73
91
  snapshot.length > 0
74
92
  ? String(snapshot[snapshot.length - 1].id || "")
@@ -100,6 +118,13 @@ export async function compactSessionMessagesIfNeeded(
100
118
  const older = snapshot.slice(0, compactCount);
101
119
  if (older.length === 0) return { compacted: false, reason: "nothing_to_compact" };
102
120
 
121
+ operation_started = true;
122
+ await publish_operation({
123
+ status: "started",
124
+ label: "Compacting session history",
125
+ progress: 0,
126
+ });
127
+
103
128
  const olderTextAll = extractPlainTextFromMessages(older);
104
129
  const maxOlderChars = 24_000;
105
130
  const olderText =
@@ -138,6 +163,12 @@ export async function compactSessionMessagesIfNeeded(
138
163
  },
139
164
  );
140
165
  summary = "(系统自动压缩:摘要生成失败,已丢弃更早历史,仅保留最近对话。)";
166
+ await publish_operation({
167
+ status: "progress",
168
+ label: "Compacting session history",
169
+ reason: "summary_fallback",
170
+ progress: 0.5,
171
+ });
141
172
  }
142
173
 
143
174
  const fromId = String(older[0]?.id || "");
@@ -159,10 +190,20 @@ export async function compactSessionMessagesIfNeeded(
159
190
  // snapshotTailId 用于 debug,不作为强一致性依赖。
160
191
  void snapshotTailId;
161
192
 
162
- const currentCompactCount = resolveCompactCount(current.length, compactRatio);
163
- const currentOlder = current.slice(0, currentCompactCount);
164
- const currentKept = current.slice(currentCompactCount);
193
+ const current_model_messages = current.filter(
194
+ (message) => !isSessionOperationMessage(message),
195
+ );
196
+ const currentCompactCount = resolveCompactCount(
197
+ current_model_messages.length,
198
+ compactRatio,
199
+ );
200
+ const currentOlder = current_model_messages.slice(0, currentCompactCount);
165
201
  if (currentOlder.length === 0) return;
202
+ const compacted_ids = new Set(
203
+ currentOlder
204
+ .map((message) => String(message.id || "").trim())
205
+ .filter(Boolean),
206
+ );
166
207
 
167
208
  if (params.archiveOnCompact) {
168
209
  const archivePath = path.join(
@@ -181,7 +222,13 @@ export async function compactSessionMessagesIfNeeded(
181
222
  );
182
223
  }
183
224
 
184
- const next = [summaryMsg, ...currentKept];
225
+ const next = [
226
+ summaryMsg,
227
+ ...current.filter((message) => {
228
+ const id = String(message.id || "").trim();
229
+ return !id || !compacted_ids.has(id);
230
+ }),
231
+ ];
185
232
 
186
233
  const messagesPath = deps.getMessagesFilePath();
187
234
  const tmp = messagesPath + ".tmp";
@@ -195,6 +242,18 @@ export async function compactSessionMessagesIfNeeded(
195
242
  });
196
243
  });
197
244
 
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
+ },
254
+ });
255
+ }
256
+
198
257
  return { compacted: true };
199
258
  }
200
259
 
@@ -8,7 +8,10 @@
8
8
  */
9
9
 
10
10
  import type { Tool } from "ai";
11
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
11
+ import {
12
+ isSessionOperationMessage,
13
+ type SessionMessageV1,
14
+ } from "@/executor/types/SessionMessages.js";
12
15
  import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
13
16
  import type {
14
17
  SessionHistoryComposer,
@@ -91,6 +94,7 @@ export class JsonlSessionHistoryComposer implements SessionHistoryComposer {
91
94
  parts,
92
95
  };
93
96
  })
97
+ .filter((message) => !isSessionOperationMessage(message))
94
98
  .filter((message) => Array.isArray(message.parts) && message.parts.length > 0);
95
99
  }
96
100
 
@@ -14,7 +14,10 @@ import {
14
14
  type Tool,
15
15
  type ToolSet,
16
16
  } from "ai";
17
- import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
17
+ import {
18
+ isSessionOperationMessage,
19
+ type SessionMessageV1,
20
+ } from "@/executor/types/SessionMessages.js";
18
21
  import {
19
22
  hydrateFileUrlPartsForModel,
20
23
  injectFilePartsFromAttachments,
@@ -77,9 +80,15 @@ export async function toModelMessages(
77
80
  // 空输入快速返回,避免调用转换器的额外开销。
78
81
  if (!Array.isArray(messages) || messages.length === 0) return [];
79
82
 
83
+ // operation message 只服务前端时间线,不能传入模型。
84
+ const model_messages = messages.filter(
85
+ (message) => !isSessionOperationMessage(message),
86
+ );
87
+ if (model_messages.length === 0) return [];
88
+
80
89
  // 第一步(中文):在 user 消息上注入 file parts(多模态附件)。
81
90
  const enrichedMessages = await injectFilePartsFromAttachments(
82
- messages,
91
+ model_messages,
83
92
  projectRoot,
84
93
  );
85
94
 
@@ -13,6 +13,7 @@ import type {
13
13
  SessionMetadataV1,
14
14
  } from "@/executor/types/SessionMessages.js";
15
15
  import type { SessionSystemMessage } from "@/executor/types/SessionPrompts.js";
16
+ import type { AgentSessionOperationRecord } from "@/types/sdk/AgentSessionOperation.js";
16
17
 
17
18
  /**
18
19
  * compact 输入参数。
@@ -47,6 +48,11 @@ export type SessionHistoryCompactInput = {
47
48
  * 前段压缩比例(0-1)。
48
49
  */
49
50
  compactRatio: number;
51
+
52
+ /**
53
+ * 可选 operation 发布回调。
54
+ */
55
+ onOperation?: (operation: AgentSessionOperationRecord) => Promise<void>;
50
56
  };
51
57
 
52
58
  /**
@@ -157,4 +163,25 @@ export interface SessionHistoryStore {
157
163
  */
158
164
  source?: "egress" | "compact";
159
165
  }): SessionMessageV1;
166
+
167
+ /**
168
+ * 构造 operation 消息。
169
+ */
170
+ operation(input: {
171
+ /**
172
+ * 当前 operation 结构化记录。
173
+ */
174
+ operation: AgentSessionOperationRecord;
175
+
176
+ /**
177
+ * 消息元信息(除 schema 字段、kind、source 与 operation 字段)。
178
+ */
179
+ metadata: Omit<SessionMetadataV1, "v" | "ts" | "kind" | "source" | "operation"> &
180
+ Partial<Pick<SessionMetadataV1, "ts">>;
181
+
182
+ /**
183
+ * 可选消息 ID(默认自动生成)。
184
+ */
185
+ id?: string;
186
+ }): SessionMessageV1;
160
187
  }
@@ -21,6 +21,7 @@ import type {
21
21
  SessionMessageV1,
22
22
  SessionMetadataV1,
23
23
  } from "@/executor/types/SessionMessages.js";
24
+ import type { AgentSessionOperationRecord } from "@/types/sdk/AgentSessionOperation.js";
24
25
  import type { SessionSystemMessage } from "@/executor/types/SessionPrompts.js";
25
26
  import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.js";
26
27
  import type { SessionHistoryPathOverrides } from "@/executor/types/SessionHistoryPaths.js";
@@ -435,6 +436,44 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
435
436
  return system.filter((item) => item && typeof item === "object");
436
437
  }
437
438
 
439
+ private normalizeOperation(
440
+ operation: AgentSessionOperationRecord,
441
+ ): AgentSessionOperationRecord {
442
+ const operation_id = String(operation.operationId || "").trim() || generateId();
443
+ const name = String(operation.name || "").trim() || "operation";
444
+ const status = String(operation.status || "").trim();
445
+ const normalized_status: AgentSessionOperationRecord["status"] =
446
+ status === "started" ||
447
+ status === "progress" ||
448
+ status === "finished" ||
449
+ status === "skipped" ||
450
+ status === "failed"
451
+ ? status
452
+ : "progress";
453
+ return {
454
+ operationId: operation_id,
455
+ name,
456
+ status: normalized_status,
457
+ ...(typeof operation.turnId === "string" && operation.turnId.trim()
458
+ ? { turnId: operation.turnId.trim() }
459
+ : {}),
460
+ ...(typeof operation.label === "string" && operation.label.trim()
461
+ ? { label: operation.label.trim() }
462
+ : {}),
463
+ ...(typeof operation.reason === "string" && operation.reason.trim()
464
+ ? { reason: operation.reason.trim() }
465
+ : {}),
466
+ ...(typeof operation.progress === "number" && Number.isFinite(operation.progress)
467
+ ? { progress: Math.max(0, Math.min(1, operation.progress)) }
468
+ : {}),
469
+ ...(operation.result !== undefined ? { result: operation.result } : {}),
470
+ ...(typeof operation.error === "string" && operation.error.trim()
471
+ ? { error: operation.error.trim() }
472
+ : {}),
473
+ ...(operation.visible === false ? { visible: false } : {}),
474
+ };
475
+ }
476
+
438
477
  async compact(input: SessionHistoryCompactInput): Promise<{
439
478
  compacted: boolean;
440
479
  reason?: string;
@@ -471,6 +510,7 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
471
510
  maxInputTokensApprox: input.maxInputTokensApprox,
472
511
  archiveOnCompact: input.archiveOnCompact,
473
512
  compactRatio: input.compactRatio,
513
+ ...(input.onOperation ? { onOperation: input.onOperation } : {}),
474
514
  },
475
515
  );
476
516
  }
@@ -633,4 +673,30 @@ export class JsonlSessionHistoryStore implements SessionHistoryStore {
633
673
  parts: [{ type: "text", text: String(input.text ?? "") }],
634
674
  };
635
675
  }
676
+
677
+ operation(input: {
678
+ operation: AgentSessionOperationRecord;
679
+ metadata: Omit<SessionMetadataV1, "v" | "ts" | "kind" | "source" | "operation"> &
680
+ Partial<Pick<SessionMetadataV1, "ts">>;
681
+ id?: string;
682
+ }): SessionMessageV1 {
683
+ const operation = this.normalizeOperation(input.operation);
684
+ const label = String(
685
+ operation.label || `${operation.name}: ${operation.status}`,
686
+ ).trim();
687
+ const { ts, ...metadata } = input.metadata;
688
+ return {
689
+ id: input.id || `op:${this.sessionId}:${operation.operationId}:${generateId()}`,
690
+ role: "assistant",
691
+ metadata: {
692
+ v: 1,
693
+ ts: typeof ts === "number" ? ts : Date.now(),
694
+ ...metadata,
695
+ source: "operation",
696
+ kind: "operation",
697
+ operation,
698
+ },
699
+ parts: [{ type: "text", text: label }],
700
+ };
701
+ }
636
702
  }
@@ -10,7 +10,6 @@
10
10
  import path from "node:path";
11
11
  import type { FileUIPart } from "ai";
12
12
  import type { JsonObject, JsonValue } from "@/types/common/Json.js";
13
- import type { AgentPlugins } from "@/plugin/types/Plugin.js";
14
13
  import type {
15
14
  PluginCallInput,
16
15
  PluginCallToolFileResult,
@@ -18,6 +17,10 @@ import type {
18
17
  PluginReadInput,
19
18
  PluginReadToolResult,
20
19
  } from "@/executor/tools/plugin/types/PluginTool.js";
20
+ import type {
21
+ InvokePluginCallToolOptions,
22
+ InvokePluginReadToolOptions,
23
+ } from "@/types/plugin/PluginToolRuntime.js";
21
24
  import {
22
25
  materializeAssistantFileParts,
23
26
  resolveAgentFilePath,
@@ -27,25 +30,6 @@ import {
27
30
  getSessionRunContext,
28
31
  } from "@executor/SessionRunScope.js";
29
32
 
30
- let plugin_tool_runtime: AgentPlugins | null = null;
31
-
32
- /**
33
- * 注入 plugin tool 运行时。
34
- */
35
- export function setPluginToolRuntime(next: AgentPlugins): void {
36
- plugin_tool_runtime = next;
37
- }
38
-
39
- /**
40
- * 读取已注入的 plugin tool 运行时。
41
- */
42
- function require_plugin_tool_runtime(): AgentPlugins {
43
- if (plugin_tool_runtime) return plugin_tool_runtime;
44
- throw new Error(
45
- "Plugin tool runtime is not initialized. Ensure agent assembly completed before using plugin_call.",
46
- );
47
- }
48
-
49
33
  /**
50
34
  * 判断值是否为普通对象。
51
35
  */
@@ -126,8 +110,9 @@ function summarize_action_data(data: JsonValue | undefined): JsonObject | undefi
126
110
  * 调用 plugin action 并桥接最终 assistant file parts。
127
111
  */
128
112
  export async function invokePluginCallTool(
129
- input: PluginCallInput,
113
+ params: InvokePluginCallToolOptions,
130
114
  ): Promise<PluginCallToolResult> {
115
+ const input: PluginCallInput = params.input;
131
116
  const plugin = String(input.plugin || "").trim();
132
117
  const action = String(input.action || "").trim();
133
118
  const payload = to_json_object(input.payload ?? {}) ?? {};
@@ -153,8 +138,7 @@ export async function invokePluginCallTool(
153
138
  }
154
139
 
155
140
  try {
156
- const runtime = require_plugin_tool_runtime();
157
- const result = await runtime.runAction({
141
+ const result = await params.plugins.runAction({
158
142
  plugin,
159
143
  action,
160
144
  payload,
@@ -203,11 +187,11 @@ export async function invokePluginCallTool(
203
187
  * 读取 plugin / action metadata。
204
188
  */
205
189
  export async function invokePluginReadTool(
206
- input: PluginReadInput,
190
+ params: InvokePluginReadToolOptions,
207
191
  ): Promise<PluginReadToolResult> {
192
+ const input: PluginReadInput = params.input;
208
193
  try {
209
- const runtime = require_plugin_tool_runtime();
210
- const data = runtime.read({
194
+ const data = params.plugins.read({
211
195
  plugin: typeof input.plugin === "string" ? input.plugin : undefined,
212
196
  action: typeof input.action === "string" ? input.action : undefined,
213
197
  });
@@ -12,42 +12,59 @@ import type {
12
12
  PluginCallInput,
13
13
  PluginReadInput,
14
14
  } from "@/executor/tools/plugin/types/PluginTool.js";
15
+ import type {
16
+ AgentPluginTools,
17
+ CreatePluginToolsOptions,
18
+ } from "@/types/plugin/PluginToolRuntime.js";
15
19
  import {
16
20
  invokePluginCallTool,
17
21
  invokePluginReadTool,
18
- setPluginToolRuntime,
19
22
  } from "./PluginToolBridge.js";
20
23
  import {
21
24
  plugin_call_input_schema,
22
25
  plugin_read_input_schema,
23
26
  } from "./PluginToolSchemas.js";
24
27
 
25
- export { setPluginToolRuntime } from "./PluginToolBridge.js";
26
-
27
28
  /**
28
- * `plugin_call`:调用已注册 plugin action。
29
+ * 创建 `plugin_call`:调用当前 Agent 已注册 plugin action。
29
30
  */
30
- export const plugin_call = tool({
31
- description:
32
- "Call a registered agent plugin action. Use plugin_read first when you need the action list, input schema, or examples. Generated files may be attached to the final assistant message automatically.",
33
- inputSchema: plugin_call_input_schema,
34
- execute: async (input) => await invokePluginCallTool(input as PluginCallInput),
35
- });
31
+ export function createPluginCallTool(options: CreatePluginToolsOptions) {
32
+ return tool({
33
+ description:
34
+ "Call a registered agent plugin action. Use plugin_read first when you need the action list, input schema, or examples. Generated files may be attached to the final assistant message automatically.",
35
+ inputSchema: plugin_call_input_schema,
36
+ execute: async (input) =>
37
+ await invokePluginCallTool({
38
+ plugins: options.plugins,
39
+ input: input as PluginCallInput,
40
+ }),
41
+ });
42
+ }
36
43
 
37
44
  /**
38
- * `plugin_read`:读取已注册 plugin / action metadata。
45
+ * 创建 `plugin_read`:读取当前 Agent 已注册 plugin / action metadata。
39
46
  */
40
- export const plugin_read = tool({
41
- description:
42
- "Read registered agent plugin metadata, including action names, descriptions, input schemas, and examples. Use this before plugin_call when the payload shape is unclear.",
43
- inputSchema: plugin_read_input_schema,
44
- execute: async (input) => await invokePluginReadTool(input as PluginReadInput),
45
- });
47
+ export function createPluginReadTool(options: CreatePluginToolsOptions) {
48
+ return tool({
49
+ description:
50
+ "Read registered agent plugin metadata, including action names, descriptions, input schemas, and examples. Use this before plugin_call when the payload shape is unclear.",
51
+ inputSchema: plugin_read_input_schema,
52
+ execute: async (input) =>
53
+ await invokePluginReadTool({
54
+ plugins: options.plugins,
55
+ input: input as PluginReadInput,
56
+ }),
57
+ });
58
+ }
46
59
 
47
60
  /**
48
- * Plugin 工具导出集合。
61
+ * 创建当前 Agent 专属 Plugin 工具集合。
49
62
  */
50
- export const plugin_tools = {
51
- plugin_call,
52
- plugin_read,
53
- };
63
+ export function createPluginTools(
64
+ options: CreatePluginToolsOptions,
65
+ ): AgentPluginTools {
66
+ return {
67
+ plugin_call: createPluginCallTool(options),
68
+ plugin_read: createPluginReadTool(options),
69
+ };
70
+ }