@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
package/dist/cli.js CHANGED
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn as spawnChild } from 'node:child_process';
3
+ import { randomUUID } from 'node:crypto';
3
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
4
5
  import { realpathSync } from 'node:fs';
5
6
  import { join as joinPath, resolve as resolvePath } from 'node:path';
6
7
  import { createInterface } from 'node:readline';
7
8
  import { Command } from 'commander';
8
9
  import { VERSION } from './version.js';
10
+ import { INIT_COMPLETION_GUIDANCE } from './init-guidance.js';
11
+ import { bootstrapPresets } from './preset-bootstrap.js';
9
12
  import { analyzeInstalls, buildInfo, buildLabel, discoverInstalls, runningLabel, } from './provenance.js';
10
13
  import { agentDir, agentsRoot, tmpRoot, logsRoot, deriveXdgRuntimeDir } from './paths.js';
11
- import { findRole, loadConfig } from './config.js';
14
+ import { findRole, loadConfig, ROLE_NAME_RE } from './config.js';
12
15
  import { formatDuration } from './duration.js';
13
- import { resolvedPlan } from './resolved-plan.js';
14
- import { Tmux, tmuxArgs } from './tmux.js';
16
+ import { redactSensitive, resolvedPlan } from './resolved-plan.js';
15
17
  import { pickBackend } from './supervisor/index.js';
16
18
  import { up, down } from './ops.js';
17
19
  import { readRestartLedger, runSupervised, runTemp } from './runner.js';
@@ -37,6 +39,7 @@ import { requestWebControl } from './web/control.js';
37
39
  import { WebServiceManager } from './web/service.js';
38
40
  import { WebAccessStore, passwordAccess, validatePublicOrigin } from './web/access.js';
39
41
  import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-proxy.js';
42
+ import { beginFleetAuditCollection, consumeFleetAuditCollection, FleetCliExit, } from './fleet-command-audit.js';
40
43
  import './harness/claude-code.js'; // registers the claude-code adapter
41
44
  import './harness/codex.js'; // registers the codex adapter
42
45
  import { registerTemplateCommands, registerTaskCommands, registerRoomCommands } from './rooms-tasks/cli.js';
@@ -66,7 +69,10 @@ const roleLifecycle = (configPath, operationDeps) => {
66
69
  return new RoleLifecycleService({ repository, ops: operationDeps, configPath,
67
70
  status: async (roleId) => (await query.detail(roleId)).status });
68
71
  };
69
- const die = (e) => { console.error(String(e instanceof Error ? e.message : e)); process.exit(1); };
72
+ const die = (e) => {
73
+ console.error(String(e instanceof Error ? e.message : e));
74
+ throw new FleetCliExit(1);
75
+ };
70
76
  /** Exec a child with our stdio (logs/attach). */
71
77
  const passthrough = (cmd, args) => new Promise(resolve => {
72
78
  const c = spawnChild(cmd, args, { stdio: 'inherit' });
@@ -74,10 +80,11 @@ const passthrough = (cmd, args) => new Promise(resolve => {
74
80
  });
75
81
  const program = new Command()
76
82
  .name('ours-fleet')
77
- .description('Fleet of persistent, identity-bound AI agents — selectable harness and tmux/ACP sessions.')
83
+ .description('Fleet of persistent, identity-bound AI agents — canonical Brain + Role definitions over ACP sessions.')
78
84
  .enablePositionalOptions()
85
+ .exitOverride()
79
86
  .version(VERSION);
80
- const cOpt = (cmd) => cmd.option('-c, --configuration <file>', 'config file (default: ~/fleet.yaml + ~/fleet.d/)');
87
+ const cOpt = (cmd) => cmd.option('-c, --configuration <file>', 'manifest (default: ~/fleet.yaml; documents under ~/fleet/)');
81
88
  const collect = (value, previous) => [...previous, value];
82
89
  program.command('docs')
83
90
  .alias('man')
@@ -218,6 +225,24 @@ function parseCodexConfig(values) {
218
225
  }
219
226
  return out;
220
227
  }
228
+ function parseAgentSelection(raw, kind) {
229
+ if (raw === undefined)
230
+ return undefined;
231
+ if (ROLE_NAME_RE.test(raw))
232
+ return { ref: raw };
233
+ if (!raw.startsWith('inline:'))
234
+ throw new Error(`invalid --${kind}; use a declared ID or inline:{...}`);
235
+ let value;
236
+ try {
237
+ value = JSON.parse(raw.slice('inline:'.length));
238
+ }
239
+ catch {
240
+ throw new Error(`invalid --${kind} inline mapping`);
241
+ }
242
+ if (!value || typeof value !== 'object' || Array.isArray(value))
243
+ throw new Error(`invalid --${kind} inline mapping`);
244
+ return { inline: value };
245
+ }
221
246
  cOpt(program.command('config').description('validate + print the merged plan (no side effects)'))
222
247
  .option('--json', 'emit the stable, versioned, secret-safe resolved plan')
223
248
  .option('--yaml-mode <mode>', 'non-plain YAML policy: compat|strict', 'compat')
@@ -261,14 +286,16 @@ cOpt(program.command('config').description('validate + print the merged plan (no
261
286
  console.log(` cwd: ${r.cwd}`);
262
287
  if (r.model)
263
288
  console.log(` model: ${r.model}`);
289
+ if (r.effort)
290
+ console.log(` effort: ${r.effort}`);
264
291
  if (r.harness_options && Object.keys(r.harness_options).length)
265
- console.log(` options: ${JSON.stringify(r.harness_options)}`);
292
+ console.log(` options: ${JSON.stringify(redactSensitive(r.harness_options))}`);
266
293
  if (r.coordinator)
267
294
  console.log(` coordinator: ${r.coordinator}`);
268
295
  if (r.mission)
269
296
  console.log(` mission: ${r.mission.split('\n')[0]}`);
270
297
  if (r.oversee?.length)
271
- console.log(` oversees: ${r.oversee.map(o => `${o.role}@${o.interval}`).join(', ')}`);
298
+ console.log(` oversees: ${r.oversee.map(o => `${o.agent}@${o.interval}`).join(', ')}`);
272
299
  if (r.isolation) {
273
300
  const iso = r.isolation;
274
301
  const caps = [
@@ -369,15 +396,11 @@ cOpt(program.command('force-restart [names...]').description('re-sync + bounce F
369
396
  });
370
397
  program.command('ls').description('list running fleet sessions')
371
398
  .action(async () => {
372
- // Each session has its own tmux server (#32), so there is no single server
373
- // to ask: the known role names ARE the list of servers to poll.
374
- const names = [];
375
399
  const acp = [];
376
400
  for (const root of [agentsRoot(), tmpRoot()]) {
377
401
  if (!existsSync(root))
378
402
  continue;
379
403
  for (const name of readdirSync(root)) {
380
- names.push(name);
381
404
  const stateDir = joinPath(root, name);
382
405
  if (!existsSync(controlSocketPath(stateDir)))
383
406
  continue;
@@ -395,14 +418,13 @@ program.command('ls').description('list running fleet sessions')
395
418
  catch { /* ignore stale sockets */ }
396
419
  }
397
420
  }
398
- const tmux = await new Tmux().list(names);
399
- console.log([tmux, ...acp].filter(Boolean).join('\n') || '(none)');
421
+ console.log(acp.join('\n') || '(none)');
400
422
  });
401
423
  program.command('attach <name>').description('open the live console (Ctrl-b d to leave)')
402
424
  .action(async (name) => {
403
425
  const stateDir = acpStateDir(name);
404
426
  if (!stateDir)
405
- process.exit(await passthrough('tmux', tmuxArgs(name, ['attach', '-t', name])));
427
+ throw new SessionControlError('offline', 'agent session is offline');
406
428
  try {
407
429
  const { socket, send } = await followControl(stateDir, message => {
408
430
  if ('event' in message)
@@ -439,7 +461,7 @@ program.command('attach <name>').description('open the live console (Ctrl-b d to
439
461
  die(e);
440
462
  }
441
463
  });
442
- /** Classify a raw tmux failure: only "no such session" proves the pane is gone. */
464
+ /** Preserve typed control failures and classify only definite missing-session errors as offline. */
443
465
  const asControlError = (e) => {
444
466
  if (e instanceof SessionControlError)
445
467
  return e;
@@ -467,23 +489,17 @@ program.command('peek <name> [lines]').description('pane snapshot without attach
467
489
  for (const event of events.slice(-(lines ? Number(lines) : 40)))
468
490
  renderSessionEvent(event);
469
491
  }
470
- else {
471
- console.log(await new Tmux().capture(name, lines ? Number(lines) : 40));
472
- }
492
+ else
493
+ throw new SessionControlError('offline', 'agent session is offline');
473
494
  }
474
495
  catch (e) {
475
496
  die(controlFailure(name, 'peek', e));
476
497
  }
477
498
  });
478
499
  program.command('send <name> [text...]').description("type into the agent's console")
479
- .option('--key <key>', 'send a raw key instead (Escape, Up, C-c, ...)')
480
500
  .action(async (name, text, opts) => {
481
501
  const stateDir = acpStateDir(name);
482
- if (stateDir && opts.key)
483
- die('--key is available only for tmux sessions');
484
- if (!stateDir && !opts.key && !text?.length)
485
- die('nothing to send: give text or --key');
486
- if (stateDir && !text?.length)
502
+ if (!text?.length)
487
503
  die('nothing to send: give text');
488
504
  try {
489
505
  if (stateDir) {
@@ -496,10 +512,8 @@ program.command('send <name> [text...]').description("type into the agent's cons
496
512
  ? `queued for ${name} behind ${queued.queuedBehind} running turn(s)`
497
513
  : `queued for ${name}`);
498
514
  }
499
- else if (opts.key)
500
- await new Tmux().sendKey(name, opts.key);
501
515
  else
502
- await new Tmux().sendText(name, text.join(' '));
516
+ throw new SessionControlError('offline', 'agent session is offline');
503
517
  }
504
518
  catch (e) {
505
519
  die(controlFailure(name, 'send', e, asControlError(e).kind === 'timeout'
@@ -1123,50 +1137,32 @@ cOpt(program.command('rm <name>').description('stop + remove a role (temporary e
1123
1137
  }
1124
1138
  });
1125
1139
  cOpt(program.command('spawn [name]').description('spawn a new agent (permanent by default)'))
1126
- .option('--role <name>', 'role name (alternative to the positional name)')
1140
+ .option('--name <name>', 'agent name (alternative to the positional name)')
1141
+ .option('--brain <selection>', 'Brain ID or inline:{...} definition')
1142
+ .option('--role <selection>', 'Role ID or inline:{...} definition')
1127
1143
  .option('--temp', 'temporary: independent transient supervisor, archived on retirement, gone on reboot')
1128
- .option('--harness <id>', 'harness adapter (default: defaults.harness)')
1129
- .option('--session <backend>', 'session backend: tmux|acp (default: defaults.session or tmux)')
1130
- .option('--mission <text>', 'one-line mission')
1131
- .option('--mission-file <path>', 'UTF-8 mission text (mutually exclusive with --mission)')
1132
- .option('--identity <name>', 'ours identity to bind (default: role name)')
1144
+ .option('--identity <name>', 'ours identity to bind (default: Agent name)')
1133
1145
  .option('--cwd <dir>', 'working directory')
1134
1146
  .option('--coordinator <name>', 'announce target')
1135
- .option('--model <id>', 'model id to launch on (e.g. claude-fable-5); default: launcher default')
1136
- .option('--permission-mode <mode>', 'harness permission mode (Codex: untrusted|on-request|never; Claude: native values)')
1137
1147
  .option('--approval <mode>', 'fleet permission mode: ask|auto|allow (Codex ACP allow selects agent-full-access; deny is deprecated)')
1138
1148
  .option('--filesystem <mode>', 'common filesystem intent: read-only|workspace|unrestricted')
1139
1149
  .option('--unattended <mode>', 'permission behavior without a console: deny|wait')
1140
- .option('--sandbox <mode>', 'Codex sandbox: read-only|workspace-write|danger-full-access')
1141
- .option('--profile <name>', 'Codex profile file name ($CODEX_HOME/<name>.config.toml)')
1142
- .option('--launcher <mode>', 'Codex launcher: auto|ours-codex|codex (default: auto)')
1143
- .option('--search', 'enable Codex live web search')
1144
- .option('--codex-config <key=value>', 'Codex config override (repeatable)', collect, [])
1145
- .option('--add-dir <dir>', 'additional Codex writable directory (repeatable)', collect, [])
1146
- .option('--monitor', 'legacy: consent to arm Codex\'s native monitor (wake owner is monitor.mode in YAML)')
1147
- .option('--bio-file <file>', 'public bio (file)')
1148
- .option('--persona-file <file>', 'persona / operating contract (file)')
1149
1150
  .option('--isolation-file <path>', 'file holding an isolation: mapping (same schema as fleet.yaml)')
1150
1151
  .option('--dry-run', 'validate and print without reserving or creating anything')
1151
1152
  .option('--json', 'with --dry-run, emit a stable secret-safe JSON result')
1152
1153
  .action(async (name, opts) => {
1153
1154
  try {
1154
- const roleName = String(name ?? opts.role ?? '');
1155
+ const roleName = String(name ?? opts.name ?? '');
1155
1156
  if (!roleName)
1156
- throw new Error('role name is required (positional or --role)');
1157
- if (name && opts.role && name !== opts.role)
1158
- throw new Error(`positional role '${name}' conflicts with --role '${opts.role}'`);
1157
+ throw new Error('agent name is required (positional or --name)');
1158
+ if (name && opts.name && name !== opts.name)
1159
+ throw new Error(`positional agent name conflicts with --name`);
1159
1160
  const o = {
1160
- name: roleName, temp: opts.temp, harness: opts.harness, session: opts.session, mission: opts.mission,
1161
- missionFile: opts.missionFile,
1161
+ name: roleName, temp: opts.temp,
1162
+ brain: parseAgentSelection(opts.brain, 'brain'), role: parseAgentSelection(opts.role, 'role'),
1162
1163
  identity: opts.identity, cwd: opts.cwd, coordinator: opts.coordinator,
1163
- model: opts.model,
1164
- permissionMode: opts.permissionMode, approval: opts.approval,
1164
+ approval: opts.approval,
1165
1165
  filesystem: opts.filesystem, unattended: opts.unattended,
1166
- sandbox: opts.sandbox, profile: opts.profile,
1167
- launcher: opts.launcher, search: opts.search,
1168
- codexConfig: parseCodexConfig(opts.codexConfig), addDirs: opts.addDir, monitor: opts.monitor,
1169
- bioFile: opts.bioFile, personaFile: opts.personaFile,
1170
1166
  isolationFile: opts.isolationFile, configPath: opts.configuration,
1171
1167
  dryRun: opts.dryRun, json: opts.json,
1172
1168
  };
@@ -1195,7 +1191,7 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
1195
1191
  if (proxyStateDir) {
1196
1192
  // Paths entered in the agent shell belong to that shell's cwd, not the
1197
1193
  // supervisor process. Normalize before crossing the control boundary.
1198
- for (const key of ['missionFile', 'bioFile', 'personaFile', 'isolationFile']) {
1194
+ for (const key of ['isolationFile']) {
1199
1195
  if (o[key])
1200
1196
  o[key] = resolvePath(o[key]);
1201
1197
  }
@@ -1259,15 +1255,19 @@ cOpt(program.command('doctor').description('prerequisite report'))
1259
1255
  });
1260
1256
  for (const c of rep.checks)
1261
1257
  console.log(`${c.ok ? 'ok ' : 'MISS'} ${c.name.padEnd(22)} ${c.detail}`);
1262
- process.exit(rep.ok ? 0 : 1);
1258
+ if (!rep.ok)
1259
+ throw new FleetCliExit(1);
1263
1260
  });
1264
- program.command('init').description('one-time host setup (units, dirs, linger)')
1265
- .action(async () => {
1261
+ cOpt(program.command('init').description('idempotent host setup plus missing packaged presets'))
1262
+ .action(async (opts) => {
1266
1263
  for (const d of [agentsRoot(), tmpRoot(), logsRoot()])
1267
1264
  mkdirSync(d, { recursive: true });
1268
1265
  for (const m of await pickBackend().init(binPath))
1269
1266
  console.log(m);
1270
- console.log('\nNext: copy examples/fleet.yaml to ~/fleet.yaml, edit, then: ours-fleet up');
1267
+ const seeded = bootstrapPresets(opts.configuration);
1268
+ console.log(`Packaged preset revision ${seeded.revision}: ${seeded.sourceRoot}`);
1269
+ console.log(`Created ${seeded.created.length}; preserved ${seeded.preserved.length} existing file(s).`);
1270
+ console.log(INIT_COMPLETION_GUIDANCE);
1271
1271
  });
1272
1272
  const webCommand = cOpt(program.command('web').description('start or open the secure localhost fleet web console'))
1273
1273
  .enablePositionalOptions()
@@ -1508,4 +1508,84 @@ cOpt(program.command('_run-watchdogs', { hidden: true }))
1508
1508
  die(e);
1509
1509
  }
1510
1510
  });
1511
- program.parseAsync(process.argv);
1511
+ async function parseFleetCli() {
1512
+ const spawnIndex = process.argv.indexOf('spawn');
1513
+ if (spawnIndex >= 0 && process.argv.slice(spawnIndex + 1).some(arg => arg === '--harness' || arg.startsWith('--harness=') || arg === '--model' || arg.startsWith('--model='))) {
1514
+ console.error('error: --harness and --model were removed; select a Brain with --brain');
1515
+ throw new FleetCliExit(1);
1516
+ }
1517
+ try {
1518
+ await program.parseAsync(process.argv);
1519
+ }
1520
+ catch (error) {
1521
+ const commander = error;
1522
+ if (commander.exitCode === 0)
1523
+ return;
1524
+ if (typeof commander.exitCode === 'number')
1525
+ throw new FleetCliExit(commander.exitCode, 'validation', 'not_started');
1526
+ throw error;
1527
+ }
1528
+ }
1529
+ async function runFleetCli() {
1530
+ const stateDir = process.env[FLEET_PROXY_STATE_DIR_ENV];
1531
+ const caller = process.env[FLEET_PROXY_CALLER_ENV];
1532
+ if (!stateDir || !caller) {
1533
+ await parseFleetCli();
1534
+ return;
1535
+ }
1536
+ const requestId = randomUUID();
1537
+ let attempt;
1538
+ const begun = await controlRequest(stateDir, {
1539
+ command: 'fleet_audit_begin', audit: { requestId, argv: process.argv.slice(2) },
1540
+ });
1541
+ if (!begun.ok)
1542
+ throw new SessionControlError(begun.kind ?? 'rejected', begun.error ?? 'fleet command audit refused');
1543
+ attempt = begun.result;
1544
+ if (attempt.caller !== caller)
1545
+ throw new SessionControlError('rejected', `fleet audit caller mismatch: expected '${caller}', got '${attempt.caller}'`);
1546
+ let exitCode = 0;
1547
+ let outcomeClass = 'success';
1548
+ let effect = 'completed';
1549
+ beginFleetAuditCollection();
1550
+ if (attempt.classification.decision !== 'allow') {
1551
+ exitCode = 1;
1552
+ outcomeClass = 'denied';
1553
+ effect = 'not_started';
1554
+ console.error(`fleet supervisor proxy ${attempt.classification.decision}: ${attempt.classification.command}`);
1555
+ }
1556
+ else {
1557
+ const originalExit = process.exit;
1558
+ process.exit = ((code) => { throw new FleetCliExit(code ?? 0); });
1559
+ try {
1560
+ await parseFleetCli();
1561
+ }
1562
+ catch (error) {
1563
+ exitCode = error instanceof FleetCliExit ? error.exitCode : 1;
1564
+ outcomeClass = error instanceof FleetCliExit ? error.outcomeClass : 'runtime';
1565
+ effect = error instanceof FleetCliExit ? error.effect : 'unknown';
1566
+ if (!(error instanceof FleetCliExit))
1567
+ console.error(error instanceof Error ? error.message : String(error));
1568
+ }
1569
+ finally {
1570
+ process.exit = originalExit;
1571
+ }
1572
+ }
1573
+ const metadata = consumeFleetAuditCollection();
1574
+ if (metadata.failure) {
1575
+ outcomeClass = metadata.failure.class;
1576
+ effect = metadata.failure.effect;
1577
+ }
1578
+ const finished = await controlRequest(stateDir, { command: 'fleet_audit_finish', audit: {
1579
+ correlationId: attempt.correlationId, class: outcomeClass, exitCode, effect,
1580
+ ...(metadata.resourceIds ? { resourceIds: metadata.resourceIds } : {}),
1581
+ ...(metadata.presentations ? { presentations: metadata.presentations } : {}),
1582
+ } });
1583
+ if (!finished.ok)
1584
+ throw new SessionControlError(finished.kind ?? 'backend', finished.error ?? `audit delivery failed after execution; effect status=${effect}`);
1585
+ process.exitCode = exitCode;
1586
+ }
1587
+ void runFleetCli().catch(error => {
1588
+ if (!(error instanceof FleetCliExit))
1589
+ console.error(error instanceof Error ? error.message : String(error));
1590
+ process.exitCode = error instanceof FleetCliExit ? error.exitCode : 1;
1591
+ });
@@ -1,5 +1,5 @@
1
1
  export type YamlMode = 'compat' | 'strict';
2
- export type ConfigDiagnosticKind = 'anchor' | 'alias' | 'explicit-tag' | 'non-scalar-key' | 'multiple-documents';
2
+ export type ConfigDiagnosticKind = 'anchor' | 'alias' | 'explicit-tag' | 'non-scalar-key' | 'multiple-documents' | 'deprecated-field';
3
3
  export interface ConfigDiagnostic {
4
4
  severity: 'warning';
5
5
  kind: ConfigDiagnosticKind;
package/dist/config.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { IsolationConfig, WrapContext } from './isolation/types.js';
3
3
  import type { ResolvedWatchdog } from './watchdog/config.js';
4
4
  import type { ResolvedLoop, ResolvedRoleLoop } from './loops/config.js';
5
5
  export interface OverseeEntry {
6
- role: string;
6
+ agent: string;
7
7
  interval: string;
8
8
  }
9
9
  export interface WorklogPolicy {
@@ -25,7 +25,7 @@ export declare const NOTIFY_EVENT_TYPES: readonly ["message_received", "file_rec
25
25
  export type NotifyEventType = (typeof NOTIFY_EVENT_TYPES)[number];
26
26
  export type InjectMode = 'notification' | 'full';
27
27
  export type MonitorMode = 'fleet' | 'native';
28
- export type SessionBackendId = 'tmux' | 'acp';
28
+ export type SessionBackendId = 'acp';
29
29
  /** Public, harness-neutral permission policy. */
30
30
  export type FleetPermissionMode = 'ask' | 'auto' | 'allow';
31
31
  /** `deny` is a deprecated, fail-closed compatibility alias retained for old fleet files. */
@@ -44,9 +44,6 @@ export interface SessionOptions {
44
44
  /** ACP agent command and arguments. Defaults are supplied by the harness adapter. */
45
45
  command?: string | string[];
46
46
  };
47
- tmux?: {
48
- boot_grace_ms?: number;
49
- };
50
47
  }
51
48
  /** Resolved per-role supervisor-monitor config. */
52
49
  export interface MonitorConfig {
@@ -126,6 +123,8 @@ export interface RoleConfig {
126
123
  briefing_file?: string;
127
124
  /** Explicit null means use the selected harness's own default, bypassing fleet defaults. */
128
125
  model?: string | null;
126
+ /** Neutral Brain reasoning effort, retained for inspection after adapter translation. */
127
+ effort?: string;
129
128
  model_chain?: string[];
130
129
  max_tokens?: number;
131
130
  autocompact_pct?: number;
@@ -138,6 +137,27 @@ export interface RoleConfig {
138
137
  worklog?: WorklogPolicyInput;
139
138
  auth_proxy?: Partial<AuthProxyConfig>;
140
139
  }
140
+ export type AgentSelection<T extends Record<string, unknown> = Record<string, unknown>> = {
141
+ ref: string;
142
+ } | {
143
+ inline: T;
144
+ };
145
+ /** Canonical authoring contract shared by configured, spawned, and room agents. */
146
+ export interface AgentDefinition {
147
+ role: AgentSelection;
148
+ brain: AgentSelection;
149
+ permissions?: Partial<CommonPermissions>;
150
+ identity?: string;
151
+ cwd?: string;
152
+ coordinator?: string;
153
+ env?: Record<string, string>;
154
+ oversee?: OverseeEntry[];
155
+ isolation?: IsolationConfig;
156
+ monitor?: Partial<MonitorConfig>;
157
+ owner_channel?: OwnerChannelConfigInput;
158
+ worklog?: WorklogPolicyInput;
159
+ auth_proxy?: Partial<AuthProxyConfig>;
160
+ }
141
161
  /** Internal first-boot payload for a Fleet-provisioned Cowork room member. */
142
162
  export interface RoomMemberStartup {
143
163
  room_id: string;
@@ -169,14 +189,41 @@ export interface ResolvedRole extends Omit<RoleConfig, 'model' | 'owner_channel'
169
189
  worklog?: WorklogPolicy;
170
190
  auth_proxy?: AuthProxyConfig;
171
191
  loops?: ResolvedRoleLoop[];
192
+ provenance?: Record<string, FieldProvenance>;
172
193
  /** Internal/transient: never accepted as a user-authored RoleConfig key. */
173
194
  roomMemberStartup?: RoomMemberStartup;
195
+ /** Original unresolved selections only; denied operational fields never enter inheritance state. */
196
+ agentSelections?: Pick<AgentDefinition, 'role' | 'brain'>;
197
+ }
198
+ export interface FieldProvenance {
199
+ sourceFile: string;
200
+ sourcePointer: string;
201
+ sourceKind: 'Agent' | 'Role' | 'Brain' | 'Manifest' | 'built-in';
202
+ sourceId?: string;
203
+ origin: 'explicit' | 'typed-default' | 'built-in';
204
+ viaReference?: {
205
+ sourcePointer: string;
206
+ kind: 'Role' | 'Brain';
207
+ id: string;
208
+ };
209
+ transforms: Array<{
210
+ kind: 'substitution' | 'adapter-normalization' | 'validation-default';
211
+ detail: string;
212
+ }>;
174
213
  }
175
214
  export interface FleetConfig {
176
215
  roles: ResolvedRole[];
216
+ /** Canonical, variable-resolved Agent authoring documents keyed by stable Agent ID. */
217
+ agentDefinitions?: Record<string, AgentDefinition>;
177
218
  vars: Record<string, string>;
178
219
  defaults: Record<string, unknown>;
179
220
  files: string[];
221
+ configMode?: 'split-v2';
222
+ sourceDocuments?: Array<{
223
+ kind: 'Manifest' | 'Agent' | 'Role' | 'Brain' | 'RoomTemplate';
224
+ id?: string;
225
+ path: string;
226
+ }>;
180
227
  /** Fleet-wide delay (ms) enforced between agent launches to avoid boot bursts (0 = none). */
181
228
  startStaggerMs: number;
182
229
  /** Warning-first non-plain YAML migration diagnostics, in source order. */
@@ -207,9 +254,22 @@ export declare function resolveRoleModel(model: string | null | undefined, harne
207
254
  */
208
255
  export declare function isolationContextFor(role: ResolvedRole): WrapContext;
209
256
  export declare const ROLE_NAME_RE: RegExp;
210
- /** Load ~/fleet.yaml (or an explicit path) merged with ~/fleet.d/*.yaml drop-ins. */
257
+ export declare const ROLE_PRESET_KEYS: string[];
258
+ export declare const BRAIN_PRESET_KEYS: string[];
259
+ export declare const AGENT_KEYS: string[];
260
+ type BarePreset = Record<string, unknown>;
261
+ export declare function validateRoleValue(value: BarePreset, file: string, pointer: string): void;
262
+ export declare function validateBrainValue(value: BarePreset, file: string, pointer: string): void;
263
+ export declare function assertBareKeys(value: unknown, allowed: string[], label: string): BarePreset;
264
+ export declare function splitRootFor(base: string): string;
265
+ /** Load a v2 manifest and bare Agent/Role/Brain documents from its stem directory. */
211
266
  export declare function loadConfig(configPath?: string, options?: {
212
267
  yamlMode?: YamlMode;
268
+ additionalAgent?: {
269
+ id: string;
270
+ definition: AgentDefinition;
271
+ };
272
+ skipWatchdogs?: boolean;
213
273
  }): FleetConfig;
214
274
  /**
215
275
  * Canonical form of a 64-hex container ID for authorization decisions. Hex