evolcore 0.0.23 → 0.0.25

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 (245) hide show
  1. package/CHANGELOG.md +583 -516
  2. package/LICENSE +21 -21
  3. package/README.md +455 -453
  4. package/bin/codex-managed-acl.ps1 +160 -155
  5. package/bin/codex-managed-hook.mjs +209 -92
  6. package/bin/ec-safe-output.js +327 -245
  7. package/bin/ec.js +29 -29
  8. package/bin/install-codex-managed-hooks.mjs +555 -544
  9. package/dist/agents/baseagent-availability.js +4 -4
  10. package/dist/agents/baseagent.js +29 -1
  11. package/dist/agents/claude-runner.js +152 -14
  12. package/dist/agents/codex/app-server-client.js +18 -8
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +215 -0
  15. package/dist/agents/codex-runner.js +445 -63
  16. package/dist/agents/ecagent-runner.js +31 -16
  17. package/dist/agents/gemini-runner.js +82 -4
  18. package/dist/agents/runner-types.js +1 -1
  19. package/dist/aun/aid/agentmd.js +56 -4
  20. package/dist/aun/aid/index.js +1 -0
  21. package/dist/aun/aid/managed-operation.js +80 -6
  22. package/dist/aun/aid/store.js +41 -0
  23. package/dist/aun/msg/managed-operation.js +6 -1
  24. package/dist/aun/outbox.js +56 -18
  25. package/dist/aun/rpc/client.js +4 -5
  26. package/dist/aun/storage/client.js +4 -1
  27. package/dist/aun/storage/index.js +1 -1
  28. package/dist/channels/aun.js +147 -76
  29. package/dist/channels/daemon.js +7 -1
  30. package/dist/channels/dingtalk.js +1 -0
  31. package/dist/channels/feishu.js +10 -4
  32. package/dist/channels/wecom.js +2 -1
  33. package/dist/cli/agent-command.js +214 -89
  34. package/dist/cli/agent-create.js +576 -0
  35. package/dist/cli/agent.js +129 -676
  36. package/dist/cli/aun-commands.js +419 -313
  37. package/dist/cli/bench.js +20 -20
  38. package/dist/cli/config.js +20 -20
  39. package/dist/cli/contact.js +10 -10
  40. package/dist/cli/ctl-command.js +22 -20
  41. package/dist/cli/daemon-commands.js +163 -32
  42. package/dist/cli/fs-command.js +67 -67
  43. package/dist/cli/handoff-command.js +7 -7
  44. package/dist/cli/index.js +79 -79
  45. package/dist/cli/link-rules.js +21 -21
  46. package/dist/cli/managed-command-guard.js +3 -0
  47. package/dist/cli/menu-client.js +11 -11
  48. package/dist/cli/net-check.js +22 -22
  49. package/dist/cli/queue-command.js +29 -29
  50. package/dist/cli/response.js +30 -30
  51. package/dist/cli/restart-monitor.js +462 -38
  52. package/dist/cli/stats.js +161 -51
  53. package/dist/cli/trigger-command.js +56 -56
  54. package/dist/config/builtin-roles.js +3 -0
  55. package/dist/config/config-manager.js +15 -0
  56. package/dist/config/config-operation-service.js +20 -3
  57. package/dist/config/resolved-config-op.js +39 -1
  58. package/dist/core/auth/auth-gateway.js +3 -2
  59. package/dist/core/auth/authorization-audit.js +72 -19
  60. package/dist/core/auth/operation-authorizer.js +125 -7
  61. package/dist/core/auth/operation-catalog.js +26 -2
  62. package/dist/core/auth/remote-access-store.js +3 -2
  63. package/dist/core/baseagent-loader.js +4 -4
  64. package/dist/core/bootstrap-messages.js +39 -18
  65. package/dist/core/bootstrap-service.js +32 -26
  66. package/dist/core/capability/capability-manager.js +8 -4
  67. package/dist/core/capability/providers/codex-capability-provider.js +1 -1
  68. package/dist/core/command/agent-control.js +60 -26
  69. package/dist/core/command/cli-intent-parser.js +18 -0
  70. package/dist/core/command/command-handler.js +173 -36
  71. package/dist/core/command/menu-catalog.js +4 -1
  72. package/dist/core/command/menu-handler.js +315 -75
  73. package/dist/core/command/menu-protocol.js +7 -2
  74. package/dist/core/command/slash-gate.js +1 -3
  75. package/dist/core/command/slash-handler.js +166 -57
  76. package/dist/core/data-migration.js +5 -15
  77. package/dist/core/event-catalog.js +8 -0
  78. package/dist/core/evolagent.js +3 -3
  79. package/dist/core/handoff/runtime.js +3 -1
  80. package/dist/core/handoff/store.js +7 -1
  81. package/dist/core/message/im-renderer.js +13 -6
  82. package/dist/core/message/message-bridge.js +5 -36
  83. package/dist/core/message/message-utils.js +6 -3
  84. package/dist/core/message/response-engine.js +85 -27
  85. package/dist/core/model/catalog-service.js +3 -1
  86. package/dist/core/model/model-catalog.js +10 -0
  87. package/dist/core/model/model-permission.js +4 -1
  88. package/dist/core/permission/approval-gateway.js +169 -33
  89. package/dist/core/permission/tool-policy.js +56 -10
  90. package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
  91. package/dist/core/session/session-manager.js +49 -13
  92. package/dist/core/session/session-renew.js +1 -0
  93. package/dist/index.js +327 -134
  94. package/dist/ipc.js +68 -21
  95. package/dist/paths.js +23 -1
  96. package/dist/stats/billing.js +18 -4
  97. package/dist/stats/db.js +159 -159
  98. package/dist/stats/eck-vars.js +9 -9
  99. package/dist/stats/query.js +139 -139
  100. package/dist/stats/role-budget.js +6 -6
  101. package/dist/stats/writer.js +51 -51
  102. package/dist/utils/atomic-write.js +26 -2
  103. package/dist/utils/avatar-upload.js +282 -23
  104. package/dist/utils/cross-platform.js +436 -1
  105. package/dist/utils/error-dict.json +160 -160
  106. package/dist/utils/error-utils.js +11 -4
  107. package/dist/utils/macos-autostart.js +22 -22
  108. package/dist/utils/model-prices.jsonl +23 -20
  109. package/dist/utils/npm-ops.js +24 -1
  110. package/dist/utils/welcome.js +22 -22
  111. package/dist/utils/windows-autostart.js +1 -1
  112. package/ecagent/LICENSE +21 -21
  113. package/ecagent/NOTICE +11 -11
  114. package/ecagent/README.md +61 -61
  115. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  116. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  117. package/ecagent/dist/harness/messages.js +7 -7
  118. package/kits/docs/GUIDE.md +20 -20
  119. package/kits/docs/INDEX.md +66 -66
  120. package/kits/docs/aun/CHEATSHEET.md +19 -19
  121. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  122. package/kits/docs/channels/aun.md +56 -56
  123. package/kits/docs/channels/feishu.md +56 -56
  124. package/kits/docs/context-assembly.md +365 -364
  125. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  126. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  127. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  128. package/kits/docs/eck_templates/runtime.template.md +19 -19
  129. package/kits/docs/evolcore/INDEX.md +71 -69
  130. package/kits/docs/evolcore/agent-create.md +170 -156
  131. package/kits/docs/evolcore/agent.md +83 -73
  132. package/kits/docs/evolcore/aid.md +64 -62
  133. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  134. package/kits/docs/evolcore/config.md +195 -195
  135. package/kits/docs/evolcore/contact.md +68 -68
  136. package/kits/docs/evolcore/ctl.md +46 -46
  137. package/kits/docs/evolcore/event.md +216 -216
  138. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  139. package/kits/docs/evolcore/fs.md +110 -110
  140. package/kits/docs/evolcore/group-collaboration.md +243 -243
  141. package/kits/docs/evolcore/group-fs.md +17 -17
  142. package/kits/docs/evolcore/group-rules.md +251 -251
  143. package/kits/docs/evolcore/group.md +152 -152
  144. package/kits/docs/evolcore/menu-client.md +34 -34
  145. package/kits/docs/evolcore/model.md +50 -50
  146. package/kits/docs/evolcore/msg.md +165 -165
  147. package/kits/docs/evolcore/response.md +75 -75
  148. package/kits/docs/evolcore/rpc.md +37 -37
  149. package/kits/docs/evolcore/self-summary.md +29 -29
  150. package/kits/docs/evolcore/stats.md +83 -83
  151. package/kits/docs/evolcore/storage.md +50 -50
  152. package/kits/docs/evolcore/trigger.md +575 -575
  153. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  154. package/kits/docs/identity/PATH_OPS.md +16 -16
  155. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  156. package/kits/docs/identity/identity-tools.md +26 -26
  157. package/kits/docs/path-registry.md +62 -62
  158. package/kits/docs/prompt-loading-architecture.md +270 -269
  159. package/kits/docs/venues/aun-group.md +45 -45
  160. package/kits/docs/venues/aun-private.md +10 -10
  161. package/kits/docs/venues/client-desktop.md +10 -10
  162. package/kits/docs/venues/client-mobile.md +10 -10
  163. package/kits/docs/venues/feishu-group.md +13 -13
  164. package/kits/docs/venues/feishu-private.md +9 -9
  165. package/kits/docs/venues/group.md +25 -25
  166. package/kits/docs/venues/private.md +10 -10
  167. package/kits/eck_manifest.auxiliary.json +43 -43
  168. package/kits/eck_manifest.json +195 -183
  169. package/kits/eck_message_manifest.json +74 -74
  170. package/kits/migrations/README-role-config-v4.md +32 -32
  171. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  172. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  173. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  174. package/kits/migrations/rename-config-file.mjs +99 -99
  175. package/kits/rules/01-overview.md +162 -162
  176. package/kits/rules/02-navigation.md +96 -96
  177. package/kits/rules/03-identity.md +39 -37
  178. package/kits/rules/04-relation.md +75 -75
  179. package/kits/rules/05-venue.md +60 -60
  180. package/kits/rules/06-channel.md +63 -63
  181. package/kits/schemas/_meta.json +53 -53
  182. package/kits/schemas/agent-config.schema.1.json +177 -177
  183. package/kits/schemas/agent-config.schema.10.json +395 -395
  184. package/kits/schemas/agent-config.schema.11.json +421 -421
  185. package/kits/schemas/agent-config.schema.12.json +427 -427
  186. package/kits/schemas/agent-config.schema.2.json +239 -239
  187. package/kits/schemas/agent-config.schema.3.json +119 -119
  188. package/kits/schemas/agent-config.schema.4.json +208 -208
  189. package/kits/schemas/agent-config.schema.5.json +326 -326
  190. package/kits/schemas/agent-config.schema.6.json +322 -322
  191. package/kits/schemas/agent-config.schema.7.json +303 -303
  192. package/kits/schemas/agent-config.schema.8.json +304 -304
  193. package/kits/schemas/agent-config.schema.9.json +364 -364
  194. package/kits/schemas/contact-book.schema.1.json +36 -36
  195. package/kits/schemas/contact-book.schema.2.json +43 -43
  196. package/kits/schemas/contact-book.schema.3.json +67 -67
  197. package/kits/schemas/daemon.schema.1.json +91 -91
  198. package/kits/schemas/daemon.schema.2.json +101 -101
  199. package/kits/schemas/daemon.schema.3.json +123 -123
  200. package/kits/schemas/daemon.schema.4.json +136 -136
  201. package/kits/schemas/daemon.schema.5.json +135 -135
  202. package/kits/schemas/daemon.schema.6.json +131 -131
  203. package/kits/schemas/defaults.schema.1.json +81 -81
  204. package/kits/schemas/defaults.schema.2.json +85 -85
  205. package/kits/schemas/defaults.schema.3.json +73 -73
  206. package/kits/schemas/defaults.schema.4.json +107 -107
  207. package/kits/schemas/defaults.schema.5.json +119 -119
  208. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  209. package/kits/schemas/migrations/README.md +30 -30
  210. package/kits/schemas/relation-config.schema.1.json +158 -158
  211. package/kits/schemas/relation-config.schema.2.json +73 -73
  212. package/kits/schemas/relation-config.schema.3.json +50 -50
  213. package/kits/schemas/relation-config.schema.4.json +47 -47
  214. package/kits/schemas/relation-config.schema.5.json +47 -47
  215. package/kits/schemas/relation-config.schema.6.json +46 -46
  216. package/kits/schemas/relation-config.schema.7.json +59 -59
  217. package/kits/schemas/relation-config.schema.8.json +103 -103
  218. package/kits/schemas/role-config.schema.1.json +201 -201
  219. package/kits/schemas/role-registry.schema.1.json +35 -35
  220. package/kits/schemas/single-session.schema.1.json +31 -31
  221. package/kits/schemas/single-session.schema.2.json +57 -57
  222. package/kits/schemas/single-session.schema.3.json +32 -32
  223. package/kits/templates/bootstrap-welcome.md +17 -17
  224. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  225. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  226. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  227. package/kits/templates/message-fragments/inject-default.md +2 -2
  228. package/kits/templates/message-fragments/item.md +2 -2
  229. package/kits/templates/roles/admin.json +96 -82
  230. package/kits/templates/roles/member.json +97 -96
  231. package/kits/templates/roles/owner.json +9 -9
  232. package/kits/templates/roles/visitor.json +86 -85
  233. package/kits/templates/system-fragments/baseagent.md +14 -14
  234. package/kits/templates/system-fragments/bootstrap.md +38 -29
  235. package/kits/templates/system-fragments/channel.md +64 -64
  236. package/kits/templates/system-fragments/commands.md +28 -28
  237. package/kits/templates/system-fragments/identity.md +13 -13
  238. package/kits/templates/system-fragments/relation.md +19 -19
  239. package/kits/templates/system-fragments/session.md +59 -59
  240. package/kits/templates/system-fragments/venue.md +31 -31
  241. package/package.json +98 -94
  242. package/AGENTS.md +0 -62
  243. package/dist/utils/codex-cli.js +0 -43
  244. package/skills/eclink/SKILL.md +0 -84
  245. package/skills/eclink/agents/openai.yaml +0 -4
@@ -680,40 +680,6 @@ export class MessageBridge {
680
680
  }
681
681
  return d;
682
682
  }
683
- availableBaseagentsFor(channelName) {
684
- const owningAgent = this.agentRegistry?.resolveByChannel(channelName);
685
- if (!owningAgent)
686
- return [];
687
- const prefix = `${owningAgent.name}::`;
688
- return this.processor.getAvailableAgents()
689
- .filter(key => key.startsWith(prefix))
690
- .map(key => key.slice(prefix.length));
691
- }
692
- async alignSessionBaseagent(channelName, session) {
693
- const owningAgent = this.agentRegistry?.resolveByChannel(channelName);
694
- if (!owningAgent)
695
- return session;
696
- const available = this.availableBaseagentsFor(channelName);
697
- if (available.length === 0 || available.includes(session.baseagent))
698
- return session;
699
- // A topic's backend binding is fenced by SessionManager and cannot be
700
- // cleared as a side effect of routing alignment. Reassigning the
701
- // baseagent while leaving the old provider's agentSessionId persisted
702
- // would make the new runner resume an incompatible backend. Keep the
703
- // binding intact and let normal baseagent-mismatch handling report it;
704
- // an explicit boundary operation must perform any future migration.
705
- if (session.threadId) {
706
- logger.error(`[MessageBridge] Cannot align topic to another baseagent: `
707
- + `session=${session.id} thread=${session.threadId} current=${session.baseagent} preferred=${owningAgent.baseagent}`);
708
- return session;
709
- }
710
- const preferred = available.includes(owningAgent.baseagent)
711
- ? owningAgent.baseagent
712
- : available[0];
713
- logger.warn(`[MessageBridge] Aligning unavailable session baseagent: session=${session.id} ${session.baseagent} -> ${preferred} agent=${owningAgent.name} available=${available.join(',')}`);
714
- await this.sessionManager.updateSession(session.id, { baseagent: preferred });
715
- return { ...session, baseagent: preferred };
716
- }
717
683
  /**
718
684
  * 为渠道注册消息桥梁:入站处理管线 + 出站命令响应
719
685
  *
@@ -1140,7 +1106,6 @@ export class MessageBridge {
1140
1106
  if (session.threadId && typeof this.messageQueue.reserveSessionIngress === 'function') {
1141
1107
  releaseSessionIngress = this.messageQueue.reserveSessionIngress(session.id);
1142
1108
  }
1143
- session = await this.alignSessionBaseagent(channelName, session);
1144
1109
  const replyToMessageId = typeof msg.replyContext?.metadata?.refMessageId === 'string'
1145
1110
  ? msg.replyContext.metadata.refMessageId
1146
1111
  : msg.replyContext?.replyToMessageId ?? null;
@@ -1163,6 +1128,7 @@ export class MessageBridge {
1163
1128
  content,
1164
1129
  replyToMessageId,
1165
1130
  isNewSession: !hadMainSession,
1131
+ currentBaseagent: owningAgent?.baseagent,
1166
1132
  causation: inboundCausation,
1167
1133
  });
1168
1134
  session = renewResult.session;
@@ -1537,7 +1503,10 @@ export class MessageBridge {
1537
1503
  channelId: msg.channelId,
1538
1504
  args,
1539
1505
  });
1540
- 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);
1506
+ 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', {
1507
+ ...(msg.replyContext ?? {}),
1508
+ ...(msg.threadId ? { threadId: msg.threadId } : {}),
1509
+ });
1541
1510
  if ('error' in result)
1542
1511
  throw result;
1543
1512
  return menuSuccess(header, result.data);
@@ -7,8 +7,9 @@ import { renderCommandCardAsText, renderActionAsText } from '../interaction-rout
7
7
  /** A legacy adapter resolved its send call without providing a structured receipt. */
8
8
  export const LEGACY_INTERACTION_ACCEPTED = 'legacy-accepted';
9
9
  export function isInteractionSendAccepted(result) {
10
- return result === LEGACY_INTERACTION_ACCEPTED
11
- || (result !== false && (result.status === 'sent' || result.status === 'queued'));
10
+ if (result === LEGACY_INTERACTION_ACCEPTED || result === 'sent')
11
+ return true;
12
+ return result !== false && (result.status === 'sent' || result.status === 'queued');
12
13
  }
13
14
  /**
14
15
  * 构造出站消息信封
@@ -148,8 +149,10 @@ export async function sendInteractionPayload(adapter, envelope, interaction, fal
148
149
  try {
149
150
  const enriched = replyCtx ? withEnvelopeReplyContext(envelope, replyCtx) : envelope;
150
151
  const receipt = await adapter.send(enriched, payload);
152
+ // Preserve the historical `sent` result for void-returning adapters while
153
+ // still accepting the newer structured SendReceipt contract.
151
154
  if (!receipt)
152
- return LEGACY_INTERACTION_ACCEPTED;
155
+ return 'sent';
153
156
  if (receipt.status === 'sent' && !(receipt.messageId || receipt.messages[0]?.messageId))
154
157
  return false;
155
158
  return receipt;
@@ -517,6 +517,7 @@ export class ResponseEngine {
517
517
  /** 已主动发送开始提示时,忽略 runner 对同一次 compact 的回调。 */
518
518
  announcedCompactStarts = new Set();
519
519
  agentMap;
520
+ legacySingleRunnerMode;
520
521
  primaryRunnerKey;
521
522
  interruptedSessions = new Map(); // sessionId → reason ('new_message' | 'stop' | ...)
522
523
  timeoutErrors = new Map();
@@ -552,27 +553,42 @@ export class ResponseEngine {
552
553
  * session/config mismatch and must not silently route to a different backend.
553
554
  */
554
555
  getAgent(channel, baseagent, selfAID) {
555
- if (selfAID && baseagent) {
556
- const key = `${selfAID}::${baseagent}`;
556
+ const requestedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
557
+ // Keep the explicit single-backend map shape usable by embedders and
558
+ // focused tests. This is an exact key lookup, not a cross-agent fallback.
559
+ if (requestedBaseagent && this.agentMap.has(requestedBaseagent)) {
560
+ return this.agentMap.get(requestedBaseagent);
561
+ }
562
+ // A caller that passed one runner explicitly has already selected the
563
+ // backend; this compatibility path does not exist for multi-agent maps.
564
+ if (this.legacySingleRunnerMode && this.agentMap.size === 1
565
+ && this.agentMap.values().next().value?.name === requestedBaseagent) {
566
+ return this.agentMap.values().next().value;
567
+ }
568
+ if (selfAID && requestedBaseagent) {
569
+ const key = `${selfAID}::${requestedBaseagent}`;
557
570
  if (this.agentMap.has(key))
558
571
  return this.agentMap.get(key);
559
572
  }
560
- if (channel && baseagent) {
573
+ if (channel && requestedBaseagent) {
561
574
  const owner = this.agentRegistry?.resolveByChannel(channel);
562
575
  const evolName = owner?.name || '<unknown>';
563
- const key = `${evolName}::${baseagent}`;
576
+ const key = `${evolName}::${requestedBaseagent}`;
564
577
  if (this.agentMap.has(key))
565
578
  return this.agentMap.get(key);
566
- const singleRunnerKey = `<unknown>::${baseagent}`;
567
- if (this.agentMap.has(singleRunnerKey))
568
- return this.agentMap.get(singleRunnerKey);
569
579
  if (owner) {
570
- throw new BaseagentRunnerUnavailableError(evolName, baseagent, this.getAvailableBaseagentsForOwner(evolName));
580
+ throw new BaseagentRunnerUnavailableError(evolName, requestedBaseagent, this.getAvailableBaseagentsForOwner(evolName));
571
581
  }
582
+ // Control/test channels may have no registry owner. Resolve only when
583
+ // exactly one loaded runner advertises this baseagent; never choose the
584
+ // primary runner or the first map entry as a backend substitute.
585
+ const matches = [...this.agentMap.entries()]
586
+ .filter(([runnerKey]) => runnerKey.endsWith(`::${requestedBaseagent}`));
587
+ if (matches.length === 1)
588
+ return matches[0][1];
589
+ throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent, [...new Set(matches.map(([runnerKey]) => runnerKey.slice(runnerKey.lastIndexOf('::') + 2)))]);
572
590
  }
573
- if (this.agentMap.has(this.primaryRunnerKey))
574
- return this.agentMap.get(this.primaryRunnerKey);
575
- return this.agentMap.values().next().value;
591
+ throw new BaseagentRunnerUnavailableError('<unknown>', requestedBaseagent || '<missing>', []);
576
592
  }
577
593
  getTextInferenceProvider(channel, baseagent, selfAID) {
578
594
  if (!baseagent)
@@ -648,11 +664,13 @@ export class ResponseEngine {
648
664
  this.turnCoordinator = new SessionTurnCoordinator(sessionManager);
649
665
  if (agentRunnerOrMap instanceof Map) {
650
666
  this.agentMap = agentRunnerOrMap;
667
+ this.legacySingleRunnerMode = false;
651
668
  this.primaryRunnerKey = primaryRunnerKey || '<unknown>::claude';
652
669
  }
653
670
  else {
654
671
  // 测试 / 单 runner 路径:占位 agent name 用 '<unknown>'
655
672
  this.agentMap = new Map([[`<unknown>::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
673
+ this.legacySingleRunnerMode = true;
656
674
  this.primaryRunnerKey = `<unknown>::${agentRunnerOrMap.name}`;
657
675
  }
658
676
  // 监听中断事件,标记被中断的 session
@@ -907,14 +925,10 @@ export class ResponseEngine {
907
925
  if (owning)
908
926
  owning.lastActivity = Date.now();
909
927
  }
910
- inferPrimaryBaseagent() {
911
- const idx = this.primaryRunnerKey.lastIndexOf('::');
912
- return idx >= 0 ? this.primaryRunnerKey.slice(idx + 2) : this.primaryRunnerKey || undefined;
913
- }
914
928
  ensureSessionBaseagent(session, fallback) {
915
- const legacyAgentId = typeof session.agentId === 'string' ? session.agentId : undefined;
916
- if (!session.baseagent) {
917
- session.baseagent = fallback || legacyAgentId || this.inferPrimaryBaseagent();
929
+ void fallback;
930
+ if (typeof session.baseagent !== 'string' || !session.baseagent.trim()) {
931
+ throw new Error(`[ResponseEngine] session ${session.id} has no baseagent binding; refusing implicit fallback`);
918
932
  }
919
933
  }
920
934
  inferDirectSessionIdentity(message, owningAgent) {
@@ -1647,6 +1661,19 @@ export class ResponseEngine {
1647
1661
  totalToolCalls: result.state.totalToolCalls,
1648
1662
  lastToolName: result.state.lastToolName,
1649
1663
  });
1664
+ const idleNotice = timeoutControl.idleNotice;
1665
+ if (idleNotice) {
1666
+ void Promise.resolve(idleNotice({
1667
+ action: result.action,
1668
+ idleSec: result.idleSec,
1669
+ message: result.message,
1670
+ totalEvents: result.state.totalEvents,
1671
+ totalToolCalls: result.state.totalToolCalls,
1672
+ lastToolName: result.state.lastToolName,
1673
+ })).catch(error => {
1674
+ logger.debug(`[ResponseEngine] Idle notice send failed: ${error instanceof Error ? error.message : String(error)}`);
1675
+ });
1676
+ }
1650
1677
  }
1651
1678
  result = monitor.check();
1652
1679
  }
@@ -2401,8 +2428,10 @@ export class ResponseEngine {
2401
2428
  opts.title = '\u2705 \u6700\u7ec8\u56de\u590d:';
2402
2429
  }
2403
2430
  opts.metadata = withTaskMetadata(opts.metadata);
2404
- if (payload.kind.startsWith('status.'))
2431
+ // status.notice is a diagnostic observation, not execution activity.
2432
+ if (payload.kind.startsWith('status.') && payload.kind !== 'status.notice') {
2405
2433
  this.touchAgentActivity(channelKey);
2434
+ }
2406
2435
  const enrichedEnvelope = withEnvelopeReplyContext(envelope, opts);
2407
2436
  snapshot.pushOutbound(session.id, taskId, { kind: payload.kind, decision: 'sent' });
2408
2437
  return await adapter.send(enrichedEnvelope, payload);
@@ -2413,6 +2442,19 @@ export class ResponseEngine {
2413
2442
  renderer,
2414
2443
  suppressActivities: shouldSuppress(),
2415
2444
  });
2445
+ // Idle notify/warn are operational diagnostic notices. Keep them on the
2446
+ // current task's renderer so envelope, stale-turn, background, and
2447
+ // channel-specific suppression rules remain identical to other sends.
2448
+ if (timeoutControl) {
2449
+ timeoutControl.idleNotice = async ({ action, idleSec, message: idleMessage, totalEvents, totalToolCalls, lastToolName }) => {
2450
+ await renderer.sendStatusNotice(idleMessage, action === 'warn' ? 'warn' : 'info', `idle-${action}`, {
2451
+ idleSec,
2452
+ totalEvents,
2453
+ totalToolCalls,
2454
+ lastToolName,
2455
+ });
2456
+ };
2457
+ }
2416
2458
  renderer.addLifecycle('started');
2417
2459
  if (isProactive) {
2418
2460
  logger.info(`[ResponseEngine] proactive mode: outputs via thought.put task=${taskId}`);
@@ -2956,6 +2998,12 @@ export class ResponseEngine {
2956
2998
  bootstrapAgentMdPath: agentMdPath(selfAid),
2957
2999
  };
2958
3000
  }
3001
+ if (selfAid && peerRole === 'owner' && effectivePermissionMode !== 'readonly') {
3002
+ modelOverride = {
3003
+ ...(modelOverride || {}),
3004
+ personalMdPath: path.join(resolveRoot(), 'agents', selfAid, 'personal', 'personal.md'),
3005
+ };
3006
+ }
2959
3007
  if (normalizedBaseagent.canonical === 'claude') {
2960
3008
  modelOverride = {
2961
3009
  ...(modelOverride || {}),
@@ -3094,6 +3142,7 @@ export class ResponseEngine {
3094
3142
  sessionId: session.id,
3095
3143
  sessionName: session.name || undefined,
3096
3144
  sessionCreatedAt: session.createdAt ? new Date(session.createdAt).toISOString() : undefined,
3145
+ managedTempDir: sessionRuntimeDir,
3097
3146
  // 时区(把 ISO 时间戳转本地时间用)+ OS 环境
3098
3147
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || undefined,
3099
3148
  tzOffset: currentTzOffset(),
@@ -3142,7 +3191,10 @@ export class ResponseEngine {
3142
3191
  try {
3143
3192
  const estTokens = (s) => s ? Math.ceil(s.length / 4) : 0;
3144
3193
  const cbModel = effectiveModel || agentModel || 'unknown';
3145
- const cbMaxTokens = 200000; // 保守默认,后续可从 model-catalog 取
3194
+ // 从统一模型参数表获取模型的真实 context_window
3195
+ const { resolveModelSpec } = await import('../../stats/billing.js');
3196
+ const modelSpec = resolveModelSpec(resolveRoot(), cbModel);
3197
+ const cbMaxTokens = modelSpec.context_window;
3146
3198
  const systemPromptTokens = estTokens(options?.systemPromptAppend);
3147
3199
  const kitTokens = estTokens(kitContext);
3148
3200
  const totalEst = estTokens(effectiveSystemPrompt);
@@ -3315,6 +3367,8 @@ export class ResponseEngine {
3315
3367
  selfAid,
3316
3368
  peerKey,
3317
3369
  issuedRole: peerRole,
3370
+ permissionMode: effectivePermissionMode,
3371
+ ...(sessionRuntimeDir ? { sessionRuntimeDir } : {}),
3318
3372
  ...(fullAccessAuthorization ? { executionIdentity: fullAccessAuthorization } : {}),
3319
3373
  });
3320
3374
  runtimeEnv[AGENT_DELEGATION_TOKEN_ENV] = delegationToken;
@@ -4513,12 +4567,12 @@ export class ResponseEngine {
4513
4567
  return undefined;
4514
4568
  const shouldClose = !writerDb;
4515
4569
  try {
4516
- const row = db.prepare(`SELECT model, input_tokens, cache_creation_tokens, cache_read_tokens,
4517
- context_tokens, max_tokens, auto_compact_tokens
4518
- FROM model_calls
4519
- WHERE session_id = ?
4520
- AND agent_session_id = ?
4521
- ORDER BY ts DESC, call_index DESC
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
4522
4576
  LIMIT 1`).get(sessionId, agentSessionId);
4523
4577
  if (!row)
4524
4578
  return undefined;
@@ -4572,7 +4626,7 @@ export class ResponseEngine {
4572
4626
  : undefined;
4573
4627
  const owningAgent = this.agentRegistry?.resolveByChannel(message.channel);
4574
4628
  const projectPath = owningAgent?.projectPath || process.cwd();
4575
- const resolvedBaseagent = message.baseagent || owningAgent?.baseagent || (!this.agentRegistry ? this.inferPrimaryBaseagent() : undefined);
4629
+ const resolvedBaseagent = message.baseagent || owningAgent?.baseagent;
4576
4630
  // 优先复用 MessageBridge 入队前已解析的身份,避免二次独立解析产生角色漂移
4577
4631
  // (曾观测到瞬时竞态下把 owner 误判为 none,导致合法用户被静默拒绝)。
4578
4632
  // 仅当消息不经 MessageBridge.register()(如 trigger 直接投递)时才回退重新解析。
@@ -4762,6 +4816,10 @@ export class ResponseEngine {
4762
4816
  // 每收到事件重置空闲超时
4763
4817
  const toolName = event.type === 'tool_use' ? event.name : undefined;
4764
4818
  resetTimer(event.type, toolName);
4819
+ // Provider reasoning/thinking is an internal liveness signal only.
4820
+ // It must never be projected as progress, activity, text, or transcript.
4821
+ if (event.type === 'heartbeat')
4822
+ continue;
4765
4823
  if (event.type === 'input_accepted') {
4766
4824
  inputAccepted = !turnLease || event.inputId === turnLease.inputId;
4767
4825
  logger.debug(`[ResponseEngine] Input accepted: session=${session.id} input=${event.inputId}`);
@@ -43,7 +43,9 @@ export function parseModelList(json, _url) {
43
43
  const rawId = typeof item === 'string' ? item : item?.id ?? item?.slug ?? item?.name ?? item?.model;
44
44
  if (typeof rawId !== 'string' || !rawId.trim())
45
45
  continue;
46
- const id = rawId.trim();
46
+ // 规范化模型 ID:去掉 Claude Code SDK 的上下文窗口后缀 [1m]/[200k]
47
+ // 这些后缀只在传给 claude baseagent 时由 applyContextWindow 添加
48
+ const id = rawId.trim().replace(/\[(?:1m|200k)\]$/i, '');
47
49
  if (out.has(id))
48
50
  continue;
49
51
  const rawLevels = item?.reasoningLevels ?? item?.supportedReasoningEfforts ?? item?.supported_reasoning_levels;
@@ -106,6 +106,16 @@ export function resolveCatalogContext(self, ba) {
106
106
  }
107
107
  return context;
108
108
  }
109
+ const HOST_BASEAGENTS = ['claude', 'codex', 'ecagent', 'gemini'];
110
+ /** Query the catalog for a host-level default connection, without Agent scope. */
111
+ export async function getBaseagentCatalog(baseagent, options = {}) {
112
+ if (!HOST_BASEAGENTS.includes(baseagent)) {
113
+ throw Object.assign(new Error(`Invalid baseagent: ${baseagent}. Valid: ${HOST_BASEAGENTS.join(', ')}`), { code: 'INVALID_ARGUMENT' });
114
+ }
115
+ const normalized = baseagent;
116
+ const snapshot = await getCatalog(undefined, normalized, options.refresh === true);
117
+ return { ...snapshot, baseagent: normalized };
118
+ }
109
119
  /** Used by the IPC handler and the owner process. Never calls IPC recursively. */
110
120
  export async function getLocalCatalog(self, ba, refresh = false) {
111
121
  return service.get(resolveCatalogContext(self, ba), refresh);
@@ -7,12 +7,15 @@ export function isModelAllowedByPatterns(model, allowedModels) {
7
7
  : model === pattern);
8
8
  }
9
9
  function normalizeBaseagent(baseagent) {
10
- return (baseagent || 'claude').trim() || 'claude';
10
+ const normalized = typeof baseagent === 'string' ? baseagent.trim() : '';
11
+ return normalized || undefined;
11
12
  }
12
13
  export function getRoleModelPermission(role, baseagent, selfAid) {
13
14
  const ba = normalizeBaseagent(baseagent);
14
15
  if (!role)
15
16
  return { allowOverride: false, constrained: true };
17
+ if (!ba)
18
+ return { allowOverride: false, constrained: true, allowedModels: [] };
16
19
  const selected = getFieldPermission(role, `baseagents.${ba}.model`, selfAid);
17
20
  if (!selected || !Array.isArray(selected.allowedModels)) {
18
21
  return { allowOverride: true, constrained: false };
@@ -8,7 +8,7 @@ import { createRootCausation, deriveCausation, normalizeCausation } from '../cau
8
8
  import { recordCausationSpan } from '../causation/audit.js';
9
9
  import { checkDangerousCommand } from './tool-policy.js';
10
10
  import { resolveProtectedCandidate } from './protected-paths.js';
11
- import { auditPermissionDecision } from '../auth/authorization-audit.js';
11
+ import { auditPermissionDecision, auditPermissionLifecycle } from '../auth/authorization-audit.js';
12
12
  export async function requestDangerousCommandPermission(gateway, sessionId, toolName, input, sendPrompt, context, grantScope = 'default', mode = 'request') {
13
13
  const dangerCheck = checkDangerousCommand(toolName, input);
14
14
  if (!dangerCheck.isDangerous) {
@@ -448,22 +448,40 @@ export class PermissionGateway {
448
448
  if (sessionGrant)
449
449
  this.addTemporaryGrant(pending, decisionCausation);
450
450
  pending.resolve(decision);
451
- if (!approved) {
452
- auditPermissionDecision({
453
- requestId,
454
- sessionId,
455
- toolName: pending.toolName,
456
- decisionSource: 'approval',
457
- policyCode: 'user_approval_denied',
458
- reason: 'owner approval denied',
459
- permissionMode: pending.permissionMode,
460
- agentAid: pending.selfAid,
461
- agentName: pending.agentName,
462
- role: pending.role,
463
- taskId: pending.taskId,
464
- summary: pending.auditSummary,
465
- });
466
- }
451
+ auditPermissionLifecycle({
452
+ phase: 'response',
453
+ requestId,
454
+ sessionId,
455
+ toolName: pending.toolName,
456
+ decision: approved ? 'allow' : 'deny',
457
+ decisionSource: 'approval',
458
+ policyCode: approved ? undefined : 'user_approval_denied',
459
+ reason: approved ? 'owner approval accepted' : 'owner approval denied',
460
+ permissionMode: pending.permissionMode,
461
+ agentAid: pending.selfAid,
462
+ agentName: pending.agentName,
463
+ role: pending.role,
464
+ taskId: pending.taskId,
465
+ summary: pending.auditSummary,
466
+ response: action,
467
+ });
468
+ auditPermissionLifecycle({
469
+ phase: 'final',
470
+ requestId,
471
+ sessionId,
472
+ toolName: pending.toolName,
473
+ decision: approved ? 'allow' : 'deny',
474
+ decisionSource: 'approval',
475
+ policyCode: approved ? undefined : 'user_approval_denied',
476
+ reason: approved ? 'owner approval accepted' : 'owner approval denied',
477
+ permissionMode: pending.permissionMode,
478
+ agentAid: pending.selfAid,
479
+ agentName: pending.agentName,
480
+ role: pending.role,
481
+ taskId: pending.taskId,
482
+ summary: pending.auditSummary,
483
+ response: action,
484
+ });
467
485
  void this.notifyPermissionState(pending, approved
468
486
  ? { state: 'approved', requestId, toolName: pending.toolName }
469
487
  : {
@@ -502,6 +520,23 @@ export class PermissionGateway {
502
520
  pending.resolve('deny');
503
521
  const decisionSource = 'infrastructure';
504
522
  const policyCode = cancelled ? 'approval_cancelled' : `approval_${reason}`;
523
+ auditPermissionLifecycle({
524
+ phase: 'response',
525
+ requestId,
526
+ sessionId,
527
+ toolName: pending.toolName,
528
+ decision: 'deny',
529
+ decisionSource,
530
+ policyCode,
531
+ reason,
532
+ permissionMode: pending.permissionMode,
533
+ agentAid: pending.selfAid,
534
+ agentName: pending.agentName,
535
+ role: pending.role,
536
+ taskId: pending.taskId,
537
+ summary: pending.auditSummary,
538
+ response: reason,
539
+ });
505
540
  auditPermissionDecision({
506
541
  requestId,
507
542
  sessionId,
@@ -565,7 +600,25 @@ export class PermissionGateway {
565
600
  const auditSummary = summarizeToolInputForAudit(challenge.toolName, challenge.toolInput).slice(0, 512);
566
601
  if (!approval || !interactionRouter) {
567
602
  await sendPrompt('当前会话权限不足,且没有可用的 owner 审批通道。');
603
+ auditPermissionLifecycle({
604
+ phase: 'response',
605
+ requestId: challenge.id,
606
+ sessionId,
607
+ toolName: challenge.toolName,
608
+ decision: 'deny',
609
+ decisionSource: 'infrastructure',
610
+ policyCode: 'approval_route_unavailable',
611
+ reason: 'owner approval routing context is unavailable',
612
+ permissionMode: context.permissionMode,
613
+ agentAid: context.selfAid,
614
+ agentName: context.agentName,
615
+ role: context.role,
616
+ taskId: context.taskId,
617
+ summary: auditSummary,
618
+ response: 'unavailable',
619
+ });
568
620
  auditPermissionDecision({
621
+ requestId: challenge.id,
569
622
  sessionId,
570
623
  toolName: challenge.toolName,
571
624
  decisionSource: 'infrastructure',
@@ -783,6 +836,23 @@ export class PermissionGateway {
783
836
  this.syncManagementRevalidationTimer();
784
837
  pending.resolve('deny');
785
838
  const decisionSource = reason === 'denied' ? 'approval' : 'infrastructure';
839
+ auditPermissionLifecycle({
840
+ phase: 'response',
841
+ requestId,
842
+ sessionId,
843
+ toolName: pending.toolName,
844
+ decision: 'deny',
845
+ decisionSource,
846
+ policyCode: `approval_${reason}`,
847
+ reason,
848
+ permissionMode: pending.permissionMode,
849
+ agentAid: pending.selfAid,
850
+ agentName: pending.agentName,
851
+ role: pending.role,
852
+ taskId: pending.taskId,
853
+ summary: pending.auditSummary,
854
+ response: reason,
855
+ });
786
856
  auditPermissionDecision({
787
857
  requestId,
788
858
  sessionId,
@@ -915,6 +985,20 @@ export class PermissionGateway {
915
985
  refs: { permissionRequestId: requestId, taskId: context.taskId, sessionId },
916
986
  });
917
987
  this.eventBus?.publish({ type: 'permission:requested', sessionId, requestId, toolName, input: displaySummary, causation: requestCausation });
988
+ auditPermissionLifecycle({
989
+ phase: 'request',
990
+ requestId,
991
+ sessionId,
992
+ toolName,
993
+ decision: 'pending',
994
+ decisionSource: 'approval',
995
+ permissionMode: context?.permissionMode,
996
+ agentAid: context?.selfAid,
997
+ agentName: context?.agentName,
998
+ role: context?.role,
999
+ taskId: context?.taskId,
1000
+ summary: auditSummary,
1001
+ });
918
1002
  const route = planApprovalRoute(challenge, context);
919
1003
  logger.info(`[PermissionGateway] approval_route request=${requestId} policy=${challenge.approverPolicy}`
920
1004
  + ` route=${route.kind}`
@@ -955,6 +1039,23 @@ export class PermissionGateway {
955
1039
  taskId: context.taskId,
956
1040
  summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
957
1041
  });
1042
+ auditPermissionLifecycle({
1043
+ phase: 'response',
1044
+ requestId,
1045
+ sessionId,
1046
+ toolName,
1047
+ decision: 'deny',
1048
+ decisionSource: 'infrastructure',
1049
+ policyCode: 'approval_route_unavailable',
1050
+ reason: route.reason,
1051
+ permissionMode: context.permissionMode,
1052
+ agentAid: context.selfAid,
1053
+ agentName: context.agentName,
1054
+ role: context.role,
1055
+ taskId: context.taskId,
1056
+ summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
1057
+ response: 'unavailable',
1058
+ });
958
1059
  return 'deny';
959
1060
  }
960
1061
  if (route.kind === 'handoff') {
@@ -1125,6 +1226,40 @@ export class PermissionGateway {
1125
1226
  pending.resolve(normalizedDecision === 'deny' ? 'deny' : 'allow');
1126
1227
  this.pending.delete(requestId);
1127
1228
  this.syncManagementRevalidationTimer();
1229
+ auditPermissionLifecycle({
1230
+ phase: 'response',
1231
+ requestId,
1232
+ sessionId,
1233
+ toolName: pending.toolName,
1234
+ decision: normalizedDecision === 'deny' ? 'deny' : 'allow',
1235
+ decisionSource: 'approval',
1236
+ policyCode: normalizedDecision === 'deny' ? 'user_approval_denied' : undefined,
1237
+ reason: normalizedDecision === 'deny' ? 'user approval denied' : 'user approval accepted',
1238
+ permissionMode: pending.permissionMode,
1239
+ agentAid: pending.selfAid,
1240
+ agentName: pending.agentName,
1241
+ role: pending.role,
1242
+ taskId: pending.taskId,
1243
+ summary: pending.auditSummary,
1244
+ response: normalizedDecision,
1245
+ });
1246
+ auditPermissionLifecycle({
1247
+ phase: 'final',
1248
+ requestId,
1249
+ sessionId,
1250
+ toolName: pending.toolName,
1251
+ decision: normalizedDecision === 'deny' ? 'deny' : 'allow',
1252
+ decisionSource: 'approval',
1253
+ policyCode: normalizedDecision === 'deny' ? 'user_approval_denied' : undefined,
1254
+ reason: normalizedDecision === 'deny' ? 'user approval denied' : 'user approval accepted',
1255
+ permissionMode: pending.permissionMode,
1256
+ agentAid: pending.selfAid,
1257
+ agentName: pending.agentName,
1258
+ role: pending.role,
1259
+ taskId: pending.taskId,
1260
+ summary: pending.auditSummary,
1261
+ response: normalizedDecision,
1262
+ });
1128
1263
  const decisionCausation = deriveCausation(pending.causation);
1129
1264
  if (normalizedDecision === 'always') {
1130
1265
  this.addTemporaryGrant(pending, decisionCausation);
@@ -1136,22 +1271,6 @@ export class PermissionGateway {
1136
1271
  });
1137
1272
  const decisionSource = 'approval';
1138
1273
  const policyCode = normalizedDecision === 'deny' ? 'user_approval_denied' : undefined;
1139
- if (normalizedDecision === 'deny') {
1140
- auditPermissionDecision({
1141
- requestId,
1142
- sessionId,
1143
- toolName: pending.toolName,
1144
- decisionSource,
1145
- policyCode: 'user_approval_denied',
1146
- reason: 'user approval denied',
1147
- permissionMode: pending.permissionMode,
1148
- agentAid: pending.selfAid,
1149
- agentName: pending.agentName,
1150
- role: pending.role,
1151
- taskId: pending.taskId,
1152
- summary: pending.auditSummary,
1153
- });
1154
- }
1155
1274
  this.eventBus?.publish({ type: 'permission:resolved', sessionId, requestId, toolName: pending.toolName, approved: normalizedDecision !== 'deny', ...(policyCode ? { decisionSource, policyCode } : { decisionSource }), causation: decisionCausation });
1156
1275
  return true;
1157
1276
  }
@@ -1215,6 +1334,23 @@ export class PermissionGateway {
1215
1334
  this.clearPendingResources(pending);
1216
1335
  pending.interactionRouter?.cancel(requestId, reason);
1217
1336
  pending.resolve('deny');
1337
+ auditPermissionLifecycle({
1338
+ phase: 'response',
1339
+ requestId,
1340
+ sessionId,
1341
+ toolName: pending.toolName,
1342
+ decision: 'deny',
1343
+ decisionSource: 'infrastructure',
1344
+ policyCode: `approval_${reason}`,
1345
+ reason,
1346
+ permissionMode: pending.permissionMode,
1347
+ agentAid: pending.selfAid,
1348
+ agentName: pending.agentName,
1349
+ role: pending.role,
1350
+ taskId: pending.taskId,
1351
+ summary: pending.auditSummary,
1352
+ response: reason,
1353
+ });
1218
1354
  auditPermissionDecision({
1219
1355
  requestId,
1220
1356
  sessionId,