yuurei 0.0.1

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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/dist/artifact/collector.d.ts +15 -0
  4. package/dist/artifact/collector.d.ts.map +1 -0
  5. package/dist/artifact/collector.js +87 -0
  6. package/dist/artifact/collector.js.map +1 -0
  7. package/dist/artifact/types.d.ts +10 -0
  8. package/dist/artifact/types.d.ts.map +1 -0
  9. package/dist/artifact/types.js +2 -0
  10. package/dist/artifact/types.js.map +1 -0
  11. package/dist/cell/digest.d.ts +10 -0
  12. package/dist/cell/digest.d.ts.map +1 -0
  13. package/dist/cell/digest.js +13 -0
  14. package/dist/cell/digest.js.map +1 -0
  15. package/dist/cell/resolver.d.ts +18 -0
  16. package/dist/cell/resolver.d.ts.map +1 -0
  17. package/dist/cell/resolver.js +41 -0
  18. package/dist/cell/resolver.js.map +1 -0
  19. package/dist/cell/types.d.ts +48 -0
  20. package/dist/cell/types.d.ts.map +1 -0
  21. package/dist/cell/types.js +2 -0
  22. package/dist/cell/types.js.map +1 -0
  23. package/dist/cli/clean.d.ts +16 -0
  24. package/dist/cli/clean.d.ts.map +1 -0
  25. package/dist/cli/clean.js +24 -0
  26. package/dist/cli/clean.js.map +1 -0
  27. package/dist/cli/doctor.d.ts +29 -0
  28. package/dist/cli/doctor.d.ts.map +1 -0
  29. package/dist/cli/doctor.js +160 -0
  30. package/dist/cli/doctor.js.map +1 -0
  31. package/dist/cli/exit-codes.d.ts +15 -0
  32. package/dist/cli/exit-codes.d.ts.map +1 -0
  33. package/dist/cli/exit-codes.js +18 -0
  34. package/dist/cli/exit-codes.js.map +1 -0
  35. package/dist/cli/init.d.ts +32 -0
  36. package/dist/cli/init.d.ts.map +1 -0
  37. package/dist/cli/init.js +143 -0
  38. package/dist/cli/init.js.map +1 -0
  39. package/dist/cli/inspect.d.ts +4 -0
  40. package/dist/cli/inspect.d.ts.map +1 -0
  41. package/dist/cli/inspect.js +24 -0
  42. package/dist/cli/inspect.js.map +1 -0
  43. package/dist/cli/output.d.ts +5 -0
  44. package/dist/cli/output.d.ts.map +1 -0
  45. package/dist/cli/output.js +5 -0
  46. package/dist/cli/output.js.map +1 -0
  47. package/dist/cli/profile-list.d.ts +3 -0
  48. package/dist/cli/profile-list.d.ts.map +1 -0
  49. package/dist/cli/profile-list.js +14 -0
  50. package/dist/cli/profile-list.js.map +1 -0
  51. package/dist/cli/run.d.ts +22 -0
  52. package/dist/cli/run.d.ts.map +1 -0
  53. package/dist/cli/run.js +95 -0
  54. package/dist/cli/run.js.map +1 -0
  55. package/dist/cli/trace-show.d.ts +3 -0
  56. package/dist/cli/trace-show.d.ts.map +1 -0
  57. package/dist/cli/trace-show.js +22 -0
  58. package/dist/cli/trace-show.js.map +1 -0
  59. package/dist/config/discovery.d.ts +3 -0
  60. package/dist/config/discovery.d.ts.map +1 -0
  61. package/dist/config/discovery.js +18 -0
  62. package/dist/config/discovery.js.map +1 -0
  63. package/dist/config/schema.d.ts +21 -0
  64. package/dist/config/schema.d.ts.map +1 -0
  65. package/dist/config/schema.js +19 -0
  66. package/dist/config/schema.js.map +1 -0
  67. package/dist/config/yuurei-config.d.ts +4 -0
  68. package/dist/config/yuurei-config.d.ts.map +1 -0
  69. package/dist/config/yuurei-config.js +10 -0
  70. package/dist/config/yuurei-config.js.map +1 -0
  71. package/dist/cost/noop.d.ts +7 -0
  72. package/dist/cost/noop.d.ts.map +1 -0
  73. package/dist/cost/noop.js +10 -0
  74. package/dist/cost/noop.js.map +1 -0
  75. package/dist/cost/types.d.ts +21 -0
  76. package/dist/cost/types.d.ts.map +1 -0
  77. package/dist/cost/types.js +2 -0
  78. package/dist/cost/types.js.map +1 -0
  79. package/dist/index.d.ts +3 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +111 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/isolation/config-root.d.ts +9 -0
  84. package/dist/isolation/config-root.d.ts.map +1 -0
  85. package/dist/isolation/config-root.js +10 -0
  86. package/dist/isolation/config-root.js.map +1 -0
  87. package/dist/isolation/env.d.ts +2 -0
  88. package/dist/isolation/env.d.ts.map +1 -0
  89. package/dist/isolation/env.js +17 -0
  90. package/dist/isolation/env.js.map +1 -0
  91. package/dist/isolation/index.d.ts +9 -0
  92. package/dist/isolation/index.d.ts.map +1 -0
  93. package/dist/isolation/index.js +21 -0
  94. package/dist/isolation/index.js.map +1 -0
  95. package/dist/isolation/level0.d.ts +20 -0
  96. package/dist/isolation/level0.d.ts.map +1 -0
  97. package/dist/isolation/level0.js +51 -0
  98. package/dist/isolation/level0.js.map +1 -0
  99. package/dist/isolation/level1.d.ts +13 -0
  100. package/dist/isolation/level1.d.ts.map +1 -0
  101. package/dist/isolation/level1.js +58 -0
  102. package/dist/isolation/level1.js.map +1 -0
  103. package/dist/isolation/tempdir.d.ts +53 -0
  104. package/dist/isolation/tempdir.d.ts.map +1 -0
  105. package/dist/isolation/tempdir.js +87 -0
  106. package/dist/isolation/tempdir.js.map +1 -0
  107. package/dist/isolation/types.d.ts +37 -0
  108. package/dist/isolation/types.d.ts.map +1 -0
  109. package/dist/isolation/types.js +2 -0
  110. package/dist/isolation/types.js.map +1 -0
  111. package/dist/profile/loader.d.ts +9 -0
  112. package/dist/profile/loader.d.ts.map +1 -0
  113. package/dist/profile/loader.js +95 -0
  114. package/dist/profile/loader.js.map +1 -0
  115. package/dist/profile/manifest.d.ts +29 -0
  116. package/dist/profile/manifest.d.ts.map +1 -0
  117. package/dist/profile/manifest.js +28 -0
  118. package/dist/profile/manifest.js.map +1 -0
  119. package/dist/profile/types.d.ts +31 -0
  120. package/dist/profile/types.d.ts.map +1 -0
  121. package/dist/profile/types.js +2 -0
  122. package/dist/profile/types.js.map +1 -0
  123. package/dist/run/id.d.ts +7 -0
  124. package/dist/run/id.d.ts.map +1 -0
  125. package/dist/run/id.js +15 -0
  126. package/dist/run/id.js.map +1 -0
  127. package/dist/run/layout.d.ts +27 -0
  128. package/dist/run/layout.d.ts.map +1 -0
  129. package/dist/run/layout.js +45 -0
  130. package/dist/run/layout.js.map +1 -0
  131. package/dist/run/pipeline.d.ts +45 -0
  132. package/dist/run/pipeline.d.ts.map +1 -0
  133. package/dist/run/pipeline.js +246 -0
  134. package/dist/run/pipeline.js.map +1 -0
  135. package/dist/run/signals.d.ts +53 -0
  136. package/dist/run/signals.d.ts.map +1 -0
  137. package/dist/run/signals.js +74 -0
  138. package/dist/run/signals.js.map +1 -0
  139. package/dist/runtime/claude-code/args.d.ts +8 -0
  140. package/dist/runtime/claude-code/args.d.ts.map +1 -0
  141. package/dist/runtime/claude-code/args.js +13 -0
  142. package/dist/runtime/claude-code/args.js.map +1 -0
  143. package/dist/runtime/claude-code/index.d.ts +11 -0
  144. package/dist/runtime/claude-code/index.d.ts.map +1 -0
  145. package/dist/runtime/claude-code/index.js +167 -0
  146. package/dist/runtime/claude-code/index.js.map +1 -0
  147. package/dist/runtime/claude-code/paths.d.ts +3 -0
  148. package/dist/runtime/claude-code/paths.d.ts.map +1 -0
  149. package/dist/runtime/claude-code/paths.js +6 -0
  150. package/dist/runtime/claude-code/paths.js.map +1 -0
  151. package/dist/runtime/codex/args.d.ts +21 -0
  152. package/dist/runtime/codex/args.d.ts.map +1 -0
  153. package/dist/runtime/codex/args.js +35 -0
  154. package/dist/runtime/codex/args.js.map +1 -0
  155. package/dist/runtime/codex/index.d.ts +13 -0
  156. package/dist/runtime/codex/index.d.ts.map +1 -0
  157. package/dist/runtime/codex/index.js +299 -0
  158. package/dist/runtime/codex/index.js.map +1 -0
  159. package/dist/runtime/codex/paths.d.ts +3 -0
  160. package/dist/runtime/codex/paths.d.ts.map +1 -0
  161. package/dist/runtime/codex/paths.js +6 -0
  162. package/dist/runtime/codex/paths.js.map +1 -0
  163. package/dist/runtime/detect.d.ts +5 -0
  164. package/dist/runtime/detect.d.ts.map +1 -0
  165. package/dist/runtime/detect.js +42 -0
  166. package/dist/runtime/detect.js.map +1 -0
  167. package/dist/runtime/exec.d.ts +24 -0
  168. package/dist/runtime/exec.d.ts.map +1 -0
  169. package/dist/runtime/exec.js +132 -0
  170. package/dist/runtime/exec.js.map +1 -0
  171. package/dist/runtime/registry.d.ts +4 -0
  172. package/dist/runtime/registry.d.ts.map +1 -0
  173. package/dist/runtime/registry.js +23 -0
  174. package/dist/runtime/registry.js.map +1 -0
  175. package/dist/runtime/reserved-paths.d.ts +22 -0
  176. package/dist/runtime/reserved-paths.d.ts.map +1 -0
  177. package/dist/runtime/reserved-paths.js +30 -0
  178. package/dist/runtime/reserved-paths.js.map +1 -0
  179. package/dist/runtime/types.d.ts +105 -0
  180. package/dist/runtime/types.d.ts.map +1 -0
  181. package/dist/runtime/types.js +2 -0
  182. package/dist/runtime/types.js.map +1 -0
  183. package/dist/trace/reader.d.ts +4 -0
  184. package/dist/trace/reader.d.ts.map +1 -0
  185. package/dist/trace/reader.js +9 -0
  186. package/dist/trace/reader.js.map +1 -0
  187. package/dist/trace/redact.d.ts +22 -0
  188. package/dist/trace/redact.d.ts.map +1 -0
  189. package/dist/trace/redact.js +124 -0
  190. package/dist/trace/redact.js.map +1 -0
  191. package/dist/trace/schema.d.ts +49 -0
  192. package/dist/trace/schema.d.ts.map +1 -0
  193. package/dist/trace/schema.js +53 -0
  194. package/dist/trace/schema.js.map +1 -0
  195. package/dist/trace/writer.d.ts +17 -0
  196. package/dist/trace/writer.d.ts.map +1 -0
  197. package/dist/trace/writer.js +45 -0
  198. package/dist/trace/writer.js.map +1 -0
  199. package/dist/util/fs.d.ts +17 -0
  200. package/dist/util/fs.d.ts.map +1 -0
  201. package/dist/util/fs.js +61 -0
  202. package/dist/util/fs.js.map +1 -0
  203. package/dist/util/hash.d.ts +3 -0
  204. package/dist/util/hash.d.ts.map +1 -0
  205. package/dist/util/hash.js +7 -0
  206. package/dist/util/hash.js.map +1 -0
  207. package/dist/util/json.d.ts +8 -0
  208. package/dist/util/json.d.ts.map +1 -0
  209. package/dist/util/json.js +24 -0
  210. package/dist/util/json.js.map +1 -0
  211. package/dist/util/logger.d.ts +8 -0
  212. package/dist/util/logger.d.ts.map +1 -0
  213. package/dist/util/logger.js +16 -0
  214. package/dist/util/logger.js.map +1 -0
  215. package/dist/version.d.ts +24 -0
  216. package/dist/version.d.ts.map +1 -0
  217. package/dist/version.js +26 -0
  218. package/dist/version.js.map +1 -0
  219. package/docs/assets/yuurei-top.jpg +0 -0
  220. package/package.json +61 -0
@@ -0,0 +1,21 @@
1
+ import type { ResolvedCell } from '../../cell/types.js';
2
+ /**
3
+ * Builds the non-interactive CLI invocation for Codex. Kept isolated from
4
+ * the rest of the codebase so a future CLI flag change only touches this
5
+ * file (design doc §6.1: adapter-internal conventions must not leak).
6
+ *
7
+ * `-c` overrides always win over a config.toml value (verified against the
8
+ * real `codex` CLI). Forcing the credential store to `file` here, at the
9
+ * adapter boundary, closes an isolation bypass: a materialized profile's
10
+ * own `config.toml` could otherwise set `cli_auth_credentials_store =
11
+ * "keyring"` (or "auto", which prefers the keyring when available) and read
12
+ * or refresh the operator's real OS-keychain-backed credential — entirely
13
+ * outside the isolated CODEX_HOME, regardless of whether
14
+ * --bridge-codex-auth-file was ever passed. mcp_oauth_credentials_store
15
+ * defaults to "auto" for the same reason and gets the same override. This
16
+ * doesn't need to defend against a profile's own -p/--profile config layer,
17
+ * since this adapter never passes -p — only the single materialized
18
+ * config.toml is ever in play.
19
+ */
20
+ export declare function buildCodexArgs(cell: ResolvedCell): string[];
21
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../../src/runtime/codex/args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAe3D"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Builds the non-interactive CLI invocation for Codex. Kept isolated from
3
+ * the rest of the codebase so a future CLI flag change only touches this
4
+ * file (design doc §6.1: adapter-internal conventions must not leak).
5
+ *
6
+ * `-c` overrides always win over a config.toml value (verified against the
7
+ * real `codex` CLI). Forcing the credential store to `file` here, at the
8
+ * adapter boundary, closes an isolation bypass: a materialized profile's
9
+ * own `config.toml` could otherwise set `cli_auth_credentials_store =
10
+ * "keyring"` (or "auto", which prefers the keyring when available) and read
11
+ * or refresh the operator's real OS-keychain-backed credential — entirely
12
+ * outside the isolated CODEX_HOME, regardless of whether
13
+ * --bridge-codex-auth-file was ever passed. mcp_oauth_credentials_store
14
+ * defaults to "auto" for the same reason and gets the same override. This
15
+ * doesn't need to defend against a profile's own -p/--profile config layer,
16
+ * since this adapter never passes -p — only the single materialized
17
+ * config.toml is ever in play.
18
+ */
19
+ export function buildCodexArgs(cell) {
20
+ const args = [
21
+ 'exec',
22
+ '--json',
23
+ '--skip-git-repo-check',
24
+ '-c',
25
+ 'cli_auth_credentials_store="file"',
26
+ '-c',
27
+ 'mcp_oauth_credentials_store="file"',
28
+ cell.resolvedTask.content,
29
+ ];
30
+ if (cell.requestedModel) {
31
+ args.push('--model', cell.requestedModel);
32
+ }
33
+ return args;
34
+ }
35
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../../src/runtime/codex/args.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,cAAc,CAAC,IAAkB;IAC/C,MAAM,IAAI,GAAG;QACX,MAAM;QACN,QAAQ;QACR,uBAAuB;QACvB,IAAI;QACJ,mCAAmC;QACnC,IAAI;QACJ,oCAAoC;QACpC,IAAI,CAAC,YAAY,CAAC,OAAO;KAC1B,CAAC;IACF,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { IsolationContext } from '../../isolation/types.js';
2
+ import type { ResolvedCell } from '../../cell/types.js';
3
+ import type { NormalizationContext, NormalizedTraceFragment, PreparedRun, RegisterCredentialPath, Runtime, RuntimeDetection, RuntimeResult } from '../types.js';
4
+ /** Remove Codex's benign non-interactive stdin notice from durable stderr logs. */
5
+ export declare function stripCodexStdinNotice(stderr: string): string;
6
+ export declare class CodexRuntime implements Runtime {
7
+ id(): string;
8
+ detect(): Promise<RuntimeDetection>;
9
+ prepare(cell: ResolvedCell, isolation: IsolationContext, registerCredentialPath: RegisterCredentialPath): Promise<PreparedRun>;
10
+ execute(run: PreparedRun, timeoutMs: number | null): Promise<RuntimeResult>;
11
+ normalize(result: RuntimeResult, context: NormalizationContext): Promise<NormalizedTraceFragment>;
12
+ }
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/runtime/codex/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,OAAO,KAAK,EACV,oBAAoB,EACpB,uBAAuB,EACvB,WAAW,EACX,sBAAsB,EACtB,OAAO,EACP,gBAAgB,EAChB,aAAa,EACd,MAAM,aAAa,CAAC;AAkBrB,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAK5D;AA0HD,qBAAa,YAAa,YAAW,OAAO;IAC1C,EAAE,IAAI,MAAM;IAIN,MAAM,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAkBnC,OAAO,CACX,IAAI,EAAE,YAAY,EAClB,SAAS,EAAE,gBAAgB,EAC3B,sBAAsB,EAAE,sBAAsB,GAC7C,OAAO,CAAC,WAAW,CAAC;IA6CjB,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;IAkB3E,SAAS,CACb,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,uBAAuB,CAAC;CA6EpC"}
@@ -0,0 +1,299 @@
1
+ import { chmod, copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { configRootOf } from '../../isolation/config-root.js';
5
+ import { EXIT_CODES, YuureiError } from '../../cli/exit-codes.js';
6
+ import { detectViaVersionFlag, isVersionAtLeast } from '../detect.js';
7
+ import { execCapture } from '../exec.js';
8
+ import { isPathWithin, pathExists, writeFileTree } from '../../util/fs.js';
9
+ import { assertNoReservedConfigPath } from '../reserved-paths.js';
10
+ import { buildCodexArgs } from './args.js';
11
+ import { codexConfigDir } from './paths.js';
12
+ const RUNTIME_ID = 'codex';
13
+ const COMMAND = 'codex';
14
+ const MIN_SUPPORTED_VERSION = [0, 100, 0];
15
+ /**
16
+ * Root-level `auth.json` is reserved for the opt-in bridged credential (see
17
+ * bridgeCodexAuthFile below). See `assertNoReservedConfigPath` for why this is
18
+ * enforced even when the bridge is disabled.
19
+ */
20
+ const CODEX_RESERVED_CONFIG_PATHS = ['auth.json'];
21
+ const CODEX_STDIN_NOTICE = 'Reading additional input from stdin...';
22
+ /** Remove Codex's benign non-interactive stdin notice from durable stderr logs. */
23
+ export function stripCodexStdinNotice(stderr) {
24
+ return stderr
25
+ .split('\n')
26
+ .filter((line) => line !== CODEX_STDIN_NOTICE && line !== `${CODEX_STDIN_NOTICE}\r`)
27
+ .join('\n');
28
+ }
29
+ /**
30
+ * Supported v0.3 method (design doc §9.2) for Codex: forward an explicitly-set
31
+ * API key from the parent environment, exactly mirroring how the Claude Code
32
+ * adapter forwards ANTHROPIC_API_KEY. Verified against the real `codex` CLI:
33
+ * `codex exec` authenticates from OPENAI_API_KEY in the environment with no
34
+ * ~/.codex/auth.json present at all, so this needs no file on disk and carries
35
+ * no rotation risk — an API key doesn't rotate mid-run the way an OAuth
36
+ * access/refresh token pair can. Returns the forwarded value (if any) so the
37
+ * pipeline can redact it from persisted logs.
38
+ */
39
+ function bridgeCodexApiKey(env) {
40
+ const value = process.env['OPENAI_API_KEY'];
41
+ if (!value)
42
+ return [];
43
+ env['OPENAI_API_KEY'] = value;
44
+ return [value];
45
+ }
46
+ function wantsCodexAuthFileBridge(executionOptions) {
47
+ return executionOptions['bridgeCodexAuthFile'] === true;
48
+ }
49
+ /**
50
+ * Best-effort extraction of the actual secret values inside a bridged
51
+ * auth.json (the API-key field, plus the OAuth token triple when present —
52
+ * see the `auth_mode`/`tokens` shape Codex itself writes), so the pipeline
53
+ * can redact them from persisted logs by exact value rather than by a
54
+ * pattern that may not match an opaque token or JWT. Never throws: a file
55
+ * we just copied ourselves failing to re-parse is treated the same as
56
+ * finding nothing to redact, not as a reason to fail preparation.
57
+ */
58
+ async function extractCodexAuthSecrets(path) {
59
+ try {
60
+ const parsed = JSON.parse(await readFile(path, 'utf8'));
61
+ if (typeof parsed !== 'object' || parsed === null)
62
+ return [];
63
+ const record = parsed;
64
+ const values = [];
65
+ const apiKey = record['OPENAI_API_KEY'];
66
+ if (typeof apiKey === 'string')
67
+ values.push(apiKey);
68
+ const tokens = record['tokens'];
69
+ if (typeof tokens === 'object' && tokens !== null) {
70
+ const tokenRecord = tokens;
71
+ for (const key of ['access_token', 'refresh_token', 'id_token']) {
72
+ const value = tokenRecord[key];
73
+ if (typeof value === 'string')
74
+ values.push(value);
75
+ }
76
+ }
77
+ return values;
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ }
83
+ /**
84
+ * Experimental, opt-in method for reusing an interactive ChatGPT login: copy
85
+ * exactly ~/.codex/auth.json into the isolated CODEX_HOME at mode 0600. Not
86
+ * the supported v0.3 path — that's bridgeCodexApiKey above — because this
87
+ * file can carry a rotating OAuth token pair. If `codex exec` refreshes the
88
+ * token mid-run, only the isolated copy receives the new state; the real
89
+ * ~/.codex/auth.json stays stale, and the valid rotated copy is then deleted
90
+ * by the pipeline's credential scrub. Writing the rotated state back to the
91
+ * real file would fix that but violates principle 1 (never modify the
92
+ * user's existing global configuration), so this is left as a known,
93
+ * documented limitation rather than "fixed" by breaking that guarantee —
94
+ * callers who opt in accept it. Copies nothing else from the real ~/.codex;
95
+ * a copy rather than a symlink for the same reason (a symlink would let a
96
+ * token refresh write *through* to the real file).
97
+ *
98
+ * `destDir` is guaranteed to hold no `auth.json` before this runs
99
+ * (assertNoReservedConfigPath rejects a profile-supplied one earlier in
100
+ * prepare()), so on any failure below the dest is left absent, not stale —
101
+ * the run then runs genuinely unauthenticated rather than under a wrong or
102
+ * partial credential — UNLESS cleaning up a failed copy also fails (a
103
+ * double fault: e.g. copyFile succeeds but chmod then fails, and the
104
+ * catch-block's own rm() fails too). In that case the file at `dest` is
105
+ * left in an unknown state. prepare() already registered `dest` with the
106
+ * pipeline before this call, so the scrub retries the removal during
107
+ * cleanup; if that second removal also fails the pipeline reports the
108
+ * residual path via onWarning. This still throws (rather than silently
109
+ * returning as if nothing were bridged) because aborting prepare() is
110
+ * judged safer than launching Codex against a leftover, untracked
111
+ * credential file at a reserved path.
112
+ */
113
+ async function bridgeCodexAuthFile(destDir) {
114
+ // homedir() follows process.env.HOME, so a test override of process.env.HOME
115
+ // reaches this lookup — matching how Level1Isolation.verify() locates the real home.
116
+ const realCodexHome = codexConfigDir(homedir());
117
+ const sourcePath = join(realCodexHome, 'auth.json');
118
+ const dest = join(destDir, 'auth.json');
119
+ // Graft from synthesis.md: guard against auth.json itself being a symlink that
120
+ // escapes ~/.codex (e.g. -> ~/.ssh/id_rsa). Non-fatal — return rather than throw.
121
+ if (!(await isPathWithin(realCodexHome, sourcePath)))
122
+ return [];
123
+ if (!(await pathExists(sourcePath)))
124
+ return [];
125
+ // Best-effort per §12.2: any I/O failure here (permission error, a source
126
+ // file removed between the checks above and the copy, a full disk) must
127
+ // not abort the run — it must leave the run unauthenticated, same as a
128
+ // missing source file. destDir already exists (prepare() creates it).
129
+ try {
130
+ await copyFile(sourcePath, dest);
131
+ await chmod(dest, 0o600);
132
+ }
133
+ catch (bridgeError) {
134
+ // Don't leave a partial copy (e.g. chmod failed after copyFile succeeded)
135
+ // sitting at the reserved path — absent is the only safe failure state.
136
+ try {
137
+ await rm(dest, { force: true });
138
+ }
139
+ catch (cleanupError) {
140
+ throw new YuureiError(`failed to bridge Codex credentials (${bridgeError instanceof Error ? bridgeError.message : String(bridgeError)}) and could not remove the partial copy left at ${dest}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, EXIT_CODES.ISOLATION_VERIFICATION_FAILED);
141
+ }
142
+ return [];
143
+ }
144
+ return extractCodexAuthSecrets(dest);
145
+ }
146
+ export class CodexRuntime {
147
+ id() {
148
+ return RUNTIME_ID;
149
+ }
150
+ async detect() {
151
+ const detection = await detectViaVersionFlag(COMMAND);
152
+ const authUsable = detection.installed && Boolean(process.env['OPENAI_API_KEY']);
153
+ return {
154
+ ...detection,
155
+ versionSupported: isVersionAtLeast(detection.version, MIN_SUPPORTED_VERSION),
156
+ authUsable,
157
+ ...(detection.installed && authUsable === false
158
+ ? {
159
+ authGuidance: 'Export OPENAI_API_KEY in this shell and re-run `yuurei doctor`. ' +
160
+ 'A shell export applies only to that shell and its child processes; do not persist the key in a profile, task, or committed file. ' +
161
+ 'Verify the variable is set without printing its value: [ -n "$OPENAI_API_KEY" ] && echo present || echo absent',
162
+ }
163
+ : {}),
164
+ };
165
+ }
166
+ async prepare(cell, isolation, registerCredentialPath) {
167
+ const env = { ...isolation.env };
168
+ const configDir = codexConfigDir(configRootOf(isolation));
169
+ await mkdir(configDir, { recursive: true, mode: 0o700 });
170
+ // A profile must never be able to supply its own auth.json (§9.2) — reject
171
+ // it before anything is written, rather than relying on write order to
172
+ // paper over the collision (a bridge that no-ops or fails would otherwise
173
+ // let a profile-supplied credential silently survive).
174
+ assertNoReservedConfigPath(configDir, cell.resolvedProfile.content.configFiles, CODEX_RESERVED_CONFIG_PATHS);
175
+ await writeFileTree(configDir, cell.resolvedProfile.content.configFiles);
176
+ const credentialValuesToRedact = bridgeCodexApiKey(env);
177
+ if (wantsCodexAuthFileBridge(cell.executionOptions)) {
178
+ // Register the destination before the copy. A signal or throw between
179
+ // the write and prepare()'s return — or the documented double fault,
180
+ // where the adapter's own cleanup rm() fails — must still leave the file
181
+ // on the scrub list; the pipeline removes a registered path even on
182
+ // paths where no PreparedRun is ever returned (§9.2). Registering a path
183
+ // the copy never reaches is harmless: the scrub's rm has `force: true`.
184
+ registerCredentialPath(join(configDir, 'auth.json'));
185
+ const secretValues = await bridgeCodexAuthFile(configDir);
186
+ credentialValuesToRedact.push(...secretValues);
187
+ }
188
+ env['CODEX_HOME'] = configDir;
189
+ const detection = await this.detect();
190
+ return {
191
+ runtimeId: RUNTIME_ID,
192
+ command: COMMAND,
193
+ args: buildCodexArgs(cell),
194
+ env,
195
+ cwd: isolation.rootDir,
196
+ isolation,
197
+ cell,
198
+ runtimeVersion: detection.version,
199
+ credentialValuesToRedact,
200
+ };
201
+ }
202
+ async execute(run, timeoutMs) {
203
+ const result = await execCapture({
204
+ command: run.command,
205
+ args: run.args,
206
+ env: run.env,
207
+ cwd: run.cwd,
208
+ stdoutPath: join(run.isolation.rootDir, 'stdout.log'),
209
+ stderrPath: join(run.isolation.rootDir, 'stderr.log'),
210
+ ...(timeoutMs !== null ? { timeoutMs } : {}),
211
+ });
212
+ const stderr = await readFile(result.stderrPath, 'utf8');
213
+ const filteredStderr = stripCodexStdinNotice(stderr);
214
+ if (filteredStderr !== stderr) {
215
+ await writeFile(result.stderrPath, filteredStderr, 'utf8');
216
+ }
217
+ return result;
218
+ }
219
+ async normalize(result, context) {
220
+ const durationMs = new Date(result.finishedAt).getTime() - new Date(result.startedAt).getTime();
221
+ let usage = {};
222
+ const warnings = [];
223
+ try {
224
+ const stdout = await readFile(result.stdoutPath, 'utf8');
225
+ // Scan all lines to count every malformed line (including those after
226
+ // turn.completed) and to treat valid-JSON non-objects as malformed too.
227
+ let firstCompleted = null;
228
+ let malformedLineCount = 0;
229
+ for (const line of stdout.split('\n')) {
230
+ const trimmed = line.trim();
231
+ if (!trimmed)
232
+ continue;
233
+ let event;
234
+ try {
235
+ event = JSON.parse(trimmed);
236
+ }
237
+ catch {
238
+ malformedLineCount++;
239
+ continue;
240
+ }
241
+ if (typeof event !== 'object' || event === null) {
242
+ // Valid JSON but not an object (null, string, number) — not a valid event
243
+ malformedLineCount++;
244
+ continue;
245
+ }
246
+ const obj = event;
247
+ if (obj['type'] === 'turn.completed' && firstCompleted === null) {
248
+ firstCompleted = obj;
249
+ }
250
+ }
251
+ if (malformedLineCount > 0) {
252
+ warnings.push(`codex: ${malformedLineCount} unparseable JSONL line(s) skipped`);
253
+ }
254
+ if (firstCompleted !== null) {
255
+ const usageRaw = firstCompleted['usage'];
256
+ if (typeof usageRaw === 'object' && usageRaw !== null) {
257
+ const u = usageRaw;
258
+ const pick = (key) => {
259
+ const v = u[key];
260
+ return typeof v === 'number' ? v : null;
261
+ };
262
+ usage = {
263
+ input_tokens: pick('input_tokens'),
264
+ output_tokens: pick('output_tokens'),
265
+ cached_input_tokens: pick('cached_input_tokens'),
266
+ cache_write_input_tokens: pick('cache_write_input_tokens'),
267
+ reasoning_output_tokens: pick('reasoning_output_tokens'),
268
+ };
269
+ }
270
+ else {
271
+ warnings.push('codex: turn.completed event had no usage field — usage unobserved');
272
+ }
273
+ }
274
+ else {
275
+ warnings.push('codex: no turn.completed event found in stdout — usage unobserved');
276
+ }
277
+ }
278
+ catch (err) {
279
+ const code = err.code;
280
+ if (code === 'ENOENT') {
281
+ // Missing stdout is expected for killed/timed-out runs; warn only on normal exit
282
+ if (result.exitCode === 0 && result.signal === null && !result.timedOut) {
283
+ warnings.push('codex: stdout absent after normal exit — usage unobserved');
284
+ }
285
+ }
286
+ else {
287
+ warnings.push(`codex: stdout unreadable (${err instanceof Error ? err.message : String(err)}) — usage unobserved`);
288
+ }
289
+ }
290
+ return {
291
+ runtime: { id: RUNTIME_ID, version: context.runtimeVersion },
292
+ model: { requested: '', resolved: null },
293
+ execution: { exitCode: result.exitCode, signal: result.signal, durationMs },
294
+ usage,
295
+ ...(warnings.length > 0 ? { warnings } : {}),
296
+ };
297
+ }
298
+ }
299
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/runtime/codex/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Codex's config root under a given HOME directory (real or isolated). */
2
+ export declare function codexConfigDir(homeDir: string): string;
3
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/runtime/codex/paths.ts"],"names":[],"mappings":"AAEA,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD"}
@@ -0,0 +1,6 @@
1
+ import { join } from 'node:path';
2
+ /** Codex's config root under a given HOME directory (real or isolated). */
3
+ export function codexConfigDir(homeDir) {
4
+ return join(homeDir, '.codex');
5
+ }
6
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/runtime/codex/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { RuntimeDetection } from './types.js';
2
+ /** Runs `<command> <versionFlag>` and reports whether the CLI is installed. */
3
+ export declare function detectViaVersionFlag(command: string, versionFlag?: string): Promise<RuntimeDetection>;
4
+ export declare function isVersionAtLeast(version: string | null, minimum: [number, number, number]): boolean | null;
5
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/runtime/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD,+EAA+E;AAC/E,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,WAAW,SAAc,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAmB3B;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAChC,OAAO,GAAG,IAAI,CAehB"}
@@ -0,0 +1,42 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ /** Runs `<command> <versionFlag>` and reports whether the CLI is installed. */
5
+ export async function detectViaVersionFlag(command, versionFlag = '--version') {
6
+ try {
7
+ const { stdout } = await execFileAsync(command, [versionFlag]);
8
+ return {
9
+ installed: true,
10
+ version: stdout.trim() || null,
11
+ versionSupported: null,
12
+ executablePath: command,
13
+ authUsable: null,
14
+ };
15
+ }
16
+ catch {
17
+ return {
18
+ installed: false,
19
+ version: null,
20
+ versionSupported: null,
21
+ executablePath: null,
22
+ authUsable: null,
23
+ };
24
+ }
25
+ }
26
+ export function isVersionAtLeast(version, minimum) {
27
+ if (!version)
28
+ return null;
29
+ const matches = [...version.matchAll(/(\d+)\.(\d+)(?:\.(\d+))?/g)];
30
+ const match = matches.at(-1);
31
+ if (!match)
32
+ return null;
33
+ const actual = [
34
+ Number(match[1]),
35
+ Number(match[2]),
36
+ Number(match[3] ?? 0),
37
+ ];
38
+ return (actual[0] > minimum[0] ||
39
+ (actual[0] === minimum[0] &&
40
+ (actual[1] > minimum[1] || (actual[1] === minimum[1] && actual[2] >= minimum[2]))));
41
+ }
42
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/runtime/detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe,EACf,WAAW,GAAG,WAAW;IAEzB,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;QAC/D,OAAO;YACL,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI;YAC9B,gBAAgB,EAAE,IAAI;YACtB,cAAc,EAAE,OAAO;YACvB,UAAU,EAAE,IAAI;SACjB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,OAAO,EAAE,IAAI;YACb,gBAAgB,EAAE,IAAI;YACtB,cAAc,EAAE,IAAI;YACpB,UAAU,EAAE,IAAI;SACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,OAAsB,EACtB,OAAiC;IAEjC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAC,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,MAAM,GAA6B;QACvC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAChB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAChB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;KACtB,CAAC;IACF,OAAO,CACL,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;QACtB,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;YACvB,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACrF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { RuntimeResult } from './types.js';
2
+ export interface ExecOptions {
3
+ command: string;
4
+ args: string[];
5
+ env: Record<string, string>;
6
+ cwd: string;
7
+ stdoutPath: string;
8
+ stderrPath: string;
9
+ timeoutMs?: number;
10
+ /** Test seam only. Overrides SIGKILL_GRACE_PERIOD_MS; production callers must omit it. */
11
+ killGracePeriodMsForTests?: number;
12
+ }
13
+ /**
14
+ * Node's setTimeout silently clamps any delay outside [1, 2^31-1] to 1ms
15
+ * (see the Node docs on timers) rather than erroring, so an out-of-range
16
+ * timeoutMs would fire almost immediately instead of behaving like "a very
17
+ * long timeout" — the opposite of what a caller passing e.g. `Infinity` or
18
+ * a 13-digit typo would expect. Rejected explicitly here rather than left
19
+ * to that silent clamp.
20
+ */
21
+ export declare const MAX_TIMEOUT_MS = 2147483647;
22
+ /** Spawns a process, captures stdout/stderr to files, and normalizes the result. */
23
+ export declare function execCapture(options: ExecOptions): Promise<RuntimeResult>;
24
+ //# sourceMappingURL=exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/runtime/exec.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC;AAaD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,aAAgB,CAAC;AAE5C,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAyHxE"}
@@ -0,0 +1,132 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createWriteStream } from 'node:fs';
3
+ import { finished } from 'node:stream/promises';
4
+ /**
5
+ * How long to wait after SIGTERM before escalating to SIGKILL. A process
6
+ * that ignores SIGTERM (or is wedged in a syscall it can't be interrupted
7
+ * from) would otherwise leave the run hanging exactly as before a timeout
8
+ * was ever fired. Not configurable in v0.3 — a fixed grace period is
9
+ * simpler than a second timeout knob, and 10s is generous enough for
10
+ * normal cleanup (flushing buffers, closing files) without materially
11
+ * extending how long a stuck run blocks the caller.
12
+ */
13
+ const SIGKILL_GRACE_PERIOD_MS = 10_000;
14
+ /**
15
+ * Node's setTimeout silently clamps any delay outside [1, 2^31-1] to 1ms
16
+ * (see the Node docs on timers) rather than erroring, so an out-of-range
17
+ * timeoutMs would fire almost immediately instead of behaving like "a very
18
+ * long timeout" — the opposite of what a caller passing e.g. `Infinity` or
19
+ * a 13-digit typo would expect. Rejected explicitly here rather than left
20
+ * to that silent clamp.
21
+ */
22
+ export const MAX_TIMEOUT_MS = 2_147_483_647;
23
+ /** Spawns a process, captures stdout/stderr to files, and normalizes the result. */
24
+ export function execCapture(options) {
25
+ if (options.timeoutMs !== undefined &&
26
+ !(Number.isFinite(options.timeoutMs) &&
27
+ options.timeoutMs > 0 &&
28
+ options.timeoutMs <= MAX_TIMEOUT_MS)) {
29
+ return Promise.reject(new RangeError(`timeoutMs must be a finite number between 1 and ${MAX_TIMEOUT_MS}, got ${options.timeoutMs}`));
30
+ }
31
+ return new Promise((resolvePromise, reject) => {
32
+ const startedAt = new Date().toISOString();
33
+ const stdoutStream = createWriteStream(options.stdoutPath);
34
+ const stderrStream = createWriteStream(options.stderrPath);
35
+ // Created — but not yet awaited — up front, before spawning: a bad
36
+ // path, EACCES, or a full disk can make either stream emit 'error'
37
+ // immediately, before the child even starts. finished() attaches its
38
+ // own listener as soon as it's called, so calling it here (rather than
39
+ // only later inside the 'close' handler) is what turns that into a
40
+ // handled rejection instead of an unhandled 'error' event, which Node
41
+ // treats as fatal and would crash the whole process.
42
+ const stdoutFinished = finished(stdoutStream);
43
+ const stderrFinished = finished(stderrStream);
44
+ const child = spawn(options.command, options.args, {
45
+ env: options.env,
46
+ cwd: options.cwd,
47
+ stdio: ['ignore', 'pipe', 'pipe'],
48
+ });
49
+ let timedOut = false;
50
+ let timeoutTimer = null;
51
+ let killTimer = null;
52
+ let settled = false;
53
+ const clearTimers = () => {
54
+ if (timeoutTimer)
55
+ clearTimeout(timeoutTimer);
56
+ if (killTimer)
57
+ clearTimeout(killTimer);
58
+ };
59
+ // Single place for anything that makes this run unrecoverable (spawn
60
+ // failure, either write stream erroring out): stop the timers, force
61
+ // the child to exit rather than leaving it running with nowhere for
62
+ // its output to go, tear down whichever stream didn't already error on
63
+ // its own, and reject exactly once — guarded so a second failure
64
+ // (e.g. both streams erroring, or 'error' racing a stream failure)
65
+ // can't double-kill or double-settle.
66
+ const failOnce = (error) => {
67
+ if (settled)
68
+ return;
69
+ settled = true;
70
+ clearTimers();
71
+ child.kill('SIGKILL');
72
+ stdoutStream.destroy();
73
+ stderrStream.destroy();
74
+ reject(error);
75
+ };
76
+ stdoutFinished.catch(failOnce);
77
+ stderrFinished.catch(failOnce);
78
+ if (options.timeoutMs) {
79
+ timeoutTimer = setTimeout(() => {
80
+ timedOut = true;
81
+ child.kill('SIGTERM');
82
+ // If the process ignores SIGTERM, force it after a grace period.
83
+ // Cleared in the exit/close handlers below if it exits sooner.
84
+ killTimer = setTimeout(() => {
85
+ child.kill('SIGKILL');
86
+ }, options.killGracePeriodMsForTests ?? SIGKILL_GRACE_PERIOD_MS);
87
+ }, options.timeoutMs);
88
+ }
89
+ child.stdout.pipe(stdoutStream);
90
+ child.stderr.pipe(stderrStream);
91
+ child.on('error', failOnce);
92
+ // 'close' waits for the child's stdio streams to finish closing, which
93
+ // can lag behind the process actually exiting. Stopping the timers here
94
+ // on 'exit' — as soon as the OS reports the process is gone — instead
95
+ // of only on 'close' avoids sending a stale SIGTERM/SIGKILL to an
96
+ // already-dead process and marking a run that finished within budget
97
+ // as timed out just because 'close' arrived a beat late.
98
+ child.on('exit', () => {
99
+ clearTimers();
100
+ });
101
+ child.on('close', (exitCode, signal) => {
102
+ if (settled)
103
+ return;
104
+ clearTimers();
105
+ // 'close' only confirms the child's own stdio streams have ended —
106
+ // not that our destination write streams have flushed their buffered
107
+ // data to disk. pipe() calls .end() on them in response, but that
108
+ // completes asynchronously; resolving before it does can hand the
109
+ // caller a stdoutPath/stderrPath that's still being written to,
110
+ // producing a truncated read for a run that actually finished fine.
111
+ Promise.all([stdoutFinished, stderrFinished])
112
+ .then(() => {
113
+ if (settled)
114
+ return;
115
+ settled = true;
116
+ resolvePromise({
117
+ exitCode,
118
+ signal,
119
+ startedAt,
120
+ finishedAt: new Date().toISOString(),
121
+ stdoutPath: options.stdoutPath,
122
+ stderrPath: options.stderrPath,
123
+ timedOut,
124
+ });
125
+ })
126
+ .catch(() => {
127
+ // Already handled by failOnce via the per-stream .catch above.
128
+ });
129
+ });
130
+ });
131
+ }
132
+ //# sourceMappingURL=exec.js.map