@jini-ai/daemon 0.3.2 → 0.3.7

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 (87) hide show
  1. package/dist/agent-executor.d.ts +133 -3
  2. package/dist/agent-executor.js +277 -12
  3. package/dist/artifacts/index.d.ts +14 -0
  4. package/dist/artifacts/index.js +14 -0
  5. package/dist/artifacts/manifest.d.ts +90 -0
  6. package/dist/artifacts/manifest.js +229 -0
  7. package/dist/artifacts/publication-guard.d.ts +28 -0
  8. package/dist/artifacts/publication-guard.js +77 -0
  9. package/dist/artifacts/runtime-compat.d.ts +37 -0
  10. package/dist/artifacts/runtime-compat.js +33 -0
  11. package/dist/artifacts/store.d.ts +85 -0
  12. package/dist/artifacts/store.js +92 -0
  13. package/dist/artifacts/stub-guard.d.ts +73 -0
  14. package/dist/artifacts/stub-guard.js +198 -0
  15. package/dist/artifacts/text-suppression.d.ts +68 -0
  16. package/dist/artifacts/text-suppression.js +184 -0
  17. package/dist/run-lifecycle.d.ts +20 -0
  18. package/dist/run-lifecycle.js +20 -0
  19. package/dist/tool-result-surfaces.d.ts +1 -1
  20. package/dist/tool-result-surfaces.js +1 -1
  21. package/package.json +6 -5
  22. package/dist/agent-executor.d.ts.map +0 -1
  23. package/dist/agent-executor.js.map +0 -1
  24. package/dist/close-status.d.ts.map +0 -1
  25. package/dist/close-status.js.map +0 -1
  26. package/dist/continuation/continuation-transport.d.ts.map +0 -1
  27. package/dist/continuation/continuation-transport.js.map +0 -1
  28. package/dist/continuation/index.d.ts.map +0 -1
  29. package/dist/continuation/index.js.map +0 -1
  30. package/dist/continuation/journal.d.ts.map +0 -1
  31. package/dist/continuation/journal.js.map +0 -1
  32. package/dist/continuation/run-scoped-context-store.d.ts.map +0 -1
  33. package/dist/continuation/run-scoped-context-store.js.map +0 -1
  34. package/dist/continuation/run-start-handler.d.ts.map +0 -1
  35. package/dist/continuation/run-start-handler.js.map +0 -1
  36. package/dist/delegated-tool-bridge.d.ts.map +0 -1
  37. package/dist/delegated-tool-bridge.js.map +0 -1
  38. package/dist/event-log.d.ts.map +0 -1
  39. package/dist/event-log.js.map +0 -1
  40. package/dist/frontend-capability-tools.d.ts.map +0 -1
  41. package/dist/frontend-capability-tools.js.map +0 -1
  42. package/dist/frontend-session-registry.d.ts.map +0 -1
  43. package/dist/frontend-session-registry.js.map +0 -1
  44. package/dist/image-prompt-delivery.d.ts.map +0 -1
  45. package/dist/image-prompt-delivery.js.map +0 -1
  46. package/dist/index.d.ts.map +0 -1
  47. package/dist/index.js.map +0 -1
  48. package/dist/legacy-data-migration.d.ts.map +0 -1
  49. package/dist/legacy-data-migration.js.map +0 -1
  50. package/dist/remote-tool-bridge.d.ts.map +0 -1
  51. package/dist/remote-tool-bridge.js.map +0 -1
  52. package/dist/routines/index.d.ts.map +0 -1
  53. package/dist/routines/index.js.map +0 -1
  54. package/dist/routines/routine-store.d.ts.map +0 -1
  55. package/dist/routines/routine-store.js.map +0 -1
  56. package/dist/routines/schedule.d.ts.map +0 -1
  57. package/dist/routines/schedule.js.map +0 -1
  58. package/dist/routines/scheduler.d.ts.map +0 -1
  59. package/dist/routines/scheduler.js.map +0 -1
  60. package/dist/routines/types.d.ts.map +0 -1
  61. package/dist/routines/types.js.map +0 -1
  62. package/dist/run/core/failure-taxonomy.d.ts.map +0 -1
  63. package/dist/run/core/failure-taxonomy.js.map +0 -1
  64. package/dist/run/core/index.d.ts.map +0 -1
  65. package/dist/run/core/index.js.map +0 -1
  66. package/dist/run/core/result.d.ts.map +0 -1
  67. package/dist/run/core/result.js.map +0 -1
  68. package/dist/run/core/retry.d.ts.map +0 -1
  69. package/dist/run/core/retry.js.map +0 -1
  70. package/dist/run/diagnostics/diagnostics.d.ts.map +0 -1
  71. package/dist/run/diagnostics/diagnostics.js.map +0 -1
  72. package/dist/run/diagnostics/index.d.ts.map +0 -1
  73. package/dist/run/diagnostics/index.js.map +0 -1
  74. package/dist/run/index.d.ts.map +0 -1
  75. package/dist/run/index.js.map +0 -1
  76. package/dist/run-lifecycle.d.ts.map +0 -1
  77. package/dist/run-lifecycle.js.map +0 -1
  78. package/dist/terminal-session.d.ts.map +0 -1
  79. package/dist/terminal-session.js.map +0 -1
  80. package/dist/tokens.d.ts.map +0 -1
  81. package/dist/tokens.js.map +0 -1
  82. package/dist/tool-executor.d.ts.map +0 -1
  83. package/dist/tool-executor.js.map +0 -1
  84. package/dist/tool-result-media.d.ts.map +0 -1
  85. package/dist/tool-result-media.js.map +0 -1
  86. package/dist/tool-result-surfaces.d.ts.map +0 -1
  87. package/dist/tool-result-surfaces.js.map +0 -1
@@ -327,6 +327,15 @@ export interface AgentExecutorRunInput {
327
327
  * CLI session. Ignored by defs that don't declare `resumesSessionViaCli`.
328
328
  */
329
329
  readonly newSessionId?: string;
330
+ /**
331
+ * Forwarded verbatim to `RuntimeBuildOptions.disallowedTools` (see `@jini-ai/agent-runtime`'s
332
+ * `types.ts`) — Finding 2 of SEC-assistant-env-isolation-2026-09-07. A mechanism only: this
333
+ * package bakes in no opinion about which tools to name; the host decides. `undefined`/empty
334
+ * means no restriction, byte-identical to today's behavior.
335
+ */
336
+ readonly disallowedTools?: readonly string[];
337
+ /** Same mechanism as {@link disallowedTools}, forwarded to `RuntimeBuildOptions.allowedTools`. */
338
+ readonly allowedTools?: readonly string[];
330
339
  }
331
340
  export interface AgentExecutor {
332
341
  /**
@@ -736,6 +745,61 @@ export type PreparedCodexHome = {
736
745
  /** Recursively removes the staged directory — the live `auth.json` copy it may hold makes this a confidentiality cleanup, not just tidiness. Safe to call more than once. */
737
746
  readonly cleanup: () => Promise<void>;
738
747
  };
748
+ /**
749
+ * Finding 1 of SEC-assistant-env-isolation-2026-09-07 — a staged, run-scoped `claude` CLI config
750
+ * directory, the `CLAUDE_CONFIG_DIR`-isolation analogue of {@link PreparedCodexHome} above.
751
+ *
752
+ * **The bug this closes:** `BASELINE_AGENT_ENV_KEYS` forwards `HOME` verbatim and neither this
753
+ * package nor `@jini-ai/agent-runtime` ever set `CLAUDE_CONFIG_DIR`, so a spawned `claude` child
754
+ * resolved its config (skills, plugins, agents, memory-path index, settings) from the OPERATOR's
755
+ * own real `$HOME/.claude` — a personal grant (including `Task`/`Edit`/`Write`/`Cron*`/worktree
756
+ * tools on this host) the host process never intended to hand the model.
757
+ */
758
+ export type PreparedClaudeConfigDir = {
759
+ /** Absolute path to hand to the spawned child as its `CLAUDE_CONFIG_DIR` env var. */
760
+ readonly path: string;
761
+ /** Recursively removes the staged directory — it may hold a copied login credential (see {@link prepareClaudeConfigDirForRun}'s doc), the same confidentiality-cleanup duty as {@link PreparedCodexHome.cleanup}. Safe to call more than once. */
762
+ readonly cleanup: () => Promise<void>;
763
+ };
764
+ /** The Claude-config-dir mechanism's injectable filesystem seams, real by default — the directory-staging analogue of {@link CodexHomeSeams}, kept as its own small options bag (see {@link CreateAgentExecutorOptions.claudeConfigDirIsolation}) rather than folded into {@link McpJsonInjectionOptions}: isolating the operator's personal config is an env-hygiene concern independent of whether this host configured MCP federation at all, and must not be gated on that unrelated flag. */
765
+ export interface ClaudeConfigDirSeams {
766
+ readonly mkdtemp: (prefix: string) => Promise<string>;
767
+ readonly readFile: (path: string) => Promise<string>;
768
+ readonly writeFile: (path: string, content: string) => Promise<void>;
769
+ readonly removeDir: (path: string) => Promise<void>;
770
+ }
771
+ /**
772
+ * `CreateAgentExecutorOptions.claudeConfigDirIsolation` — every field optional and real-filesystem
773
+ * by default, matching this file's standing "no real disk I/O by default in tests" convention (see
774
+ * `preparePromptFileForAgent`/`prepareAgentLogFile`'s identical shape). Unlike
775
+ * {@link McpJsonInjectionOptions}, this bag is never itself a gate: {@link
776
+ * prepareClaudeConfigDirIfNeeded} stages a scratch directory for every `claude`-def run regardless
777
+ * of whether a host supplies overrides here — this options bag only lets a test observe/replace the
778
+ * filesystem calls, the same way `preparePromptFileForAgent`'s own default does.
779
+ */
780
+ export interface ClaudeConfigDirIsolationOptions {
781
+ /** @default the real `fs.promises.mkdtemp(path.join(os.tmpdir(), prefix))` */
782
+ readonly mkdtemp?: (prefix: string) => Promise<string>;
783
+ /** Reads the operator's real `.credentials.json`, if any — see {@link prepareClaudeConfigDirForRun}'s own doc. @default the real `fs.promises.readFile` (utf8) */
784
+ readonly readFile?: (path: string) => Promise<string>;
785
+ /** Writes the copied `.credentials.json` into the scratch directory. @default the real `fs.promises.writeFile` (utf8) */
786
+ readonly writeFile?: (path: string, content: string) => Promise<void>;
787
+ /** @default `fs.promises.rm(path, { recursive: true, force: true })` — already-gone is success, not an error. */
788
+ readonly removeDir?: (path: string) => Promise<void>;
789
+ }
790
+ /**
791
+ * Where `claude`'s own config resolution reads the operator's REAL config from, to seed a run's
792
+ * scratch copy — never where it writes. Mirrors {@link resolveSourceCodexHomeDir}'s exact reasoning
793
+ * and resolution order: `CLAUDE_CONFIG_DIR` is not in `BASELINE_AGENT_ENV_KEYS`, so a spawned child
794
+ * never inherits it anyway — the whole point is finding wherever the *operator's actual* Claude Code
795
+ * install lives, a host-machine fact resolved against the daemon HOST process's own environment.
796
+ * @param hostEnv - The daemon process's own environment.
797
+ * @returns `hostEnv.CLAUDE_CONFIG_DIR` when set to a non-blank value (matching Claude Code's own
798
+ * resolution order, confirmed against installed Claude Code 2.1.263), else the CLI's documented
799
+ * default, `~/.claude`.
800
+ * @complexity O(1).
801
+ */
802
+ export declare function resolveSourceClaudeConfigDir(hostEnv: NodeJS.ProcessEnv): string;
739
803
  /**
740
804
  * Gap 4 of the run/chat orchestration Final Recommendation: what
741
805
  * `classifyFailure` (see `CreateAgentExecutorOptions.classifyFailure`) is
@@ -899,6 +963,39 @@ export interface CreateAgentExecutorOptions {
899
963
  * `command`/`daemonUrl` this package could assume on a caller's behalf.
900
964
  */
901
965
  readonly mcpJsonInjection?: McpJsonInjectionOptions;
966
+ /**
967
+ * Finding 1 of SEC-assistant-env-isolation-2026-09-07's injectable filesystem seams — see
968
+ * {@link ClaudeConfigDirIsolationOptions}'s own doc. This bag itself is still NOT a gate (supplying
969
+ * it only lets a test observe/replace the real filesystem calls); whether staging happens AT ALL is
970
+ * now {@link claudeConfigDirIsolationEnabled}'s job — see that field's doc for why the two were
971
+ * split apart instead of overloading this one's presence as the switch.
972
+ * @default the real `fs.promises.mkdtemp`/`readFile`/`rm` — no real disk I/O for every def other
973
+ * than `claude`, matching this factory's "no real filesystem by default in tests" convention.
974
+ */
975
+ readonly claudeConfigDirIsolation?: ClaudeConfigDirIsolationOptions;
976
+ /**
977
+ * The actual on/off switch for Finding 1's `CLAUDE_CONFIG_DIR` isolation (see
978
+ * {@link prepareClaudeConfigDirIfNeeded}'s doc for the staging behavior this gates). Split out as
979
+ * its own boolean rather than reusing {@link claudeConfigDirIsolation}'s presence, because that bag
980
+ * is a test-seam-injection convention shared with every other `*IsolationOptions`/`*Seams` field in
981
+ * this file (see `mcpJsonInjection`'s own "NOT a gate" precedent) — overloading it here would mean a
982
+ * host that only wants to override `mkdtemp` for a test silently also flips production behavior.
983
+ *
984
+ * @default `false`. This DEFAULTS OFF, which reopens the leak Finding 1 closed (the spawned
985
+ * `claude` child again reads the operator's real `~/.claude` — skills, plugins, memory index, and
986
+ * whatever tool grant that directory carries) — **not a regression discovered later, a deliberate
987
+ * rollback landed the same day as the isolation fix itself.** Reason: on macOS the Keychain login
988
+ * `claude auth status` reports is keyed to `CLAUDE_CONFIG_DIR` (see {@link
989
+ * prepareClaudeConfigDirForRun}'s doc), and no caller of this factory was passing a credential of
990
+ * its own (`AgentExecutorRunInput.credentialEnv`) when Finding 1 landed unconditionally — so every
991
+ * isolated child ran unauthenticated and the assistant reported "Not logged in" on its default
992
+ * runtime. A host that provisions a real `ANTHROPIC_API_KEY`/`CLAUDE_CODE_OAUTH_TOKEN` via
993
+ * `credentialEnv` (both outrank Keychain login in Claude Code's own auth precedence, so an isolated,
994
+ * still-logged-in child needs no access to the operator's personal Keychain entry at all) should
995
+ * flip this back on — the mechanism itself is unchanged and was already proven live
996
+ * (2026-09-07); only the default changed.
997
+ */
998
+ readonly claudeConfigDirIsolationEnabled?: boolean;
902
999
  /**
903
1000
  * Ceiling on how many bytes of a `'until-close'` def's stdout this driver will hold in memory
904
1001
  * before it stops accumulating and reports the shortfall — see
@@ -1024,7 +1121,7 @@ export declare function resolveMcpBridgeForRun(input: {
1024
1121
  export declare function computeChildEnv(spawnEnv: NodeJS.ProcessEnv, mcpBridge: McpBridgeDelivery | null, codexHomeDir?: string, systemPromptEnvOverrides?: Readonly<Record<string, string>>, stagedInstructionsFile?: {
1025
1122
  readonly varName: string;
1026
1123
  readonly path: string;
1027
- }): NodeJS.ProcessEnv;
1124
+ }, claudeConfigDir?: string): NodeJS.ProcessEnv;
1028
1125
  /**
1029
1126
  * Phase 6b: the `RuntimeContext` `buildArgs` receives — `undefined` unless a file, bridge path, or
1030
1127
  * session id was staged. Pure.
@@ -1037,8 +1134,8 @@ export declare function computeChildEnv(spawnEnv: NodeJS.ProcessEnv, mcpBridge:
1037
1134
  * case for a resumed turn.
1038
1135
  */
1039
1136
  export declare function computeRuntimeContext(preparedPromptFile: PreparedPromptFile | null, preparedLogFile: PreparedAgentLogFile | null, mcpBridge: McpBridgeDelivery | null, resumeSessionId?: string | null, newSessionId?: string): RuntimeContext | undefined;
1040
- /** Phase 9a: the def's `buildArgs` 4th argument — `undefined` when the run selects no model/reasoning/permissionMode/overlay at all (byte-identical to omitting the argument). Pure. */
1041
- export declare function buildAgentBuildArgsOptions(input: Pick<AgentExecutorRunInput, 'model' | 'reasoning' | 'permissionMode'>, systemPromptOverlay: string | null | undefined): RuntimeBuildOptions | undefined;
1137
+ /** Phase 9a: the def's `buildArgs` 4th argument — `undefined` when the run selects no model/reasoning/permissionMode/overlay/tool-restriction at all (byte-identical to omitting the argument). Pure. */
1138
+ export declare function buildAgentBuildArgsOptions(input: Pick<AgentExecutorRunInput, 'model' | 'reasoning' | 'permissionMode' | 'disallowedTools' | 'allowedTools'>, systemPromptOverlay: string | null | undefined): RuntimeBuildOptions | undefined;
1042
1139
  /**
1043
1140
  * {@link resolveSystemPromptOverlayDelivery}'s decision, threaded out of that one resolver to every
1044
1141
  * channel that can actually carry the overlay to the CLI.
@@ -1218,6 +1315,39 @@ export declare function prepareCodexHomeIfNeeded(input: {
1218
1315
  readonly releaseStagedResources: () => Promise<void>;
1219
1316
  readonly failBeforeSpawn: FailBeforeSpawn;
1220
1317
  }): Promise<PreparedCodexHome | null>;
1318
+ /**
1319
+ * Finding 1 of SEC-assistant-env-isolation-2026-09-07's one effect — stages this run's scratch
1320
+ * `CLAUDE_CONFIG_DIR` directory, returning the prepared handle `cleanupStagedFiles` should later
1321
+ * release (`null` for every def other than `claude` — see {@link prepareClaudeConfigDirForRun}'s
1322
+ * own doc for why this is unconditional for `claude` runs, unlike {@link prepareCodexHomeIfNeeded}'s
1323
+ * gate on a host-configured MCP bridge strategy).
1324
+ *
1325
+ * Gated on `def.id === 'claude'` directly rather than on `externalMcpInjection === 'claude-mcp-json'`
1326
+ * (which `codebuddy` also declares): isolating the operator's personal `~/.claude` is specific to
1327
+ * the real `claude` CLI's own config resolution, not to every def that happens to share its `.mcp.
1328
+ * json` delivery shape. Matches the existing `USER`-for-claude-login special case already singled
1329
+ * out by id in `BASELINE_AGENT_ENV_KEYS`'s own doc, a few hundred lines above.
1330
+ *
1331
+ * ALSO gated on `deps.enabled` (`CreateAgentExecutorOptions.claudeConfigDirIsolationEnabled`, default
1332
+ * `false`) since this task's own fix — see that field's doc for why it defaults off (Keychain login
1333
+ * is `CLAUDE_CONFIG_DIR`-keyed and no caller was supplying a credential when this was unconditional).
1334
+ * `def.id === 'claude'` is still checked first and independently: a host that flips this flag on
1335
+ * should not suddenly stage a directory for `codebuddy` or any other def.
1336
+ * @param input.def - Used for both the `id` gate and the failure message.
1337
+ * @param deps.enabled - The isolation on/off switch — see this function's own doc above.
1338
+ * @param deps.hostEnv - The daemon's own environment, threaded through to {@link resolveSourceClaudeConfigDir} rather than read from a module-level `process.env`, matching {@link prepareCodexHomeIfNeeded}'s identical testability reasoning.
1339
+ * @complexity O(1) plus {@link prepareClaudeConfigDirForRun}'s own cost.
1340
+ */
1341
+ export declare function prepareClaudeConfigDirIfNeeded(input: {
1342
+ readonly runId: string;
1343
+ readonly def: RuntimeAgentDef;
1344
+ }, deps: {
1345
+ readonly enabled: boolean;
1346
+ readonly claudeConfigDirIsolation: ClaudeConfigDirIsolationOptions | undefined;
1347
+ readonly hostEnv: NodeJS.ProcessEnv;
1348
+ readonly releaseStagedResources: () => Promise<void>;
1349
+ readonly failBeforeSpawn: FailBeforeSpawn;
1350
+ }): Promise<PreparedClaudeConfigDir | null>;
1221
1351
  /** Phase 11: post-`buildArgs` guard for argv-bound defs whose resolved binary is a Windows shim/.exe — a no-op off-Windows and for non-argv-bound defs. */
1222
1352
  export declare function guardWindowsCommandLineBudget(input: {
1223
1353
  readonly runId: string;
@@ -825,6 +825,16 @@ function escapeTomlBasicString(value) {
825
825
  function tomlString(value) {
826
826
  return `"${escapeTomlBasicString(value)}"`;
827
827
  }
828
+ /**
829
+ * Codex's per-tool deadline for the Jini bridge, in seconds: `@jini-ai/mcp`'s
830
+ * `DEFAULT_DELEGATED_TOOL_TIMEOUT_MS` (6 min, mirrored because this package does not depend on
831
+ * that one) plus 40 s. Codex's own default (60 s) is shorter than a delegated call parked on a
832
+ * human dialog, and when Codex abandons a call the dialog stayed answerable, so a late click ran
833
+ * an action the model had been told failed. Outlasting the bridge's deadline means the bridge
834
+ * times out first, drops its daemon request, and the daemon expires the dialog. Codex does not
835
+ * pass the parent env through to MCP servers, so the bridge under Codex always runs at its default.
836
+ */
837
+ const CODEX_TOOL_TIMEOUT_SEC = 6 * 60 + 40;
828
838
  /**
829
839
  * Mechanism 5 of 5 — `'codex-toml'`'s serialization step. Builds the `[mcp_servers.jini]` TOML
830
840
  * table (plus, when the entry carries any env vars, a separate `[mcp_servers.jini.env]` table)
@@ -841,7 +851,8 @@ function tomlString(value) {
841
851
  */
842
852
  export function buildCodexMcpServerToml(entry) {
843
853
  const argsLiteral = entry.args.map(tomlString).join(', ');
844
- const serverTable = `[mcp_servers.${JINI_MCP_SERVER_KEY}]\ncommand = ${tomlString(entry.command)}\nargs = [${argsLiteral}]\n`;
854
+ const serverTable = `[mcp_servers.${JINI_MCP_SERVER_KEY}]\ncommand = ${tomlString(entry.command)}\nargs = [${argsLiteral}]\n` +
855
+ `tool_timeout_sec = ${CODEX_TOOL_TIMEOUT_SEC}\n`;
845
856
  const envLines = Object.entries(entry.env)
846
857
  .filter((pair) => typeof pair[1] === 'string')
847
858
  .map(([key, value]) => `${key} = ${tomlString(value)}`);
@@ -849,6 +860,44 @@ export function buildCodexMcpServerToml(entry) {
849
860
  return serverTable;
850
861
  return `${serverTable}\n[mcp_servers.${JINI_MCP_SERVER_KEY}.env]\n${envLines.join('\n')}\n`;
851
862
  }
863
+ /**
864
+ * Splits a TOML table header's dotted key into its individual segments, honoring quoted parts
865
+ * (`"basic"` or `'literal'`) that may themselves contain a literal `.` — a bare `.` only
866
+ * separates segments outside of quotes. Each segment is trimmed and, if quoted, unwrapped.
867
+ *
868
+ * Not a full TOML parser: it does not resolve escape sequences (`\"`, `\u...`) inside basic
869
+ * strings. That is deliberately out of scope — see {@link stripExistingJiniMcpServerTable}'s doc
870
+ * for why it is safe to skip for this specific comparison.
871
+ * @param rawKey - The raw text between a table header's `[` and `]`.
872
+ * @returns The dotted key's segments, dequoted and trimmed.
873
+ * @complexity O(n) in the length of `rawKey`.
874
+ */
875
+ function splitTomlDottedKey(rawKey) {
876
+ const segments = [];
877
+ let current = '';
878
+ let quote = null;
879
+ for (const ch of rawKey) {
880
+ if (quote) {
881
+ if (ch === quote)
882
+ quote = null;
883
+ else
884
+ current += ch;
885
+ continue;
886
+ }
887
+ if (ch === '"' || ch === "'") {
888
+ quote = ch;
889
+ continue;
890
+ }
891
+ if (ch === '.') {
892
+ segments.push(current.trim());
893
+ current = '';
894
+ continue;
895
+ }
896
+ current += ch;
897
+ }
898
+ segments.push(current.trim());
899
+ return segments;
900
+ }
852
901
  /**
853
902
  * Removes any pre-existing `[mcp_servers.{@link JINI_MCP_SERVER_KEY}]` table — and its
854
903
  * `[mcp_servers.{@link JINI_MCP_SERVER_KEY}.*]` subtables (e.g. `.env`) — from a real Codex
@@ -856,10 +905,34 @@ export function buildCodexMcpServerToml(entry) {
856
905
  * without producing the duplicate TOML key Codex's parser rejects at startup.
857
906
  *
858
907
  * Line-oriented, not a real TOML parser (matching {@link buildCodexMcpServerToml}'s own
859
- * no-TOML-dependency constraint): a table header is any line that is, after trimming, exactly
860
- * `[...]`. Once such a header's key matches the jini table or one of its subtables, every following
861
- * line is dropped until the next table header (of any name) or EOF. Every other table, key, and
862
- * blank line is passed through untouched.
908
+ * no-TOML-dependency constraint), but wide enough to survive a hand-edited config, which is the
909
+ * scenario this whole function exists for. A table header line is recognized when, after
910
+ * trimming, it is `[<key>]` optionally followed only by a `# comment` (TOML's own grammar allows
911
+ * nothing else there) — so it tolerates leading indentation, whitespace inside the brackets, a
912
+ * trailing line comment, and a dotted key written with quoted segments (`["mcp_servers"."jini"]`),
913
+ * via {@link splitTomlDottedKey}. Once such a header's key matches the jini table or one of its
914
+ * subtables, every following line is dropped until the next table header (of any name) or EOF.
915
+ * Every other table, key, comment-only, and blank line is passed through untouched.
916
+ *
917
+ * Deliberately unhandled, and why it is safe to leave that way:
918
+ * - **Escape sequences inside quoted key segments** (e.g. a segment containing `\"`) — neither
919
+ * `mcp_servers` nor {@link JINI_MCP_SERVER_KEY} ever needs escaping, and this driver's own
920
+ * writer ({@link buildCodexMcpServerToml}) never emits a quoted form at all, so no real
921
+ * `config.toml` this driver produced can exercise this gap; a hand-edit that goes out of its
922
+ * way to escape a character inside a key that is supposed to spell "jini" would fail to match
923
+ * and fall back to today's pre-fix behavior (append a duplicate) rather than silently doing
924
+ * something worse.
925
+ * - **`[[array-of-tables]]` syntax** — Codex's schema has no array-of-tables shape for
926
+ * `mcp_servers`, and the header regex's `[^[\]]+` capture cannot match a line starting with a
927
+ * second `[`, so `[[mcp_servers.jini]]` is not recognized as a header before or after this fix
928
+ * — unchanged, not a new gap.
929
+ * - **A single quoted segment that happens to spell the same characters with the dot included**
930
+ * (e.g. `["mcp_servers.jini"]`, which in real TOML names one key literally containing a `.`,
931
+ * not two nested tables) — this collapses to the same joined string as the real nested-table
932
+ * spelling and is therefore treated as a match. This is a false positive in the conservative
933
+ * direction the reported bug calls for: a missed detection duplicates a key and crashes Codex,
934
+ * so on this axis over-matching a spelling nobody would plausibly hand-write for an MCP server
935
+ * table is preferable to under-matching the real one.
863
936
  * @param existingRaw - The real Codex home's `config.toml` content, already known to be defined
864
937
  * (callers pass `''` for a missing file).
865
938
  * @returns `existingRaw` with any jini table/subtable removed.
@@ -870,9 +943,9 @@ function stripExistingJiniMcpServerTable(existingRaw) {
870
943
  const kept = [];
871
944
  let skipping = false;
872
945
  for (const line of existingRaw.split('\n')) {
873
- const header = /^\s*\[([^[\]]+)\]\s*$/.exec(line);
946
+ const header = /^\s*\[([^[\]]+)\]\s*(#.*)?$/.exec(line);
874
947
  if (header) {
875
- const key = (header[1] ?? '').trim();
948
+ const key = splitTomlDottedKey(header[1] ?? '').join('.');
876
949
  skipping = key === jiniTableKey || key.startsWith(`${jiniTableKey}.`);
877
950
  if (skipping)
878
951
  continue;
@@ -1122,6 +1195,114 @@ async function prepareCodexHomeForRun(runId, entry, sourceCodexHomeDir, seams) {
1122
1195
  },
1123
1196
  };
1124
1197
  }
1198
+ function defaultMkdtempClaudeConfigDir(prefix) {
1199
+ return fsPromises.mkdtemp(join(tmpdir(), prefix));
1200
+ }
1201
+ function defaultRemoveClaudeConfigDir(path) {
1202
+ return fsPromises.rm(path, { recursive: true, force: true });
1203
+ }
1204
+ function resolveClaudeConfigDirSeams(options) {
1205
+ return {
1206
+ mkdtemp: options?.mkdtemp ?? defaultMkdtempClaudeConfigDir,
1207
+ readFile: options?.readFile ?? defaultReadMcpJsonFile,
1208
+ writeFile: options?.writeFile ?? defaultWriteMcpJsonFile,
1209
+ removeDir: options?.removeDir ?? defaultRemoveClaudeConfigDir,
1210
+ };
1211
+ }
1212
+ /**
1213
+ * Where `claude`'s own config resolution reads the operator's REAL config from, to seed a run's
1214
+ * scratch copy — never where it writes. Mirrors {@link resolveSourceCodexHomeDir}'s exact reasoning
1215
+ * and resolution order: `CLAUDE_CONFIG_DIR` is not in `BASELINE_AGENT_ENV_KEYS`, so a spawned child
1216
+ * never inherits it anyway — the whole point is finding wherever the *operator's actual* Claude Code
1217
+ * install lives, a host-machine fact resolved against the daemon HOST process's own environment.
1218
+ * @param hostEnv - The daemon process's own environment.
1219
+ * @returns `hostEnv.CLAUDE_CONFIG_DIR` when set to a non-blank value (matching Claude Code's own
1220
+ * resolution order, confirmed against installed Claude Code 2.1.263), else the CLI's documented
1221
+ * default, `~/.claude`.
1222
+ * @complexity O(1).
1223
+ */
1224
+ export function resolveSourceClaudeConfigDir(hostEnv) {
1225
+ const override = hostEnv.CLAUDE_CONFIG_DIR;
1226
+ return override !== undefined && override.trim().length > 0 ? override : join(homedir(), '.claude');
1227
+ }
1228
+ /**
1229
+ * Stages a fresh, randomly-named `CLAUDE_CONFIG_DIR` directory (same non-determinism requirement as
1230
+ * {@link McpJsonInjectionOptions.mkdtemp}'s own doc — `os.tmpdir()` is a shared location on a
1231
+ * multi-user host, so a guessable name is a real pre-plant/symlink target).
1232
+ *
1233
+ * **Deliberately empty by default** — unlike {@link prepareCodexHomeForRun}, which copies the real
1234
+ * `config.toml` wholesale (Codex has no personal-data problem in that file), this directory gets
1235
+ * NOTHING written into it beyond a best-effort copy of `.credentials.json` (see below). That is the
1236
+ * fix: the operator's real `skills`/`plugins`/`agents`/`memory-path index`/`settings.json` must NOT
1237
+ * carry over implicitly. `claude` runs correctly against a config directory holding nothing at all —
1238
+ * it falls back to its own built-in defaults, not an error.
1239
+ *
1240
+ * **Login preservation, verified rather than assumed** (per this task's own instruction — "prove
1241
+ * login still resolves; do not assume"): confirmed live (2026-09-07, installed Claude Code 2.1.263,
1242
+ * macOS) that `claude auth status` reports `loggedIn: false` against ANY `CLAUDE_CONFIG_DIR` other
1243
+ * than the operator's real one — including the real `HOME` with only `CLAUDE_CONFIG_DIR` swapped —
1244
+ * and confirmed against Claude Code's own docs (code.claude.com/docs/en/authentication) why: "If
1245
+ * you've set the CLAUDE_CONFIG_DIR environment variable, Claude Code keeps the .credentials.json
1246
+ * file under that directory instead, including the file the macOS fallback writes, and keys the
1247
+ * macOS Keychain entry to that directory too, so a session with a different CLAUDE_CONFIG_DIR reads
1248
+ * a different entry." So a scratch directory is never logged in by default on ANY platform, not just
1249
+ * the ones with no Keychain at all. This function only closes the *portable* case: when the source
1250
+ * directory holds a file-based `.credentials.json` (Linux, Windows, or a Keychain-locked macOS
1251
+ * fallback — none of which this function can distinguish, and does not need to), it is copied
1252
+ * best-effort into the scratch directory, exactly `prepareCodexHomeForRun`'s `auth.json` copy. When
1253
+ * it does not (a normal macOS Keychain-only install, confirmed the common case on this codebase's
1254
+ * own dev machine), this function does NOT attempt to read the macOS Keychain itself — that would
1255
+ * mean this daemon process extracting a live OAuth secret out of an OS-managed credential store into
1256
+ * a plaintext file, a materially different and larger security surface than forwarding an
1257
+ * already-resolved credential the host handed it (which `credentialEnv`/`ANTHROPIC_API_KEY` already
1258
+ * does, safely, today — see `AgentExecutorRunInput.credentialEnv`'s own doc). In that case this
1259
+ * mirrors `prepareCodexHomeForRun`'s own accepted outcome for a missing credential file verbatim:
1260
+ * "the spawned CLI runs unauthenticated" is documented, existing, precedented behavior in this file,
1261
+ * not a new failure mode invented here. A host that needs the isolated child to stay logged in on
1262
+ * such an install must supply a credential explicitly via `AgentExecutorRunInput.credentialEnv`
1263
+ * (`ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` — both outrank Keychain-based subscription login
1264
+ * in Claude Code's own auth precedence, so the isolated child authenticates without ever needing the
1265
+ * operator's personal Keychain entry at all) — see this fix's own handoff report for the operational
1266
+ * consequence on a host with no such credential configured yet.
1267
+ *
1268
+ * **Never touches the real config directory.** `sourceConfigDir` is read-only throughout.
1269
+ * @param runId - Embedded in the temp-dir prefix for traceability, same sanitization discipline as {@link prepareCodexHomeForRun}'s `safeRunId`.
1270
+ * @param sourceConfigDir - Where to read a possible real `.credentials.json` from — see {@link resolveSourceClaudeConfigDir}.
1271
+ * @param seams - Injectable mkdtemp/readFile/writeFile/removeDir, real filesystem by default.
1272
+ * @throws Whatever `mkdtemp` rejects with — the caller ({@link prepareClaudeConfigDirIfNeeded}) turns that into a pre-spawn `AGENT_SPAWN_FAILED` failure, matching {@link prepareCodexHomeForRun}'s own contract.
1273
+ * @complexity O(1) plus one directory creation and up to one best-effort file read/write round trip.
1274
+ */
1275
+ async function prepareClaudeConfigDirForRun(runId, sourceConfigDir, seams) {
1276
+ const safeRunId = runId.replace(/[^A-Za-z0-9_-]/g, '-').slice(0, 80) || 'run';
1277
+ const dir = await seams.mkdtemp(`jini-claude-config-${safeRunId}-`);
1278
+ try {
1279
+ const credentialsPath = join(sourceConfigDir, '.credentials.json');
1280
+ let credentialsRaw;
1281
+ try {
1282
+ credentialsRaw = await seams.readFile(credentialsPath);
1283
+ }
1284
+ catch {
1285
+ // No file-based credential to copy (the common macOS-Keychain-only case) — see this
1286
+ // function's own doc for why that is an accepted, documented outcome, not a failure here.
1287
+ credentialsRaw = undefined;
1288
+ }
1289
+ if (credentialsRaw !== undefined) {
1290
+ await seams.writeFile(join(dir, '.credentials.json'), credentialsRaw);
1291
+ }
1292
+ }
1293
+ catch (err) {
1294
+ await seams.removeDir(dir).catch(() => {
1295
+ // Best-effort only — the original error below is what the caller must see either way.
1296
+ });
1297
+ throw err;
1298
+ }
1299
+ return {
1300
+ path: dir,
1301
+ cleanup: async () => {
1302
+ await seams.removeDir(dir);
1303
+ },
1304
+ };
1305
+ }
1125
1306
  /**
1126
1307
  * Default ceiling on the `'until-close'` stdout accumulator (see `RuntimeStdoutPolicy` in
1127
1308
  * `@jini-ai/agent-runtime`), in bytes of received UTF-8.
@@ -1303,6 +1484,13 @@ function wireChildLifecycle(ctx) {
1303
1484
  // `content` below already carries the whole raw output, a pre-existing, unrelated gap), so
1304
1485
  // there is no `remainder` to thread back in — only the extracted blocks are used here.
1305
1486
  let media = [];
1487
+ // Suspend the slow-run watchdog for the duration of this daemon-awaited execution: the daemon
1488
+ // knows exactly why the run is quiet here (it dispatched the tool itself and is waiting on it),
1489
+ // so a legitimately long tool (an install, a build, a repo-wide scan) must not be mistaken for
1490
+ // the CPU-starved-and-silent condition the watchdog exists to catch. Resumed in `finally` so a
1491
+ // genuinely stalled stretch *after* this tool settles is still caught — see
1492
+ // `RunLifecycle.suspendSlowRunNotice`'s own doc.
1493
+ lifecycle.suspendSlowRunNotice(runId);
1306
1494
  try {
1307
1495
  const result = await continuation.toolExecutor.execute(continuation.principal, run, toolUse.name, toolUse.input);
1308
1496
  content = resultContent(result);
@@ -1313,6 +1501,9 @@ function wireChildLifecycle(ctx) {
1313
1501
  content = errorMessage(error);
1314
1502
  isError = true;
1315
1503
  }
1504
+ finally {
1505
+ lifecycle.resumeSlowRunNotice(runId);
1506
+ }
1316
1507
  await lifecycle.emit(runId, {
1317
1508
  event: 'agent',
1318
1509
  data: {
@@ -1916,7 +2107,7 @@ export async function resolveMcpBridgeForRun(input, deps) {
1916
2107
  * @complexity O(1) plus `mergeEnvContentMcpConfig`'s and `mergeEnvContentInstructions`'s own `JSON.parse`/`JSON.stringify` cost.
1917
2108
  * @overallScore 100/100
1918
2109
  */
1919
- export function computeChildEnv(spawnEnv, mcpBridge, codexHomeDir, systemPromptEnvOverrides, stagedInstructionsFile) {
2110
+ export function computeChildEnv(spawnEnv, mcpBridge, codexHomeDir, systemPromptEnvOverrides, stagedInstructionsFile, claudeConfigDir) {
1920
2111
  const envContentApplied = mcpBridge?.kind === 'env-content'
1921
2112
  ? {
1922
2113
  ...spawnEnv,
@@ -1936,7 +2127,12 @@ export function computeChildEnv(spawnEnv, mcpBridge, codexHomeDir, systemPromptE
1936
2127
  [stagedInstructionsFile.varName]: mergeEnvContentInstructions(envPassthroughApplied[stagedInstructionsFile.varName], stagedInstructionsFile.path),
1937
2128
  };
1938
2129
  const codexHomeApplied = codexHomeDir === undefined ? instructionsApplied : { ...instructionsApplied, CODEX_HOME: codexHomeDir };
1939
- return systemPromptEnvOverrides === undefined ? codexHomeApplied : { ...codexHomeApplied, ...systemPromptEnvOverrides };
2130
+ // Finding 1 of SEC-assistant-env-isolation-2026-09-07: same "only set when staged" shape as
2131
+ // codexHomeApplied above — mutually exclusive with it in practice (codexHomeDir is only ever set
2132
+ // for a 'codex-toml' bridge, claudeConfigDir only ever for a `claude`-id run), so both existing
2133
+ // side by side here is a documentation convenience, not a real collision risk.
2134
+ const claudeConfigDirApplied = claudeConfigDir === undefined ? codexHomeApplied : { ...codexHomeApplied, CLAUDE_CONFIG_DIR: claudeConfigDir };
2135
+ return systemPromptEnvOverrides === undefined ? claudeConfigDirApplied : { ...claudeConfigDirApplied, ...systemPromptEnvOverrides };
1940
2136
  }
1941
2137
  /**
1942
2138
  * Phase 6b: the `RuntimeContext` `buildArgs` receives — `undefined` unless a file, bridge path, or
@@ -1990,16 +2186,23 @@ function computeSystemPromptOverlay(promptAugmenter, agentId, runtimeContext) {
1990
2186
  turnIndex: runtimeContext?.hasPriorAssistantTurn ? 1 : 0,
1991
2187
  });
1992
2188
  }
1993
- /** Phase 9a: the def's `buildArgs` 4th argument — `undefined` when the run selects no model/reasoning/permissionMode/overlay at all (byte-identical to omitting the argument). Pure. */
2189
+ /** Phase 9a: the def's `buildArgs` 4th argument — `undefined` when the run selects no model/reasoning/permissionMode/overlay/tool-restriction at all (byte-identical to omitting the argument). Pure. */
1994
2190
  export function buildAgentBuildArgsOptions(input, systemPromptOverlay) {
1995
2191
  const hasOverlay = systemPromptOverlay !== undefined && systemPromptOverlay !== null;
1996
- if (input.model === undefined && input.reasoning === undefined && input.permissionMode === undefined && !hasOverlay) {
2192
+ if (input.model === undefined
2193
+ && input.reasoning === undefined
2194
+ && input.permissionMode === undefined
2195
+ && input.disallowedTools === undefined
2196
+ && input.allowedTools === undefined
2197
+ && !hasOverlay) {
1997
2198
  return undefined;
1998
2199
  }
1999
2200
  return {
2000
2201
  ...(input.model !== undefined ? { model: input.model } : {}),
2001
2202
  ...(input.reasoning !== undefined ? { reasoning: input.reasoning } : {}),
2002
2203
  ...(input.permissionMode !== undefined ? { permissionMode: input.permissionMode } : {}),
2204
+ ...(input.disallowedTools !== undefined ? { disallowedTools: input.disallowedTools } : {}),
2205
+ ...(input.allowedTools !== undefined ? { allowedTools: input.allowedTools } : {}),
2003
2206
  ...(hasOverlay ? { systemPromptOverlay } : {}),
2004
2207
  };
2005
2208
  }
@@ -2210,6 +2413,41 @@ export async function prepareCodexHomeIfNeeded(input, deps) {
2210
2413
  return deps.failBeforeSpawn(input.runId, 'AGENT_SPAWN_FAILED', `AgentExecutor: could not stage a CODEX_HOME for agent "${input.def.id}": ${errorMessage(err)}`);
2211
2414
  }
2212
2415
  }
2416
+ /**
2417
+ * Finding 1 of SEC-assistant-env-isolation-2026-09-07's one effect — stages this run's scratch
2418
+ * `CLAUDE_CONFIG_DIR` directory, returning the prepared handle `cleanupStagedFiles` should later
2419
+ * release (`null` for every def other than `claude` — see {@link prepareClaudeConfigDirForRun}'s
2420
+ * own doc for why this is unconditional for `claude` runs, unlike {@link prepareCodexHomeIfNeeded}'s
2421
+ * gate on a host-configured MCP bridge strategy).
2422
+ *
2423
+ * Gated on `def.id === 'claude'` directly rather than on `externalMcpInjection === 'claude-mcp-json'`
2424
+ * (which `codebuddy` also declares): isolating the operator's personal `~/.claude` is specific to
2425
+ * the real `claude` CLI's own config resolution, not to every def that happens to share its `.mcp.
2426
+ * json` delivery shape. Matches the existing `USER`-for-claude-login special case already singled
2427
+ * out by id in `BASELINE_AGENT_ENV_KEYS`'s own doc, a few hundred lines above.
2428
+ *
2429
+ * ALSO gated on `deps.enabled` (`CreateAgentExecutorOptions.claudeConfigDirIsolationEnabled`, default
2430
+ * `false`) since this task's own fix — see that field's doc for why it defaults off (Keychain login
2431
+ * is `CLAUDE_CONFIG_DIR`-keyed and no caller was supplying a credential when this was unconditional).
2432
+ * `def.id === 'claude'` is still checked first and independently: a host that flips this flag on
2433
+ * should not suddenly stage a directory for `codebuddy` or any other def.
2434
+ * @param input.def - Used for both the `id` gate and the failure message.
2435
+ * @param deps.enabled - The isolation on/off switch — see this function's own doc above.
2436
+ * @param deps.hostEnv - The daemon's own environment, threaded through to {@link resolveSourceClaudeConfigDir} rather than read from a module-level `process.env`, matching {@link prepareCodexHomeIfNeeded}'s identical testability reasoning.
2437
+ * @complexity O(1) plus {@link prepareClaudeConfigDirForRun}'s own cost.
2438
+ */
2439
+ export async function prepareClaudeConfigDirIfNeeded(input, deps) {
2440
+ if (input.def.id !== 'claude' || !deps.enabled) {
2441
+ return null;
2442
+ }
2443
+ try {
2444
+ return await prepareClaudeConfigDirForRun(input.runId, resolveSourceClaudeConfigDir(deps.hostEnv), resolveClaudeConfigDirSeams(deps.claudeConfigDirIsolation));
2445
+ }
2446
+ catch (err) {
2447
+ await deps.releaseStagedResources();
2448
+ return deps.failBeforeSpawn(input.runId, 'AGENT_SPAWN_FAILED', `AgentExecutor: could not stage a CLAUDE_CONFIG_DIR for agent "${input.def.id}": ${errorMessage(err)}`);
2449
+ }
2450
+ }
2213
2451
  /** Phase 11: post-`buildArgs` guard for argv-bound defs whose resolved binary is a Windows shim/.exe — a no-op off-Windows and for non-argv-bound defs. */
2214
2452
  export async function guardWindowsCommandLineBudget(input, deps) {
2215
2453
  const windowsBudgetError = checkWindowsCmdShimCommandLineBudget(input.def, input.launchPath, input.args) ??
@@ -2350,6 +2588,8 @@ export function createAgentExecutor(options) {
2350
2588
  const continuation = options.continuation;
2351
2589
  const classifyFailure = options.classifyFailure;
2352
2590
  const mcpJsonInjection = options.mcpJsonInjection;
2591
+ const claudeConfigDirIsolation = options.claudeConfigDirIsolation;
2592
+ const claudeConfigDirIsolationEnabled = options.claudeConfigDirIsolationEnabled ?? false;
2353
2593
  const promptAugmenter = options.promptAugmenter;
2354
2594
  /**
2355
2595
  * Transitions `runId` to `'failed'` (idempotent, never resumable — no
@@ -2458,6 +2698,13 @@ export function createAgentExecutor(options) {
2458
2698
  * Only the `'codex-toml'` mechanism stages a directory at all.
2459
2699
  */
2460
2700
  let preparedCodexHome = null;
2701
+ /**
2702
+ * Set once `prepareClaudeConfigDirIfNeeded` has actually staged this run's scratch
2703
+ * `CLAUDE_CONFIG_DIR`, so `cleanupStagedFiles` knows there is a directory (possibly holding a
2704
+ * copied login credential) to remove. Cleared as it is consumed, matching `preparedCodexHome`'s
2705
+ * identical single-removal discipline. Only a `claude`-id run stages a directory this way.
2706
+ */
2707
+ let preparedClaudeConfigDir = null;
2461
2708
  /**
2462
2709
  * Set once `prepareSystemPromptOverlayFileIfNeeded` has actually staged this run's overlay file
2463
2710
  * for a `'config-instructions-file'` def, so `cleanupStagedFiles` knows there is a temp
@@ -2481,6 +2728,11 @@ export function createAgentExecutor(options) {
2481
2728
  preparedCodexHome = null;
2482
2729
  await codexHomeToRemove.cleanup();
2483
2730
  }
2731
+ if (preparedClaudeConfigDir) {
2732
+ const claudeConfigDirToRemove = preparedClaudeConfigDir;
2733
+ preparedClaudeConfigDir = null;
2734
+ await claudeConfigDirToRemove.cleanup();
2735
+ }
2484
2736
  if (preparedSystemPromptOverlayFile) {
2485
2737
  const overlayFileToRemove = preparedSystemPromptOverlayFile;
2486
2738
  preparedSystemPromptOverlayFile = null;
@@ -2537,6 +2789,19 @@ export function createAgentExecutor(options) {
2537
2789
  // so — unlike the `.mcp.json` staging above — this can run after `buildArgs` with no ordering
2538
2790
  // constraint of its own; it is placed here only to keep the two staging steps adjacent.
2539
2791
  preparedCodexHome = await prepareCodexHomeIfNeeded({ runId: input.runId, def, mcpBridge }, { mcpJsonInjection, hostEnv: process.env, releaseStagedResources, failBeforeSpawn });
2792
+ // Finding 1 of SEC-assistant-env-isolation-2026-09-07's one effect — stage this run's scratch
2793
+ // `CLAUDE_CONFIG_DIR` directory. Unconditional for a `claude`-id run (unlike CODEX_HOME above,
2794
+ // this does not depend on `mcpJsonInjection` being configured at all — see
2795
+ // `prepareClaudeConfigDirIfNeeded`'s own doc for why). Placed here only to stay adjacent to the
2796
+ // other pre-`computeChildEnv` staging steps; `claude.ts`'s `buildArgs` needs no argv change for
2797
+ // this (CLAUDE_CONFIG_DIR is an env var, not a flag), same as CODEX_HOME.
2798
+ preparedClaudeConfigDir = await prepareClaudeConfigDirIfNeeded({ runId: input.runId, def }, {
2799
+ enabled: claudeConfigDirIsolationEnabled,
2800
+ claudeConfigDirIsolation,
2801
+ hostEnv: process.env,
2802
+ releaseStagedResources,
2803
+ failBeforeSpawn,
2804
+ });
2540
2805
  // `'config-instructions-file'`'s one effect — stage the overlay to a temp file so
2541
2806
  // `computeChildEnv` below has a real path to merge into that def's `instructions` array. A
2542
2807
  // no-op (`null`) for every other def/strategy or a run with no overlay at all. Independent of
@@ -2551,7 +2816,7 @@ export function createAgentExecutor(options) {
2551
2816
  // here ever read `childEnv`, so moving the call cost nothing.
2552
2817
  const childEnv = computeChildEnv(spawnEnv, mcpBridge, preparedCodexHome?.path, systemPromptEnvOverrides, preparedSystemPromptOverlayFile && def.systemPromptDelivery?.strategy === 'config-instructions-file'
2553
2818
  ? { varName: def.systemPromptDelivery.varName, path: preparedSystemPromptOverlayFile.path }
2554
- : undefined);
2819
+ : undefined, preparedClaudeConfigDir?.path);
2555
2820
  // Post-buildArgs guard for argv-bound defs whose resolved binary is a
2556
2821
  // Windows .cmd/.bat shim or a direct .exe: a prompt under the raw byte
2557
2822
  // budget can still expand past CreateProcess's command-line cap once
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module artifacts
3
+ *
4
+ * The generic artifact-store kernel port, ported from OD's
5
+ * `apps/daemon/src/artifacts/` (6 files). See `source-map.md` for full
6
+ * per-file provenance and the design decisions behind each generalization.
7
+ */
8
+ export * from './manifest.js';
9
+ export * from './store.js';
10
+ export * from './publication-guard.js';
11
+ export * from './runtime-compat.js';
12
+ export * from './stub-guard.js';
13
+ export * from './text-suppression.js';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module artifacts
3
+ *
4
+ * The generic artifact-store kernel port, ported from OD's
5
+ * `apps/daemon/src/artifacts/` (6 files). See `source-map.md` for full
6
+ * per-file provenance and the design decisions behind each generalization.
7
+ */
8
+ export * from './manifest.js';
9
+ export * from './store.js';
10
+ export * from './publication-guard.js';
11
+ export * from './runtime-compat.js';
12
+ export * from './stub-guard.js';
13
+ export * from './text-suppression.js';
14
+ //# sourceMappingURL=index.js.map