@aeolus-fleet/trierarch 0.32.1 → 0.33.0
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/README.md +11 -11
- package/dist/adapters/caller-check.d.ts +14 -0
- package/dist/adapters/caller-check.js +30 -0
- package/dist/adapters/caller-check.js.map +1 -1
- package/dist/adapters/claude-code.d.ts +26 -6
- package/dist/adapters/claude-code.js +54 -33
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/clone-workspace.js +10 -1
- package/dist/adapters/clone-workspace.js.map +1 -1
- package/dist/adapters/codex.d.ts +47 -16
- package/dist/adapters/codex.js +82 -45
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/container.d.ts +4 -2
- package/dist/adapters/container.js +24 -17
- package/dist/adapters/container.js.map +1 -1
- package/dist/adapters/files.d.ts +6 -0
- package/dist/adapters/files.js +16 -0
- package/dist/adapters/files.js.map +1 -1
- package/dist/adapters/git-workspace.js +21 -4
- package/dist/adapters/git-workspace.js.map +1 -1
- package/dist/adapters/harnesses.d.ts +4 -3
- package/dist/adapters/harnesses.js +6 -7
- package/dist/adapters/harnesses.js.map +1 -1
- package/dist/adapters/hold-protocol.d.ts +50 -0
- package/dist/adapters/hold-protocol.js +58 -0
- package/dist/adapters/hold-protocol.js.map +1 -0
- package/dist/adapters/hold-runtime.d.ts +36 -0
- package/dist/adapters/hold-runtime.js +146 -0
- package/dist/adapters/hold-runtime.js.map +1 -0
- package/dist/adapters/hold.d.ts +41 -0
- package/dist/adapters/hold.js +310 -0
- package/dist/adapters/hold.js.map +1 -0
- package/dist/adapters/paths.d.ts +2 -0
- package/dist/adapters/paths.js +1 -0
- package/dist/adapters/paths.js.map +1 -1
- package/dist/adapters/plugin-update.js +2 -0
- package/dist/adapters/plugin-update.js.map +1 -1
- package/dist/adapters/runtime-adapters.d.ts +14 -1
- package/dist/adapters/runtime-adapters.js +14 -2
- package/dist/adapters/runtime-adapters.js.map +1 -1
- package/dist/adapters/stop-turn.d.ts +5 -5
- package/dist/adapters/stop-turn.js +6 -6
- package/dist/adapters/stop-turn.js.map +1 -1
- package/dist/adapters/tmux.d.ts +7 -30
- package/dist/adapters/tmux.js +9 -107
- package/dist/adapters/tmux.js.map +1 -1
- package/dist/bin/aeolus-trierarch.js +16 -7
- package/dist/bin/aeolus-trierarch.js.map +1 -1
- package/dist/cli/config-check.js +1 -1
- package/dist/cli/config-check.js.map +1 -1
- package/dist/cli/hold.d.ts +7 -0
- package/dist/cli/hold.js +60 -0
- package/dist/cli/hold.js.map +1 -0
- package/dist/cli/init.d.ts +4 -0
- package/dist/cli/init.js +17 -11
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +7 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/run.js +1 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/upgrade.d.ts +6 -1
- package/dist/cli/upgrade.js +10 -2
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/core/check-session-runtime.d.ts +5 -3
- package/dist/core/check-session-runtime.js +13 -14
- package/dist/core/check-session-runtime.js.map +1 -1
- package/dist/core/ports.d.ts +18 -12
- package/dist/core/reconciler.js +20 -5
- package/dist/core/reconciler.js.map +1 -1
- package/dist/core/remote-control.d.ts +15 -0
- package/dist/core/remote-control.js +30 -0
- package/dist/core/remote-control.js.map +1 -0
- package/package.json +1 -1
- package/dist/adapters/codex-screen.d.ts +0 -1
- package/dist/adapters/codex-screen.js +0 -19
- package/dist/adapters/codex-screen.js.map +0 -1
- package/dist/adapters/codex-session-log.d.ts +0 -8
- package/dist/adapters/codex-session-log.js +0 -45
- package/dist/adapters/codex-session-log.js.map +0 -1
|
@@ -15,12 +15,13 @@ interface CommandLines {
|
|
|
15
15
|
export declare function adapterFlagsOf(configuration: TrierarchConfiguration): Record<string, readonly AdapterFlag[]>;
|
|
16
16
|
/** The flags each configured harness's adapter calls risky, for the report (#326). */
|
|
17
17
|
export declare function riskyFlagsOf(configuration: TrierarchConfiguration): Record<string, readonly string[]>;
|
|
18
|
-
/** What a harness launches with the given flags, on a first start and a restart, for config check; undefined without an adapter. */
|
|
18
|
+
/** What a harness launches with the given flags, on a first start and a restart, headless or in a terminal, for config check; undefined without an adapter. */
|
|
19
19
|
export declare function commandLinesOf(at: {
|
|
20
20
|
harness: string;
|
|
21
21
|
flags: readonly string[];
|
|
22
22
|
files: HarnessFiles;
|
|
23
23
|
permissionModes: readonly string[] | undefined;
|
|
24
|
+
isHeadless: boolean;
|
|
24
25
|
}): Promise<CommandLines | undefined>;
|
|
25
26
|
/**
|
|
26
27
|
* The adapters of the harnesses the configuration offers, by name, each with
|
|
@@ -36,8 +37,8 @@ export declare function createHarnesses(at: {
|
|
|
36
37
|
managedSettings: string;
|
|
37
38
|
/** What detection last found, read at each launch (#517). */
|
|
38
39
|
detected: () => Detected;
|
|
39
|
-
sessions: Pick<SessionRuntime, 'start' | '
|
|
40
|
-
/** For sessions in containers: a home of each ship's own (decision 0039). */
|
|
40
|
+
sessions: Pick<SessionRuntime, 'start' | 'send' | 'interrupt' | 'events'>;
|
|
41
|
+
/** For sessions in containers: a home of each ship's own (decision 0039); without, sessions run headless on the host (headless H4). */
|
|
41
42
|
shipHomes?: {
|
|
42
43
|
homes: string;
|
|
43
44
|
managedSettings: string;
|
|
@@ -5,17 +5,16 @@ import { createCodexSetup } from './codex-setup.js';
|
|
|
5
5
|
import { CODEX_ADAPTER_FLAGS, CODEX_RISKY_FLAGS, codexCommandLine, createCodexHarness, WAKE_PROMPT as CODEX_WAKE_PROMPT } from './codex.js';
|
|
6
6
|
import { firstPromptOf, wakePromptOf } from '../core/no-terminal-questions.js';
|
|
7
7
|
import { findAeolusPlugin } from './plugin.js';
|
|
8
|
-
/** What the settings' first prompt
|
|
8
|
+
/** What the settings' first prompt stands for where config check shows a command line. */
|
|
9
9
|
const FIRST_PROMPT = '<first prompt>';
|
|
10
|
-
const SESSION_NAME = '[<repository or folder>] <ship>';
|
|
11
10
|
/** Per harness this trierarch has an adapter for: the flags it adds itself, its risky flags, and its command lines for given flags. */
|
|
12
11
|
const ADAPTERS = {
|
|
13
12
|
'claude-code': {
|
|
14
13
|
adapterFlags: CLAUDE_CODE_ADAPTER_FLAGS,
|
|
15
14
|
riskyFlags: CLAUDE_CODE_RISKY_FLAGS,
|
|
16
|
-
commandLines: async (flags, { files, permissionModes }) => {
|
|
15
|
+
commandLines: async (flags, { files, permissionModes, isHeadless }) => {
|
|
17
16
|
const defaultPermissionMode = await claudeCodeSettingsDefaultMode(files.claudeCode, { flags });
|
|
18
|
-
const commandLine = (isFirstStart) => claudeCodeCommandLine({ flags,
|
|
17
|
+
const commandLine = (isFirstStart) => claudeCodeCommandLine({ flags, isHeadless, prompt: isFirstStart ? firstPromptOf(FIRST_PROMPT) : wakePromptOf(CLAUDE_CODE_WAKE_PROMPT), isFirstStart, defaultPermissionMode, permissionModes });
|
|
19
18
|
return { firstStart: commandLine(true), restart: commandLine(false) };
|
|
20
19
|
},
|
|
21
20
|
},
|
|
@@ -36,9 +35,9 @@ export function adapterFlagsOf(configuration) {
|
|
|
36
35
|
export function riskyFlagsOf(configuration) {
|
|
37
36
|
return Object.fromEntries(Object.keys(configuration.harnesses).map((name) => [name, ADAPTERS[name]?.riskyFlags ?? []]));
|
|
38
37
|
}
|
|
39
|
-
/** What a harness launches with the given flags, on a first start and a restart, for config check; undefined without an adapter. */
|
|
38
|
+
/** What a harness launches with the given flags, on a first start and a restart, headless or in a terminal, for config check; undefined without an adapter. */
|
|
40
39
|
export async function commandLinesOf(at) {
|
|
41
|
-
return ADAPTERS[at.harness]?.commandLines(at.flags, { files: at.files, permissionModes: at.permissionModes });
|
|
40
|
+
return ADAPTERS[at.harness]?.commandLines(at.flags, { files: at.files, permissionModes: at.permissionModes, isHeadless: at.isHeadless });
|
|
42
41
|
}
|
|
43
42
|
/**
|
|
44
43
|
* The adapters of the harnesses the configuration offers, by name, each with
|
|
@@ -57,7 +56,7 @@ export async function createHarnesses(at) {
|
|
|
57
56
|
}
|
|
58
57
|
const plugin = await findAeolusPlugin({ homeDirectory, env, harness: name });
|
|
59
58
|
plugins[name] = plugin;
|
|
60
|
-
harnesses[name] = name === 'codex' ? createCodexHarness({ configuration, plugin, sessions, setup: createCodexSetup() }) : createClaudeCodeHarness({ configuration, plugin, sessions, permissionModes: () => detected()['claude-code']?.permissionModes, projects: join(env.CLAUDE_CONFIG_DIR ?? join(homeDirectory, '.claude'), 'projects'), setup: createClaudeCodeSetup({ homeDirectory, managedSettings, ...(env.CLAUDE_CONFIG_DIR !== undefined && { configDirectory: env.CLAUDE_CONFIG_DIR }) }), ...(shipHomes !== undefined && { shipHomes }) });
|
|
59
|
+
harnesses[name] = name === 'codex' ? createCodexHarness({ configuration, plugin, sessions, setup: createCodexSetup() }) : createClaudeCodeHarness({ configuration, plugin, sessions, isHeadless: shipHomes === undefined, permissionModes: () => detected()['claude-code']?.permissionModes, projects: join(env.CLAUDE_CONFIG_DIR ?? join(homeDirectory, '.claude'), 'projects'), setup: createClaudeCodeSetup({ homeDirectory, managedSettings, ...(env.CLAUDE_CONFIG_DIR !== undefined && { configDirectory: env.CLAUDE_CONFIG_DIR }) }), ...(shipHomes !== undefined && { shipHomes }) });
|
|
61
60
|
}
|
|
62
61
|
return { harnesses, plugins };
|
|
63
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harnesses.js","sourceRoot":"","sources":["../../src/adapters/harnesses.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,uBAAuB,EAAE,WAAW,IAAI,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC7M,OAAO,EAAE,qBAAqB,EAAwB,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE5I,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAE/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,
|
|
1
|
+
{"version":3,"file":"harnesses.js","sourceRoot":"","sources":["../../src/adapters/harnesses.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ShipId } from '@aeolus-fleet/common';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* How the trierarch talks to a session's hold (headless H8, H9): the hold's
|
|
5
|
+
* files in the session's own folder, `<sessions>/<ship id>/`, and its socket's
|
|
6
|
+
* calls, one JSON line each way. The trierarch speaks only its own version of
|
|
7
|
+
* this protocol: a hold of another version is replaced (H19).
|
|
8
|
+
*/
|
|
9
|
+
/** A session's hold files, in its folder under the sessions folder. */
|
|
10
|
+
export interface HoldFiles {
|
|
11
|
+
readonly folder: string;
|
|
12
|
+
readonly socket: string;
|
|
13
|
+
readonly pid: string;
|
|
14
|
+
/** Every event line the harness writes, appended. */
|
|
15
|
+
readonly events: string;
|
|
16
|
+
/** The harness's own session id, as its first event names it, so a start resumes it. */
|
|
17
|
+
readonly sessionId: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function holdFilesOf(sessions: string, shipId: ShipId): HoldFiles;
|
|
20
|
+
export declare const holdRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
21
|
+
call: z.ZodLiteral<"send">;
|
|
22
|
+
line: z.ZodString;
|
|
23
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
24
|
+
call: z.ZodLiteral<"interrupt">;
|
|
25
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
26
|
+
call: z.ZodLiteral<"state">;
|
|
27
|
+
}, z.core.$strip>], "call">;
|
|
28
|
+
export type HoldRequest = z.infer<typeof holdRequestSchema>;
|
|
29
|
+
export declare const holdAnswerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
30
|
+
isOk: z.ZodLiteral<true>;
|
|
31
|
+
state: z.ZodOptional<z.ZodObject<{
|
|
32
|
+
status: z.ZodLiteral<"running">;
|
|
33
|
+
version: z.ZodString;
|
|
34
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, z.core.$strip>>;
|
|
36
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
37
|
+
isOk: z.ZodLiteral<false>;
|
|
38
|
+
error: z.ZodString;
|
|
39
|
+
}, z.core.$strip>], "isOk">;
|
|
40
|
+
export type HoldAnswer = z.infer<typeof holdAnswerSchema>;
|
|
41
|
+
/** The answer a hold gives a caller that is not the trierarch. */
|
|
42
|
+
export declare const REFUSED = "refused: only the trierarch talks to a hold";
|
|
43
|
+
/** A line as JSON; undefined when it holds none. */
|
|
44
|
+
export declare function parsedLine(line: string): unknown;
|
|
45
|
+
/** Calls the hold at the socket and answers what it said; a hold that does not answer within the time, or ends the call unanswered, throws. */
|
|
46
|
+
export declare function callHold(call: {
|
|
47
|
+
socket: string;
|
|
48
|
+
request: HoldRequest;
|
|
49
|
+
timeoutMs: number;
|
|
50
|
+
}): Promise<HoldAnswer>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { connect } from 'node:net';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export function holdFilesOf(sessions, shipId) {
|
|
5
|
+
const folder = join(sessions, shipId);
|
|
6
|
+
return { folder, socket: join(folder, 'hold.sock'), pid: join(folder, 'hold.pid'), events: join(folder, 'events.jsonl'), sessionId: join(folder, 'session-id') };
|
|
7
|
+
}
|
|
8
|
+
export const holdRequestSchema = z.discriminatedUnion('call', [
|
|
9
|
+
z.object({ call: z.literal('send'), line: z.string() }),
|
|
10
|
+
z.object({ call: z.literal('interrupt') }),
|
|
11
|
+
z.object({ call: z.literal('state') }),
|
|
12
|
+
]);
|
|
13
|
+
export const holdAnswerSchema = z.discriminatedUnion('isOk', [
|
|
14
|
+
z.object({ isOk: z.literal(true), state: z.object({ status: z.literal('running'), version: z.string(), sessionId: z.string().optional() }).optional() }),
|
|
15
|
+
z.object({ isOk: z.literal(false), error: z.string() }),
|
|
16
|
+
]);
|
|
17
|
+
/** The answer a hold gives a caller that is not the trierarch. */
|
|
18
|
+
export const REFUSED = 'refused: only the trierarch talks to a hold';
|
|
19
|
+
/** A line as JSON; undefined when it holds none. */
|
|
20
|
+
export function parsedLine(line) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(line);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Calls the hold at the socket and answers what it said; a hold that does not answer within the time, or ends the call unanswered, throws. */
|
|
29
|
+
export function callHold(call) {
|
|
30
|
+
const { socket, request, timeoutMs } = call;
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const connection = connect(socket);
|
|
33
|
+
let received = '';
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
connection.destroy();
|
|
36
|
+
reject(new Error(`the hold at ${socket} did not answer ${request.call} within ${String(timeoutMs)} ms`));
|
|
37
|
+
}, timeoutMs);
|
|
38
|
+
connection.on('connect', () => connection.write(`${JSON.stringify(request)}\n`));
|
|
39
|
+
connection.on('data', (chunk) => {
|
|
40
|
+
received += chunk.toString('utf8');
|
|
41
|
+
});
|
|
42
|
+
connection.on('error', (error) => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
reject(error);
|
|
45
|
+
});
|
|
46
|
+
connection.on('close', () => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
const answer = holdAnswerSchema.safeParse(parsedLine(received.split('\n')[0] ?? ''));
|
|
49
|
+
if (answer.success) {
|
|
50
|
+
resolve(answer.data);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
reject(new Error(`the hold at ${socket} gave no answer to ${request.call}`));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=hold-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hold-protocol.js","sourceRoot":"","sources":["../../src/adapters/hold-protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SessionRuntime } from '../core/ports.js';
|
|
2
|
+
import type { SessionRoots } from './caller-check.js';
|
|
3
|
+
/** What a session's start goes through: a systemd scope of its own under a systemd unit, which sets INVOCATION_ID, so a restart or upgrade of the service ends no session (#479, #537). */
|
|
4
|
+
export declare function sessionsLaunch(at: {
|
|
5
|
+
platform: string;
|
|
6
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
7
|
+
}): readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* The host session runtime (headless H4, H8, H9): one hold per ship, the
|
|
10
|
+
* trierarch program run as `aeolus-trierarch hold`, which runs the harness as
|
|
11
|
+
* its child and holds its input and output. Under a systemd unit a hold starts
|
|
12
|
+
* in a scope of its own, so a restart or an upgrade of the service ends no
|
|
13
|
+
* session (#479, #537). A hold is nobody's child: the fleet socket takes it
|
|
14
|
+
* as its session's root (decision 0026), and a session's processes are never
|
|
15
|
+
* under the trierarch's.
|
|
16
|
+
*
|
|
17
|
+
* A hold that answers runs; one that started since the machine did and no
|
|
18
|
+
* longer answers has exited, kept so the exit is seen with its events; one
|
|
19
|
+
* from before the machine started is gone. A hold another version started is
|
|
20
|
+
* listed as such (H19), as is a session an older trierarch started in tmux,
|
|
21
|
+
* which a start stops before the ship's hold starts.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createHoldRuntime(options: {
|
|
24
|
+
/** The sessions folder: a folder per ship, with its hold's files. */
|
|
25
|
+
sessions: string;
|
|
26
|
+
/** The trierarch program, which `hold` and its words follow. */
|
|
27
|
+
program: readonly string[];
|
|
28
|
+
version: string;
|
|
29
|
+
platform: string;
|
|
30
|
+
/** What a hold's start goes through: a systemd scope of its own under a systemd unit (see sessionsLaunch). */
|
|
31
|
+
launch?: readonly string[];
|
|
32
|
+
/** The sessions an older trierarch started in tmux: listed and stopped, never started. */
|
|
33
|
+
legacy?: Pick<SessionRuntime, 'list' | 'stop'>;
|
|
34
|
+
/** When the machine started. */
|
|
35
|
+
bootedAt?: () => Date;
|
|
36
|
+
}): SessionRuntime & SessionRoots;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readdir, readFile, rm, stat } from 'node:fs/promises';
|
|
3
|
+
import { uptime } from 'node:os';
|
|
4
|
+
import { setTimeout as wait } from 'node:timers/promises';
|
|
5
|
+
import { idSchema } from '@aeolus-fleet/common';
|
|
6
|
+
import { INTERRUPT_WAIT_MS } from './hold.js';
|
|
7
|
+
import { callHold, holdFilesOf } from './hold-protocol.js';
|
|
8
|
+
/** How long a hold has to answer a call other than interrupt. */
|
|
9
|
+
const CALL_TIMEOUT_MS = 2000;
|
|
10
|
+
/** How long a hold has to answer its first call after its start. */
|
|
11
|
+
const START_WAIT_MS = 10_000;
|
|
12
|
+
/** How often a starting hold is asked whether it answers. */
|
|
13
|
+
const START_POLL_MS = 50;
|
|
14
|
+
/** How long a stopped hold has to end with its harness before it is killed. */
|
|
15
|
+
const STOP_WAIT_MS = 5000;
|
|
16
|
+
/** How many of the last event lines are read: the launch window's error event is among the last. */
|
|
17
|
+
const EVENT_LINES = 200;
|
|
18
|
+
/** Runs the command in the background and returns at once: its process is then nobody's child, so no session runs under the trierarch's own process. */
|
|
19
|
+
const DETACHED = '"$@" < /dev/null > /dev/null 2>&1 &';
|
|
20
|
+
/** What a session's start goes through: a systemd scope of its own under a systemd unit, which sets INVOCATION_ID, so a restart or upgrade of the service ends no session (#479, #537). */
|
|
21
|
+
export function sessionsLaunch(at) {
|
|
22
|
+
return at.platform === 'linux' && at.env.INVOCATION_ID !== undefined ? ['systemd-run', '--user', '--scope', '--quiet', '--collect', '--'] : [];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The host session runtime (headless H4, H8, H9): one hold per ship, the
|
|
26
|
+
* trierarch program run as `aeolus-trierarch hold`, which runs the harness as
|
|
27
|
+
* its child and holds its input and output. Under a systemd unit a hold starts
|
|
28
|
+
* in a scope of its own, so a restart or an upgrade of the service ends no
|
|
29
|
+
* session (#479, #537). A hold is nobody's child: the fleet socket takes it
|
|
30
|
+
* as its session's root (decision 0026), and a session's processes are never
|
|
31
|
+
* under the trierarch's.
|
|
32
|
+
*
|
|
33
|
+
* A hold that answers runs; one that started since the machine did and no
|
|
34
|
+
* longer answers has exited, kept so the exit is seen with its events; one
|
|
35
|
+
* from before the machine started is gone. A hold another version started is
|
|
36
|
+
* listed as such (H19), as is a session an older trierarch started in tmux,
|
|
37
|
+
* which a start stops before the ship's hold starts.
|
|
38
|
+
*/
|
|
39
|
+
export function createHoldRuntime(options) {
|
|
40
|
+
const { sessions, version } = options;
|
|
41
|
+
const bootedAt = options.bootedAt ?? (() => new Date(Date.now() - uptime() * 1000));
|
|
42
|
+
const filesOf = (shipId) => holdFilesOf(sessions, shipId);
|
|
43
|
+
/** The hold's pid, while its process runs. */
|
|
44
|
+
const runningPid = async (shipId) => {
|
|
45
|
+
const pid = Number(await readFile(filesOf(shipId).pid, 'utf8').catch(() => ''));
|
|
46
|
+
if (!Number.isInteger(pid) || pid <= 1) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
process.kill(pid, 0);
|
|
51
|
+
return pid;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const observed = async (shipId) => {
|
|
58
|
+
const files = filesOf(shipId);
|
|
59
|
+
const started = await stat(files.pid).catch(() => undefined);
|
|
60
|
+
if (started === undefined || started.mtime < bootedAt()) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const answer = await callHold({ socket: files.socket, request: { call: 'state' }, timeoutMs: CALL_TIMEOUT_MS }).catch(() => undefined);
|
|
64
|
+
if (answer?.isOk !== true || answer.state === undefined) {
|
|
65
|
+
return { shipId, status: 'exited' };
|
|
66
|
+
}
|
|
67
|
+
return { shipId, status: 'running', ...(answer.state.version !== version && { isOtherVersion: true }) };
|
|
68
|
+
};
|
|
69
|
+
const stop = async (shipId) => {
|
|
70
|
+
await options.legacy?.stop(shipId);
|
|
71
|
+
const pid = await runningPid(shipId);
|
|
72
|
+
if (pid !== undefined) {
|
|
73
|
+
process.kill(pid, 'SIGTERM');
|
|
74
|
+
const until = Date.now() + STOP_WAIT_MS;
|
|
75
|
+
while ((await runningPid(shipId)) !== undefined && Date.now() < until) {
|
|
76
|
+
await wait(START_POLL_MS);
|
|
77
|
+
}
|
|
78
|
+
if ((await runningPid(shipId)) !== undefined) {
|
|
79
|
+
process.kill(pid, 'SIGKILL');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
await rm(filesOf(shipId).folder, { recursive: true, force: true });
|
|
83
|
+
};
|
|
84
|
+
const must = async (shipId, call) => {
|
|
85
|
+
const { request } = call;
|
|
86
|
+
const answer = await callHold({ socket: filesOf(shipId).socket, ...call });
|
|
87
|
+
if (!answer.isOk) {
|
|
88
|
+
throw new Error(`the hold of ${shipId} refused ${request.call}: ${answer.error}`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
list: async () => {
|
|
93
|
+
const folders = await readdir(sessions).catch(() => []);
|
|
94
|
+
const shipIds = folders.flatMap((folder) => {
|
|
95
|
+
const shipId = idSchema('ship').safeParse(folder);
|
|
96
|
+
return shipId.success ? [shipId.data] : [];
|
|
97
|
+
});
|
|
98
|
+
const held = (await Promise.all(shipIds.map(observed))).flatMap((session) => (session === undefined ? [] : [session]));
|
|
99
|
+
const heldIds = new Set(held.map((session) => session.shipId));
|
|
100
|
+
const legacy = ((await options.legacy?.list()) ?? []).filter((session) => !heldIds.has(session.shipId)).map((session) => ({ ...session, isOtherVersion: true }));
|
|
101
|
+
return [...held, ...legacy];
|
|
102
|
+
},
|
|
103
|
+
stop,
|
|
104
|
+
start: async ({ shipId, folder, command, harness, env = {} }) => {
|
|
105
|
+
await stop(shipId);
|
|
106
|
+
const files = filesOf(shipId);
|
|
107
|
+
const hold = [...options.program, 'hold', shipId, '--sessions', sessions, '--folder', folder, '--harness', harness, ...Object.entries(env).flatMap(([variable, value]) => ['--env', `${variable}=${value}`]), '--', ...command];
|
|
108
|
+
const [program = 'sh', ...args] = [...(options.launch ?? []), 'sh', '-c', DETACHED, 'sh', ...hold];
|
|
109
|
+
const starter = spawn(program, args, { stdio: 'ignore', detached: true });
|
|
110
|
+
await new Promise((resolve, reject) => {
|
|
111
|
+
starter.on('error', reject);
|
|
112
|
+
starter.on('exit', () => {
|
|
113
|
+
resolve();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
const until = Date.now() + START_WAIT_MS;
|
|
117
|
+
while (Date.now() < until) {
|
|
118
|
+
if ((await callHold({ socket: files.socket, request: { call: 'state' }, timeoutMs: CALL_TIMEOUT_MS }).catch(() => undefined))?.isOk === true) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// A hold whose harness ended at once has ended too: its exit is seen in the next list.
|
|
122
|
+
if ((await stat(files.pid).catch(() => undefined)) !== undefined && (await runningPid(shipId)) === undefined) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await wait(START_POLL_MS);
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`the hold of ${shipId} did not answer within ${String(START_WAIT_MS)} ms of its start`);
|
|
128
|
+
},
|
|
129
|
+
// A Codex hold starts the line's turn once the last turn's process has ended, which it waits for as long as for an interrupted turn.
|
|
130
|
+
send: async ({ shipId, line }) => {
|
|
131
|
+
await must(shipId, { request: { call: 'send', line }, timeoutMs: INTERRUPT_WAIT_MS + CALL_TIMEOUT_MS });
|
|
132
|
+
},
|
|
133
|
+
interrupt: async (shipId) => {
|
|
134
|
+
await must(shipId, { request: { call: 'interrupt' }, timeoutMs: INTERRUPT_WAIT_MS + CALL_TIMEOUT_MS });
|
|
135
|
+
},
|
|
136
|
+
events: async (shipId) => {
|
|
137
|
+
const text = await readFile(filesOf(shipId).events, 'utf8').catch(() => '');
|
|
138
|
+
return text
|
|
139
|
+
.split('\n')
|
|
140
|
+
.filter((line) => line !== '')
|
|
141
|
+
.slice(-EVENT_LINES);
|
|
142
|
+
},
|
|
143
|
+
sessionRootOf: runningPid,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=hold-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hold-runtime.js","sourceRoot":"","sources":["../../src/adapters/hold-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,UAAU,IAAI,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,QAAQ,EAAe,MAAM,sBAAsB,CAAC;AAI7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AAE7E,iEAAiE;AACjE,MAAM,eAAe,GAAG,IAAI,CAAC;AAC7B,oEAAoE;AACpE,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,6DAA6D;AAC7D,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,+EAA+E;AAC/E,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,oGAAoG;AACpG,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,wJAAwJ;AACxJ,MAAM,QAAQ,GAAG,qCAAqC,CAAC;AAEvD,2LAA2L;AAC3L,MAAM,UAAU,cAAc,CAAC,EAA2E;IACxG,OAAO,EAAE,CAAC,QAAQ,KAAK,OAAO,IAAI,EAAE,CAAC,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACjJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAajC;IACC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACpF,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAElE,8CAA8C;IAC9C,MAAM,UAAU,GAAG,KAAK,EAAE,MAAc,EAA+B,EAAE;QACvE,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACvC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACrB,OAAO,GAAG,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAwC,EAAE;QAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC7D,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,GAAG,QAAQ,EAAE,EAAE,CAAC;YACxD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACvI,IAAI,MAAM,EAAE,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YACxD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QACtC,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC1G,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,KAAK,EAAE,MAAc,EAAiB,EAAE;QACnD,MAAM,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC;YACxC,OAAO,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;gBACtE,MAAM,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5B,CAAC;YACD,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC7C,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,KAAK,EAAE,MAAc,EAAE,IAAiD,EAAiB,EAAE;QACtG,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,YAAY,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QACpF,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACxD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;gBACzC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;gBAClD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,CAAC,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACvH,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/D,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACjK,OAAO,CAAC,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI;QACJ,KAAK,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE,EAAE;YAC9D,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;YACnB,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YAC9B,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,CAAC;YAChO,MAAM,CAAC,OAAO,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;YACnG,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1E,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;oBACtB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC;YACzC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;gBAC1B,IAAI,CAAC,MAAM,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;oBAC7I,OAAO;gBACT,CAAC;gBACD,uFAAuF;gBACvF,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;oBAC7G,OAAO;gBACT,CAAC;gBACD,MAAM,IAAI,CAAC,aAAa,CAAC,CAAC;YAC5B,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,eAAe,MAAM,0BAA0B,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC;QAC1G,CAAC;QACD,qIAAqI;QACrI,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE;YAC/B,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,iBAAiB,GAAG,eAAe,EAAE,CAAC,CAAC;QAC1G,CAAC;QACD,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAC1B,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,iBAAiB,GAAG,eAAe,EAAE,CAAC,CAAC;QACzG,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YACvB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5E,OAAO,IAAI;iBACR,KAAK,CAAC,IAAI,CAAC;iBACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;iBAC7B,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC;QACzB,CAAC;QACD,aAAa,EAAE,UAAU;KAC1B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Socket } from 'node:net';
|
|
2
|
+
import type { HoldFiles } from './hold-protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* The hold (headless H8 to H12): a small process per session, started outside
|
|
5
|
+
* the trierarch's service so it outlives it, the only holder of its harness's
|
|
6
|
+
* stdin and stdout. It starts the harness as its child, appends every line the
|
|
7
|
+
* harness writes to the session's events, keeps the harness's session id, and
|
|
8
|
+
* answers the trierarch alone on its socket: send a line, interrupt the turn,
|
|
9
|
+
* say its state. When the harness ends, the hold ends; when the hold is
|
|
10
|
+
* stopped, the harness ends with it. A hold that dies without the chance to
|
|
11
|
+
* stop it, as SIGKILL leaves it, still ends its harness: a guard, a process of
|
|
12
|
+
* its own, holds a pipe from the hold and ends each harness's group once that
|
|
13
|
+
* pipe closes, which the hold's death does.
|
|
14
|
+
*
|
|
15
|
+
* Claude Code's turn is stopped with the interrupt control request on its
|
|
16
|
+
* stdin, which keeps the process (H12, Thomas, 25 Oct: "Headless option 2").
|
|
17
|
+
* When Claude Code refuses it, SIGINT stops the turn and the process, and the
|
|
18
|
+
* hold starts it again resuming its session, so the trierarch sees no exit.
|
|
19
|
+
*
|
|
20
|
+
* Codex runs one `codex exec` per turn (H11): the start command is the first
|
|
21
|
+
* turn, and each line sent is the prompt of a `codex exec resume` of the
|
|
22
|
+
* thread the first named, started once the last turn's process has ended.
|
|
23
|
+
* Between turns no Codex process runs and the hold stays; its turn is stopped
|
|
24
|
+
* with SIGINT, which ends the process (H12).
|
|
25
|
+
*/
|
|
26
|
+
/** The longest the hold waits for an interrupted turn to end before the forced line goes in anyway (timings-and-limits.md). */
|
|
27
|
+
export declare const INTERRUPT_WAIT_MS = 10000;
|
|
28
|
+
export interface HoldOptions {
|
|
29
|
+
readonly files: HoldFiles;
|
|
30
|
+
readonly folder: string;
|
|
31
|
+
/** The harness the command runs: how its turn is stopped. */
|
|
32
|
+
readonly harness: string;
|
|
33
|
+
readonly command: readonly string[];
|
|
34
|
+
/** The harness's whole environment: nothing else of the hold's reaches it (H10). */
|
|
35
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
36
|
+
readonly version: string;
|
|
37
|
+
/** Whether the caller at the other end of a connection to the socket is the trierarch (H9). */
|
|
38
|
+
readonly mayCall: (connection: Socket) => Promise<boolean>;
|
|
39
|
+
}
|
|
40
|
+
/** Runs the hold until its harness ends; answers the harness's exit code. */
|
|
41
|
+
export declare function runHold(options: HoldOptions): Promise<number>;
|