evolcore 0.0.2 → 0.0.4

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 (206) hide show
  1. package/CHANGELOG.md +93 -778
  2. package/README.md +45 -10
  3. package/bin/ec-safe-output.js +89 -24
  4. package/dist/agents/baseagent.js +46 -0
  5. package/dist/agents/claude-runner.js +198 -48
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +203 -8
  8. package/dist/agents/runner-types.js +2 -2
  9. package/dist/aun/aid/agentmd.js +79 -0
  10. package/dist/aun/aid/encryption-seed-policy.js +29 -0
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/aid/store.js +2 -5
  13. package/dist/aun/outbox.js +8 -0
  14. package/dist/channels/aun.js +241 -116
  15. package/dist/channels/daemon.js +18 -4
  16. package/dist/channels/dingtalk.js +1005 -260
  17. package/dist/channels/feishu.js +184 -56
  18. package/dist/channels/qqbot.js +23 -1
  19. package/dist/channels/wechat.js +303 -155
  20. package/dist/channels/wecom.js +1132 -164
  21. package/dist/cli/agent-command.js +2 -1
  22. package/dist/cli/aun-commands.js +98 -43
  23. package/dist/cli/bench.js +2 -2
  24. package/dist/cli/contact.js +71 -0
  25. package/dist/cli/ctl-command.js +2 -2
  26. package/dist/cli/daemon-commands.js +77 -214
  27. package/dist/cli/handoff-command.js +4 -3
  28. package/dist/cli/help.js +9 -5
  29. package/dist/cli/index.js +132 -116
  30. package/dist/cli/init-channel.js +242 -129
  31. package/dist/cli/init.js +70 -31
  32. package/dist/cli/model.js +2 -1
  33. package/dist/cli/net-check.js +2 -2
  34. package/dist/cli/queue-command.js +30 -6
  35. package/dist/cli/raw-key-input.js +25 -0
  36. package/dist/cli/response.js +5 -6
  37. package/dist/cli/restart-monitor.js +25 -1
  38. package/dist/cli/stats.js +6 -4
  39. package/dist/cli/trigger-command.js +146 -25
  40. package/dist/cli/version.js +6 -1
  41. package/dist/config/access-policy-domain.js +38 -0
  42. package/dist/config/access-policy.js +134 -0
  43. package/dist/config/builtin-role-templates.js +42 -17
  44. package/dist/config/builtin-roles.js +31 -6
  45. package/dist/config/config-field-policy.js +17 -5
  46. package/dist/config/config-manager.js +409 -29
  47. package/dist/config/config-operation-service.js +35 -38
  48. package/dist/config/contact-alias.js +68 -0
  49. package/dist/config/contact-bind-code.js +274 -0
  50. package/dist/config/contact-book-store.js +622 -0
  51. package/dist/config/contact-book-v2-startup.js +35 -0
  52. package/dist/config/contact-book.js +185 -300
  53. package/dist/config/contact-operation-service.js +116 -0
  54. package/dist/config/contact-request-service.js +331 -0
  55. package/dist/config/peer-role-resolver.js +136 -55
  56. package/dist/config/role-ranks.js +18 -0
  57. package/dist/config/role-service.js +16 -19
  58. package/dist/config/role-store.js +16 -5
  59. package/dist/config/roles.js +10 -1
  60. package/dist/config/schema-registry.js +49 -24
  61. package/dist/config-store.js +14 -4
  62. package/dist/core/auth/agent-delegation.js +105 -11
  63. package/dist/core/auth/authorization-audit.js +16 -1
  64. package/dist/core/auth/operation-authorizer.js +10 -0
  65. package/dist/core/auth/operation-catalog.js +105 -1
  66. package/dist/core/auth/trigger-authorization.js +15 -0
  67. package/dist/core/bootstrap-service.js +2 -9
  68. package/dist/core/channel-loader.js +6 -2
  69. package/dist/core/command/command-handler.js +113 -21
  70. package/dist/core/command/connect-menu.js +599 -0
  71. package/dist/core/command/evol-menu-version-gate.js +38 -0
  72. package/dist/core/command/group-menu.js +421 -0
  73. package/dist/core/command/menu-handler.js +382 -100
  74. package/dist/core/command/menu-protocol.js +8 -2
  75. package/dist/core/command/menu-token-store.js +102 -0
  76. package/dist/core/command/role-menu.js +128 -29
  77. package/dist/core/command/slash-gate.js +1 -1
  78. package/dist/core/command/slash-handler.js +186 -49
  79. package/dist/core/daemon-file-cache.js +40 -6
  80. package/dist/core/event-catalog.js +99 -0
  81. package/dist/core/evolagent-registry.js +4 -40
  82. package/dist/core/evolagent.js +20 -9
  83. package/dist/core/handoff/runtime.js +162 -37
  84. package/dist/core/handoff/store.js +46 -2
  85. package/dist/core/handoff/types.js +1 -0
  86. package/dist/core/inference/text-inference.js +16 -74
  87. package/dist/core/message/file-markers.js +7 -0
  88. package/dist/core/message/im-renderer.js +20 -14
  89. package/dist/core/message/inbound-admission.js +124 -0
  90. package/dist/core/message/message-bridge.js +670 -57
  91. package/dist/core/message/message-log.js +1 -0
  92. package/dist/core/message/message-queue.js +195 -10
  93. package/dist/core/message/message-utils.js +8 -2
  94. package/dist/core/message/peer-mode.js +7 -8
  95. package/dist/core/message/response-engine.js +642 -96
  96. package/dist/core/message/send-receipt.js +24 -0
  97. package/dist/core/message/stream-debouncer.js +11 -2
  98. package/dist/core/permission/approval-gateway.js +17 -10
  99. package/dist/core/permission/ec-command-parser.js +101 -46
  100. package/dist/core/permission/tool-policy.js +115 -38
  101. package/dist/core/protected-paths.js +38 -11
  102. package/dist/core/session/session-fs-store.js +44 -3
  103. package/dist/core/session/session-manager.js +86 -8
  104. package/dist/core/session/session-mapper.js +2 -0
  105. package/dist/core/session/session-renew.js +125 -69
  106. package/dist/core/session/session-turn-coordinator.js +5 -1
  107. package/dist/eck/kit-renderer.js +12 -1
  108. package/dist/eck/message-renderer.js +79 -1
  109. package/dist/index.js +285 -94
  110. package/dist/ipc.js +114 -8
  111. package/dist/paths.js +3 -0
  112. package/dist/response-system/config-resolver.js +29 -0
  113. package/dist/response-system/coordinator.js +8 -32
  114. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  115. package/dist/response-system/index.js +1 -0
  116. package/dist/response-system/modes/single-session/index.js +7 -4
  117. package/dist/stats/billing.js +20 -8
  118. package/dist/trigger/manager.js +3 -0
  119. package/dist/trigger/parser.js +126 -11
  120. package/dist/trigger/patch.js +12 -2
  121. package/dist/trigger/scheduler.js +444 -40
  122. package/dist/trigger/validation.js +13 -0
  123. package/dist/utils/aid-bind.js +43 -29
  124. package/dist/utils/error-dict.json +7 -0
  125. package/dist/utils/evolcore-version.js +21 -0
  126. package/dist/utils/instance-registry.js +14 -7
  127. package/dist/utils/log-writer.js +46 -0
  128. package/dist/utils/logger.js +2 -2
  129. package/dist/utils/media-cache.js +4 -1
  130. package/dist/utils/model-prices.jsonl +6 -3
  131. package/dist/utils/restart-safety.js +31 -0
  132. package/dist/utils/stable-semver.js +21 -0
  133. package/dist/utils/stats.js +33 -9
  134. package/dist/utils/system-memory.js +62 -0
  135. package/kits/docs/INDEX.md +2 -1
  136. package/kits/docs/channels/aun.md +4 -13
  137. package/kits/docs/evolcore/INDEX.md +5 -3
  138. package/kits/docs/evolcore/agent.md +9 -1
  139. package/kits/docs/evolcore/aid.md +5 -2
  140. package/kits/docs/evolcore/config.md +47 -2
  141. package/kits/docs/evolcore/contact.md +61 -0
  142. package/kits/docs/evolcore/fs.md +9 -0
  143. package/kits/docs/evolcore/group-rules.md +46 -4
  144. package/kits/docs/evolcore/group.md +15 -6
  145. package/kits/docs/evolcore/model.md +4 -1
  146. package/kits/docs/evolcore/msg.md +12 -6
  147. package/kits/docs/evolcore/response.md +16 -21
  148. package/kits/docs/evolcore/rpc.md +2 -0
  149. package/kits/docs/evolcore/stats.md +15 -2
  150. package/kits/docs/evolcore/storage.md +1 -0
  151. package/kits/docs/evolcore/trigger.md +42 -10
  152. package/kits/docs/path-registry.md +36 -17
  153. package/kits/eck_manifest.json +12 -0
  154. package/kits/eck_message_manifest.json +12 -1
  155. package/kits/migrations/migrate-contact-book-v2.mjs +754 -0
  156. package/kits/rules/01-overview.md +17 -7
  157. package/kits/rules/02-navigation.md +38 -18
  158. package/kits/rules/03-identity.md +28 -24
  159. package/kits/rules/04-relation.md +44 -28
  160. package/kits/rules/05-venue.md +31 -15
  161. package/kits/rules/06-channel.md +11 -7
  162. package/kits/schemas/_meta.json +21 -7
  163. package/kits/schemas/agent-config.schema.6.json +322 -0
  164. package/kits/schemas/agent-config.schema.7.json +303 -0
  165. package/kits/schemas/agent-config.schema.8.json +304 -0
  166. package/kits/schemas/agent-config.schema.9.json +374 -0
  167. package/kits/schemas/contact-book.schema.2.json +43 -0
  168. package/kits/schemas/contact-book.schema.3.json +67 -0
  169. package/kits/schemas/daemon.schema.1.json +3 -2
  170. package/kits/schemas/daemon.schema.2.json +101 -0
  171. package/kits/schemas/daemon.schema.3.json +123 -0
  172. package/kits/schemas/defaults.schema.2.json +85 -0
  173. package/kits/schemas/defaults.schema.3.json +73 -0
  174. package/kits/schemas/relation-config.schema.5.json +47 -0
  175. package/kits/schemas/relation-config.schema.6.json +46 -0
  176. package/kits/schemas/relation-config.schema.7.json +59 -0
  177. package/kits/schemas/role-config.schema.1.json +1 -0
  178. package/kits/schemas/role-registry.schema.1.json +2 -2
  179. package/kits/schemas/single-session.schema.2.json +57 -0
  180. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  181. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  182. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  183. package/kits/templates/roles/admin.json +47 -0
  184. package/kits/templates/roles/member.json +5 -0
  185. package/kits/templates/roles/owner.json +1 -0
  186. package/kits/templates/roles/visitor.json +5 -0
  187. package/kits/templates/system-fragments/channel.md +12 -2
  188. package/kits/templates/system-fragments/commands.md +3 -1
  189. package/kits/templates/system-fragments/identity.md +3 -1
  190. package/kits/templates/system-fragments/relation.md +1 -1
  191. package/kits/templates/system-fragments/session.md +6 -2
  192. package/package.json +7 -5
  193. package/MIGRATION-0.5.0.md +0 -378
  194. package/ROLE_ACCESS_CONTROL.md +0 -174
  195. package/assets/brand/evolcore/README.md +0 -19
  196. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  197. package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
  198. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  199. package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
  200. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  201. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  202. package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
  203. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  204. package/assets/brand/evolcore/evolcore-logo.svg +0 -14
  205. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  206. package/assets/brand/evolcore/evolcore-mark.svg +0 -10
@@ -17,6 +17,8 @@ import { isRetryableError } from '../utils/error-utils.js';
17
17
  import { renderActionAsText } from '../core/interaction-router.js';
18
18
  import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
19
19
  import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
20
+ import { AGENT_DELEGATION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
21
+ import { classifyEvolcoreShellCommand, parseLiteralShellArgv } from '../core/permission/ec-command-parser.js';
20
22
  import { compareVersions } from '../utils/npm-ops.js';
21
23
  import { resolvePaths, resolveRoot } from '../paths.js';
22
24
  import { buildSessionTurnList } from '../core/session/session-turns.js';
@@ -111,6 +113,7 @@ const CODEX_CATALOG_FALLBACK = [
111
113
  { slug: 'gpt-5.3-codex', efforts: ['low', 'medium', 'high', 'xhigh'] },
112
114
  { slug: 'gpt-5.2', efforts: ['low', 'medium', 'high', 'xhigh'] },
113
115
  ];
116
+ const CODEX_DELEGATION_CARRIER_METADATA_KEY = '__codexDelegationCarrierV1';
114
117
  let codexCatalogCache = null;
115
118
  // The permission bridge is version-locked to the v2 schema audited below:
116
119
  // named profiles, approvalsReviewer, additional/network approval context, and
@@ -211,6 +214,7 @@ export class CodexRunner {
211
214
  threadExternalToolFingerprints = new Map();
212
215
  threadExternalToolBoundaries = new Map();
213
216
  trackedApprovalItems = new Map();
217
+ threadDelegationCarriers = new Map();
214
218
  pendingInterrupts = new Map();
215
219
  appServerClient = null;
216
220
  onSessionIdUpdate;
@@ -257,12 +261,98 @@ export class CodexRunner {
257
261
  }
258
262
  return this.appServerClient;
259
263
  }
264
+ createDelegationCarrier() {
265
+ return randomBytes(32).toString('base64url');
266
+ }
267
+ approvedEvolcoreCommandArgv(toolInput) {
268
+ const explicitArgv = Array.isArray(toolInput.commandArgv)
269
+ && toolInput.commandArgv.every(value => typeof value === 'string')
270
+ ? toolInput.commandArgv
271
+ : undefined;
272
+ if (explicitArgv?.[0] === 'ec')
273
+ return explicitArgv;
274
+ const command = typeof toolInput.command === 'string' ? toolInput.command : '';
275
+ const classification = classifyEvolcoreShellCommand(command);
276
+ if (classification.kind === 'bounded-output')
277
+ return classification.command.argv;
278
+ if (classification.kind !== 'literal')
279
+ return undefined;
280
+ const argv = parseLiteralShellArgv(command);
281
+ return argv?.[0] === 'ec' ? argv : undefined;
282
+ }
283
+ armApprovedDelegationCommand(sessionId, toolInput) {
284
+ const threadId = this.activeSessions.get(sessionId);
285
+ const carrierToken = threadId ? this.threadDelegationCarriers.get(threadId) : undefined;
286
+ const argv = this.approvedEvolcoreCommandArgv(toolInput);
287
+ if (!carrierToken || !argv)
288
+ return false;
289
+ const commandHash = hashDelegatedCommandArgv(argv);
290
+ const arm = this.permissionContexts.get(sessionId)?.armApprovedDelegationCommand;
291
+ if (!commandHash || !arm)
292
+ return false;
293
+ return arm(carrierToken, commandHash);
294
+ }
295
+ async readThreadDelegationCarrier(appServer, threadId) {
296
+ try {
297
+ const result = await appServer.threadShellCommand(threadId, `node -e "process.stdout.write(process.env.${AGENT_DELEGATION_TOKEN_ENV}||String())"`);
298
+ const carrier = result.output.trim();
299
+ if (result.exitCode !== undefined && result.exitCode !== 0)
300
+ return undefined;
301
+ return /^[A-Za-z0-9_-]{32,128}$/.test(carrier) ? carrier : undefined;
302
+ }
303
+ catch (error) {
304
+ logger.warn(`[CodexRunner] failed to recover delegation carrier for loaded thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
305
+ return undefined;
306
+ }
307
+ }
308
+ async loadPersistedDelegationCarrier(sessionManager, sessionId, threadId) {
309
+ if (typeof sessionManager?.getSessionById !== 'function')
310
+ return undefined;
311
+ try {
312
+ const session = await sessionManager.getSessionById(sessionId);
313
+ const persisted = session?.metadata?.[CODEX_DELEGATION_CARRIER_METADATA_KEY];
314
+ if (!persisted || typeof persisted !== 'object' || Array.isArray(persisted))
315
+ return undefined;
316
+ if (persisted.threadId !== threadId || typeof persisted.carrier !== 'string')
317
+ return undefined;
318
+ return /^[A-Za-z0-9_-]{32,128}$/.test(persisted.carrier)
319
+ ? persisted.carrier
320
+ : undefined;
321
+ }
322
+ catch (error) {
323
+ logger.debug(`[CodexRunner] failed to load persisted delegation carrier: session=${sessionId} error=${error}`);
324
+ return undefined;
325
+ }
326
+ }
327
+ async persistDelegationCarrier(sessionManager, sessionId, threadId, carrier) {
328
+ if (typeof sessionManager?.getSessionById !== 'function'
329
+ || typeof sessionManager?.updateSession !== 'function')
330
+ return;
331
+ try {
332
+ const session = await sessionManager.getSessionById(sessionId);
333
+ if (!session)
334
+ return;
335
+ const current = session.metadata?.[CODEX_DELEGATION_CARRIER_METADATA_KEY];
336
+ if (current?.threadId === threadId && current?.carrier === carrier)
337
+ return;
338
+ await sessionManager.updateSession(sessionId, {
339
+ metadata: {
340
+ ...(session.metadata ?? {}),
341
+ [CODEX_DELEGATION_CARRIER_METADATA_KEY]: { threadId, carrier },
342
+ },
343
+ });
344
+ }
345
+ catch (error) {
346
+ logger.warn(`[CodexRunner] failed to persist delegation carrier: session=${sessionId} thread=${threadId} error=${error}`);
347
+ }
348
+ }
260
349
  resetAppServerClient() {
261
350
  const client = this.appServerClient;
262
351
  this.appServerClient = null;
263
352
  this.threadExternalToolFingerprints.clear();
264
353
  this.threadExternalToolBoundaries.clear();
265
354
  this.trackedApprovalItems.clear();
355
+ this.threadDelegationCarriers.clear();
266
356
  client?.close().catch(error => {
267
357
  logger.debug(`[CodexRunner] Failed to close stale app-server client: ${error}`);
268
358
  });
@@ -656,6 +746,7 @@ export class CodexRunner {
656
746
  // ── Core: runQuery ──
657
747
  async runQuery(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
658
748
  let agentSessionId = initialAgentSessionId || this.activeSessions.get(sessionId);
749
+ const resumingThread = !!agentSessionId;
659
750
  const callModel = modelOverride?.model || this.model;
660
751
  const callEffort = modelOverride?.effort ?? this.effort;
661
752
  // per-call 权限模式:优先 override(message-processor 解析后传入),缺省回落实例级 currentMode。
@@ -674,13 +765,24 @@ export class CodexRunner {
674
765
  const externalToolConfig = await this.resolveExternalToolApprovalConfig(appServer, projectPath, capabilityConfig, callMode);
675
766
  const managedTempInstruction = this.managedTempInstruction(sessionId);
676
767
  const developerInstructions = [systemPromptAppend, managedTempInstruction].filter(Boolean).join('\n\n') || undefined;
768
+ let knownDelegationCarrier = agentSessionId
769
+ ? this.threadDelegationCarriers.get(agentSessionId)
770
+ : undefined;
771
+ if (agentSessionId && !knownDelegationCarrier) {
772
+ knownDelegationCarrier = await this.loadPersistedDelegationCarrier(sessionManager, sessionId, agentSessionId);
773
+ if (knownDelegationCarrier) {
774
+ this.threadDelegationCarriers.set(agentSessionId, knownDelegationCarrier);
775
+ }
776
+ }
777
+ const delegationCarrierCandidate = knownDelegationCarrier ?? this.createDelegationCarrier();
778
+ const hasTaskDelegation = typeof runtimeEnv?.[AGENT_DELEGATION_TOKEN_ENV] === 'string';
677
779
  const threadOptions = {
678
780
  model: callModel,
679
781
  effort: callEffort,
680
782
  approvalPolicy: effectiveApprovalPolicy,
681
783
  approvalsReviewer: 'user',
682
784
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
683
- config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId), this.buildLifecycleLockdownConfig()),
785
+ config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig()),
684
786
  ...(developerInstructions ? { developerInstructions } : {}),
685
787
  };
686
788
  logger.info(`[CodexRunner] runQuery permMode=${requestedPermissionMode}->${callMode} ` +
@@ -688,9 +790,39 @@ export class CodexRunner {
688
790
  const threadResponse = agentSessionId
689
791
  ? await appServer.threadResume(agentSessionId, projectPath, threadOptions)
690
792
  : await appServer.threadStart(projectPath, threadOptions);
691
- const threadId = threadResponse.thread?.id || agentSessionId;
793
+ let threadId = threadResponse.thread?.id || agentSessionId;
692
794
  if (!threadId)
693
795
  throw new Error('Codex app-server did not return a thread id');
796
+ if (!resumingThread) {
797
+ this.threadDelegationCarriers.set(threadId, delegationCarrierCandidate);
798
+ await this.persistDelegationCarrier(sessionManager, sessionId, threadId, delegationCarrierCandidate);
799
+ }
800
+ else if (hasTaskDelegation && !knownDelegationCarrier) {
801
+ let recoveredCarrier = await this.readThreadDelegationCarrier(appServer, threadId);
802
+ if (!recoveredCarrier) {
803
+ const staleThreadId = threadId;
804
+ logger.warn(`[CodexRunner] loaded thread has no recoverable delegation carrier; forking once to preserve history: thread=${staleThreadId}`);
805
+ const forked = await appServer.threadFork(staleThreadId, projectPath, undefined, {
806
+ model: callModel,
807
+ effort: callEffort,
808
+ approvalPolicy: effectiveApprovalPolicy,
809
+ approvalsReviewer: 'user',
810
+ ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
811
+ config: threadOptions.config,
812
+ });
813
+ const forkedThreadId = forked.thread?.id;
814
+ if (!forkedThreadId)
815
+ throw new Error('Codex delegation-carrier recovery fork did not return a thread id');
816
+ threadId = forkedThreadId;
817
+ recoveredCarrier = delegationCarrierCandidate;
818
+ }
819
+ this.threadDelegationCarriers.set(threadId, recoveredCarrier);
820
+ await this.persistDelegationCarrier(sessionManager, sessionId, threadId, recoveredCarrier);
821
+ }
822
+ else if (knownDelegationCarrier) {
823
+ this.threadDelegationCarriers.set(threadId, knownDelegationCarrier);
824
+ await this.persistDelegationCarrier(sessionManager, sessionId, threadId, knownDelegationCarrier);
825
+ }
694
826
  agentSessionId = threadId;
695
827
  this.activeSessions.set(sessionId, threadId);
696
828
  this.threadProjectPaths.set(threadId, path.resolve(projectPath));
@@ -704,11 +836,13 @@ export class CodexRunner {
704
836
  controller.signal.addEventListener('abort', () => queue.end(), { once: true });
705
837
  const state = {
706
838
  threadId,
839
+ model: callModel,
707
840
  streamedAgentMessageIds: new Set(),
708
841
  agentMessageDeltaText: new Map(),
709
842
  completedItemIds: new Set(),
710
843
  emittedEditCallIds: new Set(),
711
844
  completedTurnIds: new Set(),
845
+ openToolCalls: new Map(),
712
846
  };
713
847
  const unsubscribe = appServer.onNotification(notification => {
714
848
  // 仅从 turn/started 锁定权威 turnId — resume 时会有上一轮 turn 的残留通知
@@ -827,6 +961,7 @@ export class CodexRunner {
827
961
  const previousThreadId = this.activeSessions.get(sessionId);
828
962
  if (previousThreadId && previousThreadId !== agentSessionId) {
829
963
  this.threadProjectPaths.delete(previousThreadId);
964
+ this.threadDelegationCarriers.delete(previousThreadId);
830
965
  this.unbindExternalToolBoundary(previousThreadId);
831
966
  this.clearTrackedApprovalItemsForThread(previousThreadId);
832
967
  }
@@ -850,6 +985,7 @@ export class CodexRunner {
850
985
  this.cleanupSessionTempDir(sessionId);
851
986
  if (threadId) {
852
987
  this.threadProjectPaths.delete(threadId);
988
+ this.threadDelegationCarriers.delete(threadId);
853
989
  this.unbindExternalToolBoundary(threadId);
854
990
  }
855
991
  this.clearTrackedApprovalItemsForThread(threadId);
@@ -882,6 +1018,7 @@ export class CodexRunner {
882
1018
  this.chatModes.delete(sessionId);
883
1019
  this.cleanupSessionTempDir(sessionId);
884
1020
  this.threadProjectPaths.delete(threadId);
1021
+ this.threadDelegationCarriers.delete(threadId);
885
1022
  this.unbindExternalToolBoundary(threadId);
886
1023
  this.clearTrackedApprovalItemsForThread(threadId);
887
1024
  this.onSessionIdUpdate?.(sessionId, '');
@@ -904,15 +1041,18 @@ export class CodexRunner {
904
1041
  const executionSandbox = this.buildExecutionSandboxOptions(_sessionId, compactMode, _projectPath);
905
1042
  const capabilityConfig = await this.resolveCapabilityThreadConfig(_projectPath);
906
1043
  const externalToolConfig = await this.resolveExternalToolApprovalConfig(appServer, _projectPath, capabilityConfig, compactMode);
1044
+ const compactDelegationCarrier = this.threadDelegationCarriers.get(agentSessionId)
1045
+ ?? this.createDelegationCarrier();
907
1046
  await appServer.threadResume(agentSessionId, _projectPath, {
908
1047
  model: this.model,
909
1048
  effort: this.effort,
910
1049
  approvalPolicy: compactPolicy,
911
1050
  approvalsReviewer: 'user',
912
1051
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
913
- config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId), this.buildLifecycleLockdownConfig()),
1052
+ config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig()),
914
1053
  });
915
1054
  this.threadProjectPaths.set(agentSessionId, path.resolve(_projectPath));
1055
+ this.threadDelegationCarriers.set(agentSessionId, compactDelegationCarrier);
916
1056
  this.bindExternalToolBoundary(agentSessionId, externalToolConfig);
917
1057
  return await this.startAndWaitForCompact(appServer, agentSessionId);
918
1058
  }
@@ -1052,17 +1192,19 @@ export class CodexRunner {
1052
1192
  const capabilityConfig = await this.resolveCapabilityThreadConfig(projectPath);
1053
1193
  const appServer = this.getAppServerClient();
1054
1194
  const externalToolConfig = await this.resolveExternalToolApprovalConfig(appServer, projectPath, capabilityConfig, mode);
1195
+ const delegationCarrier = this.createDelegationCarrier();
1055
1196
  const response = await appServer.threadFork(agentSessionId, projectPath, title, {
1056
1197
  model: this.model,
1057
1198
  effort: this.effort,
1058
1199
  approvalPolicy: this.toApprovalPolicy(mode),
1059
1200
  approvalsReviewer: 'user',
1060
1201
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
1061
- config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey), this.buildLifecycleLockdownConfig()),
1202
+ config: this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig()),
1062
1203
  });
1063
1204
  const forkedThreadId = response.thread?.id;
1064
1205
  if (!forkedThreadId)
1065
1206
  throw new Error('Codex fork did not return a thread id');
1207
+ this.threadDelegationCarriers.set(forkedThreadId, delegationCarrier);
1066
1208
  this.threadProjectPaths.set(forkedThreadId, path.resolve(projectPath));
1067
1209
  this.bindExternalToolBoundary(forkedThreadId, externalToolConfig);
1068
1210
  return forkedThreadId;
@@ -1225,6 +1367,16 @@ export class CodexRunner {
1225
1367
  logger.info(`[CodexRunner] app-server approval request id=${request.id} method=${request.method} session=${sessionKey} mode=${sessionMode} tool=${toolName} summary=${summary}`);
1226
1368
  try {
1227
1369
  const decision = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd);
1370
+ if (decision !== 'deny'
1371
+ && (request.method === 'item/commandExecution/requestApproval' || request.method === 'execCommandApproval')
1372
+ && this.approvedEvolcoreCommandArgv(toolInput)) {
1373
+ if (!this.armApprovedDelegationCommand(sessionKey, toolInput)) {
1374
+ // Approval policy and delegation authority are separate boundaries.
1375
+ // If no ticket can be armed, the CLI still fails closed at IPC token
1376
+ // validation instead of changing an otherwise valid tool decision.
1377
+ logger.warn(`[CodexRunner] EC command approved without a task-bound delegation ticket: session=${sessionKey}`);
1378
+ }
1379
+ }
1228
1380
  const response = this.toAppServerApprovalResponse(request.method, decision, toolInput, unattended);
1229
1381
  logger.info(`[CodexRunner] app-server approval response id=${request.id} method=${request.method} decision=${decision} response=${JSON.stringify(response)}`);
1230
1382
  return response;
@@ -2083,7 +2235,10 @@ export class CodexRunner {
2083
2235
  break;
2084
2236
  }
2085
2237
  case 'item/started': {
2086
- yield* this.mapAppServerItemStarted(params.item, state);
2238
+ for (const event of this.mapAppServerItemStarted(params.item, state)) {
2239
+ this.trackAppServerToolLifecycle(event, state);
2240
+ yield event;
2241
+ }
2087
2242
  break;
2088
2243
  }
2089
2244
  case 'item/agentMessage/delta': {
@@ -2099,11 +2254,17 @@ export class CodexRunner {
2099
2254
  const item = params.item;
2100
2255
  if (item?.id)
2101
2256
  state.completedItemIds.add(item.id);
2102
- yield* this.mapAppServerItemCompleted(item, state);
2257
+ for (const event of this.mapAppServerItemCompleted(item, state)) {
2258
+ this.trackAppServerToolLifecycle(event, state);
2259
+ yield event;
2260
+ }
2103
2261
  break;
2104
2262
  }
2105
2263
  case 'item/fileChange/patchUpdated': {
2106
- yield* this.mapAppServerFileChangePatchUpdated(params, state);
2264
+ for (const event of this.mapAppServerFileChangePatchUpdated(params, state)) {
2265
+ this.trackAppServerToolLifecycle(event, state);
2266
+ yield event;
2267
+ }
2107
2268
  break;
2108
2269
  }
2109
2270
  case 'turn/plan/updated': {
@@ -2131,6 +2292,7 @@ export class CodexRunner {
2131
2292
  if (turnId)
2132
2293
  state.completedTurnIds.add(turnId);
2133
2294
  this.activeTurns.delete(sessionId);
2295
+ yield* this.reconcileOpenAppServerToolCalls(state, turnId);
2134
2296
  if (turn.status === 'failed' && turn.error?.message) {
2135
2297
  if (isRetryableError(new Error(turn.error.message))) {
2136
2298
  throw new Error(turn.error.message);
@@ -2154,6 +2316,36 @@ export class CodexRunner {
2154
2316
  }
2155
2317
  }
2156
2318
  }
2319
+ trackAppServerToolLifecycle(event, state) {
2320
+ if (event.type !== 'tool_use' && event.type !== 'tool_result')
2321
+ return;
2322
+ if (!event.callId)
2323
+ return;
2324
+ if (event.type === 'tool_use') {
2325
+ state.openToolCalls.set(event.callId, event.name);
2326
+ }
2327
+ else if (event.type === 'tool_result') {
2328
+ state.openToolCalls.delete(event.callId);
2329
+ }
2330
+ }
2331
+ *reconcileOpenAppServerToolCalls(state, turnId) {
2332
+ if (state.openToolCalls.size === 0)
2333
+ return;
2334
+ const callIds = [...state.openToolCalls.keys()];
2335
+ logger.warn(`[CodexRunner] Turn completed with ${callIds.length} open tool call(s): ` +
2336
+ `thread=${state.threadId} turn=${turnId || state.turnId || 'unknown'} calls=${callIds.join(',')}`);
2337
+ for (const [callId, name] of state.openToolCalls) {
2338
+ yield {
2339
+ type: 'tool_result',
2340
+ name,
2341
+ result: '',
2342
+ isError: true,
2343
+ error: 'Codex app-server ended the turn without an item/completed notification',
2344
+ callId,
2345
+ };
2346
+ }
2347
+ state.openToolCalls.clear();
2348
+ }
2157
2349
  *mapAppServerItemStarted(item, state) {
2158
2350
  if (!item)
2159
2351
  return;
@@ -2458,6 +2650,7 @@ export class CodexRunner {
2458
2650
  costUsd: this.pickNumber(turn.costUsd, turn.totalCostUsd, turn.total_cost_usd),
2459
2651
  sessionTitle: typeof turn.sessionTitle === 'string' ? turn.sessionTitle : typeof turn.session_title === 'string' ? turn.session_title : undefined,
2460
2652
  numTurns: this.pickNumber(turn.numTurns, turn.num_turns),
2653
+ model: state.model,
2461
2654
  tokenUsage,
2462
2655
  contextUsage,
2463
2656
  };
@@ -2567,13 +2760,14 @@ export class CodexRunner {
2567
2760
  return undefined;
2568
2761
  return '临时文件必须使用环境变量 `$TMPDIR`。它指向当前 EvolCore 会话的受管临时工作区;不要直接使用 `/tmp`,也不要使用其他会话的临时目录。';
2569
2762
  }
2570
- buildEvolcoreShellEnvironmentConfig(sessionId) {
2763
+ buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrier) {
2571
2764
  const managedTempDir = this.ensureSessionTempDir(sessionId);
2572
2765
  return {
2573
2766
  shell_environment_policy: {
2574
2767
  set: {
2575
2768
  EVOLCORE_SESSION_ID: sessionId,
2576
2769
  EVOLCORE_HOME: resolveRoot(),
2770
+ ...(delegationCarrier ? { [AGENT_DELEGATION_TOKEN_ENV]: delegationCarrier } : {}),
2577
2771
  ...(managedTempDir ? { TMPDIR: managedTempDir } : {}),
2578
2772
  },
2579
2773
  },
@@ -2592,6 +2786,7 @@ export class CodexRunner {
2592
2786
  this.threadExternalToolFingerprints.clear();
2593
2787
  this.threadExternalToolBoundaries.clear();
2594
2788
  this.trackedApprovalItems.clear();
2789
+ this.threadDelegationCarriers.clear();
2595
2790
  this.pendingInterrupts.clear();
2596
2791
  this.permissionContexts.clear();
2597
2792
  this.chatModes.clear();
@@ -69,7 +69,7 @@ export function isOneMillionContextModel(model) {
69
69
  export function realContextWindowForModel(model) {
70
70
  return isOneMillionContextModel(model) ? 1000000 : 200000;
71
71
  }
72
- /** autoCompact trigger threshold: 180000 for all models (conservative for cache limits). */
72
+ /** autoCompact trigger threshold: 350000 for all models. */
73
73
  export function autoCompactWindowForModel(_model) {
74
- return 180000;
74
+ return 350000;
75
75
  }
@@ -2,6 +2,85 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getAidStore, loadAid, SLOT } from './store.js';
4
4
  import { agentMdPath, aidLocalDir, resolveRoot } from '../../paths.js';
5
+ const displayNameCache = new Map();
6
+ const displayNamePending = new Map();
7
+ const displayNameRevision = new Map();
8
+ function normalizeAid(aid) {
9
+ const value = String(aid || '').trim();
10
+ return value.startsWith('@') ? value.slice(1) : value;
11
+ }
12
+ /** Extract the display name from an agent.md YAML frontmatter block. */
13
+ export function parseAgentDisplayName(content) {
14
+ const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
15
+ if (!frontmatter)
16
+ return undefined;
17
+ const name = frontmatter.match(/^name:\s*["']?(.+?)["']?\s*$/m)?.[1]?.trim();
18
+ return name || undefined;
19
+ }
20
+ function readLocalAgentDisplayName(aid) {
21
+ try {
22
+ return parseAgentDisplayName(fs.readFileSync(agentMdPath(aid), 'utf-8'));
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ /**
29
+ * Resolve an agent.md display name without blocking on the network.
30
+ * Local content is returned immediately; a missing value starts one shared
31
+ * background refresh and remains undefined until a later lookup.
32
+ */
33
+ export function resolveAgentDisplayName(aid) {
34
+ const normalizedAid = normalizeAid(aid);
35
+ if (!normalizedAid)
36
+ return undefined;
37
+ const cached = displayNameCache.get(normalizedAid);
38
+ if (cached)
39
+ return cached;
40
+ const localName = readLocalAgentDisplayName(normalizedAid);
41
+ if (localName) {
42
+ displayNameCache.set(normalizedAid, localName);
43
+ return localName;
44
+ }
45
+ void refreshAgentDisplayName(normalizedAid);
46
+ return undefined;
47
+ }
48
+ /** Fetch agent.md once and update the shared display-name cache. */
49
+ export function refreshAgentDisplayName(aid) {
50
+ const normalizedAid = normalizeAid(aid);
51
+ if (!normalizedAid)
52
+ return Promise.resolve(undefined);
53
+ const existing = displayNamePending.get(normalizedAid);
54
+ if (existing)
55
+ return existing;
56
+ const revision = displayNameRevision.get(normalizedAid) ?? 0;
57
+ let pending;
58
+ pending = agentmdGet(normalizedAid)
59
+ .then(content => {
60
+ const name = typeof content === 'string' ? parseAgentDisplayName(content) : undefined;
61
+ if ((displayNameRevision.get(normalizedAid) ?? 0) !== revision)
62
+ return undefined;
63
+ if (name)
64
+ displayNameCache.set(normalizedAid, name);
65
+ return name;
66
+ })
67
+ .catch(() => undefined)
68
+ .finally(() => {
69
+ if (displayNamePending.get(normalizedAid) === pending) {
70
+ displayNamePending.delete(normalizedAid);
71
+ }
72
+ });
73
+ displayNamePending.set(normalizedAid, pending);
74
+ return pending;
75
+ }
76
+ export function invalidateAgentDisplayName(aid) {
77
+ const normalizedAid = normalizeAid(aid);
78
+ if (!normalizedAid)
79
+ return;
80
+ displayNameCache.delete(normalizedAid);
81
+ displayNamePending.delete(normalizedAid);
82
+ displayNameRevision.set(normalizedAid, (displayNameRevision.get(normalizedAid) ?? 0) + 1);
83
+ }
5
84
  export function buildInitialAgentMd(opts) {
6
85
  const agentName = opts.aid.split('.')[0];
7
86
  const agentType = opts.type || 'ai';
@@ -0,0 +1,29 @@
1
+ export const AUN_KEYSTORE_ENCRYPTION_SEED = 'evol';
2
+ export class AunEncryptionSeedPolicyError extends Error {
3
+ code = 'CUSTOM_AUN_ENCRYPTION_SEED_FORBIDDEN';
4
+ constructor(sources) {
5
+ super(`检测到自定义 AUN encryptionSeed(来源:${sources.join('、')})。` +
6
+ 'AUN encryptionSeed 由 EvolCore 内置管理,不允许用户自行设置。' +
7
+ '请停止操作并联系管理人员处理后再启动 EvolCore。');
8
+ this.name = 'AunEncryptionSeedPolicyError';
9
+ }
10
+ }
11
+ /**
12
+ * Reject values that would make an existing AUN keystore incompatible with
13
+ * EvolCore's fixed application seed. Explicit legacy copies of the fixed value
14
+ * remain readable so users can remove the obsolete setting without migration.
15
+ */
16
+ export function assertAunEncryptionSeedPolicy(configuredValue, environmentValue) {
17
+ const customSources = [];
18
+ if (configuredValue !== undefined
19
+ && configuredValue !== null
20
+ && configuredValue !== AUN_KEYSTORE_ENCRYPTION_SEED) {
21
+ customSources.push('daemon.json aun.encryptionSeed');
22
+ }
23
+ if (environmentValue !== undefined
24
+ && environmentValue !== AUN_KEYSTORE_ENCRYPTION_SEED) {
25
+ customSources.push('环境变量 AUN_ENCRYPTION_SEED');
26
+ }
27
+ if (customSources.length > 0)
28
+ throw new AunEncryptionSeedPolicyError(customSources);
29
+ }
@@ -1,4 +1,4 @@
1
1
  export { isValidAid, aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, verifySignAbility, probePkiRecoverability, appendAidLifecycle, readAidLifecycle } from './identity.js';
2
- export { buildInitialAgentMd, agentmdGet, agentmdPut, agentmdSync, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterName } from './agentmd.js';
2
+ export { buildInitialAgentMd, agentmdGet, agentmdPut, agentmdSync, invalidateAgentDisplayName, parseAgentDisplayName, refreshAgentDisplayName, resolveAgentDisplayName, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterName, } from './agentmd.js';
3
3
  export { MIN_AUN_CORE_SDK, AUN_CORE_SDK_PKG, isAunSdkVersionOk, resolveAunCoreSdkPkg, ensureAunSdk, isAunSdkReady, downloadCaRoot, suppressSdkLogs, } from './client.js';
4
4
  export { getAidStore, loadClient, loadAid, AidLoadError, SLOT } from './store.js';
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { AUN_KEYSTORE_ENCRYPTION_SEED } from './encryption-seed-policy.js';
3
4
  /**
4
5
  * Slot 命名约定(基于 fastaun 0.4.3 隔离键语义)。
5
6
  *
@@ -39,16 +40,12 @@ export class AidLoadError extends Error {
39
40
  */
40
41
  export async function getAidStore(opts) {
41
42
  const { aunPath: defaultAunPath } = await import('../../paths.js');
42
- const { loadDaemonConfig } = await import('../../config-store.js');
43
43
  const { AIDStore } = await import('@agentunion/fastaun');
44
44
  const aunPath = opts.aunPath ?? defaultAunPath();
45
- const encryptionSeed = loadDaemonConfig().aun?.encryptionSeed
46
- ?? process.env.AUN_ENCRYPTION_SEED
47
- ?? 'evol';
48
45
  const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
49
46
  const storeOpts = {
50
47
  aunPath,
51
- encryptionSeed,
48
+ encryptionSeed: AUN_KEYSTORE_ENCRYPTION_SEED,
52
49
  slotId: opts.slotId,
53
50
  debug: opts.debug ?? false,
54
51
  };
@@ -158,3 +158,11 @@ export function hasPending(aid) {
158
158
  return false;
159
159
  }
160
160
  }
161
+ /**
162
+ * 当前 outbox 中待发送条目数。用于诊断发送管线堵塞:depth 持续增长说明
163
+ * message.send 出队速度跟不上入队速度(网关慢 / 链路抖动 / 卡片洪泛),
164
+ * 是命令「回复慢/无回复」的先行指标。
165
+ */
166
+ export function pendingCount(aid) {
167
+ return load(aid).length;
168
+ }