evolcore 0.0.25 → 0.0.27

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 (304) hide show
  1. package/CHANGELOG.md +632 -565
  2. package/LICENSE +21 -21
  3. package/README.md +455 -455
  4. package/bin/codex-managed-acl.ps1 +203 -160
  5. package/bin/codex-managed-hook.mjs +208 -208
  6. package/bin/ec-safe-output.js +327 -327
  7. package/bin/ec.js +40 -29
  8. package/bin/install-codex-managed-hooks.mjs +582 -555
  9. package/dist/agents/baseagent-availability.js +68 -0
  10. package/dist/agents/baseagent.js +96 -46
  11. package/dist/agents/claude-runner.js +172 -33
  12. package/dist/agents/codex/app-server-client.js +76 -31
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +34 -1
  15. package/dist/agents/codex-runner.js +434 -446
  16. package/dist/agents/ecagent-runner.js +174 -34
  17. package/dist/agents/gemini-runner.js +8 -7
  18. package/dist/agents/runner-types.js +75 -19
  19. package/dist/aun/aid/agentmd.js +151 -36
  20. package/dist/aun/aid/client.js +1 -1
  21. package/dist/aun/aid/control-aid.js +180 -1
  22. package/dist/aun/aid/identity.js +4 -4
  23. package/dist/aun/aid/index.js +1 -1
  24. package/dist/aun/aid/store.js +17 -13
  25. package/dist/aun/group/history.js +303 -0
  26. package/dist/aun/group/identity.js +43 -0
  27. package/dist/aun/group/index.js +5 -0
  28. package/dist/aun/{msg/group.js → group/operations.js} +162 -39
  29. package/dist/aun/{menu-client.js → menu/client.js} +85 -6
  30. package/dist/aun/menu/index.js +2 -0
  31. package/dist/aun/menu/transport.js +49 -0
  32. package/dist/aun/msg/index.js +2 -1
  33. package/dist/aun/msg/managed-operation.js +85 -19
  34. package/dist/aun/msg/mention-schema.js +51 -0
  35. package/dist/aun/msg/p2p.js +19 -9
  36. package/dist/aun/msg/upload.js +43 -80
  37. package/dist/aun/outbox.js +29 -7
  38. package/dist/aun/rpc/client.js +1 -0
  39. package/dist/aun/storage/client.js +39 -50
  40. package/dist/channels/aun.js +842 -318
  41. package/dist/channels/daemon.js +10 -6
  42. package/dist/cli/agent-command.js +212 -89
  43. package/dist/cli/agent-create.js +626 -11
  44. package/dist/cli/agent.js +125 -13
  45. package/dist/cli/aun-commands.js +487 -337
  46. package/dist/cli/bench.js +20 -20
  47. package/dist/cli/config.js +55 -33
  48. package/dist/cli/contact.js +72 -15
  49. package/dist/cli/ctl-command.js +24 -24
  50. package/dist/cli/daemon-commands.js +140 -63
  51. package/dist/cli/diag.js +31 -0
  52. package/dist/cli/fs-command.js +70 -70
  53. package/dist/cli/handoff-command.js +46 -14
  54. package/dist/cli/index.js +91 -79
  55. package/dist/cli/init.js +34 -1
  56. package/dist/cli/link-rules.js +21 -21
  57. package/dist/cli/managed-command-guard.js +5 -1
  58. package/dist/cli/menu-client.js +51 -32
  59. package/dist/cli/model.js +3 -3
  60. package/dist/cli/net-check.js +22 -22
  61. package/dist/cli/queue-command.js +31 -31
  62. package/dist/cli/response.js +32 -32
  63. package/dist/cli/restart-monitor.js +103 -42
  64. package/dist/cli/stats.js +51 -51
  65. package/dist/cli/task-command.js +59 -0
  66. package/dist/cli/task-context.js +97 -49
  67. package/dist/cli/trigger-command.js +74 -64
  68. package/dist/config/builtin-roles.js +11 -0
  69. package/dist/config/config-field-policy.js +11 -0
  70. package/dist/config/config-manager.js +32 -4
  71. package/dist/config/config-operation-service.js +160 -0
  72. package/dist/config/contact-book-store.js +1 -1
  73. package/dist/config/contact-book.js +1 -1
  74. package/dist/config/contact-operation-service.js +97 -8
  75. package/dist/config/peer-role-resolver.js +1 -1
  76. package/dist/config/resolved-config-op.js +30 -7
  77. package/dist/config-store.js +1 -1
  78. package/dist/core/agent-application-service.js +7 -1
  79. package/dist/core/audit/event-key.js +17 -2
  80. package/dist/core/audit/log-integrity.js +34 -8
  81. package/dist/core/auth/agent-delegation.js +48 -14
  82. package/dist/core/auth/auth-gateway.js +2 -0
  83. package/dist/core/auth/authorization-audit.js +53 -49
  84. package/dist/core/auth/operation-authorizer.js +90 -7
  85. package/dist/core/auth/operation-catalog.js +145 -4
  86. package/dist/core/auth/remote-access-store.js +155 -10
  87. package/dist/core/bootstrap-messages.js +66 -7
  88. package/dist/core/bootstrap-service.js +111 -20
  89. package/dist/core/command/agent-control.js +88 -7
  90. package/dist/core/command/agent-storage-menu.js +443 -0
  91. package/dist/core/command/cli-intent-parser.js +19 -0
  92. package/dist/core/command/command-handler.js +159 -65
  93. package/dist/core/command/data-root-resolver.js +273 -0
  94. package/dist/core/command/diag-service.js +118 -0
  95. package/dist/core/command/group-menu.js +7 -2
  96. package/dist/core/command/menu-catalog.js +46 -7
  97. package/dist/core/command/menu-handler.js +392 -100
  98. package/dist/core/command/menu-protocol.js +45 -6
  99. package/dist/core/command/menu-token-store.js +41 -8
  100. package/dist/core/command/role-menu.js +2 -2
  101. package/dist/core/command/slash-gate.js +34 -4
  102. package/dist/core/command/slash-handler.js +123 -43
  103. package/dist/core/event-catalog.js +4 -0
  104. package/dist/core/evolagent-registry.js +28 -2
  105. package/dist/core/group/invite-service.js +924 -0
  106. package/dist/core/handoff/runtime.js +205 -27
  107. package/dist/core/handoff/store.js +193 -4
  108. package/dist/core/interaction-router.js +22 -8
  109. package/dist/core/message/im-renderer.js +146 -34
  110. package/dist/core/message/message-bridge.js +163 -20
  111. package/dist/core/message/message-log.js +5 -3
  112. package/dist/core/message/message-queue.js +99 -41
  113. package/dist/core/message/message-utils.js +1 -0
  114. package/dist/core/message/response-engine.js +755 -144
  115. package/dist/core/model/catalog-service.js +79 -49
  116. package/dist/core/model/context-window-suffix.js +33 -0
  117. package/dist/core/model/model-catalog.js +1 -1
  118. package/dist/core/model/model-permission.js +12 -3
  119. package/dist/core/permission/approval-gateway.js +36 -3
  120. package/dist/core/permission/ec-command-parser.js +112 -41
  121. package/dist/core/permission/protected-paths.js +134 -16
  122. package/dist/core/permission/sandbox-runtime.js +108 -17
  123. package/dist/core/permission/tool-error-code.js +15 -0
  124. package/dist/core/permission/tool-policy.js +9 -8
  125. package/dist/core/relation/peer-identity.js +57 -6
  126. package/dist/core/relation/shared-data.js +106 -0
  127. package/dist/core/role/runtime-policy.js +64 -0
  128. package/dist/core/runtime-lock.js +10 -26
  129. package/dist/core/session/runner-task-history.js +170 -0
  130. package/dist/core/session/session-manager.js +34 -6
  131. package/dist/core/session/session-mapper.js +41 -0
  132. package/dist/core/session/session-turn-coordinator.js +37 -7
  133. package/dist/core/session/session-turns.js +48 -7
  134. package/dist/core/task/task-recovery-service.js +176 -0
  135. package/dist/core/task/task-resume-operation.js +32 -0
  136. package/dist/eck/group-rules-sync.js +5 -3
  137. package/dist/eck/kit-renderer.js +1 -0
  138. package/dist/index.js +588 -217
  139. package/dist/ipc.js +193 -15
  140. package/dist/paths.js +17 -4
  141. package/dist/product.js +2 -0
  142. package/dist/response-system/engines/v1/proactive-flow.js +59 -2
  143. package/dist/stats/billing.js +41 -15
  144. package/dist/stats/db.js +159 -159
  145. package/dist/stats/eck-vars.js +9 -9
  146. package/dist/stats/price-resolver.js +28 -10
  147. package/dist/stats/query.js +139 -139
  148. package/dist/stats/role-budget.js +6 -6
  149. package/dist/stats/writer.js +51 -51
  150. package/dist/trigger/event-source.js +19 -0
  151. package/dist/trigger/feedback.js +233 -40
  152. package/dist/trigger/parser.js +55 -5
  153. package/dist/trigger/patch.js +9 -0
  154. package/dist/trigger/scheduler.js +30 -4
  155. package/dist/trigger/validation.js +20 -3
  156. package/dist/utils/cross-platform.js +30 -1
  157. package/dist/utils/ecweb-utils.js +1 -0
  158. package/dist/utils/error-dict.json +160 -160
  159. package/dist/utils/error-utils.js +61 -2
  160. package/dist/utils/evolcore-version.js +129 -0
  161. package/dist/utils/logger.js +26 -14
  162. package/dist/utils/macos-autostart.js +22 -22
  163. package/dist/utils/model-prices.jsonl +23 -23
  164. package/dist/utils/npm-ops.js +176 -35
  165. package/dist/utils/tool-summary.js +59 -3
  166. package/dist/utils/welcome.js +196 -23
  167. package/ecagent/LICENSE +21 -21
  168. package/ecagent/NOTICE +11 -11
  169. package/ecagent/README.md +61 -61
  170. package/ecagent/dist/harness/agent-harness.js +3 -0
  171. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  172. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  173. package/ecagent/dist/harness/messages.js +7 -7
  174. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  175. package/kits/docs/GUIDE.md +20 -20
  176. package/kits/docs/INDEX.md +60 -66
  177. package/kits/docs/aun/CHEATSHEET.md +19 -19
  178. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  179. package/kits/docs/channels/aun.md +69 -56
  180. package/kits/docs/channels/feishu.md +56 -56
  181. package/kits/docs/context-assembly.md +379 -367
  182. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  183. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  184. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  185. package/kits/docs/eck_templates/runtime.template.md +19 -19
  186. package/kits/docs/evolcore/INDEX.md +76 -71
  187. package/kits/docs/evolcore/agent-create.md +202 -165
  188. package/kits/docs/evolcore/agent.md +99 -67
  189. package/kits/docs/evolcore/aid.md +68 -60
  190. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -146
  191. package/kits/docs/evolcore/config.md +241 -195
  192. package/kits/docs/evolcore/contact.md +75 -68
  193. package/kits/docs/evolcore/ctl.md +46 -46
  194. package/kits/docs/evolcore/event.md +216 -216
  195. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  196. package/kits/docs/evolcore/fs.md +110 -110
  197. package/kits/docs/evolcore/group-collaboration.md +244 -244
  198. package/kits/docs/evolcore/group-fs.md +17 -17
  199. package/kits/docs/evolcore/group-rules.md +251 -251
  200. package/kits/docs/evolcore/group.md +267 -152
  201. package/kits/docs/evolcore/menu-client.md +58 -34
  202. package/kits/docs/evolcore/model.md +50 -50
  203. package/kits/docs/evolcore/msg.md +169 -165
  204. package/kits/docs/evolcore/response.md +75 -75
  205. package/kits/docs/evolcore/rpc.md +37 -37
  206. package/kits/docs/evolcore/self-summary.md +29 -29
  207. package/kits/docs/evolcore/stats.md +83 -83
  208. package/kits/docs/evolcore/storage.md +53 -50
  209. package/kits/docs/evolcore/task.md +14 -0
  210. package/kits/docs/evolcore/trigger.md +623 -575
  211. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  212. package/kits/docs/identity/PATH_OPS.md +16 -16
  213. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  214. package/kits/docs/identity/identity-tools.md +26 -26
  215. package/kits/docs/path-registry.md +62 -62
  216. package/kits/docs/prompt-loading-architecture.md +269 -269
  217. package/kits/docs/venues/aun-group.md +45 -45
  218. package/kits/docs/venues/aun-private.md +10 -10
  219. package/kits/docs/venues/client-desktop.md +10 -10
  220. package/kits/docs/venues/client-mobile.md +10 -10
  221. package/kits/docs/venues/feishu-group.md +13 -13
  222. package/kits/docs/venues/feishu-private.md +9 -9
  223. package/kits/docs/venues/group.md +42 -25
  224. package/kits/docs/venues/private.md +10 -10
  225. package/kits/eck_manifest.auxiliary.json +43 -43
  226. package/kits/eck_manifest.json +217 -190
  227. package/kits/eck_message_manifest.json +74 -74
  228. package/kits/migrations/README-role-config-v4.md +32 -32
  229. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  230. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  231. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  232. package/kits/migrations/rename-config-file.mjs +99 -99
  233. package/kits/rules/01-overview.md +166 -162
  234. package/kits/rules/02-navigation.md +96 -96
  235. package/kits/rules/03-identity.md +36 -36
  236. package/kits/rules/04-relation.md +76 -75
  237. package/kits/rules/05-venue.md +60 -60
  238. package/kits/rules/06-channel.md +65 -63
  239. package/kits/schemas/_meta.json +53 -53
  240. package/kits/schemas/agent-config.schema.1.json +177 -177
  241. package/kits/schemas/agent-config.schema.10.json +395 -395
  242. package/kits/schemas/agent-config.schema.11.json +421 -421
  243. package/kits/schemas/agent-config.schema.12.json +451 -427
  244. package/kits/schemas/agent-config.schema.2.json +239 -239
  245. package/kits/schemas/agent-config.schema.3.json +119 -119
  246. package/kits/schemas/agent-config.schema.4.json +208 -208
  247. package/kits/schemas/agent-config.schema.5.json +326 -326
  248. package/kits/schemas/agent-config.schema.6.json +322 -322
  249. package/kits/schemas/agent-config.schema.7.json +303 -303
  250. package/kits/schemas/agent-config.schema.8.json +304 -304
  251. package/kits/schemas/agent-config.schema.9.json +364 -364
  252. package/kits/schemas/contact-book.schema.1.json +36 -36
  253. package/kits/schemas/contact-book.schema.2.json +43 -43
  254. package/kits/schemas/contact-book.schema.3.json +67 -67
  255. package/kits/schemas/daemon.schema.1.json +91 -91
  256. package/kits/schemas/daemon.schema.2.json +101 -101
  257. package/kits/schemas/daemon.schema.3.json +123 -123
  258. package/kits/schemas/daemon.schema.4.json +136 -136
  259. package/kits/schemas/daemon.schema.5.json +135 -135
  260. package/kits/schemas/daemon.schema.6.json +131 -131
  261. package/kits/schemas/defaults.schema.1.json +81 -81
  262. package/kits/schemas/defaults.schema.2.json +85 -85
  263. package/kits/schemas/defaults.schema.3.json +73 -73
  264. package/kits/schemas/defaults.schema.4.json +107 -107
  265. package/kits/schemas/defaults.schema.5.json +119 -119
  266. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  267. package/kits/schemas/migrations/README.md +30 -30
  268. package/kits/schemas/relation-config.schema.1.json +158 -158
  269. package/kits/schemas/relation-config.schema.2.json +73 -73
  270. package/kits/schemas/relation-config.schema.3.json +50 -50
  271. package/kits/schemas/relation-config.schema.4.json +47 -47
  272. package/kits/schemas/relation-config.schema.5.json +47 -47
  273. package/kits/schemas/relation-config.schema.6.json +46 -46
  274. package/kits/schemas/relation-config.schema.7.json +59 -59
  275. package/kits/schemas/relation-config.schema.8.json +103 -103
  276. package/kits/schemas/role-config.schema.1.json +215 -201
  277. package/kits/schemas/role-registry.schema.1.json +35 -35
  278. package/kits/schemas/single-session.schema.1.json +31 -31
  279. package/kits/schemas/single-session.schema.2.json +57 -57
  280. package/kits/schemas/single-session.schema.3.json +32 -32
  281. package/kits/templates/bootstrap-welcome.md +17 -17
  282. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  283. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  284. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  285. package/kits/templates/message-fragments/inject-default.md +2 -2
  286. package/kits/templates/message-fragments/item.md +2 -2
  287. package/kits/templates/roles/admin.json +92 -92
  288. package/kits/templates/roles/member.json +101 -96
  289. package/kits/templates/roles/owner.json +9 -9
  290. package/kits/templates/roles/visitor.json +90 -85
  291. package/kits/templates/system-fragments/baseagent.md +14 -14
  292. package/kits/templates/system-fragments/bootstrap.md +53 -34
  293. package/kits/templates/system-fragments/channel.md +28 -64
  294. package/kits/templates/system-fragments/commands.md +36 -28
  295. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  296. package/kits/templates/system-fragments/identity.md +13 -13
  297. package/kits/templates/system-fragments/relation.md +19 -19
  298. package/kits/templates/system-fragments/reply.md +56 -0
  299. package/kits/templates/system-fragments/session.md +59 -59
  300. package/kits/templates/system-fragments/venue.md +31 -31
  301. package/package.json +104 -98
  302. package/dist/aun/group-identity.js +0 -9
  303. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  304. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -16,6 +16,13 @@ const CODEX_CUSTOM_POWERSHELL_NAMES = new Set(['pwsh.exe', 'powershell.exe']);
16
16
  const CODEX_CUSTOM_CMD_NAMES = new Set(['cmd.exe']);
17
17
  const CODEX_CMD_EXECUTABLES = new Set(['cmd', 'cmd.exe']);
18
18
  const CODEX_CMD_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Windows[\\/](?:System32|SysWOW64|Sysnative)[\\/]cmd\.exe)$/i;
19
+ // These switches only suppress interactive/profile UI behavior. `-Command`
20
+ // is handled separately as the required execute switch. Options such as
21
+ // `-NoExit`, `-ExecutionPolicy`, `-EncodedCommand`, and `-File` can change
22
+ // execution semantics and must never be accepted as a trusted carrier.
23
+ const CODEX_POWERSHELL_INERT_SWITCHES = new Set([
24
+ '-noprofile', '-noninteractive', '-nologo',
25
+ ]);
19
26
  // These switches affect only cmd startup/output formatting. `/c` is handled
20
27
  // separately as the required execute switch. Flags such as `/k`, `/v:on`,
21
28
  // `/e:*`, and `/f:*` can change process lifetime or command semantics and are
@@ -183,6 +190,10 @@ function substituteManagedTmpDirToken(value) {
183
190
  }
184
191
  return result;
185
192
  }
193
+ function containsManagedTmpDirReference(value) {
194
+ return /\$TMPDIR|\$\{TMPDIR\}/.test(value)
195
+ || /\$env:TMPDIR|\$\{env:TMPDIR\}/i.test(value);
196
+ }
186
197
  function restoreManagedTmpDirToken(value, managedTempDir) {
187
198
  return value.replaceAll(MANAGED_TMPDIR_TOKEN, managedTempDir ?? '$TMPDIR');
188
199
  }
@@ -218,24 +229,27 @@ export function normalizeEvolcoreCommandArgv(argv) {
218
229
  }
219
230
  return [...argv];
220
231
  }
221
- /** Detect the historical PowerShell pattern that overwrote the thread carrier. */
222
- export function hasPowerShellDelegationAssignment(command) {
223
- return /^\s*\$env\s*:\s*EVOLCORE_DELEGATION_TOKEN\b/i.test(command)
232
+ const POWERSHELL_CREDENTIAL_ENV_RE = /^(EVOLCORE_CODEX_SESSION_TOKEN|EVOLCORE_DELEGATION_TOKEN)$/i;
233
+ /** Detect a PowerShell prefix that materializes an EvolCore session credential. */
234
+ export function hasPowerShellCredentialAssignment(command) {
235
+ return /^\s*\$env\s*:\s*(?:EVOLCORE_CODEX_SESSION_TOKEN|EVOLCORE_DELEGATION_TOKEN)\b/i.test(command)
224
236
  && /\bec(?:\.cmd|\.exe)?(?:\s|$)/i.test(command);
225
237
  }
226
238
  /**
227
239
  * Windows Codex may materialize one environment variable assignment directly
228
240
  * in the PowerShell command body instead of inheriting it from the process
229
- * environment. Only the EvolCore delegation variable is accepted, and only
230
- * when followed by one command on the same carrier. The value is validated for
231
- * shape but never trusted for authorization; the runner still arms the stable
232
- * in-memory thread carrier.
241
+ * environment. Only an EvolCore session credential variable is accepted,
242
+ * and only when followed by one command in the same shell invocation. The
243
+ * legacy delegation name remains readable for rolling upgrades, but its value
244
+ * is still compared with the credential bound to the managed session.
233
245
  */
234
- function unwrapPowerShellDelegationPrefix(command) {
246
+ function unwrapPowerShellCredentialPrefix(command) {
235
247
  const input = command.trim();
236
- if (!/^\$env\s*:\s*EVOLCORE_DELEGATION_TOKEN\b/i.test(input)) {
248
+ const prefix = input.match(/^\$env\s*:\s*([A-Za-z_][A-Za-z0-9_]*)\b/i);
249
+ if (!prefix || !POWERSHELL_CREDENTIAL_ENV_RE.test(prefix[1])) {
237
250
  return { kind: 'none', command };
238
251
  }
252
+ const credentialEnvName = prefix[1];
239
253
  let index = 0;
240
254
  const skipSpace = () => {
241
255
  while (index < input.length && (input[index] === ' ' || input[index] === '\t'))
@@ -252,7 +266,7 @@ function unwrapPowerShellDelegationPrefix(command) {
252
266
  return { kind: 'invalid', command };
253
267
  index++;
254
268
  skipSpace();
255
- if (!consume('EVOLCORE_DELEGATION_TOKEN'))
269
+ if (!consume(credentialEnvName))
256
270
  return { kind: 'invalid', command };
257
271
  skipSpace();
258
272
  if (input[index] !== '=')
@@ -324,20 +338,20 @@ export function parseCodexCarrierCommand(carrier, options = {}) {
324
338
  }
325
339
  }
326
340
  const unwrapped = carrier.dialect === 'powershell'
327
- ? unwrapPowerShellDelegationPrefix(carrier.command)
341
+ ? unwrapPowerShellCredentialPrefix(carrier.command)
328
342
  : { kind: 'none', command: carrier.command };
329
343
  // Windows PowerShell may materialize the thread's shell environment as a
330
- // single `$env:EVOLCORE_DELEGATION_TOKEN = '...'` prefix. Keep the token as
344
+ // single `$env:EVOLCORE_CODEX_SESSION_TOKEN = '...'` prefix. Keep the token as
331
345
  // metadata for the approval layer, which must compare it with the runner's
332
- // in-memory carrier before arming delegation. Malformed or unrelated
346
+ // session credential before arming delegation. Malformed or unrelated
333
347
  // assignments remain syntax failures and can never be authorized.
334
348
  if (unwrapped.kind === 'invalid'
335
349
  || (carrier.dialect === 'powershell'
336
- && hasPowerShellDelegationAssignment(carrier.command)
350
+ && hasPowerShellCredentialAssignment(carrier.command)
337
351
  && unwrapped.kind === 'none')
338
352
  || (unwrapped.kind === 'valid'
339
- && options.expectedDelegationToken !== undefined
340
- && options.expectedDelegationToken !== unwrapped.token)) {
353
+ && options.expectedCredentialToken !== undefined
354
+ && options.expectedCredentialToken !== unwrapped.token)) {
341
355
  return parseFailure('shell-composition', carrier.command, 0, 0, carrier.dialect);
342
356
  }
343
357
  const parsed = parseLiteralShellCommand(unwrapped.command, {
@@ -348,7 +362,7 @@ export function parseCodexCarrierCommand(carrier, options = {}) {
348
362
  ? {
349
363
  ...parsed,
350
364
  argv: normalizeEvolcoreCommandArgv(parsed.argv),
351
- ...(unwrapped.kind === 'valid' ? { delegationToken: unwrapped.token } : {}),
365
+ ...(unwrapped.kind === 'valid' ? { credentialToken: unwrapped.token } : {}),
352
366
  }
353
367
  : parsed;
354
368
  }
@@ -362,7 +376,7 @@ function parseFailure(issue, input, offset, tokenIndex, dialect) {
362
376
  inputLength: input.length,
363
377
  };
364
378
  }
365
- function parseLiteralPowerShellCommand(command) {
379
+ function parseLiteralPowerShellCommand(command, options = {}) {
366
380
  const input = trimShellSpacing(command);
367
381
  if (!input)
368
382
  return parseFailure('empty-command', input, 0, 0, 'powershell');
@@ -372,6 +386,9 @@ function parseLiteralPowerShellCommand(command) {
372
386
  let token = '';
373
387
  let tokenStarted = false;
374
388
  let quote = null;
389
+ // Environment variable names are case-insensitive on Windows. Require a
390
+ // complete variable name, including PowerShell's Unicode/name characters.
391
+ const managedTmpDirReference = new RegExp('^\\$(?:env:TMPDIR(?![\\p{L}\\p{Nd}_?:])|\\{env:TMPDIR\\})', process.platform === 'win32' ? 'iu' : 'u');
375
392
  const finishToken = () => {
376
393
  if (!tokenStarted)
377
394
  return;
@@ -396,6 +413,23 @@ function parseLiteralPowerShellCommand(command) {
396
413
  }
397
414
  continue;
398
415
  }
416
+ if (char === '$' && isShellParameterExpansionStart(input[index + 1])) {
417
+ const reference = managedTmpDirReference.exec(input.slice(index))?.[0];
418
+ const next = reference ? input[index + reference.length] : undefined;
419
+ if (!reference) {
420
+ return parseFailure('unsafe-expansion', input, index, argv.length, 'powershell');
421
+ }
422
+ if (!options.allowManagedTmpDir || !options.managedTempDir
423
+ || (next !== undefined && !/[\\/\s"']/u.test(next))) {
424
+ return parseFailure('unsafe-expansion', input, index, argv.length, 'powershell');
425
+ }
426
+ // Append the registered value directly: spaces remain in this argument,
427
+ // and characters in the directory are never interpreted as shell syntax.
428
+ token += options.managedTempDir;
429
+ tokenStarted = true;
430
+ index += reference.length - 1;
431
+ continue;
432
+ }
399
433
  if (quote === 'double') {
400
434
  if (char === '"') {
401
435
  if (!isLiteralArgBoundary(input[index + 1]))
@@ -412,9 +446,6 @@ function parseLiteralPowerShellCommand(command) {
412
446
  token += next;
413
447
  continue;
414
448
  }
415
- if (char === '$' && isShellParameterExpansionStart(input[index + 1])) {
416
- return parseFailure('unsafe-expansion', input, index, argv.length, 'powershell');
417
- }
418
449
  token += char;
419
450
  continue;
420
451
  }
@@ -578,6 +609,25 @@ function resolveCodexCmdCarrierArgv(argv) {
578
609
  ? { command, dialect: 'cmd', executable: argv[0] }
579
610
  : undefined;
580
611
  }
612
+ function resolveCodexPowerShellCarrierArgv(argv) {
613
+ if (argv.length < 3 || !isCodexPowerShellExecutable(argv[0]))
614
+ return undefined;
615
+ const commandIndex = argv.length - 2;
616
+ if (argv[commandIndex].toLowerCase() !== '-command')
617
+ return undefined;
618
+ const seen = new Set();
619
+ for (const rawSwitch of argv.slice(1, commandIndex)) {
620
+ const switchValue = rawSwitch.toLowerCase();
621
+ if (!CODEX_POWERSHELL_INERT_SWITCHES.has(switchValue) || seen.has(switchValue)) {
622
+ return undefined;
623
+ }
624
+ seen.add(switchValue);
625
+ }
626
+ const command = argv.at(-1);
627
+ return typeof command === 'string' && command.trim()
628
+ ? { command, dialect: 'powershell', executable: argv[0] }
629
+ : undefined;
630
+ }
581
631
  /** Resolve a Windows cmd carrier when the app-server provides only a string. */
582
632
  function resolveCodexCmdCarrierString(command) {
583
633
  const input = trimShellSpacing(command);
@@ -593,10 +643,14 @@ function resolveCodexCmdCarrierString(command) {
593
643
  const argv = [executable, ...switches, match[4]];
594
644
  return resolveCodexCmdCarrierArgv(argv);
595
645
  }
596
- /** Resolve a PowerShell carrier when the app-server provides only a string. */
646
+ /**
647
+ * Resolve a PowerShell carrier when the app-server provides only a string.
648
+ * The prefix is converted to the same argv shape used by commandArgv so the
649
+ * startup-switch policy cannot diverge between the two transport forms.
650
+ */
597
651
  function resolveCodexPowerShellCarrierString(command) {
598
652
  const input = trimShellSpacing(command);
599
- const match = input.match(/^(?:"([^"]+)"|(\S+))\s+-command\s+(.+)$/i);
653
+ const match = input.match(/^(?:"([^"\r\n]+)"|(\S+))((?:[ \t]+-[A-Za-z]+)+)[ \t]+(.+)$/);
600
654
  if (!match)
601
655
  return undefined;
602
656
  const executable = match[1] ?? match[2];
@@ -605,13 +659,14 @@ function resolveCodexPowerShellCarrierString(command) {
605
659
  // A string-form carrier has one additional command-line quoting layer. The
606
660
  // command argument is commonly serialized as `"ec ..."`; remove exactly
607
661
  // that outer pair and leave quotes belonging to EC arguments intact.
608
- let nested = match[3].trim();
662
+ let nested = match[4].trim();
609
663
  if (nested.length >= 2
610
664
  && ((nested.startsWith('"') && nested.endsWith('"'))
611
665
  || (nested.startsWith("'") && nested.endsWith("'")))) {
612
666
  nested = nested.slice(1, -1);
613
667
  }
614
- return nested ? { command: nested, dialect: 'powershell', executable } : undefined;
668
+ const switches = match[3].trim().split(/[ \t]+/);
669
+ return resolveCodexPowerShellCarrierArgv([executable, ...switches, nested]);
615
670
  }
616
671
  /** Resolve a POSIX shell carrier when app-server provides only a string. */
617
672
  function resolveCodexPosixShellCarrierString(command) {
@@ -729,7 +784,7 @@ function doubleQuotedIssueAt(input, index) {
729
784
  */
730
785
  export function parseLiteralShellCommand(command, options = {}) {
731
786
  if (options.dialect === 'powershell') {
732
- return parseLiteralPowerShellCommand(command);
787
+ return parseLiteralPowerShellCommand(command, options);
733
788
  }
734
789
  if (options.dialect === 'cmd') {
735
790
  return parseLiteralCmdCommand(command, options);
@@ -847,11 +902,9 @@ export function resolveCodexShellCarrierArgv(argv) {
847
902
  && isCodexPosixShellExecutable(argv[0])) {
848
903
  return { command: argv[2], dialect: 'posix', executable: argv[0] };
849
904
  }
850
- if (argv.length === 3) {
851
- if (isCodexPowerShellExecutable(argv[0]) && argv[1].toLowerCase() === '-command') {
852
- return { command: argv[2], dialect: 'powershell', executable: argv[0] };
853
- }
854
- }
905
+ const powershellCarrier = resolveCodexPowerShellCarrierArgv(argv);
906
+ if (powershellCarrier)
907
+ return powershellCarrier;
855
908
  // Node/cross-spawn and Codex use cmd.exe with a small set of startup flags.
856
909
  // The execute switch must be the final switch before one command argument;
857
910
  // validation itself is shared with string-only Windows payloads below.
@@ -1416,16 +1469,16 @@ export function parseCodexExecToolCommand(input) {
1416
1469
  }
1417
1470
  return decoded;
1418
1471
  }
1419
- function parseProactiveToolCommandArgv(toolName, input) {
1472
+ function parseProactiveToolCommand(toolName, input) {
1420
1473
  const options = {
1421
1474
  verifyShellExecutable: process.platform === 'win32',
1422
1475
  };
1423
1476
  if (toolName === 'Bash' || toolName === 'Shell')
1424
- return parseCodexToolCommandArgv(input, options);
1477
+ return parseCodexToolCommand(input, options);
1425
1478
  if (toolName !== 'exec')
1426
1479
  return null;
1427
1480
  const command = parseCodexExecToolCommand(input);
1428
- return command ? parseCodexShellCommandArgv(command, { allowManagedTmpDir: true, ...options }) : null;
1481
+ return command ? parseCodexToolCommand({ command }, options) : null;
1429
1482
  }
1430
1483
  function scanBoundedOutputOperators(command) {
1431
1484
  const operators = [];
@@ -1624,7 +1677,7 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
1624
1677
  if (nested.ok && nested.argv[0] === 'ec')
1625
1678
  return { kind: 'literal' };
1626
1679
  if (!nested.ok && (hasCarrierEcCommandPrefix(carrier.command, carrier.dialect)
1627
- || (carrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(carrier.command)))) {
1680
+ || (carrier.dialect === 'powershell' && hasPowerShellCredentialAssignment(carrier.command)))) {
1628
1681
  return { kind: 'composite', issue: nested.issue };
1629
1682
  }
1630
1683
  }
@@ -1634,7 +1687,7 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
1634
1687
  if (nested.ok && nested.argv[0] === 'ec')
1635
1688
  return { kind: 'literal' };
1636
1689
  if (!nested.ok && (hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
1637
- || (stringCarrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(stringCarrier.command)))) {
1690
+ || (stringCarrier.dialect === 'powershell' && hasPowerShellCredentialAssignment(stringCarrier.command)))) {
1638
1691
  return { kind: 'composite', issue: nested.issue };
1639
1692
  }
1640
1693
  }
@@ -1656,7 +1709,7 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
1656
1709
  if (nested.ok && nested.argv[0] === 'ec')
1657
1710
  return { kind: 'literal' };
1658
1711
  if (!nested.ok && (hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
1659
- || (stringCarrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(stringCarrier.command)))) {
1712
+ || (stringCarrier.dialect === 'powershell' && hasPowerShellCredentialAssignment(stringCarrier.command)))) {
1660
1713
  return { kind: 'composite', issue: nested.issue };
1661
1714
  }
1662
1715
  return { kind: 'none' };
@@ -1711,13 +1764,13 @@ function findSingleFileOption(argv, startIndex) {
1711
1764
  const PROACTIVE_SEND_VALUE_FLAGS = new Set([
1712
1765
  '--format', '--app', '--file', '--text-from-file', '--link', '--payload',
1713
1766
  '--title', '--description', '--text', '--transcript', '--as',
1714
- '--content-type', '--mention', '--aun-path', '--thread', '--return',
1767
+ '--content-type', '--mention', '--aun-path', '--thread', '--return', '--handoff-context',
1715
1768
  ]);
1716
1769
  const PROACTIVE_SEND_BOOLEAN_FLAGS = new Set([
1717
1770
  '--encrypt', '--no-encrypt', '--mention-all',
1718
1771
  ]);
1719
1772
  const SEND_CONTENT_VALUE_FLAGS = new Set([
1720
- '--payload', '--title', '--description', '--text', '--transcript',
1773
+ '--payload', '--title', '--description', '--text', '--transcript', '--handoff-context',
1721
1774
  ]);
1722
1775
  const TRIGGER_CONTENT_VALUE_FLAGS = new Set(['--prompt']);
1723
1776
  /**
@@ -1728,7 +1781,7 @@ const TRIGGER_CONTENT_VALUE_FLAGS = new Set(['--prompt']);
1728
1781
  */
1729
1782
  export function containsLiteralManagedTmpDirOutsideSendContent(argv) {
1730
1783
  const literalIndexes = argv
1731
- .map((value, index) => value.includes('$TMPDIR') ? index : -1)
1784
+ .map((value, index) => containsManagedTmpDirReference(value) ? index : -1)
1732
1785
  .filter(index => index >= 0);
1733
1786
  if (literalIndexes.length === 0)
1734
1787
  return false;
@@ -1903,7 +1956,8 @@ export function isExactEvolcoreSendCommandForSession(toolName, input, channelId,
1903
1956
  // is represented as `exec` and is handled only for one literal inner call.
1904
1957
  if (!selfAid)
1905
1958
  return false;
1906
- const argv = parseProactiveToolCommandArgv(toolName, input);
1959
+ const result = parseProactiveToolCommand(toolName, input);
1960
+ const argv = result?.ok ? result.argv : null;
1907
1961
  const parsed = argv ? parseEvolcoreSendArgv(argv) : null;
1908
1962
  if (!parsed || parsed.action !== 'send')
1909
1963
  return false;
@@ -1914,3 +1968,20 @@ export function isExactEvolcoreSendCommandForSession(toolName, input, channelId,
1914
1968
  && !!argv
1915
1969
  && hasExecutableProactiveSendPayload(argv, expectedScope);
1916
1970
  }
1971
+ /** Diagnostic only: this never broadens the exact-send authorization check. */
1972
+ export function describeProactiveSendCommandFailure(toolName, input) {
1973
+ if (!['Bash', 'Shell', 'exec'].includes(toolName) || !input || typeof input !== 'object')
1974
+ return undefined;
1975
+ const commandText = [input.command, input.input, ...(Array.isArray(input.commandArgv) ? input.commandArgv : [])]
1976
+ .filter((value) => typeof value === 'string').join(' ');
1977
+ if (!/\bec(?:\.cmd|\.exe)?\s+(?:group|msg)\s+send\b/i.test(commandText))
1978
+ return undefined;
1979
+ const result = parseProactiveToolCommand(toolName, input);
1980
+ if (!result)
1981
+ return '当前汇报命令未通过规范解析:只支持单次、字面量的发送调用,请将发送作为独立工具调用。';
1982
+ if (!result.ok) {
1983
+ return `当前汇报命令未通过规范解析(${result.issue},${result.dialect},位置 ${result.offset});`
1984
+ + '请检查引号和转义,并将发送作为一条独立命令。';
1985
+ }
1986
+ return '当前汇报命令不符合当前会话的发送要求;请核对发送者、私聊或群目标,以及正文和参数。';
1987
+ }
@@ -28,9 +28,13 @@ const H_CLASS_RELATIVE_PATTERNS = [
28
28
  /^data\/outbox(?:\/|$)/,
29
29
  /^data\/handoff(?:\/|$)/,
30
30
  /^agents\/[^/]+\/sessions(?:\/|$)/,
31
+ /^agents\/[^/]+\/data\/async-questions(?:\/|$)/,
32
+ /^agents\/[^/]+\/data\/aun(?:\/|$)/,
31
33
  /^agents\/[^/]+\/data\/(?:channels(?:\/|$)|handoff(?:\/|$)|(?:outbox|activity-outbox)\.jsonl(?:_|__|\.tmp-.*)?$|contact-audit\.jsonl$)/,
32
34
  ];
33
35
  const H_CLASS_REFERENCE_PATTERNS = [
36
+ /(?:^|[/\\\s"'=<>])agents[\\/][^/\\\s"']+[\\/]data[\\/]async-questions(?:[\\/]|$|[\s"';&|<>)},])/,
37
+ /(?:^|[/\\\s"'=<>])agents[\\/][^/\\\s"']+[\\/]data[\\/]aun(?:[\\/]|$|[\s"';&|<>)},])/,
34
38
  /(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
35
39
  /(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
36
40
  /(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys|private))(?:[\\/]|$|[\s"';&|<>)},])/,
@@ -43,6 +47,8 @@ const H_CLASS_REFERENCE_PATTERNS = [
43
47
  // protected file. Descendants inherit the directory classification unless an
44
48
  // existing regular file gives us a more precise answer.
45
49
  const H_CLASS_DIRECTORY_RELATIVE_PATTERNS = [
50
+ /^agents\/[^/]+\/data\/aun(?:\/|$)/,
51
+ /^agents\/[^/]+\/data\/async-questions(?:\/|$)/,
46
52
  /^backups\/config(?:\/|$)/,
47
53
  /^\.snapshots(?:\/|$)/,
48
54
  /^CA(?:\/|$)/,
@@ -286,6 +292,8 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
286
292
  path.join('agents', '*', 'sessions', '**'),
287
293
  path.join('agents', '*', 'data', 'channels', '**'),
288
294
  path.join('agents', '*', 'data', 'handoff', '**'),
295
+ path.join('agents', '*', 'data', 'async-questions', '**'),
296
+ path.join('agents', '*', 'data', 'aun', '**'),
289
297
  path.join('agents', '*', 'data', 'outbox.jsonl*'),
290
298
  path.join('agents', '*', 'data', 'activity-outbox.jsonl*'),
291
299
  path.join('agents', '*', 'data', 'contact-audit.jsonl'),
@@ -303,6 +311,15 @@ export function getLClassWritePatterns(root = resolveRoot()) {
303
311
  const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
304
312
  const CLAUDE_GLOB_MAGIC = /[*?[\]]/;
305
313
  const CODEX_GLOB_MAGIC = /[*?[\]]/;
314
+ // Codex wraps the resolved Windows ACL payload in JSON and base64 before
315
+ // passing it as one CreateProcess argument. Real Codex 0.154 payloads are
316
+ // roughly 3-5x larger than EvolCore's input map, so leave enough room below
317
+ // Windows' 32,767-character command-line ceiling for helper metadata.
318
+ const CODEX_WINDOWS_FILESYSTEM_RULE_BUDGET_BYTES = 7 * 1024;
319
+ const CODEX_WINDOWS_MATERIALIZED_FILE_GLOBS = [
320
+ '**/*.json_',
321
+ '**/*.json.migrated',
322
+ ];
306
323
  function addExistingTarget(targets, value) {
307
324
  try {
308
325
  const stat = fs.lstatSync(value);
@@ -370,6 +387,8 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
370
387
  addExistingTarget(targets, path.join(agentRoot, 'sessions'));
371
388
  addExistingTarget(targets, path.join(agentRoot, 'data', 'channels'));
372
389
  addExistingTarget(targets, path.join(agentRoot, 'data', 'handoff'));
390
+ addExistingTarget(targets, path.join(agentRoot, 'data', 'async-questions'));
391
+ addExistingTarget(targets, path.join(agentRoot, 'data', 'aun'));
373
392
  addExistingTarget(targets, path.join(agentRoot, 'data', 'outbox.jsonl'));
374
393
  addExistingTarget(targets, path.join(agentRoot, 'data', 'activity-outbox.jsonl'));
375
394
  addExistingTarget(targets, path.join(agentRoot, 'data', 'contact-audit.jsonl'));
@@ -427,14 +446,14 @@ export function buildClaudeProtectedFilesystem(root = resolveRoot(), options = {
427
446
  denyWrite: allPaths,
428
447
  };
429
448
  }
430
- export function buildCodexHClassFilesystemRules(root = resolveRoot()) {
449
+ export function buildCodexHClassFilesystemRules(root = resolveRoot(), platform = process.platform) {
431
450
  const filesystem = {
432
451
  // Codex pre-expands unbounded deny globs on Linux/WSL/Windows. Without a
433
452
  // bound, patterns such as **/*.json_ are accepted but may not be enforced.
434
453
  glob_scan_max_depth: 64,
435
- ...Object.fromEntries(filterWindowsCodexFilesystemTargets(getHClassSandboxPatterns(root)).map(pattern => [pattern, 'deny'])),
454
+ ...Object.fromEntries(filterWindowsCodexFilesystemTargets(getHClassSandboxPatterns(root), platform).map(pattern => [pattern, 'deny'])),
436
455
  };
437
- return compactWindowsCodexFilesystemRules(filesystem);
456
+ return materializeWindowsCodexRecursiveFileDenies(compactWindowsCodexFilesystemRules(filesystem, platform), root, platform);
438
457
  }
439
458
  /**
440
459
  * Windows Codex sandbox setup materializes a path while applying its deny-read
@@ -462,20 +481,21 @@ export function filterWindowsCodexFilesystemTargets(patterns, platform = process
462
481
  });
463
482
  }
464
483
  /**
465
- * Codex expands recursive deny globs into individual targets. Large protected
484
+ * Codex expands recursive ACL globs into individual targets. Large protected
466
485
  * trees on Windows can then fail during AGENTS.md loading with os error 206.
467
- * Directory denies inherit to descendants through the Windows sandbox ACLs,
468
- * avoiding redundant targets. Keep broad recursive filename globs intact.
486
+ * Directory deny/read ACLs inherit to descendants, avoiding redundant targets.
487
+ * Keep broad recursive filename globs intact for the materialization pass.
469
488
  */
470
489
  export function compactWindowsCodexFilesystemRules(filesystem, platform = process.platform) {
471
490
  if (platform !== 'win32')
472
491
  return filesystem;
473
492
  const compacted = {};
493
+ const projectedKeys = new Map();
474
494
  for (const [pattern, access] of Object.entries(filesystem)) {
475
- const isTerminalRecursiveDeny = access === 'deny'
495
+ const isTerminalRecursiveAcl = (access === 'deny' || access === 'read')
476
496
  && (pattern.endsWith('/**') || pattern.endsWith('\\**'));
477
- const recursiveRoot = isTerminalRecursiveDeny ? pattern.slice(0, -3) : undefined;
478
- // Only collapse a recursive deny when its root is a real directory. A
497
+ const recursiveRoot = isTerminalRecursiveAcl ? pattern.slice(0, -3) : undefined;
498
+ // Only collapse a recursive ACL when its root is a real directory. A
479
499
  // missing root must remain a glob: turning it into an exact target can
480
500
  // make the Windows sandbox materialize that path as a directory.
481
501
  const canCompact = !!recursiveRoot
@@ -488,12 +508,104 @@ export function compactWindowsCodexFilesystemRules(filesystem, platform = proces
488
508
  }
489
509
  })());
490
510
  const compactPattern = canCompact ? recursiveRoot : pattern;
491
- if (compacted[compactPattern] !== 'deny')
492
- compacted[compactPattern] = access;
511
+ let projectedPattern = compactPattern;
512
+ {
513
+ // Resolve only the literal prefix and only casing aliases. Do not fold
514
+ // glob spelling, unresolved paths, case-sensitive directories or links.
515
+ const globIndex = compactPattern.search(CODEX_GLOB_MAGIC);
516
+ const prefix = globIndex < 0 ? compactPattern : compactPattern.slice(0, Math.max(compactPattern.lastIndexOf('/', globIndex), compactPattern.lastIndexOf('\\', globIndex)));
517
+ let key = compactPattern;
518
+ try {
519
+ const canonical = fs.realpathSync.native(prefix);
520
+ if (canonical.toLowerCase() === prefix.toLowerCase())
521
+ key = canonical + compactPattern.slice(prefix.length);
522
+ }
523
+ catch { /* An unresolved alias must retain its deny rule. */ }
524
+ projectedPattern = projectedKeys.get(key) ?? compactPattern;
525
+ projectedKeys.set(key, projectedPattern);
526
+ }
527
+ if (compacted[projectedPattern] !== 'deny')
528
+ compacted[projectedPattern] = access;
529
+ }
530
+ // Directory deny ACLs inherit. After H/L rules are combined, omit exact
531
+ // file denies already covered by an existing directory deny. Keep globs
532
+ // and any child for which a read/write override could change precedence.
533
+ const entries = Object.entries(compacted);
534
+ const denyDirectories = entries.filter(([pattern, access]) => {
535
+ if (access !== 'deny' || CODEX_GLOB_MAGIC.test(pattern))
536
+ return false;
537
+ try {
538
+ return fs.lstatSync(pattern).isDirectory();
539
+ }
540
+ catch {
541
+ return false;
542
+ }
543
+ }).map(([pattern]) => pattern);
544
+ for (const [pattern, access] of entries) {
545
+ if (access !== 'deny' || CODEX_GLOB_MAGIC.test(pattern) || denyDirectories.includes(pattern))
546
+ continue;
547
+ if (denyDirectories.some(parent => isSameOrDescendant(pattern, parent)
548
+ && !entries.some(([override, value]) => (value === 'read' || value === 'write')
549
+ && (CODEX_GLOB_MAGIC.test(override) || isSameOrDescendant(override, parent))))) {
550
+ delete compacted[pattern];
551
+ }
493
552
  }
494
553
  return compacted;
495
554
  }
555
+ /**
556
+ * Codex 0.154 serializes resolved Windows deny-read targets into a base64
557
+ * command-line argument for codex-windows-sandbox-setup.exe. Recursive file
558
+ * globs can expand into enough absolute paths to exceed CreateProcess' command
559
+ * line limit. Claude avoids the analogous limit by externalizing settings;
560
+ * Codex has no per-thread settings-file input, so materialize only these broad
561
+ * file globs in EvolCore and keep the compact exact projection in thread config.
562
+ *
563
+ * Matches below an already denied directory are omitted because the Windows
564
+ * sandbox ACL on that directory inherits to descendants. Other H-class rules
565
+ * remain dynamic globs, preserving protection for newly created agent/config
566
+ * paths without recreating the unbounded recursive expansion.
567
+ */
568
+ export function materializeWindowsCodexRecursiveFileDenies(filesystem, root = resolveRoot(), platform = process.platform) {
569
+ if (platform !== 'win32')
570
+ return filesystem;
571
+ const absoluteRoot = resolveThroughExistingAncestor(root);
572
+ const materializedPatterns = new Set(CODEX_WINDOWS_MATERIALIZED_FILE_GLOBS.map(pattern => path.join(absoluteRoot, pattern)));
573
+ const deniedDirectories = getExistingHClassMaskTargets(absoluteRoot)
574
+ .filter(target => target.kind === 'directory')
575
+ .map(target => target.path);
576
+ const projected = {};
577
+ for (const [pattern, access] of Object.entries(filesystem)) {
578
+ if (access !== 'deny' || !materializedPatterns.has(pattern)) {
579
+ projected[pattern] = access;
580
+ continue;
581
+ }
582
+ const relativePattern = path.relative(absoluteRoot, pattern).split(path.sep).join('/');
583
+ let matches;
584
+ try {
585
+ matches = fs.globSync(relativePattern, { cwd: absoluteRoot });
586
+ }
587
+ catch (error) {
588
+ throw new Error(`[CodexSandbox] failed to materialize Windows protected-file rules: ${error instanceof Error ? error.message : String(error)}`);
589
+ }
590
+ for (const match of matches) {
591
+ const candidate = path.resolve(absoluteRoot, match);
592
+ if (deniedDirectories.some(directory => isSameOrDescendant(candidate, directory)))
593
+ continue;
594
+ projected[candidate] = access;
595
+ }
596
+ }
597
+ return projected;
598
+ }
599
+ export function assertWindowsCodexFilesystemRuleBudget(filesystem, platform = process.platform) {
600
+ if (platform !== 'win32')
601
+ return;
602
+ const bytes = Buffer.byteLength(JSON.stringify(filesystem), 'utf8');
603
+ if (bytes > CODEX_WINDOWS_FILESYSTEM_RULE_BUDGET_BYTES) {
604
+ throw new Error(`[CodexSandbox] Windows filesystem rule budget exceeded: ${bytes} > ${CODEX_WINDOWS_FILESYSTEM_RULE_BUDGET_BYTES}`);
605
+ }
606
+ }
496
607
  export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options = {}) {
608
+ const platform = options.platform ?? process.platform;
497
609
  const lClassRules = {};
498
610
  const lClassAccess = options.denyLClassRead ? 'deny' : 'read';
499
611
  // Codex's Linux sandbox expands glob deny rules into per-file mount targets.
@@ -506,10 +618,14 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
506
618
  const expandLClassReadDeny = options.expandLClassReadDeny ?? true;
507
619
  for (const target of getExistingLClassReadOnlyTargets(root)) {
508
620
  if (target.kind === 'directory') {
509
- if (!expandLClassReadDeny || options.denyLClassRead || path.basename(target.path) === 'triggers') {
621
+ // :workspace/:read-only already grants L-class read access on Windows.
622
+ // Omitting redundant read ACLs keeps Codex's serialized helper payload
623
+ // below CreateProcess' command-line limit. Readonly still needs deny.
624
+ const includeWindowsRead = platform !== 'win32' || options.denyLClassRead;
625
+ if (includeWindowsRead && (!expandLClassReadDeny || options.denyLClassRead || path.basename(target.path) === 'triggers')) {
510
626
  lClassRules[target.path] = lClassAccess;
511
627
  }
512
- if (expandLClassReadDeny || !options.denyLClassRead) {
628
+ if (includeWindowsRead && (expandLClassReadDeny || !options.denyLClassRead)) {
513
629
  // Use '/**' suffix instead of path.join to satisfy Codex's glob requirement
514
630
  lClassRules[target.path + '/**'] = lClassAccess;
515
631
  }
@@ -518,10 +634,12 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
518
634
  lClassRules[target.path] = lClassAccess;
519
635
  }
520
636
  }
521
- return compactWindowsCodexFilesystemRules({
522
- ...buildCodexHClassFilesystemRules(root),
637
+ const filesystem = compactWindowsCodexFilesystemRules({
638
+ ...buildCodexHClassFilesystemRules(root, platform),
523
639
  ...lClassRules,
524
- });
640
+ }, platform);
641
+ assertWindowsCodexFilesystemRuleBudget(filesystem, platform);
642
+ return filesystem;
525
643
  }
526
644
  export function hClassGrantIncludesProtectedPath(value, options = {}) {
527
645
  const root = resolveThroughExistingAncestor(options.root ?? resolveRoot());