evolcore 0.0.20 → 0.0.22

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 (147) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +58 -9
  3. package/bin/codex-managed-hook.mjs +3 -0
  4. package/bin/install-codex-managed-hooks.mjs +3 -1
  5. package/dist/agents/baseagent.js +10 -6
  6. package/dist/agents/claude-runner.js +393 -108
  7. package/dist/agents/codex-app-server-client.js +41 -7
  8. package/dist/agents/codex-runner.js +1292 -220
  9. package/dist/agents/ecagent-runner.js +171 -61
  10. package/dist/agents/gemini-runner.js +130 -30
  11. package/dist/agents/request-identity.js +25 -0
  12. package/dist/agents/runner-types.js +19 -0
  13. package/dist/aun/aid/agentmd.js +59 -2
  14. package/dist/aun/aid/identity.js +4 -1
  15. package/dist/aun/aid/index.js +1 -1
  16. package/dist/aun/msg/group.js +72 -6
  17. package/dist/aun/msg/history.js +213 -36
  18. package/dist/aun/msg/managed-operation.js +58 -9
  19. package/dist/aun/msg/p2p.js +5 -0
  20. package/dist/aun/outbox.js +189 -80
  21. package/dist/aun/service-proxy.js +43 -25
  22. package/dist/channels/aun.js +618 -123
  23. package/dist/channels/daemon.js +6 -1
  24. package/dist/cli/agent-command.js +4 -3
  25. package/dist/cli/agent.js +66 -56
  26. package/dist/cli/aun-commands.js +177 -42
  27. package/dist/cli/command-log.js +10 -11
  28. package/dist/cli/contact.js +1 -0
  29. package/dist/cli/daemon-commands.js +98 -123
  30. package/dist/cli/init.js +27 -15
  31. package/dist/cli/task-context.js +50 -0
  32. package/dist/cli/trigger-command.js +14 -5
  33. package/dist/cli/watch-logs.js +10 -3
  34. package/dist/config/builtin-roles.js +1 -0
  35. package/dist/config/config-field-policy.js +19 -5
  36. package/dist/config/config-manager.js +167 -22
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/contact-operation-service.js +32 -1
  39. package/dist/config/contact-request-service.js +44 -0
  40. package/dist/config/daemon-services.js +186 -0
  41. package/dist/config/gateway-config.js +20 -9
  42. package/dist/config/role-service.js +54 -3
  43. package/dist/config/schema-migration.js +550 -0
  44. package/dist/config-store.js +151 -9
  45. package/dist/core/agent-application-service.js +279 -0
  46. package/dist/core/audit/log-integrity.js +102 -0
  47. package/dist/core/auth/agent-delegation.js +43 -1
  48. package/dist/core/auth/auth-gateway.js +41 -4
  49. package/dist/core/auth/authorization-audit.js +216 -8
  50. package/dist/core/auth/operation-authorizer.js +41 -1
  51. package/dist/core/auth/operation-catalog.js +9 -1
  52. package/dist/core/bootstrap-messages.js +8 -0
  53. package/dist/core/bootstrap-service.js +99 -27
  54. package/dist/core/causation/aun-association.js +7 -4
  55. package/dist/core/command/agent-control.js +56 -16
  56. package/dist/core/command/command-handler.js +311 -44
  57. package/dist/core/command/connect-menu.js +3 -4
  58. package/dist/core/command/group-menu.js +5 -7
  59. package/dist/core/command/menu-handler.js +288 -80
  60. package/dist/core/command/menu-protocol.js +1 -1
  61. package/dist/core/command/role-menu.js +21 -11
  62. package/dist/core/command/slash-gate.js +85 -18
  63. package/dist/core/command/slash-handler.js +377 -36
  64. package/dist/core/data-migration.js +11 -1
  65. package/dist/core/event-catalog.js +37 -0
  66. package/dist/core/evolagent.js +4 -0
  67. package/dist/core/handoff/dispatcher.js +4 -0
  68. package/dist/core/handoff/runtime.js +33 -3
  69. package/dist/core/handoff/store.js +32 -9
  70. package/dist/core/inference/text-inference.js +7 -15
  71. package/dist/core/message/im-renderer.js +90 -87
  72. package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
  73. package/dist/core/message/message-bridge.js +184 -12
  74. package/dist/core/message/message-log.js +47 -7
  75. package/dist/core/message/message-queue.js +227 -16
  76. package/dist/core/message/message-utils.js +12 -5
  77. package/dist/core/message/response-engine.js +658 -109
  78. package/dist/core/message/send-receipt.js +1 -0
  79. package/dist/core/message/stream-debouncer.js +9 -2
  80. package/dist/core/model/model-catalog.js +23 -15
  81. package/dist/core/model/model-diagnostics.js +28 -10
  82. package/dist/core/permission/approval-gateway.js +180 -6
  83. package/dist/core/permission/ec-command-parser.js +627 -69
  84. package/dist/core/permission/mode.js +18 -3
  85. package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
  86. package/dist/core/permission/readonly-shell-query.js +263 -9
  87. package/dist/core/permission/sandbox-runtime.js +159 -1
  88. package/dist/core/permission/tool-error-code.js +12 -0
  89. package/dist/core/permission/tool-policy.js +618 -23
  90. package/dist/core/session/session-fs-store.js +154 -5
  91. package/dist/core/session/session-manager.js +329 -30
  92. package/dist/core/session/session-renew.js +37 -13
  93. package/dist/core/session/session-turn-coordinator.js +16 -5
  94. package/dist/eck/kit-renderer.js +1 -1
  95. package/dist/index.js +316 -50
  96. package/dist/ipc.js +459 -29
  97. package/dist/paths.js +82 -7
  98. package/dist/response-system/context-builder.js +1 -7
  99. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  100. package/dist/stats/price-resolver.js +4 -0
  101. package/dist/trigger/anomaly-store.js +1 -0
  102. package/dist/trigger/feedback.js +70 -7
  103. package/dist/trigger/history.js +79 -4
  104. package/dist/trigger/legacy-session-history.js +2 -2
  105. package/dist/trigger/parser.js +13 -3
  106. package/dist/trigger/scheduler.js +20 -3
  107. package/dist/trigger/validation.js +6 -1
  108. package/dist/utils/atomic-write.js +27 -0
  109. package/dist/utils/ecweb-utils.js +16 -2
  110. package/dist/utils/error-utils.js +4 -1
  111. package/dist/utils/logger.js +30 -6
  112. package/dist/utils/process-tree-stats.js +24 -4
  113. package/dist/utils/process-tree-worker.js +31 -0
  114. package/dist/utils/project-path.js +1 -2
  115. package/dist/utils/tool-summary.js +59 -0
  116. package/dist/utils/windows-shell-trust.js +201 -0
  117. package/kits/docs/INDEX.md +1 -1
  118. package/kits/docs/evolcore/INDEX.md +3 -3
  119. package/kits/docs/evolcore/agent-create.md +146 -0
  120. package/kits/docs/evolcore/agent.md +6 -0
  121. package/kits/docs/evolcore/contact.md +7 -1
  122. package/kits/docs/evolcore/group-collaboration.md +251 -0
  123. package/kits/docs/evolcore/group-rules.md +1 -19
  124. package/kits/docs/evolcore/group.md +3 -1
  125. package/kits/docs/evolcore/msg.md +16 -0
  126. package/kits/docs/evolcore/trigger.md +6 -3
  127. package/kits/docs/prompt-loading-architecture.md +6 -0
  128. package/kits/eck_message_manifest.json +6 -6
  129. package/kits/schemas/_meta.json +7 -4
  130. package/kits/schemas/agent-config.schema.11.json +13 -0
  131. package/kits/schemas/agent-config.schema.12.json +427 -0
  132. package/kits/schemas/daemon.schema.5.json +0 -1
  133. package/kits/schemas/daemon.schema.6.json +131 -0
  134. package/kits/schemas/defaults.schema.5.json +15 -3
  135. package/kits/schemas/migrations/README.md +3 -1
  136. package/kits/schemas/relation-config.schema.8.json +13 -0
  137. package/kits/schemas/role-config.schema.1.json +1 -2
  138. package/kits/schemas/single-session.schema.3.json +32 -0
  139. package/kits/templates/message-fragments/item.md +1 -1
  140. package/kits/templates/roles/admin.json +1 -0
  141. package/kits/templates/roles/member.json +1 -0
  142. package/kits/templates/roles/visitor.json +1 -0
  143. package/kits/templates/system-fragments/bootstrap.md +2 -1
  144. package/kits/templates/system-fragments/commands.md +2 -2
  145. package/package.json +6 -3
  146. package/skills/eclink/SKILL.md +2 -0
  147. package/dist/config/aun-gateway-config.js +0 -2
@@ -246,6 +246,8 @@ const CATALOG = [
246
246
  description: 'agent 任务开始',
247
247
  fields: [
248
248
  { path: 'sessionId', type: 'string' },
249
+ { path: 'taskId', type: 'string', optional: true },
250
+ { path: 'generation', type: 'number', optional: true },
249
251
  { path: 'agentName', type: 'string', optional: true },
250
252
  { path: 'encrypt', type: 'boolean', optional: true },
251
253
  { path: 'chatmode', type: 'string', optional: true },
@@ -269,6 +271,8 @@ const CATALOG = [
269
271
  description: 'agent 任务完成',
270
272
  fields: [
271
273
  { path: 'sessionId', type: 'string' },
274
+ { path: 'taskId', type: 'string', optional: true },
275
+ { path: 'generation', type: 'number', optional: true },
272
276
  { path: 'channel', type: 'string' },
273
277
  { path: 'channelName', type: 'string', optional: true },
274
278
  { path: 'channelId', type: 'string' },
@@ -287,6 +291,8 @@ const CATALOG = [
287
291
  description: 'agent 任务失败',
288
292
  fields: [
289
293
  { path: 'sessionId', type: 'string' },
294
+ { path: 'taskId', type: 'string', optional: true },
295
+ { path: 'generation', type: 'number', optional: true },
290
296
  { path: 'error', type: 'string' },
291
297
  { path: 'errorType', type: 'string' },
292
298
  { path: 'terminalReason', type: 'string', optional: true },
@@ -300,6 +306,8 @@ const CATALOG = [
300
306
  description: 'agent 任务被中断',
301
307
  fields: [
302
308
  { path: 'sessionId', type: 'string' },
309
+ { path: 'taskId', type: 'string', optional: true },
310
+ { path: 'generation', type: 'number', optional: true },
303
311
  { path: 'reason', type: 'string', optional: true },
304
312
  { path: 'agentName', type: 'string', optional: true },
305
313
  ],
@@ -317,6 +325,7 @@ const CATALOG = [
317
325
  { path: 'eventPhase', type: 'string' },
318
326
  { path: 'callId', type: 'string', optional: true },
319
327
  { path: 'correlationId', type: 'string', optional: true },
328
+ { path: 'agentName', type: 'string', optional: true },
320
329
  { path: 'agentAid', type: 'string', optional: true },
321
330
  { path: 'permissionMode', type: 'string', optional: true },
322
331
  { path: 'decision', type: 'string' },
@@ -338,13 +347,17 @@ const CATALOG = [
338
347
  { path: 'eventPhase', type: 'string' },
339
348
  { path: 'isError', type: 'boolean', optional: true },
340
349
  { path: 'errorCode', type: 'string', optional: true },
350
+ { path: 'classificationMissing', type: 'boolean', optional: true },
341
351
  { path: 'agentName', type: 'string', optional: true },
342
352
  { path: 'callId', type: 'string', optional: true },
343
353
  { path: 'correlationId', type: 'string', optional: true },
354
+ { path: 'requestId', type: 'string', optional: true },
344
355
  { path: 'agentAid', type: 'string', optional: true },
345
356
  { path: 'permissionMode', type: 'string', optional: true },
346
357
  { path: 'decision', type: 'string' },
347
358
  { path: 'decisionSource', type: 'string' },
359
+ { path: 'policyCode', type: 'string', optional: true },
360
+ { path: 'reason', type: 'string', optional: true },
348
361
  { path: 'executed', type: 'boolean' },
349
362
  { path: 'executionState', type: 'string' },
350
363
  { path: 'timestamp', type: 'number', optional: true },
@@ -373,6 +386,8 @@ const CATALOG = [
373
386
  { path: 'approved', type: 'boolean' },
374
387
  { path: 'toolName', type: 'string', optional: true },
375
388
  { path: 'reason', type: 'string', optional: true },
389
+ { path: 'decisionSource', type: 'string', optional: true },
390
+ { path: 'policyCode', type: 'string', optional: true },
376
391
  ],
377
392
  },
378
393
  {
@@ -385,6 +400,8 @@ const CATALOG = [
385
400
  { path: 'requestId', type: 'string' },
386
401
  { path: 'toolName', type: 'string', optional: true },
387
402
  { path: 'reason', type: 'string', optional: true },
403
+ { path: 'decisionSource', type: 'string', optional: true },
404
+ { path: 'policyCode', type: 'string', optional: true },
388
405
  ],
389
406
  },
390
407
  {
@@ -394,6 +411,21 @@ const CATALOG = [
394
411
  description: 'runner 开始压缩',
395
412
  fields: [{ path: 'sessionId', type: 'string' }],
396
413
  },
414
+ {
415
+ type: 'runner:proactive-reminder',
416
+ namespace: 'runner',
417
+ name: 'proactive-reminder',
418
+ description: '主动模式提醒已生成并请求注入模型上下文',
419
+ fields: [
420
+ { path: 'sessionId', type: 'string' },
421
+ { path: 'kind', type: 'string' },
422
+ { path: 'text', type: 'string' },
423
+ { path: 'queueLength', type: 'number', optional: true },
424
+ { path: 'toolCount', type: 'number', optional: true },
425
+ { path: 'injection', type: 'string' },
426
+ { path: 'timestamp', type: 'number', optional: true },
427
+ ],
428
+ },
397
429
  {
398
430
  type: 'runner:compact-complete',
399
431
  namespace: 'runner',
@@ -685,6 +717,11 @@ const CATALOG = [
685
717
  { path: 'runId', type: 'string' },
686
718
  { path: 'originTriggerId', type: 'string' },
687
719
  { path: 'reason', type: 'string' },
720
+ { path: 'reasonCode', type: 'string', optional: true },
721
+ { path: 'decisionSource', type: 'string', optional: true },
722
+ { path: 'executionState', type: 'string', optional: true },
723
+ { path: 'generation', type: 'number', optional: true },
724
+ { path: 'currentGeneration', type: 'number', optional: true },
688
725
  { path: 'targetChannel', type: 'string' },
689
726
  { path: 'targetChannelId', type: 'string' },
690
727
  { path: 'fireTime', type: 'number', optional: true },
@@ -6,6 +6,7 @@ import { ConfigTarget, read as cfgRead, write as cfgWrite, ensureFile as cfgEnsu
6
6
  import { withLifecycleForWrite } from '../config/lifecycle.js';
7
7
  import { isStaticAgentAdmin, isStaticAgentOwner } from '../config/peer-role-resolver.js';
8
8
  import { resolvePrincipal } from '../config/contact-book.js';
9
+ import { isSupportedBaseagent } from '../agents/baseagent.js';
9
10
  /**
10
11
  * EvolAgent —— 一个 self-agent 的运行时表示。
11
12
  *
@@ -168,6 +169,9 @@ export class EvolAgent {
168
169
  // ── Baseagent 字段写入 ────────────────────────────
169
170
  /** 切换当前活跃 baseagent(写入 config.json)。 */
170
171
  setActiveBaseagent(value) {
172
+ if (value !== undefined && !isSupportedBaseagent(value)) {
173
+ throw new Error(`Unsupported baseagent: ${value} (choose: claude/codex/gemini/ecagent)`);
174
+ }
171
175
  this.merged.active_baseagent = value;
172
176
  this.updateAgentConfig(b => {
173
177
  if (value === undefined)
@@ -63,6 +63,10 @@ export class HandoffDispatcher {
63
63
  isAgentPaused(selfAid) {
64
64
  return this.pausedAgents.has(selfAid);
65
65
  }
66
+ /** Whether delivery work for this exact target is currently executing. */
67
+ isRunning(selfAid, targetSessionId) {
68
+ return this.running.has(this.key(selfAid, targetSessionId));
69
+ }
66
70
  async drain(selfAid, targetSessionId) {
67
71
  const key = this.key(selfAid, targetSessionId);
68
72
  if (this.running.has(key)) {
@@ -150,6 +150,16 @@ export class HandoffRuntime {
150
150
  .some(instance => instance.state === 'target_sent'
151
151
  || (instance.state === 'return_pending' && instance.return_policy === 'required'));
152
152
  }
153
+ /**
154
+ * Probe only in-flight delivery/return work. Persisted handoffs that are
155
+ * merely waiting in the store may cross a backend generation; the queue or
156
+ * interaction probe handles the active parts separately.
157
+ */
158
+ hasActiveSessionWork(selfAid, sessionId) {
159
+ return this.dispatcher.isRunning(selfAid, sessionId)
160
+ || this.store.list(selfAid).some(instance => (instance.origin_session_id === sessionId
161
+ || instance.target_session_id === sessionId) && instance.state === 'origin_queued' && this.dispatcher.isRunning(selfAid, instance.target_session_id));
162
+ }
153
163
  async bindReply(input) {
154
164
  const key = `${input.selfAid}\u0000${input.targetSessionId}`;
155
165
  return this.mutexes.forKey(key).runExclusive(async () => {
@@ -256,8 +266,8 @@ export class HandoffRuntime {
256
266
  }
257
267
  }
258
268
  }
259
- buildPromptItem(message) {
260
- return this.buildPromptItems(message)[0] ?? null;
269
+ buildPromptItem(message, fallbackPeerRole) {
270
+ return this.buildPromptItems(message, fallbackPeerRole)[0] ?? null;
261
271
  }
262
272
  isDiscardedDelivery(message) {
263
273
  const delivery = message.handoffDelivery;
@@ -267,7 +277,7 @@ export class HandoffRuntime {
267
277
  const ids = delivery.handoffIds?.length ? delivery.handoffIds : [delivery.handoffId];
268
278
  return ids.length > 0 && ids.every(handoffId => this.store.get(selfAid, handoffId)?.state === 'discarded');
269
279
  }
270
- buildPromptItems(message) {
280
+ buildPromptItems(message, fallbackPeerRole) {
271
281
  const delivery = message.handoffDelivery;
272
282
  const selfAid = message.selfAID;
273
283
  if (!delivery || !selfAid)
@@ -300,8 +310,18 @@ export class HandoffRuntime {
300
310
  peerId: message.peerId,
301
311
  peerName: message.peerName,
302
312
  peerType: message.peerType,
313
+ peerRole: message.batchRole ?? message.resolvedIdentity?.role ?? fallbackPeerRole,
314
+ sameDevice: message.sameDevice,
315
+ sameNetwork: message.sameNetwork,
316
+ sameEgressIp: message.sameEgressIp,
317
+ encrypted: message.encrypted,
303
318
  content: message.content,
304
319
  timestamp: message.timestamp,
320
+ receivedAt: message.receivedAt,
321
+ gatewaySeq: message.gatewaySeq,
322
+ images: message.images,
323
+ mentions: message.mentions,
324
+ mentionAids: message.mentionAids,
305
325
  causation: message.causation,
306
326
  handoff: {
307
327
  kind: 'response_to_origin',
@@ -338,8 +358,18 @@ export class HandoffRuntime {
338
358
  peerId: message.peerId,
339
359
  peerName: message.peerName,
340
360
  peerType: message.peerType,
361
+ peerRole: message.batchRole ?? message.resolvedIdentity?.role ?? fallbackPeerRole,
362
+ sameDevice: message.sameDevice,
363
+ sameNetwork: message.sameNetwork,
364
+ sameEgressIp: message.sameEgressIp,
365
+ encrypted: message.encrypted,
341
366
  content: message.content,
342
367
  timestamp: message.timestamp,
368
+ receivedAt: message.receivedAt,
369
+ gatewaySeq: message.gatewaySeq,
370
+ images: message.images,
371
+ mentions: message.mentions,
372
+ mentionAids: message.mentionAids,
343
373
  causation: message.causation,
344
374
  handoff: {
345
375
  kind: 'request_to_target',
@@ -9,6 +9,7 @@ import { HANDOFF_SCHEMA_VERSION, HANDOFF_QUERY_DEFAULT_LIMIT, HANDOFF_QUERY_MAX_
9
9
  import { normalizeCausation } from '../causation/context.js';
10
10
  const SAFE_ID_RE = /^[A-Za-z0-9._-]+$/;
11
11
  export const HANDOFF_ID_RE = /^h-[A-Za-z0-9._-]+$/;
12
+ const HANDOFF_SNAPSHOT_FILE_RE = /^(h-[A-Za-z0-9._-]+)\.json(?:__?)?$/;
12
13
  const TERMINAL_HANDOFF_STATES = new Set(['failed', 'completed', 'discarded']);
13
14
  function safeSegment(value, field) {
14
15
  if (!value || !SAFE_ID_RE.test(value) || value === '.' || value === '..') {
@@ -55,13 +56,16 @@ export class HandoffStore {
55
56
  ? path.join(this.rootDir, aid, 'data', 'handoff')
56
57
  : path.join(this.rootDir, aid);
57
58
  }
58
- handoffsDir(selfAid) {
59
- return path.join(this.agentDir(selfAid), 'handoffs');
60
- }
61
59
  instancePath(selfAid, handoffId) {
62
60
  if (!HANDOFF_ID_RE.test(handoffId))
63
61
  throw new Error('invalid handoff_id');
64
- return path.join(this.handoffsDir(selfAid), safeSegment(handoffId, 'handoff_id'), 'handoff.json');
62
+ return path.join(this.agentDir(selfAid), `${safeSegment(handoffId, 'handoff_id')}.json`);
63
+ }
64
+ /** Read the pre-flat layout during the transition to the flat store. */
65
+ legacyInstancePath(selfAid, handoffId) {
66
+ if (!HANDOFF_ID_RE.test(handoffId))
67
+ throw new Error('invalid handoff_id');
68
+ return path.join(this.agentDir(selfAid), 'handoffs', safeSegment(handoffId, 'handoff_id'), 'handoff.json');
65
69
  }
66
70
  historyPath(selfAid) {
67
71
  return path.join(this.agentDir(selfAid), 'history.jsonl');
@@ -147,7 +151,8 @@ export class HandoffStore {
147
151
  get(selfAid, handoffId) {
148
152
  if (!HANDOFF_ID_RE.test(handoffId))
149
153
  return null;
150
- const parsed = atomicReadJson(this.instancePath(selfAid, handoffId));
154
+ const parsed = atomicReadJson(this.instancePath(selfAid, handoffId))
155
+ ?? atomicReadJson(this.legacyInstancePath(selfAid, handoffId));
151
156
  if (!parsed)
152
157
  return null;
153
158
  if (parsed.schema_version !== HANDOFF_SCHEMA_VERSION || parsed.handoff_id !== handoffId) {
@@ -156,14 +161,32 @@ export class HandoffStore {
156
161
  return parsed;
157
162
  }
158
163
  list(selfAid) {
159
- const dir = this.handoffsDir(selfAid);
164
+ const dir = this.agentDir(selfAid);
160
165
  if (!fs.existsSync(dir))
161
166
  return [];
162
- const result = [];
167
+ const handoffIds = new Set();
163
168
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
164
- if (!entry.isDirectory() || !HANDOFF_ID_RE.test(entry.name))
169
+ if (!entry.isFile())
165
170
  continue;
166
- const instance = this.get(selfAid, entry.name);
171
+ const match = HANDOFF_SNAPSHOT_FILE_RE.exec(entry.name);
172
+ if (match && HANDOFF_ID_RE.test(match[1]))
173
+ handoffIds.add(match[1]);
174
+ }
175
+ // Keep nested snapshots visible; a later flat snapshot takes precedence.
176
+ const legacyDir = path.join(dir, 'handoffs');
177
+ try {
178
+ for (const entry of fs.readdirSync(legacyDir, { withFileTypes: true })) {
179
+ if (entry.isDirectory() && HANDOFF_ID_RE.test(entry.name))
180
+ handoffIds.add(entry.name);
181
+ }
182
+ }
183
+ catch (error) {
184
+ if (error.code !== 'ENOENT')
185
+ throw error;
186
+ }
187
+ const result = [];
188
+ for (const handoffId of handoffIds) {
189
+ const instance = this.get(selfAid, handoffId);
167
190
  if (instance)
168
191
  result.push(instance);
169
192
  }
@@ -1,5 +1,6 @@
1
1
  import { normalizeBaseagent, resolveAnthropicDirectConfig, resolveEcagentConfig, resolveOpenaiDirectConfig, } from '../../agents/baseagent.js';
2
2
  import { buildModelRequestHeaders } from '../../agents/request-identity.js';
3
+ import { appendRequestQueryParams } from '../../agents/request-identity.js';
3
4
  function apiEndpoint(baseUrl, resource) {
4
5
  const configured = baseUrl?.trim();
5
6
  if (!configured)
@@ -11,14 +12,6 @@ function apiEndpoint(baseUrl, resource) {
11
12
  : `${pathname}/v1/${resource}`;
12
13
  return parsed.toString();
13
14
  }
14
- function withQueryParams(url, queryParams) {
15
- if (!queryParams || Object.keys(queryParams).length === 0)
16
- return url;
17
- const parsed = new URL(url);
18
- for (const [name, value] of Object.entries(queryParams))
19
- parsed.searchParams.set(name, value);
20
- return parsed.toString();
21
- }
22
15
  async function parseError(response) {
23
16
  let raw = '';
24
17
  try {
@@ -41,7 +34,7 @@ export class AnthropicTextInferenceProvider {
41
34
  this.config = config;
42
35
  }
43
36
  async completeText(request) {
44
- const response = await fetch(withQueryParams(apiEndpoint(this.config.baseUrl, 'messages'), this.config.queryParams), {
37
+ const response = await fetch(appendRequestQueryParams(apiEndpoint(this.config.baseUrl, 'messages'), this.config.queryParams), {
45
38
  method: 'POST',
46
39
  signal: request.signal,
47
40
  headers: { ...this.headers(), ...this.config.headers },
@@ -82,7 +75,7 @@ export class OpenAITextInferenceProvider {
82
75
  this.config = config;
83
76
  }
84
77
  async completeText(request) {
85
- const response = await fetch(withQueryParams(apiEndpoint(this.config.baseUrl, 'responses'), this.config.queryParams), {
78
+ const response = await fetch(appendRequestQueryParams(apiEndpoint(this.config.baseUrl, 'responses'), this.config.queryParams), {
86
79
  method: 'POST',
87
80
  signal: request.signal,
88
81
  headers: {
@@ -118,20 +111,20 @@ export class OpenAITextInferenceProvider {
118
111
  export class EcagentTextInferenceProvider {
119
112
  config;
120
113
  baseagent = 'ecagent';
121
- constructor(config) {
114
+ constructor(config, agentAid) {
122
115
  this.config = config;
123
116
  this.config = {
124
117
  ...config,
125
118
  headers: buildModelRequestHeaders({
126
119
  baseagent: 'ecagent',
127
120
  baseUrl: config.baseUrl,
128
- agentAid: config.evolcoreAgentAid,
121
+ agentAid,
129
122
  configuredHeaders: config.headers,
130
123
  }),
131
124
  };
132
125
  }
133
126
  async completeText(request) {
134
- const response = await fetch(apiEndpoint(this.config.baseUrl, 'chat/completions'), {
127
+ const response = await fetch(appendRequestQueryParams(apiEndpoint(this.config.baseUrl, 'chat/completions'), this.config.queryParams), {
135
128
  method: 'POST',
136
129
  signal: request.signal,
137
130
  headers: {
@@ -190,10 +183,9 @@ export function createTextInferenceProvider(baseagent, config) {
190
183
  try {
191
184
  const override = {
192
185
  ...(config.baseagents?.ecagent ?? {}),
193
- evolcoreAgentAid: config.aid,
194
186
  };
195
187
  const resolved = resolveEcagentConfig({ agents: { ecagent: override } }, override);
196
- return new EcagentTextInferenceProvider(resolved);
188
+ return new EcagentTextInferenceProvider(resolved, config.aid);
197
189
  }
198
190
  catch {
199
191
  return undefined;
@@ -1,23 +1,7 @@
1
1
  import { logger } from '../../utils/logger.js';
2
- import { summarizeToolInput } from '../../utils/tool-summary.js';
2
+ import { summarizeToolInput, toolInputForDisplay } from '../../utils/tool-summary.js';
3
3
  import { getErrorMessage, isContextTooLongText } from '../../utils/error-utils.js';
4
- import fs from 'fs';
5
- import path from 'path';
6
- import { resolvePaths } from '../../paths.js';
7
4
  import { DEFAULT_FLUSH_DELAY_MS } from '../../types.js';
8
- let diagStream = null;
9
- function getDiagStream() {
10
- if (!diagStream) {
11
- const logDir = resolvePaths().logs;
12
- diagStream = fs.createWriteStream(path.join(logDir, 'im-renderer-diag.log'), { flags: 'a' });
13
- }
14
- return diagStream;
15
- }
16
- function diag(instanceId, action, meta = {}) {
17
- const line = JSON.stringify({ ts: new Date().toISOString(), id: instanceId, action, ...meta });
18
- getDiagStream().write(line + '\n');
19
- }
20
- let instanceCounter = 0;
21
5
  export class IMRenderer {
22
6
  opts;
23
7
  itemsQueue = [];
@@ -28,8 +12,6 @@ export class IMRenderer {
28
12
  sentContent = false;
29
13
  flushCount = 0;
30
14
  messageTimestamps = [];
31
- instanceId;
32
- diagEnabled;
33
15
  /** 串行发送队列:保证消息按序到达 */
34
16
  sendChain = Promise.resolve();
35
17
  /** proactive:是否已发过 text 文本(用于去重 complete.result) */
@@ -38,16 +20,6 @@ export class IMRenderer {
38
20
  syntheticCallSeq = 0;
39
21
  constructor(opts) {
40
22
  this.opts = opts;
41
- this.diagEnabled = opts.diagEnabled ?? false;
42
- this.instanceId = `R${++instanceCounter}`;
43
- if (this.diagEnabled) {
44
- diag(this.instanceId, 'created', {
45
- chatmode: opts.envelope.chatmode,
46
- flushDelay: opts.flushDelay,
47
- suppressActivityItems: this.suppressActivityItems(),
48
- suppressIntermediateText: this.suppressIntermediateText(),
49
- });
50
- }
51
23
  }
52
24
  suppressActivityItems() {
53
25
  return this.opts.suppressActivityItems ?? this.opts.suppressActivities ?? false;
@@ -62,6 +34,9 @@ export class IMRenderer {
62
34
  const correlatedEvent = event.type === 'tool_use' || event.type === 'tool_result'
63
35
  ? { ...event, correlationId: event.correlationId ?? event.callId }
64
36
  : event;
37
+ const permissionMode = typeof this.opts.permissionMode === 'function'
38
+ ? this.opts.permissionMode()
39
+ : this.opts.permissionMode;
65
40
  logger.event({
66
41
  source: 'runner',
67
42
  taskId: this.opts.envelope.taskId,
@@ -69,6 +44,7 @@ export class IMRenderer {
69
44
  channelId: this.opts.envelope.channelId,
70
45
  agentAid: this.opts.agentAid,
71
46
  agentName: this.opts.envelope.agentName,
47
+ ...(permissionMode ? { permissionMode } : {}),
72
48
  ...(correlatedEvent.type === 'tool_use' || correlatedEvent.type === 'tool_result'
73
49
  ? { correlationId: correlatedEvent.correlationId, toolName: correlatedEvent.name }
74
50
  : {}),
@@ -138,10 +114,14 @@ export class IMRenderer {
138
114
  this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
139
115
  const payload = { kind: 'result.text', text: rawText, isFinal: false };
140
116
  this.sentContent = true;
141
- this.sendChain = this.sendChain
142
- .then(() => this.opts.send(payload))
143
- .catch(e => logger.warn('[IMRenderer] flushText send failed:', e));
144
- await this.sendChain;
117
+ try {
118
+ await this.sendPayload(payload);
119
+ }
120
+ catch (error) {
121
+ // A failed durable enqueue must remain retryable by the final flush.
122
+ this.textBuffer = rawText + this.textBuffer;
123
+ throw error;
124
+ }
145
125
  this.lastFlush = Date.now();
146
126
  this.flushCount++;
147
127
  }
@@ -195,13 +175,6 @@ export class IMRenderer {
195
175
  this.textBuffer += text;
196
176
  this.allText += text;
197
177
  this.messageTimestamps.push(Date.now());
198
- if (this.diagEnabled) {
199
- diag(this.instanceId, 'addText', {
200
- len: text.length,
201
- preview: text.substring(0, 60),
202
- bufLen: this.textBuffer.length,
203
- });
204
- }
205
178
  this.scheduleFlush();
206
179
  }
207
180
  /** 添加工具调用 */
@@ -215,12 +188,10 @@ export class IMRenderer {
215
188
  kind: 'tool_call',
216
189
  call_id: callId || this.synthCallId(),
217
190
  name,
218
- arguments: input,
191
+ arguments: toolInputForDisplay(name, input),
219
192
  text: descText,
220
193
  });
221
194
  this.messageTimestamps.push(Date.now());
222
- if (this.diagEnabled)
223
- diag(this.instanceId, 'addToolCall', { name, callId });
224
195
  this.scheduleFlush();
225
196
  }
226
197
  /** 添加工具结果 */
@@ -241,8 +212,6 @@ export class IMRenderer {
241
212
  ...(descText !== undefined && { text: descText }),
242
213
  });
243
214
  this.messageTimestamps.push(Date.now());
244
- if (this.diagEnabled)
245
- diag(this.instanceId, 'addToolResult', { name, ok, callId });
246
215
  this.scheduleFlush();
247
216
  }
248
217
  /** 添加进度提示 */
@@ -330,10 +299,7 @@ export class IMRenderer {
330
299
  await this.flushText();
331
300
  const payload = { kind: 'result.text', text, isFinal: false };
332
301
  this.sentContent = true;
333
- this.sendChain = this.sendChain
334
- .then(() => this.opts.send(payload))
335
- .catch(e => logger.warn('[IMRenderer] notice text send failed:', e));
336
- await this.sendChain;
302
+ await this.sendPayload(payload);
337
303
  this.lastFlush = Date.now();
338
304
  this.flushCount++;
339
305
  return true;
@@ -342,10 +308,48 @@ export class IMRenderer {
342
308
  synthCallId() {
343
309
  return `synth-${this.opts.envelope.taskId}-${++this.syntheticCallSeq}`;
344
310
  }
311
+ /** Convert a structured failed receipt into the same error shape as a rejected sender. */
312
+ receiptError(receipt) {
313
+ return Object.assign(new Error(receipt.error), {
314
+ code: receipt.code,
315
+ outboxId: receipt.outboxId,
316
+ operationId: receipt.operationId,
317
+ status: receipt.status,
318
+ });
319
+ }
320
+ isOutboxFull(error) {
321
+ const value = error && typeof error === 'object' ? error : {};
322
+ const cause = value.cause && typeof value.cause === 'object'
323
+ ? value.cause
324
+ : undefined;
325
+ return value.code === 'OUTBOX_FULL' || cause?.code === 'OUTBOX_FULL';
326
+ }
327
+ /**
328
+ * Serialize durable sends while keeping the chain usable after a rejected
329
+ * operation. Activity queue saturation is best-effort: it is logged, but
330
+ * must not prevent the final response from being sent.
331
+ */
332
+ sendPayload(payload) {
333
+ const operation = this.sendChain.then(async () => {
334
+ const receipt = await this.opts.send(payload);
335
+ if (receipt?.status === 'failed') {
336
+ throw this.receiptError(receipt);
337
+ }
338
+ });
339
+ const handled = operation.catch(error => {
340
+ if (payload.kind === 'activity.batch' && this.isOutboxFull(error)) {
341
+ logger.warn(`[IMRenderer] activity outbox full; continuing without activity batch: ${error instanceof Error ? error.message : String(error)}`);
342
+ return;
343
+ }
344
+ throw error;
345
+ });
346
+ // A failed operation must not poison later sends. The caller still gets
347
+ // the rejection through `handled`, while the internal tail is recovered.
348
+ this.sendChain = handled.catch(() => { });
349
+ return handled;
350
+ }
345
351
  scheduleFlush() {
346
352
  if (this.timer) {
347
- if (this.diagEnabled)
348
- diag(this.instanceId, 'scheduleFlush:skip', { reason: 'timer_exists' });
349
353
  return;
350
354
  }
351
355
  const interval = this.opts.flushDelay ?? DEFAULT_FLUSH_DELAY_MS;
@@ -364,15 +368,11 @@ export class IMRenderer {
364
368
  }
365
369
  const elapsed = Date.now() - this.lastFlush;
366
370
  const delay = Math.max(0, targetDelay - elapsed);
367
- if (this.diagEnabled) {
368
- diag(this.instanceId, 'scheduleFlush:set', {
369
- flushCount: this.flushCount,
370
- targetDelay,
371
- elapsed,
372
- actualDelay: delay,
371
+ this.timer = setTimeout(() => {
372
+ void this.flushInternal().catch(error => {
373
+ logger.warn(`[IMRenderer] scheduled flush failed: ${error instanceof Error ? error.message : String(error)}`);
373
374
  });
374
- }
375
- this.timer = setTimeout(() => this.flushInternal(), delay);
375
+ }, delay);
376
376
  }
377
377
  calculateDynamicDelay(interval) {
378
378
  const recent = this.messageTimestamps.slice(-10);
@@ -402,13 +402,14 @@ export class IMRenderer {
402
402
  if (this.suppressActivityItems())
403
403
  return;
404
404
  const payload = { kind: 'activity.batch', items: nonThinking };
405
- if (this.diagEnabled)
406
- diag(this.instanceId, 'flushActivitiesOnly', { itemCount: nonThinking.length });
407
405
  this.sentContent = true;
408
- this.sendChain = this.sendChain
409
- .then(() => this.opts.send(payload))
410
- .catch(e => logger.warn('[IMRenderer] activity.batch send failed:', e));
411
- await this.sendChain;
406
+ try {
407
+ await this.sendPayload(payload);
408
+ }
409
+ catch (error) {
410
+ this.itemsQueue = [...nonThinking, ...this.itemsQueue];
411
+ throw error;
412
+ }
412
413
  this.lastFlush = Date.now();
413
414
  this.flushCount++;
414
415
  }
@@ -446,24 +447,20 @@ export class IMRenderer {
446
447
  const finalText = isFinal ? this.textBuffer : '';
447
448
  if (isFinal)
448
449
  this.textBuffer = '';
449
- if (this.diagEnabled) {
450
- diag(this.instanceId, 'flush', {
451
- isFinal,
452
- itemCount: items.length,
453
- finalTextLen: finalText.length,
454
- flushCount: this.flushCount,
455
- sinceLastFlush: Date.now() - this.lastFlush,
456
- });
457
- }
458
450
  // 1. interactive 模式下:不发 text items(由 result.text 统一发送最终文本)
459
451
  const itemsForBatch = this.suppressActivityItems() ? [] : items.filter(it => it.kind !== 'text');
460
452
  if (itemsForBatch.length > 0) {
461
453
  const payload = { kind: 'activity.batch', items: itemsForBatch };
462
454
  this.sentContent = true;
463
- this.sendChain = this.sendChain
464
- .then(() => this.opts.send(payload))
465
- .catch(e => logger.warn('[IMRenderer] activity.batch send failed:', e));
466
- await this.sendChain;
455
+ try {
456
+ await this.sendPayload(payload);
457
+ }
458
+ catch (error) {
459
+ this.itemsQueue = [...itemsForBatch, ...this.itemsQueue];
460
+ if (isFinal && finalText)
461
+ this.textBuffer = finalText + this.textBuffer;
462
+ throw error;
463
+ }
467
464
  this.lastFlush = Date.now();
468
465
  this.flushCount++;
469
466
  }
@@ -481,10 +478,13 @@ export class IMRenderer {
481
478
  this.textBuffer = '';
482
479
  const payload = { kind: 'result.text', text, isFinal: false };
483
480
  this.sentContent = true;
484
- this.sendChain = this.sendChain
485
- .then(() => this.opts.send(payload))
486
- .catch(e => logger.warn('[IMRenderer] timed result.text send failed:', e));
487
- await this.sendChain;
481
+ try {
482
+ await this.sendPayload(payload);
483
+ }
484
+ catch (error) {
485
+ this.textBuffer = text + this.textBuffer;
486
+ throw error;
487
+ }
488
488
  this.lastFlush = Date.now();
489
489
  this.flushCount++;
490
490
  }
@@ -492,10 +492,13 @@ export class IMRenderer {
492
492
  if (isFinal && finalText.length > 0) {
493
493
  const payload = { kind: 'result.text', text: finalText, isFinal: true };
494
494
  this.sentContent = true;
495
- this.sendChain = this.sendChain
496
- .then(() => this.opts.send(payload))
497
- .catch(e => logger.warn('[IMRenderer] result.text send failed:', e));
498
- await this.sendChain;
495
+ try {
496
+ await this.sendPayload(payload);
497
+ }
498
+ catch (error) {
499
+ this.textBuffer = finalText + this.textBuffer;
500
+ throw error;
501
+ }
499
502
  this.lastFlush = Date.now();
500
503
  this.flushCount++;
501
504
  }
@@ -569,7 +572,7 @@ export class IMRenderer {
569
572
  text: desc,
570
573
  };
571
574
  if (event.input !== undefined)
572
- item.arguments = event.input;
575
+ item.arguments = toolInputForDisplay(event.name, event.input);
573
576
  return item;
574
577
  }
575
578
  case 'tool_result':