@ours.network/fleet 1.0.5 → 1.1.0-nightly.1
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 +28 -41
- package/dist/application/capabilities.d.ts +0 -1
- package/dist/application/capabilities.js +3 -13
- package/dist/application/fleet-query-service.d.ts +0 -3
- package/dist/application/fleet-query-service.js +1 -24
- package/dist/application/role-creation-service.d.ts +5 -5
- package/dist/application/role-creation-service.js +5 -7
- package/dist/application/role-repository.d.ts +1 -2
- package/dist/application/role-repository.js +7 -7
- package/dist/application/session-control.d.ts +0 -12
- package/dist/application/session-control.js +0 -42
- package/dist/application/session-mutations.d.ts +5 -5
- package/dist/application/types.d.ts +0 -7
- package/dist/briefing.js +1 -4
- package/dist/build-info.json +5 -5
- package/dist/cli.js +9 -23
- package/dist/config.d.ts +1 -4
- package/dist/config.js +13 -17
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +20 -26
- package/dist/doctor.js +0 -7
- package/dist/fleet-proxy.d.ts +1 -1
- package/dist/harness/acp-session-transport.d.ts +8 -0
- package/dist/harness/acp-session-transport.js +3 -0
- package/dist/harness/agent-session.d.ts +33 -0
- package/dist/harness/agent-session.js +1 -0
- package/dist/harness/claude-code-session.d.ts +18 -0
- package/dist/harness/claude-code-session.js +28 -0
- package/dist/harness/claude-code.d.ts +2 -1
- package/dist/harness/claude-code.js +29 -81
- package/dist/harness/codex-session.d.ts +18 -0
- package/dist/harness/codex-session.js +28 -0
- package/dist/harness/codex.d.ts +2 -1
- package/dist/harness/codex.js +35 -45
- package/dist/harness/types.d.ts +6 -38
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1 -3
- package/dist/isolation/resources.d.ts +3 -7
- package/dist/isolation/resources.js +3 -7
- package/dist/model-env.d.ts +2 -3
- package/dist/model-env.js +2 -3
- package/dist/monitor.d.ts +1 -24
- package/dist/monitor.js +5 -140
- package/dist/owner-channel/channel.d.ts +2 -2
- package/dist/runner.d.ts +5 -15
- package/dist/runner.js +26 -102
- package/dist/session/acp.d.ts +2 -2
- package/dist/session/acp.js +2 -3
- package/dist/session/activity.d.ts +2 -3
- package/dist/session/activity.js +2 -3
- package/dist/session/arbiter.d.ts +4 -4
- package/dist/session/control.d.ts +3 -3
- package/dist/session/types.d.ts +11 -5
- package/dist/spawn.js +8 -9
- package/dist/supervisor/none.d.ts +1 -1
- package/dist/supervisor/none.js +2 -2
- package/dist/tmux.d.ts +1 -14
- package/dist/tmux.js +1 -51
- package/dist/watchdog/config.js +6 -4
- package/dist/watchdog/run.js +8 -23
- package/dist/web/auth.d.ts +1 -1
- package/dist/web/runtime.js +4 -15
- package/dist/web/server.d.ts +1 -3
- package/dist/web/server.js +3 -14
- package/dist/web/topology-promote.js +2 -5
- package/dist/web-app/assets/index-59GF-gsZ.css +1 -0
- package/dist/web-app/assets/{index-BCBK78hw.js → index-DhMDVRU1.js} +6 -6
- package/dist/web-app/index.html +2 -2
- package/package.json +3 -8
- package/dist/session/tmux.d.ts +0 -28
- package/dist/session/tmux.js +0 -80
- package/dist/web/terminal/bridge.d.ts +0 -27
- package/dist/web/terminal/bridge.js +0 -317
- package/dist/web-app/assets/TerminalView-C_G1ID2P.js +0 -9
- package/dist/web-app/assets/index-DuC-xnX4.css +0 -1
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ 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
|
|
12
|
-
|
|
11
|
+
- **run through structured ACP sessions** behind a shared Codex/Claude Code
|
|
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,
|
|
15
15
|
- **resume their context** across restarts (when the harness supports it),
|
|
@@ -44,8 +44,7 @@ roles:
|
|
|
44
44
|
~/fleet.yaml + ~/fleet.d/*.yaml your declaration
|
|
45
45
|
│ ours-fleet up
|
|
46
46
|
▼
|
|
47
|
-
briefing.md per role ──►
|
|
48
|
-
└─► ACP client ──► ACP agent (codex-acp …)
|
|
47
|
+
briefing.md per role ──► agent session adapter ──► ACP client/session ──► ACP agent
|
|
49
48
|
▲ │
|
|
50
49
|
systemd --user / launchd ───────┘ restart on crash, start at boot/login
|
|
51
50
|
```
|
|
@@ -78,7 +77,6 @@ The state dir contract:
|
|
|
78
77
|
| What | Why | Install |
|
|
79
78
|
|---|---|---|
|
|
80
79
|
| Node ≥ 20 | runs `ours-fleet` itself | nodejs.org, `apt`, or `brew` |
|
|
81
|
-
| tmux | roles using `session: tmux` (the default) | `apt install tmux` / `brew install tmux` |
|
|
82
80
|
| Node ≥ 22 | Claude roles using `session: acp` | required by the maintained Claude ACP adapter |
|
|
83
81
|
| a harness CLI, logged in | the agent itself | e.g. Claude Code (`claude`) or Codex CLI (`codex`) |
|
|
84
82
|
| `ours` CLI + shared daemon | identity + agent-to-agent messaging | `npm i -g @ours.network/cli && ours daemon start` |
|
|
@@ -98,8 +96,8 @@ ours-fleet doctor # verifies everything above, with actionable messages
|
|
|
98
96
|
The maintained Codex and Claude ACP adapters install as optional dependencies of
|
|
99
97
|
`ours-fleet` and are resolved internally; users do not install adapter commands
|
|
100
98
|
or add them to `PATH`. An explicit `session_options.acp.command` remains
|
|
101
|
-
available for custom adapters. On Node 20–21,
|
|
102
|
-
|
|
99
|
+
available for custom adapters. On Node 20–21, Codex ACP remains available,
|
|
100
|
+
while maintained Claude ACP requires upgrading to Node 22.
|
|
103
101
|
|
|
104
102
|
Each OS user manages their own fleet — to host roles under a sandboxed account,
|
|
105
103
|
become that account and repeat.
|
|
@@ -139,8 +137,8 @@ fleet monitor policy, and same-harness model inherit from the caller. Explicit
|
|
|
139
137
|
flags win; changing harness without a model lets the selected harness/fleet
|
|
140
138
|
defaults choose one. After creation succeeds, fleet can deterministically notify
|
|
141
139
|
the caller's owner channel with the caller and spawned-role details. This is an
|
|
142
|
-
honest-actor convenience and attribution path, not a security boundary;
|
|
143
|
-
|
|
140
|
+
honest-actor convenience and attribution path, not a security boundary; host
|
|
141
|
+
shells and deliberately bypassed absolute binaries retain direct behavior.
|
|
144
142
|
|
|
145
143
|
## Local web console
|
|
146
144
|
|
|
@@ -209,8 +207,8 @@ offline shell and no stale fleet state.
|
|
|
209
207
|
|
|
210
208
|
The console provides evidence-separated inventory and status, ACP activity and
|
|
211
209
|
permission controls, redacted logs, typed text send, confirmed lifecycle
|
|
212
|
-
actions, transactional permanent/temporary creation
|
|
213
|
-
|
|
210
|
+
actions, and transactional permanent/temporary creation. Identity is fixed to
|
|
211
|
+
the role name. Creation uses the authenticated
|
|
214
212
|
daemon inventory and reports verified, missing, or unknown evidence. A missing
|
|
215
213
|
permanent role identity is created deterministically before launch with local
|
|
216
214
|
discovery and auto-accept enabled, then the provisioning lease is released so
|
|
@@ -243,19 +241,16 @@ never force-adopts or deletes identity state. Permanent roles are
|
|
|
243
241
|
provisioned by fleet before launch and never delegate normal identity creation
|
|
244
242
|
to the harness.
|
|
245
243
|
|
|
246
|
-
|
|
247
|
-
non-terminal features remain available and tmux Terminal is disabled with a
|
|
248
|
-
diagnostic.
|
|
244
|
+
The web console uses structured ACP activity rather than exposing a raw agent terminal.
|
|
249
245
|
|
|
250
246
|
Security boundaries:
|
|
251
247
|
|
|
252
248
|
- configured `Host` and `Origin`, CSRF, one-time WebSocket tickets, and explicit
|
|
253
249
|
bind/origin policy are enforced server-side;
|
|
254
250
|
- cwd values must resolve beneath configured local roots;
|
|
255
|
-
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
isolated tmux sockets—never active role state.
|
|
251
|
+
- normal logs are bounded and redacted;
|
|
252
|
+
- tests use temporary fleet homes and fake supervisors/identity providers—never
|
|
253
|
+
active role state.
|
|
259
254
|
|
|
260
255
|
Permanent spawns are written to `~/fleet.d/<Name>.yaml`; the CLI never edits your
|
|
261
256
|
hand-written `~/fleet.yaml`. `ours-fleet rm <Name>` unspawns.
|
|
@@ -292,7 +287,6 @@ them and unsticks them:
|
|
|
292
287
|
|
|
293
288
|
```sh
|
|
294
289
|
ours-fleet peek Worker # what is it doing?
|
|
295
|
-
ours-fleet send Worker --key 1 # answer the menu it's stuck on
|
|
296
290
|
ours-fleet send Worker "continue with the tests, then report"
|
|
297
291
|
```
|
|
298
292
|
|
|
@@ -315,7 +309,7 @@ ours-fleet docs | man AI-friendly complete reference
|
|
|
315
309
|
ours-fleet up|down|restart|force-restart [-c FILE] [Name...]
|
|
316
310
|
ours-fleet config [-c FILE] validate + print merged plan
|
|
317
311
|
ours-fleet ls | attach | peek | logs [-f] | status <Name>
|
|
318
|
-
ours-fleet send <Name> "text"
|
|
312
|
+
ours-fleet send <Name> "text"
|
|
319
313
|
ours-fleet spawn [--temp] [<Name> | --role <Name>] [--harness --session --mission --model --approval ...]
|
|
320
314
|
ours-fleet loops validate|list|status
|
|
321
315
|
ours-fleet loops reload <Role>
|
|
@@ -355,7 +349,7 @@ vars: { work_root: /home/me/work } # ${var} substitution anywhere below
|
|
|
355
349
|
start_stagger_ms: 0 # delay between agent LAUNCHES (host-wide, ms); 0 = no stagger
|
|
356
350
|
defaults:
|
|
357
351
|
harness: claude-code # for roles that don't set one
|
|
358
|
-
session:
|
|
352
|
+
session: acp # supported Fleet↔agent session path
|
|
359
353
|
permissions: # common intent, translated by each harness/backend
|
|
360
354
|
approval: ask # ask | auto | allow (`deny` is a deprecated alias)
|
|
361
355
|
filesystem: workspace # read-only | workspace | unrestricted
|
|
@@ -371,7 +365,7 @@ defaults:
|
|
|
371
365
|
roles:
|
|
372
366
|
Name: # [A-Za-z0-9_-]+
|
|
373
367
|
harness: claude-code
|
|
374
|
-
session: acp #
|
|
368
|
+
session: acp # optional; ACP is the only supported session
|
|
375
369
|
session_options:
|
|
376
370
|
acp:
|
|
377
371
|
command: claude-agent-acp # optional advanced override
|
|
@@ -446,15 +440,14 @@ loops: # trusted local scheduled ACP turns
|
|
|
446
440
|
|
|
447
441
|
Merge order: `fleet.yaml` ← `fleet.d/*.yaml`; a duplicate role name is a hard
|
|
448
442
|
error naming both files. Identities and roles are decoupled — removing a role
|
|
449
|
-
never deletes an identity. `session` is
|
|
450
|
-
|
|
451
|
-
contract. `defaults.harness_options` is shallow-merged with each
|
|
443
|
+
never deletes an identity. `session` is ACP-backed for both supported harnesses.
|
|
444
|
+
`defaults.harness_options` is shallow-merged with each
|
|
452
445
|
role's `harness_options`, so a fleet can set common Codex permission/profile defaults
|
|
453
446
|
and override individual keys per role. `monitor` merges the same way — a role block
|
|
454
447
|
overrides `defaults.monitor` key-by-key.
|
|
455
448
|
|
|
456
449
|
Every supervised role is a client of the operator-configured ours daemon. Fleet strips the
|
|
457
|
-
obsolete, presence-sensitive `OURS_AUTOSTART` variable from
|
|
450
|
+
obsolete, presence-sensitive `OURS_AUTOSTART` variable from ACP harness
|
|
458
451
|
processes. `ours-mcp proxy` is client-only and never starts a daemon; operators and explicit
|
|
459
452
|
installer/setup flows remain responsible for starting it.
|
|
460
453
|
|
|
@@ -597,9 +590,7 @@ since a human can still attach a console and answer.
|
|
|
597
590
|
`auto` selects Codex ACP `agent` (`on-request` + `workspace-write`) / Claude
|
|
598
591
|
`acceptEdits`. `allow` selects Codex ACP's fully non-interactive yolo mode,
|
|
599
592
|
reported by the adapter as `agent-full-access` (`never` +
|
|
600
|
-
`danger-full-access`); Claude uses `bypassPermissions`.
|
|
601
|
-
approval and sandbox flags remain independent, `auto` is `on-request` and
|
|
602
|
-
`allow` is `never` while `filesystem` still chooses the sandbox.
|
|
593
|
+
`danger-full-access`); Claude uses `bypassPermissions`.
|
|
603
594
|
`dontAsk` suppresses only the *prompt*, not the denial, which is why an
|
|
604
595
|
`allow` role previously ran unable to do its job. Legacy `deny` is accepted
|
|
605
596
|
only for compatibility and retains its conservative Codex `on-request` /
|
|
@@ -689,8 +680,8 @@ their boots ~4 s apart instead of firing all seven at once.
|
|
|
689
680
|
With `monitor.mode: fleet` (the default), the **ours-fleet supervisor** delivers
|
|
690
681
|
a role's mail wakes: the per-role runner long-polls the ours daemon's notification API and
|
|
691
682
|
submits a single `[fleet-monitor] N new messages from … — run get_messages` prompt
|
|
692
|
-
through the
|
|
693
|
-
and falls back to structured `session/prompt
|
|
683
|
+
through the shared agent session. ACP uses live steering when its adapter supports it
|
|
684
|
+
and falls back to structured `session/prompt`.
|
|
694
685
|
Set `monitor.interrupt: true` on roles where every configured wake should cancel
|
|
695
686
|
the active turn before the notification is delivered. This is intentionally
|
|
696
687
|
content-blind: the supervisor cannot inspect encrypted message bodies, so all
|
|
@@ -700,8 +691,7 @@ tool result or pending permission. Fleet waits for terminal ACP tool/update
|
|
|
700
691
|
evidence, then steers the wake without calling `session.cancel`. If the tool is
|
|
701
692
|
still active after 120 seconds, or the adapter cannot expose authenticated tool
|
|
702
693
|
boundaries/steering, fleet visibly degrades to non-cancelling steering or queued
|
|
703
|
-
delivery.
|
|
704
|
-
interrupts remain immediate.
|
|
694
|
+
delivery. Explicit human and control interrupts remain immediate.
|
|
705
695
|
The default is `false`: a role that must begin a post-readiness mission
|
|
706
696
|
immediately, including second-and-later mail received while it is working, must
|
|
707
697
|
set `monitor.mode: fleet` and `monitor.interrupt: true` explicitly. Readiness and
|
|
@@ -794,8 +784,8 @@ invites, message bodies, credentials, or keys. A corrupt overlay fails closed
|
|
|
794
784
|
(no effective owners and no mutation), and the last effective owner cannot be
|
|
795
785
|
revoked.
|
|
796
786
|
|
|
797
|
-
These commands require a running
|
|
798
|
-
stopped,
|
|
787
|
+
These commands require a running role with `owner_channel` enabled. Missing,
|
|
788
|
+
stopped, disabled, draining, and unavailable-MCP targets fail without
|
|
799
789
|
starting a second client, binding an identity, or opening a network listener.
|
|
800
790
|
|
|
801
791
|
The managed agent can use its ordinary ours `send_message` or `send_file` tool to
|
|
@@ -1015,11 +1005,8 @@ gets a durable pre-send marker: a failed/ambiguous send becomes `uncertain` and
|
|
|
1015
1005
|
not blindly retried, while files not yet attempted remain recoverable after restart.
|
|
1016
1006
|
Logs contain counts and byte totals, never filenames or raw bytes.
|
|
1017
1007
|
|
|
1018
|
-
Owner channels
|
|
1019
|
-
|
|
1020
|
-
cannot reliably distinguish the final answer from thoughts, tool output, or
|
|
1021
|
-
unrelated concurrent work. The config rejects tmux instead of silently offering
|
|
1022
|
-
weaker semantics.
|
|
1008
|
+
Owner channels use the structured agent-session interface for turn-correlated
|
|
1009
|
+
assistant output and reliable automatic replies.
|
|
1023
1010
|
|
|
1024
1011
|
## Codex roles
|
|
1025
1012
|
|
|
@@ -1083,7 +1070,7 @@ role-specific ours daemon, or configure the host default in `~/.ours/config.json
|
|
|
1083
1070
|
|
|
1084
1071
|
Each role can be sandboxed at the environment level via an `isolation:` block —
|
|
1085
1072
|
**fully additive: a role with no block behaves exactly as before.** The agent's
|
|
1086
|
-
|
|
1073
|
+
agent process is wrapped in [bubblewrap](https://github.com/containers/bubblewrap)
|
|
1087
1074
|
(rootless, no setuid), resource-limited by `systemd-run --user --scope`.
|
|
1088
1075
|
|
|
1089
1076
|
An empty `isolation: {}` gives a sensible default posture: filesystem-confined to
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { RoleRecord, RoleCapabilities, RoleStatus } from './types.js';
|
|
2
2
|
export interface CapabilityContext {
|
|
3
|
-
terminalPtyAvailable?: boolean;
|
|
4
3
|
controlProtocolVersion?: number;
|
|
5
4
|
}
|
|
6
5
|
export declare function roleCapabilities(role: RoleRecord, status: RoleStatus, context?: CapabilityContext): RoleCapabilities;
|
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
export function roleCapabilities(role, status, context = {}) {
|
|
2
|
-
const backend = role.configuredBackend ?? (role.detectedBackend === 'acp'
|
|
2
|
+
const backend = role.configuredBackend ?? (role.detectedBackend === 'acp' ? 'acp' : undefined);
|
|
3
3
|
const online = status.session.reachability === 'online';
|
|
4
4
|
const acp = backend === 'acp';
|
|
5
|
-
const tmux = backend === 'tmux';
|
|
6
5
|
const protocolVersion = context.controlProtocolVersion ?? 1;
|
|
7
6
|
const inactive = status.overall === 'offline'
|
|
8
7
|
|| (!online && status.supervisor.liveness === 'stopped');
|
|
9
|
-
const terminalAvailable = tmux && online && context.terminalPtyAvailable === true;
|
|
10
|
-
const terminalReason = terminalAvailable ? undefined
|
|
11
|
-
: !tmux ? 'wrong_backend'
|
|
12
|
-
: !online ? 'offline'
|
|
13
|
-
: 'pty_unavailable';
|
|
14
8
|
return {
|
|
15
9
|
protocolVersion,
|
|
16
10
|
inventory: true,
|
|
17
11
|
status: true,
|
|
18
12
|
output: {
|
|
19
|
-
recent: acp
|
|
13
|
+
recent: acp, stream: acp && protocolVersion >= 2,
|
|
20
14
|
structured: acp, replayCursor: acp && protocolVersion >= 2,
|
|
21
15
|
},
|
|
22
|
-
input: { text: online &&
|
|
16
|
+
input: { text: online && acp, interrupt: online && acp, steering: false },
|
|
23
17
|
permissions: { observe: acp, respond: acp && online },
|
|
24
|
-
terminal: {
|
|
25
|
-
available: terminalAvailable, reason: terminalReason,
|
|
26
|
-
multiViewer: terminalAvailable, writerLease: terminalAvailable,
|
|
27
|
-
},
|
|
28
18
|
lifecycle: {
|
|
29
19
|
start: role.lifetime === 'permanent' && status.supervisor.liveness !== 'running',
|
|
30
20
|
stop: role.lifetime === 'permanent' && !inactive && status.supervisor.liveness !== 'stopped',
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { SupervisorBackend } from '../supervisor/types.js';
|
|
2
2
|
import { controlRequest } from '../session/control.js';
|
|
3
|
-
import { Tmux } from '../tmux.js';
|
|
4
3
|
import { type CapabilityContext } from './capabilities.js';
|
|
5
4
|
import { RoleRepository } from './role-repository.js';
|
|
6
5
|
import type { RoleCapabilities, RoleRecord, RoleStatus } from './types.js';
|
|
7
6
|
export interface FleetQueryOptions {
|
|
8
7
|
repository: RoleRepository;
|
|
9
8
|
supervisor: SupervisorBackend;
|
|
10
|
-
tmux?: Tmux;
|
|
11
9
|
control?: typeof controlRequest;
|
|
12
10
|
capabilityContext?: CapabilityContext;
|
|
13
11
|
/**
|
|
@@ -24,7 +22,6 @@ export interface FleetQueryOptions {
|
|
|
24
22
|
}
|
|
25
23
|
export declare class FleetQueryService {
|
|
26
24
|
private readonly options;
|
|
27
|
-
private readonly tmux;
|
|
28
25
|
private readonly control;
|
|
29
26
|
constructor(options: FleetQueryOptions);
|
|
30
27
|
list(): Promise<Array<{
|
|
@@ -4,7 +4,6 @@ import { classifyActivity } from '../session/activity.js';
|
|
|
4
4
|
import { controlRequest } from '../session/control.js';
|
|
5
5
|
import { SessionControlError } from '../session/types.js';
|
|
6
6
|
import { readExitRecord, readRestartLedger } from '../runner.js';
|
|
7
|
-
import { Tmux } from '../tmux.js';
|
|
8
7
|
import { roleCapabilities } from './capabilities.js';
|
|
9
8
|
import { FleetError } from './errors.js';
|
|
10
9
|
const clean = (value, max = 512) => value.replace(/[\0-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '').trim().slice(0, max);
|
|
@@ -50,7 +49,7 @@ function sessionOverall(supervisor, session, restart, monitor, isolation, proble
|
|
|
50
49
|
|| problems.some(problem => problem.severity === 'error' || problem.source === 'watchdog')
|
|
51
50
|
|| session.readiness === 'failed')
|
|
52
51
|
return 'attention';
|
|
53
|
-
// A reachable
|
|
52
|
+
// A reachable agent session is the user's live interaction surface. Its
|
|
54
53
|
// evidence remains authoritative even when a service manager no longer owns
|
|
55
54
|
// the process (for example, a detached ACP session with an inactive unit).
|
|
56
55
|
if (session.reachability === 'online'
|
|
@@ -69,11 +68,9 @@ function sessionOverall(supervisor, session, restart, monitor, isolation, proble
|
|
|
69
68
|
}
|
|
70
69
|
export class FleetQueryService {
|
|
71
70
|
options;
|
|
72
|
-
tmux;
|
|
73
71
|
control;
|
|
74
72
|
constructor(options) {
|
|
75
73
|
this.options = options;
|
|
76
|
-
this.tmux = options.tmux ?? new Tmux();
|
|
77
74
|
this.control = options.control ?? controlRequest;
|
|
78
75
|
}
|
|
79
76
|
async list() {
|
|
@@ -174,26 +171,6 @@ export class FleetQueryService {
|
|
|
174
171
|
};
|
|
175
172
|
}
|
|
176
173
|
}
|
|
177
|
-
if (intended === 'tmux' || role.detectedBackend === 'tmux') {
|
|
178
|
-
try {
|
|
179
|
-
const has = await this.tmux.has(role.id);
|
|
180
|
-
return {
|
|
181
|
-
backend: 'tmux', reachability: has ? 'online' : supervisor === 'stopped' ? 'offline' : 'unknown',
|
|
182
|
-
readiness: has ? 'idle' : supervisor === 'running' ? 'starting' : 'failed',
|
|
183
|
-
evidence: 'inferred',
|
|
184
|
-
// tmux exposes no agent-side evidence at all, and `readiness: idle`
|
|
185
|
-
// here is a pane-liveness inference, not an activity claim.
|
|
186
|
-
activity: { state: 'unobservable' },
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
return {
|
|
191
|
-
backend: 'tmux', reachability: 'unknown', readiness: 'unknown',
|
|
192
|
-
evidence: 'inferred', lastError: clean(error.message),
|
|
193
|
-
activity: { state: 'unobservable' },
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
174
|
return {
|
|
198
175
|
backend: 'unknown', reachability: supervisor === 'stopped' ? 'offline' : 'unknown',
|
|
199
176
|
readiness: supervisor === 'stopped' ? 'failed' : 'unknown', evidence: 'inferred',
|
|
@@ -12,7 +12,7 @@ export interface CreateRoleSessionRequest {
|
|
|
12
12
|
/** null means the selected harness's own default; web blank fields send null. */
|
|
13
13
|
model?: string | null;
|
|
14
14
|
reasoningEffort?: string | null;
|
|
15
|
-
session: 'acp'
|
|
15
|
+
session: 'acp';
|
|
16
16
|
cwd?: string;
|
|
17
17
|
lifetime: 'permanent' | 'temporary';
|
|
18
18
|
mission?: string;
|
|
@@ -41,7 +41,7 @@ export interface CreationCapabilities {
|
|
|
41
41
|
harnesses: Array<{
|
|
42
42
|
id: 'codex' | 'claude-code';
|
|
43
43
|
available: boolean;
|
|
44
|
-
sessions: Array<'acp'
|
|
44
|
+
sessions: Array<'acp'>;
|
|
45
45
|
defaultModel?: string;
|
|
46
46
|
models: string[];
|
|
47
47
|
modelOptions: HarnessModelOption[];
|
|
@@ -71,7 +71,7 @@ export interface CreationPreview {
|
|
|
71
71
|
name: string;
|
|
72
72
|
identity: string;
|
|
73
73
|
harness: string;
|
|
74
|
-
session: 'acp'
|
|
74
|
+
session: 'acp';
|
|
75
75
|
model?: string;
|
|
76
76
|
reasoningEffort?: string;
|
|
77
77
|
cwd?: string;
|
|
@@ -95,7 +95,7 @@ export interface CreationAction {
|
|
|
95
95
|
actionId: string;
|
|
96
96
|
requestHash: string;
|
|
97
97
|
roleId: string;
|
|
98
|
-
session: 'acp'
|
|
98
|
+
session: 'acp';
|
|
99
99
|
lifetime: 'permanent' | 'temporary';
|
|
100
100
|
state: CreationStage;
|
|
101
101
|
stages: Array<{
|
|
@@ -121,7 +121,7 @@ export interface RoleCreationServiceOptions {
|
|
|
121
121
|
tempLauncher?: SupervisorLauncher;
|
|
122
122
|
allowedCwdRoots?: string[];
|
|
123
123
|
journalDir?: string;
|
|
124
|
-
probeReady?: (name: string, session: 'acp'
|
|
124
|
+
probeReady?: (name: string, session: 'acp') => Promise<'ready' | 'attention' | 'unknown'>;
|
|
125
125
|
onProgress?: (action: CreationAction) => void;
|
|
126
126
|
modelCatalogs?: Partial<Record<'codex' | 'claude-code', () => HarnessModelCatalog>>;
|
|
127
127
|
/** Direct/managed callers must not create or restore the web action journal. */
|
|
@@ -109,13 +109,13 @@ export class RoleCreationService {
|
|
|
109
109
|
reasons,
|
|
110
110
|
harnesses: [
|
|
111
111
|
{
|
|
112
|
-
id: 'codex', available: true, sessions: ['acp'
|
|
112
|
+
id: 'codex', available: true, sessions: ['acp'],
|
|
113
113
|
defaultModel: resolveRoleModel(undefined, 'codex', defaults),
|
|
114
114
|
...codexModels, catalogSource: 'codex-runtime-catalog', customModelAllowed: true,
|
|
115
115
|
warnings: codexCatalog.warnings,
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
|
-
id: 'claude-code', available: true, sessions: ['acp'
|
|
118
|
+
id: 'claude-code', available: true, sessions: ['acp'],
|
|
119
119
|
defaultModel: resolveRoleModel(undefined, 'claude-code', defaults),
|
|
120
120
|
...claudeModels, catalogSource: 'claude-adapter-2.1', customModelAllowed: true,
|
|
121
121
|
warnings: claudeCatalog.warnings,
|
|
@@ -149,7 +149,7 @@ export class RoleCreationService {
|
|
|
149
149
|
const effective = {
|
|
150
150
|
name: request.name, identity: request.name,
|
|
151
151
|
harness: request.harness ?? defaults.harness ?? 'claude-code',
|
|
152
|
-
session: request.session ?? defaults.session ?? '
|
|
152
|
+
session: request.session ?? defaults.session ?? 'acp',
|
|
153
153
|
model: resolveRoleModel(request.model, request.harness, defaults),
|
|
154
154
|
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
155
155
|
cwd, lifetime: request.lifetime,
|
|
@@ -264,9 +264,7 @@ export class RoleCreationService {
|
|
|
264
264
|
this.stage(action, 'waiting_for_session');
|
|
265
265
|
const ready = await this.waitForReady(action.roleId, action.session);
|
|
266
266
|
if (ready === 'ready') {
|
|
267
|
-
action.openPath = action.
|
|
268
|
-
? `/roles/${encodeURIComponent(action.roleId)}/activity`
|
|
269
|
-
: `/roles/${encodeURIComponent(action.roleId)}/terminal`;
|
|
267
|
+
action.openPath = `/roles/${encodeURIComponent(action.roleId)}/activity`;
|
|
270
268
|
this.stage(action, 'session_reachable', 'session is reachable; identity binding has no structured evidence in this version');
|
|
271
269
|
}
|
|
272
270
|
else if (ready === 'attention') {
|
|
@@ -311,7 +309,7 @@ export class RoleCreationService {
|
|
|
311
309
|
throw new FleetError('invalid_request', 'invalid role name');
|
|
312
310
|
if (!['codex', 'claude-code'].includes(input.harness))
|
|
313
311
|
throw new FleetError('invalid_request', 'unsupported harness');
|
|
314
|
-
if (
|
|
312
|
+
if (input.session !== 'acp')
|
|
315
313
|
throw new FleetError('invalid_request', 'unsupported session backend');
|
|
316
314
|
if (!['permanent', 'temporary'].includes(input.lifetime))
|
|
317
315
|
throw new FleetError('invalid_request', 'unsupported lifetime');
|
|
@@ -3,9 +3,8 @@ export interface RoleRepositoryOptions {
|
|
|
3
3
|
configPath?: string;
|
|
4
4
|
permanentRoot?: string;
|
|
5
5
|
temporaryRoot?: string;
|
|
6
|
-
probeBackend?: (name: string, intended?: 'acp'
|
|
6
|
+
probeBackend?: (name: string, intended?: 'acp') => Promise<{
|
|
7
7
|
acp: boolean;
|
|
8
|
-
tmux: boolean;
|
|
9
8
|
}>;
|
|
10
9
|
concurrency?: number;
|
|
11
10
|
timeoutMs?: number;
|
|
@@ -42,7 +42,11 @@ function snapshot(path, name) {
|
|
|
42
42
|
const raw = parse(readFileSync(path, 'utf8'));
|
|
43
43
|
if (!raw || typeof raw !== 'object')
|
|
44
44
|
throw new Error('snapshot must be a mapping');
|
|
45
|
-
|
|
45
|
+
if (raw.session === 'tmux')
|
|
46
|
+
throw new Error('session: tmux is no longer supported; use session: acp with the Codex or Claude Code adapter');
|
|
47
|
+
if (raw.session !== undefined && raw.session !== 'acp')
|
|
48
|
+
throw new Error(`unsupported session '${String(raw.session)}'; expected session: acp`);
|
|
49
|
+
const session = 'acp';
|
|
46
50
|
return {
|
|
47
51
|
role: {
|
|
48
52
|
...raw, name, harness: typeof raw.harness === 'string' ? raw.harness : 'claude-code',
|
|
@@ -167,14 +171,10 @@ export class RoleRepository {
|
|
|
167
171
|
const timeoutMs = this.options.timeoutMs ?? 2_000;
|
|
168
172
|
const result = await Promise.race([
|
|
169
173
|
this.options.probeBackend(name, intended),
|
|
170
|
-
new Promise(resolve => setTimeout(() => resolve({ acp: false
|
|
171
|
-
]).catch(() => ({ acp: false
|
|
172
|
-
if (result.acp && result.tmux)
|
|
173
|
-
return 'ambiguous';
|
|
174
|
+
new Promise(resolve => setTimeout(() => resolve({ acp: false }), timeoutMs)),
|
|
175
|
+
]).catch(() => ({ acp: false }));
|
|
174
176
|
if (result.acp)
|
|
175
177
|
return 'acp';
|
|
176
|
-
if (result.tmux)
|
|
177
|
-
return 'tmux';
|
|
178
178
|
return 'none';
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { controlRequest } from '../session/control.js';
|
|
2
2
|
import type { ConversationEventV1, ConversationSnapshot, PromptReceipt } from '../session/conversation-types.js';
|
|
3
3
|
import type { SessionSnapshot } from '../session/types.js';
|
|
4
|
-
import { Tmux } from '../tmux.js';
|
|
5
4
|
import type { OutputPage, SendReceipt, SessionDescriptor } from './types.js';
|
|
6
5
|
export interface ConversationPageView {
|
|
7
6
|
events: ConversationEventV1[];
|
|
@@ -120,14 +119,3 @@ export declare class AcpRoleSessionAdapter implements RoleSessionControl {
|
|
|
120
119
|
}>;
|
|
121
120
|
private call;
|
|
122
121
|
}
|
|
123
|
-
export declare class TmuxRoleSessionAdapter implements RoleSessionControl {
|
|
124
|
-
private readonly roleId;
|
|
125
|
-
private readonly tmux;
|
|
126
|
-
constructor(roleId: string, tmux?: Tmux);
|
|
127
|
-
describe(): Promise<SessionDescriptor>;
|
|
128
|
-
snapshot(): Promise<SessionSnapshot>;
|
|
129
|
-
recentOutput(request?: {
|
|
130
|
-
limit?: number;
|
|
131
|
-
}): Promise<OutputPage>;
|
|
132
|
-
sendText(text: string): Promise<SendReceipt>;
|
|
133
|
-
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
1
|
import { controlRequest, followConversation } from '../session/control.js';
|
|
3
|
-
import { Tmux } from '../tmux.js';
|
|
4
2
|
import { FleetError, normalizeError } from './errors.js';
|
|
5
3
|
const visibleEvents = (events) => events.filter(event => event.kind !== 'thought');
|
|
6
4
|
export class AcpRoleSessionAdapter {
|
|
@@ -149,43 +147,3 @@ export class AcpRoleSessionAdapter {
|
|
|
149
147
|
}
|
|
150
148
|
}
|
|
151
149
|
}
|
|
152
|
-
export class TmuxRoleSessionAdapter {
|
|
153
|
-
roleId;
|
|
154
|
-
tmux;
|
|
155
|
-
constructor(roleId, tmux = new Tmux()) {
|
|
156
|
-
this.roleId = roleId;
|
|
157
|
-
this.tmux = tmux;
|
|
158
|
-
}
|
|
159
|
-
async describe() {
|
|
160
|
-
return { backend: 'tmux', protocolVersion: 1, features: ['text', 'capture'] };
|
|
161
|
-
}
|
|
162
|
-
async snapshot() {
|
|
163
|
-
const alive = await this.tmux.has(this.roleId);
|
|
164
|
-
return { backend: 'tmux', alive, readiness: alive ? 'idle' : 'failed' };
|
|
165
|
-
}
|
|
166
|
-
async recentOutput(request = {}) {
|
|
167
|
-
try {
|
|
168
|
-
const text = await this.tmux.capture(this.roleId, Math.min(request.limit ?? 100, 500));
|
|
169
|
-
return { events: [], text, truncated: false };
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
throw normalizeError(error);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
async sendText(text) {
|
|
176
|
-
if (!text.trim())
|
|
177
|
-
throw new FleetError('invalid_request', 'text is required');
|
|
178
|
-
if (Buffer.byteLength(text) > 32 * 1024)
|
|
179
|
-
throw new FleetError('invalid_request', 'text exceeds 32 KiB');
|
|
180
|
-
try {
|
|
181
|
-
await this.tmux.sendText(this.roleId, text);
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
throw normalizeError(error);
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
accepted: true, promptId: randomUUID(), queuedBehind: 0, terminalOutcomeKnown: false,
|
|
188
|
-
detail: 'literal text and Enter sent; terminal outcome is unknown',
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { InterruptOutcome, QueuedPrompt,
|
|
1
|
+
import type { AgentSession, InterruptOutcome, QueuedPrompt, SubmitPromptOptions, TurnCancellationSource } from '../session/types.js';
|
|
2
2
|
/** Thin in-process session mutation kernel. Callers retain every policy decision. */
|
|
3
|
-
export declare const queueSessionPrompt: (session:
|
|
4
|
-
export declare const interruptSession: (session:
|
|
5
|
-
export declare const respondSessionPermission: (session:
|
|
3
|
+
export declare const queueSessionPrompt: (session: AgentSession, text: string, options?: SubmitPromptOptions) => Promise<QueuedPrompt>;
|
|
4
|
+
export declare const interruptSession: (session: AgentSession, source: TurnCancellationSource) => Promise<InterruptOutcome>;
|
|
5
|
+
export declare const respondSessionPermission: (session: AgentSession, permissionId: string, optionId: string) => boolean;
|
|
6
6
|
export type GenerationPermissionResult = 'accepted' | 'stale' | 'unavailable';
|
|
7
|
-
export declare const respondSessionPermissionV2: (session:
|
|
7
|
+
export declare const respondSessionPermissionV2: (session: AgentSession, permissionId: string, optionId: string, sessionGeneration: string) => GenerationPermissionResult;
|
|
@@ -117,7 +117,6 @@ export interface RoleCapabilities {
|
|
|
117
117
|
};
|
|
118
118
|
input: {
|
|
119
119
|
text: boolean;
|
|
120
|
-
rawKeys: boolean;
|
|
121
120
|
interrupt: boolean;
|
|
122
121
|
steering: boolean;
|
|
123
122
|
};
|
|
@@ -125,12 +124,6 @@ export interface RoleCapabilities {
|
|
|
125
124
|
observe: boolean;
|
|
126
125
|
respond: boolean;
|
|
127
126
|
};
|
|
128
|
-
terminal: {
|
|
129
|
-
available: boolean;
|
|
130
|
-
reason?: 'wrong_backend' | 'pty_unavailable' | 'offline' | 'incompatible_runner';
|
|
131
|
-
multiViewer: boolean;
|
|
132
|
-
writerLease: boolean;
|
|
133
|
-
};
|
|
134
127
|
lifecycle: {
|
|
135
128
|
start: boolean;
|
|
136
129
|
stop: boolean;
|
package/dist/briefing.js
CHANGED
|
@@ -190,9 +190,6 @@ export function generateBriefing(role, v, opts) {
|
|
|
190
190
|
L.push(`7. Await messages. When the monitor wakes you (or the owner requests a manual check),`);
|
|
191
191
|
L.push(` call **${v.getMessagesTool}**, act on them,`);
|
|
192
192
|
L.push(` and reply with ${v.sendTool}. No coordinator is configured — the owner drives you`);
|
|
193
|
-
// NOT `tmux attach -t <name>`: each role's pane lives on its own tmux
|
|
194
|
-
// socket (#32), so a bare attach finds no server. `ours-fleet attach`
|
|
195
|
-
// addresses the right one.
|
|
196
193
|
L.push(` via \`ours-fleet attach ${role.name}\` or by messaging "${id}".`);
|
|
197
194
|
}
|
|
198
195
|
if (role.oversee?.length) {
|
|
@@ -221,7 +218,7 @@ export function generateBriefing(role, v, opts) {
|
|
|
221
218
|
L.push('idle or stalled from `readiness=idle` alone.');
|
|
222
219
|
L.push('');
|
|
223
220
|
L.push('Then judge the console content: stuck on a prompt/menu/trust dialog → answer it directly');
|
|
224
|
-
L.push('with `ours-fleet send <Name> "<text>"
|
|
221
|
+
L.push('with `ours-fleet send <Name> "<text>"`; idle with work');
|
|
225
222
|
L.push('assigned → nudge; actively working → do nothing, and do not mistake a long turn for a');
|
|
226
223
|
L.push('stall. Escalate over ours messaging only when you cannot resolve it yourself.');
|
|
227
224
|
}
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
5
|
-
"dirty":
|
|
6
|
-
"builtAt": "2026-08-
|
|
2
|
+
"version": "1.1.0-nightly.1",
|
|
3
|
+
"buildId": "bf3c406ba57c",
|
|
4
|
+
"commit": "10d6da8bfd9903d216bce80a9ef89a42c282f8d9",
|
|
5
|
+
"dirty": true,
|
|
6
|
+
"builtAt": "2026-08-30T07:44:27.551Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"monitor.interrupt.after_tool"
|
|
9
9
|
]
|