evolcore 0.0.26 → 0.0.28

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 (177) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/baseagent.js +10 -5
  5. package/dist/agents/claude-runner.js +123 -22
  6. package/dist/agents/codex/app-server-client.js +70 -26
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex-runner.js +363 -421
  9. package/dist/agents/ecagent-runner.js +170 -25
  10. package/dist/agents/gemini-runner.js +6 -0
  11. package/dist/agents/runner-types.js +50 -2
  12. package/dist/aun/aid/agentmd.js +73 -31
  13. package/dist/aun/aid/client.js +1 -1
  14. package/dist/aun/aid/control-aid.js +180 -1
  15. package/dist/aun/aid/identity.js +3 -3
  16. package/dist/aun/group/history.js +303 -0
  17. package/dist/aun/group/identity.js +43 -0
  18. package/dist/aun/group/index.js +5 -0
  19. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  20. package/dist/aun/{menu-client.js → menu/client.js} +13 -7
  21. package/dist/aun/menu/index.js +2 -0
  22. package/dist/aun/menu/transport.js +49 -0
  23. package/dist/aun/msg/index.js +2 -1
  24. package/dist/aun/msg/managed-operation.js +84 -18
  25. package/dist/aun/msg/mention-schema.js +51 -0
  26. package/dist/aun/msg/p2p.js +13 -8
  27. package/dist/aun/msg/upload.js +43 -88
  28. package/dist/aun/outbox.js +29 -7
  29. package/dist/aun/rpc/client.js +1 -0
  30. package/dist/aun/storage/client.js +39 -50
  31. package/dist/channels/aun.js +544 -239
  32. package/dist/channels/daemon.js +10 -6
  33. package/dist/cli/agent-command.js +108 -6
  34. package/dist/cli/agent-create.js +515 -2
  35. package/dist/cli/agent.js +109 -8
  36. package/dist/cli/aun-commands.js +191 -42
  37. package/dist/cli/config.js +8 -5
  38. package/dist/cli/contact.js +2 -2
  39. package/dist/cli/ctl-command.js +2 -2
  40. package/dist/cli/daemon-commands.js +111 -162
  41. package/dist/cli/diag.js +3 -3
  42. package/dist/cli/fs-command.js +3 -3
  43. package/dist/cli/handoff-command.js +6 -6
  44. package/dist/cli/index.js +8 -2
  45. package/dist/cli/init.js +34 -1
  46. package/dist/cli/managed-command-guard.js +2 -1
  47. package/dist/cli/menu-client.js +13 -5
  48. package/dist/cli/model.js +3 -3
  49. package/dist/cli/queue-command.js +2 -2
  50. package/dist/cli/response.js +2 -2
  51. package/dist/cli/restart-monitor.js +81 -19
  52. package/dist/cli/task-command.js +59 -0
  53. package/dist/cli/task-context.js +97 -49
  54. package/dist/cli/trigger-command.js +20 -10
  55. package/dist/config/builtin-roles.js +10 -0
  56. package/dist/config/config-field-policy.js +11 -0
  57. package/dist/config/config-manager.js +32 -4
  58. package/dist/config/config-operation-service.js +20 -5
  59. package/dist/config/contact-book-store.js +1 -1
  60. package/dist/config/contact-book.js +1 -1
  61. package/dist/config/contact-operation-service.js +1 -1
  62. package/dist/config/peer-role-resolver.js +1 -1
  63. package/dist/config/resolved-config-op.js +3 -0
  64. package/dist/config-store.js +1 -1
  65. package/dist/core/agent-application-service.js +5 -0
  66. package/dist/core/audit/event-key.js +17 -2
  67. package/dist/core/audit/log-integrity.js +34 -8
  68. package/dist/core/auth/agent-delegation.js +48 -14
  69. package/dist/core/auth/auth-gateway.js +2 -0
  70. package/dist/core/auth/authorization-audit.js +34 -38
  71. package/dist/core/auth/operation-authorizer.js +67 -5
  72. package/dist/core/auth/operation-catalog.js +110 -0
  73. package/dist/core/auth/remote-access-store.js +115 -9
  74. package/dist/core/bootstrap-messages.js +66 -7
  75. package/dist/core/bootstrap-service.js +110 -19
  76. package/dist/core/command/agent-control.js +88 -7
  77. package/dist/core/command/agent-storage-menu.js +443 -0
  78. package/dist/core/command/cli-intent-parser.js +19 -0
  79. package/dist/core/command/command-handler.js +105 -40
  80. package/dist/core/command/data-root-resolver.js +273 -0
  81. package/dist/core/command/diag-service.js +292 -27
  82. package/dist/core/command/group-menu.js +7 -2
  83. package/dist/core/command/menu-catalog.js +34 -4
  84. package/dist/core/command/menu-handler.js +364 -96
  85. package/dist/core/command/menu-protocol.js +43 -6
  86. package/dist/core/command/role-menu.js +2 -2
  87. package/dist/core/command/slash-handler.js +61 -37
  88. package/dist/core/event-catalog.js +4 -0
  89. package/dist/core/evolagent-registry.js +28 -2
  90. package/dist/core/group/invite-service.js +924 -0
  91. package/dist/core/interaction-router.js +22 -8
  92. package/dist/core/message/im-renderer.js +94 -16
  93. package/dist/core/message/message-bridge.js +140 -13
  94. package/dist/core/message/message-log.js +5 -3
  95. package/dist/core/message/message-queue.js +73 -41
  96. package/dist/core/message/response-engine.js +729 -118
  97. package/dist/core/permission/approval-gateway.js +36 -3
  98. package/dist/core/permission/ec-command-parser.js +76 -36
  99. package/dist/core/permission/protected-paths.js +134 -16
  100. package/dist/core/permission/sandbox-runtime.js +72 -3
  101. package/dist/core/permission/tool-error-code.js +15 -0
  102. package/dist/core/permission/tool-policy.js +8 -8
  103. package/dist/core/relation/peer-identity.js +57 -6
  104. package/dist/core/relation/shared-data.js +106 -0
  105. package/dist/core/role/runtime-policy.js +64 -0
  106. package/dist/core/runtime-lock.js +10 -26
  107. package/dist/core/session/runner-task-history.js +170 -0
  108. package/dist/core/session/session-manager.js +38 -6
  109. package/dist/core/session/session-mapper.js +41 -0
  110. package/dist/core/session/session-turn-coordinator.js +37 -7
  111. package/dist/core/session/session-turns.js +48 -7
  112. package/dist/core/task/task-recovery-service.js +176 -0
  113. package/dist/core/task/task-resume-operation.js +32 -0
  114. package/dist/eck/group-rules-sync.js +5 -3
  115. package/dist/eck/kit-renderer.js +1 -0
  116. package/dist/index.js +322 -207
  117. package/dist/ipc.js +141 -15
  118. package/dist/paths.js +17 -4
  119. package/dist/product.js +3 -0
  120. package/dist/trigger/event-source.js +19 -0
  121. package/dist/trigger/feedback.js +233 -40
  122. package/dist/trigger/parser.js +55 -5
  123. package/dist/trigger/patch.js +9 -0
  124. package/dist/trigger/scheduler.js +30 -4
  125. package/dist/trigger/validation.js +20 -3
  126. package/dist/utils/atomic-write.js +98 -0
  127. package/dist/utils/cross-platform.js +87 -16
  128. package/dist/utils/ecweb-utils.js +1 -0
  129. package/dist/utils/error-utils.js +40 -2
  130. package/dist/utils/logger.js +23 -13
  131. package/dist/utils/npm-ops.js +119 -4
  132. package/dist/utils/package-version-info.js +244 -0
  133. package/dist/utils/tool-summary.js +59 -3
  134. package/dist/utils/welcome.js +174 -1
  135. package/dist/utils/windows-shell-trust.js +37 -0
  136. package/ecagent/dist/harness/agent-harness.js +3 -0
  137. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  138. package/kits/docs/INDEX.md +0 -6
  139. package/kits/docs/channels/aun.md +23 -2
  140. package/kits/docs/channels/feishu.md +1 -1
  141. package/kits/docs/context-assembly.md +29 -17
  142. package/kits/docs/evolcore/INDEX.md +8 -3
  143. package/kits/docs/evolcore/agent-create.md +39 -7
  144. package/kits/docs/evolcore/agent.md +33 -1
  145. package/kits/docs/evolcore/aid.md +9 -1
  146. package/kits/docs/evolcore/config.md +40 -1
  147. package/kits/docs/evolcore/group-collaboration.md +1 -1
  148. package/kits/docs/evolcore/group.md +117 -2
  149. package/kits/docs/evolcore/menu-client.md +16 -0
  150. package/kits/docs/evolcore/msg.md +3 -0
  151. package/kits/docs/evolcore/storage.md +3 -0
  152. package/kits/docs/evolcore/task.md +14 -0
  153. package/kits/docs/evolcore/trigger.md +51 -3
  154. package/kits/docs/venues/group.md +5 -0
  155. package/kits/eck_manifest.json +27 -0
  156. package/kits/rules/01-overview.md +11 -7
  157. package/kits/rules/04-relation.md +2 -1
  158. package/kits/rules/05-venue.md +1 -1
  159. package/kits/rules/06-channel.md +6 -4
  160. package/kits/schemas/agent-config.schema.12.json +25 -1
  161. package/kits/schemas/daemon.schema.6.json +2 -2
  162. package/kits/schemas/defaults.schema.5.json +1 -1
  163. package/kits/schemas/relation-config.schema.1.json +1 -1
  164. package/kits/schemas/relation-config.schema.5.json +1 -1
  165. package/kits/schemas/role-config.schema.1.json +14 -0
  166. package/kits/templates/roles/member.json +5 -0
  167. package/kits/templates/roles/visitor.json +5 -0
  168. package/kits/templates/system-fragments/baseagent.md +1 -1
  169. package/kits/templates/system-fragments/bootstrap.md +26 -9
  170. package/kits/templates/system-fragments/channel.md +8 -44
  171. package/kits/templates/system-fragments/commands.md +10 -2
  172. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  173. package/kits/templates/system-fragments/reply.md +56 -0
  174. package/package.json +7 -3
  175. package/dist/aun/group-identity.js +0 -9
  176. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  177. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -46,6 +46,8 @@ export class InteractionRouter {
46
46
  clearTimeout(existing.timer);
47
47
  const isReplacement = !!existing;
48
48
  const pending = {
49
+ blocking: opts?.blocking !== false,
50
+ retainOnRejected: opts?.retainOnRejected === true,
49
51
  callback,
50
52
  sessionId,
51
53
  initiatorId: opts?.initiatorId,
@@ -61,7 +63,8 @@ export class InteractionRouter {
61
63
  if (this.handlers.get(id) !== pending)
62
64
  return;
63
65
  this.handlers.delete(id);
64
- this.decPending(pending.sessionId);
66
+ if (pending.blocking)
67
+ this.decPending(pending.sessionId);
65
68
  const cancelReason = opts.timeoutCancelReason ?? 'timeout';
66
69
  logger.debug(`[InteractionRouter] Timeout for interaction: ${id}`);
67
70
  opts.onTimeout?.();
@@ -73,7 +76,9 @@ export class InteractionRouter {
73
76
  }, opts.timeoutMs);
74
77
  }
75
78
  this.handlers.set(id, pending);
76
- if (!isReplacement)
79
+ if (existing?.blocking && (!pending.blocking || existing.sessionId !== sessionId))
80
+ this.decPending(existing.sessionId);
81
+ if (pending.blocking && (!isReplacement || !existing?.blocking || existing.sessionId !== sessionId))
77
82
  this.incPending(sessionId);
78
83
  }
79
84
  dispatch(response) {
@@ -104,10 +109,15 @@ export class InteractionRouter {
104
109
  // The callback may already have resolved/cancelled this exact handler.
105
110
  if (this.handlers.get(response.id) !== handler)
106
111
  return accepted;
112
+ if (!accepted && handler.retainOnRejected && rejectionReason === 'backend_rejected') {
113
+ handler.inFlight = false;
114
+ return false;
115
+ }
107
116
  if (handler.timer)
108
117
  clearTimeout(handler.timer);
109
118
  this.handlers.delete(response.id);
110
- this.decPending(handler.sessionId);
119
+ if (handler.blocking)
120
+ this.decPending(handler.sessionId);
111
121
  if (!accepted && handler.onCancel) {
112
122
  Promise.resolve(handler.onCancel(rejectionReason)).catch(err => {
113
123
  logger.error(`[InteractionRouter] Rejection cancellation error for ${response.id}:`, err);
@@ -153,10 +163,13 @@ export class InteractionRouter {
153
163
  const pending = [];
154
164
  for (const [id, handler] of this.handlers.entries()) {
155
165
  if (handler.sessionId === sessionId) {
166
+ if (!handler.blocking && reason === 'new_message')
167
+ continue;
156
168
  if (handler.timer)
157
169
  clearTimeout(handler.timer);
158
170
  this.handlers.delete(id);
159
- this.decPending(handler.sessionId);
171
+ if (handler.blocking)
172
+ this.decPending(handler.sessionId);
160
173
  if (reason && handler.onCancel) {
161
174
  pending.push(Promise.resolve(handler.onCancel(reason)).then(() => undefined));
162
175
  }
@@ -170,7 +183,8 @@ export class InteractionRouter {
170
183
  if (handler.timer)
171
184
  clearTimeout(handler.timer);
172
185
  this.handlers.delete(id);
173
- this.decPending(handler.sessionId);
186
+ if (handler.blocking)
187
+ this.decPending(handler.sessionId);
174
188
  if (reason && handler.onCancel)
175
189
  await handler.onCancel(reason);
176
190
  return true;
@@ -208,10 +222,10 @@ export class InteractionRouter {
208
222
  return false;
209
223
  }
210
224
  }
211
- getPending(sessionId) {
225
+ getPending(sessionId, command) {
212
226
  const ids = [];
213
227
  for (const [id, handler] of this.handlers.entries()) {
214
- if (handler.sessionId === sessionId)
228
+ if (handler.sessionId === sessionId && (!command || handler.fallbackCommand === command))
215
229
  ids.push(id);
216
230
  }
217
231
  return ids;
@@ -281,7 +295,7 @@ export function renderActionAsText(req) {
281
295
  if (fb.acceptFreeText && fb.freeTextHint) {
282
296
  lines.push(` ${fb.freeTextHint}`);
283
297
  }
284
- if (action.allowCustomInput) {
298
+ if (action.allowCustomInput && req.blocking !== false) {
285
299
  lines.push(` 或直接输入自定义内容`);
286
300
  }
287
301
  return lines.join('\n');
@@ -1,8 +1,9 @@
1
1
  import { logger } from '../../utils/logger.js';
2
- import { summarizeToolInput, toolInputForDisplay } from '../../utils/tool-summary.js';
2
+ import { summarizeToolInput, summarizeToolInputForDisplay, toolInputForDisplay } from '../../utils/tool-summary.js';
3
3
  import { getErrorMessage, isContextTooLongText } from '../../utils/error-utils.js';
4
4
  import { tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
5
5
  import { DEFAULT_FLUSH_DELAY_MS } from '../../types.js';
6
+ import { classifyAunToolErrorCode } from '../permission/tool-error-code.js';
6
7
  export class IMRenderer {
7
8
  opts;
8
9
  itemsQueue = [];
@@ -19,6 +20,8 @@ export class IMRenderer {
19
20
  hasEmittedText = false;
20
21
  /** 自增 callId 兜底(runner 没提供时用) */
21
22
  syntheticCallSeq = 0;
23
+ currentAssistantUuid;
24
+ currentTextPhase;
22
25
  constructor(opts) {
23
26
  this.opts = opts;
24
27
  }
@@ -61,12 +64,12 @@ export class IMRenderer {
61
64
  // interactive 模式由 MessageProcessor 显式调 addText/addToolCall/... 推入 items
62
65
  }
63
66
  /** 强制刷新所有 pending 事件 */
64
- async flush(isFinal) {
67
+ async flush(isFinal, attention) {
65
68
  if (this.opts.envelope.chatmode === 'proactive') {
66
69
  // proactive 是 fire-and-forget,无 pending buffer
67
70
  return;
68
71
  }
69
- return this.flushInternal(isFinal);
72
+ return this.flushInternal(isFinal, attention);
70
73
  }
71
74
  /** 仅 flush activities,保留 textBuffer(用于中间 complete 事件) */
72
75
  async flushActivitiesOnly() {
@@ -122,7 +125,12 @@ export class IMRenderer {
122
125
  this.textBuffer = '';
123
126
  // 清掉已消费的 text items,包括被拦截的纯空白正文。
124
127
  this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
125
- const payload = { kind: 'result.text', text: rawText, isFinal: false };
128
+ const payload = {
129
+ kind: 'result.text',
130
+ text: rawText,
131
+ isFinal: false,
132
+ ...this.forkAnchorMetadata(),
133
+ };
126
134
  this.sentContent = true;
127
135
  try {
128
136
  await this.sendPayload(payload);
@@ -168,9 +176,24 @@ export class IMRenderer {
168
176
  }
169
177
  // ── 文本/活动注入(替代 StreamFlusher.addText/addActivity)──
170
178
  /** 添加文本片段(流式 text) */
171
- addText(text, outputTokens, turn) {
179
+ async addText(text, outputTokens, turn, assistantUuid, phase) {
172
180
  if (this.opts.envelope.chatmode === 'proactive')
173
181
  return;
182
+ const sameAssistant = this.currentAssistantUuid === assistantUuid;
183
+ // In deferred AUN private delivery, only explicit text_start/tool_use
184
+ // handlers are semantic boundaries. assistantUuid is correlation data and
185
+ // may be absent or change without proving that the prior text is final.
186
+ const hadPendingText = this.textBuffer.length > 0;
187
+ if (hadPendingText && !this.opts.deferTextUntilBoundary && !sameAssistant
188
+ && !this.suppressIntermediateText()) {
189
+ await this.flushText();
190
+ }
191
+ // Do not carry the previous item's anchor onto unanchored output.
192
+ this.currentAssistantUuid = assistantUuid;
193
+ // A mixed buffer has no trustworthy phase, even when its latest chunk is commentary.
194
+ this.currentTextPhase = hadPendingText && this.opts.deferTextUntilBoundary
195
+ ? this.currentTextPhase === phase ? phase : undefined
196
+ : phase;
174
197
  this.emitProgress('text', outputTokens, turn);
175
198
  if (!text)
176
199
  return;
@@ -195,18 +218,19 @@ export class IMRenderer {
195
218
  this.emitProgress('tool_call', outputTokens, turn, { toolName: name, callId });
196
219
  if (this.suppressActivityItems())
197
220
  return;
221
+ const displayInput = toolInputForDisplay(name, input);
198
222
  this.itemsQueue.push({
199
223
  kind: 'tool_call',
200
224
  call_id: callId || this.synthCallId(),
201
225
  name,
202
- arguments: toolInputForDisplay(name, input),
203
- text: descText,
226
+ arguments: displayInput,
227
+ text: displayInput !== input ? summarizeToolInput(name, displayInput || {}) : descText,
204
228
  });
205
229
  this.messageTimestamps.push(Date.now());
206
230
  this.scheduleFlush();
207
231
  }
208
232
  /** 添加工具结果 */
209
- addToolResult(name, ok, result, error, callId, durationMs, descText) {
233
+ addToolResult(name, ok, result, error, callId, durationMs, descText, classification) {
210
234
  if (this.opts.envelope.chatmode === 'proactive')
211
235
  return;
212
236
  this.emitProgress('tool_result', undefined, undefined, { toolName: name, callId, ok, durationMs });
@@ -219,6 +243,7 @@ export class IMRenderer {
219
243
  ok,
220
244
  ...(result !== undefined && { result }),
221
245
  ...(error !== undefined && { error }),
246
+ ...(!ok && { error_code: classifyAunToolErrorCode(classification ?? {}) }),
222
247
  ...(durationMs !== undefined && { duration_ms: durationMs }),
223
248
  ...(descText !== undefined && { text: descText }),
224
249
  });
@@ -260,6 +285,21 @@ export class IMRenderer {
260
285
  logger.warn(`[IMRenderer] status notice send failed: ${error instanceof Error ? error.message : String(error)}`);
261
286
  }
262
287
  }
288
+ /** Notify the client that a new model text block has started. */
289
+ async sendTextStart(contentIndex, turn) {
290
+ if (this.opts.deferTextUntilBoundary)
291
+ await this.flushText();
292
+ const payload = {
293
+ kind: 'status.progress',
294
+ metadata: { activityType: 'text', kind: 'text_start', contentIndex, turn },
295
+ };
296
+ try {
297
+ await this.opts.send(payload);
298
+ }
299
+ catch (error) {
300
+ logger.debug(`[IMRenderer] text_start status send failed: ${error instanceof Error ? error.message : String(error)}`);
301
+ }
302
+ }
263
303
  /** 兼容旧调用方:发送 warning 级别的任务 notice。 */
264
304
  async sendWarningStatus(message, subtype, details) {
265
305
  return this.sendStatusNotice(message, 'warn', subtype, details);
@@ -317,7 +357,11 @@ export class IMRenderer {
317
357
  return false;
318
358
  // 保持与流式正文的到达顺序:先发出已缓冲的正文,再投影运行状态。
319
359
  await this.flushText();
320
- const payload = { kind: 'result.text', text, isFinal: false };
360
+ const payload = {
361
+ kind: 'result.text',
362
+ text,
363
+ isFinal: false,
364
+ };
321
365
  this.sentContent = true;
322
366
  await this.sendPayload(payload);
323
367
  this.lastFlush = Date.now();
@@ -422,7 +466,7 @@ export class IMRenderer {
422
466
  const elapsed = Date.now() - this.lastFlush;
423
467
  const delay = Math.max(0, targetDelay - elapsed);
424
468
  this.timer = setTimeout(() => {
425
- void this.flushInternal().catch(error => {
469
+ void this.flushInternal(undefined, undefined, true).catch(error => {
426
470
  logger.warn(`[IMRenderer] scheduled flush failed: ${error instanceof Error ? error.message : String(error)}`);
427
471
  });
428
472
  }, delay);
@@ -470,7 +514,7 @@ export class IMRenderer {
470
514
  * 完整 flush:把 itemsQueue 里所有 items 打包成 activity.batch 发送。
471
515
  * 如果 isFinal=true,还会在 batch 之后单独发一条 result.text 作为最终回复。
472
516
  */
473
- async flushInternal(isFinal) {
517
+ async flushInternal(isFinal, attention, scheduled = false) {
474
518
  if (this.timer) {
475
519
  clearTimeout(this.timer);
476
520
  this.timer = undefined;
@@ -501,6 +545,7 @@ export class IMRenderer {
501
545
  });
502
546
  this.itemsQueue = [];
503
547
  const finalText = isFinal ? this.textBuffer : '';
548
+ const finalAnchor = this.forkAnchorMetadata();
504
549
  if (isFinal)
505
550
  this.textBuffer = '';
506
551
  // 1. interactive 模式下:不发 text items(由 result.text 统一发送最终文本)
@@ -530,9 +575,18 @@ export class IMRenderer {
530
575
  if (!isFinal && this.textBuffer.length > 0) {
531
576
  if (this.suppressIntermediateText())
532
577
  return;
578
+ // A timer cannot decide whether this is the final answer. Keep it for
579
+ // an explicit boundary; known commentary can still be sent immediately.
580
+ if (scheduled && this.opts.deferTextUntilBoundary && this.currentTextPhase !== 'commentary')
581
+ return;
533
582
  const text = this.textBuffer;
534
583
  this.textBuffer = '';
535
- const payload = { kind: 'result.text', text, isFinal: false };
584
+ const payload = {
585
+ kind: 'result.text',
586
+ text,
587
+ isFinal: false,
588
+ ...this.forkAnchorMetadata(),
589
+ };
536
590
  this.sentContent = true;
537
591
  try {
538
592
  await this.sendPayload(payload);
@@ -546,7 +600,14 @@ export class IMRenderer {
546
600
  }
547
601
  // 2. isFinal=true 时单独发最终回复文本
548
602
  if (isFinal && finalText.length > 0) {
549
- const payload = { kind: 'result.text', text: finalText, isFinal: true };
603
+ const payload = {
604
+ kind: 'result.text',
605
+ text: finalText,
606
+ isFinal: true,
607
+ ...finalAnchor,
608
+ };
609
+ if (attention !== undefined)
610
+ payload.attention = attention;
550
611
  this.sentContent = true;
551
612
  try {
552
613
  await this.sendPayload(payload);
@@ -559,6 +620,15 @@ export class IMRenderer {
559
620
  this.flushCount++;
560
621
  }
561
622
  }
623
+ forkAnchorMetadata() {
624
+ const assistantMessageId = this.currentAssistantUuid?.trim();
625
+ if (!assistantMessageId)
626
+ return {};
627
+ const anchor = {
628
+ assistantMessageId,
629
+ };
630
+ return { metadata: { forkAnchor: anchor } };
631
+ }
562
632
  // ── 内部:status.progress 发送 ──
563
633
  emitProgress(activityType, outputTokens, turn, extra) {
564
634
  if (this.suppressActivityItems())
@@ -592,9 +662,16 @@ export class IMRenderer {
592
662
  const item = this.mapEventToItem(event);
593
663
  if (!item)
594
664
  return;
665
+ // Proactive output is optional thought content. Blank runner blocks are
666
+ // normal; keep diagnostics without reporting a failed message send.
667
+ if ((item.kind === 'text' || (item.kind === 'summary' && !item.is_error)) && !item.text.trim()) {
668
+ const { envelope, agentAid } = this.opts;
669
+ logger.debug(`[IMRenderer] Skipping blank proactive thought: kind=${item.kind}`
670
+ + ` task=${envelope.taskId} session=${envelope.sessionId ?? '<none>'}`
671
+ + ` agent=${agentAid ?? envelope.agentName} textLength=${item.text.length}`);
672
+ return;
673
+ }
595
674
  if (item.kind === 'text') {
596
- if (!this.hasSendableText(item.text, 'proactive'))
597
- return;
598
675
  this.hasEmittedText = true;
599
676
  this.allText += item.text;
600
677
  }
@@ -622,7 +699,7 @@ export class IMRenderer {
622
699
  return null;
623
700
  return { kind: 'text', text: event.text };
624
701
  case 'tool_use': {
625
- const desc = summarizeToolInput(event.name, event.input || {});
702
+ const desc = summarizeToolInputForDisplay(event.name, event.input || {});
626
703
  const item = {
627
704
  kind: 'tool_call',
628
705
  call_id: event.callId || this.synthCallId(),
@@ -640,6 +717,7 @@ export class IMRenderer {
640
717
  call_id: event.callId || this.synthCallId(),
641
718
  name: event.name,
642
719
  ok: false,
720
+ error_code: classifyAunToolErrorCode(event),
643
721
  error: event.error || (typeof event.result === 'string' ? event.result : '执行失败'),
644
722
  ...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
645
723
  };