@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.
- package/README.md +244 -40
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +20 -5
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +4 -4
- package/dist/application/role-creation-service.js +19 -1
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +93 -40
- package/dist/application/task-room-service.js +579 -173
- package/dist/briefing.js +85 -24
- package/dist/build-info.json +4 -4
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +217 -51
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +38 -3
- package/dist/config.js +240 -28
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +7 -4
- package/dist/daemon-recovery.d.ts +93 -0
- package/dist/daemon-recovery.js +328 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +213 -42
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +54 -4
- package/dist/fleet-command-audit.d.ts +181 -0
- package/dist/fleet-command-audit.js +738 -0
- package/dist/fleet-proxy.d.ts +7 -2
- package/dist/harness/agent-session.d.ts +5 -0
- package/dist/harness/claude-code-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +6 -1
- package/dist/harness/claude-code.js +2 -0
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +11 -2
- package/dist/harness/codex-session.js +22 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +110 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +4 -3
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +631 -0
- package/dist/lifecycle-summary.d.ts +134 -0
- package/dist/lifecycle-summary.js +179 -0
- package/dist/loops/config.d.ts +10 -1
- package/dist/loops/config.js +38 -5
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +75 -3
- package/dist/owner-channel/channel.js +531 -123
- package/dist/owner-channel/commands.d.ts +28 -6
- package/dist/owner-channel/commands.js +112 -36
- package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
- package/dist/owner-channel/lifecycle-outbox.js +87 -0
- package/dist/owner-channel/ours-client.d.ts +19 -0
- package/dist/owner-channel/ours-client.js +3 -0
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/permissions.js +1 -1
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -0
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +450 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.js +13 -1
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +519 -261
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +23 -7
- package/dist/rooms-tasks/config.js +16 -29
- package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
- package/dist/rooms-tasks/cowork-adapter.js +13 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +9 -0
- package/dist/rooms-tasks/external-worker.js +52 -9
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +181 -0
- package/dist/rooms-tasks/member-overrides.d.ts +30 -0
- package/dist/rooms-tasks/member-overrides.js +204 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +9 -2
- package/dist/rooms-tasks/provision.js +146 -56
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-room-name.d.ts +12 -0
- package/dist/rooms-tasks/task-room-name.js +61 -0
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -2
- package/dist/rooms-tasks/templates.js +42 -78
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +10 -4
- package/dist/rooms-tasks/types.d.ts +94 -8
- package/dist/rooms-tasks/types.js +14 -1
- package/dist/runner.d.ts +12 -2
- package/dist/runner.js +167 -56
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +35 -9
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +14 -1
- package/dist/session/control.js +51 -0
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/types.d.ts +24 -6
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +9 -0
- package/dist/spawn.js +77 -9
- package/dist/temp-lifecycle.d.ts +1 -1
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +27 -8
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
- package/dist/web-app/index.html +1 -1
- package/package.json +5 -4
- package/presets/brain-catalog.json +17 -0
- package/presets/fleet/agent_templates/Critic.yaml +5 -0
- package/presets/fleet/agent_templates/Developer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
- package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +30 -0
- package/presets/fleet/roles/Critic.yaml +28 -0
- package/presets/fleet/roles/Developer.yaml +28 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
- package/presets/fleet/room_templates/pair.yaml +13 -0
- package/presets/fleet/room_templates/single.yaml +9 -0
- package/presets/fleet/room_templates/team.yaml +16 -0
- package/presets/fleet.yaml +7 -0
- 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
|
|
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:
|
|
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 ──►
|
|
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` |
|
|
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 ≥
|
|
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 #
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
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:"
|
|
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
|
-
#
|
|
400
|
-
#
|
|
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.
|
|
471
|
-
is
|
|
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
|
|
492
|
-
["*"]`.
|
|
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.**
|
|
579
|
-
`auto`
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
`
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
14
|
-
|
|
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',
|