evolcore 0.0.27 → 0.0.29-test.0

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 (195) hide show
  1. package/CHANGELOG.md +38 -15
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +29 -14
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +13 -2
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex/diagnostics.js +4 -3
  9. package/dist/agents/codex-runner.js +444 -106
  10. package/dist/agents/gemini-runner.js +35 -17
  11. package/dist/aun/group/history.js +130 -90
  12. package/dist/aun/group/operations.js +1 -3
  13. package/dist/aun/menu/client.js +1 -0
  14. package/dist/aun/menu/transport.js +5 -3
  15. package/dist/aun/msg/managed-operation.js +70 -0
  16. package/dist/aun/msg/p2p.js +1 -3
  17. package/dist/aun/msg/upload.js +2 -2
  18. package/dist/aun/outbox.js +1 -37
  19. package/dist/channels/aun.js +262 -86
  20. package/dist/channels/daemon.js +18 -1
  21. package/dist/channels/feishu.js +1 -2
  22. package/dist/cli/agent-command.js +23 -3
  23. package/dist/cli/agent-create.js +68 -2
  24. package/dist/cli/agent.js +12 -2
  25. package/dist/cli/aun-commands.js +87 -13
  26. package/dist/cli/cli-argv.js +26 -0
  27. package/dist/cli/daemon-commands.js +116 -149
  28. package/dist/cli/data-command.js +4 -4
  29. package/dist/cli/diag.js +1 -1
  30. package/dist/cli/handoff-command.js +2 -0
  31. package/dist/cli/index.js +18 -1
  32. package/dist/cli/managed-command-guard.js +10 -0
  33. package/dist/cli/memory-command.js +441 -0
  34. package/dist/cli/menu-client.js +4 -0
  35. package/dist/cli/restart-monitor.js +127 -23
  36. package/dist/cli/skill-command.js +41 -0
  37. package/dist/cli/task-context.js +2 -23
  38. package/dist/cli/trigger-command.js +9 -1
  39. package/dist/cli/watch-msg-diag.js +209 -0
  40. package/dist/config/config-field-policy.js +52 -3
  41. package/dist/config/config-manager.js +17 -4
  42. package/dist/config/contact-book-store.js +2 -2
  43. package/dist/config/mention-mode.js +7 -2
  44. package/dist/config/merge.js +28 -9
  45. package/dist/config/resolved-config-op.js +1 -1
  46. package/dist/config/runtime-env.js +137 -0
  47. package/dist/config/schema-registry.js +11 -1
  48. package/dist/config-store.js +10 -10
  49. package/dist/core/audit/event-key.js +3 -1
  50. package/dist/core/audit/log-integrity.js +4 -0
  51. package/dist/core/auth/agent-delegation.js +2 -1
  52. package/dist/core/auth/auth-gateway.js +13 -6
  53. package/dist/core/auth/authorization-audit.js +54 -19
  54. package/dist/core/auth/operation-authorizer.js +17 -0
  55. package/dist/core/auth/operation-catalog.js +9 -0
  56. package/dist/core/causation/context.js +5 -1
  57. package/dist/core/command/agent-control.js +1 -2
  58. package/dist/core/command/command-handler.js +80 -24
  59. package/dist/core/command/diag-service.js +292 -27
  60. package/dist/core/command/memory-slash.js +270 -0
  61. package/dist/core/command/menu-catalog.js +1 -0
  62. package/dist/core/command/menu-handler.js +331 -79
  63. package/dist/core/command/menu-protocol.js +45 -4
  64. package/dist/core/command/slash-gate.js +10 -10
  65. package/dist/core/command/slash-handler.js +230 -79
  66. package/dist/core/data-migration.js +282 -505
  67. package/dist/core/event-catalog.js +26 -0
  68. package/dist/core/evolagent-registry.js +6 -0
  69. package/dist/core/evolagent.js +0 -10
  70. package/dist/core/handoff/store.js +1 -20
  71. package/dist/core/inference/text-inference.js +57 -1
  72. package/dist/core/memory/consolidation-context.js +505 -0
  73. package/dist/core/memory/consolidation-ops-records.js +124 -0
  74. package/dist/core/memory/consolidation-trigger.js +394 -0
  75. package/dist/core/memory/index-manager.js +308 -0
  76. package/dist/core/memory/launch-consolidation.js +395 -0
  77. package/dist/core/memory/memory-admin.js +234 -0
  78. package/dist/core/memory/memory-document.js +100 -0
  79. package/dist/core/memory/memory-session-policy.js +41 -0
  80. package/dist/core/memory/memory-signature.js +257 -0
  81. package/dist/core/memory/memory-writer.js +432 -0
  82. package/dist/core/memory/staleness.js +117 -0
  83. package/dist/core/message/auto-mention-gate.js +454 -0
  84. package/dist/core/message/im-renderer.js +90 -20
  85. package/dist/core/message/message-bridge.js +333 -23
  86. package/dist/core/message/message-log.js +1 -0
  87. package/dist/core/message/message-queue.js +114 -27
  88. package/dist/core/message/pending-hints.js +329 -161
  89. package/dist/core/message/response-engine.js +596 -90
  90. package/dist/core/permission/ec-command-parser.js +1044 -87
  91. package/dist/core/permission/protected-paths.js +376 -126
  92. package/dist/core/permission/sandbox-runtime.js +40 -12
  93. package/dist/core/permission/tool-error-code.js +96 -13
  94. package/dist/core/permission/tool-policy.js +277 -24
  95. package/dist/core/role/runtime-policy.js +3 -7
  96. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  97. package/dist/core/session/session-fs-store.js +0 -76
  98. package/dist/core/session/session-idle-monitor.js +355 -0
  99. package/dist/core/session/session-manager.js +482 -17
  100. package/dist/core/session/session-mapper.js +33 -16
  101. package/dist/core/session/session-renew.js +217 -155
  102. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  103. package/dist/core/skill/global-watcher.js +282 -0
  104. package/dist/core/skill/index-generator.js +84 -0
  105. package/dist/core/skill/startup.js +73 -0
  106. package/dist/core/skill/switch-resolver.js +115 -0
  107. package/dist/core/task/task-activity-store.js +219 -0
  108. package/dist/eck/kit-renderer.js +30 -3
  109. package/dist/eck/manifest-engine.js +26 -4
  110. package/dist/index.js +722 -171
  111. package/dist/ipc.js +564 -2
  112. package/dist/paths.js +30 -9
  113. package/dist/product.js +1 -0
  114. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  115. package/dist/response-system/modes/single-session/index.js +3 -0
  116. package/dist/trigger/feedback.js +1 -0
  117. package/dist/trigger/manager.js +0 -10
  118. package/dist/trigger/scheduler.js +11 -0
  119. package/dist/trigger/validation.js +22 -0
  120. package/dist/utils/atomic-write.js +98 -0
  121. package/dist/utils/avatar-upload.js +59 -18
  122. package/dist/utils/cross-platform.js +87 -16
  123. package/dist/utils/error-dict.json +6 -3
  124. package/dist/utils/error-utils.js +27 -0
  125. package/dist/utils/logger.js +32 -10
  126. package/dist/utils/media-cache.js +84 -0
  127. package/dist/utils/msg-diag.js +97 -0
  128. package/dist/utils/npm-ops.js +119 -4
  129. package/dist/utils/package-version-info.js +244 -0
  130. package/dist/utils/welcome.js +37 -8
  131. package/dist/utils/windows-shell-trust.js +37 -0
  132. package/kits/docs/AUTHORING.md +314 -0
  133. package/kits/docs/GUIDE.md +2 -0
  134. package/kits/docs/INDEX.md +2 -0
  135. package/kits/docs/aun/CHEATSHEET.md +4 -0
  136. package/kits/docs/aun/SDK.md +60 -0
  137. package/kits/docs/channels/aun.md +8 -0
  138. package/kits/docs/context-assembly.md +10 -6
  139. package/kits/docs/evolcore/INDEX.md +17 -3
  140. package/kits/docs/evolcore/agent-create.md +24 -0
  141. package/kits/docs/evolcore/agent.md +5 -2
  142. package/kits/docs/evolcore/config.md +1 -1
  143. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  144. package/kits/docs/evolcore/group.md +34 -18
  145. package/kits/docs/evolcore/memory-architecture.md +78 -0
  146. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  147. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  148. package/kits/docs/evolcore/memory-tags.md +63 -0
  149. package/kits/docs/evolcore/memory.md +110 -0
  150. package/kits/docs/evolcore/menu-client.md +27 -11
  151. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  152. package/kits/docs/evolcore/menu-protocol.md +213 -0
  153. package/kits/docs/evolcore/msg.md +2 -0
  154. package/kits/docs/evolcore/trigger.md +7 -5
  155. package/kits/docs/path-registry.md +6 -8
  156. package/kits/docs/prompt-loading-architecture.md +5 -3
  157. package/kits/docs/venues/group.md +5 -4
  158. package/kits/eck_manifest.json +294 -88
  159. package/kits/eck_manifest.memory.json +257 -0
  160. package/kits/rules/03-identity.md +8 -8
  161. package/kits/rules/05-venue.md +4 -2
  162. package/kits/rules/06-channel.md +1 -1
  163. package/kits/schemas/_meta.json +276 -47
  164. package/kits/schemas/agent-config.schema.12.json +38 -2
  165. package/kits/schemas/agent-config.schema.13.json +591 -0
  166. package/kits/schemas/daemon.schema.6.json +9 -9
  167. package/kits/schemas/defaults.schema.5.json +29 -0
  168. package/kits/schemas/relation-config.schema.8.json +14 -1
  169. package/kits/schemas/relation-config.schema.9.json +129 -0
  170. package/kits/schemas/role-config.schema.1.json +3 -3
  171. package/kits/skills/.switches.json +3 -0
  172. package/kits/skills/ec-skill/SKILL.md +217 -0
  173. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  174. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  175. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  176. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  177. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  178. package/kits/templates/decision/auto-mention.md +25 -0
  179. package/kits/templates/decision/session-renew.md +22 -0
  180. package/kits/templates/memory-consolidation/forget.md +104 -0
  181. package/kits/templates/memory-consolidation/generalize.md +76 -0
  182. package/kits/templates/memory-consolidation/notes.md +133 -0
  183. package/kits/templates/memory-consolidation/profile.md +58 -0
  184. package/kits/templates/memory-consolidation/promote.md +72 -0
  185. package/kits/templates/memory-consolidation/workflow.md +75 -0
  186. package/kits/templates/system-fragments/bootstrap.md +14 -5
  187. package/kits/templates/system-fragments/commands.md +1 -0
  188. package/kits/templates/system-fragments/group-history.md +6 -0
  189. package/kits/templates/system-fragments/identity.md +0 -3
  190. package/kits/templates/system-fragments/memory.md +79 -0
  191. package/kits/templates/system-fragments/reply.md +1 -0
  192. package/kits/templates/system-fragments/venue.md +1 -1
  193. package/package.json +4 -6
  194. package/dist/trigger/legacy-session-history.js +0 -19
  195. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -5,9 +5,9 @@ import { formatAsyncQuestionAnswer, hasTrustedFullAccessContext, parseAsyncUserQ
5
5
  import { requestDangerousCommandPermission } from '../core/permission/approval-gateway.js';
6
6
  import { checkReadonly, checkDangerousCommand, evaluateToolPreflight } from '../core/permission/tool-policy.js';
7
7
  import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
8
- import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
8
+ import { assertWindowsCodexFilesystemRuleBudget, buildCodexProtectedFilesystemRules, getExistingRelationAttachmentDirs, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
9
9
  import { CODEX_THREAD_ACTIVE_WRITER, CodexAppServerClient, } from './codex/app-server-client.js';
10
- import { resolveOpenaiConfig, resolveCodexCliPath } from './baseagent.js';
10
+ import { resolveOpenaiConfig, resolveCodexCliPath, resolveCodexPowerShellShimPath, } from './baseagent.js';
11
11
  import { logger } from '../utils/logger.js';
12
12
  import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
13
13
  import { auditCodexApprovalDecision, auditPermissionLifecycle, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
@@ -17,26 +17,20 @@ import { buildEnvelope, isInteractionSendAccepted, sendInteractionPayload } from
17
17
  import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
18
18
  import { AGENT_DELEGATION_TOKEN_ENV, CODEX_SESSION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
19
19
  import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
20
- import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, hasCodexCmdCarrierEcIntent, hasPowerShellCredentialAssignment, normalizeEvolcoreCommandArgv, parseCodexToolCommand, parseCodexCarrierCommand, parseLiteralShellCommand, parseCodexShellCommandArgv, resolveCodexShellCarrierArgv, } from '../core/permission/ec-command-parser.js';
20
+ import { classifyEvolcoreShellCommand, containsLiteralManagedTmpDirOutsideSendContent, containsUnresolvedManagedTmpDirReference, hasCodexCmdCarrierEcIntent, hasPowerShellCredentialAssignment, normalizeEvolcoreCommandArgv, canonicalizeEvolcoreCommandArgv, parseCodexToolCommand, parseCodexCarrierCommand, parseLiteralShellCommand, parseCodexShellCommandArgv, resolveCodexShellCarrierArgv, parseShellApplyPatchInvocation, } from '../core/permission/ec-command-parser.js';
21
21
  import { compareVersions } from '../utils/npm-ops.js';
22
- import { agentSessionsDir, resolvePaths, resolveRoot } from '../paths.js';
22
+ import { agentCodexTokenPath, resolvePaths, resolveRoot } from '../paths.js';
23
23
  import { ensureSessionRuntimeDir, getManagedTaskTempDir } from '../cli/task-context.js';
24
- import { buildSessionTurnList } from '../core/session/session-turns.js';
24
+ import { buildTranscriptTurns } from '../core/session/session-transcript.js';
25
25
  import { CodexSessionFileAdapter } from '../core/session/adapters/codex-session-file-adapter.js';
26
26
  import { execFileSync } from 'child_process';
27
27
  import { probeCli, probeCliSync } from '../utils/cross-platform.js';
28
28
  import { createHash, randomBytes } from 'crypto';
29
29
  import { buildModelRequestHeaders } from './request-identity.js';
30
+ import { normalizeToolErrorClassification, normalizeToolErrorCode, normalizeToolErrorDetailCode, } from '../core/permission/tool-error-code.js';
30
31
  import fs from 'fs';
31
32
  import path from 'path';
32
33
  import os from 'os';
33
- // ── MIME → 扩展名映射 ──
34
- const MIME_EXT = {
35
- 'image/png': '.png',
36
- 'image/jpeg': '.jpg',
37
- 'image/gif': '.gif',
38
- 'image/webp': '.webp',
39
- };
40
34
  const APPROVAL_LOG_SUMMARY_MAX_LENGTH = 256;
41
35
  const CODEX_INTERRUPT_TIMEOUT_MS = 5_000;
42
36
  function toAgentErrorType(message) {
@@ -92,7 +86,7 @@ class AsyncEventQueue {
92
86
  }
93
87
  }
94
88
  }
95
- function errorCodeForToolDenial(decisionSource, policyCode) {
89
+ function fallbackErrorCodeForToolDenial(decisionSource, policyCode) {
96
90
  if (decisionSource === 'policy')
97
91
  return 'POLICY_DENIED';
98
92
  if (decisionSource === 'approval')
@@ -103,6 +97,35 @@ function errorCodeForToolDenial(decisionSource, policyCode) {
103
97
  return 'INVALID_ARGUMENT';
104
98
  return 'EXECUTION_FAILED';
105
99
  }
100
+ function errorCodeForToolDenial(decisionSource, policyCode) {
101
+ return normalizeToolErrorClassification({
102
+ errorCode: fallbackErrorCodeForToolDenial(decisionSource, policyCode),
103
+ decisionSource,
104
+ policyCode,
105
+ executed: false,
106
+ executionState: 'blocked',
107
+ }).errorCode;
108
+ }
109
+ function errorDetailCodeForToolDenial(decisionSource, policyCode, errorDetailCode) {
110
+ return normalizeToolErrorClassification({
111
+ errorCode: fallbackErrorCodeForToolDenial(decisionSource, policyCode),
112
+ errorDetailCode,
113
+ decisionSource,
114
+ policyCode,
115
+ executed: false,
116
+ executionState: 'blocked',
117
+ }).errorDetailCode;
118
+ }
119
+ function codexItemErrorFields(item) {
120
+ const rawCode = item?.error?.code ?? item?.error?.errorCode;
121
+ if (typeof rawCode !== 'string' || !rawCode.trim())
122
+ return {};
123
+ const errorCode = normalizeToolErrorCode(rawCode);
124
+ if (errorCode)
125
+ return { errorCode };
126
+ const errorDetailCode = normalizeToolErrorDetailCode(rawCode);
127
+ return errorDetailCode ? { errorDetailCode } : {};
128
+ }
106
129
  function isCodexReasoningNotification(method) {
107
130
  return method === 'item/reasoning/textDelta'
108
131
  || method === 'item/reasoning/summaryTextDelta'
@@ -212,10 +235,13 @@ function compactCodexProbeText(value, maxLength = 600) {
212
235
  const text = value.replace(/\s+/gu, ' ').trim();
213
236
  return text.length <= maxLength ? text : `${text.slice(0, maxLength - 3).trimEnd()}...`;
214
237
  }
215
- function logCodexProbeFailure(args, startedAt, error, timeoutMs) {
238
+ function logCodexProbeFailure(args, startedAt, error, timeoutMs, launch) {
216
239
  const detail = error;
217
240
  logger.warn(`[CodexProbe] CLI probe failed: ${JSON.stringify({
218
241
  command: ['codex', ...args],
242
+ resolvedPath: launch?.resolvedPath ?? null,
243
+ launcherKind: launch?.launcherKind ?? null,
244
+ launcherPath: launch?.launcherPath ?? null,
219
245
  path: detail?.path ?? null,
220
246
  spawnargs: detail?.spawnargs ?? null,
221
247
  elapsedMs: Date.now() - startedAt,
@@ -229,13 +255,13 @@ function logCodexProbeFailure(args, startedAt, error, timeoutMs) {
229
255
  stderr: compactCodexProbeText(detail?.stderr),
230
256
  })}`);
231
257
  }
232
- function probeCodexCli(args) {
233
- return probeCliSync('codex', args, { onFailure: logCodexProbeFailure });
258
+ function probeCodexCli(args, executable) {
259
+ return probeCliSync('codex', args, { executable, onFailure: logCodexProbeFailure });
234
260
  }
235
- export function getCodexCliVersion() {
261
+ export function getCodexCliVersion(executable) {
236
262
  const startedAt = Date.now();
237
263
  try {
238
- const output = probeCodexCli(['--version']);
264
+ const output = probeCodexCli(['--version'], executable);
239
265
  const version = parseCodexCliVersion(output);
240
266
  if (!version) {
241
267
  logCodexProbeFailure(['--version'], startedAt, {
@@ -254,9 +280,15 @@ export function getCodexCliVersion() {
254
280
  export function getCodexAppServerAvailability() {
255
281
  const cliPath = resolveCodexCliPath();
256
282
  if (!cliPath) {
257
- return { available: false, reason: '未检测到 Codex CLI。' };
283
+ const powershellShim = resolveCodexPowerShellShimPath();
284
+ return {
285
+ available: false,
286
+ reason: powershellShim
287
+ ? '检测到 codex.ps1,但当前 Windows 启动路径要求 codex.exe/codex.cmd/codex.bat;请重新安装 @openai/codex 以恢复 npm shim。'
288
+ : '未检测到 Codex CLI。',
289
+ };
258
290
  }
259
- const version = getCodexCliVersion();
291
+ const version = getCodexCliVersion(cliPath);
260
292
  if (!version) {
261
293
  return { available: false, reason: '无法读取 Codex CLI 版本。' };
262
294
  }
@@ -269,7 +301,7 @@ export function getCodexAppServerAvailability() {
269
301
  };
270
302
  }
271
303
  try {
272
- probeCodexCli(['app-server', '--help']);
304
+ probeCodexCli(['app-server', '--help'], cliPath);
273
305
  return { available: true, version };
274
306
  }
275
307
  catch (error) {
@@ -284,10 +316,21 @@ export function isCodexAppServerAvailable() {
284
316
  }
285
317
  /** Daemon startup/hot reload use asynchronous probes; sync CLI callers share the cache. */
286
318
  export async function getCodexAppServerAvailabilityAsync() {
319
+ const cliPath = resolveCodexCliPath();
287
320
  const options = {
288
321
  timeouts: [CODEX_CLI_PROBE_TIMEOUT_MS, CODEX_CLI_PROBE_RETRY_TIMEOUT_MS],
322
+ executable: cliPath ?? undefined,
289
323
  onFailure: logCodexProbeFailure,
290
324
  };
325
+ if (!cliPath) {
326
+ const powershellShim = resolveCodexPowerShellShimPath();
327
+ return {
328
+ available: false,
329
+ reason: powershellShim
330
+ ? '检测到 codex.ps1,但当前 Windows 启动路径要求 codex.exe/codex.cmd/codex.bat;请重新安装 @openai/codex 以恢复 npm shim。'
331
+ : '未检测到 Codex CLI。',
332
+ };
333
+ }
291
334
  let version;
292
335
  try {
293
336
  version = parseCodexCliVersion(await probeCli('codex', ['--version'], options)) ?? undefined;
@@ -348,6 +391,7 @@ export class CodexRunner {
348
391
  /** Fallback authorization lifecycle ids for app-server requests without an RPC id. */
349
392
  approvalLifecycleIds = new WeakMap();
350
393
  resolvedConfig;
394
+ codexVersion;
351
395
  pendingInterruptTtlMs = 30_000;
352
396
  /** Fallback for direct runner callers that do not inject SessionManager. */
353
397
  codexSessionFileAdapter = new CodexSessionFileAdapter();
@@ -389,6 +433,7 @@ export class CodexRunner {
389
433
  if (this.resolvedConfig.effort)
390
434
  this.effort = this.resolvedConfig.effort;
391
435
  this.onSessionIdUpdate = callbacks.onSessionIdUpdate;
436
+ this.codexVersion = runtime?.codexVersion;
392
437
  }
393
438
  getAppServerClient(fullAccess = false) {
394
439
  const existing = fullAccess ? this.fullAccessAppServerClient : this.appServerClient;
@@ -396,6 +441,7 @@ export class CodexRunner {
396
441
  return existing;
397
442
  const client = new CodexAppServerClient({
398
443
  apiKey: this.resolvedConfig.apiKey,
444
+ codexVersion: this.codexVersion,
399
445
  authType: this.resolvedConfig.authType,
400
446
  baseUrl: this.resolvedConfig.baseUrl,
401
447
  provider: this.resolvedConfig.provider,
@@ -406,6 +452,7 @@ export class CodexRunner {
406
452
  enableRequestUserInput: this.resolvedConfig.enableRequestUserInput,
407
453
  approvalsReviewer: 'user',
408
454
  protectEvolcoreFilesystem: !fullAccess,
455
+ selfAid: this.resolvedConfig.agentAid,
409
456
  // A completed turn is explicitly unsubscribed below. This is only an
410
457
  // optimization on newer Codex versions; old versions retain the thread
411
458
  // until the cross-client archive fallback runs.
@@ -590,6 +637,13 @@ export class CodexRunner {
590
637
  this.threadSubscriptions.set(threadId, { client: targetClient, state: 'subscribed' });
591
638
  }
592
639
  approvedLiteralEvolcoreCommand(command, managedTempDir, dialect = 'posix', expectedCredentialToken, executable) {
640
+ if (/\\\$(?:TMPDIR|\{TMPDIR\})/i.test(command)
641
+ && /\bec\s+fs\s+(?:cp|ls|stat|find|cat|mkdir|rm|mv|upload)\b/i.test(command)) {
642
+ return { parseFailure: {
643
+ issue: 'unsafe-expansion', offset: 0, tokenIndex: 0,
644
+ dialect, inputLength: command.length,
645
+ } };
646
+ }
593
647
  const parseOptions = {
594
648
  allowManagedTmpDir: true,
595
649
  dialect,
@@ -621,11 +675,21 @@ export class CodexRunner {
621
675
  } };
622
676
  }
623
677
  return {
624
- argv: normalizeEvolcoreCommandArgv(parsed.argv),
678
+ argv: canonicalizeEvolcoreCommandArgv(parsed.argv),
625
679
  ...(parsed.credentialToken ? { credentialToken: parsed.credentialToken } : {}),
626
680
  };
627
681
  }
628
682
  resolveCanonicalEcApproval(toolInput, managedTempDir, expectedCredentialToken) {
683
+ const rawCommand = typeof toolInput.command === 'string' ? toolInput.command : '';
684
+ if (/\\\$(?:TMPDIR|\{TMPDIR\})/i.test(rawCommand)
685
+ && /\bec\s+fs\s+(?:cp|ls|stat|find|cat|mkdir|rm|mv|upload)\b/i.test(rawCommand)
686
+ && !/\bec\s+(?:msg|group)\s+send\b/i.test(rawCommand)
687
+ && !/\bec\s+trigger\s+(?:create|set|update)\b/i.test(rawCommand)) {
688
+ return { parseFailure: {
689
+ issue: 'unsafe-expansion', offset: 0, tokenIndex: 0,
690
+ dialect: 'posix', inputLength: rawCommand.length,
691
+ } };
692
+ }
629
693
  const explicitArgv = Array.isArray(toolInput.commandArgv)
630
694
  && toolInput.commandArgv.every(value => typeof value === 'string')
631
695
  ? toolInput.commandArgv
@@ -638,9 +702,9 @@ export class CodexRunner {
638
702
  dialect: 'posix', inputLength: 0,
639
703
  } };
640
704
  }
641
- return containsLiteralManagedTmpDirOutsideSendContent(explicitArgv)
705
+ return containsLiteralManagedTmpDirOutsideSendContent(explicitArgv) || containsUnresolvedManagedTmpDirReference(explicitArgv)
642
706
  ? { parseFailure: { issue: 'unsafe-expansion', offset: 0, tokenIndex: 0, dialect: 'posix', inputLength: 0 } }
643
- : { argv: normalizeEvolcoreCommandArgv(explicitArgv) };
707
+ : { argv: canonicalizeEvolcoreCommandArgv(explicitArgv) };
644
708
  }
645
709
  const explicitCarrier = explicitArgv
646
710
  ? resolveCodexShellCarrierArgv(explicitArgv)
@@ -648,6 +712,21 @@ export class CodexRunner {
648
712
  if (explicitCarrier !== undefined) {
649
713
  return this.approvedLiteralEvolcoreCommand(explicitCarrier.command, managedTempDir, explicitCarrier.dialect, expectedCredentialToken, explicitCarrier.executable);
650
714
  }
715
+ if (explicitArgv?.length === 1 && typeof explicitArgv[0] === 'string') {
716
+ const parsed = parseCodexToolCommand({ commandArgv: explicitArgv }, {
717
+ allowManagedTmpDir: true,
718
+ verifyShellExecutable: process.platform === 'win32',
719
+ ...(managedTempDir ? { managedTempDir } : {}),
720
+ ...(expectedCredentialToken !== undefined ? { expectedCredentialToken } : {}),
721
+ });
722
+ if (parsed.ok && parsed.argv[0] === 'ec') {
723
+ return containsLiteralManagedTmpDirOutsideSendContent(parsed.argv) || containsUnresolvedManagedTmpDirReference(parsed.argv)
724
+ ? { parseFailure: { issue: 'unsafe-expansion', offset: 0, tokenIndex: 0, dialect: 'powershell', inputLength: explicitArgv[0].length } }
725
+ : { argv: canonicalizeEvolcoreCommandArgv(parsed.argv) };
726
+ }
727
+ if (!parsed.ok)
728
+ return { parseFailure: parsed };
729
+ }
651
730
  if (hasCodexCmdCarrierEcIntent(toolInput)) {
652
731
  const parsed = parseCodexToolCommand(toolInput, {
653
732
  verifyShellExecutable: process.platform === 'win32',
@@ -656,13 +735,13 @@ export class CodexRunner {
656
735
  if (!parsed.ok)
657
736
  return { parseFailure: parsed };
658
737
  if (parsed.argv[0] === 'ec') {
659
- return containsLiteralManagedTmpDirOutsideSendContent(parsed.argv)
738
+ return containsLiteralManagedTmpDirOutsideSendContent(parsed.argv) || containsUnresolvedManagedTmpDirReference(parsed.argv)
660
739
  ? { parseFailure: {
661
740
  issue: 'unsafe-expansion', offset: 0, tokenIndex: 0,
662
741
  dialect: 'cmd', inputLength: 0,
663
742
  } }
664
743
  : {
665
- argv: normalizeEvolcoreCommandArgv(parsed.argv),
744
+ argv: canonicalizeEvolcoreCommandArgv(parsed.argv),
666
745
  ...(parsed.credentialToken ? { credentialToken: parsed.credentialToken } : {}),
667
746
  };
668
747
  }
@@ -686,7 +765,7 @@ export class CodexRunner {
686
765
  ...(expectedCredentialToken !== undefined ? { expectedCredentialToken } : {}),
687
766
  });
688
767
  if (canonicalCommand?.[0] === 'ec') {
689
- return containsLiteralManagedTmpDirOutsideSendContent(canonicalCommand)
768
+ return containsLiteralManagedTmpDirOutsideSendContent(canonicalCommand) || containsUnresolvedManagedTmpDirReference(canonicalCommand)
690
769
  ? { parseFailure: {
691
770
  issue: 'unsafe-expansion',
692
771
  offset: 0,
@@ -694,7 +773,7 @@ export class CodexRunner {
694
773
  dialect: 'posix',
695
774
  inputLength: command.length,
696
775
  } }
697
- : { argv: canonicalCommand };
776
+ : { argv: canonicalizeEvolcoreCommandArgv(canonicalCommand) };
698
777
  }
699
778
  const outer = parseLiteralShellCommand(command, {
700
779
  allowManagedTmpDir: true,
@@ -724,6 +803,10 @@ export class CodexRunner {
724
803
  || shellCarrier !== undefined
725
804
  && classifyEvolcoreShellCommand(shellCarrier.command, {
726
805
  dialect: shellCarrier.dialect,
806
+ }).kind !== 'none'
807
+ || rawArgv.length === 1
808
+ && classifyEvolcoreShellCommand(rawArgv[0], {
809
+ dialect: 'powershell',
727
810
  }).kind !== 'none';
728
811
  }
729
812
  const command = typeof toolInput.command === 'string' ? toolInput.command : '';
@@ -1195,8 +1278,15 @@ export class CodexRunner {
1195
1278
  enabled: true,
1196
1279
  unix_sockets: { [resolvePaths().instanceSocket]: 'allow' },
1197
1280
  };
1281
+ const relationAttachmentDenies = {};
1282
+ for (const directory of getExistingRelationAttachmentDirs(resolveRoot(), {
1283
+ selfAid: this.resolvedConfig.agentAid,
1284
+ onlyOtherAgents: true,
1285
+ }))
1286
+ relationAttachmentDenies[directory] = 'deny';
1198
1287
  const filesystem = {
1199
1288
  ...buildCodexProtectedFilesystemRules(resolveRoot(), {
1289
+ selfAid: this.resolvedConfig.agentAid,
1200
1290
  denyLClassRead: mode === 'readonly',
1201
1291
  // The app-server itself is launched behind EvolCore's Linux
1202
1292
  // H-class guard, which already mounts every L-class directory
@@ -1212,6 +1302,7 @@ export class CodexRunner {
1212
1302
  ...(personalMdPath ? {
1213
1303
  [path.resolve(personalMdPath)]: mode === 'readonly' ? 'read' : 'write',
1214
1304
  } : {}),
1305
+ ...relationAttachmentDenies,
1215
1306
  ...(mode !== 'readonly' && managedTempDir ? {
1216
1307
  [managedTempDir]: 'write',
1217
1308
  [managedTempDir + '/**']: 'write',
@@ -1372,6 +1463,7 @@ export class CodexRunner {
1372
1463
  const normalizedInput = toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput)
1373
1464
  ? toolInput
1374
1465
  : {};
1466
+ const isShellTool = toolName === 'Bash' || toolName === 'Shell' || toolName === 'exec_command';
1375
1467
  const recordPreToolUseDenial = async (policyCode, reason) => {
1376
1468
  const summary = summarizeToolInputForAudit(toolName, normalizedInput).slice(0, 512);
1377
1469
  auditToolPreflightDenial({
@@ -1446,6 +1538,28 @@ export class CodexRunner {
1446
1538
  await recordPreToolUseDenial('fullaccess_context_untrusted', reason);
1447
1539
  return { ok: false, applicable: true, decision: 'deny', reason };
1448
1540
  }
1541
+ const preflightProjectPath = this.threadProjectPaths.get(threadId)
1542
+ ?? (activeThread ? this.threadProjectPaths.get(activeThread) : undefined)
1543
+ ?? process.cwd();
1544
+ const preflight = evaluateToolPreflight(isShellTool ? 'Bash' : toolName, normalizedInput, {
1545
+ sessionId: sessionKey,
1546
+ managedTempDir: context.managedTempDir ?? this.getManagedTempDir(sessionKey) ?? '',
1547
+ sessionCredential: sessionToken,
1548
+ allowProtectedMetadata: false,
1549
+ selfAid: context.selfAid,
1550
+ channel: context.channel,
1551
+ userId: context.userId,
1552
+ role: context.role,
1553
+ permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
1554
+ allowReadonlySourceDiagnostics: context.allowReadonlySourceDiagnostics === true,
1555
+ projectPath: preflightProjectPath,
1556
+ workspacePath: preflightProjectPath,
1557
+ });
1558
+ if (preflight.behavior === 'deny') {
1559
+ const reason = preflight.message ?? 'tool preflight denied';
1560
+ await recordPreToolUseDenial(preflight.policyCode ?? 'tool_preflight_denied', reason);
1561
+ return { ok: true, applicable: true, decision: 'deny', reason };
1562
+ }
1449
1563
  // A tracked interactive thread still needs an authenticated, explicit
1450
1564
  // allow so the managed hook can fail closed when no runner matches.
1451
1565
  if (!context.preToolUsePolicyHook)
@@ -1687,7 +1801,7 @@ export class CodexRunner {
1687
1801
  }
1688
1802
  this.chatModes.set(sessionId, callMode);
1689
1803
  const callApprovalPolicy = this.toApprovalPolicy(callMode);
1690
- const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath, modelOverride?.personalMdPath);
1804
+ const executionSandbox = this.buildExecutionSandboxOptions(sessionId, callMode, projectPath, modelOverride?.bootstrapAgentMdPath, modelOverride?.personalMdPath, undefined);
1691
1805
  const appServer = this.getAppServerClient(callMode === 'fullaccess');
1692
1806
  this.dropExpiredPendingInterrupt(sessionId);
1693
1807
  const effectiveApprovalPolicy = callApprovalPolicy;
@@ -1816,12 +1930,11 @@ export class CodexRunner {
1816
1930
  this.bindExternalToolBoundary(threadId, externalToolConfig);
1817
1931
  const controller = new AbortController();
1818
1932
  this.activeAbortControllers.set(sessionId, controller);
1819
- const tempFiles = [];
1820
1933
  const answer = modelOverride?.asyncQuestionAnswer;
1821
1934
  if (answer && answer.providerThreadId !== threadId) {
1822
1935
  throw new Error('Async question reply cannot resume a different Codex thread');
1823
1936
  }
1824
- const input = this.buildAppServerInput(answer ? formatAsyncQuestionAnswer(answer) : prompt, images, tempFiles, sessionId, runtimeEnv);
1937
+ const input = this.buildAppServerInput(answer ? formatAsyncQuestionAnswer(answer) : prompt, images);
1825
1938
  const queue = new AsyncEventQueue();
1826
1939
  controller.signal.addEventListener('abort', () => queue.end(), { once: true });
1827
1940
  const state = {
@@ -1908,7 +2021,7 @@ export class CodexRunner {
1908
2021
  if (!resumingThread)
1909
2022
  this.cleanupProvisionalThread(sessionId, threadId);
1910
2023
  logger.info(`[CodexRunner] Applied pending interrupt before turn start: ${sessionId}`);
1911
- return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, appServer, tempFiles);
2024
+ return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, appServer);
1912
2025
  }
1913
2026
  try {
1914
2027
  const turnResponse = await appServer.turnStart(threadId, input, {
@@ -1990,12 +2103,11 @@ export class CodexRunner {
1990
2103
  this.activeAbortControllers.delete(sessionId);
1991
2104
  this.activeTurns.delete(sessionId);
1992
2105
  this.pendingInterrupts.delete(sessionId);
1993
- this.cleanupTempFiles(tempFiles);
1994
2106
  if (!resumingThread)
1995
2107
  this.cleanupProvisionalThread(sessionId, threadId);
1996
2108
  throw error;
1997
2109
  }
1998
- return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, appServer, tempFiles);
2110
+ return this.transformAppServerStream(queue, sessionId, state, controller, unsubscribe, appServer);
1999
2111
  }
2000
2112
  // ── Interrupt ──
2001
2113
  async interrupt(sessionKey) {
@@ -2444,15 +2556,9 @@ export class CodexRunner {
2444
2556
  // They follow the thread (not the daemon process) so a daemon restart does
2445
2557
  // not strand a resumable thread: the token file is re-read on resume, and
2446
2558
  // it is removed lazily once the rollout file itself disappears.
2447
- threadCredentialsDir() {
2448
- const aid = this.resolvedConfig.agentAid;
2449
- if (!aid)
2450
- return undefined;
2451
- return path.join(agentSessionsDir(aid), 'codex-tokens');
2452
- }
2453
2559
  threadCredentialPath(threadId) {
2454
- const dir = this.threadCredentialsDir();
2455
- return dir ? path.join(dir, `${encodeURIComponent(threadId)}.json`) : undefined;
2560
+ const aid = this.resolvedConfig.agentAid;
2561
+ return aid ? agentCodexTokenPath(aid, threadId) : undefined;
2456
2562
  }
2457
2563
  createThreadCredential() {
2458
2564
  return randomBytes(32).toString('hex');
@@ -2592,7 +2698,7 @@ export class CodexRunner {
2592
2698
  const appServer = this.getAppServerClient();
2593
2699
  const history = await this.readForkHistory(appServer, agentSessionId);
2594
2700
  const messages = this.mapThreadToSessionMessages(history, agentSessionId);
2595
- const turns = buildSessionTurnList(messages);
2701
+ const turns = buildTranscriptTurns(messages);
2596
2702
  const targetIndex = turns.findIndex(turn => turn.assistantUuid === assistantMessageId);
2597
2703
  if (targetIndex < 0)
2598
2704
  throw new Error('fork point not found');
@@ -2610,7 +2716,7 @@ export class CodexRunner {
2610
2716
  if (typeof turn.id !== 'string' || !turn.id)
2611
2717
  continue;
2612
2718
  for (const item of this.getTurnItems(turn)) {
2613
- if (item.type === 'agentMessage' && item.id === assistantMessageId)
2719
+ if (item.id === assistantMessageId)
2614
2720
  return turn.id;
2615
2721
  }
2616
2722
  // Older app-server histories may omit item ids; the mapper then uses the
@@ -3016,7 +3122,7 @@ export class CodexRunner {
3016
3122
  ? this.resolveCanonicalEcApproval(toolInput, this.getManagedTempDir(sessionKey), sessionCredential)
3017
3123
  : undefined;
3018
3124
  const approvedCommand = canonicalEcApproval?.argv;
3019
- const denyInfrastructure = async (policyCode, message) => {
3125
+ const denyInfrastructure = async (policyCode, message, errorDetailCode) => {
3020
3126
  logger.warn(`[CodexRunner] ${message}: session=${sessionKey} requestId=${request.id ?? '<missing>'}`);
3021
3127
  try {
3022
3128
  await permissionContext?.recordExecutionAnomaly?.({
@@ -3044,6 +3150,7 @@ export class CodexRunner {
3044
3150
  decision: 'deny',
3045
3151
  decisionSource: 'infrastructure',
3046
3152
  policyCode,
3153
+ ...(errorDetailCode ? { errorDetailCode } : {}),
3047
3154
  reason: message,
3048
3155
  });
3049
3156
  this.logAppServerApprovalAudit(request, sessionKey, toolName, 'deny', 'infrastructure', policyCode, message, canonicalEcApproval?.parseFailure);
@@ -3054,15 +3161,28 @@ export class CodexRunner {
3054
3161
  if (canonicalEcApproval?.parseFailure?.issue === 'untrusted-shell') {
3055
3162
  return await denyInfrastructure('ec_shell_untrusted_executable', 'EC command approval denied because the shell executable failed Windows Authenticode trust validation');
3056
3163
  }
3164
+ if (canonicalEcApproval?.parseFailure?.issue === 'unsupported-escape') {
3165
+ return await denyInfrastructure('ec_shell_unsupported_escape', 'EC inline text does not support PowerShell `n. Use literal \\n in the argument instead.');
3166
+ }
3167
+ if (canonicalEcApproval?.parseFailure?.issue === 'lossy-cmd-shim') {
3168
+ return await denyInfrastructure('ec_shell_lossy_cmd_shim', 'PowerShell cannot pass physical multiline EC arguments through ec.cmd intact. Use ec ... via the PowerShell-native shim instead.');
3169
+ }
3057
3170
  return await denyInfrastructure('ec_command_not_canonical', canonicalEcApproval?.parseFailure
3058
3171
  ? `EC command approval denied because canonical argv parsing failed (${canonicalEcApproval.parseFailure.issue})`
3059
- : 'EC command approval denied because no canonical argv could be derived');
3172
+ + (canonicalEcApproval.parseFailure.dialect === 'powershell' && canonicalEcApproval.parseFailure.issue === 'invalid-quote'
3173
+ ? '. PowerShell treats smart quotes as delimiters too; escape quotes for PowerShell or use --text-from-file for message text.'
3174
+ : '')
3175
+ : 'EC command approval denied because no canonical argv could be derived', canonicalEcApproval?.parseFailure?.issue === 'invalid-quote' ? 'INVALID_QUOTE' : undefined);
3060
3176
  }
3061
3177
  try {
3062
3178
  const resolution = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd, request.id, typeof params.itemId === 'string' ? params.itemId : typeof params.item_id === 'string' ? params.item_id : undefined);
3063
3179
  const decision = typeof resolution === 'string' ? resolution : resolution.decision;
3064
3180
  const resolutionMeta = typeof resolution === 'string' ? undefined : resolution;
3065
3181
  if (decision !== 'deny' && managedEcIntent && approvedCommand) {
3182
+ if (containsLiteralManagedTmpDirOutsideSendContent(approvedCommand)
3183
+ || containsUnresolvedManagedTmpDirReference(approvedCommand)) {
3184
+ return await denyInfrastructure('ec_command_unsafe_tmpdir_reference', 'EC command contains an unresolved or unsafe managed TMPDIR reference');
3185
+ }
3066
3186
  const credentialThreadIds = [
3067
3187
  this.activeSessions.get(sessionKey),
3068
3188
  this.provisionalSessions.get(sessionKey),
@@ -3097,6 +3217,7 @@ export class CodexRunner {
3097
3217
  decision: 'deny',
3098
3218
  decisionSource,
3099
3219
  ...(policyCode ? { policyCode } : {}),
3220
+ ...(resolutionMeta?.errorDetailCode ? { errorDetailCode: resolutionMeta.errorDetailCode } : {}),
3100
3221
  reason: denialReason,
3101
3222
  });
3102
3223
  setImmediate(() => this.steerPolicyReason(sessionKey, policyCode, denialReason));
@@ -3140,6 +3261,7 @@ export class CodexRunner {
3140
3261
  decision,
3141
3262
  decisionSource,
3142
3263
  policyCode,
3264
+ ...(parseFailure?.issue === 'invalid-quote' ? { errorDetailCode: 'INVALID_QUOTE' } : {}),
3143
3265
  permissionMode: normalizeExecutionPermissionMode(this.chatModes.get(sessionKey) ?? this.currentMode),
3144
3266
  role: context?.role,
3145
3267
  taskId: context?.taskId,
@@ -3661,6 +3783,7 @@ export class CodexRunner {
3661
3783
  channel: permCtx?.channel,
3662
3784
  peerId: permCtx?.userId,
3663
3785
  role: permCtx?.role,
3786
+ selfAid: permCtx?.selfAid ?? this.resolvedConfig.agentAid,
3664
3787
  allowProtectedMetadata: false,
3665
3788
  // Revalidate ownership, mode, and parent containment at the final
3666
3789
  // readonly decision. The map entry alone is stale if the directory was
@@ -3769,9 +3892,17 @@ export class CodexRunner {
3769
3892
  && this.hasNetworkPermissionRequest(toolInput);
3770
3893
  const hasUnknownPermissions = (toolName === 'Bash' || toolName === 'PermissionGrant')
3771
3894
  && this.hasUnknownPermissionRequest(toolInput);
3895
+ const applyPatch = (toolName === 'Bash' || toolName === 'Shell' || toolName === 'exec_command')
3896
+ ? parseShellApplyPatchInvocation(toolInput)
3897
+ : { kind: 'not-apply-patch' };
3898
+ const applyPatchFileChanges = applyPatch.kind === 'file-change' ? applyPatch.fileChanges : undefined;
3899
+ const applyPatchInput = applyPatchFileChanges
3900
+ ? { ...toolInput, fileChanges: applyPatchFileChanges }
3901
+ : toolInput;
3772
3902
  const hasPermissionExpansion = hasFilesystemPermissions || hasNetworkPermissions || hasUnknownPermissions;
3773
3903
  const hasFileChangeExpansion = toolName === 'FileChange'
3774
- && this.fileChangeRequiresExpansion(toolInput, workspacePath, sessionKey);
3904
+ ? this.fileChangeRequiresExpansion(toolInput, workspacePath, sessionKey)
3905
+ : !!applyPatchFileChanges && this.fileChangeRequiresExpansion(applyPatchInput, workspacePath, sessionKey);
3775
3906
  // per-session 权限模式(runQuery 写入);缺省回落实例级 currentMode(兼容无 runQuery 上下文的调用)
3776
3907
  const rawMode = this.chatModes.get(sessionKey) ?? this.currentMode;
3777
3908
  const mode = normalizeExecutionPermissionMode(rawMode);
@@ -3850,12 +3981,17 @@ export class CodexRunner {
3850
3981
  decision: 'deny',
3851
3982
  decisionSource: 'policy',
3852
3983
  policyCode: preflight.policyCode ?? 'tool_preflight_denied',
3984
+ ...(preflight.errorDetailCode ? { errorDetailCode: preflight.errorDetailCode } : {}),
3853
3985
  reason: preflight.message ?? 'tool preflight denied',
3854
3986
  };
3855
3987
  }
3856
3988
  if (preflight.behavior === 'allow')
3857
3989
  return 'allow';
3858
3990
  const checkedInput = preflight.input;
3991
+ const policyInput = applyPatchFileChanges
3992
+ ? { ...checkedInput, fileChanges: applyPatchFileChanges }
3993
+ : checkedInput;
3994
+ const policyToolName = applyPatchFileChanges ? 'FileChange' : toolName;
3859
3995
  if (toolName === 'FileChange') {
3860
3996
  const hasGrantRoot = typeof checkedInput.grantRoot === 'string'
3861
3997
  && checkedInput.grantRoot.length > 0;
@@ -3875,9 +4011,9 @@ export class CodexRunner {
3875
4011
  return 'deny';
3876
4012
  const grantScope = `codex:${this.permissionProfileName(sessionKey)}:${mode}`;
3877
4013
  if (mode === 'readonly') {
3878
- const readonly = this.checkCodexReadonly(toolName, checkedInput, operationCwd, sessionKey);
4014
+ const readonly = this.checkCodexReadonly(policyToolName, policyInput, operationCwd, sessionKey);
3879
4015
  if (readonly.behavior === 'deny') {
3880
- await recordBlockedOperation(readonly.policyCode ?? 'readonly_mode', checkedInput, readonly.message);
4016
+ await recordBlockedOperation(readonly.policyCode ?? 'readonly_mode', policyInput, readonly.message);
3881
4017
  return 'deny';
3882
4018
  }
3883
4019
  return 'allow';
@@ -3888,7 +4024,7 @@ export class CodexRunner {
3888
4024
  // auto 模式下检查危险命令,危险命令自动拒绝
3889
4025
  if (mode === 'auto') {
3890
4026
  if (hasFileChangeExpansion || hasFilesystemPermissions || hasUnknownPermissions) {
3891
- await recordBlockedOperation('auto_permission_expansion', checkedInput);
4027
+ await recordBlockedOperation('auto_permission_expansion', policyInput);
3892
4028
  return 'deny';
3893
4029
  }
3894
4030
  if (toolName === 'PermissionGrant' && !hasNetworkPermissions) {
@@ -3908,8 +4044,8 @@ export class CodexRunner {
3908
4044
  if (requiresSandboxExpansionApproval) {
3909
4045
  if (!this.permissionGateway || !permissionPrompt)
3910
4046
  return 'deny';
3911
- if (toolName === 'FileChange') {
3912
- return this.permissionGateway.requestPermission(sessionKey, toolName, checkedInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope, undefined, { fileChangeCwd: workspacePath });
4047
+ if (policyToolName === 'FileChange') {
4048
+ return this.permissionGateway.requestPermission(sessionKey, policyToolName, policyInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope, undefined, { fileChangeCwd: workspacePath });
3913
4049
  }
3914
4050
  return this.permissionGateway.requestPermission(sessionKey, toolName, checkedInput, permissionPrompt, this.permissionContexts.get(sessionKey), summary, reason || 'Codex 请求扩大当前命令或 turn 的文件系统权限', grantScope);
3915
4051
  }
@@ -3954,20 +4090,80 @@ export class CodexRunner {
3954
4090
  }
3955
4091
  throw new Error('Unsupported Codex app-server request: ' + method);
3956
4092
  }
3957
- async rollbackSessionTurns(agentSessionId, _projectPath, numTurns) {
3958
- const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'rollback');
4093
+ async rollbackSessionTurns(agentSessionId, projectPath, numTurns, targetAssistantMessageId) {
4094
+ const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'resume+rollback');
3959
4095
  try {
3960
- return await this.rollbackSessionTurnsLocked(agentSessionId, numTurns);
4096
+ return await this.rollbackSessionTurnsLocked(agentSessionId, projectPath, numTurns, targetAssistantMessageId);
3961
4097
  }
3962
4098
  finally {
3963
4099
  release();
3964
4100
  }
3965
4101
  }
3966
- async rollbackSessionTurnsLocked(agentSessionId, numTurns) {
4102
+ async rollbackSessionTurnsLocked(agentSessionId, projectPath, numTurns, targetAssistantMessageId) {
3967
4103
  if (numTurns < 1)
3968
4104
  return true;
3969
- const response = await this.getAppServerClientForThread(agentSessionId).threadRollback(agentSessionId, numTurns);
3970
- return !!response.thread;
4105
+ const appServer = this.getAppServerClientForThread(agentSessionId);
4106
+ const previousAppServer = await this.prepareThreadSubscription(appServer, agentSessionId);
4107
+ let resumed = false;
4108
+ try {
4109
+ let resumeResponse;
4110
+ try {
4111
+ resumeResponse = await appServer.threadResume(agentSessionId, projectPath);
4112
+ }
4113
+ catch (error) {
4114
+ if (isCodexActiveWriterError(error) && previousAppServer && previousAppServer !== appServer) {
4115
+ await this.archiveThreadForHandoff(previousAppServer, appServer, agentSessionId);
4116
+ resumeResponse = await appServer.threadResume(agentSessionId, projectPath);
4117
+ }
4118
+ else {
4119
+ throw error;
4120
+ }
4121
+ }
4122
+ this.markThreadSubscribed(appServer, agentSessionId);
4123
+ this.threadProjectPaths.set(agentSessionId, path.resolve(projectPath));
4124
+ resumed = true;
4125
+ if (resumeResponse.thread?.historyMode === 'paginated') {
4126
+ const beforeTurnId = await this.findPaginatedRewindTurnId(appServer, agentSessionId, numTurns, targetAssistantMessageId);
4127
+ const response = await appServer.threadRevert(agentSessionId, beforeTurnId);
4128
+ return !!response.thread;
4129
+ }
4130
+ const response = await appServer.threadRollback(agentSessionId, numTurns);
4131
+ return !!response.thread;
4132
+ }
4133
+ finally {
4134
+ if (resumed) {
4135
+ await this.releaseThreadSubscription(appServer, agentSessionId, 'rollback-finished');
4136
+ }
4137
+ }
4138
+ }
4139
+ async findPaginatedRewindTurnId(appServer, threadId, numTurns, targetAssistantMessageId) {
4140
+ if (targetAssistantMessageId) {
4141
+ const history = await this.readForkHistory(appServer, threadId);
4142
+ const targetTurnId = this.findTurnIdForAssistantMessage(history, targetAssistantMessageId);
4143
+ if (!targetTurnId)
4144
+ throw new Error('Codex paginated rewind target turn not found');
4145
+ return targetTurnId;
4146
+ }
4147
+ let cursor;
4148
+ let seenCursor;
4149
+ let seenTurns = 0;
4150
+ do {
4151
+ const page = await appServer.threadTurnsList(threadId, 100, cursor);
4152
+ for (const turn of page.data ?? []) {
4153
+ if (seenTurns === numTurns - 1) {
4154
+ if (typeof turn.id !== 'string' || !turn.id)
4155
+ throw new Error('Codex paginated thread rewind target has no turn id');
4156
+ return turn.id;
4157
+ }
4158
+ seenTurns++;
4159
+ }
4160
+ const nextCursor = typeof page.nextCursor === 'string' && page.nextCursor ? page.nextCursor : undefined;
4161
+ if (!nextCursor || nextCursor === seenCursor)
4162
+ break;
4163
+ seenCursor = nextCursor;
4164
+ cursor = nextCursor;
4165
+ } while (true);
4166
+ throw new Error('Codex paginated thread has fewer than ' + numTurns + ' turns');
3971
4167
  }
3972
4168
  async rewindFiles(agentSessionId, projectPath, userMessageId) {
3973
4169
  const messages = await this.getSessionMessages(agentSessionId, projectPath);
@@ -4036,6 +4232,26 @@ export class CodexRunner {
4036
4232
  parent_tool_use_id: null,
4037
4233
  });
4038
4234
  }
4235
+ else if (item.id && [
4236
+ 'commandExecution', 'mcpToolCall', 'dynamicToolCall', 'fileChange',
4237
+ 'webSearch', 'collabAgentToolCall', 'imageView', 'sleep', 'imageGeneration',
4238
+ ].includes(item.type || '')) {
4239
+ messages.push({
4240
+ type: 'assistant',
4241
+ uuid: item.id,
4242
+ session_id: threadId,
4243
+ message: { role: 'assistant', content: this.mapCodexToolItemToContent(item) },
4244
+ parent_tool_use_id: null,
4245
+ });
4246
+ if (item.type === 'fileChange')
4247
+ messages.push({
4248
+ type: 'system',
4249
+ uuid: `${item.id}-file-change`,
4250
+ session_id: threadId,
4251
+ message: { role: 'system', content: this.mapFileChangeToContent(item) },
4252
+ parent_tool_use_id: null,
4253
+ });
4254
+ }
4039
4255
  else if (item.type === 'file_change') {
4040
4256
  messages.push({
4041
4257
  type: 'system',
@@ -4049,6 +4265,86 @@ export class CodexRunner {
4049
4265
  }
4050
4266
  return messages;
4051
4267
  }
4268
+ /** Preserve the same bounded display data that live tool cards receive. */
4269
+ mapCodexToolItemToContent(item) {
4270
+ const id = typeof item.id === 'string' ? item.id : undefined;
4271
+ const type = item.type || 'tool';
4272
+ let name = type;
4273
+ let input;
4274
+ let result;
4275
+ let isError = false;
4276
+ switch (type) {
4277
+ case 'commandExecution':
4278
+ name = 'Shell';
4279
+ input = { command: item.command, cwd: item.cwd };
4280
+ result = item.aggregatedOutput ?? item.output ?? '';
4281
+ isError = item.exitCode != null
4282
+ ? Number(item.exitCode) !== 0
4283
+ : item.status === 'failed' || item.status === 'declined';
4284
+ break;
4285
+ case 'mcpToolCall':
4286
+ name = `MCP:${item.server}/${item.tool}`;
4287
+ input = item.arguments;
4288
+ result = item.result ?? item.contentItems;
4289
+ isError = item.status === 'failed';
4290
+ break;
4291
+ case 'dynamicToolCall':
4292
+ name = item.namespace ? `${item.namespace}:${item.tool}` : String(item.tool || 'tool');
4293
+ input = item.arguments;
4294
+ result = item.result ?? item.contentItems;
4295
+ isError = item.success === false || item.status === 'failed';
4296
+ break;
4297
+ case 'fileChange':
4298
+ name = 'FileChange';
4299
+ input = { changes: item.changes };
4300
+ result = item.changes;
4301
+ isError = item.status === 'failed';
4302
+ break;
4303
+ case 'webSearch':
4304
+ name = 'WebSearch';
4305
+ input = { query: item.query, ...(item.action != null ? { action: item.action } : {}) };
4306
+ result = input;
4307
+ break;
4308
+ case 'collabAgentToolCall':
4309
+ name = `Collab:${item.tool}`;
4310
+ input = {
4311
+ receiverThreadIds: item.receiverThreadIds,
4312
+ ...(item.prompt != null ? { prompt: item.prompt } : {}),
4313
+ ...(item.model != null ? { model: item.model } : {}),
4314
+ ...(item.reasoningEffort != null ? { reasoningEffort: item.reasoningEffort } : {}),
4315
+ };
4316
+ result = item.contentItems ?? { receiverThreadIds: item.receiverThreadIds, agentsStates: item.agentsStates };
4317
+ isError = item.status === 'failed';
4318
+ break;
4319
+ case 'imageView':
4320
+ name = 'ViewImage';
4321
+ input = { path: item.path };
4322
+ result = input;
4323
+ break;
4324
+ case 'sleep':
4325
+ name = 'Sleep';
4326
+ input = { durationMs: item.durationMs };
4327
+ result = input;
4328
+ break;
4329
+ case 'imageGeneration':
4330
+ name = 'ImageGeneration';
4331
+ input = item.revisedPrompt != null ? { prompt: item.revisedPrompt } : {};
4332
+ result = item.result ?? item.output;
4333
+ break;
4334
+ default:
4335
+ input = item.input;
4336
+ result = item.result ?? item.output;
4337
+ }
4338
+ return [
4339
+ { type: 'tool_use', id, name, input },
4340
+ {
4341
+ type: 'tool_result',
4342
+ ...(id ? { tool_use_id: id } : {}),
4343
+ content: result,
4344
+ is_error: isError,
4345
+ },
4346
+ ];
4347
+ }
4052
4348
  getTurnItems(turn) {
4053
4349
  const items = Array.isArray(turn?.items) ? turn.items : [];
4054
4350
  const input = Array.isArray(turn?.input) ? turn.input : [];
@@ -4117,36 +4413,18 @@ export class CodexRunner {
4117
4413
  this.onCompactStart = callback;
4118
4414
  }
4119
4415
  // ── Event stream transformation ──
4120
- buildAppServerInput(prompt, images, tempFiles, sessionId, runtimeEnv) {
4416
+ buildAppServerInput(prompt, images) {
4121
4417
  const input = [{ type: 'text', text: prompt, text_elements: [] }];
4122
4418
  if (!images?.length)
4123
4419
  return input;
4124
- const tmpDir = getManagedTaskTempDir(runtimeEnv)
4125
- ?? runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR
4126
- ?? (sessionId ? this.getManagedTempDir(sessionId) : undefined)
4127
- ?? process.env.TMPDIR;
4128
- if (!tmpDir || !path.isAbsolute(tmpDir))
4129
- throw new Error('managed TMPDIR is unset or not absolute');
4130
- for (let i = 0; i < images.length; i++) {
4131
- const img = images[i];
4132
- const ext = MIME_EXT[img.mimeType || ''] || '.jpg';
4133
- const tmpPath = path.join(tmpDir, `evolcore-img-${Date.now()}-${i}${ext}`);
4134
- fs.writeFileSync(tmpPath, Buffer.from(img.data, 'base64'));
4135
- tempFiles.push(tmpPath);
4136
- input.push({ type: 'localImage', path: tmpPath });
4137
- }
4138
- logger.info(`[CodexRunner] Attached ${images.length} image(s) as localImage`);
4139
- return input;
4140
- }
4141
- cleanupTempFiles(tempFiles) {
4142
- if (!tempFiles?.length)
4143
- return;
4144
- for (const tempFile of tempFiles) {
4145
- try {
4146
- fs.unlinkSync(tempFile);
4147
- }
4148
- catch { /* ignore */ }
4420
+ for (const image of images) {
4421
+ input.push({
4422
+ type: 'image',
4423
+ url: `data:${image.mimeType || 'image/png'};base64,${image.data}`,
4424
+ });
4149
4425
  }
4426
+ logger.info(`[CodexRunner] Attached ${images.length} image(s) as base64 data URL`);
4427
+ return input;
4150
4428
  }
4151
4429
  extractTurnId(notification) {
4152
4430
  const params = notification.params || {};
@@ -4267,7 +4545,7 @@ export class CodexRunner {
4267
4545
  }
4268
4546
  queue.end();
4269
4547
  }
4270
- async *transformAppServerStream(notifications, sessionId, state, controller, unsubscribe, appServer, tempFiles) {
4548
+ async *transformAppServerStream(notifications, sessionId, state, controller, unsubscribe, appServer) {
4271
4549
  try {
4272
4550
  let sessionIdEmitted = state.committed;
4273
4551
  if (sessionIdEmitted)
@@ -4321,7 +4599,6 @@ export class CodexRunner {
4321
4599
  for (const childThreadId of state.activeSubagentThreads)
4322
4600
  this.unregisterChildThread(childThreadId);
4323
4601
  state.activeSubagentThreads.clear();
4324
- this.cleanupTempFiles(tempFiles);
4325
4602
  }
4326
4603
  }
4327
4604
  *mapAppServerNotification(notification, sessionId, state) {
@@ -4339,7 +4616,8 @@ export class CodexRunner {
4339
4616
  case 'item/started': {
4340
4617
  for (const event of this.mapAppServerItemStarted(params.item, state)) {
4341
4618
  this.trackAppServerToolLifecycle(event, state);
4342
- yield event;
4619
+ yield event.type === 'tool_use' && params.item?.id
4620
+ ? { ...event, assistantUuid: params.item.id } : event;
4343
4621
  }
4344
4622
  break;
4345
4623
  }
@@ -4371,7 +4649,8 @@ export class CodexRunner {
4371
4649
  state.completedItemIds.add(item.id);
4372
4650
  for (const event of this.mapAppServerItemCompleted(item, state)) {
4373
4651
  this.trackAppServerToolLifecycle(event, state);
4374
- yield event;
4652
+ yield event.type === 'tool_result' && item?.id
4653
+ ? { ...event, assistantUuid: item.id } : event;
4375
4654
  }
4376
4655
  break;
4377
4656
  }
@@ -4603,6 +4882,9 @@ export class CodexRunner {
4603
4882
  isError: true,
4604
4883
  error: denial.reason,
4605
4884
  errorCode: errorCodeForToolDenial(denial.decisionSource, denial.policyCode),
4885
+ ...(errorDetailCodeForToolDenial(denial.decisionSource, denial.policyCode, denial.errorDetailCode)
4886
+ ? { errorDetailCode: errorDetailCodeForToolDenial(denial.decisionSource, denial.policyCode, denial.errorDetailCode) }
4887
+ : {}),
4606
4888
  decision: 'deny',
4607
4889
  decisionSource: denial.decisionSource,
4608
4890
  policyCode: denial.policyCode,
@@ -4629,6 +4911,9 @@ export class CodexRunner {
4629
4911
  isError: true,
4630
4912
  error: managedHookDenial.reason,
4631
4913
  errorCode: 'POLICY_DENIED',
4914
+ ...(errorDetailCodeForToolDenial('policy', managedHookDenial.policyCode, managedHookDenial.errorDetailCode)
4915
+ ? { errorDetailCode: errorDetailCodeForToolDenial('policy', managedHookDenial.policyCode, managedHookDenial.errorDetailCode) }
4916
+ : {}),
4632
4917
  decision: 'deny',
4633
4918
  decisionSource: 'policy',
4634
4919
  policyCode: managedHookDenial.policyCode,
@@ -4680,24 +4965,39 @@ export class CodexRunner {
4680
4965
  const text = typeof item.text === 'string' && item.text ? item.text : buffered;
4681
4966
  if (text) {
4682
4967
  state.lastAssistantUuid = item.id || state.turnId;
4683
- yield { type: 'text', text, assistantUuid: state.lastAssistantUuid };
4968
+ yield {
4969
+ type: 'text', text, assistantUuid: state.lastAssistantUuid,
4970
+ ...(item.phase === 'commentary' || item.phase === 'final_answer' ? { phase: item.phase } : {}),
4971
+ };
4684
4972
  }
4685
4973
  if (item.id)
4686
4974
  state.agentMessageDeltaText.delete(item.id);
4687
4975
  }
4688
4976
  break;
4689
4977
  case 'commandExecution':
4690
- yield {
4691
- type: 'tool_result',
4692
- name: 'Shell',
4693
- result: item.aggregatedOutput ?? '',
4694
- ...(blockedFields ?? {
4695
- isError: item.exitCode !== null && item.exitCode !== undefined ? item.exitCode !== 0 : item.status === 'failed' || item.status === 'declined',
4696
- ...(item.error?.code || item.error?.errorCode ? { errorCode: String(item.error.code ?? item.error.errorCode) } : {}),
4697
- }),
4698
- callId: item.id,
4699
- durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
4700
- };
4978
+ {
4979
+ const exitCode = typeof item.exitCode === 'number' && Number.isInteger(item.exitCode)
4980
+ ? item.exitCode
4981
+ : undefined;
4982
+ const isError = exitCode !== undefined
4983
+ ? exitCode !== 0
4984
+ : item.status === 'failed' || item.status === 'declined';
4985
+ yield {
4986
+ type: 'tool_result',
4987
+ name: 'Shell',
4988
+ result: item.aggregatedOutput ?? '',
4989
+ ...(blockedFields ?? {
4990
+ isError,
4991
+ ...codexItemErrorFields(item),
4992
+ executed: true,
4993
+ executionState: isError ? 'failed' : 'completed',
4994
+ ...(exitCode !== undefined ? { exitCode } : {}),
4995
+ }),
4996
+ ...(blockedFields ? {} : exitCode !== undefined ? { exitCode } : {}),
4997
+ callId: item.id,
4998
+ durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
4999
+ };
5000
+ }
4701
5001
  break;
4702
5002
  case 'mcpToolCall':
4703
5003
  yield {
@@ -4706,7 +5006,11 @@ export class CodexRunner {
4706
5006
  result: item.result,
4707
5007
  ...(blockedFields ?? { isError: item.status === 'failed' }),
4708
5008
  ...(blockedFields ? {} : { error: item.error?.message }),
4709
- ...(item.error?.code || item.error?.errorCode ? { errorCode: String(item.error.code ?? item.error.errorCode) } : {}),
5009
+ ...(blockedFields ? {} : codexItemErrorFields(item)),
5010
+ ...(blockedFields ?? {
5011
+ executed: true,
5012
+ executionState: item.status === 'failed' ? 'failed' : 'completed',
5013
+ }),
4710
5014
  callId: item.id,
4711
5015
  durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
4712
5016
  };
@@ -4717,7 +5021,11 @@ export class CodexRunner {
4717
5021
  name: item.namespace ? `${item.namespace}:${item.tool}` : item.tool,
4718
5022
  result: item.contentItems,
4719
5023
  ...(blockedFields ?? { isError: item.success === false || item.status === 'failed' }),
4720
- ...(item.error?.code || item.error?.errorCode ? { errorCode: String(item.error.code ?? item.error.errorCode) } : {}),
5024
+ ...(blockedFields ? {} : codexItemErrorFields(item)),
5025
+ ...(blockedFields ?? {
5026
+ executed: true,
5027
+ executionState: item.success === false || item.status === 'failed' ? 'failed' : 'completed',
5028
+ }),
4721
5029
  callId: item.id,
4722
5030
  durationMs: typeof item.durationMs === 'number' ? item.durationMs : undefined,
4723
5031
  };
@@ -4731,12 +5039,34 @@ export class CodexRunner {
4731
5039
  yield editEvent;
4732
5040
  }
4733
5041
  }
4734
- yield { type: 'tool_result', name: 'Edit', result: item.changes, ...(blockedFields ?? { isError: item.status === 'failed' }), callId: item.id };
5042
+ yield {
5043
+ type: 'tool_result',
5044
+ name: 'Edit',
5045
+ result: item.changes,
5046
+ ...(blockedFields ?? {
5047
+ isError: item.status === 'failed',
5048
+ executed: true,
5049
+ executionState: item.status === 'failed' ? 'failed' : 'completed',
5050
+ ...codexItemErrorFields(item),
5051
+ }),
5052
+ callId: item.id,
5053
+ };
4735
5054
  }
4736
5055
  else {
4737
5056
  const desc = this.normalizeFileChanges(item.changes).map((change) => this.describeFileChange(change)).join(', ');
4738
5057
  yield { type: 'tool_use', name: 'FileChange', input: { description: desc }, callId: item.id };
4739
- yield { type: 'tool_result', name: 'FileChange', result: item.changes, ...(blockedFields ?? { isError: item.status === 'failed' }), callId: item.id };
5058
+ yield {
5059
+ type: 'tool_result',
5060
+ name: 'FileChange',
5061
+ result: item.changes,
5062
+ ...(blockedFields ?? {
5063
+ isError: item.status === 'failed',
5064
+ executed: true,
5065
+ executionState: item.status === 'failed' ? 'failed' : 'completed',
5066
+ ...codexItemErrorFields(item),
5067
+ }),
5068
+ callId: item.id,
5069
+ };
4740
5070
  }
4741
5071
  break;
4742
5072
  case 'webSearch':
@@ -4941,7 +5271,7 @@ export class CodexRunner {
4941
5271
  const managedTempDir = ensureSessionRuntimeDir(sessionId);
4942
5272
  if (!managedTempDir)
4943
5273
  return undefined;
4944
- return `临时文件必须使用环境变量 \`$TMPDIR\`。当前值是 \`${managedTempDir}\`,它是当前 EvolCore 会话唯一允许的临时工作区;不要直接使用 \`/tmp\`,也不要使用其他会话的临时目录。该目录随 EvolCore sessionId 保持稳定;若文件不存在,请先在当前 \`$TMPDIR\` 重新创建,再继续执行后续 \`ec\` 命令。`;
5274
+ return `临时文件必须使用环境变量 \`$TMPDIR\`。当前值是 \`${managedTempDir}\`,它是当前 EvolCore 会话唯一允许的临时工作区;不要直接使用 \`/tmp\`,也不要使用其他会话的临时目录。该目录随 EvolCore sessionId 保持稳定;若文件不存在,请先在当前 \`$TMPDIR\` 重新创建,再继续执行后续 \`ec\` 命令。文件修改优先使用 Codex 原生 FileChange/Edit;若当前 Codex 版本未暴露该工具,可使用独立且可解析的 \`apply_patch\` patch。不得把 patch 与其它命令拼接、放入管道或使用任意重定向;目标文件仍受 workspace、H/L 路径和当前权限模式限制。`;
4945
5275
  }
4946
5276
  buildEvolcoreShellEnvironmentConfig(sessionId, sessionToken, taskDelegationToken) {
4947
5277
  const managedTempDir = ensureSessionRuntimeDir(sessionId);
@@ -5077,6 +5407,14 @@ export class CodexAgentPlugin {
5077
5407
  },
5078
5408
  },
5079
5409
  };
5080
- return { evolagentName: agent.name, baseagent: 'codex', agent: new CodexRunner(merged, callbacks, { agentAid: agent.config.aid, agentConfig: agent.config }) };
5410
+ return {
5411
+ evolagentName: agent.name,
5412
+ baseagent: 'codex',
5413
+ agent: new CodexRunner(merged, callbacks, {
5414
+ agentAid: agent.config.aid,
5415
+ agentConfig: agent.config,
5416
+ codexVersion: availability.version,
5417
+ }),
5418
+ };
5081
5419
  }
5082
5420
  }