@aeolus-fleet/trierarch 0.17.2 → 0.17.3

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 CHANGED
@@ -4,7 +4,7 @@ Keeps the ships on its wanted list crewed on the machine it runs on: it starts,
4
4
 
5
5
  ## Install
6
6
 
7
- On the machine that will run the sessions (macOS or Linux), with Node 26, `git`, `tmux`, Claude Code and the aeolus plugin for Claude Code installed:
7
+ On the machine that will run the sessions (macOS or Linux), with Node 26, `git`, `tmux`, and Claude Code with the aeolus plugin for Claude Code, Codex with the aeolus plugin for Codex, or both, installed. Codex must be on the `PATH` as `codex`:
8
8
 
9
9
  ```sh
10
10
  npm install --global @aeolus-fleet/trierarch
@@ -22,10 +22,11 @@ That gives the command `aeolus-trierarch`.
22
22
  It asks for what it needs, in a short flow:
23
23
  - the fleet URL, the ship id and the secret. The secret is not shown as you type it, so it stays out of your shell history and any transcript. It registers the ship, keeps its crew token in `~/.aeolus/trierarch/crew-token` (readable by you only) and writes the secret nowhere;
24
24
  - whether Claude Code launches with `--dangerously-skip-permissions` and with `--remote-control` (both no unless you say yes);
25
+ - where `codex` runs or Codex is configured already: whether to offer Codex, and whether it launches with `--dangerously-bypass-approvals-and-sandbox` (no unless you say yes). Every Codex session launches with `--no-daemon` whatever the flags, so stopping it stops its work;
25
26
  - the repositories it may make worktrees of, by name and path;
26
27
  - the caps: how many ships on its list, how many sessions at once.
27
28
 
28
- It writes `~/.aeolus/trierarch/config.json`, with `config.schema.json` beside it so an editor checks it. It answers Claude Code's one-time questions ahead, so a session nobody watches never waits on one: it trusts the worktree root in `~/.claude.json`, which covers every worktree under it, and each configured folder (on every run, so a folder added since is trusted too), and, when the sessions skip permissions, offers to accept bypass permissions mode in `~/.claude/settings.json`. Then it offers to install and start the service: a launchd agent on macOS (`~/Library/LaunchAgents/dev.aeolus-fleet.trierarch.plist`), a systemd user unit on Linux (`aeolus-trierarch.service`), started at login and again whenever it stops.
29
+ It writes `~/.aeolus/trierarch/config.json`, with `config.schema.json` beside it so an editor checks it. When the configuration offers Codex, it answers Codex's one-time questions ahead too, through `codex app-server` as Codex's own dialogs do: it trusts each configured repository and folder in `~/.codex/config.toml` (a repository covers its worktrees), and trusts the aeolus plugin's hooks, without which a Codex session marks no turn and is never woken. A plugin update changes the hooks: run `aeolus-trierarch init` again after updating the aeolus plugin for Codex. It answers Claude Code's one-time questions ahead, so a session nobody watches never waits on one: it trusts the worktree root in `~/.claude.json`, which covers every worktree under it, and each configured folder (on every run, so a folder added since is trusted too), and, when the sessions skip permissions, offers to accept bypass permissions mode in `~/.claude/settings.json`. Then it offers to install and start the service: a launchd agent on macOS (`~/Library/LaunchAgents/dev.aeolus-fleet.trierarch.plist`), a systemd user unit on Linux (`aeolus-trierarch.service`), started at login and again whenever it stops.
29
30
 
30
31
  Without a terminal, pass everything as flags and take every default with `--yes`: `aeolus-trierarch init --fleet-url https://fleet.example.com --ship-id shp_... --secret aeolus_sk_v1_... --yes`. Prefer the prompt: a secret in a flag lands in your shell history.
31
32
 
@@ -65,6 +66,12 @@ To stop the trierarch: `aeolus-trierarch stop`, or `aeolus-trierarch uninstall`
65
66
  "options": {
66
67
  "model": { "values": { "opus": ["--model", "claude-opus-5-5"], "sonnet": ["--model", "claude-sonnet-5-5"] }, "default": "opus" }
67
68
  }
69
+ },
70
+ "codex": {
71
+ "flags": ["--dangerously-bypass-approvals-and-sandbox"],
72
+ "options": {
73
+ "model": { "values": { "sol": ["-m", "gpt-5.6-sol"] }, "default": "sol" }
74
+ }
68
75
  }
69
76
  }
70
77
  }
@@ -73,11 +80,11 @@ To stop the trierarch: `aeolus-trierarch stop`, or `aeolus-trierarch uninstall`
73
80
  - **caps**: how many ships it keeps on its list, and how many sessions run at once.
74
81
  - **repositories**: by name. A want for a worktree of one gets a git worktree under `~/.aeolus/trierarch/worktrees/<repository>/<ship>`, detached at the want's ref or the repository's HEAD. It fetches the repository first, and a ref `origin` has a branch for checks out that remote branch (`origin/<ref>`), so a ship starts from current code; when the fetch fails it uses what the repository has. `worktreeRoot` moves that root.
75
82
  - **folders**: by name, used as they are, one ship per folder, never removed.
76
- - **harnesses**: the flags every launch gets, and named options. A want picks option values by name; it never adds a flag, and messages never carry paths or flags. There is no policy on which flags: put `--dangerously-skip-permissions` in `flags` if you want it. The trierarch never adds a flag by itself. With `--remote-control` and no name after it, each session is named `[<repository or folder>] <ship>`, such as `[aeolus-fleet] trial-1`, so it is easy to find among your remote-control sessions.
83
+ - **harnesses**: `claude-code`, `codex` or both: the flags every launch gets, and named options. A want picks option values by name; it never adds a flag, and messages never carry paths or flags. There is no policy on which flags: put `--dangerously-skip-permissions` in `flags` if you want it. The trierarch never adds a flag by itself. With `--remote-control` and no name after it, each session is named `[<repository or folder>] <ship>`, such as `[aeolus-fleet] trial-1`, so it is easy to find among your remote-control sessions.
77
84
 
78
- `aeolus-trierarch init` writes caps, repositories and Claude Code's flags; edit the file for folders, options and `worktreeRoot`, then `aeolus-trierarch config check` and `aeolus-trierarch restart`.
85
+ `aeolus-trierarch init` writes caps, repositories, and Claude Code's and Codex's flags; edit the file for folders, options and `worktreeRoot`, then `aeolus-trierarch config check` and `aeolus-trierarch restart`.
79
86
 
80
- The trierarch finds the aeolus plugin in Claude Code's plugin cache (the newest version). Set `AEOLUS_PLUGIN_ROOT` and `AEOLUS_PLUGIN_DATA` to point it elsewhere. `aeolus-trierarch install` carries these, `AEOLUS_TRIERARCH_CONFIG` and `PATH` into the service as they are set where it runs.
87
+ The trierarch finds the aeolus plugin in each offered harness's plugin cache (the newest version): Claude Code's under `~/.claude/plugins`, Codex's under `~/.codex/plugins` (or `$CODEX_HOME/plugins`). Set `AEOLUS_PLUGIN_ROOT` and `AEOLUS_PLUGIN_DATA` (Claude Code), or `AEOLUS_CODEX_PLUGIN_ROOT` and `AEOLUS_CODEX_PLUGIN_DATA` (Codex), to point it elsewhere. `aeolus-trierarch install` carries these, `CODEX_HOME`, `AEOLUS_TRIERARCH_CONFIG` and `PATH` into the service as they are set where it runs.
81
88
 
82
89
  ## What is here
83
90
 
@@ -90,7 +97,7 @@ The trierarch finds the aeolus plugin in Claude Code's plugin cache (the newest
90
97
  - the fleet over REST;
91
98
  - tmux, on its own server (`tmux -L aeolus-trierarch`, sessions named `trierarch-<ship id>` and kept on exit). Attach to one with `tmux -L aeolus-trierarch attach -t trierarch-<ship id>`;
92
99
  - git worktrees and folders;
93
- - Claude Code, through the aeolus plugin's `aeolus-identity.sh`;
100
+ - Claude Code and Codex, through the aeolus plugin's `aeolus-identity.sh`. A Codex session starts with `codex <prompt>`, always with `--no-daemon`, comes back with `codex resume --last`, and is woken by typing `$aeolus-wake`;
94
101
  - the JSON state store.
95
102
  - **The command** (`src/cli`): `init`, `status`, `list`, `logs`, `config check`, `start`, `stop`, `restart`, `install`, `uninstall`, `run`.
96
- - **Adapters for the setup**: the service (launchd or systemd), and Claude Code's own files for its one-time questions.
103
+ - **Adapters for the setup**: the service (launchd or systemd), Claude Code's own files and Codex's app server for their one-time questions.
@@ -1,5 +1,6 @@
1
1
  import type { TrierarchConfiguration } from '@aeolus-fleet/common';
2
2
  import type { HarnessPort } from '../core/ports.js';
3
+ import { type AeolusPlugin } from './plugin-identity.js';
3
4
  import type { Tmux } from './tmux.js';
4
5
  /**
5
6
  * Claude Code as a harness (docs/architecture.md, "First adapters"): `claude`
@@ -8,11 +9,6 @@ import type { Tmux } from './tmux.js';
8
9
  * plugin's own `aeolus-identity.sh`, never a copy of how the plugin names its
9
10
  * files.
10
11
  */
11
- /** The aeolus plugin as Claude Code installed it: its scripts and its data folder. */
12
- export interface AeolusPlugin {
13
- readonly root: string;
14
- readonly data: string;
15
- }
16
12
  export declare const WAKE_PROMPT = "/aeolus:wake";
17
13
  export declare function createClaudeCodeHarness(options: {
18
14
  configuration: TrierarchConfiguration;
@@ -1,6 +1,12 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import { effectiveFlags } from './flags.js';
3
- import { runCommand } from './run-command.js';
2
+ import { createPluginIdentity } from './plugin-identity.js';
3
+ /**
4
+ * Claude Code as a harness (docs/architecture.md, "First adapters"): `claude`
5
+ * in the folder, `--continue` on a restart, `/aeolus:wake` typed to wake an
6
+ * idle session. A session's identity is written and read through the aeolus
7
+ * plugin's own `aeolus-identity.sh`, never a copy of how the plugin names its
8
+ * files.
9
+ */
4
10
  export const WAKE_PROMPT = '/aeolus:wake';
5
11
  const REMOTE_CONTROL = '--remote-control';
6
12
  /**
@@ -19,31 +25,8 @@ function namedRemoteControl(flags, name) {
19
25
  }
20
26
  export function createClaudeCodeHarness(options) {
21
27
  const { configuration, plugin, sessions } = options;
22
- const identity = async (folder, args) => runCommand('bash', { args: [`${plugin.root}/scripts/aeolus-identity.sh`, ...args], env: { AEOLUS_FOLDER: folder, AEOLUS_DATA: plugin.data } });
23
28
  return {
24
- prepareIdentity: async ({ folder, identity: crew }) => {
25
- const written = await identity(folder, [
26
- 'write',
27
- '--wake-by',
28
- 'trierarch',
29
- crew.fleetUrl,
30
- crew.shipId,
31
- crew.shipName,
32
- crew.crewToken,
33
- ...(crew.squadron === undefined ? [] : [crew.squadron]),
34
- ]);
35
- if (written.status !== 0) {
36
- throw new Error(`aeolus-identity.sh write failed: ${written.stderr.trim()}`);
37
- }
38
- },
39
- crewTokenOf: async (folder) => {
40
- const path = (await identity(folder, ['path'])).stdout.trim();
41
- const text = await readFile(path, 'utf8').catch(() => '');
42
- return /^crewToken=(.+)$/m.exec(text)?.[1];
43
- },
44
- removeIdentity: async (folder) => {
45
- await identity(folder, ['delete']);
46
- },
29
+ ...createPluginIdentity(plugin),
47
30
  launch: async ({ shipId, shipName, folder, workspace, harness, options: picked, isFirstStart, firstPrompt }) => {
48
31
  const settings = configuration.harnesses[harness];
49
32
  if (settings === undefined) {
@@ -55,11 +38,6 @@ export function createClaudeCodeHarness(options) {
55
38
  const command = [options.command ?? 'claude', prompt, ...flags, ...(isFirstStart ? [] : ['--continue'])];
56
39
  await sessions.start({ shipId, folder, command });
57
40
  },
58
- turnOf: async (folder) => {
59
- const turn = await identity(folder, ['turn']);
60
- const [state] = turn.stdout.split(' ');
61
- return turn.status === 0 && (state === 'busy' || state === 'idle') ? state : 'unknown';
62
- },
63
41
  wake: async ({ shipId }) => {
64
42
  await sessions.type({ shipId, text: WAKE_PROMPT });
65
43
  },
@@ -1 +1 @@
1
- {"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/adapters/claude-code.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/adapters/claude-code.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAqB,MAAM,sBAAsB,CAAC;AAG/E;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC;AAE1C,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAE1C;;;;;GAKG;AACH,SAAS,kBAAkB,CAAC,KAAwB,EAAE,IAAY;IAChE,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3B,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC/D,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,OAMvC;IACC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAEpD,OAAO;QACL,GAAG,oBAAoB,CAAC,MAAM,CAAC;QAC/B,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,EAAE;YAC7G,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAClD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,EAAE,CAAC,CAAC;YACjE,CAAC;YACD,MAAM,MAAM,GAAG,YAAY,IAAI,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC;YACrF,MAAM,KAAK,GAAG,kBAAkB,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,IAAI,SAAS,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC,CAAC;YAC7J,0GAA0G;YAC1G,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,QAAQ,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACzG,MAAM,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;YACzB,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACrD,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Codex's one-time questions, answered ahead so a session nobody watches never
3
+ * waits on one. Codex asks whether to trust a folder on its first start there;
4
+ * trusting a repository covers its worktrees, but trusting a parent folder
5
+ * covers no repository under it, so each configured repository and folder is
6
+ * trusted. It asks to review hooks that are new or changed, and runs none it
7
+ * does not trust, so the aeolus plugin's hooks (the turn marker among them)
8
+ * are trusted too. Both answers go through Codex's own app server, as its
9
+ * dialogs give them: the hash a hook is trusted by is Codex's to compute.
10
+ */
11
+ export interface CodexSetup {
12
+ trust(folders: readonly string[]): Promise<void>;
13
+ /** Trusts the aeolus plugin's hooks that are new or changed, as Codex sees them from the folder: their keys. */
14
+ trustAeolusHooks(folder: string): Promise<readonly string[]>;
15
+ }
16
+ export declare function createCodexSetup(options?: {
17
+ command?: string;
18
+ }): CodexSetup;
@@ -0,0 +1,106 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createInterface } from 'node:readline';
3
+ import { z } from 'zod';
4
+ import { runningVersion } from './version.js';
5
+ const AEOLUS_PLUGIN_PREFIX = 'aeolus@';
6
+ /** How long the app server may take to answer before the setup gives up. */
7
+ const ANSWER_TIMEOUT_MS = 30_000;
8
+ const answerSchema = z.object({
9
+ id: z.number(),
10
+ result: z.unknown().optional(),
11
+ error: z.object({ message: z.string() }).optional(),
12
+ });
13
+ const hooksListSchema = z.object({
14
+ data: z.array(z.object({
15
+ hooks: z.array(z.object({ key: z.string(), pluginId: z.string().nullish(), currentHash: z.string(), trustStatus: z.string() })),
16
+ })),
17
+ });
18
+ /** A line the app server printed, as JSON; anything else is no answer. */
19
+ function parseJson(line) {
20
+ try {
21
+ return JSON.parse(line);
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
27
+ /**
28
+ * Runs `codex app-server` for one conversation: introduces itself, lets `talk`
29
+ * send its requests, one answer each, and ends the server after.
30
+ */
31
+ async function withAppServer(command, talk) {
32
+ const child = spawn(command, ['app-server'], { stdio: ['pipe', 'pipe', 'ignore'] });
33
+ const failed = new Promise((_resolve, reject) => {
34
+ child.on('error', (error) => {
35
+ reject(new Error(`Codex is not installed, or ${command} does not run: ${error.message}`));
36
+ });
37
+ child.on('exit', (code) => {
38
+ reject(new Error(`codex app-server ended before it answered (exit ${String(code)})`));
39
+ });
40
+ });
41
+ const waiting = new Map();
42
+ createInterface({ input: child.stdout }).on('line', (line) => {
43
+ const parsed = answerSchema.safeParse(parseJson(line));
44
+ const waiter = parsed.success ? waiting.get(parsed.data.id) : undefined;
45
+ if (!parsed.success || waiter === undefined) {
46
+ return;
47
+ }
48
+ waiting.delete(parsed.data.id);
49
+ if (parsed.data.error === undefined) {
50
+ waiter.resolve(parsed.data.result);
51
+ }
52
+ else {
53
+ waiter.reject(new Error(`${waiter.method}: ${parsed.data.error.message}`));
54
+ }
55
+ });
56
+ let nextId = 0;
57
+ const send = (message) => {
58
+ child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', ...message })}\n`);
59
+ };
60
+ const request = async (method, params) => {
61
+ nextId += 1;
62
+ const id = nextId;
63
+ const answered = new Promise((resolve, reject) => {
64
+ waiting.set(id, { resolve, reject, method });
65
+ });
66
+ send({ id, method, params });
67
+ const timeout = new Promise((_resolve, reject) => {
68
+ setTimeout(() => {
69
+ reject(new Error(`codex app-server did not answer ${method}`));
70
+ }, ANSWER_TIMEOUT_MS).unref();
71
+ });
72
+ return Promise.race([answered, failed, timeout]);
73
+ };
74
+ try {
75
+ await request('initialize', { clientInfo: { name: 'aeolus-trierarch', version: runningVersion() } });
76
+ send({ method: 'initialized' });
77
+ return await talk(request);
78
+ }
79
+ finally {
80
+ child.removeAllListeners('exit');
81
+ failed.catch(() => undefined);
82
+ child.kill();
83
+ }
84
+ }
85
+ function upsert(keyPath, value) {
86
+ return { edits: [{ keyPath, value, mergeStrategy: 'upsert' }], reloadUserConfig: true };
87
+ }
88
+ export function createCodexSetup(options = {}) {
89
+ const command = options.command ?? 'codex';
90
+ return {
91
+ trust: async (folders) => {
92
+ await withAppServer(command, (request) => request('config/batchWrite', upsert('projects', Object.fromEntries(folders.map((folder) => [folder, { trust_level: 'trusted' }])))));
93
+ },
94
+ trustAeolusHooks: async (folder) => withAppServer(command, async (request) => {
95
+ const listed = hooksListSchema.parse(await request('hooks/list', { cwds: [folder] }));
96
+ const untrusted = listed.data
97
+ .flatMap((entry) => entry.hooks)
98
+ .filter((hook) => hook.pluginId?.startsWith(AEOLUS_PLUGIN_PREFIX) === true && (hook.trustStatus === 'untrusted' || hook.trustStatus === 'modified'));
99
+ if (untrusted.length > 0) {
100
+ await request('config/batchWrite', upsert('hooks.state', Object.fromEntries(untrusted.map((hook) => [hook.key, { trusted_hash: hook.currentHash }]))));
101
+ }
102
+ return untrusted.map((hook) => hook.key);
103
+ }),
104
+ };
105
+ }
106
+ //# sourceMappingURL=codex-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-setup.js","sourceRoot":"","sources":["../../src/adapters/codex-setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { TrierarchConfiguration } from '@aeolus-fleet/common';
2
+ import type { HarnessPort } from '../core/ports.js';
3
+ import { type AeolusPlugin } from './plugin-identity.js';
4
+ import type { Tmux } from './tmux.js';
5
+ /**
6
+ * Codex as a harness (docs/architecture.md, "First adapters"): `codex` in the
7
+ * folder, `codex resume --last` on a restart (the folder's last conversation;
8
+ * one ship per folder), always with `--no-daemon`, and `$aeolus-wake` typed
9
+ * to wake an idle session. The identity and the turn marker go through the
10
+ * aeolus plugin for Codex, in Codex's own plugin data folder.
11
+ */
12
+ export declare const WAKE_PROMPT = "$aeolus-wake";
13
+ /**
14
+ * How long a typed wake waits before Enter. Codex takes an Enter that comes
15
+ * right after fast typing as part of a paste, a newline, and submits nothing.
16
+ */
17
+ export declare const CODEX_TYPING_SETTLE_MS = 1000;
18
+ export declare function createCodexHarness(options: {
19
+ configuration: TrierarchConfiguration;
20
+ plugin: AeolusPlugin;
21
+ sessions: Pick<Tmux, 'start' | 'type'>;
22
+ /** The program to run; `codex` unless a test runs another. */
23
+ command?: string;
24
+ }): HarnessPort;
@@ -0,0 +1,43 @@
1
+ import { effectiveFlags } from './flags.js';
2
+ import { createPluginIdentity } from './plugin-identity.js';
3
+ /**
4
+ * Codex as a harness (docs/architecture.md, "First adapters"): `codex` in the
5
+ * folder, `codex resume --last` on a restart (the folder's last conversation;
6
+ * one ship per folder), always with `--no-daemon`, and `$aeolus-wake` typed
7
+ * to wake an idle session. The identity and the turn marker go through the
8
+ * aeolus plugin for Codex, in Codex's own plugin data folder.
9
+ */
10
+ export const WAKE_PROMPT = '$aeolus-wake';
11
+ /**
12
+ * How long a typed wake waits before Enter. Codex takes an Enter that comes
13
+ * right after fast typing as part of a paste, a newline, and submits nothing.
14
+ */
15
+ export const CODEX_TYPING_SETTLE_MS = 1000;
16
+ /**
17
+ * Added to every launch, as mechanism: without it Codex runs the session's
18
+ * work in its shared app-server daemon, which keeps a turn running after the
19
+ * pane is gone, so a release or a crash would stop the pane but not the work.
20
+ */
21
+ const NO_DAEMON = '--no-daemon';
22
+ export function createCodexHarness(options) {
23
+ const { configuration, plugin, sessions } = options;
24
+ return {
25
+ ...createPluginIdentity(plugin),
26
+ launch: async ({ shipId, folder, harness, options: picked, isFirstStart, firstPrompt }) => {
27
+ const settings = configuration.harnesses[harness];
28
+ if (settings === undefined) {
29
+ throw new Error(`The configuration has no harness ${harness}`);
30
+ }
31
+ const prompt = isFirstStart && firstPrompt !== undefined ? firstPrompt : WAKE_PROMPT;
32
+ const program = options.command ?? 'codex';
33
+ const command = isFirstStart ? [program, prompt] : [program, 'resume', '--last', prompt];
34
+ const flags = effectiveFlags(settings, picked).filter((flag) => flag !== NO_DAEMON);
35
+ await sessions.start({ shipId, folder, command: [...command, ...flags, NO_DAEMON] });
36
+ },
37
+ wake: async ({ shipId }) => {
38
+ // The space closes Codex's skill picker, which would otherwise take the Enter.
39
+ await sessions.type({ shipId, text: `${WAKE_PROMPT} `, settleMs: CODEX_TYPING_SETTLE_MS });
40
+ },
41
+ };
42
+ }
43
+ //# sourceMappingURL=codex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/adapters/codex.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAqB,MAAM,sBAAsB,CAAC;AAG/E;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC;AAE1C;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAE3C;;;;GAIG;AACH,MAAM,SAAS,GAAG,aAAa,CAAC;AAEhC,MAAM,UAAU,kBAAkB,CAAC,OAMlC;IACC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAEpD,OAAO;QACL,GAAG,oBAAoB,CAAC,MAAM,CAAC;QAC/B,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,EAAE;YACxF,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAClD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,EAAE,CAAC,CAAC;YACjE,CAAC;YACD,MAAM,MAAM,GAAG,YAAY,IAAI,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC;YACrF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC;YAC3C,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;YACzF,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;YACpF,MAAM,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;YACzB,+EAA+E;YAC/E,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,WAAW,GAAG,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC,CAAC;QAC7F,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { TrierarchConfiguration } from '@aeolus-fleet/common';
2
+ import type { HarnessPort } from '../core/ports.js';
3
+ import type { AeolusPlugin } from './plugin-identity.js';
4
+ import type { Tmux } from './tmux.js';
5
+ /**
6
+ * The adapters of the harnesses the configuration offers, by name, each with
7
+ * the aeolus plugin its harness installed. A plugin is looked for only for a
8
+ * harness on offer, so a machine without Codex needs no Codex plugin. A
9
+ * configured harness this trierarch has no adapter for is refused at start,
10
+ * so no want for it is ever taken.
11
+ */
12
+ export declare function createHarnesses(at: {
13
+ configuration: TrierarchConfiguration;
14
+ homeDirectory: string;
15
+ env: Readonly<Record<string, string | undefined>>;
16
+ sessions: Pick<Tmux, 'start' | 'type'>;
17
+ }): Promise<{
18
+ harnesses: Record<string, HarnessPort>;
19
+ plugins: Record<string, AeolusPlugin>;
20
+ }>;
@@ -0,0 +1,25 @@
1
+ import { createClaudeCodeHarness } from './claude-code.js';
2
+ import { createCodexHarness } from './codex.js';
3
+ import { findAeolusPlugin } from './plugin.js';
4
+ /**
5
+ * The adapters of the harnesses the configuration offers, by name, each with
6
+ * the aeolus plugin its harness installed. A plugin is looked for only for a
7
+ * harness on offer, so a machine without Codex needs no Codex plugin. A
8
+ * configured harness this trierarch has no adapter for is refused at start,
9
+ * so no want for it is ever taken.
10
+ */
11
+ export async function createHarnesses(at) {
12
+ const { configuration, homeDirectory, env, sessions } = at;
13
+ const harnesses = {};
14
+ const plugins = {};
15
+ for (const name of Object.keys(configuration.harnesses)) {
16
+ if (name !== 'claude-code' && name !== 'codex') {
17
+ throw new Error(`This trierarch has no adapter for the harness ${name}: it offers claude-code and codex`);
18
+ }
19
+ const plugin = await findAeolusPlugin({ homeDirectory, env, harness: name });
20
+ plugins[name] = plugin;
21
+ harnesses[name] = name === 'codex' ? createCodexHarness({ configuration, plugin, sessions }) : createClaudeCodeHarness({ configuration, plugin, sessions });
22
+ }
23
+ return { harnesses, plugins };
24
+ }
25
+ //# sourceMappingURL=harnesses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harnesses.js","sourceRoot":"","sources":["../../src/adapters/harnesses.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAKrC;IACC,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;IAC3D,MAAM,SAAS,GAAgC,EAAE,CAAC;IAClD,MAAM,OAAO,GAAiC,EAAE,CAAC;IACjD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;QACxD,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,iDAAiD,IAAI,mCAAmC,CAAC,CAAC;QAC5G,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;QACvB,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9J,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { HarnessPort } from '../core/ports.js';
2
+ /** The aeolus plugin as a harness installed it: its scripts and its data folder. */
3
+ export interface AeolusPlugin {
4
+ readonly root: string;
5
+ readonly data: string;
6
+ }
7
+ /**
8
+ * A session's identity and turn, through the aeolus plugin's own
9
+ * `aeolus-identity.sh`, never a copy of how the plugin names its files. The
10
+ * same for every harness; each keeps them in its own plugin's data folder.
11
+ */
12
+ export declare function createPluginIdentity(plugin: AeolusPlugin): Pick<HarnessPort, 'prepareIdentity' | 'crewTokenOf' | 'removeIdentity' | 'turnOf'>;
@@ -0,0 +1,41 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { runCommand } from './run-command.js';
3
+ /**
4
+ * A session's identity and turn, through the aeolus plugin's own
5
+ * `aeolus-identity.sh`, never a copy of how the plugin names its files. The
6
+ * same for every harness; each keeps them in its own plugin's data folder.
7
+ */
8
+ export function createPluginIdentity(plugin) {
9
+ const identity = async (folder, args) => runCommand('bash', { args: [`${plugin.root}/scripts/aeolus-identity.sh`, ...args], env: { AEOLUS_FOLDER: folder, AEOLUS_DATA: plugin.data } });
10
+ return {
11
+ prepareIdentity: async ({ folder, identity: crew }) => {
12
+ const written = await identity(folder, [
13
+ 'write',
14
+ '--wake-by',
15
+ 'trierarch',
16
+ crew.fleetUrl,
17
+ crew.shipId,
18
+ crew.shipName,
19
+ crew.crewToken,
20
+ ...(crew.squadron === undefined ? [] : [crew.squadron]),
21
+ ]);
22
+ if (written.status !== 0) {
23
+ throw new Error(`aeolus-identity.sh write failed: ${written.stderr.trim()}`);
24
+ }
25
+ },
26
+ crewTokenOf: async (folder) => {
27
+ const path = (await identity(folder, ['path'])).stdout.trim();
28
+ const text = await readFile(path, 'utf8').catch(() => '');
29
+ return /^crewToken=(.+)$/m.exec(text)?.[1];
30
+ },
31
+ removeIdentity: async (folder) => {
32
+ await identity(folder, ['delete']);
33
+ },
34
+ turnOf: async (folder) => {
35
+ const turn = await identity(folder, ['turn']);
36
+ const [state] = turn.stdout.split(' ');
37
+ return turn.status === 0 && (state === 'busy' || state === 'idle') ? state : 'unknown';
38
+ },
39
+ };
40
+ }
41
+ //# sourceMappingURL=plugin-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-identity.js","sourceRoot":"","sources":["../../src/adapters/plugin-identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQ9C;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAE,IAAuB,EAA+D,EAAE,CAC9H,UAAU,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,6BAA6B,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAEjJ,OAAO;QACL,eAAe,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE;YACpD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE;gBACrC,OAAO;gBACP,WAAW;gBACX,WAAW;gBACX,IAAI,CAAC,QAAQ;gBACb,IAAI,CAAC,MAAM;gBACX,IAAI,CAAC,QAAQ;gBACb,IAAI,CAAC,SAAS;gBACd,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;aACxD,CAAC,CAAC;YACH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;QACD,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC9D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC1D,OAAO,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAC/B,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACrC,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAiB,EAAE;YACtC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACvC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1,10 +1,15 @@
1
- import type { AeolusPlugin } from './claude-code.js';
1
+ import type { AeolusPlugin } from './plugin-identity.js';
2
+ /** The harnesses that install the aeolus plugin, each in its own plugin folders. */
3
+ export type PluginHarness = 'claude-code' | 'codex';
2
4
  /**
3
- * Where Claude Code installed the aeolus plugin: AEOLUS_PLUGIN_ROOT and
4
- * AEOLUS_PLUGIN_DATA when set, else the newest version in Claude Code's
5
- * plugin cache and the plugin's data folder.
5
+ * Where a harness installed the aeolus plugin: the newest version in its
6
+ * plugin cache and the plugin's data folder, unless variables name others
7
+ * (AEOLUS_PLUGIN_ROOT and AEOLUS_PLUGIN_DATA for Claude Code,
8
+ * AEOLUS_CODEX_PLUGIN_ROOT and AEOLUS_CODEX_PLUGIN_DATA for Codex, whose
9
+ * folders follow CODEX_HOME as Codex does).
6
10
  */
7
11
  export declare function findAeolusPlugin(at: {
8
12
  homeDirectory: string;
9
13
  env: Readonly<Record<string, string | undefined>>;
14
+ harness?: PluginHarness;
10
15
  }): Promise<AeolusPlugin>;
@@ -1,21 +1,31 @@
1
1
  import { readdir } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
+ /** Where each harness keeps its plugins, and the variables that point elsewhere. */
4
+ function placesOf(at) {
5
+ const { homeDirectory, env } = at;
6
+ if (at.harness === 'codex') {
7
+ return { name: 'Codex', plugins: join(env.CODEX_HOME ?? join(homeDirectory, '.codex'), 'plugins'), root: env.AEOLUS_CODEX_PLUGIN_ROOT, data: env.AEOLUS_CODEX_PLUGIN_DATA, rootVariable: 'AEOLUS_CODEX_PLUGIN_ROOT' };
8
+ }
9
+ return { name: 'Claude Code', plugins: join(homeDirectory, '.claude', 'plugins'), root: env.AEOLUS_PLUGIN_ROOT, data: env.AEOLUS_PLUGIN_DATA, rootVariable: 'AEOLUS_PLUGIN_ROOT' };
10
+ }
3
11
  /**
4
- * Where Claude Code installed the aeolus plugin: AEOLUS_PLUGIN_ROOT and
5
- * AEOLUS_PLUGIN_DATA when set, else the newest version in Claude Code's
6
- * plugin cache and the plugin's data folder.
12
+ * Where a harness installed the aeolus plugin: the newest version in its
13
+ * plugin cache and the plugin's data folder, unless variables name others
14
+ * (AEOLUS_PLUGIN_ROOT and AEOLUS_PLUGIN_DATA for Claude Code,
15
+ * AEOLUS_CODEX_PLUGIN_ROOT and AEOLUS_CODEX_PLUGIN_DATA for Codex, whose
16
+ * folders follow CODEX_HOME as Codex does).
7
17
  */
8
18
  export async function findAeolusPlugin(at) {
9
- const plugins = join(at.homeDirectory, '.claude', 'plugins');
10
- const data = at.env.AEOLUS_PLUGIN_DATA ?? join(plugins, 'data', 'aeolus-aeolus-fleet');
11
- if (at.env.AEOLUS_PLUGIN_ROOT !== undefined) {
12
- return { root: at.env.AEOLUS_PLUGIN_ROOT, data };
19
+ const places = placesOf({ ...at, harness: at.harness ?? 'claude-code' });
20
+ const data = places.data ?? join(places.plugins, 'data', 'aeolus-aeolus-fleet');
21
+ if (places.root !== undefined) {
22
+ return { root: places.root, data };
13
23
  }
14
- const cache = join(plugins, 'cache', 'aeolus-fleet', 'aeolus');
24
+ const cache = join(places.plugins, 'cache', 'aeolus-fleet', 'aeolus');
15
25
  const versions = (await readdir(cache).catch(() => [])).filter((name) => /^\d+\.\d+\.\d+$/.test(name)).sort(byVersion);
16
26
  const newest = versions.at(-1);
17
27
  if (newest === undefined) {
18
- throw new Error(`The aeolus plugin is not installed for Claude Code (nothing in ${cache}): install it, or set AEOLUS_PLUGIN_ROOT`);
28
+ throw new Error(`The aeolus plugin is not installed for ${places.name} (nothing in ${cache}): install it, or set ${places.rootVariable}`);
19
29
  }
20
30
  return { root: join(cache, newest), data };
21
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/adapters/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAgF;IACrH,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,EAAE,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,qBAAqB,CAAC,CAAC;IACvF,IAAI,EAAE,CAAC,GAAG,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;QAC5C,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,CAAC;IACnD,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvH,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,kEAAkE,KAAK,0CAA0C,CAAC,CAAC;IACrI,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,KAAa;IAC5C,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACzD,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/adapters/plugin.ts"],"names":[],"mappings":"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"}
@@ -37,7 +37,7 @@ export interface Service {
37
37
  }
38
38
  /** Runs a program and answers how it ended. */
39
39
  export type Exec = (command: string, args: readonly string[]) => Promise<CommandResult>;
40
- /** The service's environment: PATH, so it finds tmux, git and claude, and the trierarch's own variables. */
40
+ /** The service's environment: PATH, so it finds tmux, git, claude and codex, and the trierarch's own variables. */
41
41
  export declare function serviceEnvironment(env: Readonly<Record<string, string | undefined>>): Record<string, string>;
42
42
  export declare function createService(options: {
43
43
  platform: string;
@@ -10,9 +10,9 @@ import { join } from 'node:path';
10
10
  export const LAUNCHD_LABEL = 'dev.aeolus-fleet.trierarch';
11
11
  export const SYSTEMD_UNIT = 'aeolus-trierarch.service';
12
12
  /** The variables the trierarch itself reads, carried into the service when set where install ran. */
13
- const TRIERARCH_VARIABLES = ['AEOLUS_PLUGIN_ROOT', 'AEOLUS_PLUGIN_DATA', 'AEOLUS_TRIERARCH_CONFIG'];
13
+ const TRIERARCH_VARIABLES = ['AEOLUS_PLUGIN_ROOT', 'AEOLUS_PLUGIN_DATA', 'AEOLUS_CODEX_PLUGIN_ROOT', 'AEOLUS_CODEX_PLUGIN_DATA', 'CODEX_HOME', 'AEOLUS_TRIERARCH_CONFIG'];
14
14
  const DEFAULT_PATH = '/usr/bin:/bin';
15
- /** The service's environment: PATH, so it finds tmux, git and claude, and the trierarch's own variables. */
15
+ /** The service's environment: PATH, so it finds tmux, git, claude and codex, and the trierarch's own variables. */
16
16
  export function serviceEnvironment(env) {
17
17
  const environment = { PATH: env.PATH ?? DEFAULT_PATH };
18
18
  for (const name of TRIERARCH_VARIABLES) {