evolcore 0.0.25 → 0.0.26

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 (231) hide show
  1. package/CHANGELOG.md +607 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +160 -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.js +96 -46
  10. package/dist/agents/claude-runner.js +50 -12
  11. package/dist/agents/codex/app-server-client.js +13 -5
  12. package/dist/agents/codex/catalog.js +1 -1
  13. package/dist/agents/codex/diagnostics.js +34 -1
  14. package/dist/agents/codex-runner.js +121 -38
  15. package/dist/agents/ecagent-runner.js +4 -9
  16. package/dist/agents/gemini-runner.js +2 -7
  17. package/dist/agents/runner-types.js +25 -17
  18. package/dist/aun/aid/agentmd.js +81 -8
  19. package/dist/aun/aid/client.js +1 -1
  20. package/dist/aun/aid/identity.js +1 -1
  21. package/dist/aun/aid/index.js +1 -1
  22. package/dist/aun/aid/store.js +17 -13
  23. package/dist/aun/menu-client.js +74 -0
  24. package/dist/aun/msg/group.js +1 -1
  25. package/dist/aun/msg/managed-operation.js +1 -1
  26. package/dist/aun/msg/p2p.js +6 -1
  27. package/dist/aun/msg/upload.js +30 -22
  28. package/dist/channels/aun.js +329 -94
  29. package/dist/cli/agent-command.js +105 -84
  30. package/dist/cli/agent-create.js +112 -10
  31. package/dist/cli/agent.js +16 -5
  32. package/dist/cli/aun-commands.js +297 -296
  33. package/dist/cli/bench.js +20 -20
  34. package/dist/cli/config.js +47 -28
  35. package/dist/cli/contact.js +70 -13
  36. package/dist/cli/ctl-command.js +22 -22
  37. package/dist/cli/daemon-commands.js +105 -40
  38. package/dist/cli/diag.js +31 -0
  39. package/dist/cli/fs-command.js +67 -67
  40. package/dist/cli/handoff-command.js +41 -9
  41. package/dist/cli/index.js +83 -77
  42. package/dist/cli/link-rules.js +21 -21
  43. package/dist/cli/managed-command-guard.js +3 -0
  44. package/dist/cli/menu-client.js +46 -31
  45. package/dist/cli/net-check.js +22 -22
  46. package/dist/cli/queue-command.js +29 -29
  47. package/dist/cli/response.js +30 -30
  48. package/dist/cli/restart-monitor.js +87 -40
  49. package/dist/cli/stats.js +51 -51
  50. package/dist/cli/trigger-command.js +56 -56
  51. package/dist/config/builtin-roles.js +1 -0
  52. package/dist/config/config-operation-service.js +145 -0
  53. package/dist/config/contact-operation-service.js +96 -7
  54. package/dist/config/resolved-config-op.js +27 -7
  55. package/dist/core/agent-application-service.js +2 -1
  56. package/dist/core/auth/authorization-audit.js +20 -12
  57. package/dist/core/auth/operation-authorizer.js +25 -4
  58. package/dist/core/auth/operation-catalog.js +35 -4
  59. package/dist/core/auth/remote-access-store.js +40 -1
  60. package/dist/core/bootstrap-service.js +1 -1
  61. package/dist/core/command/command-handler.js +83 -26
  62. package/dist/core/command/diag-service.js +118 -0
  63. package/dist/core/command/menu-catalog.js +12 -3
  64. package/dist/core/command/menu-handler.js +47 -26
  65. package/dist/core/command/menu-protocol.js +5 -0
  66. package/dist/core/command/menu-token-store.js +41 -8
  67. package/dist/core/command/slash-gate.js +34 -4
  68. package/dist/core/command/slash-handler.js +86 -21
  69. package/dist/core/handoff/runtime.js +205 -27
  70. package/dist/core/handoff/store.js +193 -4
  71. package/dist/core/message/im-renderer.js +72 -21
  72. package/dist/core/message/message-bridge.js +69 -9
  73. package/dist/core/message/message-queue.js +26 -0
  74. package/dist/core/message/message-utils.js +1 -0
  75. package/dist/core/message/response-engine.js +47 -28
  76. package/dist/core/model/catalog-service.js +79 -49
  77. package/dist/core/model/context-window-suffix.js +33 -0
  78. package/dist/core/model/model-catalog.js +1 -1
  79. package/dist/core/model/model-permission.js +12 -3
  80. package/dist/core/permission/ec-command-parser.js +51 -12
  81. package/dist/core/permission/sandbox-runtime.js +36 -14
  82. package/dist/core/permission/tool-policy.js +2 -1
  83. package/dist/index.js +280 -163
  84. package/dist/ipc.js +54 -2
  85. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  86. package/dist/stats/billing.js +41 -15
  87. package/dist/stats/db.js +159 -159
  88. package/dist/stats/eck-vars.js +9 -9
  89. package/dist/stats/price-resolver.js +28 -10
  90. package/dist/stats/query.js +139 -139
  91. package/dist/stats/role-budget.js +6 -6
  92. package/dist/stats/writer.js +51 -51
  93. package/dist/utils/cross-platform.js +30 -1
  94. package/dist/utils/error-dict.json +160 -160
  95. package/dist/utils/error-utils.js +21 -0
  96. package/dist/utils/evolcore-version.js +129 -0
  97. package/dist/utils/logger.js +3 -1
  98. package/dist/utils/macos-autostart.js +22 -22
  99. package/dist/utils/model-prices.jsonl +23 -23
  100. package/dist/utils/npm-ops.js +176 -35
  101. package/dist/utils/welcome.js +22 -22
  102. package/ecagent/LICENSE +21 -21
  103. package/ecagent/NOTICE +11 -11
  104. package/ecagent/README.md +61 -61
  105. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  106. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  107. package/ecagent/dist/harness/messages.js +7 -7
  108. package/kits/docs/GUIDE.md +20 -20
  109. package/kits/docs/INDEX.md +66 -66
  110. package/kits/docs/aun/CHEATSHEET.md +19 -19
  111. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  112. package/kits/docs/channels/aun.md +56 -56
  113. package/kits/docs/channels/feishu.md +56 -56
  114. package/kits/docs/context-assembly.md +364 -364
  115. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  116. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  117. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  118. package/kits/docs/eck_templates/runtime.template.md +19 -19
  119. package/kits/docs/evolcore/INDEX.md +71 -71
  120. package/kits/docs/evolcore/agent-create.md +170 -165
  121. package/kits/docs/evolcore/agent.md +66 -66
  122. package/kits/docs/evolcore/aid.md +60 -60
  123. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  124. package/kits/docs/evolcore/config.md +202 -195
  125. package/kits/docs/evolcore/contact.md +75 -68
  126. package/kits/docs/evolcore/ctl.md +46 -46
  127. package/kits/docs/evolcore/event.md +216 -216
  128. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  129. package/kits/docs/evolcore/fs.md +110 -110
  130. package/kits/docs/evolcore/group-collaboration.md +243 -243
  131. package/kits/docs/evolcore/group-fs.md +17 -17
  132. package/kits/docs/evolcore/group-rules.md +251 -251
  133. package/kits/docs/evolcore/group.md +152 -152
  134. package/kits/docs/evolcore/menu-client.md +42 -34
  135. package/kits/docs/evolcore/model.md +50 -50
  136. package/kits/docs/evolcore/msg.md +166 -165
  137. package/kits/docs/evolcore/response.md +75 -75
  138. package/kits/docs/evolcore/rpc.md +37 -37
  139. package/kits/docs/evolcore/self-summary.md +29 -29
  140. package/kits/docs/evolcore/stats.md +83 -83
  141. package/kits/docs/evolcore/storage.md +50 -50
  142. package/kits/docs/evolcore/trigger.md +575 -575
  143. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  144. package/kits/docs/identity/PATH_OPS.md +16 -16
  145. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  146. package/kits/docs/identity/identity-tools.md +26 -26
  147. package/kits/docs/path-registry.md +62 -62
  148. package/kits/docs/prompt-loading-architecture.md +269 -269
  149. package/kits/docs/venues/aun-group.md +45 -45
  150. package/kits/docs/venues/aun-private.md +10 -10
  151. package/kits/docs/venues/client-desktop.md +10 -10
  152. package/kits/docs/venues/client-mobile.md +10 -10
  153. package/kits/docs/venues/feishu-group.md +13 -13
  154. package/kits/docs/venues/feishu-private.md +9 -9
  155. package/kits/docs/venues/group.md +37 -25
  156. package/kits/docs/venues/private.md +10 -10
  157. package/kits/eck_manifest.auxiliary.json +43 -43
  158. package/kits/eck_manifest.json +190 -190
  159. package/kits/eck_message_manifest.json +74 -74
  160. package/kits/migrations/README-role-config-v4.md +32 -32
  161. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  162. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  163. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  164. package/kits/migrations/rename-config-file.mjs +99 -99
  165. package/kits/rules/01-overview.md +162 -162
  166. package/kits/rules/02-navigation.md +96 -96
  167. package/kits/rules/03-identity.md +36 -36
  168. package/kits/rules/04-relation.md +75 -75
  169. package/kits/rules/05-venue.md +60 -60
  170. package/kits/rules/06-channel.md +63 -63
  171. package/kits/schemas/_meta.json +53 -53
  172. package/kits/schemas/agent-config.schema.1.json +177 -177
  173. package/kits/schemas/agent-config.schema.10.json +395 -395
  174. package/kits/schemas/agent-config.schema.11.json +421 -421
  175. package/kits/schemas/agent-config.schema.12.json +427 -427
  176. package/kits/schemas/agent-config.schema.2.json +239 -239
  177. package/kits/schemas/agent-config.schema.3.json +119 -119
  178. package/kits/schemas/agent-config.schema.4.json +208 -208
  179. package/kits/schemas/agent-config.schema.5.json +326 -326
  180. package/kits/schemas/agent-config.schema.6.json +322 -322
  181. package/kits/schemas/agent-config.schema.7.json +303 -303
  182. package/kits/schemas/agent-config.schema.8.json +304 -304
  183. package/kits/schemas/agent-config.schema.9.json +364 -364
  184. package/kits/schemas/contact-book.schema.1.json +36 -36
  185. package/kits/schemas/contact-book.schema.2.json +43 -43
  186. package/kits/schemas/contact-book.schema.3.json +67 -67
  187. package/kits/schemas/daemon.schema.1.json +91 -91
  188. package/kits/schemas/daemon.schema.2.json +101 -101
  189. package/kits/schemas/daemon.schema.3.json +123 -123
  190. package/kits/schemas/daemon.schema.4.json +136 -136
  191. package/kits/schemas/daemon.schema.5.json +135 -135
  192. package/kits/schemas/daemon.schema.6.json +131 -131
  193. package/kits/schemas/defaults.schema.1.json +81 -81
  194. package/kits/schemas/defaults.schema.2.json +85 -85
  195. package/kits/schemas/defaults.schema.3.json +73 -73
  196. package/kits/schemas/defaults.schema.4.json +107 -107
  197. package/kits/schemas/defaults.schema.5.json +119 -119
  198. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  199. package/kits/schemas/migrations/README.md +30 -30
  200. package/kits/schemas/relation-config.schema.1.json +158 -158
  201. package/kits/schemas/relation-config.schema.2.json +73 -73
  202. package/kits/schemas/relation-config.schema.3.json +50 -50
  203. package/kits/schemas/relation-config.schema.4.json +47 -47
  204. package/kits/schemas/relation-config.schema.5.json +47 -47
  205. package/kits/schemas/relation-config.schema.6.json +46 -46
  206. package/kits/schemas/relation-config.schema.7.json +59 -59
  207. package/kits/schemas/relation-config.schema.8.json +103 -103
  208. package/kits/schemas/role-config.schema.1.json +201 -201
  209. package/kits/schemas/role-registry.schema.1.json +35 -35
  210. package/kits/schemas/single-session.schema.1.json +31 -31
  211. package/kits/schemas/single-session.schema.2.json +57 -57
  212. package/kits/schemas/single-session.schema.3.json +32 -32
  213. package/kits/templates/bootstrap-welcome.md +17 -17
  214. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  215. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  216. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  217. package/kits/templates/message-fragments/inject-default.md +2 -2
  218. package/kits/templates/message-fragments/item.md +2 -2
  219. package/kits/templates/roles/admin.json +92 -92
  220. package/kits/templates/roles/member.json +96 -96
  221. package/kits/templates/roles/owner.json +9 -9
  222. package/kits/templates/roles/visitor.json +85 -85
  223. package/kits/templates/system-fragments/baseagent.md +14 -14
  224. package/kits/templates/system-fragments/bootstrap.md +31 -29
  225. package/kits/templates/system-fragments/channel.md +64 -64
  226. package/kits/templates/system-fragments/commands.md +28 -28
  227. package/kits/templates/system-fragments/identity.md +13 -13
  228. package/kits/templates/system-fragments/relation.md +19 -19
  229. package/kits/templates/system-fragments/session.md +59 -59
  230. package/kits/templates/system-fragments/venue.md +31 -31
  231. package/package.json +100 -98
@@ -1,6 +1,7 @@
1
1
  import { logger } from '../../utils/logger.js';
2
2
  import { summarizeToolInput, 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';
5
6
  export class IMRenderer {
6
7
  opts;
@@ -79,9 +80,9 @@ export class IMRenderer {
79
80
  }
80
81
  /** 是否有会抑制兜底错误消息的正文/过程内容;lifecycle 边界不算。 */
81
82
  hasNonLifecycleContent() {
82
- return this.textBuffer.length > 0 || this.itemsQueue.some(it => {
83
+ return this.textBuffer.trim().length > 0 || this.itemsQueue.some(it => {
83
84
  if (it.kind === 'text')
84
- return it.text.length > 0;
85
+ return it.text.trim().length > 0;
85
86
  return it.kind !== 'started' && it.kind !== 'completed';
86
87
  });
87
88
  }
@@ -95,6 +96,21 @@ export class IMRenderer {
95
96
  return;
96
97
  if (this.textBuffer.length === 0)
97
98
  return;
99
+ // 文件标记过滤后若只剩空白,应立即消费并告警;尤其避免 final-only
100
+ // 模式把工具调用前的空白继续带到最终回复。
101
+ const rawText = this.opts.fileMarkerPattern
102
+ ? this.textBuffer.replace(this.opts.fileMarkerPattern, '')
103
+ : this.textBuffer;
104
+ if (!rawText.trim()) {
105
+ if (this.timer) {
106
+ clearTimeout(this.timer);
107
+ this.timer = undefined;
108
+ }
109
+ this.textBuffer = '';
110
+ this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
111
+ this.hasSendableText(rawText, 'flushText');
112
+ return;
113
+ }
98
114
  if (this.suppressIntermediateText()) {
99
115
  this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
100
116
  return;
@@ -103,14 +119,8 @@ export class IMRenderer {
103
119
  clearTimeout(this.timer);
104
120
  this.timer = undefined;
105
121
  }
106
- // 文件标记过滤:tool_use 前的文本块也不能把 marker 暴露给用户
107
- const rawText = this.opts.fileMarkerPattern
108
- ? this.textBuffer.replace(this.opts.fileMarkerPattern, '')
109
- : this.textBuffer;
110
122
  this.textBuffer = '';
111
- if (!rawText)
112
- return;
113
- // 清掉 itemsQueue 中的 text items(已发出)
123
+ // 清掉已消费的 text items,包括被拦截的纯空白正文。
114
124
  this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
115
125
  const payload = { kind: 'result.text', text: rawText, isFinal: false };
116
126
  this.sentContent = true;
@@ -164,6 +174,7 @@ export class IMRenderer {
164
174
  this.emitProgress('text', outputTokens, turn);
165
175
  if (!text)
166
176
  return;
177
+ // 保留流式片段之间的空格/换行;仅在发送完整正文时判定是否纯空白。
167
178
  // 同一窗口内连续 text delta 合并到最后一个 text item
168
179
  const last = this.itemsQueue[this.itemsQueue.length - 1];
169
180
  if (last && last.kind === 'text') {
@@ -302,6 +313,8 @@ export class IMRenderer {
302
313
  async sendOperationalNoticeAsText(text) {
303
314
  if (!this.opts.operationalNoticesAsText || this.opts.envelope.chatmode === 'proactive' || !text)
304
315
  return false;
316
+ if (!this.hasSendableText(text, 'operational-notice'))
317
+ return false;
305
318
  // 保持与流式正文的到达顺序:先发出已缓冲的正文,再投影运行状态。
306
319
  await this.flushText();
307
320
  const payload = { kind: 'result.text', text, isFinal: false };
@@ -315,6 +328,17 @@ export class IMRenderer {
315
328
  synthCallId() {
316
329
  return `synth-${this.opts.envelope.taskId}-${++this.syntheticCallSeq}`;
317
330
  }
331
+ /** Reject blank bodies before sending, without trimming meaningful formatting. */
332
+ hasSendableText(text, source) {
333
+ if (text.trim())
334
+ return true;
335
+ // An unused buffer is normal. A nonempty whitespace body is diagnostic evidence.
336
+ if (text.length > 0) {
337
+ const { envelope, agentAid } = this.opts;
338
+ 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))}`);
339
+ }
340
+ return false;
341
+ }
318
342
  /** Convert a structured failed receipt into the same error shape as a rejected sender. */
319
343
  receiptError(receipt) {
320
344
  return Object.assign(new Error(receipt.error), {
@@ -331,6 +355,18 @@ export class IMRenderer {
331
355
  : undefined;
332
356
  return value.code === 'OUTBOX_FULL' || cause?.code === 'OUTBOX_FULL';
333
357
  }
358
+ isPermanentActivityError(error) {
359
+ const value = error && typeof error === 'object' ? error : {};
360
+ const cause = value.cause && typeof value.cause === 'object'
361
+ ? value.cause
362
+ : undefined;
363
+ const code = String(value.code ?? cause?.code ?? '');
364
+ return value.permanent === true
365
+ || value.status === 'failed'
366
+ || code === 'AUN_SEND_FAILED'
367
+ || code === '-1'
368
+ || /payload is too large/i.test(String(value.message ?? value.error ?? ''));
369
+ }
334
370
  /**
335
371
  * Serialize durable sends while keeping the chain usable after a rejected
336
372
  * operation. Activity queue saturation is best-effort: it is logged, but
@@ -344,9 +380,19 @@ export class IMRenderer {
344
380
  }
345
381
  });
346
382
  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;
383
+ if (payload.kind === 'activity.batch') {
384
+ const detail = error instanceof Error ? error.message : String(error);
385
+ if (this.isOutboxFull(error)) {
386
+ logger.warn(`[IMRenderer] activity outbox full; continuing without activity batch: ${detail}`);
387
+ return;
388
+ }
389
+ if (this.isPermanentActivityError(error)) {
390
+ // Activity is best-effort. A permanent structured-send failure (for
391
+ // example an AUN payload-size rejection) must not requeue the same
392
+ // item indefinitely or prevent the final result.text from flushing.
393
+ logger.warn(`[IMRenderer] activity delivery failed; dropping activity batch and continuing: ${detail}`);
394
+ return;
395
+ }
350
396
  }
351
397
  throw error;
352
398
  });
@@ -432,22 +478,25 @@ export class IMRenderer {
432
478
  // 文件标记过滤:marker 任何时候都不该出现在用户文本里,必须在 *每次* flush
433
479
  // (含非最终的定时 flush)执行。否则文本块若滞留 buffer 后被定时器触发,会走
434
480
  // 下方非-final 的 result.text 路径,把 [SEND_FILE:...] 原样发给用户。
435
- // 非最终 flush 不做 trim,避免 trim 掉 Markdown 块级换行。
481
+ // 先保留空白,以便空正文告警仍能记录原长度。
436
482
  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);
483
+ this.textBuffer = this.textBuffer.replace(this.opts.fileMarkerPattern, '');
442
484
  for (const item of this.itemsQueue) {
443
485
  if (item.kind === 'text')
444
486
  item.text = item.text.replace(this.opts.fileMarkerPattern, '');
445
487
  }
446
488
  }
489
+ if (!this.hasSendableText(this.textBuffer, isFinal ? 'final' : 'flush')) {
490
+ this.textBuffer = '';
491
+ }
492
+ else if (isFinal && this.opts.fileMarkerPattern) {
493
+ // 保持既有最终文件标记过滤语义;中间文本的格式原样保留。
494
+ this.textBuffer = this.textBuffer.trim();
495
+ }
447
496
  // 清掉空 text items
448
497
  const items = this.itemsQueue.filter(it => {
449
498
  if (it.kind === 'text')
450
- return it.text.length > 0;
499
+ return it.text.trim().length > 0;
451
500
  return true;
452
501
  });
453
502
  this.itemsQueue = [];
@@ -544,6 +593,8 @@ export class IMRenderer {
544
593
  if (!item)
545
594
  return;
546
595
  if (item.kind === 'text') {
596
+ if (!this.hasSendableText(item.text, 'proactive'))
597
+ return;
547
598
  this.hasEmittedText = true;
548
599
  this.allText += item.text;
549
600
  }
@@ -633,7 +684,7 @@ export class IMRenderer {
633
684
  if (isContextTooLongText(event.error || ''))
634
685
  return null;
635
686
  const text = getErrorMessage(new Error(event.error || '任务执行失败'), undefined, false);
636
- return { kind: 'notice', text, severity: 'warn' };
687
+ return { kind: 'notice', text: tagErrorTextWithEvolcoreVersion(text), severity: 'warn' };
637
688
  }
638
689
  case 'complete': {
639
690
  if (event.queryFinal === false)
@@ -650,7 +701,7 @@ export class IMRenderer {
650
701
  const text = getErrorMessage(new Error(errText), event.terminalReason, false);
651
702
  const item = {
652
703
  kind: 'summary',
653
- text,
704
+ text: tagErrorTextWithEvolcoreVersion(text),
654
705
  is_error: true,
655
706
  };
656
707
  if (event.subtype !== undefined)
@@ -25,7 +25,7 @@ import { createRootCausation, deriveCausation, normalizeCausation } from '../cau
25
25
  import { recordCausationSpan } from '../causation/audit.js';
26
26
  import { renderActionAsText } from '../interaction-router.js';
27
27
  import { planApprovalRoute } from '../permission/approval-gateway.js';
28
- import { parseFullAccessCommand } from '../command/slash-gate.js';
28
+ import { guardGroupSlashCommand, parseFullAccessCommand } from '../command/slash-gate.js';
29
29
  import { isFullAccessEnabled } from '../../config-store.js';
30
30
  import { auditFullAccessEvent } from '../auth/authorization-audit.js';
31
31
  import { consumeAdmissionReplyBudget, clearAdmissionReplyBudgets, evaluateInboundAdmissionPreflight, evaluateOrdinaryInboundAdmission, } from './message-admission.js';
@@ -500,9 +500,31 @@ export class MessageBridge {
500
500
  async sendAdmissionControlReply(context, msg, sendReply, text) {
501
501
  if (!text || !consumeAdmissionReplyBudget(context))
502
502
  return false;
503
- await sendReply(msg.channelId, text, msg.replyContext);
503
+ if (context.chatType === 'group' && /^\s*\/(?:bind|request)(?:\s|$)/i.test(msg.content)) {
504
+ await this.sendSlashControlReply(context.channelKey, msg, sendReply, { kind: 'command.error', text });
505
+ }
506
+ else {
507
+ await sendReply(msg.channelId, text, msg.replyContext);
508
+ }
504
509
  return true;
505
510
  }
511
+ /** Controls handled before the normal slash handler still need structured group feedback. */
512
+ async sendSlashControlReply(channel, msg, sendReply, payload, adapter = this.processor.getChannelInfo?.(channel)?.adapter) {
513
+ if (msg.chatType === 'group' && adapter?.send) {
514
+ await adapter.send(buildEnvelope({
515
+ taskId: `cmd-${randomBytes(5).toString('hex')}`,
516
+ channel,
517
+ channelId: msg.channelId,
518
+ chatmode: 'interactive',
519
+ replyContext: {
520
+ ...msg.replyContext,
521
+ ...(msg.threadId ? { threadId: msg.threadId } : {}),
522
+ },
523
+ }), payload);
524
+ return;
525
+ }
526
+ await sendReply(msg.channelId, payload.text, msg.replyContext);
527
+ }
506
528
  admissionDeniedNotice(context, channelType) {
507
529
  if (context.contactStatus === 'pending') {
508
530
  return '联系人申请审核中;可再次发送 /request 更新说明。';
@@ -801,6 +823,16 @@ export class MessageBridge {
801
823
  : { isMenu: false };
802
824
  let menuResponseWarning;
803
825
  if (menuControl.isMenu) {
826
+ // Menu is an AUN private control plane. Group messages must never
827
+ // execute Menu commands or participate in token issuance/rotation.
828
+ if (msg.chatType === 'group') {
829
+ this.logMenuDiagnostic('unsupported-chat-type', channelName, msg, menuControl, 'MENU_PRIVATE_ONLY');
830
+ logMenuRequestCompleted(menuControl.request, undefined, this.menuFlowContext(channelName, msg), {
831
+ delivery: 'dropped',
832
+ reason: 'unsupported-chat-type',
833
+ });
834
+ return;
835
+ }
804
836
  // AUN captures this mark at its adapter boundary. Legacy/test
805
837
  // adapters fall back to the bridge entry mark captured above.
806
838
  Object.assign(msg, menuTiming);
@@ -837,7 +869,10 @@ export class MessageBridge {
837
869
  const tokenSupplied = Object.prototype.hasOwnProperty.call(menuControl.request, 'menu_token');
838
870
  const tokenValid = (menuTokenRequired || tokenSupplied)
839
871
  && !!tokenOwnerAid
840
- && this.menuTokenStore.use(tokenOwnerAid, msg.peerId, menuControl.request.menu_token);
872
+ && this.menuTokenStore.use(tokenOwnerAid, msg.peerId, menuControl.request.menu_token, {
873
+ deviceId: msg.peerDeviceId,
874
+ slotId: msg.peerSlotId,
875
+ });
841
876
  if (menuTokenRequired && !tokenValid) {
842
877
  this.logMenuDiagnostic('token-required', channelName, msg, menuControl, 'MENU_TOKEN_REQUIRED');
843
878
  await this.sendMenuResponse(adapter, channelName, msg, menuFailure(header, {
@@ -915,6 +950,11 @@ export class MessageBridge {
915
950
  // internal per-call authorization carried into ResponseEngine.
916
951
  const fullAccessCommand = parseFullAccessCommand(content);
917
952
  if (fullAccessCommand.matched) {
953
+ const groupGuard = guardGroupSlashCommand(content, chatType);
954
+ if (groupGuard?.kind === 'command.error') {
955
+ await this.sendSlashControlReply(channelName, msg, sendReply, groupGuard, adapter);
956
+ return;
957
+ }
918
958
  const reject = async (text) => {
919
959
  logger.warn(`[MessageBridge] fullaccess command rejected: channel=${channelName} `
920
960
  + `actor=${authSubject.principalId ?? authSubject.actorId ?? '<none>'} reason=${text}`);
@@ -1002,6 +1042,13 @@ export class MessageBridge {
1002
1042
  // 2. 命令快速路径(去除引用前缀后检查,兼容话题中引用上文的情况)
1003
1043
  const contentForCmd = content.replace(/^(>[^\n]*\n)+\n?/, '').trim();
1004
1044
  const cmdContent = contentForCmd || content;
1045
+ if (msg.source !== 'handoff' && !executionPermissionOverride) {
1046
+ const groupGuard = guardGroupSlashCommand(cmdContent, chatType);
1047
+ if (groupGuard?.kind === 'command.error') {
1048
+ await this.sendSlashControlReply(channelName, msg, sendReply, groupGuard, adapter);
1049
+ return;
1050
+ }
1051
+ }
1005
1052
  const isCmd = msg.source !== 'handoff'
1006
1053
  // Once /fa has been accepted, its remainder is always model input.
1007
1054
  // A prompt such as `/fa /status` must not be re-routed as the
@@ -1411,7 +1458,10 @@ export class MessageBridge {
1411
1458
  }
1412
1459
  }
1413
1460
  const scope = msg.chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
1414
- const dedupKey = [msg.selfAID || channel, channelType, msg.chatType || 'private', scope, msg.peerId, msg.threadId || '', parsed.id].join('\u001f');
1461
+ // Separate installations can generate identical request IDs. Token
1462
+ // issuance and replay must remain scoped to the authenticated requester.
1463
+ const dedupKey = [msg.selfAID || channel, channelType, msg.chatType || 'private', scope, msg.peerId,
1464
+ msg.peerDeviceId || '', msg.peerSlotId || '', msg.threadId || '', parsed.id].join('\u001f');
1415
1465
  let afterResponse;
1416
1466
  const deduped = await this.menuDeduper.execute(dedupKey, menuPayloadFingerprint(parsed.raw), () => isTokenRequest
1417
1467
  ? this.issueMenuTokenResponse(header, channel, msg)
@@ -1452,7 +1502,9 @@ export class MessageBridge {
1452
1502
  case 'menu.list':
1453
1503
  if (args?.view === 'catalog') {
1454
1504
  validateMenuCatalogArgs(args);
1455
- const data = await this.cmdHandler.getMenuCatalog(channel, msg.channelId, authSubject, args.context, msg.isControlChannel ? 'control' : 'agent');
1505
+ const data = await this.cmdHandler.getMenuCatalog(channel, msg.channelId, authSubject, args.context, msg.isControlChannel ? 'control' : 'agent', !msg.isControlChannel || (msg.encrypted === true
1506
+ && msg.protectedHeaders?.auth_method !== 'remote-access-token'
1507
+ && msg.protectedHeaders?.auth_method !== 'ecweb-token'));
1456
1508
  return menuSuccess(header, data);
1457
1509
  }
1458
1510
  if (args?.view !== undefined) {
@@ -1615,6 +1667,9 @@ export class MessageBridge {
1615
1667
  clientName: this.menuTokenHeaderValue(headers.client_name ?? headers.app_name),
1616
1668
  clientVersion: this.menuTokenHeaderValue(headers.client_version ?? headers.app_version),
1617
1669
  evolVersion: this.menuTokenHeaderValue(headers.evol_version),
1670
+ }, {
1671
+ deviceId: msg.peerDeviceId,
1672
+ slotId: msg.peerSlotId,
1618
1673
  });
1619
1674
  if (!issued) {
1620
1675
  return menuFailure(header, { code: 'MENU_TOKEN_REJECTED', message: '授权申请未通过' });
@@ -1732,20 +1787,25 @@ export class MessageBridge {
1732
1787
  logger.info(`[${channel}] ${channelId}: ${content}${source === 'card-trigger' ? ' [card]' : ''}`);
1733
1788
  const adapter = this.processor.getChannelInfo?.(channel)?.adapter;
1734
1789
  const cmdResult = await this.cmdHandler.handle(content, channel, channelId, async (_cid, text, opts, updatePayload) => {
1735
- if (updatePayload && adapter?.send) {
1790
+ const payload = updatePayload ?? (chatType === 'group' ? { kind: 'command.result', text } : undefined);
1791
+ if (payload && adapter?.send) {
1736
1792
  const envelope = buildEnvelope({
1737
1793
  taskId: `cmd-update-${randomBytes(5).toString('hex')}`,
1738
1794
  channel,
1739
1795
  channelId,
1740
1796
  agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>',
1741
1797
  chatmode: 'interactive',
1742
- replyContext: opts ?? replyContext,
1798
+ replyContext: {
1799
+ ...(opts ?? {}),
1800
+ ...(replyContext ?? {}),
1801
+ metadata: { ...opts?.metadata, ...replyContext?.metadata },
1802
+ },
1743
1803
  });
1744
- await adapter.send(envelope, updatePayload);
1804
+ await adapter.send(envelope, payload);
1745
1805
  return;
1746
1806
  }
1747
1807
  await sendReply(text, opts, updatePayload);
1748
- }, userId, threadId, chatType, source, messageId, selfAID, authSubject?.identity, authSubject, replyContext);
1808
+ }, userId, threadId, chatType, source, messageId, selfAID, authSubject?.identity, authSubject, replyContext, true);
1749
1809
  logger.debug(`[MessageBridge] handleCommand: result type=${typeof cmdResult}`);
1750
1810
  if (cmdResult === undefined)
1751
1811
  return false;
@@ -1275,6 +1275,8 @@ export class MessageQueue {
1275
1275
  return false;
1276
1276
  if (first.projectPath !== next.projectPath || first.agentName !== next.agentName)
1277
1277
  return false;
1278
+ // Results from the same parent handoff are still mergeable. Their
1279
+ // resultRefs are retained and consumed individually after one turn.
1278
1280
  const a = first.message.handoffDelivery?.originAuthorization;
1279
1281
  const b = next.message.handoffDelivery?.originAuthorization;
1280
1282
  if (!a || !b)
@@ -1323,6 +1325,10 @@ export class MessageQueue {
1323
1325
  let hasMessagesAfterRestartResume = false;
1324
1326
  const originHandoffIds = [];
1325
1327
  const seenOriginHandoffIds = new Set();
1328
+ const originResultIds = [];
1329
+ const seenOriginResultIds = new Set();
1330
+ const originResultRefs = [];
1331
+ const seenOriginResultRefs = new Set();
1326
1332
  const allOriginHandoffs = items.every(item => this.isOriginHandoff(item.message));
1327
1333
  for (const item of items) {
1328
1334
  const m = item.message;
@@ -1347,6 +1353,24 @@ export class MessageQueue {
1347
1353
  originHandoffIds.push(id);
1348
1354
  }
1349
1355
  }
1356
+ const refs = m.handoffDelivery.resultRefs?.length
1357
+ ? m.handoffDelivery.resultRefs
1358
+ : (m.handoffDelivery.resultIds?.length
1359
+ ? m.handoffDelivery.resultIds.map(resultId => ({ handoffId: m.handoffDelivery.handoffId, resultId }))
1360
+ : (m.handoffDelivery.resultId
1361
+ ? [{ handoffId: m.handoffDelivery.handoffId, resultId: m.handoffDelivery.resultId }]
1362
+ : []));
1363
+ for (const ref of refs) {
1364
+ const refKey = `${ref.handoffId}\u0000${ref.resultId}`;
1365
+ if (!seenOriginResultRefs.has(refKey)) {
1366
+ seenOriginResultRefs.add(refKey);
1367
+ originResultRefs.push(ref);
1368
+ }
1369
+ if (!seenOriginResultIds.has(ref.resultId)) {
1370
+ seenOriginResultIds.add(ref.resultId);
1371
+ originResultIds.push(ref.resultId);
1372
+ }
1373
+ }
1350
1374
  }
1351
1375
  for (const id of [...(m.sourceMessageIds ?? []), ...(m.messageId ? [m.messageId] : [])]) {
1352
1376
  if (!seenSourceMessageIds.has(id)) {
@@ -1418,6 +1442,8 @@ export class MessageQueue {
1418
1442
  ...last.message.handoffDelivery,
1419
1443
  handoffId: originHandoffIds[0],
1420
1444
  handoffIds: originHandoffIds,
1445
+ ...(originResultIds.length > 0 ? { resultIds: originResultIds } : {}),
1446
+ ...(originResultRefs.length > 0 ? { resultRefs: originResultRefs } : {}),
1421
1447
  }
1422
1448
  : last.message.handoffDelivery,
1423
1449
  };
@@ -101,6 +101,7 @@ export function deliveryTargetFromSession(session) {
101
101
  export function replyContextFromSession(session, context) {
102
102
  return {
103
103
  ...(session.metadata?.replyContext ?? {}),
104
+ ...(session.threadId ? { threadId: session.threadId } : {}),
104
105
  ...(context ?? {}),
105
106
  // Session chatType/groupId is the persisted routing authority. The nested
106
107
  // reply context can outlive a route correction, so only an explicit
@@ -2,7 +2,7 @@ import path from 'path';
2
2
  import fs from 'fs';
3
3
  import os from 'os';
4
4
  import crypto from 'crypto';
5
- import { BaseagentRunnerUnavailableError, hasCompact, autoCompactWindowForContextWindow, autoCompactWindowForModel, isClaudeContextUsageModel, numericToken } from '../../agents/runner-types.js';
5
+ import { BaseagentRunnerUnavailableError, hasCompact, autoCompactWindowForModel, isClaudeContextUsageModel, numericToken } from '../../agents/runner-types.js';
6
6
  import { buildTaskRuntimeEnv, ensureRuntimeLockDir, ensureSessionRuntimeDir } from '../../cli/task-context.js';
7
7
  import { IMRenderer } from './im-renderer.js';
8
8
  import { createSendFileMarkerPattern } from './file-markers.js';
@@ -11,7 +11,8 @@ import { StreamIdleMonitor } from './stream-idle-monitor.js';
11
11
  import { logger } from '../../utils/logger.js';
12
12
  import { isHostChinese } from '../../utils/locale.js';
13
13
  import { getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError, isAbortTerminalReason, prefixErrorType, isRetryableError, isContextTooLongText } from '../../utils/error-utils.js';
14
- import { isExactEvolcoreSendCommandForSession } from '../permission/ec-command-parser.js';
14
+ import { readEvolcoreRuntimeFlavor, tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
15
+ import { describeProactiveSendCommandFailure, isExactEvolcoreSendCommandForSession } from '../permission/ec-command-parser.js';
15
16
  import { summarizeToolInput } from '../../utils/tool-summary.js';
16
17
  import { agentDir as resolveAgentDir, agentMdPath, getPackageRoot, resolveRoot, resolvePaths } from '../../paths.js';
17
18
  import { renderKitSections } from '../../eck/kit-renderer.js';
@@ -485,9 +486,6 @@ export async function resolveGroupRulesForTurn(opts, sync = syncGroupRulesContex
485
486
  }
486
487
  return sync({ selfAid: opts.selfAid, groupId: opts.groupId, channel: opts.channel });
487
488
  }
488
- function autoCompactTokensFromMaxTokens(maxTokens) {
489
- return autoCompactWindowForContextWindow(maxTokens);
490
- }
491
489
  /**
492
490
  * 构造 OutboundEnvelope —— 出站三件套的信封部分。
493
491
  *
@@ -915,6 +913,20 @@ export class ResponseEngine {
915
913
  causation: runtime?.causation,
916
914
  });
917
915
  }
916
+ async recoverAuthorizedHandoffResult(params, selfAid) {
917
+ if (!this.handoffRuntime) {
918
+ return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff runtime not configured' };
919
+ }
920
+ const runtime = this.activeTaskRuntimeContexts.get(params.sessionId);
921
+ return this.handoffRuntime.recoverReturnHandoff({
922
+ selfAid,
923
+ recoverySessionId: params.sessionId,
924
+ handoffId: params.handoffId,
925
+ content: params.content,
926
+ actorId: params.actorId,
927
+ causation: runtime?.causation,
928
+ });
929
+ }
918
930
  agentRegistry;
919
931
  setAgentRegistry(registry) {
920
932
  this.agentRegistry = registry;
@@ -2132,6 +2144,7 @@ export class ResponseEngine {
2132
2144
  modeConfig: resolvedMode.context.modeConfig,
2133
2145
  state: modeState,
2134
2146
  isSendCommand: (toolName, toolInput) => isExactEvolcoreSendCommandForSession(toolName, toolInput, session.channelId, chatType, selfAid),
2147
+ describeSendCommandFailure: describeProactiveSendCommandFailure,
2135
2148
  logger,
2136
2149
  } : null;
2137
2150
  if (resolvedMode?.mode.beforeProcess && modeProcessCtx) {
@@ -2764,6 +2777,7 @@ export class ResponseEngine {
2764
2777
  let previousFallbackState;
2765
2778
  let agentModel;
2766
2779
  let v2HandoffIds = [];
2780
+ let v2HandoffResultRefs = [];
2767
2781
  let v2HandoffDirection;
2768
2782
  let handoffPromptRendered = false;
2769
2783
  let runtimeEnv;
@@ -3098,7 +3112,7 @@ export class ResponseEngine {
3098
3112
  KITS_FRAGMENTS: path.join(pkgRoot, 'kits', 'templates', 'system-fragments'),
3099
3113
  KITS_MESSAGE_FRAGMENTS: path.join(pkgRoot, 'kits', 'templates', 'message-fragments'),
3100
3114
  // evolcore 运行模式:dev=源码仓库 | install=全局安装包
3101
- evolcoreMode: fs.existsSync(path.join(pkgRoot, 'src', 'index.ts')) ? 'dev' : 'install',
3115
+ evolcoreMode: readEvolcoreRuntimeFlavor(),
3102
3116
  // 路径变量(用于 manifest 路径展开,resolvePath 用 ctx.vars 取真值)
3103
3117
  PERSONAL_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid, 'personal') : undefined,
3104
3118
  RELATIONS_DIR: selfAid ? path.join(resolveRoot(), 'agents', selfAid, 'relations') : undefined,
@@ -3241,6 +3255,7 @@ export class ResponseEngine {
3241
3255
  v2HandoffIds = Array.from(new Set(items.flatMap(item => (item.handoff?.handoffIds?.length
3242
3256
  ? item.handoff.handoffIds
3243
3257
  : (item.handoff?.handoffId ? [item.handoff.handoffId] : [])))));
3258
+ v2HandoffResultRefs = items.flatMap(item => item.handoff?.resultRefs ?? []);
3244
3259
  v2HandoffDirection = message.handoffDelivery.direction;
3245
3260
  return items;
3246
3261
  }
@@ -3314,11 +3329,18 @@ export class ResponseEngine {
3314
3329
  return;
3315
3330
  }
3316
3331
  if (v2HandoffIds.length > 0 && handoffPromptRendered && this.handoffRuntime && selfAid) {
3317
- for (const handoffId of v2HandoffIds) {
3318
- if (v2HandoffDirection === 'origin')
3319
- this.handoffRuntime.completeOriginContext(selfAid, handoffId);
3320
- else if (v2HandoffDirection === 'target')
3321
- this.handoffRuntime.completeTargetContext(selfAid, handoffId);
3332
+ if (v2HandoffDirection === 'origin' && v2HandoffResultRefs.length > 0) {
3333
+ for (const ref of v2HandoffResultRefs) {
3334
+ this.handoffRuntime.completeOriginContext(selfAid, ref.handoffId, ref.resultId);
3335
+ }
3336
+ }
3337
+ else {
3338
+ for (const handoffId of v2HandoffIds) {
3339
+ if (v2HandoffDirection === 'origin')
3340
+ this.handoffRuntime.completeOriginContext(selfAid, handoffId);
3341
+ else if (v2HandoffDirection === 'target')
3342
+ this.handoffRuntime.completeTargetContext(selfAid, handoffId);
3343
+ }
3322
3344
  }
3323
3345
  }
3324
3346
  // The runner may already own a stronger per-session directory (Codex
@@ -4452,7 +4474,9 @@ export class ResponseEngine {
4452
4474
  kind: 'activity.batch',
4453
4475
  items: [{
4454
4476
  kind: 'notice',
4455
- text: userMessage,
4477
+ // activity.batch 载荷混合正常内容与错误,顶层出站包裹不整体打标;
4478
+ // 这里的 notice 是 proactive 下用户能看到的错误副本,单独打标。
4479
+ text: tagErrorTextWithEvolcoreVersion(userMessage),
4456
4480
  severity: 'warn',
4457
4481
  subtype: 'task-error',
4458
4482
  }],
@@ -4567,12 +4591,12 @@ export class ResponseEngine {
4567
4591
  return undefined;
4568
4592
  const shouldClose = !writerDb;
4569
4593
  try {
4570
- const row = db.prepare(`SELECT model, input_tokens, cache_creation_tokens, cache_read_tokens,
4571
- context_tokens, max_tokens, auto_compact_tokens
4572
- FROM model_calls
4573
- WHERE session_id = ?
4574
- AND agent_session_id = ?
4575
- ORDER BY ts DESC, call_index DESC
4594
+ const row = db.prepare(`SELECT model, input_tokens, cache_creation_tokens, cache_read_tokens,
4595
+ context_tokens
4596
+ FROM model_calls
4597
+ WHERE session_id = ?
4598
+ AND agent_session_id = ?
4599
+ ORDER BY ts DESC, call_index DESC
4576
4600
  LIMIT 1`).get(sessionId, agentSessionId);
4577
4601
  if (!row)
4578
4602
  return undefined;
@@ -4590,16 +4614,11 @@ export class ResponseEngine {
4590
4614
  }
4591
4615
  if (totalTokens <= 0)
4592
4616
  return undefined;
4593
- const recordedAutoCompactTokens = row.auto_compact_tokens ?? undefined;
4594
- const inferredAutoCompactTokens = autoCompactTokensFromMaxTokens(row.max_tokens ?? undefined);
4595
- const candidateAutoCompactTokens = recordedAutoCompactTokens && recordedAutoCompactTokens > 0
4596
- ? recordedAutoCompactTokens
4597
- : inferredAutoCompactTokens ?? autoCompactWindowForModel(model);
4598
- // Historic rows may contain the old fixed 350k threshold. Never let a
4599
- // persisted value exceed the context window that accompanied it.
4600
- const autoCompactTokens = inferredAutoCompactTokens
4601
- ? Math.min(candidateAutoCompactTokens, inferredAutoCompactTokens)
4602
- : candidateAutoCompactTokens;
4617
+ // EvolCore 的自动压缩阈值固定为 650k,与模型上下文窗口无关:
4618
+ // 大窗口模型(1M)到 650k 由 EvolCore 压缩;小窗口模型(200k/128k)
4619
+ // 到不了该阈值,交给 base agent 自身的压缩机制处理。
4620
+ // 历史行里可能存着旧的 350k 阈值,这里一律忽略,只认当前常量。
4621
+ const autoCompactTokens = autoCompactWindowForModel(model);
4603
4622
  return { totalTokens, autoCompactTokens };
4604
4623
  }
4605
4624
  finally {