@ours.network/fleet 1.1.0-nightly.3 → 1.1.0-nightly.30

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 (208) hide show
  1. package/README.md +244 -40
  2. package/dist/agent-recovery-gate.d.ts +17 -0
  3. package/dist/agent-recovery-gate.js +135 -0
  4. package/dist/application/capabilities.js +18 -6
  5. package/dist/application/fleet-query-service.js +20 -5
  6. package/dist/application/role-command-service.js +9 -0
  7. package/dist/application/role-creation-service.d.ts +4 -4
  8. package/dist/application/role-creation-service.js +19 -1
  9. package/dist/application/role-repository.d.ts +4 -2
  10. package/dist/application/role-repository.js +6 -3
  11. package/dist/application/session-control.d.ts +3 -1
  12. package/dist/application/session-control.js +4 -2
  13. package/dist/application/task-room-service.d.ts +93 -40
  14. package/dist/application/task-room-service.js +579 -173
  15. package/dist/briefing.js +85 -24
  16. package/dist/build-info.json +4 -4
  17. package/dist/canonical-json.d.ts +2 -0
  18. package/dist/canonical-json.js +10 -0
  19. package/dist/cli.js +217 -51
  20. package/dist/config-yaml.d.ts +1 -1
  21. package/dist/config.d.ts +38 -3
  22. package/dist/config.js +240 -28
  23. package/dist/creation.d.ts +1 -1
  24. package/dist/creation.js +7 -4
  25. package/dist/daemon-recovery.d.ts +93 -0
  26. package/dist/daemon-recovery.js +328 -0
  27. package/dist/docs.d.ts +1 -1
  28. package/dist/docs.js +213 -42
  29. package/dist/doctor.d.ts +2 -0
  30. package/dist/doctor.js +54 -4
  31. package/dist/fleet-command-audit.d.ts +181 -0
  32. package/dist/fleet-command-audit.js +738 -0
  33. package/dist/fleet-proxy.d.ts +7 -2
  34. package/dist/harness/agent-session.d.ts +5 -0
  35. package/dist/harness/claude-code-session.d.ts +2 -0
  36. package/dist/harness/claude-code-session.js +6 -1
  37. package/dist/harness/claude-code.js +2 -0
  38. package/dist/harness/codex-app-server-proxy.d.ts +40 -0
  39. package/dist/harness/codex-app-server-proxy.js +348 -14
  40. package/dist/harness/codex-session.d.ts +11 -2
  41. package/dist/harness/codex-session.js +22 -2
  42. package/dist/harness/codex.d.ts +5 -1
  43. package/dist/harness/codex.js +110 -10
  44. package/dist/harness/types.d.ts +1 -1
  45. package/dist/index.d.ts +4 -3
  46. package/dist/index.js +3 -2
  47. package/dist/init-guidance.d.ts +1 -1
  48. package/dist/init-guidance.js +4 -3
  49. package/dist/init-wizard.d.ts +98 -0
  50. package/dist/init-wizard.js +631 -0
  51. package/dist/lifecycle-summary.d.ts +134 -0
  52. package/dist/lifecycle-summary.js +179 -0
  53. package/dist/loops/config.d.ts +10 -1
  54. package/dist/loops/config.js +38 -5
  55. package/dist/model-recovery.d.ts +1 -1
  56. package/dist/ops.js +2 -17
  57. package/dist/owner-channel/channel.d.ts +75 -3
  58. package/dist/owner-channel/channel.js +531 -123
  59. package/dist/owner-channel/commands.d.ts +28 -6
  60. package/dist/owner-channel/commands.js +112 -36
  61. package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
  62. package/dist/owner-channel/lifecycle-outbox.js +87 -0
  63. package/dist/owner-channel/ours-client.d.ts +19 -0
  64. package/dist/owner-channel/ours-client.js +3 -0
  65. package/dist/owner-channel/state.d.ts +2 -0
  66. package/dist/owner-channel/state.js +8 -2
  67. package/dist/permissions.js +1 -1
  68. package/dist/preset-bootstrap.d.ts +10 -0
  69. package/dist/preset-bootstrap.js +85 -0
  70. package/dist/preset-migration.d.ts +42 -0
  71. package/dist/preset-migration.js +450 -0
  72. package/dist/reports/artifact.d.ts +10 -0
  73. package/dist/reports/artifact.js +32 -0
  74. package/dist/reports/index.d.ts +5 -0
  75. package/dist/reports/index.js +5 -0
  76. package/dist/reports/render.d.ts +6 -0
  77. package/dist/reports/render.js +240 -0
  78. package/dist/reports/service.d.ts +42 -0
  79. package/dist/reports/service.js +57 -0
  80. package/dist/reports/tasks.d.ts +52 -0
  81. package/dist/reports/tasks.js +177 -0
  82. package/dist/reports/types.d.ts +315 -0
  83. package/dist/reports/types.js +6 -0
  84. package/dist/resolved-plan.js +13 -1
  85. package/dist/rooms-tasks/cli.d.ts +2 -0
  86. package/dist/rooms-tasks/cli.js +519 -261
  87. package/dist/rooms-tasks/close.d.ts +9 -1
  88. package/dist/rooms-tasks/close.js +23 -7
  89. package/dist/rooms-tasks/config.js +16 -29
  90. package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
  91. package/dist/rooms-tasks/cowork-adapter.js +13 -0
  92. package/dist/rooms-tasks/deletion.d.ts +51 -0
  93. package/dist/rooms-tasks/deletion.js +217 -0
  94. package/dist/rooms-tasks/external-worker.d.ts +9 -0
  95. package/dist/rooms-tasks/external-worker.js +52 -9
  96. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  97. package/dist/rooms-tasks/launch-snapshot.js +181 -0
  98. package/dist/rooms-tasks/member-overrides.d.ts +30 -0
  99. package/dist/rooms-tasks/member-overrides.js +204 -0
  100. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  101. package/dist/rooms-tasks/member-startup.js +9 -2
  102. package/dist/rooms-tasks/provision.js +146 -56
  103. package/dist/rooms-tasks/room-state.d.ts +1 -0
  104. package/dist/rooms-tasks/room-state.js +12 -1
  105. package/dist/rooms-tasks/task-room-name.d.ts +12 -0
  106. package/dist/rooms-tasks/task-room-name.js +61 -0
  107. package/dist/rooms-tasks/task-state.d.ts +115 -3
  108. package/dist/rooms-tasks/task-state.js +361 -11
  109. package/dist/rooms-tasks/templates.d.ts +4 -2
  110. package/dist/rooms-tasks/templates.js +42 -78
  111. package/dist/rooms-tasks/terminal.d.ts +3 -0
  112. package/dist/rooms-tasks/terminal.js +10 -4
  113. package/dist/rooms-tasks/types.d.ts +94 -8
  114. package/dist/rooms-tasks/types.js +14 -1
  115. package/dist/runner.d.ts +12 -2
  116. package/dist/runner.js +167 -56
  117. package/dist/session/acp.d.ts +9 -1
  118. package/dist/session/acp.js +35 -9
  119. package/dist/session/arbiter.d.ts +2 -1
  120. package/dist/session/arbiter.js +2 -0
  121. package/dist/session/codex-app-server-transport.d.ts +53 -0
  122. package/dist/session/codex-app-server-transport.js +184 -0
  123. package/dist/session/codex-app-server.d.ts +122 -0
  124. package/dist/session/codex-app-server.js +1031 -0
  125. package/dist/session/control.d.ts +14 -1
  126. package/dist/session/control.js +51 -0
  127. package/dist/session/conversation-types.d.ts +4 -4
  128. package/dist/session/types.d.ts +24 -6
  129. package/dist/session/types.js +28 -0
  130. package/dist/spawn.d.ts +9 -0
  131. package/dist/spawn.js +77 -9
  132. package/dist/temp-lifecycle.d.ts +1 -1
  133. package/dist/web/fleet-config-service.d.ts +1 -0
  134. package/dist/web/fleet-config-service.js +27 -8
  135. package/dist/web/runtime.js +11 -6
  136. package/dist/web/server.js +28 -1
  137. package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
  138. package/dist/web-app/index.html +1 -1
  139. package/package.json +5 -4
  140. package/presets/brain-catalog.json +17 -0
  141. package/presets/fleet/agent_templates/Critic.yaml +5 -0
  142. package/presets/fleet/agent_templates/Developer.yaml +5 -0
  143. package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
  144. package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
  145. package/presets/fleet/brains/claude-default.yaml +5 -0
  146. package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
  147. package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
  148. package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
  149. package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
  150. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
  151. package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
  152. package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
  153. package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
  154. package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
  155. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
  156. package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
  157. package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
  158. package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
  159. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
  160. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
  161. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
  162. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
  163. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
  164. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
  165. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
  166. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
  167. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
  168. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
  169. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
  170. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
  171. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
  172. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
  173. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
  174. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
  175. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
  176. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
  177. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
  178. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
  179. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
  180. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
  181. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
  182. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
  183. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
  184. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
  185. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
  186. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
  187. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
  188. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
  189. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
  190. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
  191. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
  192. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
  193. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
  194. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
  195. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
  196. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
  197. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
  198. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
  199. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
  200. package/presets/fleet/roles/Coordinator.yaml +30 -0
  201. package/presets/fleet/roles/Critic.yaml +28 -0
  202. package/presets/fleet/roles/Developer.yaml +28 -0
  203. package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
  204. package/presets/fleet/room_templates/pair.yaml +13 -0
  205. package/presets/fleet/room_templates/single.yaml +9 -0
  206. package/presets/fleet/room_templates/team.yaml +16 -0
  207. package/presets/fleet.yaml +7 -0
  208. package/presets/manifest.json +6 -0
package/README.md CHANGED
@@ -8,7 +8,7 @@ harnesses — from one declarative file.**
8
8
  An AI coding agent in a terminal dies when you close the laptop. `ours-fleet`
9
9
  turns such sessions into **roles**: long-lived agents that
10
10
 
11
- - **run through structured ACP sessions** behind a shared Codex/Claude Code
11
+ - **run through structured managed sessions** behind one provider-neutral
12
12
  agent-session interface, which you can inspect or prompt,
13
13
  - are **supervised** — systemd (Linux) or launchd (macOS) restarts them on crash
14
14
  and brings them back after a reboot,
@@ -37,16 +37,16 @@ brain: { inline: { harness: claude-code, session: acp } }
37
37
 
38
38
  # ~/fleet/agents/Prototyper.yaml
39
39
  role: { inline: { mission: Build prototypes } }
40
- brain: { inline: { harness: codex, session: acp } }
40
+ brain: { inline: { harness: codex, session: codex-app-server } }
41
41
  ```
42
42
 
43
43
  ## How it works
44
44
 
45
45
  ```
46
- ~/fleet.yaml + ~/fleet/{agents,roles,brains}/*.yaml your declaration
46
+ ~/fleet.yaml + ~/fleet/{agents,agent_templates,roles,brains,room_templates}/*.yaml your declaration
47
47
  │ ours-fleet up
48
48
  ▼
49
- briefing.md per role ──► agent session adapter ──► ACP client/session ──► ACP agent
49
+ briefing.md per role ──► agent session adapter ──► native app-server or ACP transport
50
50
  ▲ │
51
51
  systemd --user / launchd ───────┘ restart on crash, start at boot/login
52
52
  ```
@@ -72,14 +72,13 @@ The state dir contract:
72
72
  | `.owner-channel-message-recovery.json` | owner-channel bridge | mode-0600 body-free message claim journal: wire ID, history sequence, and claim time only |
73
73
  | `.owner-channel-attachment-recovery.json` | owner-channel bridge | mode-0600 attachment route journal; never filenames, paths, transcript text, or bytes |
74
74
  | `.owner-channel-binder.lock/`, `.owner-channel-binder.json` | owner-channel supervisor | mode-0600 role/identity + PID/start-marker ownership and release metadata; never mail plaintext or credentials |
75
- | `.session-events.jsonl`, `.control.sock`, `.control-token` | ACP backend | bounded typed console projection and private attachment control |
75
+ | `.session-events.jsonl`, `.control.sock`, `.control-token` | managed session backend | bounded typed console projection and private attachment control |
76
76
 
77
77
  ## Prerequisites
78
78
 
79
79
  | What | Why | Install |
80
80
  |---|---|---|
81
- | Node ≥ 20 | runs `ours-fleet` itself | nodejs.org, `apt`, or `brew` |
82
- | Node ≥ 22 | Claude roles using `session: acp` | required by the maintained Claude ACP adapter |
81
+ | Node ≥ 22 | runs `ours-fleet` and its maintained adapters | nodejs.org, `apt`, or `brew` |
83
82
  | a harness CLI, logged in | the agent itself | e.g. Claude Code (`claude`) or Codex CLI (`codex`) |
84
83
  | `ours` CLI + shared daemon | identity + agent-to-agent messaging | `npm i -g @ours.network/cli && ours daemon start` |
85
84
 
@@ -91,15 +90,16 @@ equivalent); logs land in `~/.ours-fleet/logs/`.
91
90
 
92
91
  ```sh
93
92
  npm i -g @ours.network/fleet
94
- ours-fleet init # units/dirs/linger for this user
93
+ ours-fleet init # interactive reviewed defaults + host setup
95
94
  ours-fleet doctor # verifies everything above, with actionable messages
96
95
  ```
97
96
 
98
- The maintained Codex and Claude ACP adapters install as optional dependencies of
99
- `ours-fleet` and are resolved internally; users do not install adapter commands
100
- or add them to `PATH`. An explicit `session_options.acp.command` remains
101
- available for custom adapters. On Node 20–21, Codex ACP remains available,
102
- while maintained Claude ACP requires upgrading to Node 22.
97
+ Native Codex roles use the logged-in Codex CLI's `app-server` command directly.
98
+ The maintained Codex and Claude ACP adapters remain bundled optional dependencies
99
+ and are resolved internally; users do not install adapter commands or add them to
100
+ `PATH`. Explicit `session_options.codex_app_server.command` and
101
+ `session_options.acp.command` overrides remain available. ours-fleet and its
102
+ maintained adapters require Node 22 or newer.
103
103
 
104
104
  Each OS user manages their own fleet — to host roles under a sandboxed account,
105
105
  become that account and repeat.
@@ -107,8 +107,8 @@ become that account and repeat.
107
107
  ## Quickstart
108
108
 
109
109
  ```sh
110
- cp -R "$(npm root -g)/@ours.network/fleet/examples/fleet" ~/fleet
111
- cp "$(npm root -g)/@ours.network/fleet/examples/fleet.yaml" ~/fleet.yaml
110
+ ours-fleet init # interactive; adds missing defaults and preserves existing files
111
+ ours-fleet config # validates Agents, Roles, Brains, and Room templates
112
112
  $EDITOR ~/fleet/agents/*.yaml # compose Role + Brain and operational settings
113
113
  ours-fleet up # boot the fleet (staggered)
114
114
  ours-fleet ls # running consoles
@@ -130,7 +130,7 @@ ours-fleet spawn Coder --brain codex-fleet --role developer \
130
130
  --approval ask --filesystem workspace --coordinator FleetCoordinator
131
131
  ```
132
132
 
133
- Inside a managed ACP role, `ours-fleet spawn` is transparently routed through
133
+ Inside a managed role, `ours-fleet spawn` is transparently routed through
134
134
  that role's live supervisor. `ours-fleet spawn DeveloperX --temp` is the
135
135
  minimal form: omitted Brain/Role selections, cwd, coordinator, neutral permissions,
136
136
  and fleet monitor policy inherit from the caller. Identity, environment, owner routing,
@@ -356,9 +356,82 @@ defaults:
356
356
  max_kb: 1024 # rotate only above this active-log size
357
357
  keep_tail_kb: 256 # UTF-8 tail; line-aligned when one fits
358
358
  max_archives: 12 # recent beside log; older preserved cold
359
- Agent document (\`~/fleet/agents/Name.yaml\`):
360
- role: { ref: RoleID }
361
- brain: { ref: BrainID } # Brain owns harness/model/session/reasoning
359
+ ```
360
+
361
+ Agent Template document (\`~/fleet/agent_templates/Worker.yaml\`) is inert and reusable:
362
+
363
+ ```yaml
364
+ role: { ref: developer }
365
+ brain: { ref: claude-default }
366
+ permissions: { approval: ask, filesystem: workspace, unattended: deny }
367
+ monitor: { mode: fleet, interrupt: after_tool }
368
+ loops: # optional; temporary launches only
369
+ progress:
370
+ interval: 10m # required; 1m..30d
371
+ initial_delay: 10m # default: interval; 0s..30d
372
+ jitter: 30s # default: 0s; < interval and <=1h
373
+ enabled: true # default true
374
+ prompt: Continue the assigned work and report only material progress.
375
+ ```
376
+
377
+ Agent Template-local `loops` are limited to 64 entries, implicitly scoped to the temporary agent, and
378
+ reuse Fleet's scheduled-turn semantics: a busy occurrence is skipped, ordinary
379
+ missed occurrences are not backlogged or replayed, and restart recovery may
380
+ preserve at most one recent late occurrence. The trusted authoring file and
381
+ private sealed role snapshot retain exact prompt text; resolved launch, task,
382
+ room, provenance, and audit presentation surfaces show only bytes and SHA-256.
383
+
384
+ Every packaged `Developer`, `Critic`, and `LocalCoordinator` Agent Template uses
385
+ `monitor.mode: fleet` with `monitor.interrupt: after_tool`. Consequently every
386
+ member of the standard `single`, `pair`, and `team` Room Templates resolves to
387
+ that policy. A custom Agent Template or explicit per-member override remains
388
+ authoritative and is merged key by key without rewriting unrelated values.
389
+
390
+ Direct temporary spawn can set the same whole block from an owner-only regular
391
+ file containing exactly a top-level `loops:` mapping, or explicitly disable it:
392
+
393
+ ```sh
394
+ ours-fleet spawn --temp Scout --role 'inline:{mission: inspect}' --brain 'inline:{harness: codex}' --loops-file ./scout-loops.yaml
395
+ ours-fleet spawn --temp Scout --role 'inline:{mission: inspect}' --brain 'inline:{harness: codex}' --no-loops
396
+ ours-fleet task start TASK --member developer --loops-file ./developer-loops.yaml
397
+ ours-fleet task start TASK --member critic --no-loops
398
+ ```
399
+
400
+ `--loops-file` and `--no-loops` are mutually exclusive and temporary-only.
401
+ For room members they override the selected Agent Template as a whole; the
402
+ Agent Template overrides legacy omission. Omission preserves the historical
403
+ no-loop temporary behavior and never inherits manifest `roles: ["*"]` loops.
404
+ The file must be owned by the current user, mode 0600, non-symlink, no larger
405
+ than 1 MB, and contain a non-empty map. Validation completes before identity,
406
+ Cowork, snapshot, or supervisor side effects. Recovery and replacement reuse
407
+ the private sealed normalized definition even if mutable templates later change.
408
+
409
+ Persistent Agent instance (\`~/fleet/agents/Name.yaml\`) either remains a canonical
410
+ Role/Brain composition or explicitly reuses a template:
411
+
412
+ ```yaml
413
+ template: Worker
414
+ overrides:
415
+ cwd: /work/name
416
+ permissions: { approval: allow }
417
+ ```
418
+
419
+ Templates never create identities, supervisors, sessions, or lifecycle/status rows.
420
+ Map overrides merge recursively; scalars and arrays replace; null deletion is rejected.
421
+
422
+ Canonical Agent document (\`~/fleet/agents/Name.yaml\`):
423
+
424
+ ```yaml
425
+ role: { ref: RoleID }
426
+ brain: { ref: BrainID } # Brain owns harness/model/session/reasoning
427
+ identity: "Display Name" # ours identity to bind (default: Name)
428
+ cwd: ${work_root}/repo # where the harness process runs
429
+ coordinator: FleetCoordinator # announce target on boot
430
+ ```
431
+
432
+ Additional operational field inventory (schematic, not one YAML document):
433
+
434
+ ```text
362
435
  identity: "Display Name" # ours identity to bind (default: Name)
363
436
  cwd: ${work_root}/repo # where the harness process runs
364
437
  coordinator: FleetCoordinator # announce target on boot
@@ -370,13 +443,13 @@ Agent document (\`~/fleet/agents/Name.yaml\`):
370
443
  - file_received # local_contact_request, pending_message)
371
444
  batch_ms: 2000 # coalesce a burst into one line (default 2000)
372
445
  inject: notification # notification (default) | full (bodies inline; roadmap)
373
- owner_channel: # optional trusted owner ingress; requires session: acp
446
+ owner_channel: # optional trusted owner ingress; requires a managed session
374
447
  identity: "Name Owner Channel" # dedicated identity; fleet creates it with safe local defaults
375
448
  owners: [owner-contact-cid] # authenticated ours contact IDs, never display names
376
449
  agent: managed-agent-cid # exact role CID allowed to relay messages/files outward
377
450
  interrupt: false # false queues; true cancels current work first
378
451
  progress_interval_ms: 30000 # fleet-generated progress notices; 0 disables
379
- comments: true # relay live "🟡 Live update:" ACP commentary (default true);
452
+ comments: true # relay live "🟡 Live update:" structured commentary (default true);
380
453
  # restart baseline for /comments on|off
381
454
  attachments: # secure inbound documents, images, and voice
382
455
  enabled: true
@@ -396,13 +469,10 @@ Agent document (\`~/fleet/agents/Name.yaml\`):
396
469
  # mem_palace: false # claude-code: disable memory plugin
397
470
  # permission_mode: dontAsk # claude-code: launch permission mode —
398
471
  # one of default | acceptEdits | plan | dontAsk | bypassPermissions
399
- # sandbox: workspace-write # codex: --sandbox —
400
- # one of read-only | workspace-write | danger-full-access
401
- # approval: never # codex: --ask-for-approval —
402
- # one of untrusted | on-request | never
403
- # permission_mode: never # codex alias for approval
472
+ # Codex ACP compatibility only: native codex-app-server rejects Codex
473
+ # permission overrides here; use the common permissions block above.
404
474
  # launcher: auto # codex: auto | ours-codex | codex
405
- # profile: fleet # codex: $CODEX_HOME/fleet.config.toml
475
+ # profile: fleet # codex ACP only: $CODEX_HOME/fleet.config.toml
406
476
  # search: true # codex: enable --search (live web search)
407
477
  # add_dirs: [/data/shared] # codex: repeat --add-dir
408
478
  # monitor: true # explicit persistent consent to arm mail wake
@@ -442,6 +512,72 @@ installer/setup flows remain responsible for starting it.
442
512
 
443
513
  ### Rooms and tasks
444
514
 
515
+ Init installs editable `single`, `pair`, and `team` definitions under
516
+ `~/fleet/room_templates/`, plus every exact-cased Agent, Role, and Brain they
517
+ reference. Inspect them with `ours-fleet template list` and
518
+ `ours-fleet template show team`. After configuring the authenticated owner below:
519
+
520
+ ```sh
521
+ ours-fleet task create --title "Solo task" --template single
522
+ ours-fleet task create --title "Reviewed change" --template pair
523
+ ours-fleet task create --title "Phased delivery" --template team
524
+ ```
525
+
526
+ The default `team` gives only its LocalCoordinator Agent Template a 15-minute
527
+ continuity loop. Each idle-only pass uses authenticated assigned-room evidence,
528
+ posts at most one missing interval update or status request, and reports `STALLED`
529
+ only after concrete stopped/blocked evidence or the role's documented request/ETA
530
+ window. Stable history/checkpoint fingerprints suppress duplicates; `single` and
531
+ `pair` remain loop-free. This preset does not widen permissions: task planning and
532
+ launch still fail closed when the selected harness cannot supply the configured
533
+ unattended messaging and checkpoint capabilities.
534
+
535
+ `ours-fleet init -c /path/custom.yaml` reviews the literal resolved manifest and
536
+ `/path/custom/` split directory before doing anything. It requires a TTY and two
537
+ default-No confirmations. You select Codex, Claude, or both; then either one explicit
538
+ model for every job or explicit development/review/coordination models; then one
539
+ Quick/Balanced/Thorough reasoning level (`low`/`medium`/`high`). Catalog entries are
540
+ supported IDs, not recommendations or entitlement claims: use `ours-fleet doctor` for
541
+ local Codex availability, while Claude entitlement is validated at launch. No
542
+ `model_chain` is generated and Fleet never silently substitutes another model.
543
+
544
+ A successful rerun adds any missing packaged defaults while preserving every existing
545
+ manifest, Role, Agent Template, and Room Template byte. Explicit adoption of newer defaults
546
+ is a separate reviewable migration. At either confirmation,
547
+ N, Enter, Escape, Ctrl-C, Ctrl-D, or EOF cancels. In a picker, Escape, Ctrl-C, Ctrl-D,
548
+ or EOF cancels; Enter records the highlighted choice (or continues a non-empty multi-select),
549
+ N is ignored, and an empty subscription selection remains blocked. Every cancellation
550
+ before the final Yes performs no host setup or config publication. Non-TTY use stops with
551
+ the same zero-mutation guarantee. After the final Yes, host integration runs before locked
552
+ publication; a hard kill, power loss, or host crash can therefore leave host integration
553
+ or private stage/recovery evidence to inspect.
554
+ Existing targets and their tree must be owner-private regular files/directories on the
555
+ configuration parent's filesystem; symlinks, foreign ownership, unsafe modes, and a
556
+ non-owner-controlled parent fail closed before host setup and are rechecked under the
557
+ per-setup init lock before publication.
558
+
559
+ The generated task experiences are fixed consequences, not extra questions: `single`
560
+ uses `Developer`; `pair` uses `Developer` with an independent `Critic`; and `team` uses
561
+ task-local `LocalCoordinator`, `Developer`, and `Critic`. The persistent
562
+ `FleetCoordinator` uses the separate packaged `Coordinator` contract and coordination
563
+ model.
564
+
565
+ Exact known revision-3 through revision-5 role and Agent Template defaults have an
566
+ explicit fail-closed adoption command: first run
567
+ `ours-fleet migrate-role-defaults -c FILE` for a zero-write plan, review every removal,
568
+ replacement, addition, preserved custom file, staging path, and recovery path, then rerun
569
+ with `--write`. Only exact semantic matches for packaged-bootstrap and generated
570
+ revision-3/4/5 forms are changed; same-named customized files remain byte-identical, and a
571
+ dangling custom reference refuses publication. Rerunning the migration is a no-op.
572
+
573
+ For manual adoption of a single newer packaged file, copy the reported source beside the
574
+ target as `.new-default`, inspect `diff -u TARGET TARGET.new-default`, then replace the
575
+ target yourself. Rerunning init is not an update. Users with the still older exact generated
576
+ six-worker starter Agent set must first run `ours-fleet migrate-agent-templates -c FILE`
577
+ for its zero-write plan, review every move/addition/recovery path, and rerun with `--write`.
578
+ Customized or ambiguous known starters are refused without mutation; unrelated custom
579
+ persistent Agents are preserved.
580
+
445
581
  Rooms always use `ours-cowork`; there is no room-provider selector. Configure
446
582
  the cowork daemon connection and the room owner directly:
447
583
 
@@ -467,8 +603,37 @@ Fleet launches each template member with a dedicated one-time Cowork invite.
467
603
  The generated temporary-agent briefing contains the exact identity name, invite,
468
604
  Cowork role, and task. The agent creates that identity itself with ours MCP
469
605
  `create_temporary_identity`, accepts the invite with `add_contact`, and starts
470
- work immediately. Fleet activates the room from Cowork's authenticated seat; there
471
- is no briefing hash, startup ACK, or separate role-briefing readiness gate.
606
+ work immediately. A room is ready only after the Cowork room (and its task, when
607
+ task-bound) is durably active, every configured member seat is authenticated and active with its matching
608
+ live Fleet launch, and the configured Owner seat is active when owner attachment is
609
+ enabled. There is no briefing hash, startup ACK, or separate role-briefing readiness
610
+ gate.
611
+
612
+ Normal Task provisioning emits exactly one authenticated Owner lifecycle notice after
613
+ the ready predicate above is true; standalone Room provisioning likewise emits one
614
+ ready notice. Intermediate task, saga, member-spawn, timeout, and recoverable-failure
615
+ transitions stay in local state and logs. A terminal failed Task emits one actionable
616
+ failure notice; the command result carries the exact blocker and canonical recovery
617
+ action.
618
+
619
+ `task start` and create-and-start wait for readiness. If their bounded wait expires,
620
+ they return an explicit `in_progress` result and start a safe continuation. The detached
621
+ continuation is serialized per Task and remains alive until convergence or an
622
+ Owner-action blocker. Re-running `task start <id>` safely resumes the same durable
623
+ provisioning operation after the blocker is corrected or a process restarts.
624
+
625
+ Set `room.anonymous: true` on a room template, or pass `--anonymous` to
626
+ `task create`, `task start`, `task work`, or `room create`, to create an
627
+ anonymous Cowork room. `--no-anonymous` explicitly overrides an anonymous
628
+ template. Fleet records the resolved value before room creation so retries keep
629
+ the same choice. Temporary members of an anonymous room are instructed to call
630
+ `create_temporary_identity` with `expose_local=false`. Their generated briefings
631
+ do not disclose or compare an Owner participant CID. A participant-originated
632
+ instruction has Owner authority only when the authenticated Cowork room envelope
633
+ attributes that participant seat the exact `Owner` role. Literal text, display
634
+ names, ordinary direct messages, and room-authored or rest-role messages with an
635
+ Owner-looking label never grant that authority. Non-anonymous rooms remain pinned
636
+ to the exact authenticated Owner CID.
472
637
 
473
638
  Human task and room results use the same compact Markdown presentation in the
474
639
  CLI and authenticated owner channel: a short heading, icon-plus-word status,
@@ -488,8 +653,9 @@ optional `rooms.owner.provider` setting is separate and defaults to
488
653
  ### Scheduled agent loops
489
654
 
490
655
  Top-level `loops` schedule literal prompts from trusted local YAML. Enabled targets
491
- must use `session: acp`; temporary roles never inherit loops, including `roles:
492
- ["*"]`. Fleet rejects an enabled loop when its explicitly selected base config is
656
+ must use a managed session (`acp` or `codex-app-server`); temporary roles never
657
+ inherit these manifest loops, including `roles: ["*"]`. Only their sealed Agent
658
+ Template/CLI-local `loops` apply. Fleet rejects an enabled loop when its explicitly selected base config is
493
659
  a symlink, is owned by another user, or is group/world writable. Prompts are
494
660
  bounded and normalized at validation time, but only their size and SHA-256 appear
495
661
  in `config`, `list`, logs, or durable state.
@@ -575,11 +741,13 @@ native settings actually grant, against a fixed floor:
575
741
  those requests with nobody to see it. With `unattended: wait` it **warns**,
576
742
  since a human can still attach a console and answer.
577
743
 
578
- **Security meaning.** `ask` maps to Codex `untrusted` / Claude `default`.
579
- `auto` selects Codex ACP `agent` (`on-request` + `workspace-write`) / Claude
580
- `acceptEdits`. `allow` selects Codex ACP's fully non-interactive yolo mode,
581
- reported by the adapter as `agent-full-access` (`never` +
582
- `danger-full-access`); Claude uses `bypassPermissions`.
744
+ **Security meaning.** For native Codex, Fleet translates `ask` → `untrusted`,
745
+ `auto` → `on-request`, and `allow` → `never`; users configure only the Fleet
746
+ names. `permissions.filesystem` independently maps `read-only` → `read-only`,
747
+ `workspace` → `workspace-write`, and `unrestricted` → `danger-full-access`.
748
+ Claude maps `ask` to `default`, `auto` to `acceptEdits`, and `allow` to
749
+ `bypassPermissions`. Codex ACP retains its adapter-specific coupled modes:
750
+ `auto` selects `agent`, while `allow` selects `agent-full-access`.
583
751
  `dontAsk` suppresses only the *prompt*, not the denial, which is why an
584
752
  `allow` role previously ran unable to do its job. Legacy `deny` is accepted
585
753
  only for compatibility and retains its conservative Codex `on-request` /
@@ -889,6 +1057,15 @@ unchanged. The registry in `src/owner-channel/commands.ts` is the single source
889
1057
  of truth — `/help` renders exactly that table, so adding an entry there is the
890
1058
  whole registration step for a new command.
891
1059
 
1060
+ The supervisor also advertises every primary registry entry through ours typed
1061
+ commands. The menu's `arguments` field is converted back to the exact text after
1062
+ the slash command name and enters the same dispatcher, so validation, replies,
1063
+ lifecycle effects, and audit behavior stay identical. Aliases remain available
1064
+ as slash commands but are not duplicated in the typed menu. Typed handlers
1065
+ re-check the authenticated sender CID against the live Owner boundary before
1066
+ dispatch; the SDK completion result is `null` because the existing correlated
1067
+ owner-channel reply remains the command result.
1068
+
892
1069
  | Command | Effect |
893
1070
  | --- | --- |
894
1071
  | `/help` (alias `/commands`) | list all deterministic owner-channel commands |
@@ -1031,19 +1208,46 @@ The main Codex controls are Brain-owned and also available when spawning:
1031
1208
  ```yaml
1032
1209
  # ~/fleet/brains/codex-review.yaml
1033
1210
  harness: codex
1034
- session: acp
1211
+ session: codex-app-server
1035
1212
  model: gpt-5.4
1213
+ permissions:
1214
+ approval: auto
1215
+ filesystem: read-only
1216
+ unattended: deny
1036
1217
  harness_options:
1037
1218
  launcher: auto
1038
- profile: fleet
1039
- sandbox: read-only
1040
- approval: on-request
1041
1219
  monitor: true
1042
1220
  search: true
1043
1221
  add_dirs: [/data/shared]
1044
1222
  config: { model_reasoning_effort: high }
1045
1223
  ```
1046
1224
 
1225
+ `codex-app-server` is the opt-in direct Codex transport. It streams native items
1226
+ and message phases, maps Codex approval
1227
+ requests into Fleet permissions, supports turn steering/interrupt, and resumes
1228
+ the durable Codex thread through Fleet's shared `.session-id` lifecycle.
1229
+ Configure its authority only through the shared `permissions:` block;
1230
+ `on-request` and other Codex policy names are internal adapter values.
1231
+ Codex rejects `--profile` for `app-server`, so native roles also reject
1232
+ `harness_options.profile`; use the explicit config map only for non-authority
1233
+ settings. Native config currently allows only `model_reasoning_effort`; Codex ACP
1234
+ retains its legacy profile and config support.
1235
+ Packaged Codex brains and the init wizard continue to select `session: acp` for
1236
+ compatibility; Claude Code also uses ACP. Existing durable sessions are not assumed portable
1237
+ between unrelated providers; a failed fast resume is handled by Fleet's normal
1238
+ bounded fresh-session recovery.
1239
+
1240
+ To replace the default launcher for a native Codex role:
1241
+
1242
+ ```yaml
1243
+ session_options:
1244
+ codex_app_server:
1245
+ command: [codex, app-server]
1246
+ ```
1247
+
1248
+ The command override is exact: Fleet does not append `--profile`, `--search`, or
1249
+ `app-server`, so include every required argument in the command itself.
1250
+
1047
1251
  One-off/permanent spawn selects a Brain that owns model, reasoning, native permission,
1048
1252
  sandbox, profile, launcher, search, monitor consent, native config, and additional roots.
1049
1253
  Use `env.OURS_PORT`/`env.OURS_CONFIG` for a
@@ -0,0 +1,17 @@
1
+ import type { ConversationEventV1 } from './session/conversation-types.js';
2
+ import type { AgentSession } from './session/types.js';
3
+ export interface AgentRecoveryEvidence {
4
+ ok: boolean;
5
+ reason: 'RECOVERY_TOOLS_VERIFIED' | 'RECOVERY_PROMPT_MISSING' | 'RECOVERY_TURN_INCOMPLETE' | 'RECOVERY_TURN_FAILED' | 'RECOVERY_CHOOSE_MISSING' | 'RECOVERY_CURRENT_MISSING' | 'RECOVERY_GET_MESSAGES_MISSING' | 'RECOVERY_TOOL_ORDER_INVALID';
6
+ chooseIdentity: boolean;
7
+ currentIdentity: boolean;
8
+ getMessages: boolean;
9
+ turnCompleted: boolean;
10
+ }
11
+ /**
12
+ * Verify one exact recovery turn from its durable conversation ledger. Tool
13
+ * arguments/results are inspected only to derive these booleans and are never
14
+ * returned or persisted by this gate.
15
+ */
16
+ export declare function evaluateAgentRecovery(events: readonly ConversationEventV1[], promptId: string, identity: string): AgentRecoveryEvidence;
17
+ export declare function recoverAgentIdentity(session: AgentSession, identity: string): Promise<AgentRecoveryEvidence>;
@@ -0,0 +1,135 @@
1
+ const TOOL_NAMES = {
2
+ choose: new Set(['choose_identity', 'ours.choose_identity', 'mcp__ours__choose_identity']),
3
+ current: new Set(['current_identity', 'ours.current_identity', 'mcp__ours__current_identity']),
4
+ messages: new Set(['get_messages', 'ours.get_messages', 'mcp__ours__get_messages']),
5
+ };
6
+ function recordFor(records, event) {
7
+ if (event.kind !== 'tool.upsert' || !event.toolCallId)
8
+ return undefined;
9
+ const payload = event.payload;
10
+ const previous = records.get(event.toolCallId) ?? {};
11
+ const next = {
12
+ ...previous,
13
+ ...(payload.title !== undefined ? { title: payload.title } : {}),
14
+ ...(payload.status !== undefined ? { status: payload.status } : {}),
15
+ ...(payload.rawInput !== undefined ? { rawInput: payload.rawInput } : {}),
16
+ ...(payload.status === 'completed' ? { completedSeq: event.seq } : {}),
17
+ };
18
+ records.set(event.toolCallId, next);
19
+ return next;
20
+ }
21
+ function objectInput(input) {
22
+ if (!input || input.truncated || input.redacted || !input.json
23
+ || typeof input.json !== 'object' || Array.isArray(input.json))
24
+ return undefined;
25
+ return input.json;
26
+ }
27
+ function safeChoose(record, identity) {
28
+ if (!record.title || !TOOL_NAMES.choose.has(record.title) || record.status !== 'completed')
29
+ return false;
30
+ const input = objectInput(record.rawInput);
31
+ if (!input)
32
+ return false;
33
+ const keys = Object.keys(input).sort();
34
+ if (keys.some(key => key !== 'force' && key !== 'name'))
35
+ return false;
36
+ return input.name === identity && (input.force === undefined || input.force === false);
37
+ }
38
+ function safeNoOrBoundedInput(record, names, allowed) {
39
+ if (!record.title || !names.has(record.title) || record.status !== 'completed')
40
+ return false;
41
+ if (!record.rawInput)
42
+ return allowed({});
43
+ const input = objectInput(record.rawInput);
44
+ return input !== undefined && allowed(input);
45
+ }
46
+ function safeCurrent(record) {
47
+ return safeNoOrBoundedInput(record, TOOL_NAMES.current, input => Object.keys(input).length === 0);
48
+ }
49
+ function safeMessages(record) {
50
+ return safeNoOrBoundedInput(record, TOOL_NAMES.messages, input => {
51
+ const keys = Object.keys(input);
52
+ if (keys.some(key => key !== 'limit'))
53
+ return false;
54
+ return input.limit === undefined
55
+ || (Number.isSafeInteger(input.limit) && input.limit >= 1 && input.limit <= 200);
56
+ });
57
+ }
58
+ /**
59
+ * Verify one exact recovery turn from its durable conversation ledger. Tool
60
+ * arguments/results are inspected only to derive these booleans and are never
61
+ * returned or persisted by this gate.
62
+ */
63
+ export function evaluateAgentRecovery(events, promptId, identity) {
64
+ const ordered = [...events].sort((a, b) => a.seq - b.seq);
65
+ const admitted = ordered.find(event => event.kind === 'prompt.admitted' && event.promptId === promptId);
66
+ const empty = (reason) => ({
67
+ ok: false, reason, chooseIdentity: false, currentIdentity: false,
68
+ getMessages: false, turnCompleted: false,
69
+ });
70
+ if (!admitted)
71
+ return empty('RECOVERY_PROMPT_MISSING');
72
+ const terminal = ordered.find(event => event.seq > admitted.seq
73
+ && event.kind === 'turn.completed'
74
+ && event.promptId === promptId
75
+ && event.sessionGeneration === admitted.sessionGeneration);
76
+ if (!terminal)
77
+ return empty('RECOVERY_TURN_INCOMPLETE');
78
+ const outcome = terminal.payload.outcome;
79
+ if (outcome !== 'completed')
80
+ return empty('RECOVERY_TURN_FAILED');
81
+ const records = new Map();
82
+ for (const event of ordered) {
83
+ if (event.seq <= admitted.seq || event.seq >= terminal.seq
84
+ || event.promptId !== promptId
85
+ || event.sessionGeneration !== admitted.sessionGeneration)
86
+ continue;
87
+ recordFor(records, event);
88
+ }
89
+ const values = [...records.values()];
90
+ const chooses = values.filter(record => safeChoose(record, identity));
91
+ const currents = values.filter(safeCurrent);
92
+ const messages = values.filter(safeMessages);
93
+ const chooseIdentity = chooses.length > 0;
94
+ const currentIdentity = currents.length > 0;
95
+ const getMessages = messages.length > 0;
96
+ const base = { chooseIdentity, currentIdentity, getMessages, turnCompleted: true };
97
+ if (!chooseIdentity)
98
+ return { ok: false, reason: 'RECOVERY_CHOOSE_MISSING', ...base };
99
+ if (!currentIdentity)
100
+ return { ok: false, reason: 'RECOVERY_CURRENT_MISSING', ...base };
101
+ if (!getMessages)
102
+ return { ok: false, reason: 'RECOVERY_GET_MESSAGES_MISSING', ...base };
103
+ const orderedChain = chooses.some(choose => currents.some(current => messages.some(message => choose.completedSeq !== undefined && current.completedSeq !== undefined
104
+ && message.completedSeq !== undefined
105
+ && choose.completedSeq < current.completedSeq
106
+ && current.completedSeq < message.completedSeq)));
107
+ if (!orderedChain)
108
+ return { ok: false, reason: 'RECOVERY_TOOL_ORDER_INVALID', ...base };
109
+ return { ok: true, reason: 'RECOVERY_TOOLS_VERIFIED', ...base };
110
+ }
111
+ export async function recoverAgentIdentity(session, identity) {
112
+ if (!session.subscribeConversation)
113
+ return {
114
+ ok: false, reason: 'RECOVERY_PROMPT_MISSING', chooseIdentity: false,
115
+ currentIdentity: false, getMessages: false, turnCompleted: false,
116
+ };
117
+ const events = [];
118
+ const unsubscribe = session.subscribeConversation(event => events.push(event));
119
+ try {
120
+ const queued = await session.queuePrompt([
121
+ '[fleet-recovery] The shared ours daemon restarted.',
122
+ `Call ours choose_identity with name ${JSON.stringify(identity)} and force false.`,
123
+ 'Then call current_identity, then get_messages. Complete all three in that order.',
124
+ 'Do not create/delete identities, force-bind, interrupt, or restart any service/session.',
125
+ ].join('\n'), { origin: { kind: 'fleet-monitor' } });
126
+ await queued.completion;
127
+ // Conversation publication is synchronous with terminal settlement in the
128
+ // in-tree ACP store; filtering by exact promptId/sessionGeneration remains
129
+ // the authority even if unrelated events arrived concurrently.
130
+ return evaluateAgentRecovery(events, queued.promptId, identity);
131
+ }
132
+ finally {
133
+ unsubscribe();
134
+ }
135
+ }
@@ -1,7 +1,10 @@
1
+ import { sessionBackendCapabilities } from '../session/types.js';
1
2
  export function roleCapabilities(role, status, context = {}) {
2
- const backend = role.configuredBackend ?? (role.detectedBackend === 'acp' ? 'acp' : undefined);
3
+ const backend = role.configuredBackend
4
+ ?? (role.detectedBackend === 'acp' || role.detectedBackend === 'codex-app-server'
5
+ ? role.detectedBackend : undefined);
3
6
  const online = status.session.reachability === 'online';
4
- const acp = backend === 'acp';
7
+ const session = backend ? sessionBackendCapabilities(backend, role.config?.harness) : undefined;
5
8
  const protocolVersion = context.controlProtocolVersion ?? 1;
6
9
  const inactive = status.overall === 'offline'
7
10
  || (!online && status.supervisor.liveness === 'stopped');
@@ -10,11 +13,20 @@ export function roleCapabilities(role, status, context = {}) {
10
13
  inventory: true,
11
14
  status: true,
12
15
  output: {
13
- recent: acp, stream: acp && protocolVersion >= 2,
14
- structured: acp, replayCursor: acp && protocolVersion >= 2,
16
+ recent: Boolean(session?.streaming),
17
+ stream: Boolean(session?.streaming) && protocolVersion >= 2,
18
+ structured: Boolean(session?.durableConversation),
19
+ replayCursor: Boolean(session?.durableConversation) && protocolVersion >= 2,
20
+ },
21
+ input: {
22
+ text: online && Boolean(session?.promptInput),
23
+ interrupt: online && Boolean(session?.interrupt),
24
+ steering: online && Boolean(session?.steering),
25
+ },
26
+ permissions: {
27
+ observe: Boolean(session?.permissions),
28
+ respond: online && Boolean(session?.permissions),
15
29
  },
16
- input: { text: online && acp, interrupt: online && acp, steering: false },
17
- permissions: { observe: acp, respond: acp && online },
18
30
  lifecycle: {
19
31
  start: role.lifetime === 'permanent' && status.supervisor.liveness !== 'running',
20
32
  stop: role.lifetime === 'permanent' && !inactive && status.supervisor.liveness !== 'stopped',