@ours.network/fleet 0.10.0-nightly.2 → 0.10.0-nightly.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
@@ -75,7 +75,7 @@ The state dir contract:
75
75
  |---|---|---|
76
76
  | Node ≥ 20 | runs `ours-fleet` itself | nodejs.org, `apt`, or `brew` |
77
77
  | tmux | roles using `session: tmux` (the default) | `apt install tmux` / `brew install tmux` |
78
- | an ACP adapter | roles using `session: acp` | `codex-acp` or `claude-agent-acp` |
78
+ | Node 22 | Claude roles using `session: acp` | required by the maintained Claude ACP adapter |
79
79
  | a harness CLI, logged in | the agent itself | e.g. Claude Code (`claude`) or Codex CLI (`codex`) |
80
80
  | `ours-mcp` daemon | identity + agent-to-agent messaging | `npm i -g @ours.network/mcp && ours-mcp start` |
81
81
 
@@ -91,6 +91,12 @@ ours-fleet init # units/dirs/linger for this user
91
91
  ours-fleet doctor # verifies everything above, with actionable messages
92
92
  ```
93
93
 
94
+ The maintained Codex and Claude ACP adapters install as optional dependencies of
95
+ `ours-fleet` and are resolved internally; users do not install adapter commands
96
+ or add them to `PATH`. An explicit `session_options.acp.command` remains
97
+ available for custom adapters. On Node 20–21, tmux and Codex ACP remain
98
+ available, while maintained Claude ACP requires upgrading to Node 22.
99
+
94
100
  Each OS user manages their own fleet — to host roles under a sandboxed account,
95
101
  become that account and repeat.
96
102
 
@@ -123,21 +129,11 @@ ours-fleet spawn Coder --harness codex --model gpt-5.4 \
123
129
  Permanent spawns are written to `~/fleet.d/<Name>.yaml` — your hand-written
124
130
  `~/fleet.yaml` is **never** machine-edited. `ours-fleet rm <Name>` unspawns.
125
131
 
126
- From inside Claude Code: install the `ours-fleet` plugin (ships in this repo under
127
- `integrations/claude-code`) and say **"spawn ours agent …"** the agent asks
128
- temp-vs-permanent, co-drafts the bio and persona with you, spawns, and arms
129
- oversight.
130
-
131
- From inside Codex, install the native fleet plugin:
132
-
133
- ```sh
134
- npm i -g @ours.network/fleet-codex
135
- ours-fleet-codex-install
136
- ```
137
-
138
- Start a new Codex session and say **"spawn an ours agent …"**. The bundled skill
139
- walks through lifetime, model, sandbox, approval policy, profile, launcher, and
140
- mail-monitor consent, then verifies the real tmux session and offers oversight.
132
+ From inside Claude Code, Codex, or Hermes with the core `ours` plugin installed,
133
+ say **"spawn an ours agent …"**. The core skill checks for `ours-fleet`, installs
134
+ and initializes it when absent, then consults `ours-fleet docs` for the exact
135
+ version-matched workflow. The older fleet-specific harness packages remain
136
+ published for compatibility but are no longer required or installed by default.
141
137
 
142
138
  ## Oversight ("keep an eye")
143
139
 
@@ -165,6 +161,7 @@ roles:
165
161
  ## Command reference
166
162
 
167
163
  ```
164
+ ours-fleet docs | man AI-friendly complete reference
168
165
  ours-fleet up|down|restart|force-restart [-c FILE] [Name...]
169
166
  ours-fleet config [-c FILE] validate + print merged plan
170
167
  ours-fleet ls | attach | peek | logs [-f] | status <Name>
@@ -301,11 +298,11 @@ console later; no terminal UI is part of the monitor or session backend.
301
298
 
302
299
  ## Codex roles
303
300
 
304
- Install Codex, the native ours plugin, and the fleet skills once on the fleet host:
301
+ Install Codex, the native ours plugin, and the fleet CLI once on the fleet host:
305
302
 
306
303
  ```sh
307
- npm i -g @ours.network/fleet-codex
308
- ours-fleet-codex-install
304
+ npm i -g @ours.network/fleet
305
+ ours-fleet init
309
306
  ours-fleet doctor --harness codex
310
307
  ```
311
308
 
@@ -407,7 +404,7 @@ cleanly as long as each role has its own `cwd` (the common case); two roles
407
404
  sharing an identical `cwd` could have their resumes cross — give them distinct
408
405
  working directories if that matters. MCP and monitor wiring is provided by
409
406
  [`@ours.network/codex`](https://github.com/adapt-toolkit/ours-mcp/tree/main/packages/codex);
410
- the native spawn/oversight skills are provided by `@ours.network/fleet-codex`.
407
+ the core ours skill discovers fleet behavior through `ours-fleet docs`.
411
408
  `ours-fleet doctor --harness codex` verifies the CLI, ours plugin, and enhanced launcher/fallback.
412
409
 
413
410
  ## Learn more
package/dist/cli.js CHANGED
@@ -14,6 +14,7 @@ import { up, down, restartRoles, rmRole } from './ops.js';
14
14
  import { runOnce, runTemp } from './runner.js';
15
15
  import { spawnPermanent, spawnTemp } from './spawn.js';
16
16
  import { doctor } from './doctor.js';
17
+ import { AI_DOCS } from './docs.js';
17
18
  import { controlRequest, controlSocketPath, followControl, } from './session/control.js';
18
19
  import './harness/claude-code.js'; // registers the claude-code adapter
19
20
  import './harness/codex.js'; // registers the codex adapter
@@ -39,10 +40,14 @@ const passthrough = (cmd, args) => new Promise(resolve => {
39
40
  });
40
41
  const program = new Command()
41
42
  .name('ours-fleet')
42
- .description('Fleet of persistent, identity-bound AI agents — harness-agnostic, tmux + systemd/launchd.')
43
+ .description('Fleet of persistent, identity-bound AI agents — selectable harness and tmux/ACP sessions.')
43
44
  .version(VERSION);
44
45
  const cOpt = (cmd) => cmd.option('-c, --configuration <file>', 'config file (default: ~/fleet.yaml + ~/fleet.d/)');
45
46
  const collect = (value, previous) => [...previous, value];
47
+ program.command('docs')
48
+ .alias('man')
49
+ .description('print the complete AI-friendly command and configuration reference')
50
+ .action(() => { process.stdout.write(AI_DOCS); });
46
51
  function acpStateDir(name) {
47
52
  const permanent = agentDir(name);
48
53
  if (existsSync(controlSocketPath(permanent)))
@@ -306,7 +311,7 @@ cOpt(program.command('rm <name>').description('stop + delete state dir (+ its fl
306
311
  }
307
312
  });
308
313
  cOpt(program.command('spawn <name>').description('spawn a new agent (permanent by default)'))
309
- .option('--temp', 'temporary: plain tmux, auto-cleaned, gone on reboot')
314
+ .option('--temp', 'temporary: detached supervisor, auto-cleaned, gone on reboot')
310
315
  .option('--harness <id>', 'harness adapter (default: defaults.harness)')
311
316
  .option('--session <backend>', 'session backend: tmux|acp (default: defaults.session or tmux)')
312
317
  .option('--mission <text>', 'one-line mission')
package/dist/docs.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Stable, AI-friendly CLI and configuration reference.
3
+ *
4
+ * Keep this concise enough to place directly in an agent context. Unlike
5
+ * Commander's per-command help, this describes how the pieces compose.
6
+ */
7
+ export declare const AI_DOCS = "# ours-fleet reference\n\nours-fleet runs persistent or temporary, identity-bound AI roles. A role selects\na harness independently from its session backend:\n\n- harness: `claude-code` or `codex`\n- session: `tmux` (default) or `acp`\n- lifetime: permanent (supervised, restartable) or `spawn --temp`\n\n## Discover and validate\n\n```sh\nours-fleet docs # this complete reference (`man` is an alias)\nours-fleet help <command> # exact flags for one command\nours-fleet config [-c FILE] # validate and print the merged plan; no changes\nours-fleet doctor [-c FILE] [--harness codex|claude-code]\n```\n\nDefault configuration is `~/fleet.yaml` plus sorted `~/fleet.d/*.yaml` role\ndrop-ins. An explicit `-c FILE` replaces `~/fleet.yaml`; fleet.d still adds\nroles. Validate with `config` and `doctor` before starting or restarting.\n\n## Lifecycle and console commands\n\n```sh\nours-fleet init\nours-fleet up|down [Name...]\nours-fleet restart [Name...] # preserve/resume harness context\nours-fleet force-restart [Name...] # fresh context; briefing is reloaded\nours-fleet ls\nours-fleet status|peek|attach|logs Name\nours-fleet logs -f Name\nours-fleet send Name \"prompt\"\nours-fleet send Name --key Enter # tmux only\nours-fleet rm Name\n```\n\n`peek`, `attach`, and text `send` work with tmux and ACP. ACP attachment\nalso accepts `/permit <permission-id> <option-id>`, `/interrupt`, and\n`/detach`. Raw `--key` input is tmux-only.\n\n## Spawn\n\n```sh\nours-fleet spawn [--temp] Name \\\n --harness codex|claude-code --session tmux|acp \\\n --mission \"one line\" --cwd /absolute/path --identity Identity \\\n --coordinator Coordinator --model MODEL \\\n --approval ask|allow|deny \\\n --filesystem read-only|workspace|unrestricted \\\n --unattended deny|wait \\\n --bio-file /path/bio.md --persona-file /path/persona.md\n```\n\nPermanent spawn writes `~/fleet.d/Name.yaml` and starts a supervised role.\n`--temp` writes ephemeral state, starts a detached supervisor, and removes the\nrole after exit/reboot. Both lifetimes support `--session acp`.\n\nCodex-specific spawn flags: `--sandbox`, `--permission-mode`, `--launcher`,\n`--profile`, `--search`, repeatable `--codex-config key=value`, repeatable\n`--add-dir`, and `--monitor`. Run `ours-fleet help spawn` for exact values.\n\n## fleet.yaml\n\n```yaml\nvars:\n work_root: /home/me/work\nstart_stagger_ms: 0\ndefaults:\n harness: codex\n session: acp\n model: gpt-model-id\n permissions:\n approval: ask\n filesystem: workspace\n unattended: deny\n monitor:\n enabled: true\nroles:\n Coordinator:\n harness: codex\n session: acp\n identity: Coordinator\n cwd: ${work_root}/project\n mission: Coordinate work and delegate implementation.\n model: gpt-model-id\n permissions:\n approval: ask\n filesystem: workspace\n unattended: deny\n session_options: # advanced overrides; normally omit\n # acp:\n # command: [/custom/codex-acp, --flag]\n tmux:\n boot_grace_ms: 10000\n monitor:\n enabled: true\n wake_sources: [message_received, file_received, local_contact_request, pending_message]\n batch_ms: 2000\n inject: notification\n turn_fail_threshold: 3\n harness_options:\n launcher: auto\n sandbox: workspace-write\n approval: on-request\n search: false\n profile: fleet\n add_dirs: [/data/shared]\n config:\n model_reasoning_effort: high\n bio: Public role card and when peers should engage it.\n persona: Local operating contract, boundaries, and escalation policy.\n briefing_file: /absolute/custom-briefing.md\n coordinator: AnotherCoordinator\n env:\n KEY: value\n oversee:\n - { role: Worker, interval: 5m }\n```\n\nRole values override defaults. `${name}` substitutes entries from `vars`.\nOther role fields include `max_tokens`, `autocompact_pct`, and `isolation`.\nUse README.md for the complete isolation policy and resource-cap schema.\n\n## Permissions\n\nPrefer the harness-neutral `permissions` block:\n\n- `approval: ask|allow|deny`: whether actions may request or receive approval\n- `filesystem: read-only|workspace|unrestricted`: filesystem intent\n- `unattended: deny|wait`: what ACP does when no console can answer a request\n\nThe backend translates this common intent. Harness-native settings in\n`harness_options` take precedence where supplied. Do not choose\n`allow`/`unrestricted`, Codex `never`/`danger-full-access`, or Claude\n`bypassPermissions` without explicit authorization.\n\nClaude `harness_options`: `permission_mode` (default, acceptEdits, plan,\ndontAsk, bypassPermissions), `plugins`, `mem_palace`, and\n`mem_palace_midsession_autosave`.\n\nCodex `harness_options`: `launcher` (auto, ours-codex, codex), `sandbox`\n(read-only, workspace-write, danger-full-access), `approval` or\n`permission_mode` (untrusted, on-request, never), `profile`, `search`,\n`config`, `add_dirs`, and `monitor`.\n\n## ACP adapters\n\nThe maintained `@agentclientprotocol/codex-acp` and\n`@agentclientprotocol/claude-agent-acp` runtimes are bundled automatically as\noptional ours-fleet dependencies. The supervisor resolves their executable\nentrypoints internally, so default ACP roles do not depend on global PATH.\nThe maintained Claude adapter requires Node 22; tmux and Codex ACP continue to\nwork on the ours-fleet core minimum of Node 20.\n\nOverride an adapter only when necessary with `session_options.acp.command`\n(string or argv list). If optional dependencies were deliberately omitted,\nours-fleet falls back to a compatible globally installed `codex-acp` or\n`claude-agent-acp`. `ours-fleet doctor -c FILE` verifies the resolved adapter.\n\n## Reliable mail wake\n\nThe supervisor monitor is enabled by default. It consumes body-free daemon\nevents and advances its durable cursor only after delivery is accepted. ACP uses\na structured `session/prompt`; tmux uses verified console injection. Message\nbodies are released only when the role calls the ours `get_messages` tool.\n\nSet `monitor.enabled: false` only to retain legacy in-session monitoring.\nInspect `ours-fleet status Name`, `peek Name`, role logs, and\n`~/.ours-fleet/agents/Name/.monitor-status` when diagnosing delivery.\n";
package/dist/docs.js ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Stable, AI-friendly CLI and configuration reference.
3
+ *
4
+ * Keep this concise enough to place directly in an agent context. Unlike
5
+ * Commander's per-command help, this describes how the pieces compose.
6
+ */
7
+ export const AI_DOCS = `# ours-fleet reference
8
+
9
+ ours-fleet runs persistent or temporary, identity-bound AI roles. A role selects
10
+ a harness independently from its session backend:
11
+
12
+ - harness: \`claude-code\` or \`codex\`
13
+ - session: \`tmux\` (default) or \`acp\`
14
+ - lifetime: permanent (supervised, restartable) or \`spawn --temp\`
15
+
16
+ ## Discover and validate
17
+
18
+ \`\`\`sh
19
+ ours-fleet docs # this complete reference (\`man\` is an alias)
20
+ ours-fleet help <command> # exact flags for one command
21
+ ours-fleet config [-c FILE] # validate and print the merged plan; no changes
22
+ ours-fleet doctor [-c FILE] [--harness codex|claude-code]
23
+ \`\`\`
24
+
25
+ Default configuration is \`~/fleet.yaml\` plus sorted \`~/fleet.d/*.yaml\` role
26
+ drop-ins. An explicit \`-c FILE\` replaces \`~/fleet.yaml\`; fleet.d still adds
27
+ roles. Validate with \`config\` and \`doctor\` before starting or restarting.
28
+
29
+ ## Lifecycle and console commands
30
+
31
+ \`\`\`sh
32
+ ours-fleet init
33
+ ours-fleet up|down [Name...]
34
+ ours-fleet restart [Name...] # preserve/resume harness context
35
+ ours-fleet force-restart [Name...] # fresh context; briefing is reloaded
36
+ ours-fleet ls
37
+ ours-fleet status|peek|attach|logs Name
38
+ ours-fleet logs -f Name
39
+ ours-fleet send Name "prompt"
40
+ ours-fleet send Name --key Enter # tmux only
41
+ ours-fleet rm Name
42
+ \`\`\`
43
+
44
+ \`peek\`, \`attach\`, and text \`send\` work with tmux and ACP. ACP attachment
45
+ also accepts \`/permit <permission-id> <option-id>\`, \`/interrupt\`, and
46
+ \`/detach\`. Raw \`--key\` input is tmux-only.
47
+
48
+ ## Spawn
49
+
50
+ \`\`\`sh
51
+ ours-fleet spawn [--temp] Name \\
52
+ --harness codex|claude-code --session tmux|acp \\
53
+ --mission "one line" --cwd /absolute/path --identity Identity \\
54
+ --coordinator Coordinator --model MODEL \\
55
+ --approval ask|allow|deny \\
56
+ --filesystem read-only|workspace|unrestricted \\
57
+ --unattended deny|wait \\
58
+ --bio-file /path/bio.md --persona-file /path/persona.md
59
+ \`\`\`
60
+
61
+ Permanent spawn writes \`~/fleet.d/Name.yaml\` and starts a supervised role.
62
+ \`--temp\` writes ephemeral state, starts a detached supervisor, and removes the
63
+ role after exit/reboot. Both lifetimes support \`--session acp\`.
64
+
65
+ Codex-specific spawn flags: \`--sandbox\`, \`--permission-mode\`, \`--launcher\`,
66
+ \`--profile\`, \`--search\`, repeatable \`--codex-config key=value\`, repeatable
67
+ \`--add-dir\`, and \`--monitor\`. Run \`ours-fleet help spawn\` for exact values.
68
+
69
+ ## fleet.yaml
70
+
71
+ \`\`\`yaml
72
+ vars:
73
+ work_root: /home/me/work
74
+ start_stagger_ms: 0
75
+ defaults:
76
+ harness: codex
77
+ session: acp
78
+ model: gpt-model-id
79
+ permissions:
80
+ approval: ask
81
+ filesystem: workspace
82
+ unattended: deny
83
+ monitor:
84
+ enabled: true
85
+ roles:
86
+ Coordinator:
87
+ harness: codex
88
+ session: acp
89
+ identity: Coordinator
90
+ cwd: \${work_root}/project
91
+ mission: Coordinate work and delegate implementation.
92
+ model: gpt-model-id
93
+ permissions:
94
+ approval: ask
95
+ filesystem: workspace
96
+ unattended: deny
97
+ session_options: # advanced overrides; normally omit
98
+ # acp:
99
+ # command: [/custom/codex-acp, --flag]
100
+ tmux:
101
+ boot_grace_ms: 10000
102
+ monitor:
103
+ enabled: true
104
+ wake_sources: [message_received, file_received, local_contact_request, pending_message]
105
+ batch_ms: 2000
106
+ inject: notification
107
+ turn_fail_threshold: 3
108
+ harness_options:
109
+ launcher: auto
110
+ sandbox: workspace-write
111
+ approval: on-request
112
+ search: false
113
+ profile: fleet
114
+ add_dirs: [/data/shared]
115
+ config:
116
+ model_reasoning_effort: high
117
+ bio: Public role card and when peers should engage it.
118
+ persona: Local operating contract, boundaries, and escalation policy.
119
+ briefing_file: /absolute/custom-briefing.md
120
+ coordinator: AnotherCoordinator
121
+ env:
122
+ KEY: value
123
+ oversee:
124
+ - { role: Worker, interval: 5m }
125
+ \`\`\`
126
+
127
+ Role values override defaults. \`\${name}\` substitutes entries from \`vars\`.
128
+ Other role fields include \`max_tokens\`, \`autocompact_pct\`, and \`isolation\`.
129
+ Use README.md for the complete isolation policy and resource-cap schema.
130
+
131
+ ## Permissions
132
+
133
+ Prefer the harness-neutral \`permissions\` block:
134
+
135
+ - \`approval: ask|allow|deny\`: whether actions may request or receive approval
136
+ - \`filesystem: read-only|workspace|unrestricted\`: filesystem intent
137
+ - \`unattended: deny|wait\`: what ACP does when no console can answer a request
138
+
139
+ The backend translates this common intent. Harness-native settings in
140
+ \`harness_options\` take precedence where supplied. Do not choose
141
+ \`allow\`/\`unrestricted\`, Codex \`never\`/\`danger-full-access\`, or Claude
142
+ \`bypassPermissions\` without explicit authorization.
143
+
144
+ Claude \`harness_options\`: \`permission_mode\` (default, acceptEdits, plan,
145
+ dontAsk, bypassPermissions), \`plugins\`, \`mem_palace\`, and
146
+ \`mem_palace_midsession_autosave\`.
147
+
148
+ Codex \`harness_options\`: \`launcher\` (auto, ours-codex, codex), \`sandbox\`
149
+ (read-only, workspace-write, danger-full-access), \`approval\` or
150
+ \`permission_mode\` (untrusted, on-request, never), \`profile\`, \`search\`,
151
+ \`config\`, \`add_dirs\`, and \`monitor\`.
152
+
153
+ ## ACP adapters
154
+
155
+ The maintained \`@agentclientprotocol/codex-acp\` and
156
+ \`@agentclientprotocol/claude-agent-acp\` runtimes are bundled automatically as
157
+ optional ours-fleet dependencies. The supervisor resolves their executable
158
+ entrypoints internally, so default ACP roles do not depend on global PATH.
159
+ The maintained Claude adapter requires Node 22; tmux and Codex ACP continue to
160
+ work on the ours-fleet core minimum of Node 20.
161
+
162
+ Override an adapter only when necessary with \`session_options.acp.command\`
163
+ (string or argv list). If optional dependencies were deliberately omitted,
164
+ ours-fleet falls back to a compatible globally installed \`codex-acp\` or
165
+ \`claude-agent-acp\`. \`ours-fleet doctor -c FILE\` verifies the resolved adapter.
166
+
167
+ ## Reliable mail wake
168
+
169
+ The supervisor monitor is enabled by default. It consumes body-free daemon
170
+ events and advances its durable cursor only after delivery is accepted. ACP uses
171
+ a structured \`session/prompt\`; tmux uses verified console injection. Message
172
+ bodies are released only when the role calls the ours \`get_messages\` tool.
173
+
174
+ Set \`monitor.enabled: false\` only to retain legacy in-session monitoring.
175
+ Inspect \`ours-fleet status Name\`, \`peek Name\`, role logs, and
176
+ \`~/.ours-fleet/agents/Name/.monitor-status\` when diagnosing delivery.
177
+ `;
package/dist/doctor.js CHANGED
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs';
3
3
  import { realExec } from './exec.js';
4
4
  import { loadConfig } from './config.js';
5
5
  import { getAdapter } from './harness/registry.js';
6
+ import { resolveBundledAcpAgent } from './harness/acp-agent.js';
6
7
  import { agentDir, home, deriveXdgRuntimeDir } from './paths.js';
7
8
  import { resolveIsolation } from './isolation/policy.js';
8
9
  import { makeBubblewrapBackend } from './isolation/bubblewrap.js';
@@ -173,6 +174,13 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
173
174
  }
174
175
  for (const role of roles.filter(role => role.session === 'acp')) {
175
176
  const configured = role.session_options?.acp?.command;
177
+ const bundled = configured == null
178
+ ? role.harness === 'codex'
179
+ ? resolveBundledAcpAgent('@agentclientprotocol/codex-acp', 'codex-acp', 'codex-acp')
180
+ : role.harness === 'claude-code'
181
+ ? resolveBundledAcpAgent('@agentclientprotocol/claude-agent-acp', 'claude-agent-acp', 'claude-agent-acp')
182
+ : undefined
183
+ : undefined;
176
184
  const command = Array.isArray(configured)
177
185
  ? configured[0]
178
186
  : typeof configured === 'string'
@@ -189,6 +197,13 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
189
197
  });
190
198
  continue;
191
199
  }
200
+ if (bundled?.bundled) {
201
+ checks.push({
202
+ name: `acp: ${role.name}`, ok: true,
203
+ detail: `${command} bundled with ours-fleet`,
204
+ });
205
+ continue;
206
+ }
192
207
  const result = await exec('sh', ['-c', 'command -v "$1" >/dev/null 2>&1', 'sh', command]);
193
208
  checks.push({
194
209
  name: `acp: ${role.name}`,
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Resolve an ACP agent shipped as an ours-fleet dependency. Running the JS
3
+ * entrypoint through this process's Node avoids depending on npm exposing a
4
+ * transitive dependency's bin on the user's global PATH.
5
+ */
6
+ export interface AcpAgentResolution {
7
+ argv: string[];
8
+ bundled: boolean;
9
+ }
10
+ export declare function resolveBundledAcpAgent(packageName: string, binName: string, fallbackCommand: string): AcpAgentResolution;
11
+ export declare function bundledAcpAgent(packageName: string, binName: string, fallbackCommand: string): string[];
@@ -0,0 +1,27 @@
1
+ import { createRequire } from 'node:module';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ const require = createRequire(import.meta.url);
5
+ export function resolveBundledAcpAgent(packageName, binName, fallbackCommand) {
6
+ try {
7
+ const manifestPath = require.resolve(`${packageName}/package.json`);
8
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
9
+ const relative = typeof manifest.bin === 'string'
10
+ ? manifest.bin
11
+ : manifest.bin?.[binName];
12
+ if (!relative)
13
+ return { argv: [fallbackCommand], bundled: false };
14
+ const entrypoint = resolve(dirname(manifestPath), relative);
15
+ if (!existsSync(entrypoint))
16
+ return { argv: [fallbackCommand], bundled: false };
17
+ return { argv: [process.execPath, entrypoint], bundled: true };
18
+ }
19
+ catch {
20
+ // Supports development installs that intentionally omit optional
21
+ // dependencies and existing hosts with a globally installed adapter.
22
+ return { argv: [fallbackCommand], bundled: false };
23
+ }
24
+ }
25
+ export function bundledAcpAgent(packageName, binName, fallbackCommand) {
26
+ return resolveBundledAcpAgent(packageName, binName, fallbackCommand).argv;
27
+ }
@@ -3,6 +3,7 @@ import { join } from 'node:path';
3
3
  import { home } from '../paths.js';
4
4
  import { realExec } from '../exec.js';
5
5
  import { registerAdapter } from './registry.js';
6
+ import { bundledAcpAgent } from './acp-agent.js';
6
7
  const OPTION_KEYS = ['plugins', 'mem_palace', 'mem_palace_midsession_autosave', 'permission_mode'];
7
8
  /** Claude Code's accepted --permission-mode values. */
8
9
  const PERMISSION_MODES = ['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'];
@@ -119,7 +120,7 @@ export function makeClaudeCodeAdapter(exec = realExec) {
119
120
  ? [...configured]
120
121
  : typeof configured === 'string'
121
122
  ? ['sh', '-c', configured]
122
- : ['claude-agent-acp'];
123
+ : bundledAcpAgent('@agentclientprotocol/claude-agent-acp', 'claude-agent-acp', 'claude-agent-acp');
123
124
  return { argv, env: prep.env };
124
125
  },
125
126
  translatePermissions(permissions) {
@@ -2,6 +2,7 @@ import { join } from 'node:path';
2
2
  import { agentDir } from '../paths.js';
3
3
  import { realExec } from '../exec.js';
4
4
  import { registerAdapter } from './registry.js';
5
+ import { bundledAcpAgent } from './acp-agent.js';
5
6
  const OPTION_KEYS = [
6
7
  'launcher', 'sandbox', 'approval', 'permission_mode', 'search', 'profile', 'config', 'add_dirs',
7
8
  'monitor',
@@ -193,7 +194,7 @@ export function makeCodexAdapter(exec = realExec) {
193
194
  ? [...configured]
194
195
  : typeof configured === 'string'
195
196
  ? ['sh', '-c', configured]
196
- : ['codex-acp'];
197
+ : bundledAcpAgent('@agentclientprotocol/codex-acp', 'codex-acp', 'codex-acp');
197
198
  return { argv, env: prep.env };
198
199
  },
199
200
  translatePermissions(permissions) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "0.10.0-nightly.2",
3
+ "version": "0.10.0-nightly.3",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux or ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -30,6 +30,10 @@
30
30
  "commander": "^12.1.0",
31
31
  "yaml": "^2.5.0"
32
32
  },
33
+ "optionalDependencies": {
34
+ "@agentclientprotocol/claude-agent-acp": "^0.63.0",
35
+ "@agentclientprotocol/codex-acp": "^1.1.7"
36
+ },
33
37
  "devDependencies": {
34
38
  "@types/node": "^20.14.0",
35
39
  "typescript": "^5.5.0",