@ours.network/fleet 1.0.4 → 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.
Files changed (91) hide show
  1. package/README.md +41 -50
  2. package/dist/application/capabilities.d.ts +0 -1
  3. package/dist/application/capabilities.js +3 -13
  4. package/dist/application/errors.d.ts +1 -1
  5. package/dist/application/fleet-query-service.d.ts +0 -3
  6. package/dist/application/fleet-query-service.js +1 -24
  7. package/dist/application/role-creation-service.d.ts +5 -5
  8. package/dist/application/role-creation-service.js +5 -7
  9. package/dist/application/role-repository.d.ts +1 -2
  10. package/dist/application/role-repository.js +7 -7
  11. package/dist/application/session-control.d.ts +0 -12
  12. package/dist/application/session-control.js +0 -42
  13. package/dist/application/session-mutations.d.ts +5 -5
  14. package/dist/application/task-room-service.d.ts +37 -2
  15. package/dist/application/task-room-service.js +49 -7
  16. package/dist/application/types.d.ts +0 -7
  17. package/dist/atomic-file.d.ts +5 -3
  18. package/dist/atomic-file.js +32 -8
  19. package/dist/briefing.js +1 -4
  20. package/dist/build-info.json +5 -5
  21. package/dist/cli.js +9 -23
  22. package/dist/config.d.ts +7 -7
  23. package/dist/config.js +13 -34
  24. package/dist/docs.d.ts +1 -1
  25. package/dist/docs.js +46 -30
  26. package/dist/doctor.js +0 -7
  27. package/dist/fleet-proxy.d.ts +1 -1
  28. package/dist/harness/acp-session-transport.d.ts +8 -0
  29. package/dist/harness/acp-session-transport.js +3 -0
  30. package/dist/harness/agent-session.d.ts +33 -0
  31. package/dist/harness/agent-session.js +1 -0
  32. package/dist/harness/claude-code-session.d.ts +18 -0
  33. package/dist/harness/claude-code-session.js +28 -0
  34. package/dist/harness/claude-code.d.ts +2 -1
  35. package/dist/harness/claude-code.js +29 -81
  36. package/dist/harness/codex-session.d.ts +18 -0
  37. package/dist/harness/codex-session.js +28 -0
  38. package/dist/harness/codex.d.ts +2 -1
  39. package/dist/harness/codex.js +35 -45
  40. package/dist/harness/types.d.ts +6 -38
  41. package/dist/index.d.ts +3 -5
  42. package/dist/index.js +1 -3
  43. package/dist/isolation/resources.d.ts +3 -7
  44. package/dist/isolation/resources.js +3 -7
  45. package/dist/model-env.d.ts +2 -3
  46. package/dist/model-env.js +2 -3
  47. package/dist/monitor.d.ts +1 -24
  48. package/dist/monitor.js +5 -140
  49. package/dist/owner-channel/attachments.d.ts +2 -4
  50. package/dist/owner-channel/attachments.js +6 -39
  51. package/dist/owner-channel/channel.d.ts +2 -2
  52. package/dist/owner-channel/channel.js +16 -3
  53. package/dist/owner-channel/commands.d.ts +18 -1
  54. package/dist/owner-channel/commands.js +73 -4
  55. package/dist/rooms-tasks/cli.js +121 -2
  56. package/dist/rooms-tasks/task-lists.d.ts +19 -0
  57. package/dist/rooms-tasks/task-lists.js +96 -0
  58. package/dist/rooms-tasks/task-state.d.ts +3 -0
  59. package/dist/rooms-tasks/task-state.js +281 -175
  60. package/dist/rooms-tasks/types.d.ts +11 -1
  61. package/dist/runner.d.ts +5 -15
  62. package/dist/runner.js +26 -102
  63. package/dist/session/acp.d.ts +2 -2
  64. package/dist/session/acp.js +2 -3
  65. package/dist/session/activity.d.ts +2 -3
  66. package/dist/session/activity.js +2 -3
  67. package/dist/session/arbiter.d.ts +4 -4
  68. package/dist/session/control.d.ts +3 -3
  69. package/dist/session/types.d.ts +11 -5
  70. package/dist/spawn.js +8 -9
  71. package/dist/supervisor/none.d.ts +1 -1
  72. package/dist/supervisor/none.js +2 -2
  73. package/dist/tmux.d.ts +1 -14
  74. package/dist/tmux.js +1 -51
  75. package/dist/watchdog/config.js +6 -4
  76. package/dist/watchdog/run.js +8 -23
  77. package/dist/web/auth.d.ts +1 -1
  78. package/dist/web/runtime.js +6 -15
  79. package/dist/web/server.d.ts +3 -3
  80. package/dist/web/server.js +89 -15
  81. package/dist/web/topology-promote.js +2 -5
  82. package/dist/web-app/assets/index-59GF-gsZ.css +1 -0
  83. package/dist/web-app/assets/{index-BCBK78hw.js → index-DhMDVRU1.js} +6 -6
  84. package/dist/web-app/index.html +2 -2
  85. package/package.json +3 -8
  86. package/dist/session/tmux.d.ts +0 -28
  87. package/dist/session/tmux.js +0 -80
  88. package/dist/web/terminal/bridge.d.ts +0 -27
  89. package/dist/web/terminal/bridge.js +0 -317
  90. package/dist/web-app/assets/TerminalView-C_G1ID2P.js +0 -9
  91. 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 a selectable session backend** existing detached tmux consoles
12
- or structured ACP sessions — which you can attach to, peek at, or prompt,
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 ──► tmux session ──► harness CLI (claude …)
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, tmux and Codex ACP remain
102
- available, while maintained Claude ACP requires upgrading to Node 22.
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; tmux,
143
- host shells, and deliberately bypassed absolute binaries retain direct behavior.
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, and a shared tmux browser
213
- terminal. Identity is fixed to the role name. Creation uses the authenticated
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
- `node-pty` is optional: if its native module cannot load, ACP and all
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
- - terminal bytes are intentionally unredacted and are never copied into audit
256
- records; normal logs are bounded and redacted;
257
- - tests use temporary fleet homes, fake supervisors/identity providers, and
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" | --key <K>
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: tmux # tmux (default) | acp
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 # one flag selects ACP; omit for tmux
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
@@ -400,7 +394,6 @@ roles:
400
394
  max_file_bytes: 10485760 # 10 MiB
401
395
  max_request_bytes: 20971520 # 20 MiB total, and >= max_file_bytes
402
396
  retention_ms: 86400000 # stale crash cleanup; 1 minute..30 days
403
- allowed_mime: [application/pdf, text/plain, image/png, audio/ogg]
404
397
  model: claude-fable-5 # launch on a specific model (pass-through id; default: launcher default)
405
398
  mission: one line
406
399
  persona: | # operating contract (published as persona)
@@ -447,15 +440,14 @@ loops: # trusted local scheduled ACP turns
447
440
 
448
441
  Merge order: `fleet.yaml` ← `fleet.d/*.yaml`; a duplicate role name is a hard
449
442
  error naming both files. Identities and roles are decoupled — removing a role
450
- never deletes an identity. `session` is independent of `harness`, so changing a
451
- role from tmux to ACP does not change its identity, mission, monitor, or permission
452
- 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
453
445
  role's `harness_options`, so a fleet can set common Codex permission/profile defaults
454
446
  and override individual keys per role. `monitor` merges the same way — a role block
455
447
  overrides `defaults.monitor` key-by-key.
456
448
 
457
449
  Every supervised role is a client of the operator-configured ours daemon. Fleet strips the
458
- obsolete, presence-sensitive `OURS_AUTOSTART` variable from both tmux and ACP harness
450
+ obsolete, presence-sensitive `OURS_AUTOSTART` variable from ACP harness
459
451
  processes. `ours-mcp proxy` is client-only and never starts a daemon; operators and explicit
460
452
  installer/setup flows remain responsible for starting it.
461
453
 
@@ -598,9 +590,7 @@ since a human can still attach a console and answer.
598
590
  `auto` selects Codex ACP `agent` (`on-request` + `workspace-write`) / Claude
599
591
  `acceptEdits`. `allow` selects Codex ACP's fully non-interactive yolo mode,
600
592
  reported by the adapter as `agent-full-access` (`never` +
601
- `danger-full-access`); Claude uses `bypassPermissions`. For Codex tmux, where the
602
- approval and sandbox flags remain independent, `auto` is `on-request` and
603
- `allow` is `never` while `filesystem` still chooses the sandbox.
593
+ `danger-full-access`); Claude uses `bypassPermissions`.
604
594
  `dontAsk` suppresses only the *prompt*, not the denial, which is why an
605
595
  `allow` role previously ran unable to do its job. Legacy `deny` is accepted
606
596
  only for compatibility and retains its conservative Codex `on-request` /
@@ -690,8 +680,8 @@ their boots ~4 s apart instead of firing all seven at once.
690
680
  With `monitor.mode: fleet` (the default), the **ours-fleet supervisor** delivers
691
681
  a role's mail wakes: the per-role runner long-polls the ours daemon's notification API and
692
682
  submits a single `[fleet-monitor] N new messages from … — run get_messages` prompt
693
- through the selected backend. ACP uses live steering when its adapter supports it
694
- and falls back to structured `session/prompt`; tmux uses verified console input.
683
+ through the shared agent session. ACP uses live steering when its adapter supports it
684
+ and falls back to structured `session/prompt`.
695
685
  Set `monitor.interrupt: true` on roles where every configured wake should cancel
696
686
  the active turn before the notification is delivered. This is intentionally
697
687
  content-blind: the supervisor cannot inspect encrypted message bodies, so all
@@ -701,8 +691,7 @@ tool result or pending permission. Fleet waits for terminal ACP tool/update
701
691
  evidence, then steers the wake without calling `session.cancel`. If the tool is
702
692
  still active after 120 seconds, or the adapter cannot expose authenticated tool
703
693
  boundaries/steering, fleet visibly degrades to non-cancelling steering or queued
704
- delivery. Tmux never receives `C-c` for `after_tool`. Explicit human and control
705
- interrupts remain immediate.
694
+ delivery. Explicit human and control interrupts remain immediate.
706
695
  The default is `false`: a role that must begin a post-readiness mission
707
696
  immediately, including second-and-later mail received while it is working, must
708
697
  set `monitor.mode: fleet` and `monitor.interrupt: true` explicitly. Readiness and
@@ -795,8 +784,8 @@ invites, message bodies, credentials, or keys. A corrupt overlay fails closed
795
784
  (no effective owners and no mutation), and the last effective owner cannot be
796
785
  revoked.
797
786
 
798
- These commands require a running ACP role with `owner_channel` enabled. Missing,
799
- stopped, tmux, disabled, draining, and unavailable-MCP targets fail without
787
+ These commands require a running role with `owner_channel` enabled. Missing,
788
+ stopped, disabled, draining, and unavailable-MCP targets fail without
800
789
  starting a second client, binding an identity, or opening a network listener.
801
790
 
802
791
  The managed agent can use its ordinary ours `send_message` or `send_file` tool to
@@ -967,20 +956,25 @@ message plaintext in fleet state.
967
956
  Inbound owner attachments use the same authenticated-CID and exact-wire routing
968
957
  boundary. Fleet first calls the metadata-only `listIncomingFiles`, groups a
969
958
  file-only wake or a same-sender reply-linked text caption, and checks the enabled,
970
- count, declared MIME, per-file size, and total-size policy before retrieving any
971
- bytes. It then calls selective `getFiles` only for the admitted wire IDs. An
959
+ count, per-file size, and total-size policy before retrieving any bytes. MIME values,
960
+ extensions, file categories, and declared-versus-detected mismatches are metadata,
961
+ never rejection criteria. It then calls selective `getFiles` only for the admitted wire IDs. An
972
962
  unauthorized sender is ignored without retrieval or reply. A rejected authorized
973
963
  request receives a bounded reason correlated to its file wire.
974
964
 
975
965
  Retrieved files must be regular, non-symlink paths whose byte count and SHA-256
976
- match typed SDK metadata. Fleet additionally checks content signatures against the
977
- declared MIME, sanitizes traversal/control characters from names, and copies each
966
+ match typed SDK metadata. Fleet detects recognizable content only to report metadata,
967
+ sanitizes traversal/control characters from names, and copies each
978
968
  file into a random request-scoped directory at mode 0700 with files at mode 0600.
979
969
  The `[fleet-owner]` turn receives only bounded metadata, the private local paths,
980
970
  and an explicit daemon transcription result for voice messages. Successful
981
971
  transcripts are included; `failed` and `unavailable` states are stated plainly so
982
972
  the agent must use the audio path rather than inventing text.
983
973
 
974
+ The legacy `attachments.allowed_mime` key is accepted and ignored so existing
975
+ configurations continue to load. It is omitted from resolved configuration and
976
+ cannot affect attachment admission.
977
+
984
978
  Request files are removed after final delivery and stale directories are removed
985
979
  after `retention_ms`. A bounded mode-0600 recovery journal stores only owner CID
986
980
  and wire routing metadata—never filenames, paths, captions, transcripts, or file
@@ -1002,20 +996,17 @@ Managed-agent file egress is a separate trust direction. It retains the same
1002
996
  metadata provenance checks, count/per-file/request byte caps, regular-file and
1003
997
  no-symlink reads, actual byte-count/SHA-256 verification, filename sanitization,
1004
998
  private staging, fixed owner routing, and bounded body-free recovery metadata.
1005
- It deliberately does not consult `attachments.enabled` or `allowed_mime`; MIME is
999
+ It deliberately does not consult `attachments.enabled`; MIME is
1006
1000
  still detected during byte validation, but a declared-versus-detected mismatch is
1007
- not an egress deny gate. Markdown, HTML, octet-stream, unknown extensions, and normal
1008
- binaries are therefore routable only from the exact configured `agent` CID.
1009
- Owner-to-agent admission above remains byte-for-byte strict. Each outbound file
1001
+ not a deny gate in either direction. Markdown, HTML, octet-stream, unknown extensions,
1002
+ and normal binaries are therefore routable only across their authenticated direction.
1003
+ Each outbound file
1010
1004
  gets a durable pre-send marker: a failed/ambiguous send becomes `uncertain` and is
1011
1005
  not blindly retried, while files not yet attempted remain recoverable after restart.
1012
1006
  Logs contain counts and byte totals, never filenames or raw bytes.
1013
1007
 
1014
- Owner channels currently require `session: acp`. Fleet needs structured,
1015
- turn-correlated assistant output for automatic replies; scraping a tmux pane
1016
- cannot reliably distinguish the final answer from thoughts, tool output, or
1017
- unrelated concurrent work. The config rejects tmux instead of silently offering
1018
- weaker semantics.
1008
+ Owner channels use the structured agent-session interface for turn-correlated
1009
+ assistant output and reliable automatic replies.
1019
1010
 
1020
1011
  ## Codex roles
1021
1012
 
@@ -1079,7 +1070,7 @@ role-specific ours daemon, or configure the host default in `~/.ours/config.json
1079
1070
 
1080
1071
  Each role can be sandboxed at the environment level via an `isolation:` block —
1081
1072
  **fully additive: a role with no block behaves exactly as before.** The agent's
1082
- tmux-pane process is wrapped in [bubblewrap](https://github.com/containers/bubblewrap)
1073
+ agent process is wrapped in [bubblewrap](https://github.com/containers/bubblewrap)
1083
1074
  (rootless, no setuid), resource-limited by `systemd-run --user --scope`.
1084
1075
 
1085
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' || role.detectedBackend === 'tmux' ? role.detectedBackend : undefined);
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 || tmux, stream: (acp && protocolVersion >= 2) || terminalAvailable,
13
+ recent: acp, stream: acp && protocolVersion >= 2,
20
14
  structured: acp, replayCursor: acp && protocolVersion >= 2,
21
15
  },
22
- input: { text: online && (acp || tmux), rawKeys: terminalAvailable, interrupt: online && acp, steering: false },
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,4 +1,4 @@
1
- export type FleetErrorCode = 'role_not_found' | 'capability_unavailable' | 'invalid_request' | 'offline' | 'control_unavailable' | 'timeout' | 'rejected' | 'backend_failure' | 'conflict' | 'stale_state' | 'prerequisite_unavailable' | 'idempotency_conflict' | 'rollback_incomplete' | 'launched_unconfirmed' | 'unauthorized' | 'forbidden' | 'rate_limited' | 'incompatible_version' | 'internal';
1
+ export type FleetErrorCode = 'role_not_found' | 'resource_not_found' | 'capability_unavailable' | 'invalid_request' | 'offline' | 'control_unavailable' | 'timeout' | 'rejected' | 'backend_failure' | 'conflict' | 'stale_state' | 'prerequisite_unavailable' | 'idempotency_conflict' | 'rollback_incomplete' | 'launched_unconfirmed' | 'unauthorized' | 'forbidden' | 'rate_limited' | 'incompatible_version' | 'internal';
2
2
  export interface FleetErrorShape {
3
3
  code: FleetErrorCode;
4
4
  message: string;
@@ -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 ACP/tmux session is the user's live interaction surface. Its
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' | 'tmux';
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' | 'tmux'>;
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' | 'tmux';
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' | 'tmux';
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' | 'tmux') => Promise<'ready' | 'attention' | 'unknown'>;
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', 'tmux'],
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', 'tmux'],
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 ?? 'tmux',
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.session === 'acp'
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 (!['acp', 'tmux'].includes(input.session))
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' | 'tmux') => Promise<{
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
- const session = raw.session === 'acp' || raw.session === 'tmux' ? raw.session : 'tmux';
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, tmux: false }), timeoutMs)),
171
- ]).catch(() => ({ acp: false, tmux: 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, SessionHandle, SubmitPromptOptions, TurnCancellationSource } from '../session/types.js';
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: SessionHandle, text: string, options?: SubmitPromptOptions) => Promise<QueuedPrompt>;
4
- export declare const interruptSession: (session: SessionHandle, source: TurnCancellationSource) => Promise<InterruptOutcome>;
5
- export declare const respondSessionPermission: (session: SessionHandle, permissionId: string, optionId: string) => boolean;
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: SessionHandle, permissionId: string, optionId: string, sessionGeneration: string) => GenerationPermissionResult;
7
+ export declare const respondSessionPermissionV2: (session: AgentSession, permissionId: string, optionId: string, sessionGeneration: string) => GenerationPermissionResult;