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.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/dist/artifact/collector.d.ts +15 -0
- package/dist/artifact/collector.d.ts.map +1 -0
- package/dist/artifact/collector.js +87 -0
- package/dist/artifact/collector.js.map +1 -0
- package/dist/artifact/types.d.ts +10 -0
- package/dist/artifact/types.d.ts.map +1 -0
- package/dist/artifact/types.js +2 -0
- package/dist/artifact/types.js.map +1 -0
- package/dist/cell/digest.d.ts +10 -0
- package/dist/cell/digest.d.ts.map +1 -0
- package/dist/cell/digest.js +13 -0
- package/dist/cell/digest.js.map +1 -0
- package/dist/cell/resolver.d.ts +18 -0
- package/dist/cell/resolver.d.ts.map +1 -0
- package/dist/cell/resolver.js +41 -0
- package/dist/cell/resolver.js.map +1 -0
- package/dist/cell/types.d.ts +48 -0
- package/dist/cell/types.d.ts.map +1 -0
- package/dist/cell/types.js +2 -0
- package/dist/cell/types.js.map +1 -0
- package/dist/cli/clean.d.ts +16 -0
- package/dist/cli/clean.d.ts.map +1 -0
- package/dist/cli/clean.js +24 -0
- package/dist/cli/clean.js.map +1 -0
- package/dist/cli/doctor.d.ts +29 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +160 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +15 -0
- package/dist/cli/exit-codes.d.ts.map +1 -0
- package/dist/cli/exit-codes.js +18 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/init.d.ts +32 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +143 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/inspect.d.ts +4 -0
- package/dist/cli/inspect.d.ts.map +1 -0
- package/dist/cli/inspect.js +24 -0
- package/dist/cli/inspect.js.map +1 -0
- package/dist/cli/output.d.ts +5 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +5 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/profile-list.d.ts +3 -0
- package/dist/cli/profile-list.d.ts.map +1 -0
- package/dist/cli/profile-list.js +14 -0
- package/dist/cli/profile-list.js.map +1 -0
- package/dist/cli/run.d.ts +22 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +95 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/trace-show.d.ts +3 -0
- package/dist/cli/trace-show.d.ts.map +1 -0
- package/dist/cli/trace-show.js +22 -0
- package/dist/cli/trace-show.js.map +1 -0
- package/dist/config/discovery.d.ts +3 -0
- package/dist/config/discovery.d.ts.map +1 -0
- package/dist/config/discovery.js +18 -0
- package/dist/config/discovery.js.map +1 -0
- package/dist/config/schema.d.ts +21 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +19 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/yuurei-config.d.ts +4 -0
- package/dist/config/yuurei-config.d.ts.map +1 -0
- package/dist/config/yuurei-config.js +10 -0
- package/dist/config/yuurei-config.js.map +1 -0
- package/dist/cost/noop.d.ts +7 -0
- package/dist/cost/noop.d.ts.map +1 -0
- package/dist/cost/noop.js +10 -0
- package/dist/cost/noop.js.map +1 -0
- package/dist/cost/types.d.ts +21 -0
- package/dist/cost/types.d.ts.map +1 -0
- package/dist/cost/types.js +2 -0
- package/dist/cost/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/index.js.map +1 -0
- package/dist/isolation/config-root.d.ts +9 -0
- package/dist/isolation/config-root.d.ts.map +1 -0
- package/dist/isolation/config-root.js +10 -0
- package/dist/isolation/config-root.js.map +1 -0
- package/dist/isolation/env.d.ts +2 -0
- package/dist/isolation/env.d.ts.map +1 -0
- package/dist/isolation/env.js +17 -0
- package/dist/isolation/env.js.map +1 -0
- package/dist/isolation/index.d.ts +9 -0
- package/dist/isolation/index.d.ts.map +1 -0
- package/dist/isolation/index.js +21 -0
- package/dist/isolation/index.js.map +1 -0
- package/dist/isolation/level0.d.ts +20 -0
- package/dist/isolation/level0.d.ts.map +1 -0
- package/dist/isolation/level0.js +51 -0
- package/dist/isolation/level0.js.map +1 -0
- package/dist/isolation/level1.d.ts +13 -0
- package/dist/isolation/level1.d.ts.map +1 -0
- package/dist/isolation/level1.js +58 -0
- package/dist/isolation/level1.js.map +1 -0
- package/dist/isolation/tempdir.d.ts +53 -0
- package/dist/isolation/tempdir.d.ts.map +1 -0
- package/dist/isolation/tempdir.js +87 -0
- package/dist/isolation/tempdir.js.map +1 -0
- package/dist/isolation/types.d.ts +37 -0
- package/dist/isolation/types.d.ts.map +1 -0
- package/dist/isolation/types.js +2 -0
- package/dist/isolation/types.js.map +1 -0
- package/dist/profile/loader.d.ts +9 -0
- package/dist/profile/loader.d.ts.map +1 -0
- package/dist/profile/loader.js +95 -0
- package/dist/profile/loader.js.map +1 -0
- package/dist/profile/manifest.d.ts +29 -0
- package/dist/profile/manifest.d.ts.map +1 -0
- package/dist/profile/manifest.js +28 -0
- package/dist/profile/manifest.js.map +1 -0
- package/dist/profile/types.d.ts +31 -0
- package/dist/profile/types.d.ts.map +1 -0
- package/dist/profile/types.js +2 -0
- package/dist/profile/types.js.map +1 -0
- package/dist/run/id.d.ts +7 -0
- package/dist/run/id.d.ts.map +1 -0
- package/dist/run/id.js +15 -0
- package/dist/run/id.js.map +1 -0
- package/dist/run/layout.d.ts +27 -0
- package/dist/run/layout.d.ts.map +1 -0
- package/dist/run/layout.js +45 -0
- package/dist/run/layout.js.map +1 -0
- package/dist/run/pipeline.d.ts +45 -0
- package/dist/run/pipeline.d.ts.map +1 -0
- package/dist/run/pipeline.js +246 -0
- package/dist/run/pipeline.js.map +1 -0
- package/dist/run/signals.d.ts +53 -0
- package/dist/run/signals.d.ts.map +1 -0
- package/dist/run/signals.js +74 -0
- package/dist/run/signals.js.map +1 -0
- package/dist/runtime/claude-code/args.d.ts +8 -0
- package/dist/runtime/claude-code/args.d.ts.map +1 -0
- package/dist/runtime/claude-code/args.js +13 -0
- package/dist/runtime/claude-code/args.js.map +1 -0
- package/dist/runtime/claude-code/index.d.ts +11 -0
- package/dist/runtime/claude-code/index.d.ts.map +1 -0
- package/dist/runtime/claude-code/index.js +167 -0
- package/dist/runtime/claude-code/index.js.map +1 -0
- package/dist/runtime/claude-code/paths.d.ts +3 -0
- package/dist/runtime/claude-code/paths.d.ts.map +1 -0
- package/dist/runtime/claude-code/paths.js +6 -0
- package/dist/runtime/claude-code/paths.js.map +1 -0
- package/dist/runtime/codex/args.d.ts +21 -0
- package/dist/runtime/codex/args.d.ts.map +1 -0
- package/dist/runtime/codex/args.js +35 -0
- package/dist/runtime/codex/args.js.map +1 -0
- package/dist/runtime/codex/index.d.ts +13 -0
- package/dist/runtime/codex/index.d.ts.map +1 -0
- package/dist/runtime/codex/index.js +299 -0
- package/dist/runtime/codex/index.js.map +1 -0
- package/dist/runtime/codex/paths.d.ts +3 -0
- package/dist/runtime/codex/paths.d.ts.map +1 -0
- package/dist/runtime/codex/paths.js +6 -0
- package/dist/runtime/codex/paths.js.map +1 -0
- package/dist/runtime/detect.d.ts +5 -0
- package/dist/runtime/detect.d.ts.map +1 -0
- package/dist/runtime/detect.js +42 -0
- package/dist/runtime/detect.js.map +1 -0
- package/dist/runtime/exec.d.ts +24 -0
- package/dist/runtime/exec.d.ts.map +1 -0
- package/dist/runtime/exec.js +132 -0
- package/dist/runtime/exec.js.map +1 -0
- package/dist/runtime/registry.d.ts +4 -0
- package/dist/runtime/registry.d.ts.map +1 -0
- package/dist/runtime/registry.js +23 -0
- package/dist/runtime/registry.js.map +1 -0
- package/dist/runtime/reserved-paths.d.ts +22 -0
- package/dist/runtime/reserved-paths.d.ts.map +1 -0
- package/dist/runtime/reserved-paths.js +30 -0
- package/dist/runtime/reserved-paths.js.map +1 -0
- package/dist/runtime/types.d.ts +105 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/trace/reader.d.ts +4 -0
- package/dist/trace/reader.d.ts.map +1 -0
- package/dist/trace/reader.js +9 -0
- package/dist/trace/reader.js.map +1 -0
- package/dist/trace/redact.d.ts +22 -0
- package/dist/trace/redact.d.ts.map +1 -0
- package/dist/trace/redact.js +124 -0
- package/dist/trace/redact.js.map +1 -0
- package/dist/trace/schema.d.ts +49 -0
- package/dist/trace/schema.d.ts.map +1 -0
- package/dist/trace/schema.js +53 -0
- package/dist/trace/schema.js.map +1 -0
- package/dist/trace/writer.d.ts +17 -0
- package/dist/trace/writer.d.ts.map +1 -0
- package/dist/trace/writer.js +45 -0
- package/dist/trace/writer.js.map +1 -0
- package/dist/util/fs.d.ts +17 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +61 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/hash.d.ts +3 -0
- package/dist/util/hash.d.ts.map +1 -0
- package/dist/util/hash.js +7 -0
- package/dist/util/hash.js.map +1 -0
- package/dist/util/json.d.ts +8 -0
- package/dist/util/json.d.ts.map +1 -0
- package/dist/util/json.js +24 -0
- package/dist/util/json.js.map +1 -0
- package/dist/util/logger.d.ts +8 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +16 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/version.d.ts +24 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +26 -0
- package/dist/version.js.map +1 -0
- package/docs/assets/yuurei-top.jpg +0 -0
- 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 @@
|
|
|
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 @@
|
|
|
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
|