evolcore 0.0.21 → 0.0.22

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 (64) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/bin/codex-managed-hook.mjs +3 -0
  3. package/bin/install-codex-managed-hooks.mjs +3 -1
  4. package/dist/agents/claude-runner.js +14 -0
  5. package/dist/agents/codex-app-server-client.js +31 -5
  6. package/dist/agents/codex-runner.js +926 -121
  7. package/dist/aun/outbox.js +7 -0
  8. package/dist/channels/aun.js +209 -35
  9. package/dist/cli/daemon-commands.js +29 -8
  10. package/dist/cli/task-context.js +4 -0
  11. package/dist/cli/trigger-command.js +13 -4
  12. package/dist/config/config-field-policy.js +3 -0
  13. package/dist/config/config-manager.js +32 -5
  14. package/dist/config/contact-book-store.js +25 -3
  15. package/dist/core/auth/agent-delegation.js +12 -0
  16. package/dist/core/auth/auth-gateway.js +8 -0
  17. package/dist/core/auth/authorization-audit.js +66 -6
  18. package/dist/core/bootstrap-messages.js +8 -0
  19. package/dist/core/bootstrap-service.js +93 -25
  20. package/dist/core/command/command-handler.js +21 -0
  21. package/dist/core/command/menu-handler.js +9 -0
  22. package/dist/core/command/menu-protocol.js +1 -1
  23. package/dist/core/command/slash-handler.js +41 -18
  24. package/dist/core/data-migration.js +11 -1
  25. package/dist/core/event-catalog.js +32 -0
  26. package/dist/core/handoff/runtime.js +23 -3
  27. package/dist/core/message/im-renderer.js +7 -3
  28. package/dist/core/message/message-bridge.js +60 -2
  29. package/dist/core/message/message-log.js +33 -0
  30. package/dist/core/message/message-queue.js +21 -0
  31. package/dist/core/message/response-engine.js +172 -41
  32. package/dist/core/permission/ec-command-parser.js +272 -70
  33. package/dist/core/permission/protected-paths.js +11 -10
  34. package/dist/core/permission/tool-error-code.js +12 -0
  35. package/dist/core/permission/tool-policy.js +46 -5
  36. package/dist/core/session/session-manager.js +30 -0
  37. package/dist/core/session/session-renew.js +18 -1
  38. package/dist/core/session/session-turn-coordinator.js +5 -1
  39. package/dist/index.js +64 -5
  40. package/dist/ipc.js +97 -17
  41. package/dist/paths.js +18 -0
  42. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  43. package/dist/stats/price-resolver.js +4 -0
  44. package/dist/trigger/feedback.js +14 -2
  45. package/dist/trigger/parser.js +10 -1
  46. package/dist/trigger/scheduler.js +20 -3
  47. package/dist/utils/logger.js +9 -4
  48. package/dist/utils/tool-summary.js +59 -0
  49. package/dist/utils/windows-shell-trust.js +201 -0
  50. package/kits/docs/evolcore/INDEX.md +2 -2
  51. package/kits/docs/evolcore/agent-create.md +146 -0
  52. package/kits/docs/evolcore/agent.md +6 -0
  53. package/kits/docs/evolcore/group-collaboration.md +251 -0
  54. package/kits/docs/evolcore/group-rules.md +1 -19
  55. package/kits/docs/evolcore/group.md +3 -1
  56. package/kits/docs/evolcore/trigger.md +6 -3
  57. package/kits/docs/prompt-loading-architecture.md +6 -0
  58. package/kits/eck_message_manifest.json +6 -6
  59. package/kits/schemas/_meta.json +3 -2
  60. package/kits/schemas/agent-config.schema.12.json +427 -0
  61. package/kits/templates/message-fragments/item.md +1 -1
  62. package/kits/templates/system-fragments/bootstrap.md +2 -1
  63. package/kits/templates/system-fragments/commands.md +2 -2
  64. package/package.json +2 -2
@@ -1,15 +1,19 @@
1
+ import { isTrustedWindowsShellExecutable } from '../../utils/windows-shell-trust.js';
1
2
  const HEAD_EXECUTABLES = new Set(['head', '/bin/head', '/usr/bin/head']);
2
3
  // Codex app-server sends command executions through a fixed shell carrier.
3
4
  // Treat the carrier as syntax only after parsing it as literal argv; never
4
5
  // accept an arbitrary shell command string or unwrap composition here.
5
6
  const CODEX_POSIX_SHELL_EXECUTABLES = new Set([
6
- 'bash', '/bin/bash', '/usr/bin/bash',
7
- 'sh', '/bin/sh', '/usr/bin/sh',
7
+ 'bash', 'bash.exe', '/bin/bash', '/usr/bin/bash',
8
+ 'sh', 'sh.exe', '/bin/sh', '/usr/bin/sh',
8
9
  ]);
9
10
  const CODEX_POWERSHELL_EXECUTABLES = new Set([
10
11
  'pwsh', 'pwsh.exe', 'powershell', 'powershell.exe',
11
12
  ]);
12
13
  const CODEX_POWERSHELL_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Program Files[\\/]PowerShell[\\/][0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9.-]+)?[\\/]pwsh\.exe|Windows[\\/]System32[\\/]WindowsPowerShell[\\/]v1\.0[\\/]powershell\.exe)$/i;
14
+ const CODEX_CUSTOM_POSIX_SHELL_NAMES = new Set(['bash', 'bash.exe', 'sh', 'sh.exe']);
15
+ const CODEX_CUSTOM_POWERSHELL_NAMES = new Set(['pwsh.exe', 'powershell.exe']);
16
+ const CODEX_CUSTOM_CMD_NAMES = new Set(['cmd.exe']);
13
17
  const CODEX_CMD_EXECUTABLES = new Set(['cmd', 'cmd.exe']);
14
18
  const CODEX_CMD_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Windows[\\/](?:System32|SysWOW64|Sysnative)[\\/]cmd\.exe)$/i;
15
19
  // These switches affect only cmd startup/output formatting. `/c` is handled
@@ -17,6 +21,12 @@ const CODEX_CMD_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Windows[\\/](?:System32|Sys
17
21
  // `/e:*`, and `/f:*` can change process lifetime or command semantics and are
18
22
  // intentionally not treated as a trusted Codex carrier.
19
23
  const CODEX_CMD_INERT_SWITCHES = new Set(['/d', '/s', '/q', '/a', '/u']);
24
+ const EVOLCORE_EXECUTABLE_ALIASES = new Set(['ec', 'ec.cmd', 'ec.exe']);
25
+ const EVOLCORE_NODE_EXECUTABLES = new Set(['node', 'node.exe']);
26
+ // Development/install entrypoints that are part of this repository's CLI.
27
+ // Only these exact relative paths are canonicalized; arbitrary Node scripts
28
+ // must never become an EC command merely because their filename is similar.
29
+ const EVOLCORE_NODE_WRAPPER_PATHS = new Set(['bin/ec.js', 'dist/cli/index.js']);
20
30
  const SHELL_UNQUOTED_META = new Set([
21
31
  ';', '&', '|', '`', '<', '>', '$', '(', ')', '*', '?', '[', ']', '{', '}', '#',
22
32
  ]);
@@ -68,8 +78,7 @@ function hasEcCommandPrefix(command) {
68
78
  }
69
79
  if (boundary && /\s/.test(char))
70
80
  continue;
71
- if (boundary && command.slice(index, index + 2).toLowerCase() === 'ec'
72
- && (index + 2 === command.length || /\s|[;&|`()<>]/.test(command[index + 2])))
81
+ if (boundary && /^ec(?:\.cmd|\.exe)?(?=$|\s|[;&|`()<>])/i.test(command.slice(index)))
73
82
  return true;
74
83
  boundary = false;
75
84
  }
@@ -78,6 +87,45 @@ function hasEcCommandPrefix(command) {
78
87
  function trimShellSpacing(value) {
79
88
  return value.replace(/^[ \t]+|[ \t]+$/g, '');
80
89
  }
90
+ /**
91
+ * Accept an explicitly materialized absolute shell path, including Windows
92
+ * Store/App Execution Alias paths such as `...\\WindowsApps\\pwsh.exe`.
93
+ * Relative names remain restricted to the fixed executable allowlists above.
94
+ */
95
+ function isAbsoluteCustomShellPath(value, executableNames) {
96
+ if (!value || /[\0\r\n"'<>|?*]/.test(value))
97
+ return false;
98
+ const normalized = value.replaceAll('\\', '/');
99
+ if (!/^[A-Za-z]:\//.test(normalized) && !normalized.startsWith('//'))
100
+ return false;
101
+ const basename = normalized.slice(normalized.lastIndexOf('/') + 1).toLowerCase();
102
+ return executableNames.has(basename);
103
+ }
104
+ function isNodeExecutable(value) {
105
+ if (!value)
106
+ return false;
107
+ const normalized = value.toLowerCase();
108
+ return EVOLCORE_NODE_EXECUTABLES.has(normalized)
109
+ || isAbsoluteCustomShellPath(value, EVOLCORE_NODE_EXECUTABLES);
110
+ }
111
+ function normalizeNodeWrapperPath(value) {
112
+ const normalized = value.replaceAll('\\', '/').replace(/^\.\//, '').toLowerCase();
113
+ return EVOLCORE_NODE_WRAPPER_PATHS.has(normalized) ? normalized : undefined;
114
+ }
115
+ function isCodexPosixShellExecutable(value) {
116
+ if (!value)
117
+ return false;
118
+ return CODEX_POSIX_SHELL_EXECUTABLES.has(value.toLowerCase())
119
+ || isAbsoluteCustomShellPath(value, CODEX_CUSTOM_POSIX_SHELL_NAMES);
120
+ }
121
+ function isCodexPowerShellExecutable(value) {
122
+ if (!value)
123
+ return false;
124
+ const normalized = value.toLowerCase();
125
+ return CODEX_POWERSHELL_EXECUTABLES.has(normalized)
126
+ || CODEX_POWERSHELL_INSTALL_PATH_RE.test(value)
127
+ || isAbsoluteCustomShellPath(value, CODEX_CUSTOM_POWERSHELL_NAMES);
128
+ }
81
129
  const MANAGED_TMPDIR_TOKEN = '__EC_MANAGED_TMPDIR__';
82
130
  /**
83
131
  * Preserve the one environment reference allowed in managed EC paths.
@@ -154,7 +202,155 @@ function hasCarrierEcCommandPrefix(command, dialect) {
154
202
  const inner = input.length >= 2 && input.startsWith('"') && input.endsWith('"')
155
203
  ? input.slice(1, -1)
156
204
  : input;
157
- return /^\s*ec(?:\s|$)/i.test(inner);
205
+ return /^\s*ec(?:\.cmd|\.exe)?(?:\s|$)/i.test(inner);
206
+ }
207
+ /** Normalize the Windows npm shims to the canonical EvolCore CLI name. */
208
+ export function normalizeEvolcoreCommandArgv(argv) {
209
+ if (argv.length === 0)
210
+ return [];
211
+ const executable = argv[0];
212
+ if (typeof executable === 'string' && EVOLCORE_EXECUTABLE_ALIASES.has(executable.toLowerCase())) {
213
+ return ['ec', ...argv.slice(1)];
214
+ }
215
+ if (typeof executable === 'string' && isNodeExecutable(executable) && argv.length >= 2
216
+ && typeof argv[1] === 'string' && normalizeNodeWrapperPath(argv[1])) {
217
+ return ['ec', ...argv.slice(2)];
218
+ }
219
+ return [...argv];
220
+ }
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)
224
+ && /\bec(?:\.cmd|\.exe)?(?:\s|$)/i.test(command);
225
+ }
226
+ /**
227
+ * Windows Codex may materialize one environment variable assignment directly
228
+ * 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.
233
+ */
234
+ function unwrapPowerShellDelegationPrefix(command) {
235
+ const input = command.trim();
236
+ if (!/^\$env\s*:\s*EVOLCORE_DELEGATION_TOKEN\b/i.test(input)) {
237
+ return { kind: 'none', command };
238
+ }
239
+ let index = 0;
240
+ const skipSpace = () => {
241
+ while (index < input.length && (input[index] === ' ' || input[index] === '\t'))
242
+ index++;
243
+ };
244
+ const consume = (value) => {
245
+ if (input.slice(index, index + value.length).toLowerCase() !== value.toLowerCase())
246
+ return false;
247
+ index += value.length;
248
+ return true;
249
+ };
250
+ skipSpace();
251
+ if (!consume('$env') || input[index] !== ':')
252
+ return { kind: 'invalid', command };
253
+ index++;
254
+ skipSpace();
255
+ if (!consume('EVOLCORE_DELEGATION_TOKEN'))
256
+ return { kind: 'invalid', command };
257
+ skipSpace();
258
+ if (input[index] !== '=')
259
+ return { kind: 'invalid', command };
260
+ index++;
261
+ skipSpace();
262
+ const quote = input[index];
263
+ if (quote !== "'" && quote !== '"')
264
+ return { kind: 'invalid', command };
265
+ const valueStart = index++;
266
+ let value = '';
267
+ let closed = false;
268
+ // When the complete -Command body is itself single-quoted, command-line
269
+ // serialization can leave the assignment value in a doubled `''token''`
270
+ // form. Its token alphabet cannot contain quotes, so this exact shape is
271
+ // unambiguous and can be decoded without executing PowerShell grammar.
272
+ if (quote === "'" && input[valueStart + 1] === "'") {
273
+ const contentStart = valueStart + 2;
274
+ const end = input.indexOf("''", contentStart);
275
+ if (end < contentStart)
276
+ return { kind: 'invalid', command };
277
+ value = input.slice(contentStart, end);
278
+ index = end + 2;
279
+ closed = true;
280
+ }
281
+ while (index < input.length) {
282
+ if (closed)
283
+ break;
284
+ const char = input[index];
285
+ if (char === quote) {
286
+ // PowerShell single-quoted strings escape a quote as `''`. For the
287
+ // delegation token alphabet this is not needed, but accepting it lets us
288
+ // decode the doubled-quote form emitted by powershell.exe argv quoting.
289
+ if (quote === "'" && input[index + 1] === "'") {
290
+ value += "'";
291
+ index += 2;
292
+ continue;
293
+ }
294
+ index++;
295
+ closed = true;
296
+ break;
297
+ }
298
+ if (quote === '"' && char === '`')
299
+ return { kind: 'invalid', command };
300
+ if (char === '\r' || char === '\n')
301
+ return { kind: 'invalid', command };
302
+ value += char;
303
+ index++;
304
+ }
305
+ if (!closed || index <= valueStart + 1 || !/^[A-Za-z0-9_-]{32,128}$/.test(value)) {
306
+ return { kind: 'invalid', command };
307
+ }
308
+ if (input[index] !== ';')
309
+ return { kind: 'invalid', command };
310
+ index++;
311
+ const remainder = input.slice(index).trim();
312
+ if (!remainder)
313
+ return { kind: 'invalid', command };
314
+ return { kind: 'valid', command: remainder, token: value };
315
+ }
316
+ export function parseCodexCarrierCommand(carrier, options = {}) {
317
+ if (options.verifyShellExecutable) {
318
+ if (!carrier.executable) {
319
+ return parseFailure('untrusted-shell', carrier.command, 0, 0, carrier.dialect);
320
+ }
321
+ const kind = carrier.dialect === 'posix' ? 'bash' : carrier.dialect;
322
+ if (!isTrustedWindowsShellExecutable(carrier.executable, kind)) {
323
+ return parseFailure('untrusted-shell', carrier.command, 0, 0, carrier.dialect);
324
+ }
325
+ }
326
+ const unwrapped = carrier.dialect === 'powershell'
327
+ ? unwrapPowerShellDelegationPrefix(carrier.command)
328
+ : { kind: 'none', command: carrier.command };
329
+ // Windows PowerShell may materialize the thread's shell environment as a
330
+ // single `$env:EVOLCORE_DELEGATION_TOKEN = '...'` prefix. Keep the token as
331
+ // metadata for the approval layer, which must compare it with the runner's
332
+ // in-memory carrier before arming delegation. Malformed or unrelated
333
+ // assignments remain syntax failures and can never be authorized.
334
+ if (unwrapped.kind === 'invalid'
335
+ || (carrier.dialect === 'powershell'
336
+ && hasPowerShellDelegationAssignment(carrier.command)
337
+ && unwrapped.kind === 'none')
338
+ || (unwrapped.kind === 'valid'
339
+ && options.expectedDelegationToken !== undefined
340
+ && options.expectedDelegationToken !== unwrapped.token)) {
341
+ return parseFailure('shell-composition', carrier.command, 0, 0, carrier.dialect);
342
+ }
343
+ const parsed = parseLiteralShellCommand(unwrapped.command, {
344
+ ...options,
345
+ dialect: carrier.dialect,
346
+ });
347
+ return parsed.ok
348
+ ? {
349
+ ...parsed,
350
+ argv: normalizeEvolcoreCommandArgv(parsed.argv),
351
+ ...(unwrapped.kind === 'valid' ? { delegationToken: unwrapped.token } : {}),
352
+ }
353
+ : parsed;
158
354
  }
159
355
  function parseFailure(issue, input, offset, tokenIndex, dialect) {
160
356
  return {
@@ -355,7 +551,9 @@ function isCodexCmdExecutable(value) {
355
551
  if (!value)
356
552
  return false;
357
553
  const normalized = value.toLowerCase();
358
- return CODEX_CMD_EXECUTABLES.has(normalized) || CODEX_CMD_INSTALL_PATH_RE.test(value);
554
+ return CODEX_CMD_EXECUTABLES.has(normalized)
555
+ || CODEX_CMD_INSTALL_PATH_RE.test(value)
556
+ || isAbsoluteCustomShellPath(value, CODEX_CUSTOM_CMD_NAMES);
359
557
  }
360
558
  function resolveCodexCmdCarrierArgv(argv) {
361
559
  if (argv.length < 3 || !isCodexCmdExecutable(argv[0]))
@@ -377,7 +575,7 @@ function resolveCodexCmdCarrierArgv(argv) {
377
575
  }
378
576
  const command = argv.at(-1);
379
577
  return typeof command === 'string' && command.trim()
380
- ? { command, dialect: 'cmd' }
578
+ ? { command, dialect: 'cmd', executable: argv[0] }
381
579
  : undefined;
382
580
  }
383
581
  /** Resolve a Windows cmd carrier when the app-server provides only a string. */
@@ -402,10 +600,7 @@ function resolveCodexPowerShellCarrierString(command) {
402
600
  if (!match)
403
601
  return undefined;
404
602
  const executable = match[1] ?? match[2];
405
- const normalized = executable.toLowerCase();
406
- const isPowerShell = CODEX_POWERSHELL_EXECUTABLES.has(normalized)
407
- || CODEX_POWERSHELL_INSTALL_PATH_RE.test(executable);
408
- if (!isPowerShell)
603
+ if (!isCodexPowerShellExecutable(executable))
409
604
  return undefined;
410
605
  // A string-form carrier has one additional command-line quoting layer. The
411
606
  // command argument is commonly serialized as `"ec ..."`; remove exactly
@@ -416,11 +611,31 @@ function resolveCodexPowerShellCarrierString(command) {
416
611
  || (nested.startsWith("'") && nested.endsWith("'")))) {
417
612
  nested = nested.slice(1, -1);
418
613
  }
419
- return nested ? { command: nested, dialect: 'powershell' } : undefined;
614
+ return nested ? { command: nested, dialect: 'powershell', executable } : undefined;
615
+ }
616
+ /** Resolve a POSIX shell carrier when app-server provides only a string. */
617
+ function resolveCodexPosixShellCarrierString(command) {
618
+ const input = trimShellSpacing(command);
619
+ const match = input.match(/^(?:"([^"]+)"|(\S+))\s+(-lc|-c)\s+(.+)$/);
620
+ if (!match)
621
+ return undefined;
622
+ const executable = match[1] ?? match[2];
623
+ if (!isCodexPosixShellExecutable(executable))
624
+ return undefined;
625
+ // A string-form carrier has one additional command-line quoting layer. Strip
626
+ // only the outer pair; quotes belonging to the nested command remain intact.
627
+ let nested = match[4].trim();
628
+ if (nested.length >= 2
629
+ && ((nested.startsWith('"') && nested.endsWith('"'))
630
+ || (nested.startsWith("'") && nested.endsWith("'")))) {
631
+ nested = nested.slice(1, -1);
632
+ }
633
+ return nested ? { command: nested, dialect: 'posix', executable } : undefined;
420
634
  }
421
635
  export function resolveCodexShellCarrierString(command) {
422
636
  return resolveCodexCmdCarrierString(command)
423
- ?? resolveCodexPowerShellCarrierString(command);
637
+ ?? resolveCodexPowerShellCarrierString(command)
638
+ ?? resolveCodexPosixShellCarrierString(command);
424
639
  }
425
640
  /**
426
641
  * Extract a trusted cmd executable's command body without trusting its startup
@@ -462,7 +677,7 @@ function looksLikeEcCommandBody(command) {
462
677
  return false;
463
678
  const input = command.trim();
464
679
  const body = (input.startsWith('"') ? input.slice(1) : input).trimStart();
465
- return /^ec(?:\s|$)/i.test(body);
680
+ return /^ec(?:\.cmd|\.exe)?(?:\s|$)/i.test(body);
466
681
  }
467
682
  /** True only when a trusted cmd executable is carrying an EC command body. */
468
683
  export function hasCodexCmdCarrierEcIntent(input) {
@@ -627,15 +842,14 @@ export function parseLiteralShellArgv(command, options = {}) {
627
842
  return result.ok ? result.argv : null;
628
843
  }
629
844
  export function resolveCodexShellCarrierArgv(argv) {
630
- if (argv.length === 3 && argv[1] === '-lc' && CODEX_POSIX_SHELL_EXECUTABLES.has(argv[0])) {
631
- return { command: argv[2], dialect: 'posix' };
845
+ if (argv.length === 3
846
+ && (argv[1] === '-lc' || argv[1] === '-c')
847
+ && isCodexPosixShellExecutable(argv[0])) {
848
+ return { command: argv[2], dialect: 'posix', executable: argv[0] };
632
849
  }
633
850
  if (argv.length === 3) {
634
- const powershellExecutable = argv[0].toLowerCase();
635
- const isPowerShell = CODEX_POWERSHELL_EXECUTABLES.has(powershellExecutable)
636
- || CODEX_POWERSHELL_INSTALL_PATH_RE.test(argv[0]);
637
- if (isPowerShell && argv[1].toLowerCase() === '-command') {
638
- return { command: argv[2], dialect: 'powershell' };
851
+ if (isCodexPowerShellExecutable(argv[0]) && argv[1].toLowerCase() === '-command') {
852
+ return { command: argv[2], dialect: 'powershell', executable: argv[0] };
639
853
  }
640
854
  }
641
855
  // Node/cross-spawn and Codex use cmd.exe with a small set of startup flags.
@@ -656,27 +870,26 @@ export function parseCodexShellCommandArgv(command, options = {}) {
656
870
  if (outer) {
657
871
  const carrier = resolveCodexShellCarrierArgv(outer);
658
872
  if (carrier !== undefined) {
659
- return parseLiteralShellArgv(carrier.command, { ...options, dialect: carrier.dialect });
873
+ const parsed = parseCodexCarrierCommand(carrier, options);
874
+ return parsed.ok ? parsed.argv : null;
660
875
  }
661
876
  const stringCarrier = resolveCodexShellCarrierString(command);
662
877
  if (stringCarrier) {
663
- return parseLiteralShellArgv(stringCarrier.command, { ...options, dialect: stringCarrier.dialect });
878
+ const parsed = parseCodexCarrierCommand(stringCarrier, options);
879
+ return parsed.ok ? parsed.argv : null;
664
880
  }
665
881
  if (parseCodexCmdCarrierStringFailure(command, options))
666
882
  return null;
667
- return outer;
883
+ return normalizeEvolcoreCommandArgv(outer);
668
884
  }
669
885
  const stringCarrier = resolveCodexShellCarrierString(command);
670
886
  if (!stringCarrier)
671
887
  return null;
672
- const parsed = parseLiteralShellCommand(stringCarrier.command, {
673
- ...options,
674
- dialect: stringCarrier.dialect,
675
- });
676
- return parsed.ok ? parsed.argv : null;
888
+ const parsed = parseCodexCarrierCommand(stringCarrier, options);
889
+ return parsed.ok ? normalizeEvolcoreCommandArgv(parsed.argv) : null;
677
890
  }
678
891
  /** Parse a Codex tool input while preserving safe syntax failure metadata. */
679
- export function parseCodexToolCommand(input) {
892
+ export function parseCodexToolCommand(input, options = {}) {
680
893
  if (Array.isArray(input.commandArgv)) {
681
894
  if (!input.commandArgv.every(value => typeof value === 'string')) {
682
895
  return parseFailure('invalid-control-char', '', 0, 0, 'posix');
@@ -687,46 +900,36 @@ export function parseCodexToolCommand(input) {
687
900
  return parseFailure('invalid-control-char', '', 0, nulIndex, 'posix');
688
901
  const carrier = resolveCodexShellCarrierArgv(outer);
689
902
  if (carrier === undefined) {
690
- return parseCodexCmdCarrierArgvFailure(outer) ?? { ok: true, argv: outer };
903
+ return parseCodexCmdCarrierArgvFailure(outer)
904
+ ?? { ok: true, argv: normalizeEvolcoreCommandArgv(outer) };
691
905
  }
692
- return parseLiteralShellCommand(carrier.command, {
693
- allowManagedTmpDir: true,
694
- dialect: carrier.dialect,
695
- });
906
+ return parseCodexCarrierCommand(carrier, { allowManagedTmpDir: true, ...options });
696
907
  }
697
908
  const command = typeof input.command === 'string' ? input.command : '';
698
909
  const outer = parseLiteralShellCommand(command, { allowManagedTmpDir: true });
699
910
  if (outer.ok) {
700
911
  const carrier = resolveCodexShellCarrierArgv(outer.argv);
701
912
  if (carrier !== undefined) {
702
- return parseLiteralShellCommand(carrier.command, {
703
- allowManagedTmpDir: true,
704
- dialect: carrier.dialect,
705
- });
913
+ return parseCodexCarrierCommand(carrier, { allowManagedTmpDir: true, ...options });
706
914
  }
707
915
  const stringCarrier = resolveCodexShellCarrierString(command);
708
916
  return stringCarrier
709
- ? parseLiteralShellCommand(stringCarrier.command, {
710
- allowManagedTmpDir: true,
711
- dialect: stringCarrier.dialect,
712
- })
713
- : parseCodexCmdCarrierStringFailure(command, { allowManagedTmpDir: true }) ?? outer;
917
+ ? parseCodexCarrierCommand(stringCarrier, { allowManagedTmpDir: true, ...options })
918
+ : parseCodexCmdCarrierStringFailure(command, { allowManagedTmpDir: true })
919
+ ?? { ...outer, argv: normalizeEvolcoreCommandArgv(outer.argv) };
714
920
  }
715
921
  const stringCarrier = resolveCodexShellCarrierString(command);
716
922
  return stringCarrier
717
- ? parseLiteralShellCommand(stringCarrier.command, {
718
- allowManagedTmpDir: true,
719
- dialect: stringCarrier.dialect,
720
- })
923
+ ? parseCodexCarrierCommand(stringCarrier, { allowManagedTmpDir: true, ...options })
721
924
  : parseCodexCmdCarrierStringFailure(command, { allowManagedTmpDir: true }) ?? outer;
722
925
  }
723
926
  /**
724
927
  * Resolve a Codex command input while preserving the raw `commandArgv` form
725
928
  * supplied by app-server. Joining that argv with spaces loses the quoting of
726
- * the inner `-lc` command and was the source of a false proactive denial.
929
+ * the inner `-c`/`-lc` command and was the source of a false proactive denial.
727
930
  */
728
- export function parseCodexToolCommandArgv(input) {
729
- const result = parseCodexToolCommand(input);
931
+ export function parseCodexToolCommandArgv(input, options = {}) {
932
+ const result = parseCodexToolCommand(input, options);
730
933
  return result.ok ? result.argv : null;
731
934
  }
732
935
  function isSimpleExecStringLiteral(value) {
@@ -1214,12 +1417,15 @@ export function parseCodexExecToolCommand(input) {
1214
1417
  return decoded;
1215
1418
  }
1216
1419
  function parseProactiveToolCommandArgv(toolName, input) {
1420
+ const options = {
1421
+ verifyShellExecutable: process.platform === 'win32',
1422
+ };
1217
1423
  if (toolName === 'Bash' || toolName === 'Shell')
1218
- return parseCodexToolCommandArgv(input);
1424
+ return parseCodexToolCommandArgv(input, options);
1219
1425
  if (toolName !== 'exec')
1220
1426
  return null;
1221
1427
  const command = parseCodexExecToolCommand(input);
1222
- return command ? parseCodexShellCommandArgv(command, { allowManagedTmpDir: true }) : null;
1428
+ return command ? parseCodexShellCommandArgv(command, { allowManagedTmpDir: true, ...options }) : null;
1223
1429
  }
1224
1430
  function scanBoundedOutputOperators(command) {
1225
1431
  const operators = [];
@@ -1410,29 +1616,25 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
1410
1616
  const parseOptions = { allowManagedTmpDir: true, ...options };
1411
1617
  const literal = parseLiteralShellCommand(command, parseOptions);
1412
1618
  if (literal.ok) {
1413
- if (literal.argv[0] === 'ec')
1619
+ if (normalizeEvolcoreCommandArgv(literal.argv)[0] === 'ec')
1414
1620
  return { kind: 'literal' };
1415
1621
  const carrier = resolveCodexShellCarrierArgv(literal.argv);
1416
1622
  if (carrier !== undefined) {
1417
- const nested = parseLiteralShellCommand(carrier.command, {
1418
- ...parseOptions,
1419
- dialect: carrier.dialect,
1420
- });
1623
+ const nested = parseCodexCarrierCommand(carrier, parseOptions);
1421
1624
  if (nested.ok && nested.argv[0] === 'ec')
1422
1625
  return { kind: 'literal' };
1423
- if (!nested.ok && hasCarrierEcCommandPrefix(carrier.command, carrier.dialect)) {
1626
+ if (!nested.ok && (hasCarrierEcCommandPrefix(carrier.command, carrier.dialect)
1627
+ || (carrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(carrier.command)))) {
1424
1628
  return { kind: 'composite', issue: nested.issue };
1425
1629
  }
1426
1630
  }
1427
1631
  const stringCarrier = resolveCodexShellCarrierString(command);
1428
1632
  if (stringCarrier) {
1429
- const nested = parseLiteralShellCommand(stringCarrier.command, {
1430
- ...parseOptions,
1431
- dialect: stringCarrier.dialect,
1432
- });
1633
+ const nested = parseCodexCarrierCommand(stringCarrier, parseOptions);
1433
1634
  if (nested.ok && nested.argv[0] === 'ec')
1434
1635
  return { kind: 'literal' };
1435
- if (!nested.ok && hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)) {
1636
+ if (!nested.ok && (hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
1637
+ || (stringCarrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(stringCarrier.command)))) {
1436
1638
  return { kind: 'composite', issue: nested.issue };
1437
1639
  }
1438
1640
  }
@@ -1450,13 +1652,11 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
1450
1652
  }
1451
1653
  const stringCarrier = resolveCodexShellCarrierString(command);
1452
1654
  if (stringCarrier) {
1453
- const nested = parseLiteralShellCommand(stringCarrier.command, {
1454
- ...parseOptions,
1455
- dialect: stringCarrier.dialect,
1456
- });
1655
+ const nested = parseCodexCarrierCommand(stringCarrier, parseOptions);
1457
1656
  if (nested.ok && nested.argv[0] === 'ec')
1458
1657
  return { kind: 'literal' };
1459
- if (!nested.ok && hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)) {
1658
+ if (!nested.ok && (hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
1659
+ || (stringCarrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(stringCarrier.command)))) {
1460
1660
  return { kind: 'composite', issue: nested.issue };
1461
1661
  }
1462
1662
  return { kind: 'none' };
@@ -1687,7 +1887,9 @@ export function parseEvolcoreSendCommand(command) {
1687
1887
  export function isEvolcoreSendCommandForSession(toolName, input, channelId) {
1688
1888
  if (toolName !== 'Bash')
1689
1889
  return false;
1690
- const argv = parseCodexToolCommandArgv(input);
1890
+ const argv = parseCodexToolCommandArgv(input, {
1891
+ verifyShellExecutable: process.platform === 'win32',
1892
+ });
1691
1893
  const parsed = argv ? parseEvolcoreSendArgv(argv) : null;
1692
1894
  if (!parsed)
1693
1895
  return false;
@@ -268,21 +268,21 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
268
268
  '.lock',
269
269
  'daemon.pid',
270
270
  path.join('data', 'causation-aun.json*'),
271
- path.join('data', 'triggers', '**'),
272
- path.join('data', 'sessions', '**'),
271
+ path.join('data', 'triggers') + '/**',
272
+ path.join('data', 'sessions') + '/**',
273
273
  path.join('data', 'contact-book-audit.jsonl'),
274
274
  path.join('data', 'feishu-seen-*.jsonl'),
275
275
  path.join('data', '*-greeted-*.jsonl'),
276
276
  path.join('data', 'message-queue.json*'),
277
277
  path.join('data', 'daemon', 'message-queue.json*'),
278
- path.join('data', 'daemon', 'control', '**'),
279
- path.join('data', 'daemon', 'migrations', '**'),
280
- path.join('data', 'channel-state', '**'),
278
+ path.join('data', 'daemon', 'control') + '/**',
279
+ path.join('data', 'daemon', 'migrations') + '/**',
280
+ path.join('data', 'channel-state') + '/**',
281
281
  path.join('data', 'instance', 'control.token'),
282
282
  path.join('data', 'outbox'),
283
- path.join('data', 'outbox', '**'),
283
+ path.join('data', 'outbox') + '/**',
284
284
  path.join('data', 'handoff'),
285
- path.join('data', 'handoff', '**'),
285
+ path.join('data', 'handoff') + '/**',
286
286
  path.join('agents', '*', 'sessions', '**'),
287
287
  path.join('agents', '*', 'data', 'channels', '**'),
288
288
  path.join('agents', '*', 'data', 'handoff', '**'),
@@ -296,8 +296,8 @@ export function getLClassWritePatterns(root = resolveRoot()) {
296
296
  const absoluteRoot = resolveThroughExistingAncestor(root);
297
297
  return [
298
298
  path.join(absoluteRoot, 'agents', '*', 'triggers'),
299
- path.join(absoluteRoot, 'agents', '*', 'triggers', '**'),
300
- path.join(absoluteRoot, 'agents', '*', 'roles', '**'),
299
+ path.join(absoluteRoot, 'agents', '*', 'triggers') + '/**',
300
+ path.join(absoluteRoot, 'agents', '*', 'roles') + '/**',
301
301
  ];
302
302
  }
303
303
  const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
@@ -451,7 +451,8 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
451
451
  lClassRules[target.path] = lClassAccess;
452
452
  }
453
453
  if (expandLClassReadDeny || !options.denyLClassRead) {
454
- lClassRules[path.join(target.path, '**')] = lClassAccess;
454
+ // Use '/**' suffix instead of path.join to satisfy Codex's glob requirement
455
+ lClassRules[target.path + '/**'] = lClassAccess;
455
456
  }
456
457
  }
457
458
  else {
@@ -14,6 +14,18 @@ export function classifyToolErrorCode(input) {
14
14
  const explicit = normalizeToolErrorCode(input.errorCode);
15
15
  if (explicit)
16
16
  return explicit;
17
+ if (input.decisionSource === 'policy')
18
+ return 'POLICY_DENIED';
19
+ if (input.decisionSource === 'approval')
20
+ return 'USER_DENIED';
21
+ return 'EXECUTION_FAILED';
22
+ }
23
+ /**
24
+ * Compatibility classifier for historical, unstructured logs only.
25
+ * Production event producers must use classifyToolErrorCode() and explicit
26
+ * errorCode/decisionSource fields instead of interpreting command output.
27
+ */
28
+ export function classifyLegacyToolErrorText(input) {
17
29
  const text = [input.error, input.result]
18
30
  .map(value => {
19
31
  if (typeof value === 'string')