@ours.network/fleet 1.0.5 → 1.1.0-nightly.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +122 -133
  2. package/dist/agent-recovery-gate.d.ts +17 -0
  3. package/dist/agent-recovery-gate.js +135 -0
  4. package/dist/application/capabilities.d.ts +0 -1
  5. package/dist/application/capabilities.js +3 -13
  6. package/dist/application/fleet-query-service.d.ts +0 -3
  7. package/dist/application/fleet-query-service.js +14 -25
  8. package/dist/application/role-creation-service.d.ts +5 -24
  9. package/dist/application/role-creation-service.js +27 -67
  10. package/dist/application/role-removal-service.js +6 -3
  11. package/dist/application/role-repository.d.ts +1 -2
  12. package/dist/application/role-repository.js +7 -7
  13. package/dist/application/session-control.d.ts +0 -12
  14. package/dist/application/session-control.js +0 -42
  15. package/dist/application/session-mutations.d.ts +5 -5
  16. package/dist/application/task-room-service.d.ts +1 -1
  17. package/dist/application/task-room-service.js +154 -16
  18. package/dist/application/types.d.ts +1 -8
  19. package/dist/briefing.js +12 -11
  20. package/dist/build-info.json +5 -5
  21. package/dist/cli.js +143 -63
  22. package/dist/config-yaml.d.ts +1 -1
  23. package/dist/config.d.ts +66 -6
  24. package/dist/config.js +398 -43
  25. package/dist/creation.d.ts +1 -1
  26. package/dist/creation.js +2 -2
  27. package/dist/daemon-recovery.d.ts +93 -0
  28. package/dist/daemon-recovery.js +328 -0
  29. package/dist/docs.d.ts +1 -1
  30. package/dist/docs.js +103 -127
  31. package/dist/doctor.js +18 -11
  32. package/dist/fleet-command-audit.d.ts +160 -0
  33. package/dist/fleet-command-audit.js +433 -0
  34. package/dist/fleet-proxy.d.ts +3 -1
  35. package/dist/fleet-proxy.js +30 -8
  36. package/dist/generated-agent-source.d.ts +9 -0
  37. package/dist/generated-agent-source.js +53 -0
  38. package/dist/harness/acp-session-transport.d.ts +8 -0
  39. package/dist/harness/acp-session-transport.js +3 -0
  40. package/dist/harness/agent-session.d.ts +51 -0
  41. package/dist/harness/agent-session.js +1 -0
  42. package/dist/harness/claude-code-session.d.ts +24 -0
  43. package/dist/harness/claude-code-session.js +36 -0
  44. package/dist/harness/claude-code.d.ts +2 -1
  45. package/dist/harness/claude-code.js +41 -81
  46. package/dist/harness/codex-session.d.ts +24 -0
  47. package/dist/harness/codex-session.js +36 -0
  48. package/dist/harness/codex.d.ts +2 -1
  49. package/dist/harness/codex.js +55 -45
  50. package/dist/harness/registry.js +4 -0
  51. package/dist/harness/types.d.ts +6 -38
  52. package/dist/index.d.ts +4 -6
  53. package/dist/index.js +2 -4
  54. package/dist/init-guidance.d.ts +1 -0
  55. package/dist/init-guidance.js +6 -0
  56. package/dist/isolation/resources.d.ts +3 -7
  57. package/dist/isolation/resources.js +3 -7
  58. package/dist/model-env.d.ts +4 -6
  59. package/dist/model-env.js +5 -8
  60. package/dist/monitor.d.ts +1 -24
  61. package/dist/monitor.js +5 -140
  62. package/dist/ops.js +4 -2
  63. package/dist/owner-channel/channel.d.ts +58 -2
  64. package/dist/owner-channel/channel.js +269 -29
  65. package/dist/owner-channel/commands.d.ts +2 -0
  66. package/dist/owner-channel/commands.js +31 -13
  67. package/dist/owner-channel/state.d.ts +2 -0
  68. package/dist/owner-channel/state.js +8 -2
  69. package/dist/preset-bootstrap.d.ts +10 -0
  70. package/dist/preset-bootstrap.js +85 -0
  71. package/dist/reports/artifact.d.ts +10 -0
  72. package/dist/reports/artifact.js +32 -0
  73. package/dist/reports/index.d.ts +5 -0
  74. package/dist/reports/index.js +5 -0
  75. package/dist/reports/render.d.ts +6 -0
  76. package/dist/reports/render.js +240 -0
  77. package/dist/reports/service.d.ts +42 -0
  78. package/dist/reports/service.js +57 -0
  79. package/dist/reports/tasks.d.ts +52 -0
  80. package/dist/reports/tasks.js +177 -0
  81. package/dist/reports/types.d.ts +315 -0
  82. package/dist/reports/types.js +6 -0
  83. package/dist/resolved-plan.d.ts +3 -2
  84. package/dist/resolved-plan.js +21 -5
  85. package/dist/rooms-tasks/cli.js +225 -40
  86. package/dist/rooms-tasks/config.js +24 -13
  87. package/dist/rooms-tasks/external-worker.d.ts +1 -0
  88. package/dist/rooms-tasks/external-worker.js +14 -9
  89. package/dist/rooms-tasks/provision.js +98 -36
  90. package/dist/rooms-tasks/templates.d.ts +0 -1
  91. package/dist/rooms-tasks/templates.js +9 -76
  92. package/dist/rooms-tasks/types.d.ts +10 -16
  93. package/dist/rooms-tasks/types.js +1 -4
  94. package/dist/runner.d.ts +13 -16
  95. package/dist/runner.js +110 -122
  96. package/dist/sensitive-config.d.ts +2 -0
  97. package/dist/sensitive-config.js +4 -0
  98. package/dist/session/acp.d.ts +7 -2
  99. package/dist/session/acp.js +29 -6
  100. package/dist/session/activity.d.ts +2 -3
  101. package/dist/session/activity.js +2 -3
  102. package/dist/session/arbiter.d.ts +4 -4
  103. package/dist/session/control.d.ts +17 -4
  104. package/dist/session/control.js +35 -0
  105. package/dist/session/types.d.ts +11 -5
  106. package/dist/spawn.d.ts +8 -33
  107. package/dist/spawn.js +53 -213
  108. package/dist/supervisor/none.d.ts +1 -1
  109. package/dist/supervisor/none.js +2 -2
  110. package/dist/temp-lifecycle.d.ts +1 -1
  111. package/dist/tmux.d.ts +1 -14
  112. package/dist/tmux.js +1 -51
  113. package/dist/watchdog/config.d.ts +10 -7
  114. package/dist/watchdog/config.js +30 -18
  115. package/dist/watchdog/run.js +12 -36
  116. package/dist/web/auth.d.ts +1 -1
  117. package/dist/web/fleet-config-service.d.ts +11 -8
  118. package/dist/web/fleet-config-service.js +384 -183
  119. package/dist/web/runtime.js +4 -15
  120. package/dist/web/server.d.ts +1 -3
  121. package/dist/web/server.js +3 -14
  122. package/dist/web/topology-promote.js +26 -19
  123. package/dist/web/topology.js +2 -2
  124. package/dist/web/yaml-document-edit.js +2 -0
  125. package/dist/web-app/assets/index-59GF-gsZ.css +1 -0
  126. package/dist/web-app/assets/index-BbTc5KtZ.js +10 -0
  127. package/dist/web-app/index.html +2 -2
  128. package/examples/fleet/agents/Alice.yaml +11 -0
  129. package/examples/fleet/agents/FleetCoordinator.yaml +7 -0
  130. package/examples/fleet/brains/claude-default.yaml +5 -0
  131. package/examples/fleet/roles/coordinator.yaml +5 -0
  132. package/examples/fleet/roles/developer.yaml +3 -0
  133. package/examples/fleet.yaml +74 -0
  134. package/package.json +5 -8
  135. package/presets/fleet/agents/Agent.yaml +3 -0
  136. package/presets/fleet/agents/Architect.yaml +3 -0
  137. package/presets/fleet/agents/Critic.yaml +3 -0
  138. package/presets/fleet/agents/Developer.yaml +3 -0
  139. package/presets/fleet/agents/Secretary.yaml +3 -0
  140. package/presets/fleet/agents/Tester.yaml +3 -0
  141. package/presets/fleet/brains/claude-default.yaml +5 -0
  142. package/presets/fleet/roles/Agent.yaml +6 -0
  143. package/presets/fleet/roles/Architect.yaml +6 -0
  144. package/presets/fleet/roles/Critic.yaml +6 -0
  145. package/presets/fleet/roles/Developer.yaml +6 -0
  146. package/presets/fleet/roles/Secretary.yaml +6 -0
  147. package/presets/fleet/roles/Tester.yaml +6 -0
  148. package/presets/fleet/room_templates/pair.yaml +11 -0
  149. package/presets/fleet/room_templates/single.yaml +8 -0
  150. package/presets/fleet/room_templates/team.yaml +12 -0
  151. package/presets/fleet.yaml +7 -0
  152. package/presets/manifest.json +5 -0
  153. package/dist/session/tmux.d.ts +0 -28
  154. package/dist/session/tmux.js +0 -80
  155. package/dist/web/terminal/bridge.d.ts +0 -27
  156. package/dist/web/terminal/bridge.js +0 -317
  157. package/dist/web-app/assets/TerminalView-C_G1ID2P.js +0 -9
  158. package/dist/web-app/assets/index-BCBK78hw.js +0 -10
  159. package/dist/web-app/assets/index-DuC-xnX4.css +0 -1
@@ -0,0 +1,24 @@
1
+ import type { ResolvedRole } from '../config.js';
2
+ import type { AcpMcpServer, SessionPrep } from './types.js';
3
+ import type { AgentSessionAdapter, AgentSessionStartOptions, PreparedAgentSessionLaunch } from './agent-session.js';
4
+ import { type AcpSessionTransport } from './acp-session-transport.js';
5
+ export interface ClaudeCodeSessionStrategy {
6
+ resolveBrain: AgentSessionAdapter['resolveBrain'];
7
+ modelEnvironmentVariable: AgentSessionAdapter['modelEnvironmentVariable'];
8
+ prepareLaunch(role: ResolvedRole, prep: SessionPrep): PreparedAgentSessionLaunch;
9
+ sessionConfigSelections(role: ResolvedRole): ReturnType<AgentSessionAdapter['sessionConfigSelections']>;
10
+ permissionModeId(role: ResolvedRole): string | undefined;
11
+ mcpServers(role: ResolvedRole): AcpMcpServer[] | undefined;
12
+ sessionMeta(role: ResolvedRole, prep: SessionPrep): Record<string, unknown> | undefined;
13
+ }
14
+ /** Explicit Claude Code implementation of Fleet's shared agent-session factory. */
15
+ export declare class ClaudeCodeAgentSessionAdapter implements AgentSessionAdapter {
16
+ private readonly strategy;
17
+ private readonly transport;
18
+ constructor(strategy: ClaudeCodeSessionStrategy, transport?: AcpSessionTransport);
19
+ resolveBrain(brain: Parameters<AgentSessionAdapter['resolveBrain']>[0]): import("./agent-session.js").ResolvedBrainSelection;
20
+ modelEnvironmentVariable(): string | undefined;
21
+ prepareLaunch(role: ResolvedRole, prep: SessionPrep): PreparedAgentSessionLaunch;
22
+ sessionConfigSelections(role: ResolvedRole): import("./agent-session.js").AgentSessionConfigSelection[];
23
+ start(options: AgentSessionStartOptions): Promise<import("../index.js").AgentSession>;
24
+ }
@@ -0,0 +1,36 @@
1
+ import { AcpSession } from '../session/acp.js';
2
+ import { acpAdapterState, } from './acp-session-transport.js';
3
+ /** Explicit Claude Code implementation of Fleet's shared agent-session factory. */
4
+ export class ClaudeCodeAgentSessionAdapter {
5
+ strategy;
6
+ transport;
7
+ constructor(strategy, transport = AcpSession.start) {
8
+ this.strategy = strategy;
9
+ this.transport = transport;
10
+ }
11
+ resolveBrain(brain) {
12
+ return this.strategy.resolveBrain(brain);
13
+ }
14
+ modelEnvironmentVariable() { return this.strategy.modelEnvironmentVariable(); }
15
+ prepareLaunch(role, prep) {
16
+ return this.strategy.prepareLaunch(role, prep);
17
+ }
18
+ sessionConfigSelections(role) {
19
+ return this.strategy.sessionConfigSelections(role);
20
+ }
21
+ start(options) {
22
+ const { role, prep, launch } = options;
23
+ return this.transport({
24
+ name: role.name, argv: launch.argv, cwd: options.cwd, env: launch.env,
25
+ stateDir: options.stateDir, mode: options.mode, permissions: options.permissions,
26
+ modeId: this.strategy.permissionModeId(role),
27
+ mcpServers: this.strategy.mcpServers(role),
28
+ sessionMeta: this.strategy.sessionMeta(role, prep),
29
+ configSelections: this.strategy.sessionConfigSelections(role),
30
+ permissionMode: options.permissionMode,
31
+ permissionMetadataSource: acpAdapterState(launch.adapterState).permissionMetadataSource,
32
+ scrubObsoleteOursAutostart: true,
33
+ log: options.log,
34
+ });
35
+ }
36
+ }
@@ -2,6 +2,7 @@ import { type Exec } from '../exec.js';
2
2
  import type { ResolvedRole } from '../config.js';
3
3
  import type { HarnessAdapter, UnattendedCapability } from './types.js';
4
4
  import { type LockDeps } from '../atomic-file.js';
5
+ import type { AcpSessionTransport } from './acp-session-transport.js';
5
6
  /**
6
7
  * Neutral approval → native Claude mode. The ONE definition, shared by launch
7
8
  * and by translation so the two can never disagree about what a role will run
@@ -40,5 +41,5 @@ export declare function pretrust(dir: string, deps?: {
40
41
  log?(line: string): void;
41
42
  lock?: LockDeps;
42
43
  }): Promise<void>;
43
- export declare function makeClaudeCodeAdapter(exec?: Exec): HarnessAdapter;
44
+ export declare function makeClaudeCodeAdapter(exec?: Exec, transport?: AcpSessionTransport): HarnessAdapter;
44
45
  export declare const claudeCodeAdapter: HarnessAdapter;
@@ -6,6 +6,7 @@ import { registerAdapter } from './registry.js';
6
6
  import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
7
7
  import { harnessRuntimeDir } from '../isolation/policy.js';
8
8
  import { bundledAcpAgent } from './acp-agent.js';
9
+ import { ClaudeCodeAgentSessionAdapter } from './claude-code-session.js';
9
10
  const OPTION_KEYS = [
10
11
  'plugins', 'mem_palace', 'mem_palace_midsession_autosave', 'permission_mode', 'effort',
11
12
  'mcp_servers', 'mcp_servers_only',
@@ -223,9 +224,44 @@ const watchCommand = (id) => 'ours api watch-notifications --input '
223
224
  const armMonitor = (id) => 'arm a **persistent Monitor** (the Monitor TOOL — NOT a background Bash command; a ' +
224
225
  `background Bash task never wakes you on output) running \`${watchCommand(id)}\` ` +
225
226
  'so inbound ours mail wakes you';
226
- export function makeClaudeCodeAdapter(exec = realExec) {
227
+ export function makeClaudeCodeAdapter(exec = realExec, transport) {
227
228
  return {
228
229
  id: 'claude-code',
230
+ agentSession: new ClaudeCodeAgentSessionAdapter({
231
+ resolveBrain(brain) {
232
+ if (brain.effort != null
233
+ && (typeof brain.effort !== 'string' || !EFFORT_LEVELS.includes(brain.effort)))
234
+ throw new Error(`Claude Code Brain effort must be one of: ${EFFORT_LEVELS.join(', ')}`);
235
+ return {
236
+ model: brain.model,
237
+ harnessOptions: { ...(brain.harnessOptions ?? {}), ...(brain.effort ? { effort: brain.effort } : {}) },
238
+ };
239
+ },
240
+ modelEnvironmentVariable: () => 'ANTHROPIC_MODEL',
241
+ prepareLaunch(role, prep) {
242
+ const configured = role.session_options?.acp?.command;
243
+ const argv = Array.isArray(configured)
244
+ ? [...configured]
245
+ : typeof configured === 'string'
246
+ ? ['sh', '-c', configured]
247
+ : bundledAcpAgent('@agentclientprotocol/claude-agent-acp', 'claude-agent-acp', 'claude-agent-acp');
248
+ return { argv, env: prep.env };
249
+ },
250
+ sessionConfigSelections: role => typeof role.effort === 'string'
251
+ ? [{ configId: 'effort', value: role.effort }] : [],
252
+ permissionModeId: role => permissionMode(role),
253
+ mcpServers: role => acpMcpServersFor(role.harness_options?.mcp_servers),
254
+ sessionMeta(role, prep) {
255
+ if (customAcpCommand(role))
256
+ return undefined;
257
+ const options = {};
258
+ if (prep.settingsOverlay)
259
+ options.settings = prep.settingsOverlay;
260
+ if (role.harness_options?.mcp_servers_only === true)
261
+ options.strictMcpConfig = true;
262
+ return Object.keys(options).length ? { claudeCode: { options } } : undefined;
263
+ },
264
+ }, transport),
229
265
  supportsResume: true,
230
266
  async checkPrereqs() {
231
267
  const r = await exec('claude', ['--version']);
@@ -305,7 +341,7 @@ export function makeClaudeCodeAdapter(exec = realExec) {
305
341
  MEMPALACE_MIDSESSION_AUTOSAVE: o.mem_palace_midsession_autosave ? 'true' : 'false',
306
342
  // The role's identity, for the ours connector to bind at startup instead of
307
343
  // the briefing telling the MODEL to call choose_identity. Both launches
308
- // return `prep.env`, so this one line covers tmux and ACP alike.
344
+ // reaches the adapter-owned ACP child through `prep.env`.
309
345
  //
310
346
  // The bind the connector performs is PLAIN and fail-closed: it can never
311
347
  // evict a live session, and a role whose identity does not exist yet simply
@@ -321,99 +357,23 @@ export function makeClaudeCodeAdapter(exec = realExec) {
321
357
  // sandbox needs this directory to exist before entry.
322
358
  if (role.isolation)
323
359
  mkdirSync(harnessRuntimeDir(dirs.stateDir, 'claude'), { recursive: true });
324
- const argv = [];
325
360
  let settingsOverlay;
326
361
  if (Object.keys(enabledPlugins).length) {
327
362
  settingsOverlay = join(dirs.stateDir, '.settings-overlay.json');
328
363
  writeFileSync(settingsOverlay, JSON.stringify({ enabledPlugins }, null, 2));
329
- argv.push('--settings', settingsOverlay);
330
364
  }
331
- // `harness_options.mcp_servers` — the tmux delivery. `--mcp-config` ADDS the
332
- // file's servers; `--strict-mcp-config` is what makes the set exclusive, and
333
- // it is opt-in per role because it drops everything else the user has,
334
- // plugins included (see `declaresOursConnector`).
365
+ // Keep the MCP config artifact for diagnostics and adapter metadata.
335
366
  let mcpConfigFile;
336
367
  if (o.mcp_servers) {
337
368
  mcpConfigFile = join(dirs.stateDir, '.mcp-config.json');
338
369
  writeFileSync(mcpConfigFile, JSON.stringify({ mcpServers: o.mcp_servers }, null, 2), { mode: 0o600 });
339
- argv.push('--mcp-config', mcpConfigFile);
340
- if (o.mcp_servers_only === true)
341
- argv.push('--strict-mcp-config');
342
370
  }
343
371
  return {
344
- argv, env,
372
+ env,
345
373
  ...(settingsOverlay ? { settingsOverlay } : {}),
346
374
  ...(mcpConfigFile ? { mcpConfigFile } : {}),
347
375
  };
348
376
  },
349
- buildLaunch(role, mode, s, prep) {
350
- const stateDir = roleStateDir(role);
351
- const pm = permissionMode(role);
352
- const o = role.harness_options;
353
- const base = ['claude', ...(role.model ? ['--model', role.model] : []),
354
- ...(o?.effort ? ['--effort', o.effort] : []),
355
- ...(pm ? ['--permission-mode', pm] : []),
356
- ...prep.argv, '--remote-control', role.name];
357
- const argv = mode === 'fresh'
358
- ? [...base, '--session-id', s.sessionId, `Read and follow ${join(stateDir, 'briefing.md')} now.`]
359
- : [...base, '--resume', s.sessionId,
360
- this.vocabulary.restartPrompt(role.identity, join(stateDir, 'WORKLOG.md'), role)];
361
- return { argv, env: prep.env };
362
- },
363
- buildAcpLaunch(role, prep) {
364
- const configured = role.session_options?.acp?.command;
365
- const argv = Array.isArray(configured)
366
- ? [...configured]
367
- : typeof configured === 'string'
368
- ? ['sh', '-c', configured]
369
- : bundledAcpAgent('@agentclientprotocol/claude-agent-acp', 'claude-agent-acp', 'claude-agent-acp');
370
- return { argv, env: prep.env };
371
- },
372
- // Same source as buildLaunch's --permission-mode flag, so the tmux and ACP
373
- // backends cannot disagree about what a role's permissions translate to.
374
- acpPermissionModeId(role) {
375
- return permissionMode(role);
376
- },
377
- /**
378
- * Deliver, over ACP, the two things the tmux launch delivers as flags.
379
- *
380
- * `buildAcpLaunch` builds its own argv and cannot carry `prep.argv`: the
381
- * process it launches is the ACP agent, not `claude`, and it takes none of
382
- * claude's flags. That is why `harness_options.plugins` did nothing at all on
383
- * an ACP role — the overlay was written and then dropped, and the mem-palace
384
- * toggle rode `prep.env` and survived, so the failure was silent AND
385
- * selective.
386
- *
387
- * `_meta.claudeCode.options` is the bundled agent's own passthrough into the
388
- * Claude Agent SDK (@agentclientprotocol/claude-agent-acp, acp-agent.js:4092
389
- * → the `options` object at :4144). `settings` takes the same overlay path
390
- * `--settings` takes; `strictMcpConfig` is the SDK's spelling of
391
- * `--strict-mcp-config`. Both are spread BEFORE the fields the agent forces,
392
- * so neither is overwritten.
393
- *
394
- * ⚠ RETURNS NOTHING FOR A ROLE THAT NAMES ITS OWN ACP COMMAND. That process
395
- * is not the bundled agent and has no reason to read this vocabulary; sending
396
- * it anyway would be the silent drop again, one level down. `validateOptions`
397
- * refuses those roles instead.
398
- */
399
- acpSessionMeta(role, prep) {
400
- if (customAcpCommand(role))
401
- return undefined;
402
- const options = {};
403
- if (prep.settingsOverlay)
404
- options.settings = prep.settingsOverlay;
405
- if (role.harness_options?.mcp_servers_only === true)
406
- options.strictMcpConfig = true;
407
- return Object.keys(options).length ? { claudeCode: { options } } : undefined;
408
- },
409
- /**
410
- * The declared servers, in ACP's array shape. Sent on `session/new` and on
411
- * resume/load, because the SDK builds its server set once per session and a
412
- * resumed session that dropped them would quietly lose its tools.
413
- */
414
- acpMcpServers(role) {
415
- return acpMcpServersFor(role.harness_options?.mcp_servers);
416
- },
417
377
  isolationPaths(_role, _dirs) {
418
378
  const claudeHome = join(home(), '.claude');
419
379
  return {
@@ -501,7 +461,7 @@ export function makeClaudeCodeAdapter(exec = realExec) {
501
461
  }
502
462
  // The adapter needs the state dir for briefing/worklog paths in launch prompts.
503
463
  // Roles' state dirs are canonical: agentDir(name) — temp roles carry their dir in cwd handling
504
- // by the runner, which passes dirs to prepareSession; buildLaunch derives from the same rule.
464
+ // by the runner, which passes dirs to prepareSession.
505
465
  import { agentDir } from '../paths.js';
506
466
  function roleStateDir(role) {
507
467
  // Temp roles are marked by the runner via a private field to keep the interface small.
@@ -0,0 +1,24 @@
1
+ import type { ResolvedRole } from '../config.js';
2
+ import type { AcpMcpServer, SessionPrep } from './types.js';
3
+ import type { AgentSessionAdapter, AgentSessionStartOptions, PreparedAgentSessionLaunch } from './agent-session.js';
4
+ import { type AcpSessionTransport } from './acp-session-transport.js';
5
+ export interface CodexSessionStrategy {
6
+ resolveBrain: AgentSessionAdapter['resolveBrain'];
7
+ modelEnvironmentVariable: AgentSessionAdapter['modelEnvironmentVariable'];
8
+ prepareLaunch(role: ResolvedRole, prep: SessionPrep): PreparedAgentSessionLaunch;
9
+ sessionConfigSelections(role: ResolvedRole): ReturnType<AgentSessionAdapter['sessionConfigSelections']>;
10
+ permissionModeId(role: ResolvedRole): string | undefined;
11
+ mcpServers(role: ResolvedRole): AcpMcpServer[] | undefined;
12
+ sessionMeta(role: ResolvedRole, prep: SessionPrep): Record<string, unknown> | undefined;
13
+ }
14
+ /** Explicit Codex implementation of Fleet's shared agent-session factory. */
15
+ export declare class CodexAgentSessionAdapter implements AgentSessionAdapter {
16
+ private readonly strategy;
17
+ private readonly transport;
18
+ constructor(strategy: CodexSessionStrategy, transport?: AcpSessionTransport);
19
+ resolveBrain(brain: Parameters<AgentSessionAdapter['resolveBrain']>[0]): import("./agent-session.js").ResolvedBrainSelection;
20
+ modelEnvironmentVariable(): string | undefined;
21
+ prepareLaunch(role: ResolvedRole, prep: SessionPrep): PreparedAgentSessionLaunch;
22
+ sessionConfigSelections(role: ResolvedRole): import("./agent-session.js").AgentSessionConfigSelection[];
23
+ start(options: AgentSessionStartOptions): Promise<import("../index.js").AgentSession>;
24
+ }
@@ -0,0 +1,36 @@
1
+ import { AcpSession } from '../session/acp.js';
2
+ import { acpAdapterState, } from './acp-session-transport.js';
3
+ /** Explicit Codex implementation of Fleet's shared agent-session factory. */
4
+ export class CodexAgentSessionAdapter {
5
+ strategy;
6
+ transport;
7
+ constructor(strategy, transport = AcpSession.start) {
8
+ this.strategy = strategy;
9
+ this.transport = transport;
10
+ }
11
+ resolveBrain(brain) {
12
+ return this.strategy.resolveBrain(brain);
13
+ }
14
+ modelEnvironmentVariable() { return this.strategy.modelEnvironmentVariable(); }
15
+ prepareLaunch(role, prep) {
16
+ return this.strategy.prepareLaunch(role, prep);
17
+ }
18
+ sessionConfigSelections(role) {
19
+ return this.strategy.sessionConfigSelections(role);
20
+ }
21
+ start(options) {
22
+ const { role, prep, launch } = options;
23
+ return this.transport({
24
+ name: role.name, argv: launch.argv, cwd: options.cwd, env: launch.env,
25
+ stateDir: options.stateDir, mode: options.mode, permissions: options.permissions,
26
+ modeId: this.strategy.permissionModeId(role),
27
+ mcpServers: this.strategy.mcpServers(role),
28
+ sessionMeta: this.strategy.sessionMeta(role, prep),
29
+ configSelections: this.strategy.sessionConfigSelections(role),
30
+ permissionMode: options.permissionMode,
31
+ permissionMetadataSource: acpAdapterState(launch.adapterState).permissionMetadataSource,
32
+ scrubObsoleteOursAutostart: true,
33
+ log: options.log,
34
+ });
35
+ }
36
+ }
@@ -1,6 +1,7 @@
1
1
  import { type Exec } from '../exec.js';
2
2
  import type { AcpLaunch, HarnessAdapter, UnattendedCapability } from './types.js';
3
3
  import { type AcpAgentResolution } from './acp-agent.js';
4
+ import type { AcpSessionTransport } from './acp-session-transport.js';
4
5
  /**
5
6
  * What an unattended role can actually do under Codex's native settings.
6
7
  * `on-request` and `untrusted` stop to ask, and with no console attached that
@@ -9,5 +10,5 @@ import { type AcpAgentResolution } from './acp-agent.js';
9
10
  export declare function codexCapabilities(approval: string, sandbox: string): UnattendedCapability[];
10
11
  /** Bind launch argv and metadata provenance to one already-completed resolution. */
11
12
  export declare function codexAcpLaunchForResolution(resolution: AcpAgentResolution): Pick<AcpLaunch, 'argv' | 'permissionMetadataSource'>;
12
- export declare function makeCodexAdapter(exec?: Exec): HarnessAdapter;
13
+ export declare function makeCodexAdapter(exec?: Exec, transport?: AcpSessionTransport): HarnessAdapter;
13
14
  export declare const codexAdapter: HarnessAdapter;
@@ -6,6 +6,7 @@ import { realExec } from '../exec.js';
6
6
  import { registerAdapter } from './registry.js';
7
7
  import { harnessRuntimeDir } from '../isolation/policy.js';
8
8
  import { resolveBundledAcpAgent, } from './acp-agent.js';
9
+ import { CodexAgentSessionAdapter } from './codex-session.js';
9
10
  const OPTION_KEYS = [
10
11
  'launcher', 'sandbox', 'approval', 'permission_mode', 'search', 'profile', 'config', 'add_dirs',
11
12
  'monitor',
@@ -124,6 +125,24 @@ function launcherMode(role) {
124
125
  function bundledCodexAcp() {
125
126
  return resolveBundledAcpAgent(CODEX_ACP_PACKAGE, 'codex-acp', 'codex-acp');
126
127
  }
128
+ function codexAgentLaunch(role, prep) {
129
+ const configured = role.session_options?.acp?.command;
130
+ const resolved = configured == null
131
+ ? codexAcpLaunchForResolution(bundledCodexAcp())
132
+ : undefined;
133
+ const argv = Array.isArray(configured)
134
+ ? [...configured]
135
+ : typeof configured === 'string'
136
+ ? ['sh', '-c', configured]
137
+ : resolved.argv;
138
+ const initialMode = acpAgentMode(role);
139
+ return {
140
+ argv,
141
+ env: initialMode ? { ...prep.env, INITIAL_AGENT_MODE: initialMode } : prep.env,
142
+ ...(resolved?.permissionMetadataSource
143
+ ? { permissionMetadataSource: resolved.permissionMetadataSource } : {}),
144
+ };
145
+ }
127
146
  /** Bind launch argv and metadata provenance to one already-completed resolution. */
128
147
  export function codexAcpLaunchForResolution(resolution) {
129
148
  const permissionMetadataSource = resolution.bundled
@@ -228,9 +247,43 @@ function hasInstalledOursPlugin(output) {
228
247
  return false;
229
248
  }
230
249
  }
231
- export function makeCodexAdapter(exec = realExec) {
250
+ export function makeCodexAdapter(exec = realExec, transport) {
232
251
  return {
233
252
  id: 'codex',
253
+ agentSession: new CodexAgentSessionAdapter({
254
+ resolveBrain(brain) {
255
+ const levels = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
256
+ if (brain.effort != null
257
+ && (typeof brain.effort !== 'string' || !levels.includes(brain.effort)))
258
+ throw new Error(`Codex Brain effort must be one of: ${levels.join(', ')}`);
259
+ const harnessOptions = { ...(brain.harnessOptions ?? {}) };
260
+ if (brain.effort)
261
+ harnessOptions.config = {
262
+ ...(harnessOptions.config ?? {}),
263
+ model_reasoning_effort: brain.effort,
264
+ };
265
+ return { model: brain.model, harnessOptions };
266
+ },
267
+ modelEnvironmentVariable: () => undefined,
268
+ prepareLaunch(role, prep) {
269
+ const launch = codexAgentLaunch(role, prep);
270
+ const { permissionMetadataSource, ...neutral } = launch;
271
+ return {
272
+ ...neutral,
273
+ ...(permissionMetadataSource
274
+ ? { adapterState: { permissionMetadataSource } } : {}),
275
+ };
276
+ },
277
+ sessionConfigSelections: role => [
278
+ ...(typeof role.model === 'string'
279
+ ? [{ configId: 'model', value: role.model }] : []),
280
+ ...(typeof role.effort === 'string'
281
+ ? [{ configId: 'reasoning_effort', value: role.effort }] : []),
282
+ ],
283
+ permissionModeId: role => acpAgentMode(role),
284
+ mcpServers: () => undefined,
285
+ sessionMeta: () => undefined,
286
+ }, transport),
234
287
  supportsResume: true,
235
288
  async checkPrereqs() {
236
289
  const [r, hasOursCodex, plugins] = await Promise.all([
@@ -314,58 +367,15 @@ export function makeCodexAdapter(exec = realExec) {
314
367
  // sandbox needs this directory to exist before entry.
315
368
  if (role.isolation)
316
369
  mkdirSync(harnessRuntimeDir(dirs.stateDir, 'codex'), { recursive: true });
317
- const requested = launcherMode(role);
318
- const hasOursCodex = await commandAvailable('ours-codex', exec);
319
- if (requested === 'ours-codex' && !hasOursCodex)
320
- throw new Error('harness_options.launcher is ours-codex, but ours-codex is not on PATH; install @ours.network/codex or use launcher: auto');
321
- const command = requested === 'codex' ? 'codex' : hasOursCodex ? 'ours-codex' : 'codex';
322
370
  return {
323
- argv: [],
324
371
  // OURS_BIND_IDENTITY is the connector's startup bind seed — see the note in
325
372
  // claude-code.ts's prepareSession. It belongs on EVERY harness that runs a
326
373
  // role with an ours identity, not just claude-code: a seed that works on one
327
374
  // harness and silently does nothing on the other is the same class of defect
328
375
  // as a config key that only works on one session type.
329
376
  env: { OURS_BIND_IDENTITY: role.identity, ...codexAcpEnvironment(role, dirs) },
330
- command,
331
- };
332
- },
333
- buildLaunch(role, mode, _s, prep) {
334
- const stateDir = roleStateDir(role);
335
- const flags = commonFlags(role);
336
- const command = prep.command ?? 'codex';
337
- const argv = mode === 'fresh'
338
- ? [command, ...flags, ...prep.argv, `Read and follow ${join(stateDir, 'briefing.md')} now.`]
339
- : [command, 'resume', '--last', ...flags, ...prep.argv,
340
- this.vocabulary.restartPrompt(role.identity, join(stateDir, 'WORKLOG.md'), role)];
341
- return { argv, env: prep.env };
342
- },
343
- buildAcpLaunch(role, prep) {
344
- const configured = role.session_options?.acp?.command;
345
- // Resolve once: both argv and permission-metadata provenance must describe
346
- // the same artifact. A bare PATH fallback is launchable for compatibility,
347
- // but is never authenticated for protected-MCP auto-approval.
348
- const resolved = configured == null
349
- ? codexAcpLaunchForResolution(bundledCodexAcp())
350
- : undefined;
351
- const argv = Array.isArray(configured)
352
- ? [...configured]
353
- : typeof configured === 'string'
354
- ? ['sh', '-c', configured]
355
- : resolved.argv;
356
- const initialMode = acpAgentMode(role);
357
- return {
358
- argv,
359
- env: initialMode ? { ...prep.env, INITIAL_AGENT_MODE: initialMode } : prep.env,
360
- ...(resolved?.permissionMetadataSource
361
- ? { permissionMetadataSource: resolved.permissionMetadataSource } : {}),
362
377
  };
363
378
  },
364
- // INITIAL_AGENT_MODE covers session/new in codex-acp; session/set_mode
365
- // keeps resumed/loaded sessions and live status on the identical mode.
366
- acpPermissionModeId(role) {
367
- return acpAgentMode(role);
368
- },
369
379
  isolationPaths(role, _dirs) {
370
380
  const codexHome = join(home(), '.codex');
371
381
  const profile = role.harness_options?.profile;
@@ -508,7 +518,7 @@ export function makeCodexAdapter(exec = realExec) {
508
518
  }
509
519
  // The adapter needs the state dir for briefing/worklog paths in launch prompts.
510
520
  // Roles' state dirs are canonical: agentDir(name) — temp roles carry their dir in cwd handling
511
- // by the runner, which passes dirs to prepareSession; buildLaunch derives from the same rule.
521
+ // by the runner, which passes dirs to prepareSession.
512
522
  function roleStateDir(role) {
513
523
  // Temp roles are marked by the runner via a private field to keep the interface small.
514
524
  const temp = role.__temp === true;
@@ -1,5 +1,9 @@
1
1
  const adapters = new Map();
2
2
  export function registerAdapter(a) {
3
+ if (typeof a.agentSession?.resolveBrain !== 'function')
4
+ throw new Error(`harness adapter '${a.id}' must implement agentSession.resolveBrain(): translate neutral model/effort`);
5
+ if (typeof a.agentSession?.modelEnvironmentVariable !== 'function')
6
+ throw new Error(`harness adapter '${a.id}' must implement agentSession.modelEnvironmentVariable()`);
3
7
  // Enforced here rather than left to the type system: an adapter that silently
4
8
  // omits the capability is how the neutral-permission warnings ended up with no
5
9
  // caller and no reader.
@@ -1,5 +1,6 @@
1
1
  import type { McpServer } from '@agentclientprotocol/sdk';
2
2
  import type { CommonPermissions, FleetPermissionMode, ResolvedRole } from '../config.js';
3
+ import type { AgentSessionAdapter } from './agent-session.js';
3
4
  export interface PrereqCheck {
4
5
  name: string;
5
6
  ok: boolean;
@@ -13,27 +14,19 @@ export interface RoleDirs {
13
14
  stateDir: string;
14
15
  runCwd: string;
15
16
  }
16
- export interface SessionState {
17
- sessionId: string;
18
- }
19
17
  /** Extra command/argv/env contributed by prepareSession (overlays, trust, limits). */
20
18
  export interface SessionPrep {
21
- argv: string[];
22
19
  env: Record<string, string>;
23
- /** Optional launcher selected after runtime prerequisite probing. */
24
- command?: string;
25
20
  /**
26
21
  * The settings overlay prepareSession wrote, if it wrote one.
27
22
  *
28
- * The tmux launch delivers this as `--settings <path>` in `argv`; an ACP agent
29
- * takes no flags, so it needs the PATH rather than the flag. Recorded here so
30
- * the two deliveries read one value instead of each re-deriving the filename.
23
+ * Claude's ACP agent takes no settings flag, so it needs the path in session
24
+ * metadata. Record it here rather than re-deriving the filename.
31
25
  */
32
26
  settingsOverlay?: string;
33
27
  /**
34
28
  * The MCP config file prepareSession wrote for `harness_options.mcp_servers`,
35
- * if the role declared any. Same reason as `settingsOverlay`: the tmux launch
36
- * passes the file, the ACP launch has to send the servers themselves.
29
+ * if the role declared any. The ACP launch sends the parsed servers themselves.
37
30
  */
38
31
  mcpConfigFile?: string;
39
32
  }
@@ -121,6 +114,8 @@ export interface ValidationError {
121
114
  }
122
115
  export interface HarnessAdapter {
123
116
  id: string;
117
+ /** Harness-specific construction behind Fleet's shared live-session contract. */
118
+ agentSession: AgentSessionAdapter;
124
119
  supportsResume: boolean;
125
120
  checkPrereqs(): Promise<PrereqReport>;
126
121
  /**
@@ -131,33 +126,6 @@ export interface HarnessAdapter {
131
126
  */
132
127
  validateOptions(opts: unknown, role?: ResolvedRole): ValidationError[];
133
128
  prepareSession(role: ResolvedRole, dirs: RoleDirs): Promise<SessionPrep>;
134
- buildLaunch(role: ResolvedRole, mode: 'fresh' | 'resume', s: SessionState, prep: SessionPrep): Launch;
135
- buildAcpLaunch?(role: ResolvedRole, prep: SessionPrep): AcpLaunch;
136
- /**
137
- * The native permission-mode id an ACP session should run at, from the same
138
- * translation `buildLaunch` uses for its CLI flag. `undefined` keeps the
139
- * agent's default. Omit for a harness whose ACP agent has no modes.
140
- */
141
- acpPermissionModeId?(role: ResolvedRole): string | undefined;
142
- /**
143
- * The MCP servers this role declares, for the `mcpServers` array of ACP's
144
- * `session/new` / `resume` / `load`. ACP requires an array on the wire, so
145
- * `undefined` is encoded as `[]` while the authenticated bundled adapter is
146
- * left to preserve its inherited configuration. An explicit empty array uses
147
- * that adapter's compatibility path to disable all inherited servers.
148
- */
149
- acpMcpServers?(role: ResolvedRole): AcpMcpServer[] | undefined;
150
- /**
151
- * Agent-specific `_meta` for `session/new` — how a capability the CLI takes as
152
- * a flag reaches an ACP agent that accepts no flags.
153
- *
154
- * ⚠ THIS IS A PER-AGENT VOCABULARY, NOT PROTOCOL. `_meta` is free-form in ACP,
155
- * so what an adapter puts here is only honoured by the agent it was written
156
- * for. An adapter must therefore return nothing for an ACP command it did not
157
- * choose, and the options that depend on it must be refused at validation for
158
- * such a role rather than sent and silently ignored.
159
- */
160
- acpSessionMeta?(role: ResolvedRole, prep: SessionPrep): Record<string, unknown> | undefined;
161
129
  /** Effective portable policy and harness-native approval mode after native overrides win. */
162
130
  effectivePermissionMode?(role: ResolvedRole): {
163
131
  fleetMode: FleetPermissionMode;
package/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  export { loadConfig, findRole, resolvePermissions, ConfigError } from './config.js';
2
2
  export type { FleetConfig, ResolvedRole, RoleConfig, OverseeEntry, SessionBackendId, CommonPermissions, FleetPermissionMode, ApprovalMode, SessionOptions, OwnerChannelConfig, } from './config.js';
3
- export type { HarnessAdapter, BriefingVocab, ExitPolicy, PrereqReport, PrereqCheck, SessionPrep, SessionState, Launch, AcpLaunch, PermissionTranslation, RoleDirs, ValidationError, } from './harness/types.js';
4
- export type { SessionHandle, SessionSnapshot, SessionEvent, TurnResult, InterruptOutcome, InterruptResult, TurnCancellationSource, QueuedPrompt, ExitRecord, } from './session/types.js';
3
+ export type { HarnessAdapter, BriefingVocab, ExitPolicy, PrereqReport, PrereqCheck, SessionPrep, Launch, AcpLaunch, PermissionTranslation, RoleDirs, ValidationError, } from './harness/types.js';
4
+ export type { AgentSession, SessionHandle, SessionSnapshot, SessionEvent, TurnResult, InterruptOutcome, InterruptResult, TurnCancellationSource, QueuedPrompt, ExitRecord, } from './session/types.js';
5
5
  export { interruptOutcome } from './session/types.js';
6
6
  export { AcpSession } from './session/acp.js';
7
7
  export { OwnerChannel } from './owner-channel/channel.js';
8
- export { TmuxSession } from './session/tmux.js';
9
8
  export { registerAdapter, getAdapter, knownAdapters } from './harness/registry.js';
10
9
  export { claudeCodeAdapter, makeClaudeCodeAdapter } from './harness/claude-code.js';
11
10
  export { codexAdapter, makeCodexAdapter } from './harness/codex.js';
@@ -14,10 +13,10 @@ export { effectivePermissionMode } from './permissions.js';
14
13
  export { pickBackend } from './supervisor/index.js';
15
14
  export type { SupervisorBackend } from './supervisor/types.js';
16
15
  export { up, down, restartRoles, rmRole, applyRole } from './ops.js';
17
- export { spawnPermanent, spawnTemp, buildRoleConfig, profileValues, validateSpawnOpts, } from './spawn.js';
16
+ export { spawnPermanent, spawnTemp, agentDefinitionFromSpawn, validateSpawnOpts, } from './spawn.js';
18
17
  export { RoleRepository } from './application/role-repository.js';
19
18
  export { FleetQueryService } from './application/fleet-query-service.js';
20
- export { AcpRoleSessionAdapter, TmuxRoleSessionAdapter } from './application/session-control.js';
19
+ export { AcpRoleSessionAdapter } from './application/session-control.js';
21
20
  export { RoleCreationService } from './application/role-creation-service.js';
22
21
  export { RoleCommandService, RoleLifecycleService } from './application/role-command-service.js';
23
22
  export { StructuredLogService } from './application/log-service.js';
@@ -26,5 +25,4 @@ export { FleetError, normalizeError } from './application/errors.js';
26
25
  export type * from './application/types.js';
27
26
  export { doctor } from './doctor.js';
28
27
  export { runOnce, runTemp } from './runner.js';
29
- export { Tmux } from './tmux.js';
30
28
  export { VERSION } from './version.js';
package/dist/index.js CHANGED
@@ -2,7 +2,6 @@ export { loadConfig, findRole, resolvePermissions, ConfigError } from './config.
2
2
  export { interruptOutcome } from './session/types.js';
3
3
  export { AcpSession } from './session/acp.js';
4
4
  export { OwnerChannel } from './owner-channel/channel.js';
5
- export { TmuxSession } from './session/tmux.js';
6
5
  export { registerAdapter, getAdapter, knownAdapters } from './harness/registry.js';
7
6
  export { claudeCodeAdapter, makeClaudeCodeAdapter } from './harness/claude-code.js';
8
7
  export { codexAdapter, makeCodexAdapter } from './harness/codex.js';
@@ -10,10 +9,10 @@ export { generateBriefing } from './briefing.js';
10
9
  export { effectivePermissionMode } from './permissions.js';
11
10
  export { pickBackend } from './supervisor/index.js';
12
11
  export { up, down, restartRoles, rmRole, applyRole } from './ops.js';
13
- export { spawnPermanent, spawnTemp, buildRoleConfig, profileValues, validateSpawnOpts, } from './spawn.js';
12
+ export { spawnPermanent, spawnTemp, agentDefinitionFromSpawn, validateSpawnOpts, } from './spawn.js';
14
13
  export { RoleRepository } from './application/role-repository.js';
15
14
  export { FleetQueryService } from './application/fleet-query-service.js';
16
- export { AcpRoleSessionAdapter, TmuxRoleSessionAdapter } from './application/session-control.js';
15
+ export { AcpRoleSessionAdapter } from './application/session-control.js';
17
16
  export { RoleCreationService } from './application/role-creation-service.js';
18
17
  export { RoleCommandService, RoleLifecycleService } from './application/role-command-service.js';
19
18
  export { StructuredLogService } from './application/log-service.js';
@@ -21,5 +20,4 @@ export { roleCapabilities } from './application/capabilities.js';
21
20
  export { FleetError, normalizeError } from './application/errors.js';
22
21
  export { doctor } from './doctor.js';
23
22
  export { runOnce, runTemp } from './runner.js';
24
- export { Tmux } from './tmux.js';
25
23
  export { VERSION } from './version.js';
@@ -0,0 +1 @@
1
+ export declare const INIT_COMPLETION_GUIDANCE = "\nNext:\n ours-fleet config\n ours-fleet template list\n edit the seeded manifest and fleet/{agents,roles,brains,room_templates}/*.yaml\n configure rooms.owner, then create a task with --template single, pair, or team";
@@ -0,0 +1,6 @@
1
+ export const INIT_COMPLETION_GUIDANCE = `
2
+ Next:
3
+ ours-fleet config
4
+ ours-fleet template list
5
+ edit the seeded manifest and fleet/{agents,roles,brains,room_templates}/*.yaml
6
+ configure rooms.owner, then create a task with --template single, pair, or team`;