evolcore 0.0.26 → 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 (169) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/claude-runner.js +123 -22
  5. package/dist/agents/codex/app-server-client.js +63 -26
  6. package/dist/agents/codex-runner.js +316 -411
  7. package/dist/agents/ecagent-runner.js +170 -25
  8. package/dist/agents/gemini-runner.js +6 -0
  9. package/dist/agents/runner-types.js +50 -2
  10. package/dist/aun/aid/agentmd.js +73 -31
  11. package/dist/aun/aid/client.js +1 -1
  12. package/dist/aun/aid/control-aid.js +180 -1
  13. package/dist/aun/aid/identity.js +3 -3
  14. package/dist/aun/group/history.js +303 -0
  15. package/dist/aun/group/identity.js +43 -0
  16. package/dist/aun/group/index.js +5 -0
  17. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  18. package/dist/aun/{menu-client.js → menu/client.js} +12 -7
  19. package/dist/aun/menu/index.js +2 -0
  20. package/dist/aun/menu/transport.js +49 -0
  21. package/dist/aun/msg/index.js +2 -1
  22. package/dist/aun/msg/managed-operation.js +84 -18
  23. package/dist/aun/msg/mention-schema.js +51 -0
  24. package/dist/aun/msg/p2p.js +13 -8
  25. package/dist/aun/msg/upload.js +43 -88
  26. package/dist/aun/outbox.js +29 -7
  27. package/dist/aun/rpc/client.js +1 -0
  28. package/dist/aun/storage/client.js +39 -50
  29. package/dist/channels/aun.js +522 -233
  30. package/dist/channels/daemon.js +10 -6
  31. package/dist/cli/agent-command.js +108 -6
  32. package/dist/cli/agent-create.js +515 -2
  33. package/dist/cli/agent.js +109 -8
  34. package/dist/cli/aun-commands.js +191 -42
  35. package/dist/cli/config.js +8 -5
  36. package/dist/cli/contact.js +2 -2
  37. package/dist/cli/ctl-command.js +2 -2
  38. package/dist/cli/daemon-commands.js +35 -23
  39. package/dist/cli/diag.js +2 -2
  40. package/dist/cli/fs-command.js +3 -3
  41. package/dist/cli/handoff-command.js +6 -6
  42. package/dist/cli/index.js +8 -2
  43. package/dist/cli/init.js +34 -1
  44. package/dist/cli/managed-command-guard.js +2 -1
  45. package/dist/cli/menu-client.js +9 -5
  46. package/dist/cli/model.js +3 -3
  47. package/dist/cli/queue-command.js +2 -2
  48. package/dist/cli/response.js +2 -2
  49. package/dist/cli/restart-monitor.js +16 -2
  50. package/dist/cli/task-command.js +59 -0
  51. package/dist/cli/task-context.js +97 -49
  52. package/dist/cli/trigger-command.js +20 -10
  53. package/dist/config/builtin-roles.js +10 -0
  54. package/dist/config/config-field-policy.js +11 -0
  55. package/dist/config/config-manager.js +32 -4
  56. package/dist/config/config-operation-service.js +20 -5
  57. package/dist/config/contact-book-store.js +1 -1
  58. package/dist/config/contact-book.js +1 -1
  59. package/dist/config/contact-operation-service.js +1 -1
  60. package/dist/config/peer-role-resolver.js +1 -1
  61. package/dist/config/resolved-config-op.js +3 -0
  62. package/dist/config-store.js +1 -1
  63. package/dist/core/agent-application-service.js +5 -0
  64. package/dist/core/audit/event-key.js +17 -2
  65. package/dist/core/audit/log-integrity.js +34 -8
  66. package/dist/core/auth/agent-delegation.js +48 -14
  67. package/dist/core/auth/auth-gateway.js +2 -0
  68. package/dist/core/auth/authorization-audit.js +34 -38
  69. package/dist/core/auth/operation-authorizer.js +67 -5
  70. package/dist/core/auth/operation-catalog.js +110 -0
  71. package/dist/core/auth/remote-access-store.js +115 -9
  72. package/dist/core/bootstrap-messages.js +66 -7
  73. package/dist/core/bootstrap-service.js +110 -19
  74. package/dist/core/command/agent-control.js +88 -7
  75. package/dist/core/command/agent-storage-menu.js +443 -0
  76. package/dist/core/command/cli-intent-parser.js +19 -0
  77. package/dist/core/command/command-handler.js +76 -39
  78. package/dist/core/command/data-root-resolver.js +273 -0
  79. package/dist/core/command/group-menu.js +7 -2
  80. package/dist/core/command/menu-catalog.js +34 -4
  81. package/dist/core/command/menu-handler.js +348 -77
  82. package/dist/core/command/menu-protocol.js +40 -6
  83. package/dist/core/command/role-menu.js +2 -2
  84. package/dist/core/command/slash-handler.js +37 -22
  85. package/dist/core/event-catalog.js +4 -0
  86. package/dist/core/evolagent-registry.js +28 -2
  87. package/dist/core/group/invite-service.js +924 -0
  88. package/dist/core/interaction-router.js +22 -8
  89. package/dist/core/message/im-renderer.js +76 -15
  90. package/dist/core/message/message-bridge.js +94 -11
  91. package/dist/core/message/message-log.js +5 -3
  92. package/dist/core/message/message-queue.js +73 -41
  93. package/dist/core/message/response-engine.js +709 -117
  94. package/dist/core/permission/approval-gateway.js +36 -3
  95. package/dist/core/permission/ec-command-parser.js +61 -29
  96. package/dist/core/permission/protected-paths.js +134 -16
  97. package/dist/core/permission/sandbox-runtime.js +72 -3
  98. package/dist/core/permission/tool-error-code.js +15 -0
  99. package/dist/core/permission/tool-policy.js +7 -7
  100. package/dist/core/relation/peer-identity.js +57 -6
  101. package/dist/core/relation/shared-data.js +106 -0
  102. package/dist/core/role/runtime-policy.js +64 -0
  103. package/dist/core/runtime-lock.js +10 -26
  104. package/dist/core/session/runner-task-history.js +170 -0
  105. package/dist/core/session/session-manager.js +34 -6
  106. package/dist/core/session/session-mapper.js +41 -0
  107. package/dist/core/session/session-turn-coordinator.js +37 -7
  108. package/dist/core/session/session-turns.js +48 -7
  109. package/dist/core/task/task-recovery-service.js +176 -0
  110. package/dist/core/task/task-resume-operation.js +32 -0
  111. package/dist/eck/group-rules-sync.js +5 -3
  112. package/dist/eck/kit-renderer.js +1 -0
  113. package/dist/index.js +308 -54
  114. package/dist/ipc.js +141 -15
  115. package/dist/paths.js +17 -4
  116. package/dist/product.js +2 -0
  117. package/dist/trigger/event-source.js +19 -0
  118. package/dist/trigger/feedback.js +233 -40
  119. package/dist/trigger/parser.js +55 -5
  120. package/dist/trigger/patch.js +9 -0
  121. package/dist/trigger/scheduler.js +30 -4
  122. package/dist/trigger/validation.js +20 -3
  123. package/dist/utils/ecweb-utils.js +1 -0
  124. package/dist/utils/error-utils.js +40 -2
  125. package/dist/utils/logger.js +23 -13
  126. package/dist/utils/tool-summary.js +59 -3
  127. package/dist/utils/welcome.js +174 -1
  128. package/ecagent/dist/harness/agent-harness.js +3 -0
  129. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  130. package/kits/docs/INDEX.md +0 -6
  131. package/kits/docs/channels/aun.md +15 -2
  132. package/kits/docs/channels/feishu.md +1 -1
  133. package/kits/docs/context-assembly.md +29 -17
  134. package/kits/docs/evolcore/INDEX.md +8 -3
  135. package/kits/docs/evolcore/agent-create.md +39 -7
  136. package/kits/docs/evolcore/agent.md +33 -1
  137. package/kits/docs/evolcore/aid.md +9 -1
  138. package/kits/docs/evolcore/config.md +40 -1
  139. package/kits/docs/evolcore/group-collaboration.md +1 -1
  140. package/kits/docs/evolcore/group.md +117 -2
  141. package/kits/docs/evolcore/menu-client.md +16 -0
  142. package/kits/docs/evolcore/msg.md +3 -0
  143. package/kits/docs/evolcore/storage.md +3 -0
  144. package/kits/docs/evolcore/task.md +14 -0
  145. package/kits/docs/evolcore/trigger.md +51 -3
  146. package/kits/docs/venues/group.md +5 -0
  147. package/kits/eck_manifest.json +27 -0
  148. package/kits/rules/01-overview.md +11 -7
  149. package/kits/rules/04-relation.md +2 -1
  150. package/kits/rules/05-venue.md +1 -1
  151. package/kits/rules/06-channel.md +6 -4
  152. package/kits/schemas/agent-config.schema.12.json +25 -1
  153. package/kits/schemas/daemon.schema.6.json +2 -2
  154. package/kits/schemas/defaults.schema.5.json +1 -1
  155. package/kits/schemas/relation-config.schema.1.json +1 -1
  156. package/kits/schemas/relation-config.schema.5.json +1 -1
  157. package/kits/schemas/role-config.schema.1.json +14 -0
  158. package/kits/templates/roles/member.json +5 -0
  159. package/kits/templates/roles/visitor.json +5 -0
  160. package/kits/templates/system-fragments/baseagent.md +1 -1
  161. package/kits/templates/system-fragments/bootstrap.md +26 -9
  162. package/kits/templates/system-fragments/channel.md +8 -44
  163. package/kits/templates/system-fragments/commands.md +10 -2
  164. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  165. package/kits/templates/system-fragments/reply.md +56 -0
  166. package/package.json +7 -3
  167. package/dist/aun/group-identity.js +0 -9
  168. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  169. /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,7 @@ export class IMRenderer {
19
20
  hasEmittedText = false;
20
21
  /** 自增 callId 兜底(runner 没提供时用) */
21
22
  syntheticCallSeq = 0;
23
+ currentAssistantUuid;
22
24
  constructor(opts) {
23
25
  this.opts = opts;
24
26
  }
@@ -61,12 +63,12 @@ export class IMRenderer {
61
63
  // interactive 模式由 MessageProcessor 显式调 addText/addToolCall/... 推入 items
62
64
  }
63
65
  /** 强制刷新所有 pending 事件 */
64
- async flush(isFinal) {
66
+ async flush(isFinal, attention) {
65
67
  if (this.opts.envelope.chatmode === 'proactive') {
66
68
  // proactive 是 fire-and-forget,无 pending buffer
67
69
  return;
68
70
  }
69
- return this.flushInternal(isFinal);
71
+ return this.flushInternal(isFinal, attention);
70
72
  }
71
73
  /** 仅 flush activities,保留 textBuffer(用于中间 complete 事件) */
72
74
  async flushActivitiesOnly() {
@@ -122,7 +124,12 @@ export class IMRenderer {
122
124
  this.textBuffer = '';
123
125
  // 清掉已消费的 text items,包括被拦截的纯空白正文。
124
126
  this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
125
- 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
+ };
126
133
  this.sentContent = true;
127
134
  try {
128
135
  await this.sendPayload(payload);
@@ -168,9 +175,14 @@ export class IMRenderer {
168
175
  }
169
176
  // ── 文本/活动注入(替代 StreamFlusher.addText/addActivity)──
170
177
  /** 添加文本片段(流式 text) */
171
- addText(text, outputTokens, turn) {
178
+ async addText(text, outputTokens, turn, assistantUuid) {
172
179
  if (this.opts.envelope.chatmode === 'proactive')
173
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;
174
186
  this.emitProgress('text', outputTokens, turn);
175
187
  if (!text)
176
188
  return;
@@ -195,18 +207,19 @@ export class IMRenderer {
195
207
  this.emitProgress('tool_call', outputTokens, turn, { toolName: name, callId });
196
208
  if (this.suppressActivityItems())
197
209
  return;
210
+ const displayInput = toolInputForDisplay(name, input);
198
211
  this.itemsQueue.push({
199
212
  kind: 'tool_call',
200
213
  call_id: callId || this.synthCallId(),
201
214
  name,
202
- arguments: toolInputForDisplay(name, input),
203
- text: descText,
215
+ arguments: displayInput,
216
+ text: displayInput !== input ? summarizeToolInput(name, displayInput || {}) : descText,
204
217
  });
205
218
  this.messageTimestamps.push(Date.now());
206
219
  this.scheduleFlush();
207
220
  }
208
221
  /** 添加工具结果 */
209
- addToolResult(name, ok, result, error, callId, durationMs, descText) {
222
+ addToolResult(name, ok, result, error, callId, durationMs, descText, classification) {
210
223
  if (this.opts.envelope.chatmode === 'proactive')
211
224
  return;
212
225
  this.emitProgress('tool_result', undefined, undefined, { toolName: name, callId, ok, durationMs });
@@ -219,6 +232,7 @@ export class IMRenderer {
219
232
  ok,
220
233
  ...(result !== undefined && { result }),
221
234
  ...(error !== undefined && { error }),
235
+ ...(!ok && { error_code: classifyAunToolErrorCode(classification ?? {}) }),
222
236
  ...(durationMs !== undefined && { duration_ms: durationMs }),
223
237
  ...(descText !== undefined && { text: descText }),
224
238
  });
@@ -260,6 +274,19 @@ export class IMRenderer {
260
274
  logger.warn(`[IMRenderer] status notice send failed: ${error instanceof Error ? error.message : String(error)}`);
261
275
  }
262
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
+ }
263
290
  /** 兼容旧调用方:发送 warning 级别的任务 notice。 */
264
291
  async sendWarningStatus(message, subtype, details) {
265
292
  return this.sendStatusNotice(message, 'warn', subtype, details);
@@ -317,7 +344,11 @@ export class IMRenderer {
317
344
  return false;
318
345
  // 保持与流式正文的到达顺序:先发出已缓冲的正文,再投影运行状态。
319
346
  await this.flushText();
320
- const payload = { kind: 'result.text', text, isFinal: false };
347
+ const payload = {
348
+ kind: 'result.text',
349
+ text,
350
+ isFinal: false,
351
+ };
321
352
  this.sentContent = true;
322
353
  await this.sendPayload(payload);
323
354
  this.lastFlush = Date.now();
@@ -470,7 +501,7 @@ export class IMRenderer {
470
501
  * 完整 flush:把 itemsQueue 里所有 items 打包成 activity.batch 发送。
471
502
  * 如果 isFinal=true,还会在 batch 之后单独发一条 result.text 作为最终回复。
472
503
  */
473
- async flushInternal(isFinal) {
504
+ async flushInternal(isFinal, attention) {
474
505
  if (this.timer) {
475
506
  clearTimeout(this.timer);
476
507
  this.timer = undefined;
@@ -501,6 +532,7 @@ export class IMRenderer {
501
532
  });
502
533
  this.itemsQueue = [];
503
534
  const finalText = isFinal ? this.textBuffer : '';
535
+ const finalAnchor = this.forkAnchorMetadata();
504
536
  if (isFinal)
505
537
  this.textBuffer = '';
506
538
  // 1. interactive 模式下:不发 text items(由 result.text 统一发送最终文本)
@@ -532,7 +564,12 @@ export class IMRenderer {
532
564
  return;
533
565
  const text = this.textBuffer;
534
566
  this.textBuffer = '';
535
- const payload = { kind: 'result.text', text, isFinal: false };
567
+ const payload = {
568
+ kind: 'result.text',
569
+ text,
570
+ isFinal: false,
571
+ ...this.forkAnchorMetadata(),
572
+ };
536
573
  this.sentContent = true;
537
574
  try {
538
575
  await this.sendPayload(payload);
@@ -546,7 +583,14 @@ export class IMRenderer {
546
583
  }
547
584
  // 2. isFinal=true 时单独发最终回复文本
548
585
  if (isFinal && finalText.length > 0) {
549
- 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;
550
594
  this.sentContent = true;
551
595
  try {
552
596
  await this.sendPayload(payload);
@@ -559,6 +603,15 @@ export class IMRenderer {
559
603
  this.flushCount++;
560
604
  }
561
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
+ }
562
615
  // ── 内部:status.progress 发送 ──
563
616
  emitProgress(activityType, outputTokens, turn, extra) {
564
617
  if (this.suppressActivityItems())
@@ -592,9 +645,16 @@ export class IMRenderer {
592
645
  const item = this.mapEventToItem(event);
593
646
  if (!item)
594
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
+ }
595
657
  if (item.kind === 'text') {
596
- if (!this.hasSendableText(item.text, 'proactive'))
597
- return;
598
658
  this.hasEmittedText = true;
599
659
  this.allText += item.text;
600
660
  }
@@ -622,7 +682,7 @@ export class IMRenderer {
622
682
  return null;
623
683
  return { kind: 'text', text: event.text };
624
684
  case 'tool_use': {
625
- const desc = summarizeToolInput(event.name, event.input || {});
685
+ const desc = summarizeToolInputForDisplay(event.name, event.input || {});
626
686
  const item = {
627
687
  kind: 'tool_call',
628
688
  call_id: event.callId || this.synthCallId(),
@@ -640,6 +700,7 @@ export class IMRenderer {
640
700
  call_id: event.callId || this.synthCallId(),
641
701
  name: event.name,
642
702
  ok: false,
703
+ error_code: classifyAunToolErrorCode(event),
643
704
  error: event.error || (typeof event.result === 'string' ? event.result : '执行失败'),
644
705
  ...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
645
706
  };
@@ -1,9 +1,13 @@
1
+ import { loadAgent } from '../../config-store.js';
2
+ import { initializationBlocksTasks } from '../../cli/agent-create.js';
1
3
  import { createHash, randomBytes } from 'crypto';
4
+ import { agentStorageMenuService, isAgentStorageMenuRequest } from '../command/agent-storage-menu.js';
2
5
  import { logger } from '../../utils/logger.js';
3
6
  import { StreamDebouncer } from './stream-debouncer.js';
4
7
  import { appendMessageLog, appendMessageLogStrict, buildInboundEntry, isTransientProtocolMessage } from './message-log.js';
5
8
  import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from './message-utils.js';
6
9
  import { chatDirPath } from '../session/session-fs-store.js';
10
+ import { readTopicLable } from '../session/session-mapper.js';
7
11
  import { formatChannelKey, tryParseChannelKey } from '../channel-loader.js';
8
12
  import { agentDir, resolvePaths } from '../../paths.js';
9
13
  import { listStaticAgentAdmins, listStaticAgentOwners, resolvePeerRoleDetail, } from '../../config/peer-role-resolver.js';
@@ -27,6 +31,7 @@ import { renderActionAsText } from '../interaction-router.js';
27
31
  import { planApprovalRoute } from '../permission/approval-gateway.js';
28
32
  import { guardGroupSlashCommand, parseFullAccessCommand } from '../command/slash-gate.js';
29
33
  import { isFullAccessEnabled } from '../../config-store.js';
34
+ import { createAgentPolicyAuthorization } from '../role/runtime-policy.js';
30
35
  import { auditFullAccessEvent } from '../auth/authorization-audit.js';
31
36
  import { consumeAdmissionReplyBudget, clearAdmissionReplyBudgets, evaluateInboundAdmissionPreflight, evaluateOrdinaryInboundAdmission, } from './message-admission.js';
32
37
  const CONTACT_REJECTION_NOTICE = '[rejection] Not accepting messages from you.';
@@ -113,6 +118,7 @@ export class MessageBridge {
113
118
  aidStatsCollector;
114
119
  contactBindRuntimeChecker;
115
120
  interactionRouter;
121
+ groupInviteReviewService;
116
122
  constructor(defaultProjectPath, sessionManager, processor, messageQueue, cmdHandler, eventBus, defaultDebounce, processOwnersProvider) {
117
123
  this.defaultProjectPath = defaultProjectPath;
118
124
  this.sessionManager = sessionManager;
@@ -144,6 +150,12 @@ export class MessageBridge {
144
150
  setInteractionRouter(router) {
145
151
  this.interactionRouter = router;
146
152
  }
153
+ setGroupInviteReviewService(service) {
154
+ this.groupInviteReviewService = service;
155
+ }
156
+ registerGroupInviteReviewChannel(binding) {
157
+ this.groupInviteReviewService?.attach(binding);
158
+ }
147
159
  /**
148
160
  * AUN supplies an authenticated sender AID, while the IM-side code proves
149
161
  * possession of the external account. This command only creates that
@@ -483,7 +495,7 @@ export class MessageBridge {
483
495
  ...(msg.replyContext ?? {}),
484
496
  metadata: {
485
497
  ...(msg.replyContext?.metadata ?? {}),
486
- ...evolMenuResponseTransportMetadata(),
498
+ ...evolMenuResponseTransportMetadata(msg.protectedHeaders?.sdk_version),
487
499
  },
488
500
  },
489
501
  }), {
@@ -795,6 +807,20 @@ export class MessageBridge {
795
807
  }
796
808
  return;
797
809
  }
810
+ if (!trustedHandoffEcho && selfAid && actorId && this.groupInviteReviewService) {
811
+ const inviteCommand = await this.groupInviteReviewService.handleCommand({
812
+ selfAid,
813
+ ownerAid: actorId,
814
+ channelKey,
815
+ channelType: resolvedChannelType,
816
+ chatType,
817
+ content,
818
+ });
819
+ if (inviteCommand.handled) {
820
+ await this.sendAdmissionControlReply(admissionPreflight.context, msg, sendReply, inviteCommand.text ?? '群邀请命令已处理。');
821
+ return;
822
+ }
823
+ }
798
824
  if (!trustedHandoffEcho && await this.handleReservedContactControl({
799
825
  context: admissionPreflight.context,
800
826
  msg,
@@ -1027,6 +1053,11 @@ export class MessageBridge {
1027
1053
  ...(chatType === 'private' ? { routeId: msg.channelId } : {}),
1028
1054
  });
1029
1055
  }
1056
+ const initializationConfig = selfAid ? loadAgent(selfAid) : null;
1057
+ if (initializationConfig && initializationBlocksTasks(initializationConfig)) {
1058
+ await sendReply(msg.channelId, 'Agent 初始化尚未完成,请由创建者查询 ec agent show 或恢复初始化。');
1059
+ return;
1060
+ }
1030
1061
  // 1. owner 绑定(按实例名绑定)
1031
1062
  if (adapter && actorId && roleDetail.effectiveRole === 'owner') {
1032
1063
  await this.bootstrapService?.tryStartBootstrap({
@@ -1106,6 +1137,14 @@ export class MessageBridge {
1106
1137
  return sendReply(msg.channelId, text, msg.replyContext);
1107
1138
  }, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext, msg.messageId, msg.selfAID, authSubject))
1108
1139
  return;
1140
+ // Commands retain their normal route. Only authenticated direct model
1141
+ // input can obtain the Agent policy's per-call authorization.
1142
+ if (!executionPermissionOverride && selfAid && owningAgent?.aid === selfAid) {
1143
+ executionPermissionOverride = createAgentPolicyAuthorization({
1144
+ agentAid: selfAid, actor: roleDetail.actor, chatType,
1145
+ source: msg.source, peerType: msg.peerType,
1146
+ });
1147
+ }
1109
1148
  // 3. session 解析(使用 Channel 层填充的 chatType)
1110
1149
  if (!(await this.canCreateThreadSession(channelName, msg, chatType))) {
1111
1150
  // 静默丢弃:绝不向群里注入回复。
@@ -1149,7 +1188,7 @@ export class MessageBridge {
1149
1188
  // has been appended and create another renewed session.
1150
1189
  releaseAdmission = await this.acquireAdmission(admissionRoute);
1151
1190
  const hadMainSession = msg.threadId ? true : this.sessionManager.hasMainSession(channelName, msg.channelId, msg.channelType || effectiveChannelType, msg.selfAID || selfAid);
1152
- let session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity);
1191
+ let session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity, readTopicLable(msg) ?? readTopicLable(msg.replyContext?.metadata));
1153
1192
  if (session.threadId && typeof this.messageQueue.reserveSessionIngress === 'function') {
1154
1193
  releaseSessionIngress = this.messageQueue.reserveSessionIngress(session.id);
1155
1194
  }
@@ -1205,6 +1244,7 @@ export class MessageBridge {
1205
1244
  mentions: msg.mentions, mentionAids: msg.mentionAids, threadId: msg.threadId,
1206
1245
  isMentioned: msg.isMentioned,
1207
1246
  topicName: this.extractTopicName(msg),
1247
+ lable: session.lable,
1208
1248
  replyContext: msg.replyContext,
1209
1249
  source: msg.source,
1210
1250
  ...(executionPermissionOverride ? { executionPermissionOverride } : {}),
@@ -1427,6 +1467,8 @@ export class MessageBridge {
1427
1467
  const menuAuthSubject = {
1428
1468
  ...authSubject,
1429
1469
  requestId: parsed.id,
1470
+ menuEncrypted: msg.encrypted === true,
1471
+ fromControlChannel: authSubject.fromControlChannel || msg.isControlChannel === true,
1430
1472
  ...(activeSession?.id ? { sessionId: activeSession.id } : {}),
1431
1473
  };
1432
1474
  const isTokenRequest = parsed.type === 'menu.token.request';
@@ -1457,6 +1499,17 @@ export class MessageBridge {
1457
1499
  return;
1458
1500
  }
1459
1501
  }
1502
+ // Reauthorize even cached responses: roots expire and static owners can be revoked.
1503
+ const isStorageRequest = isAgentStorageMenuRequest(parsed.request);
1504
+ if (isStorageRequest) {
1505
+ try {
1506
+ agentStorageMenuService.preflight(parsed.request, menuAuthSubject);
1507
+ }
1508
+ catch (error) {
1509
+ await this.sendMenuResponse(adapter, channel, msg, menuFailure(header, normalizeMenuError(error)));
1510
+ return;
1511
+ }
1512
+ }
1460
1513
  const scope = msg.chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
1461
1514
  // Separate installations can generate identical request IDs. Token
1462
1515
  // issuance and replay must remain scoped to the authenticated requester.
@@ -1474,6 +1527,16 @@ export class MessageBridge {
1474
1527
  }));
1475
1528
  return;
1476
1529
  }
1530
+ // A replay may have awaited an in-flight request; check again before release.
1531
+ if (isStorageRequest) {
1532
+ try {
1533
+ agentStorageMenuService.preflight(parsed.request, menuAuthSubject, false);
1534
+ }
1535
+ catch (error) {
1536
+ await this.sendMenuResponse(adapter, channel, msg, menuFailure(header, normalizeMenuError(error)));
1537
+ return;
1538
+ }
1539
+ }
1477
1540
  if (deduped.replayed)
1478
1541
  this.logMenuDiagnostic('request-replay', channel, msg, parsed);
1479
1542
  if (deduped.retry)
@@ -1488,7 +1551,20 @@ export class MessageBridge {
1488
1551
  await deferredEffect();
1489
1552
  }
1490
1553
  catch (error) {
1491
- logger.warn(`[MenuControl] deferred effect failed: request=${parsed.id} error=${error instanceof Error ? error.message : String(error)}`);
1554
+ const detail = error instanceof Error ? error.message : String(error);
1555
+ const code = error && typeof error === 'object' && 'code' in error
1556
+ ? String(error.code ?? 'EXEC_FAILED')
1557
+ : 'EXEC_FAILED';
1558
+ logger.warn(`[MenuControl] deferred effect failed: request=${parsed.id} error=${detail}`);
1559
+ // File fetch is acknowledged before the upload completes. Send a
1560
+ // second response with the same request id so the client can finish
1561
+ // the pending fetch with an actionable error instead of timing out.
1562
+ const failedResponse = menuFailure(header, {
1563
+ code,
1564
+ message: detail.startsWith('文件发送失败') ? detail : `文件发送失败: ${detail}`,
1565
+ });
1566
+ await this.menuDeduper.replaceResult(dedupKey, menuPayloadFingerprint(parsed.raw), deduped.value, failedResponse);
1567
+ await this.sendMenuResponse(adapter, channel, msg, failedResponse);
1492
1568
  }
1493
1569
  }
1494
1570
  }
@@ -1498,6 +1574,13 @@ export class MessageBridge {
1498
1574
  const name = header.name ?? '';
1499
1575
  const cmd = typeof request.cmd === 'string' ? request.cmd : undefined;
1500
1576
  const args = request.args;
1577
+ const transportReplyContext = {
1578
+ ...(msg.replyContext ?? {}),
1579
+ ...(msg.threadId ? { threadId: msg.threadId } : {}),
1580
+ };
1581
+ // Explicit group context can target a different conversation. Its
1582
+ // sessions must not inherit the thread of the source conversation.
1583
+ const replyContextFor = (channelId) => channelId === msg.channelId ? transportReplyContext : undefined;
1501
1584
  switch (request.type) {
1502
1585
  case 'menu.list':
1503
1586
  if (args?.view === 'catalog') {
@@ -1514,7 +1597,7 @@ export class MessageBridge {
1514
1597
  case 'menu.query': {
1515
1598
  if (name === 'menu' && args?.view === 'values') {
1516
1599
  validateMenuValueBatchArgs(args);
1517
- const data = await this.cmdHandler.execMenuValueBatch(channel, msg.channelId, authSubject, args, args.context, msg.isControlChannel ? 'control' : 'agent');
1600
+ const data = await this.cmdHandler.execMenuValueBatch(channel, msg.channelId, authSubject, args, args.context, msg.isControlChannel ? 'control' : 'agent', transportReplyContext);
1518
1601
  return menuSuccess(header, data);
1519
1602
  }
1520
1603
  if (name === 'menu') {
@@ -1527,7 +1610,7 @@ export class MessageBridge {
1527
1610
  channelId: msg.channelId,
1528
1611
  args,
1529
1612
  });
1530
- const result = await this.cmdHandler.execMenuQuery(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject.identity, execution.subject);
1613
+ const result = await this.cmdHandler.execMenuQuery(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, execution.args, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject.identity, execution.subject, 'menu', replyContextFor(execution.channelId));
1531
1614
  if ('error' in result)
1532
1615
  throw result;
1533
1616
  return menuSuccess(header, result.data);
@@ -1544,7 +1627,7 @@ export class MessageBridge {
1544
1627
  if ((name === 'topic' || cmd === '/topic') && args?.mode === 'fork-turns' && msg.threadId && !args?.sourceThreadId) {
1545
1628
  optionArgs = { ...(execution.args ?? {}), sourceThreadId: msg.threadId };
1546
1629
  }
1547
- const data = await this.cmdHandler.getSubMenuItems(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, optionArgs, execution.subject.identity, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject) ?? [];
1630
+ const data = await this.cmdHandler.getSubMenuItems(this.resolveCmd(name, cmd), channel, execution.channelId, execution.subject.actorId, optionArgs, execution.subject.identity, execution.subject.chatType, execution.subject.fromControlChannel, execution.subject, 'menu', replyContextFor(execution.channelId)) ?? [];
1548
1631
  return menuSuccess(header, data);
1549
1632
  }
1550
1633
  case 'menu.update': {
@@ -1555,10 +1638,7 @@ export class MessageBridge {
1555
1638
  channelId: msg.channelId,
1556
1639
  args,
1557
1640
  });
1558
- const result = await this.cmdHandler.execMenuUpdate(this.resolveCmd(name, cmd), request.value, channel, execution.channelId, execution.subject.actorId, execution.subject.identity, execution.subject.fromControlChannel, execution.args, execution.subject, 'menu', {
1559
- ...(msg.replyContext ?? {}),
1560
- ...(msg.threadId ? { threadId: msg.threadId } : {}),
1561
- });
1641
+ const result = await this.cmdHandler.execMenuUpdate(this.resolveCmd(name, cmd), request.value, channel, execution.channelId, execution.subject.actorId, execution.subject.identity, execution.subject.fromControlChannel, execution.args, execution.subject, 'menu', replyContextFor(execution.channelId));
1562
1642
  if ('error' in result)
1563
1643
  throw result;
1564
1644
  return menuSuccess(header, result.data);
@@ -1621,9 +1701,11 @@ export class MessageBridge {
1621
1701
  const agentName = this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>';
1622
1702
  const replyContext = {
1623
1703
  ...(msg.replyContext ?? {}),
1704
+ ...(msg.peerDeviceId ? { toDeviceId: msg.peerDeviceId } : {}),
1624
1705
  metadata: {
1625
1706
  ...(msg.replyContext?.metadata ?? {}),
1626
- ...evolMenuResponseTransportMetadata(),
1707
+ ...evolMenuResponseTransportMetadata(msg.protectedHeaders?.sdk_version),
1708
+ ...(isAgentStorageMenuRequest(request) ? { encrypted: true } : {}),
1627
1709
  },
1628
1710
  };
1629
1711
  if (msg.channelType === 'aun' && !replyContext.delivery) {
@@ -1890,6 +1972,7 @@ export class MessageBridge {
1890
1972
  }
1891
1973
  /** 注销单个渠道的 debouncer(热重载断开渠道时调用) */
1892
1974
  removeChannel(channelName) {
1975
+ this.groupInviteReviewService?.detach(channelName);
1893
1976
  const d = this.debouncers.get(channelName);
1894
1977
  if (d) {
1895
1978
  d.dispose();
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { appendJsonl, chatDirPath } from '../session/session-fs-store.js';
3
+ import { appendJsonl, appendJsonlDurable, chatDirPath } from '../session/session-fs-store.js';
4
4
  import { logger } from '../../utils/logger.js';
5
5
  const TRANSIENT_PROTOCOL_TYPE_PREFIXES = [
6
6
  'contact.request.',
@@ -313,13 +313,13 @@ export function appendMessageLog(chatDir, entry) {
313
313
  }
314
314
  }
315
315
  /** Strict conversation-history append used by handoff reply binding. */
316
- export function appendMessageLogStrict(chatDir, entry) {
316
+ export function appendMessageLogStrict(chatDir, entry, durable = false) {
317
317
  if (isTransientProtocolMessage(entry))
318
318
  return;
319
319
  if (entry.dir === 'in' && hasDuplicate(chatDir, entry.msgId))
320
320
  return;
321
321
  fs.mkdirSync(chatDir, { recursive: true });
322
- appendJsonl(messageLogPath(chatDir), entry);
322
+ (durable ? appendJsonlDurable : appendJsonl)(messageLogPath(chatDir), entry);
323
323
  if (entry.dir === 'in')
324
324
  rememberMessageId(chatDir, entry.msgId);
325
325
  }
@@ -343,6 +343,8 @@ export function buildInboundEntry(opts) {
343
343
  msgType: isCommand ? 'command' : (opts.msgType ?? 'text'),
344
344
  payloadType: opts.payloadType,
345
345
  payloadSummary: opts.payloadSummary,
346
+ groupAttention: opts.groupAttention,
347
+ groupDelivery: opts.groupDelivery,
346
348
  content: opts.content,
347
349
  replyTo: opts.replyTo ?? null,
348
350
  agent: null,