evolcore 0.0.19 → 0.0.21

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 (136) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +60 -9
  3. package/bin/install-codex-managed-hooks.mjs +61 -0
  4. package/dist/agents/baseagent.js +10 -6
  5. package/dist/agents/claude-runner.js +383 -111
  6. package/dist/agents/codex-app-server-client.js +10 -2
  7. package/dist/agents/codex-runner.js +405 -137
  8. package/dist/agents/ecagent-runner.js +174 -63
  9. package/dist/agents/gemini-runner.js +130 -30
  10. package/dist/agents/request-identity.js +25 -0
  11. package/dist/agents/runner-types.js +19 -0
  12. package/dist/aun/aid/agentmd.js +66 -2
  13. package/dist/aun/aid/identity.js +4 -1
  14. package/dist/aun/aid/index.js +1 -1
  15. package/dist/aun/msg/group.js +86 -9
  16. package/dist/aun/msg/history.js +213 -36
  17. package/dist/aun/msg/managed-operation.js +58 -9
  18. package/dist/aun/msg/p2p.js +26 -11
  19. package/dist/aun/outbox.js +295 -68
  20. package/dist/aun/service-proxy.js +43 -25
  21. package/dist/channels/aun.js +1004 -273
  22. package/dist/channels/daemon.js +6 -1
  23. package/dist/cli/agent-command.js +4 -3
  24. package/dist/cli/agent.js +66 -56
  25. package/dist/cli/aun-commands.js +177 -42
  26. package/dist/cli/command-log.js +10 -11
  27. package/dist/cli/contact.js +1 -0
  28. package/dist/cli/daemon-commands.js +81 -121
  29. package/dist/cli/index.js +1 -0
  30. package/dist/cli/init.js +76 -24
  31. package/dist/cli/restart-monitor.js +3 -3
  32. package/dist/cli/task-context.js +46 -0
  33. package/dist/cli/trigger-command.js +1 -1
  34. package/dist/cli/watch-logs.js +10 -3
  35. package/dist/config/builtin-roles.js +1 -0
  36. package/dist/config/config-field-policy.js +16 -5
  37. package/dist/config/config-manager.js +226 -24
  38. package/dist/config/config-operation-service.js +1 -2
  39. package/dist/config/contact-operation-service.js +32 -1
  40. package/dist/config/contact-request-service.js +44 -0
  41. package/dist/config/daemon-services.js +186 -0
  42. package/dist/config/gateway-config.js +29 -16
  43. package/dist/config/lifecycle.js +16 -5
  44. package/dist/config/role-service.js +54 -3
  45. package/dist/config/schema-migration.js +550 -0
  46. package/dist/config-store.js +161 -12
  47. package/dist/core/agent-application-service.js +279 -0
  48. package/dist/core/audit/log-integrity.js +102 -0
  49. package/dist/core/auth/agent-delegation.js +31 -1
  50. package/dist/core/auth/auth-gateway.js +33 -4
  51. package/dist/core/auth/authorization-audit.js +155 -4
  52. package/dist/core/auth/operation-authorizer.js +41 -1
  53. package/dist/core/auth/operation-catalog.js +9 -1
  54. package/dist/core/bootstrap-messages.js +2 -2
  55. package/dist/core/bootstrap-service.js +27 -38
  56. package/dist/core/causation/aun-association.js +7 -4
  57. package/dist/core/channel-loader.js +0 -2
  58. package/dist/core/command/agent-control.js +56 -16
  59. package/dist/core/command/command-handler.js +290 -44
  60. package/dist/core/command/connect-menu.js +3 -4
  61. package/dist/core/command/group-menu.js +5 -7
  62. package/dist/core/command/menu-handler.js +279 -80
  63. package/dist/core/command/role-menu.js +21 -11
  64. package/dist/core/command/slash-gate.js +85 -18
  65. package/dist/core/command/slash-handler.js +350 -32
  66. package/dist/core/event-catalog.js +5 -0
  67. package/dist/core/evolagent.js +9 -4
  68. package/dist/core/handoff/dispatcher.js +4 -0
  69. package/dist/core/handoff/runtime.js +10 -0
  70. package/dist/core/handoff/store.js +32 -9
  71. package/dist/core/inference/text-inference.js +7 -15
  72. package/dist/core/message/im-renderer.js +83 -84
  73. package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
  74. package/dist/core/message/message-bridge.js +124 -10
  75. package/dist/core/message/message-log.js +14 -7
  76. package/dist/core/message/message-queue.js +206 -16
  77. package/dist/core/message/message-utils.js +12 -5
  78. package/dist/core/message/response-engine.js +495 -72
  79. package/dist/core/message/send-receipt.js +1 -0
  80. package/dist/core/message/stream-debouncer.js +9 -2
  81. package/dist/core/model/model-catalog.js +23 -15
  82. package/dist/core/model/model-diagnostics.js +28 -10
  83. package/dist/core/permission/approval-gateway.js +180 -6
  84. package/dist/core/permission/ec-command-parser.js +465 -56
  85. package/dist/core/permission/mode.js +18 -3
  86. package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
  87. package/dist/core/permission/readonly-shell-query.js +263 -9
  88. package/dist/core/permission/sandbox-runtime.js +205 -13
  89. package/dist/core/permission/tool-policy.js +673 -62
  90. package/dist/core/relation/peer-identity.js +18 -0
  91. package/dist/core/session/session-fs-store.js +154 -5
  92. package/dist/core/session/session-manager.js +299 -30
  93. package/dist/core/session/session-renew.js +19 -12
  94. package/dist/core/session/session-turn-coordinator.js +11 -4
  95. package/dist/eck/kit-renderer.js +18 -9
  96. package/dist/index.js +283 -65
  97. package/dist/ipc.js +374 -24
  98. package/dist/paths.js +64 -7
  99. package/dist/response-system/context-builder.js +1 -7
  100. package/dist/trigger/anomaly-store.js +1 -0
  101. package/dist/trigger/feedback.js +56 -5
  102. package/dist/trigger/history.js +79 -4
  103. package/dist/trigger/legacy-session-history.js +2 -2
  104. package/dist/trigger/parser.js +3 -2
  105. package/dist/trigger/validation.js +6 -1
  106. package/dist/utils/atomic-write.js +27 -0
  107. package/dist/utils/ecweb-utils.js +16 -2
  108. package/dist/utils/error-utils.js +4 -1
  109. package/dist/utils/logger.js +21 -2
  110. package/dist/utils/process-tree-stats.js +24 -4
  111. package/dist/utils/process-tree-worker.js +31 -0
  112. package/dist/utils/project-path.js +1 -2
  113. package/dist/utils/stats.js +52 -18
  114. package/dist/utils/welcome.js +2 -2
  115. package/kits/docs/INDEX.md +1 -1
  116. package/kits/docs/evolcore/INDEX.md +1 -1
  117. package/kits/docs/evolcore/contact.md +7 -1
  118. package/kits/docs/evolcore/msg.md +16 -0
  119. package/kits/rules/01-overview.md +1 -1
  120. package/kits/rules/03-identity.md +1 -1
  121. package/kits/rules/05-venue.md +1 -1
  122. package/kits/schemas/_meta.json +10 -5
  123. package/kits/schemas/agent-config.schema.10.json +2 -1
  124. package/kits/schemas/agent-config.schema.11.json +421 -0
  125. package/kits/schemas/daemon.schema.5.json +135 -0
  126. package/kits/schemas/daemon.schema.6.json +131 -0
  127. package/kits/schemas/defaults.schema.5.json +119 -0
  128. package/kits/schemas/migrations/README.md +3 -1
  129. package/kits/schemas/relation-config.schema.8.json +13 -0
  130. package/kits/schemas/role-config.schema.1.json +1 -2
  131. package/kits/schemas/single-session.schema.3.json +32 -0
  132. package/kits/templates/roles/admin.json +1 -0
  133. package/kits/templates/roles/member.json +1 -0
  134. package/kits/templates/roles/visitor.json +1 -0
  135. package/package.json +7 -3
  136. package/skills/eclink/SKILL.md +2 -0
@@ -5,10 +5,11 @@
5
5
  * Implements the same interface surface as AgentRunner (claude-runner.ts)
6
6
  * so MessageProcessor and CommandHandler can work with it transparently.
7
7
  */
8
+ import { hasTrustedFullAccessContext } from './runner-types.js';
8
9
  import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
9
10
  import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../core/permission/tool-policy.js';
10
- import { normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
11
- import { buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtectedCandidate } from '../core/protected-paths.js';
11
+ import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
12
+ import { buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtectedCandidate } from '../core/permission/protected-paths.js';
12
13
  import { CodexAppServerClient } from './codex-app-server-client.js';
13
14
  import { resolveOpenaiConfig } from './baseagent.js';
14
15
  import { logger } from '../utils/logger.js';
@@ -16,14 +17,14 @@ import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
16
17
  import { auditCodexApprovalDecision, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
17
18
  import { isRetryableError } from '../utils/error-utils.js';
18
19
  import { renderActionAsText } from '../core/interaction-router.js';
19
- import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
20
+ import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from '../core/message/message-utils.js';
20
21
  import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
21
22
  import { AGENT_DELEGATION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
22
23
  import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
23
- import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, parseLiteralShellArgv, resolveCodexShellCarrierArgv, } from '../core/permission/ec-command-parser.js';
24
+ import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, hasCodexCmdCarrierEcIntent, parseCodexToolCommand, parseLiteralShellCommand, parseCodexShellCommandArgv, resolveCodexShellCarrierArgv, } from '../core/permission/ec-command-parser.js';
24
25
  import { compareVersions } from '../utils/npm-ops.js';
25
26
  import { resolvePaths, resolveRoot } from '../paths.js';
26
- import { ensureProcessManagedTempDir } from '../cli/task-context.js';
27
+ import { ensureProcessManagedTempDir, getManagedTaskTempDir } from '../cli/task-context.js';
27
28
  import { buildSessionTurnList } from '../core/session/session-turns.js';
28
29
  import { execFileSync } from 'child_process';
29
30
  import { execCodexCliSync, resolveCodexCliPath } from '../utils/codex-cli.js';
@@ -39,6 +40,13 @@ const MIME_EXT = {
39
40
  'image/gif': '.gif',
40
41
  'image/webp': '.webp',
41
42
  };
43
+ const APPROVAL_LOG_SUMMARY_MAX_LENGTH = 256;
44
+ export function formatApprovalLogSummary(summary) {
45
+ const singleLine = summary.replace(/\s+/gu, ' ').trim();
46
+ if (singleLine.length <= APPROVAL_LOG_SUMMARY_MAX_LENGTH)
47
+ return singleLine;
48
+ return singleLine.slice(0, APPROVAL_LOG_SUMMARY_MAX_LENGTH - 3).trimEnd() + '...';
49
+ }
42
50
  class AsyncEventQueue {
43
51
  queue = [];
44
52
  done = false;
@@ -267,16 +275,21 @@ export class CodexRunner {
267
275
  permissionContexts = new Map();
268
276
  resolvedConfig;
269
277
  pendingInterruptTtlMs = 30_000;
270
- constructor(config, callbacks) {
271
- this.resolvedConfig = resolveOpenaiConfig(config);
278
+ constructor(config, callbacks, runtime) {
279
+ // Compatibility for legacy direct callers that still pass the removed
280
+ // internal fields in a synthetic config. AgentLoader uses runtime binding.
281
+ const legacy = config.agents?.codex;
282
+ const binding = {
283
+ agentAid: runtime?.agentAid ?? legacy?.['evolcoreAgentAid'],
284
+ agentConfig: runtime?.agentConfig ?? legacy?.['evolcoreAgentConfig'],
285
+ };
286
+ this.resolvedConfig = resolveOpenaiConfig(config, undefined, binding);
272
287
  this.resolvedConfig.headers = buildModelRequestHeaders({
273
288
  baseagent: 'codex',
274
289
  baseUrl: this.resolvedConfig.baseUrl,
275
- agentAid: config.agents?.codex?.evolcoreAgentAid,
290
+ agentAid: this.resolvedConfig.agentAid,
276
291
  configuredHeaders: this.resolvedConfig.headers,
277
292
  });
278
- this.resolvedConfig.evolcoreAgentAid = config.agents?.codex?.evolcoreAgentAid;
279
- this.resolvedConfig.evolcoreAgentConfig = config.agents?.codex?.evolcoreAgentConfig;
280
293
  this.capabilities = {
281
294
  clear: false,
282
295
  compact: true,
@@ -288,6 +301,7 @@ export class CodexRunner {
288
301
  planApproval: false,
289
302
  // Current file rewind is intentionally degraded: it restores touched files from Git HEAD.
290
303
  fileRewind: 'git-head',
304
+ fullaccess: true,
291
305
  };
292
306
  this.model = this.resolvedConfig.model;
293
307
  if (this.resolvedConfig.effort)
@@ -316,7 +330,7 @@ export class CodexRunner {
316
330
  createDelegationCarrier() {
317
331
  return randomBytes(32).toString('base64url');
318
332
  }
319
- approvedLiteralEvolcoreCommandArgv(command, managedTempDir, dialect = 'posix') {
333
+ approvedLiteralEvolcoreCommand(command, managedTempDir, dialect = 'posix') {
320
334
  const parseOptions = {
321
335
  allowManagedTmpDir: true,
322
336
  dialect,
@@ -324,47 +338,100 @@ export class CodexRunner {
324
338
  };
325
339
  const classification = classifyEvolcoreShellCommand(command, parseOptions);
326
340
  if (classification.kind === 'bounded-output')
327
- return classification.command.argv;
328
- if (classification.kind !== 'literal')
329
- return undefined;
341
+ return { argv: classification.command.argv };
330
342
  // Keep the approval/delegation parser aligned with the preflight parser:
331
343
  // the session-managed `$TMPDIR` token is a permitted EC path reference.
332
- const argv = parseLiteralShellArgv(command, parseOptions);
333
- if (argv && containsLiteralManagedTmpDirOutsideSendContent(argv))
334
- return undefined;
335
- return argv?.[0] === 'ec' ? argv : undefined;
336
- }
337
- approvedEvolcoreCommandArgv(toolInput, managedTempDir) {
344
+ const parsed = parseLiteralShellCommand(command, parseOptions);
345
+ if (!parsed.ok)
346
+ return { parseFailure: parsed };
347
+ if (parsed.argv[0] !== 'ec')
348
+ return {};
349
+ if (containsLiteralManagedTmpDirOutsideSendContent(parsed.argv)) {
350
+ return { parseFailure: {
351
+ issue: 'unsafe-expansion',
352
+ offset: 0,
353
+ tokenIndex: 0,
354
+ dialect: dialect ?? 'posix',
355
+ inputLength: command.length,
356
+ } };
357
+ }
358
+ return { argv: parsed.argv };
359
+ }
360
+ resolveCanonicalEcApproval(toolInput, managedTempDir) {
338
361
  const explicitArgv = Array.isArray(toolInput.commandArgv)
339
362
  && toolInput.commandArgv.every(value => typeof value === 'string')
340
363
  ? toolInput.commandArgv
341
364
  : undefined;
342
365
  if (explicitArgv?.[0] === 'ec') {
343
- return explicitArgv.some(value => value.includes('\0'))
344
- || containsLiteralManagedTmpDirOutsideSendContent(explicitArgv)
345
- ? undefined
346
- : explicitArgv;
366
+ if (explicitArgv.some(value => value.includes('\0'))) {
367
+ return { parseFailure: {
368
+ issue: 'invalid-control-char', offset: 0,
369
+ tokenIndex: explicitArgv.findIndex(value => value.includes('\0')),
370
+ dialect: 'posix', inputLength: 0,
371
+ } };
372
+ }
373
+ return containsLiteralManagedTmpDirOutsideSendContent(explicitArgv)
374
+ ? { parseFailure: { issue: 'unsafe-expansion', offset: 0, tokenIndex: 0, dialect: 'posix', inputLength: 0 } }
375
+ : { argv: explicitArgv };
347
376
  }
348
377
  const explicitCarrier = explicitArgv
349
378
  ? resolveCodexShellCarrierArgv(explicitArgv)
350
379
  : undefined;
351
380
  if (explicitCarrier !== undefined) {
352
- return this.approvedLiteralEvolcoreCommandArgv(explicitCarrier.command, managedTempDir, explicitCarrier.dialect);
381
+ return this.approvedLiteralEvolcoreCommand(explicitCarrier.command, managedTempDir, explicitCarrier.dialect);
382
+ }
383
+ if (hasCodexCmdCarrierEcIntent(toolInput)) {
384
+ const parsed = parseCodexToolCommand(toolInput);
385
+ if (!parsed.ok)
386
+ return { parseFailure: parsed };
387
+ if (parsed.argv[0] === 'ec') {
388
+ return containsLiteralManagedTmpDirOutsideSendContent(parsed.argv)
389
+ ? { parseFailure: {
390
+ issue: 'unsafe-expansion', offset: 0, tokenIndex: 0,
391
+ dialect: 'cmd', inputLength: 0,
392
+ } }
393
+ : { argv: parsed.argv };
394
+ }
395
+ const command = typeof toolInput.command === 'string' ? toolInput.command : '';
396
+ const classification = classifyEvolcoreShellCommand(command, { dialect: 'cmd' });
397
+ return {
398
+ parseFailure: {
399
+ issue: classification.kind === 'composite' ? classification.issue : 'shell-composition',
400
+ offset: 0,
401
+ tokenIndex: 0,
402
+ dialect: 'cmd',
403
+ inputLength: command.length,
404
+ },
405
+ };
353
406
  }
354
407
  const command = typeof toolInput.command === 'string' ? toolInput.command : '';
355
- const directArgv = this.approvedLiteralEvolcoreCommandArgv(command, managedTempDir);
356
- if (directArgv)
357
- return directArgv;
358
- const wrappedArgv = parseLiteralShellArgv(command, {
408
+ const canonicalCommand = parseCodexShellCommandArgv(command, {
359
409
  allowManagedTmpDir: true,
360
410
  ...(managedTempDir ? { managedTempDir } : {}),
361
411
  });
362
- const shellCarrier = wrappedArgv
363
- ? resolveCodexShellCarrierArgv(wrappedArgv)
364
- : undefined;
365
- return shellCarrier === undefined
366
- ? undefined
367
- : this.approvedLiteralEvolcoreCommandArgv(shellCarrier.command, managedTempDir, shellCarrier.dialect);
412
+ if (canonicalCommand?.[0] === 'ec') {
413
+ return containsLiteralManagedTmpDirOutsideSendContent(canonicalCommand)
414
+ ? { parseFailure: {
415
+ issue: 'unsafe-expansion',
416
+ offset: 0,
417
+ tokenIndex: 0,
418
+ dialect: 'posix',
419
+ inputLength: command.length,
420
+ } }
421
+ : { argv: canonicalCommand };
422
+ }
423
+ const outer = parseLiteralShellCommand(command, {
424
+ allowManagedTmpDir: true,
425
+ ...(managedTempDir ? { managedTempDir } : {}),
426
+ });
427
+ if (!outer.ok) {
428
+ const classification = classifyEvolcoreShellCommand(command, {
429
+ allowManagedTmpDir: true,
430
+ ...(managedTempDir ? { managedTempDir } : {}),
431
+ });
432
+ return classification.kind === 'composite' ? { parseFailure: outer } : {};
433
+ }
434
+ return {};
368
435
  }
369
436
  isManagedEvolcoreCommandIntent(toolInput) {
370
437
  if (Array.isArray(toolInput.commandArgv)) {
@@ -374,10 +441,11 @@ export class CodexRunner {
374
441
  if (!rawArgv.every(value => typeof value === 'string'))
375
442
  return false;
376
443
  const shellCarrier = resolveCodexShellCarrierArgv(rawArgv);
377
- return shellCarrier !== undefined
378
- && classifyEvolcoreShellCommand(shellCarrier.command, {
379
- dialect: shellCarrier.dialect,
380
- }).kind !== 'none';
444
+ return hasCodexCmdCarrierEcIntent(toolInput)
445
+ || shellCarrier !== undefined
446
+ && classifyEvolcoreShellCommand(shellCarrier.command, {
447
+ dialect: shellCarrier.dialect,
448
+ }).kind !== 'none';
381
449
  }
382
450
  const command = typeof toolInput.command === 'string' ? toolInput.command : '';
383
451
  return classifyEvolcoreShellCommand(command).kind !== 'none';
@@ -430,8 +498,7 @@ export class CodexRunner {
430
498
  }
431
499
  }
432
500
  async persistDelegationCarrier(sessionManager, sessionId, threadId, carrier) {
433
- if (typeof sessionManager?.getSessionById !== 'function'
434
- || typeof sessionManager?.updateSession !== 'function')
501
+ if (typeof sessionManager?.getSessionById !== 'function')
435
502
  return;
436
503
  try {
437
504
  const session = await sessionManager.getSessionById(sessionId);
@@ -440,12 +507,16 @@ export class CodexRunner {
440
507
  const current = session.metadata?.[CODEX_DELEGATION_CARRIER_METADATA_KEY];
441
508
  if (current?.threadId === threadId && current?.carrier === carrier)
442
509
  return;
443
- await sessionManager.updateSession(sessionId, {
444
- metadata: {
445
- ...(session.metadata ?? {}),
510
+ if (typeof sessionManager.patchSessionMetadata === 'function') {
511
+ await sessionManager.patchSessionMetadata(sessionId, {
446
512
  [CODEX_DELEGATION_CARRIER_METADATA_KEY]: { threadId, carrier },
447
- },
448
- });
513
+ });
514
+ }
515
+ else if (typeof sessionManager.updateSession === 'function') {
516
+ await sessionManager.updateSession(sessionId, {
517
+ metadata: { [CODEX_DELEGATION_CARRIER_METADATA_KEY]: { threadId, carrier } },
518
+ });
519
+ }
449
520
  }
450
521
  catch (error) {
451
522
  logger.warn(`[CodexRunner] failed to persist delegation carrier: session=${sessionId} thread=${threadId} error=${error}`);
@@ -485,7 +556,7 @@ export class CodexRunner {
485
556
  return Object.keys(merged).length > 0 ? merged : null;
486
557
  }
487
558
  async resolveCapabilityThreadConfig(projectPath) {
488
- const agentConfig = this.resolvedConfig.evolcoreAgentConfig;
559
+ const agentConfig = this.resolvedConfig.agentConfig;
489
560
  if (!agentConfig) {
490
561
  return {
491
562
  skills: { config: [{ name: 'eclink', enabled: false }] },
@@ -732,6 +803,8 @@ export class CodexRunner {
732
803
  chatModes = new Map();
733
804
  /** 将权限模式映射为 Codex app-server 的 approvalPolicy(纯函数,无副作用,供 per-call 派生用)。 */
734
805
  toApprovalPolicy(mode) {
806
+ if (mode === 'fullaccess')
807
+ return 'never';
735
808
  // `untrusted` guarantees that commands outside Codex's trusted read-only
736
809
  // set reach the EvolCore bridge for EvolCore's per-mode decision.
737
810
  void mode;
@@ -814,6 +887,12 @@ export class CodexRunner {
814
887
  filesystem: {
815
888
  ...buildCodexProtectedFilesystemRules(resolveRoot(), {
816
889
  denyLClassRead: mode === 'readonly',
890
+ // The app-server itself is launched behind EvolCore's Linux
891
+ // H-class guard, which already mounts every L-class directory
892
+ // read-only. Avoid Codex's per-file glob expansion inside that
893
+ // nested mount namespace; it can fail while creating a missing
894
+ // target such as triggers/history.jsonl.
895
+ expandLClassReadDeny: process.platform !== 'linux',
817
896
  }),
818
897
  ...this.resolveGitMetadataWriteRules(projectPath, mode),
819
898
  ...(bootstrapAgentMdPath ? {
@@ -841,14 +920,17 @@ export class CodexRunner {
841
920
  sandbox: decision.state === 'off' ? 'danger-full-access' : undefined,
842
921
  };
843
922
  }
844
- buildLifecycleLockdownConfig() {
923
+ isTrustedFullAccessSession(sessionKey) {
924
+ return hasTrustedFullAccessContext(this.permissionContexts.get(sessionKey));
925
+ }
926
+ buildLifecycleLockdownConfig(permissionMode) {
845
927
  return {
846
928
  features: {
847
929
  // The app-server is launched with allow_managed_hooks_only=true, so
848
930
  // enabling the feature exposes only EvolCore's requirements-managed
849
931
  // PreToolUse hook. Disabling it here suppresses that managed hook too
850
932
  // and bypasses the pause/proactive policy boundary entirely.
851
- hooks: true,
933
+ hooks: permissionMode !== 'fullaccess',
852
934
  },
853
935
  };
854
936
  }
@@ -895,6 +977,11 @@ export class CodexRunner {
895
977
  return { ok: true, applicable: true, decision: 'deny', reason: '工具调用已取消' };
896
978
  }
897
979
  }
980
+ if ((this.chatModes.get(sessionKey) ?? this.currentMode) === 'fullaccess') {
981
+ return this.isTrustedFullAccessSession(sessionKey)
982
+ ? { ok: true, applicable: true, decision: 'allow' }
983
+ : { ok: false, applicable: true, decision: 'deny', reason: 'Untrusted fullaccess execution context' };
984
+ }
898
985
  // A tracked interactive thread still needs an authenticated, explicit
899
986
  // allow so the managed hook can fail closed when no runner matches.
900
987
  if (!context.preToolUsePolicyHook)
@@ -922,6 +1009,7 @@ export class CodexRunner {
922
1009
  this.activeStreams.delete(key);
923
1010
  this.activeAbortControllers.delete(key);
924
1011
  this.pendingInterrupts.delete(key);
1012
+ this.chatModes.delete(key);
925
1013
  const release = this.activeOperationReleases.get(key);
926
1014
  if (release) {
927
1015
  this.activeOperationReleases.delete(key);
@@ -1032,8 +1120,22 @@ export class CodexRunner {
1032
1120
  }
1033
1121
  }
1034
1122
  // ── Core: runQuery ──
1123
+ cachedThreadForTopic(sessionId, agentSessionId) {
1124
+ if (!agentSessionId)
1125
+ return undefined;
1126
+ return this.activeSessions.get(sessionId) === agentSessionId ? agentSessionId : undefined;
1127
+ }
1035
1128
  async runQuery(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
1036
- const knownThreadId = initialAgentSessionId || this.activeSessions.get(sessionId);
1129
+ const topicBinding = modelOverride?.backend?.kind === 'topic';
1130
+ if (topicBinding && (!modelOverride?.turn || modelOverride.turn.sessionId !== sessionId || !this.onSessionIdUpdate)) {
1131
+ const error = new Error('topic backend run requires an active TurnLease and binding callback');
1132
+ error.code = 'TOPIC_TURN_LEASE_REQUIRED';
1133
+ throw error;
1134
+ }
1135
+ const topicAgentSessionId = topicBinding ? modelOverride?.backend?.agentSessionId ?? null : undefined;
1136
+ const knownThreadId = topicBinding
1137
+ ? this.cachedThreadForTopic(sessionId, topicAgentSessionId)
1138
+ : initialAgentSessionId || this.activeSessions.get(sessionId);
1037
1139
  const operationKey = this.threadOperationKey(knownThreadId, sessionId);
1038
1140
  const release = await this.acquireThreadOperation(operationKey, 'thread/resume+turn/start');
1039
1141
  try {
@@ -1047,7 +1149,14 @@ export class CodexRunner {
1047
1149
  }
1048
1150
  }
1049
1151
  async runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
1050
- let agentSessionId = initialAgentSessionId || this.activeSessions.get(sessionId);
1152
+ // A daemon-managed task must never fall back to the app-server's
1153
+ // process-wide TMPDIR if its session namespace disappeared.
1154
+ getManagedTaskTempDir(runtimeEnv);
1155
+ const topicBinding = modelOverride?.backend?.kind === 'topic';
1156
+ const topicAgentSessionId = topicBinding ? modelOverride?.backend?.agentSessionId ?? null : undefined;
1157
+ let agentSessionId = topicBinding
1158
+ ? (topicAgentSessionId || undefined)
1159
+ : initialAgentSessionId || this.activeSessions.get(sessionId);
1051
1160
  const resumingThread = !!agentSessionId;
1052
1161
  const callModel = modelOverride?.model || this.model;
1053
1162
  const callEffort = modelOverride?.effortMode === 'model_default'
@@ -1057,8 +1166,10 @@ export class CodexRunner {
1057
1166
  // 写入 chatModes 供异步审批回调按 sessionKey 读取,并据此派生本次 thread 的 approvalPolicy/sandbox,
1058
1167
  // 不依赖共享的 this.approvalPolicy/this.sandboxMode(多会话并发互不污染)。
1059
1168
  const requestedPermissionMode = modelOverride?.permissionMode || this.currentMode;
1060
- const normalizedPermission = normalizePermissionMode(requestedPermissionMode);
1061
- const callMode = normalizedPermission.mode;
1169
+ const callMode = normalizeExecutionPermissionMode(requestedPermissionMode);
1170
+ if (callMode === 'fullaccess' && !hasTrustedFullAccessContext(this.permissionContexts.get(sessionId))) {
1171
+ throw new Error('Codex fullaccess requires a trusted per-call execution authorization');
1172
+ }
1062
1173
  this.chatModes.set(sessionId, callMode);
1063
1174
  const callApprovalPolicy = this.toApprovalPolicy(callMode);
1064
1175
  const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath);
@@ -1066,8 +1177,11 @@ export class CodexRunner {
1066
1177
  this.dropExpiredPendingInterrupt(sessionId);
1067
1178
  const effectiveApprovalPolicy = callApprovalPolicy;
1068
1179
  const capabilityConfig = await this.resolveCapabilityThreadConfig(projectPath);
1069
- const externalToolConfig = await this.resolveExternalToolApprovalConfig(appServer, projectPath, capabilityConfig, callMode);
1070
- await this.assertProactiveHookAvailability(sessionId, appServer);
1180
+ const externalToolConfig = callMode === 'fullaccess'
1181
+ ? {}
1182
+ : await this.resolveExternalToolApprovalConfig(appServer, projectPath, capabilityConfig, callMode);
1183
+ if (callMode !== 'fullaccess')
1184
+ await this.assertProactiveHookAvailability(sessionId, appServer);
1071
1185
  const managedTempInstruction = this.managedTempInstruction(sessionId);
1072
1186
  const developerInstructions = [systemPromptAppend, managedTempInstruction].filter(Boolean).join('\n\n') || undefined;
1073
1187
  let knownDelegationCarrier = agentSessionId
@@ -1075,11 +1189,12 @@ export class CodexRunner {
1075
1189
  : undefined;
1076
1190
  if (agentSessionId && !knownDelegationCarrier) {
1077
1191
  knownDelegationCarrier = await this.loadPersistedDelegationCarrier(sessionManager, sessionId, agentSessionId);
1078
- if (knownDelegationCarrier) {
1079
- this.threadDelegationCarriers.set(agentSessionId, knownDelegationCarrier);
1080
- }
1081
1192
  }
1082
1193
  const delegationCarrierCandidate = knownDelegationCarrier ?? this.createDelegationCarrier();
1194
+ // Keep newly discovered carrier state local until generation-aware
1195
+ // activation accepts the backend. A stale run must not publish a carrier
1196
+ // that a later generation could mistake for its own thread.
1197
+ let carrierToPublish;
1083
1198
  const hasTaskDelegation = typeof runtimeEnv?.[AGENT_DELEGATION_TOKEN_ENV] === 'string';
1084
1199
  const threadOptions = {
1085
1200
  model: callModel,
@@ -1087,7 +1202,7 @@ export class CodexRunner {
1087
1202
  approvalPolicy: effectiveApprovalPolicy,
1088
1203
  approvalsReviewer: 'user',
1089
1204
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
1090
- config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig()), callMode),
1205
+ config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, runtimeEnv ? { shell_environment_policy: { set: runtimeEnv } } : undefined, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionId, delegationCarrierCandidate), this.buildLifecycleLockdownConfig(callMode)), callMode),
1091
1206
  ...(developerInstructions ? { developerInstructions } : {}),
1092
1207
  };
1093
1208
  logger.info(`[CodexRunner] runQuery permMode=${requestedPermissionMode}->${callMode} ` +
@@ -1099,11 +1214,17 @@ export class CodexRunner {
1099
1214
  if (!threadId)
1100
1215
  throw new Error('Codex app-server did not return a thread id');
1101
1216
  if (!resumingThread) {
1102
- this.threadDelegationCarriers.set(threadId, delegationCarrierCandidate);
1103
- await this.persistDelegationCarrier(sessionManager, sessionId, threadId, delegationCarrierCandidate);
1217
+ carrierToPublish = { threadId, carrier: delegationCarrierCandidate };
1104
1218
  }
1105
1219
  else if (hasTaskDelegation && !knownDelegationCarrier) {
1106
- let recoveredCarrier = await this.readThreadDelegationCarrier(appServer, threadId);
1220
+ // A BOUND topic cannot silently fork to migrate carrier metadata: its
1221
+ // active-turn activation is allowed to confirm only the authoritative
1222
+ // backend ID, so a fork would deterministically conflict. threadResume
1223
+ // already received the candidate in this turn's environment config;
1224
+ // publish it for the same thread after binding confirmation instead.
1225
+ let recoveredCarrier = topicBinding
1226
+ ? delegationCarrierCandidate
1227
+ : await this.readThreadDelegationCarrier(appServer, threadId);
1107
1228
  if (!recoveredCarrier) {
1108
1229
  const staleThreadId = threadId;
1109
1230
  logger.warn(`[CodexRunner] loaded thread has no recoverable delegation carrier; forking once to preserve history: thread=${staleThreadId}`);
@@ -1121,19 +1242,35 @@ export class CodexRunner {
1121
1242
  threadId = forkedThreadId;
1122
1243
  recoveredCarrier = delegationCarrierCandidate;
1123
1244
  }
1124
- this.threadDelegationCarriers.set(threadId, recoveredCarrier);
1125
- await this.persistDelegationCarrier(sessionManager, sessionId, threadId, recoveredCarrier);
1245
+ carrierToPublish = { threadId, carrier: recoveredCarrier };
1126
1246
  }
1127
1247
  else if (knownDelegationCarrier) {
1128
- this.threadDelegationCarriers.set(threadId, knownDelegationCarrier);
1129
- await this.persistDelegationCarrier(sessionManager, sessionId, threadId, knownDelegationCarrier);
1248
+ carrierToPublish = { threadId, carrier: knownDelegationCarrier };
1130
1249
  }
1131
1250
  agentSessionId = threadId;
1251
+ const turn = modelOverride?.turn;
1252
+ const bindingResult = this.onSessionIdUpdate
1253
+ ? ((turn === undefined
1254
+ ? await this.onSessionIdUpdate(sessionId, threadId)
1255
+ : await this.onSessionIdUpdate(sessionId, threadId, { turn }))
1256
+ ?? 'legacy_updated')
1257
+ : 'legacy_updated';
1258
+ const bindingAccepted = bindingResult === 'activated'
1259
+ || bindingResult === 'already_active'
1260
+ || bindingResult === 'legacy_updated';
1261
+ if (!bindingAccepted) {
1262
+ const error = new Error(`backend discovery rejected: ${bindingResult}`);
1263
+ error.code = 'TOPIC_BACKEND_DISCOVERY_REJECTED';
1264
+ throw error;
1265
+ }
1132
1266
  this.activeSessions.set(sessionId, threadId);
1267
+ if (carrierToPublish) {
1268
+ this.threadDelegationCarriers.set(carrierToPublish.threadId, carrierToPublish.carrier);
1269
+ await this.persistDelegationCarrier(sessionManager, sessionId, carrierToPublish.threadId, carrierToPublish.carrier);
1270
+ }
1133
1271
  this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${threadId}`);
1134
1272
  this.threadProjectPaths.set(threadId, path.resolve(projectPath));
1135
1273
  this.bindExternalToolBoundary(threadId, externalToolConfig);
1136
- this.onSessionIdUpdate?.(sessionId, threadId);
1137
1274
  const controller = new AbortController();
1138
1275
  this.activeAbortControllers.set(sessionId, controller);
1139
1276
  const tempFiles = [];
@@ -1180,7 +1317,7 @@ export class CodexRunner {
1180
1317
  this.activeAbortControllers.delete(sessionId);
1181
1318
  this.activeStreams.delete(sessionId);
1182
1319
  logger.info(`[CodexRunner] Applied pending interrupt before turn start: ${sessionId}`);
1183
- return this.transformAppServerStream(queue, sessionId, state, unsubscribe, tempFiles);
1320
+ return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, tempFiles);
1184
1321
  }
1185
1322
  try {
1186
1323
  const turnResponse = await appServer.turnStart(threadId, input, {
@@ -1216,7 +1353,7 @@ export class CodexRunner {
1216
1353
  this.cleanupTempFiles(tempFiles);
1217
1354
  throw error;
1218
1355
  }
1219
- return this.transformAppServerStream(queue, sessionId, state, unsubscribe, tempFiles);
1356
+ return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, tempFiles);
1220
1357
  }
1221
1358
  // ── Interrupt ──
1222
1359
  async interrupt(sessionKey) {
@@ -1266,40 +1403,82 @@ export class CodexRunner {
1266
1403
  // ── Session commands ──
1267
1404
  updateSessionId(sessionId, agentSessionId) {
1268
1405
  const previousThreadId = this.activeSessions.get(sessionId);
1269
- if (previousThreadId && previousThreadId !== agentSessionId) {
1270
- this.threadProjectPaths.delete(previousThreadId);
1271
- this.threadDelegationCarriers.delete(previousThreadId);
1272
- this.unbindExternalToolBoundary(previousThreadId);
1273
- this.clearTrackedApprovalItemsForThread(previousThreadId);
1274
- }
1275
- if (agentSessionId) {
1276
- this.activeSessions.set(sessionId, agentSessionId);
1277
- this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${agentSessionId}`);
1278
- }
1279
- else {
1280
- this.activeSessions.delete(sessionId);
1406
+ const applyLocal = () => {
1407
+ const currentThreadId = this.activeSessions.get(sessionId);
1408
+ if (currentThreadId !== previousThreadId && !(previousThreadId === undefined && currentThreadId === undefined))
1409
+ return;
1410
+ if (previousThreadId && previousThreadId !== agentSessionId) {
1411
+ this.threadProjectPaths.delete(previousThreadId);
1412
+ this.threadDelegationCarriers.delete(previousThreadId);
1413
+ this.unbindExternalToolBoundary(previousThreadId);
1414
+ this.clearTrackedApprovalItemsForThread(previousThreadId);
1415
+ }
1416
+ if (agentSessionId)
1417
+ this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${agentSessionId}`);
1418
+ this.pruneThreadOperationLocks();
1419
+ if (agentSessionId)
1420
+ this.activeSessions.set(sessionId, agentSessionId);
1421
+ else
1422
+ this.activeSessions.delete(sessionId);
1423
+ };
1424
+ if (!this.onSessionIdUpdate) {
1425
+ applyLocal();
1426
+ return;
1281
1427
  }
1282
- this.pruneThreadOperationLocks();
1283
- this.onSessionIdUpdate?.(sessionId, agentSessionId);
1428
+ void this.onSessionIdUpdate(sessionId, agentSessionId).then(result => {
1429
+ if (result === 'activated' || result === 'already_active' || result === 'legacy_updated' || !agentSessionId) {
1430
+ applyLocal();
1431
+ }
1432
+ }).catch(error => {
1433
+ logger.warn(`[CodexRunner] session binding callback failed: ${error instanceof Error ? error.message : String(error)}`);
1434
+ if (!agentSessionId)
1435
+ applyLocal();
1436
+ });
1284
1437
  }
1285
1438
  async closeSession(sessionId) {
1439
+ const capturedThreadId = this.activeSessions.get(sessionId);
1440
+ const capturedPermissionContext = this.permissionContexts.get(sessionId);
1441
+ const capturedStream = this.activeStreams.get(sessionId);
1442
+ const capturedController = this.activeAbortControllers.get(sessionId);
1443
+ const capturedTurn = this.activeTurns.get(sessionId);
1286
1444
  this.permissionContexts.get(sessionId)?.pauseController?.cancel();
1287
- const threadId = this.activeSessions.get(sessionId);
1288
- this.activeSessions.delete(sessionId);
1289
- this.activeStreams.delete(sessionId);
1290
- this.activeAbortControllers.delete(sessionId);
1291
- this.activeTurns.delete(sessionId);
1292
- this.pendingInterrupts.delete(sessionId);
1293
- this.permissionContexts.delete(sessionId);
1294
- this.chatModes.delete(sessionId);
1295
- this.cleanupSessionTempDir(sessionId);
1296
- if (threadId) {
1297
- this.threadProjectPaths.delete(threadId);
1298
- this.threadDelegationCarriers.delete(threadId);
1299
- this.unbindExternalToolBoundary(threadId);
1445
+ // Do not call interrupt(sessionId) here: a new run may have replaced the
1446
+ // session-scoped maps while an older close was waiting. Interrupt and
1447
+ // clean only the exact lifecycle captured at entry.
1448
+ if (capturedTurn) {
1449
+ await this.interruptAppServerTurn(capturedTurn.threadId, capturedTurn.turnId).catch(() => { });
1450
+ }
1451
+ capturedController?.abort('Session closed');
1452
+ const lifecycleReplaced = (capturedStream !== undefined && this.activeStreams.get(sessionId) !== capturedStream)
1453
+ || (capturedController !== undefined && this.activeAbortControllers.get(sessionId) !== capturedController)
1454
+ || (capturedTurn !== undefined && this.activeTurns.get(sessionId) !== capturedTurn)
1455
+ || (capturedPermissionContext !== undefined && this.permissionContexts.get(sessionId) !== capturedPermissionContext)
1456
+ || (capturedThreadId !== undefined && this.activeSessions.get(sessionId) !== capturedThreadId);
1457
+ if (capturedStream !== undefined && this.activeStreams.get(sessionId) === capturedStream)
1458
+ this.activeStreams.delete(sessionId);
1459
+ if (capturedController !== undefined && this.activeAbortControllers.get(sessionId) === capturedController)
1460
+ this.activeAbortControllers.delete(sessionId);
1461
+ if (capturedTurn !== undefined && this.activeTurns.get(sessionId) === capturedTurn)
1462
+ this.activeTurns.delete(sessionId);
1463
+ if (this.pendingInterrupts.has(sessionId) && !lifecycleReplaced)
1464
+ this.pendingInterrupts.delete(sessionId);
1465
+ if (this.permissionContexts.get(sessionId) === capturedPermissionContext)
1466
+ this.permissionContexts.delete(sessionId);
1467
+ // Session-level configuration and thread indexes are shared by later
1468
+ // runs. Only remove them when the captured lifecycle still owns the key.
1469
+ if (!lifecycleReplaced) {
1470
+ if (this.activeSessions.get(sessionId) === capturedThreadId)
1471
+ this.activeSessions.delete(sessionId);
1472
+ this.chatModes.delete(sessionId);
1473
+ this.cleanupSessionTempDir(sessionId);
1474
+ if (capturedThreadId) {
1475
+ this.threadProjectPaths.delete(capturedThreadId);
1476
+ this.threadDelegationCarriers.delete(capturedThreadId);
1477
+ this.unbindExternalToolBoundary(capturedThreadId);
1478
+ }
1479
+ this.clearTrackedApprovalItemsForThread(capturedThreadId);
1480
+ this.pruneThreadOperationLocks();
1300
1481
  }
1301
- this.clearTrackedApprovalItemsForThread(threadId);
1302
- this.pruneThreadOperationLocks();
1303
1482
  }
1304
1483
  resolveSessionFile(agentSessionId, _projectPath) {
1305
1484
  // Codex session 文件: ~/.codex/sessions/YYYY/MM/DD/rollout-*-{threadId}.jsonl
@@ -1333,54 +1512,67 @@ export class CodexRunner {
1333
1512
  this.unbindExternalToolBoundary(threadId);
1334
1513
  this.clearTrackedApprovalItemsForThread(threadId);
1335
1514
  this.pruneThreadOperationLocks();
1336
- this.onSessionIdUpdate?.(sessionId, '');
1515
+ await this.onSessionIdUpdate?.(sessionId, '');
1337
1516
  return true;
1338
1517
  }
1339
- async compactSession(_sessionId, agentSessionId, _projectPath) {
1518
+ async compactSession(_sessionId, agentSessionId, _projectPath, modelOverride) {
1519
+ const startedAt = Date.now();
1520
+ const finish = (result) => {
1521
+ logger.info(`[CodexRunner] Compact result: thread=${agentSessionId} ok=${result.ok} code=${result.ok ? 'ok' : result.code} durationMs=${result.durationMs}`);
1522
+ return result;
1523
+ };
1340
1524
  const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId, _sessionId), 'compact');
1341
1525
  try {
1342
1526
  const appServer = this.getAppServerClient();
1343
1527
  this.onCompactStart?.(_sessionId);
1344
1528
  try {
1345
- return await this.startAndWaitForCompact(appServer, agentSessionId);
1529
+ await this.startAndWaitForCompact(appServer, agentSessionId);
1530
+ return finish({ ok: true, durationMs: Date.now() - startedAt });
1346
1531
  }
1347
1532
  catch (error) {
1348
1533
  if (!this.isThreadNotFoundError(error))
1349
1534
  throw error;
1350
1535
  logger.info(`[CodexRunner] Compact thread not loaded, resuming before compact: ${agentSessionId}`);
1351
- // 优先用 per-session 模式派生(与 runQuery 一致),缺省回落实例级
1352
- const compactMode = this.chatModes.get(_sessionId) ?? this.currentMode;
1536
+ const compactMode = normalizeExecutionPermissionMode(modelOverride?.permissionMode ?? this.chatModes.get(_sessionId) ?? this.currentMode);
1537
+ const compactModel = modelOverride?.model || this.model;
1538
+ const compactEffort = modelOverride?.effortMode === 'model_default'
1539
+ ? undefined
1540
+ : modelOverride?.effort ?? this.effort;
1353
1541
  const compactPolicy = this.toApprovalPolicy(compactMode);
1354
1542
  const executionSandbox = this.buildExecutionSandboxOptions(_sessionId, compactMode, _projectPath);
1355
1543
  const capabilityConfig = await this.resolveCapabilityThreadConfig(_projectPath);
1356
- const externalToolConfig = await this.resolveExternalToolApprovalConfig(appServer, _projectPath, capabilityConfig, compactMode);
1357
- await this.assertProactiveHookAvailability(_sessionId, appServer);
1544
+ const externalToolConfig = compactMode === 'fullaccess'
1545
+ ? {}
1546
+ : await this.resolveExternalToolApprovalConfig(appServer, _projectPath, capabilityConfig, compactMode);
1547
+ if (compactMode !== 'fullaccess')
1548
+ await this.assertProactiveHookAvailability(_sessionId, appServer);
1358
1549
  const compactDelegationCarrier = this.threadDelegationCarriers.get(agentSessionId)
1359
1550
  ?? this.createDelegationCarrier();
1360
1551
  await appServer.threadResume(agentSessionId, _projectPath, {
1361
- model: this.model,
1362
- effort: this.effort,
1552
+ model: compactModel,
1553
+ effort: compactEffort,
1363
1554
  approvalPolicy: compactPolicy,
1364
1555
  approvalsReviewer: 'user',
1365
1556
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
1366
- config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig()), compactMode),
1557
+ config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(_sessionId, compactDelegationCarrier), this.buildLifecycleLockdownConfig(compactMode)), compactMode),
1367
1558
  });
1368
1559
  this.threadProjectPaths.set(agentSessionId, path.resolve(_projectPath));
1369
1560
  this.threadDelegationCarriers.set(agentSessionId, compactDelegationCarrier);
1370
1561
  this.bindExternalToolBoundary(agentSessionId, externalToolConfig);
1371
- return await this.startAndWaitForCompact(appServer, agentSessionId);
1562
+ await this.startAndWaitForCompact(appServer, agentSessionId);
1563
+ return finish({ ok: true, durationMs: Date.now() - startedAt });
1372
1564
  }
1373
1565
  }
1374
1566
  catch (error) {
1375
- logger.error('[CodexRunner] Compact failed:', error);
1376
- return false;
1567
+ const message = error instanceof Error ? error.message : String(error);
1568
+ return finish({ ok: false, code: 'sdk_error', message, durationMs: Date.now() - startedAt });
1377
1569
  }
1378
1570
  finally {
1379
1571
  release();
1380
1572
  }
1381
1573
  }
1382
- async compact(sessionId, agentSessionId, projectPath) {
1383
- return this.compactSession(sessionId, agentSessionId, projectPath);
1574
+ async compact(sessionId, agentSessionId, projectPath, modelOverride) {
1575
+ return this.compactSession(sessionId, agentSessionId, projectPath, modelOverride);
1384
1576
  }
1385
1577
  async startAndWaitForCompact(appServer, threadId) {
1386
1578
  const completion = this.waitForThreadCompacted(appServer, threadId, Date.now());
@@ -1527,7 +1719,7 @@ export class CodexRunner {
1527
1719
  approvalPolicy: this.toApprovalPolicy(mode),
1528
1720
  approvalsReviewer: 'user',
1529
1721
  ...(executionSandbox.sandbox ? { sandbox: executionSandbox.sandbox } : {}),
1530
- config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig()), mode),
1722
+ config: this.sanitizeThreadEnvironmentConfig(this.mergeThreadConfig(executionSandbox.permissionProfileConfig, capabilityConfig, externalToolConfig, this.buildEvolcoreShellEnvironmentConfig(sessionKey, delegationCarrier), this.buildLifecycleLockdownConfig(mode)), mode),
1531
1723
  });
1532
1724
  const forkedThreadId = response.thread?.id;
1533
1725
  if (!forkedThreadId)
@@ -1654,7 +1846,12 @@ export class CodexRunner {
1654
1846
  }
1655
1847
  if (request.method === 'item/tool/requestUserInput') {
1656
1848
  const trackedItem = this.findTrackedApprovalItem(params);
1849
+ const requestSessionKey = this.findSessionKeyByThread(params.threadId, params.conversationId);
1850
+ const trustedFullAccess = (this.chatModes.get(requestSessionKey) ?? this.currentMode) === 'fullaccess'
1851
+ && this.isTrustedFullAccessSession(requestSessionKey);
1657
1852
  if (trackedItem?.type === 'mcpToolCall') {
1853
+ if (trustedFullAccess)
1854
+ return this.handleMcpToolApproval(params, trackedItem);
1658
1855
  const unattendedDenial = await this.denyUnattendedExternalToolApproval(params, `MCP:${String(trackedItem.server || 'unknown')}/${String(trackedItem.tool || 'unknown')}`, request.id);
1659
1856
  if (unattendedDenial)
1660
1857
  return unattendedDenial;
@@ -1664,6 +1861,12 @@ export class CodexRunner {
1664
1861
  return this.handleToolRequestUserInput(params);
1665
1862
  }
1666
1863
  if (this.looksLikeExternalToolApproval(params.questions)) {
1864
+ // In trusted fullaccess, an untracked approval prompt is still an SDK
1865
+ // permission surface, not ordinary model/user interaction. The
1866
+ // fullaccess branch in handleMcpToolApproval deliberately does not
1867
+ // require capability-bound metadata and selects a one-shot allow.
1868
+ if (trustedFullAccess)
1869
+ return this.handleMcpToolApproval(params, {});
1667
1870
  const unattendedDenial = await this.denyUnattendedExternalToolApproval(params, 'MCP:untracked', request.id);
1668
1871
  if (unattendedDenial)
1669
1872
  return unattendedDenial;
@@ -1690,6 +1893,17 @@ export class CodexRunner {
1690
1893
  ? 'FileChange'
1691
1894
  : 'Bash';
1692
1895
  const toolInput = this.buildPermissionInput(request.method, params);
1896
+ // A trusted fullaccess run has already been explicitly authorized by the
1897
+ // daemon owner. Do not route its approvals through ordinary capability,
1898
+ // workspace, or unattended-trigger policy checks.
1899
+ if ((this.chatModes.get(sessionKey) ?? this.currentMode) === 'fullaccess') {
1900
+ if (!this.isTrustedFullAccessSession(sessionKey)) {
1901
+ return this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
1902
+ }
1903
+ const response = this.toAppServerApprovalResponse(request.method, 'allow', toolInput, false);
1904
+ this.logAppServerApprovalAudit(request, sessionKey, toolName, 'allow', 'approval', undefined, 'trusted fullaccess execution');
1905
+ return response;
1906
+ }
1693
1907
  // proactive 模式行为策略(首次工具调用必须是 ec msg send)
1694
1908
  const policyResult = this.permissionContexts.get(sessionKey)?.policyHook?.(toolName, toolInput);
1695
1909
  if (policyResult?.block) {
@@ -1705,6 +1919,11 @@ export class CodexRunner {
1705
1919
  toolName,
1706
1920
  ...(request.id !== undefined ? { requestId: String(request.id) } : {}),
1707
1921
  policyCode: policyResult.policyCode ?? 'session_policy_hook',
1922
+ decisionSource: 'policy',
1923
+ agentAid: permissionContext?.selfAid,
1924
+ agentName: permissionContext?.agentName,
1925
+ sessionId: sessionKey,
1926
+ permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
1708
1927
  summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
1709
1928
  effect: 'operation_skipped',
1710
1929
  });
@@ -1727,13 +1946,15 @@ export class CodexRunner {
1727
1946
  }
1728
1947
  const operationCwd = this.resolvePermissionOperationCwd(params, workspacePath, toolName);
1729
1948
  const sessionMode = this.chatModes.get(sessionKey) ?? this.currentMode;
1730
- logger.info(`[CodexRunner] app-server approval request id=${request.id} method=${request.method} session=${sessionKey} mode=${sessionMode} tool=${toolName} summary=${summary}`);
1949
+ const logSummary = formatApprovalLogSummary(summary);
1950
+ logger.info(`[CodexRunner] app-server approval request id=${request.id} method=${request.method} session=${sessionKey} mode=${sessionMode} tool=${toolName} summary=${logSummary}`);
1731
1951
  const isCommandApproval = request.method === 'item/commandExecution/requestApproval'
1732
1952
  || request.method === 'execCommandApproval';
1733
1953
  const managedEcIntent = isCommandApproval && this.isManagedEvolcoreCommandIntent(toolInput);
1734
- const approvedCommand = isCommandApproval
1735
- ? this.approvedEvolcoreCommandArgv(toolInput, this.getManagedTempDir(sessionKey))
1954
+ const canonicalEcApproval = isCommandApproval
1955
+ ? this.resolveCanonicalEcApproval(toolInput, this.getManagedTempDir(sessionKey))
1736
1956
  : undefined;
1957
+ const approvedCommand = canonicalEcApproval?.argv;
1737
1958
  const denyInfrastructure = async (policyCode, message) => {
1738
1959
  logger.warn(`[CodexRunner] ${message}: session=${sessionKey} requestId=${request.id ?? '<missing>'}`);
1739
1960
  try {
@@ -1747,8 +1968,9 @@ export class CodexRunner {
1747
1968
  policyCode,
1748
1969
  decisionSource: 'infrastructure',
1749
1970
  agentAid: permissionContext?.selfAid,
1971
+ agentName: permissionContext?.agentName,
1750
1972
  sessionId: sessionKey,
1751
- permissionMode: normalizePermissionMode(sessionMode).mode,
1973
+ permissionMode: normalizeExecutionPermissionMode(sessionMode),
1752
1974
  summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
1753
1975
  effect: 'operation_skipped',
1754
1976
  });
@@ -1757,11 +1979,13 @@ export class CodexRunner {
1757
1979
  logger.warn(`[CodexRunner] failed to record infrastructure approval denial: session=${sessionKey} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
1758
1980
  }
1759
1981
  const response = this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
1760
- this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode, message);
1982
+ this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode, message, canonicalEcApproval?.parseFailure);
1761
1983
  return response;
1762
1984
  };
1763
1985
  if (managedEcIntent && !approvedCommand) {
1764
- return await denyInfrastructure('ec_command_not_canonical', 'EC command approval denied because no canonical argv could be derived');
1986
+ return await denyInfrastructure('ec_command_not_canonical', canonicalEcApproval?.parseFailure
1987
+ ? `EC command approval denied because canonical argv parsing failed (${canonicalEcApproval.parseFailure.issue})`
1988
+ : 'EC command approval denied because no canonical argv could be derived');
1765
1989
  }
1766
1990
  try {
1767
1991
  const decision = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd, request.id);
@@ -1798,20 +2022,22 @@ export class CodexRunner {
1798
2022
  throw error;
1799
2023
  }
1800
2024
  }
1801
- logAppServerApprovalAudit(request, sessionKey, toolName, decision, decisionSource, policyCode, reason) {
2025
+ logAppServerApprovalAudit(request, sessionKey, toolName, decision, decisionSource, policyCode, reason, parseFailure) {
1802
2026
  const context = this.permissionContexts.get(sessionKey);
1803
2027
  auditCodexApprovalDecision({
1804
2028
  requestId: request.id !== undefined ? String(request.id) : undefined,
1805
2029
  method: request.method,
1806
2030
  sessionId: sessionKey,
1807
2031
  agentAid: context?.selfAid,
2032
+ agentName: context?.agentName,
1808
2033
  toolName,
1809
2034
  decision,
1810
2035
  decisionSource,
1811
2036
  policyCode,
1812
- permissionMode: normalizePermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode).mode,
2037
+ permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
1813
2038
  role: context?.role,
1814
2039
  reason: reason ?? policyCode ?? (decision === 'allow' ? 'approval accepted' : 'approval denied'),
2040
+ parseFailure,
1815
2041
  });
1816
2042
  logger.info(`[CodexRunner] app-server approval audit ${JSON.stringify({
1817
2043
  requestId: request.id !== undefined ? String(request.id) : undefined,
@@ -1821,6 +2047,7 @@ export class CodexRunner {
1821
2047
  decision,
1822
2048
  decisionSource,
1823
2049
  ...(policyCode ? { policyCode } : {}),
2050
+ ...(parseFailure ? { parseFailure } : {}),
1824
2051
  reason: reason ?? policyCode ?? (decision === 'allow' ? 'approval accepted' : 'approval denied'),
1825
2052
  executed: false,
1826
2053
  })}`);
@@ -1910,6 +2137,12 @@ export class CodexRunner {
1910
2137
  ? { requestId: String(requestId) }
1911
2138
  : {}),
1912
2139
  policy: context.approvalRouting?.approverPolicy ?? 'requester',
2140
+ policyCode: 'permission_mode_denied',
2141
+ decisionSource: 'policy',
2142
+ agentAid: context.selfAid,
2143
+ agentName: context.agentName,
2144
+ sessionId: sessionKey,
2145
+ permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
1913
2146
  summary: `${toolName} runtime authorization denied by unattended Trigger policy`,
1914
2147
  effect: 'operation_skipped',
1915
2148
  });
@@ -1924,6 +2157,27 @@ export class CodexRunner {
1924
2157
  const questions = Array.isArray(params.questions) ? params.questions : [];
1925
2158
  if (questions.length === 0)
1926
2159
  throw new Error('Codex MCP 审批未包含问题,已拒绝');
2160
+ const sessionKey = this.findSessionKeyByThread(params.threadId, params.conversationId);
2161
+ if ((this.chatModes.get(sessionKey) ?? this.currentMode) === 'fullaccess') {
2162
+ if (!this.isTrustedFullAccessSession(sessionKey))
2163
+ return this.denyUntrackedExternalToolApproval(params);
2164
+ const answers = {};
2165
+ for (const question of questions) {
2166
+ const questionId = typeof question.id === 'string' ? question.id : `q-${Object.keys(answers).length + 1}`;
2167
+ const labels = Array.isArray(question.options)
2168
+ ? question.options.map((option) => typeof option?.label === 'string' ? option.label.trim() : '').filter(Boolean)
2169
+ : [];
2170
+ let answer = '';
2171
+ try {
2172
+ answer = this.selectMcpApprovalAnswer(question, true);
2173
+ }
2174
+ catch {
2175
+ answer = labels[0] ?? 'allow';
2176
+ }
2177
+ answers[questionId] = { answers: [answer] };
2178
+ }
2179
+ return { answers };
2180
+ }
1927
2181
  const hasExactIdentity = typeof trackedItem.server === 'string'
1928
2182
  && trackedItem.server.length > 0
1929
2183
  && typeof trackedItem.tool === 'string'
@@ -1940,7 +2194,6 @@ export class CodexRunner {
1940
2194
  logger.warn(`[CodexRunner] MCP approval denied because the hardened external-tool config is unbound: thread=${threadId ?? '<missing>'}`);
1941
2195
  return this.denyUntrackedExternalToolApproval(params);
1942
2196
  }
1943
- const sessionKey = this.findSessionKeyByThread(params.threadId, params.conversationId);
1944
2197
  const server = trackedItem.server;
1945
2198
  const tool = trackedItem.tool;
1946
2199
  const isAppCall = typeof trackedItem.mcpAppResourceUri === 'string'
@@ -1964,7 +2217,7 @@ export class CodexRunner {
1964
2217
  };
1965
2218
  const policyResult = this.permissionContexts.get(sessionKey)?.policyHook?.(toolName, toolInput);
1966
2219
  const workspacePath = this.resolvePermissionWorkspacePath(params);
1967
- const mode = normalizePermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode).mode;
2220
+ const mode = normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode);
1968
2221
  // MCP servers are inherited unless capability config explicitly disables
1969
2222
  // them. Apps retain their separate request/bypass lifecycle policy.
1970
2223
  let allow = !policyResult?.block
@@ -2084,7 +2337,8 @@ export class CodexRunner {
2084
2337
  chatmode: context.chatmode,
2085
2338
  replyContext: context.replyContext,
2086
2339
  });
2087
- sent = !!await sendInteractionPayload(context.adapter, envelope, interaction, undefined, context.replyContext);
2340
+ const receipt = await sendInteractionPayload(context.adapter, envelope, interaction, undefined, context.replyContext);
2341
+ sent = isInteractionSendAccepted(receipt);
2088
2342
  }
2089
2343
  if (!sent) {
2090
2344
  await sendPrompt(renderActionAsText(interaction));
@@ -2239,7 +2493,7 @@ export class CodexRunner {
2239
2493
  // Revalidate ownership, mode, and parent containment at the final
2240
2494
  // readonly decision. The map entry alone is stale if the directory was
2241
2495
  // replaced between preflight and the approval callback.
2242
- managedTempDir: this.getManagedTempDir(sessionKey),
2496
+ managedTempDir: permCtx?.managedTempDir ?? this.getManagedTempDir(sessionKey),
2243
2497
  allowReadonlySourceDiagnostics: permCtx?.allowReadonlySourceDiagnostics === true,
2244
2498
  } : undefined;
2245
2499
  if (toolName === 'Bash')
@@ -2355,8 +2609,14 @@ export class CodexRunner {
2355
2609
  && this.fileChangeRequiresExpansion(toolInput, workspacePath, sessionKey);
2356
2610
  // per-session 权限模式(runQuery 写入);缺省回落实例级 currentMode(兼容无 runQuery 上下文的调用)
2357
2611
  const rawMode = this.chatModes.get(sessionKey) ?? this.currentMode;
2358
- const mode = normalizePermissionMode(rawMode).mode;
2359
- const recordBlockedOperation = async (policyCode, operationInput = toolInput, denialReason = 'policy denied') => {
2612
+ const mode = normalizeExecutionPermissionMode(rawMode);
2613
+ if (rawMode === 'fullaccess') {
2614
+ if (!this.isTrustedFullAccessSession(sessionKey)) {
2615
+ return 'deny';
2616
+ }
2617
+ return 'allow';
2618
+ }
2619
+ const recordBlockedOperation = async (policyCode, operationInput = toolInput, denialReason = 'policy denied', matchedPath) => {
2360
2620
  const summary = summarizeToolInputForAudit(toolName, operationInput).slice(0, 512);
2361
2621
  auditToolPreflightDenial({
2362
2622
  toolName,
@@ -2365,6 +2625,7 @@ export class CodexRunner {
2365
2625
  summary,
2366
2626
  sessionId: sessionKey,
2367
2627
  agentAid: permissionContext?.selfAid,
2628
+ agentName: permissionContext?.agentName,
2368
2629
  permissionMode: mode,
2369
2630
  channel: permissionContext?.channel,
2370
2631
  actorId: permissionContext?.userId,
@@ -2372,6 +2633,7 @@ export class CodexRunner {
2372
2633
  selfAid: permissionContext?.selfAid,
2373
2634
  requestId: typeof requestId === 'string' || typeof requestId === 'number' ? String(requestId) : undefined,
2374
2635
  taskId: permissionContext?.taskId,
2636
+ matchedPath,
2375
2637
  });
2376
2638
  try {
2377
2639
  await permissionContext?.recordExecutionAnomaly?.({
@@ -2384,6 +2646,7 @@ export class CodexRunner {
2384
2646
  policyCode,
2385
2647
  decisionSource: 'policy',
2386
2648
  agentAid: permissionContext?.selfAid,
2649
+ agentName: permissionContext?.agentName,
2387
2650
  permissionMode: mode,
2388
2651
  summary,
2389
2652
  effect: 'operation_skipped',
@@ -2398,7 +2661,7 @@ export class CodexRunner {
2398
2661
  // An unavailable registered root must fail closed; the policy helper
2399
2662
  // distinguishes this explicit empty value from runners without a
2400
2663
  // session-temp capability.
2401
- managedTempDir: this.getManagedTempDir(sessionKey) ?? '',
2664
+ managedTempDir: permissionContext?.managedTempDir ?? this.getManagedTempDir(sessionKey) ?? '',
2402
2665
  allowProtectedMetadata: false,
2403
2666
  selfAid: permissionContext?.selfAid,
2404
2667
  channel: permissionContext?.channel,
@@ -2413,7 +2676,7 @@ export class CodexRunner {
2413
2676
  logger.warn(`[CodexRunner] tool preflight denied: session=${sessionKey} tool=${toolName} ` +
2414
2677
  `policy=${preflight.policyCode ?? 'unknown'} reason=${preflight.message ?? 'policy denied'}`);
2415
2678
  if (preflight.policyCode) {
2416
- await recordBlockedOperation(preflight.policyCode, toolInput, preflight.message ?? 'tool preflight denied');
2679
+ await recordBlockedOperation(preflight.policyCode, toolInput, preflight.message ?? 'tool preflight denied', preflight.matchedPath);
2417
2680
  }
2418
2681
  return 'deny';
2419
2682
  }
@@ -2685,7 +2948,8 @@ export class CodexRunner {
2685
2948
  const input = [{ type: 'text', text: prompt, text_elements: [] }];
2686
2949
  if (!images?.length)
2687
2950
  return input;
2688
- const tmpDir = runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR
2951
+ const tmpDir = getManagedTaskTempDir(runtimeEnv)
2952
+ ?? runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR
2689
2953
  ?? (sessionId ? this.getManagedTempDir(sessionId) : undefined)
2690
2954
  ?? process.env.TMPDIR;
2691
2955
  if (!tmpDir || !path.isAbsolute(tmpDir))
@@ -2728,7 +2992,7 @@ export class CodexRunner {
2728
2992
  }
2729
2993
  return !state.turnId || !turnId || turnId === state.turnId;
2730
2994
  }
2731
- async *transformAppServerStream(notifications, sessionId, state, unsubscribe, tempFiles) {
2995
+ async *transformAppServerStream(notifications, sessionId, state, controller, unsubscribe, tempFiles) {
2732
2996
  try {
2733
2997
  yield { type: 'session_id', sessionId: state.threadId };
2734
2998
  for await (const notification of notifications) {
@@ -2739,8 +3003,14 @@ export class CodexRunner {
2739
3003
  }
2740
3004
  finally {
2741
3005
  unsubscribe();
2742
- this.activeAbortControllers.delete(sessionId);
2743
- this.activeTurns.delete(sessionId);
3006
+ if (this.activeAbortControllers.get(sessionId) === controller) {
3007
+ this.activeAbortControllers.delete(sessionId);
3008
+ }
3009
+ const activeTurn = this.activeTurns.get(sessionId);
3010
+ if (activeTurn?.threadId === state.threadId
3011
+ && (!state.turnId || activeTurn.turnId === state.turnId)) {
3012
+ this.activeTurns.delete(sessionId);
3013
+ }
2744
3014
  this.cleanupTempFiles(tempFiles);
2745
3015
  }
2746
3016
  }
@@ -3361,11 +3631,9 @@ export class CodexAgentPlugin {
3361
3631
  agents: {
3362
3632
  codex: {
3363
3633
  ...(override || {}),
3364
- evolcoreAgentAid: agent.config.aid,
3365
- evolcoreAgentConfig: agent.config,
3366
3634
  },
3367
3635
  },
3368
3636
  };
3369
- return { evolagentName: agent.name, baseagent: 'codex', agent: new CodexRunner(merged, callbacks) };
3637
+ return { evolagentName: agent.name, baseagent: 'codex', agent: new CodexRunner(merged, callbacks, { agentAid: agent.config.aid, agentConfig: agent.config }) };
3370
3638
  }
3371
3639
  }