evolcore 0.0.25 → 0.0.27

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 (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /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,7 +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
+ import { tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
4
5
  import { DEFAULT_FLUSH_DELAY_MS } from '../../types.js';
6
+ import { classifyAunToolErrorCode } from '../permission/tool-error-code.js';
5
7
  export class IMRenderer {
6
8
  opts;
7
9
  itemsQueue = [];
@@ -18,6 +20,7 @@ export class IMRenderer {
18
20
  hasEmittedText = false;
19
21
  /** 自增 callId 兜底(runner 没提供时用) */
20
22
  syntheticCallSeq = 0;
23
+ currentAssistantUuid;
21
24
  constructor(opts) {
22
25
  this.opts = opts;
23
26
  }
@@ -60,12 +63,12 @@ export class IMRenderer {
60
63
  // interactive 模式由 MessageProcessor 显式调 addText/addToolCall/... 推入 items
61
64
  }
62
65
  /** 强制刷新所有 pending 事件 */
63
- async flush(isFinal) {
66
+ async flush(isFinal, attention) {
64
67
  if (this.opts.envelope.chatmode === 'proactive') {
65
68
  // proactive 是 fire-and-forget,无 pending buffer
66
69
  return;
67
70
  }
68
- return this.flushInternal(isFinal);
71
+ return this.flushInternal(isFinal, attention);
69
72
  }
70
73
  /** 仅 flush activities,保留 textBuffer(用于中间 complete 事件) */
71
74
  async flushActivitiesOnly() {
@@ -79,9 +82,9 @@ export class IMRenderer {
79
82
  }
80
83
  /** 是否有会抑制兜底错误消息的正文/过程内容;lifecycle 边界不算。 */
81
84
  hasNonLifecycleContent() {
82
- return this.textBuffer.length > 0 || this.itemsQueue.some(it => {
85
+ return this.textBuffer.trim().length > 0 || this.itemsQueue.some(it => {
83
86
  if (it.kind === 'text')
84
- return it.text.length > 0;
87
+ return it.text.trim().length > 0;
85
88
  return it.kind !== 'started' && it.kind !== 'completed';
86
89
  });
87
90
  }
@@ -95,6 +98,21 @@ export class IMRenderer {
95
98
  return;
96
99
  if (this.textBuffer.length === 0)
97
100
  return;
101
+ // 文件标记过滤后若只剩空白,应立即消费并告警;尤其避免 final-only
102
+ // 模式把工具调用前的空白继续带到最终回复。
103
+ const rawText = this.opts.fileMarkerPattern
104
+ ? this.textBuffer.replace(this.opts.fileMarkerPattern, '')
105
+ : this.textBuffer;
106
+ if (!rawText.trim()) {
107
+ if (this.timer) {
108
+ clearTimeout(this.timer);
109
+ this.timer = undefined;
110
+ }
111
+ this.textBuffer = '';
112
+ this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
113
+ this.hasSendableText(rawText, 'flushText');
114
+ return;
115
+ }
98
116
  if (this.suppressIntermediateText()) {
99
117
  this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
100
118
  return;
@@ -103,16 +121,15 @@ export class IMRenderer {
103
121
  clearTimeout(this.timer);
104
122
  this.timer = undefined;
105
123
  }
106
- // 文件标记过滤:tool_use 前的文本块也不能把 marker 暴露给用户
107
- const rawText = this.opts.fileMarkerPattern
108
- ? this.textBuffer.replace(this.opts.fileMarkerPattern, '')
109
- : this.textBuffer;
110
124
  this.textBuffer = '';
111
- if (!rawText)
112
- return;
113
- // 清掉 itemsQueue 中的 text items(已发出)
125
+ // 清掉已消费的 text items,包括被拦截的纯空白正文。
114
126
  this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
115
- const payload = { kind: 'result.text', text: rawText, isFinal: false };
127
+ const payload = {
128
+ kind: 'result.text',
129
+ text: rawText,
130
+ isFinal: false,
131
+ ...this.forkAnchorMetadata(),
132
+ };
116
133
  this.sentContent = true;
117
134
  try {
118
135
  await this.sendPayload(payload);
@@ -158,12 +175,18 @@ export class IMRenderer {
158
175
  }
159
176
  // ── 文本/活动注入(替代 StreamFlusher.addText/addActivity)──
160
177
  /** 添加文本片段(流式 text) */
161
- addText(text, outputTokens, turn) {
178
+ async addText(text, outputTokens, turn, assistantUuid) {
162
179
  if (this.opts.envelope.chatmode === 'proactive')
163
180
  return;
181
+ if (this.textBuffer && this.currentAssistantUuid !== assistantUuid && !this.suppressIntermediateText()) {
182
+ await this.flushText();
183
+ }
184
+ // Do not carry the previous item's anchor onto unanchored output.
185
+ this.currentAssistantUuid = assistantUuid;
164
186
  this.emitProgress('text', outputTokens, turn);
165
187
  if (!text)
166
188
  return;
189
+ // 保留流式片段之间的空格/换行;仅在发送完整正文时判定是否纯空白。
167
190
  // 同一窗口内连续 text delta 合并到最后一个 text item
168
191
  const last = this.itemsQueue[this.itemsQueue.length - 1];
169
192
  if (last && last.kind === 'text') {
@@ -184,18 +207,19 @@ export class IMRenderer {
184
207
  this.emitProgress('tool_call', outputTokens, turn, { toolName: name, callId });
185
208
  if (this.suppressActivityItems())
186
209
  return;
210
+ const displayInput = toolInputForDisplay(name, input);
187
211
  this.itemsQueue.push({
188
212
  kind: 'tool_call',
189
213
  call_id: callId || this.synthCallId(),
190
214
  name,
191
- arguments: toolInputForDisplay(name, input),
192
- text: descText,
215
+ arguments: displayInput,
216
+ text: displayInput !== input ? summarizeToolInput(name, displayInput || {}) : descText,
193
217
  });
194
218
  this.messageTimestamps.push(Date.now());
195
219
  this.scheduleFlush();
196
220
  }
197
221
  /** 添加工具结果 */
198
- addToolResult(name, ok, result, error, callId, durationMs, descText) {
222
+ addToolResult(name, ok, result, error, callId, durationMs, descText, classification) {
199
223
  if (this.opts.envelope.chatmode === 'proactive')
200
224
  return;
201
225
  this.emitProgress('tool_result', undefined, undefined, { toolName: name, callId, ok, durationMs });
@@ -208,6 +232,7 @@ export class IMRenderer {
208
232
  ok,
209
233
  ...(result !== undefined && { result }),
210
234
  ...(error !== undefined && { error }),
235
+ ...(!ok && { error_code: classifyAunToolErrorCode(classification ?? {}) }),
211
236
  ...(durationMs !== undefined && { duration_ms: durationMs }),
212
237
  ...(descText !== undefined && { text: descText }),
213
238
  });
@@ -249,6 +274,19 @@ export class IMRenderer {
249
274
  logger.warn(`[IMRenderer] status notice send failed: ${error instanceof Error ? error.message : String(error)}`);
250
275
  }
251
276
  }
277
+ /** Notify the client that a new model text block has started. */
278
+ async sendTextStart(contentIndex, turn) {
279
+ const payload = {
280
+ kind: 'status.progress',
281
+ metadata: { activityType: 'text', kind: 'text_start', contentIndex, turn },
282
+ };
283
+ try {
284
+ await this.opts.send(payload);
285
+ }
286
+ catch (error) {
287
+ logger.debug(`[IMRenderer] text_start status send failed: ${error instanceof Error ? error.message : String(error)}`);
288
+ }
289
+ }
252
290
  /** 兼容旧调用方:发送 warning 级别的任务 notice。 */
253
291
  async sendWarningStatus(message, subtype, details) {
254
292
  return this.sendStatusNotice(message, 'warn', subtype, details);
@@ -302,9 +340,15 @@ export class IMRenderer {
302
340
  async sendOperationalNoticeAsText(text) {
303
341
  if (!this.opts.operationalNoticesAsText || this.opts.envelope.chatmode === 'proactive' || !text)
304
342
  return false;
343
+ if (!this.hasSendableText(text, 'operational-notice'))
344
+ return false;
305
345
  // 保持与流式正文的到达顺序:先发出已缓冲的正文,再投影运行状态。
306
346
  await this.flushText();
307
- const payload = { kind: 'result.text', text, isFinal: false };
347
+ const payload = {
348
+ kind: 'result.text',
349
+ text,
350
+ isFinal: false,
351
+ };
308
352
  this.sentContent = true;
309
353
  await this.sendPayload(payload);
310
354
  this.lastFlush = Date.now();
@@ -315,6 +359,17 @@ export class IMRenderer {
315
359
  synthCallId() {
316
360
  return `synth-${this.opts.envelope.taskId}-${++this.syntheticCallSeq}`;
317
361
  }
362
+ /** Reject blank bodies before sending, without trimming meaningful formatting. */
363
+ hasSendableText(text, source) {
364
+ if (text.trim())
365
+ return true;
366
+ // An unused buffer is normal. A nonempty whitespace body is diagnostic evidence.
367
+ if (text.length > 0) {
368
+ const { envelope, agentAid } = this.opts;
369
+ logger.warn(`[IMRenderer] Attempted to send empty message, skipping: code=EMPTY_MESSAGE source=${source} task=${envelope.taskId} session=${envelope.sessionId ?? '<none>'} agent=${agentAid ?? envelope.agentName} textLength=${text.length} text=${JSON.stringify(text.slice(0, 80))}`);
370
+ }
371
+ return false;
372
+ }
318
373
  /** Convert a structured failed receipt into the same error shape as a rejected sender. */
319
374
  receiptError(receipt) {
320
375
  return Object.assign(new Error(receipt.error), {
@@ -331,6 +386,18 @@ export class IMRenderer {
331
386
  : undefined;
332
387
  return value.code === 'OUTBOX_FULL' || cause?.code === 'OUTBOX_FULL';
333
388
  }
389
+ isPermanentActivityError(error) {
390
+ const value = error && typeof error === 'object' ? error : {};
391
+ const cause = value.cause && typeof value.cause === 'object'
392
+ ? value.cause
393
+ : undefined;
394
+ const code = String(value.code ?? cause?.code ?? '');
395
+ return value.permanent === true
396
+ || value.status === 'failed'
397
+ || code === 'AUN_SEND_FAILED'
398
+ || code === '-1'
399
+ || /payload is too large/i.test(String(value.message ?? value.error ?? ''));
400
+ }
334
401
  /**
335
402
  * Serialize durable sends while keeping the chain usable after a rejected
336
403
  * operation. Activity queue saturation is best-effort: it is logged, but
@@ -344,9 +411,19 @@ export class IMRenderer {
344
411
  }
345
412
  });
346
413
  const handled = operation.catch(error => {
347
- if (payload.kind === 'activity.batch' && this.isOutboxFull(error)) {
348
- logger.warn(`[IMRenderer] activity outbox full; continuing without activity batch: ${error instanceof Error ? error.message : String(error)}`);
349
- return;
414
+ if (payload.kind === 'activity.batch') {
415
+ const detail = error instanceof Error ? error.message : String(error);
416
+ if (this.isOutboxFull(error)) {
417
+ logger.warn(`[IMRenderer] activity outbox full; continuing without activity batch: ${detail}`);
418
+ return;
419
+ }
420
+ if (this.isPermanentActivityError(error)) {
421
+ // Activity is best-effort. A permanent structured-send failure (for
422
+ // example an AUN payload-size rejection) must not requeue the same
423
+ // item indefinitely or prevent the final result.text from flushing.
424
+ logger.warn(`[IMRenderer] activity delivery failed; dropping activity batch and continuing: ${detail}`);
425
+ return;
426
+ }
350
427
  }
351
428
  throw error;
352
429
  });
@@ -424,7 +501,7 @@ export class IMRenderer {
424
501
  * 完整 flush:把 itemsQueue 里所有 items 打包成 activity.batch 发送。
425
502
  * 如果 isFinal=true,还会在 batch 之后单独发一条 result.text 作为最终回复。
426
503
  */
427
- async flushInternal(isFinal) {
504
+ async flushInternal(isFinal, attention) {
428
505
  if (this.timer) {
429
506
  clearTimeout(this.timer);
430
507
  this.timer = undefined;
@@ -432,26 +509,30 @@ export class IMRenderer {
432
509
  // 文件标记过滤:marker 任何时候都不该出现在用户文本里,必须在 *每次* flush
433
510
  // (含非最终的定时 flush)执行。否则文本块若滞留 buffer 后被定时器触发,会走
434
511
  // 下方非-final 的 result.text 路径,把 [SEND_FILE:...] 原样发给用户。
435
- // 非最终 flush 不做 trim,避免 trim 掉 Markdown 块级换行。
512
+ // 先保留空白,以便空正文告警仍能记录原长度。
436
513
  if (this.opts.fileMarkerPattern) {
437
- const strip = (s) => {
438
- const replaced = s.replace(this.opts.fileMarkerPattern, '');
439
- return isFinal ? replaced.trim() : replaced;
440
- };
441
- this.textBuffer = strip(this.textBuffer);
514
+ this.textBuffer = this.textBuffer.replace(this.opts.fileMarkerPattern, '');
442
515
  for (const item of this.itemsQueue) {
443
516
  if (item.kind === 'text')
444
517
  item.text = item.text.replace(this.opts.fileMarkerPattern, '');
445
518
  }
446
519
  }
520
+ if (!this.hasSendableText(this.textBuffer, isFinal ? 'final' : 'flush')) {
521
+ this.textBuffer = '';
522
+ }
523
+ else if (isFinal && this.opts.fileMarkerPattern) {
524
+ // 保持既有最终文件标记过滤语义;中间文本的格式原样保留。
525
+ this.textBuffer = this.textBuffer.trim();
526
+ }
447
527
  // 清掉空 text items
448
528
  const items = this.itemsQueue.filter(it => {
449
529
  if (it.kind === 'text')
450
- return it.text.length > 0;
530
+ return it.text.trim().length > 0;
451
531
  return true;
452
532
  });
453
533
  this.itemsQueue = [];
454
534
  const finalText = isFinal ? this.textBuffer : '';
535
+ const finalAnchor = this.forkAnchorMetadata();
455
536
  if (isFinal)
456
537
  this.textBuffer = '';
457
538
  // 1. interactive 模式下:不发 text items(由 result.text 统一发送最终文本)
@@ -483,7 +564,12 @@ export class IMRenderer {
483
564
  return;
484
565
  const text = this.textBuffer;
485
566
  this.textBuffer = '';
486
- const payload = { kind: 'result.text', text, isFinal: false };
567
+ const payload = {
568
+ kind: 'result.text',
569
+ text,
570
+ isFinal: false,
571
+ ...this.forkAnchorMetadata(),
572
+ };
487
573
  this.sentContent = true;
488
574
  try {
489
575
  await this.sendPayload(payload);
@@ -497,7 +583,14 @@ export class IMRenderer {
497
583
  }
498
584
  // 2. isFinal=true 时单独发最终回复文本
499
585
  if (isFinal && finalText.length > 0) {
500
- const payload = { kind: 'result.text', text: finalText, isFinal: true };
586
+ const payload = {
587
+ kind: 'result.text',
588
+ text: finalText,
589
+ isFinal: true,
590
+ ...finalAnchor,
591
+ };
592
+ if (attention !== undefined)
593
+ payload.attention = attention;
501
594
  this.sentContent = true;
502
595
  try {
503
596
  await this.sendPayload(payload);
@@ -510,6 +603,15 @@ export class IMRenderer {
510
603
  this.flushCount++;
511
604
  }
512
605
  }
606
+ forkAnchorMetadata() {
607
+ const assistantMessageId = this.currentAssistantUuid?.trim();
608
+ if (!assistantMessageId)
609
+ return {};
610
+ const anchor = {
611
+ assistantMessageId,
612
+ };
613
+ return { metadata: { forkAnchor: anchor } };
614
+ }
513
615
  // ── 内部:status.progress 发送 ──
514
616
  emitProgress(activityType, outputTokens, turn, extra) {
515
617
  if (this.suppressActivityItems())
@@ -543,6 +645,15 @@ export class IMRenderer {
543
645
  const item = this.mapEventToItem(event);
544
646
  if (!item)
545
647
  return;
648
+ // Proactive output is optional thought content. Blank runner blocks are
649
+ // normal; keep diagnostics without reporting a failed message send.
650
+ if ((item.kind === 'text' || (item.kind === 'summary' && !item.is_error)) && !item.text.trim()) {
651
+ const { envelope, agentAid } = this.opts;
652
+ logger.debug(`[IMRenderer] Skipping blank proactive thought: kind=${item.kind}`
653
+ + ` task=${envelope.taskId} session=${envelope.sessionId ?? '<none>'}`
654
+ + ` agent=${agentAid ?? envelope.agentName} textLength=${item.text.length}`);
655
+ return;
656
+ }
546
657
  if (item.kind === 'text') {
547
658
  this.hasEmittedText = true;
548
659
  this.allText += item.text;
@@ -571,7 +682,7 @@ export class IMRenderer {
571
682
  return null;
572
683
  return { kind: 'text', text: event.text };
573
684
  case 'tool_use': {
574
- const desc = summarizeToolInput(event.name, event.input || {});
685
+ const desc = summarizeToolInputForDisplay(event.name, event.input || {});
575
686
  const item = {
576
687
  kind: 'tool_call',
577
688
  call_id: event.callId || this.synthCallId(),
@@ -589,6 +700,7 @@ export class IMRenderer {
589
700
  call_id: event.callId || this.synthCallId(),
590
701
  name: event.name,
591
702
  ok: false,
703
+ error_code: classifyAunToolErrorCode(event),
592
704
  error: event.error || (typeof event.result === 'string' ? event.result : '执行失败'),
593
705
  ...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
594
706
  };
@@ -633,7 +745,7 @@ export class IMRenderer {
633
745
  if (isContextTooLongText(event.error || ''))
634
746
  return null;
635
747
  const text = getErrorMessage(new Error(event.error || '任务执行失败'), undefined, false);
636
- return { kind: 'notice', text, severity: 'warn' };
748
+ return { kind: 'notice', text: tagErrorTextWithEvolcoreVersion(text), severity: 'warn' };
637
749
  }
638
750
  case 'complete': {
639
751
  if (event.queryFinal === false)
@@ -650,7 +762,7 @@ export class IMRenderer {
650
762
  const text = getErrorMessage(new Error(errText), event.terminalReason, false);
651
763
  const item = {
652
764
  kind: 'summary',
653
- text,
765
+ text: tagErrorTextWithEvolcoreVersion(text),
654
766
  is_error: true,
655
767
  };
656
768
  if (event.subtype !== undefined)