@ours.network/fleet 1.1.0-nightly.1 → 1.1.0-nightly.3
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 +76 -93
- package/dist/application/role-creation-service.d.ts +2 -21
- package/dist/application/role-creation-service.js +15 -64
- package/dist/application/role-removal-service.js +6 -3
- package/dist/application/task-room-service.js +7 -2
- package/dist/application/types.d.ts +1 -1
- package/dist/briefing.js +5 -5
- package/dist/build-info.json +4 -4
- package/dist/cli.js +48 -38
- package/dist/config.d.ts +65 -2
- package/dist/config.js +337 -25
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +2 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +48 -97
- package/dist/doctor.js +4 -4
- package/dist/fleet-proxy.js +30 -8
- package/dist/generated-agent-source.d.ts +9 -0
- package/dist/generated-agent-source.js +53 -0
- package/dist/harness/agent-session.d.ts +13 -0
- package/dist/harness/claude-code-session.d.ts +4 -0
- package/dist/harness/claude-code-session.js +4 -0
- package/dist/harness/claude-code.js +10 -0
- package/dist/harness/codex-session.d.ts +4 -0
- package/dist/harness/codex-session.js +4 -0
- package/dist/harness/codex.js +14 -0
- package/dist/harness/registry.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/init-guidance.d.ts +1 -0
- package/dist/init-guidance.js +5 -0
- package/dist/model-env.d.ts +2 -3
- package/dist/model-env.js +3 -5
- package/dist/ops.js +4 -2
- package/dist/owner-channel/commands.js +2 -1
- package/dist/resolved-plan.d.ts +3 -2
- package/dist/resolved-plan.js +21 -5
- package/dist/rooms-tasks/cli.js +2 -1
- package/dist/rooms-tasks/config.js +24 -9
- package/dist/rooms-tasks/provision.js +71 -36
- package/dist/rooms-tasks/templates.js +6 -6
- package/dist/rooms-tasks/types.d.ts +8 -15
- package/dist/rooms-tasks/types.js +1 -4
- package/dist/sensitive-config.d.ts +2 -0
- package/dist/sensitive-config.js +4 -0
- package/dist/spawn.d.ts +8 -33
- package/dist/spawn.js +52 -211
- package/dist/watchdog/config.d.ts +10 -7
- package/dist/watchdog/config.js +29 -19
- package/dist/watchdog/run.js +4 -13
- package/dist/web/fleet-config-service.d.ts +11 -8
- package/dist/web/fleet-config-service.js +383 -183
- package/dist/web/topology-promote.js +25 -15
- package/dist/web/topology.js +2 -2
- package/dist/web/yaml-document-edit.js +2 -0
- package/dist/web-app/assets/index-BbTc5KtZ.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/examples/fleet/agents/Alice.yaml +11 -0
- package/examples/fleet/agents/FleetCoordinator.yaml +7 -0
- package/examples/fleet/brains/claude-default.yaml +5 -0
- package/examples/fleet/roles/coordinator.yaml +5 -0
- package/examples/fleet/roles/developer.yaml +3 -0
- package/examples/fleet.yaml +74 -0
- package/package.json +2 -1
- package/dist/web-app/assets/index-DhMDVRU1.js +0 -10
package/README.md
CHANGED
|
@@ -19,10 +19,10 @@ turns such sessions into **roles**: long-lived agents that
|
|
|
19
19
|
- can **spawn subagents** (permanent or temporary) and **oversee** them: peek into
|
|
20
20
|
a ward's console, answer a stuck prompt, nudge it back to work.
|
|
21
21
|
|
|
22
|
-
The
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
reality match
|
|
22
|
+
The fleet uses a v2 manifest plus typed documents: `~/fleet.yaml` holds
|
|
23
|
+
fleet-wide operational policy and automation, while `~/fleet/agents`,
|
|
24
|
+
`~/fleet/roles`, and `~/fleet/brains` hold bare Agent, Role, and Brain documents.
|
|
25
|
+
`ours-fleet up` makes reality match that trusted split source set.
|
|
26
26
|
|
|
27
27
|
**Harness-agnostic by design.** The core never assumes a specific agent CLI; each
|
|
28
28
|
harness is a small adapter (how to launch, how to resume, how to wire config).
|
|
@@ -31,17 +31,19 @@ public — each additional harness (Gemini CLI, OpenCode, …) is a small adapte
|
|
|
31
31
|
A single fleet can mix harnesses per role:
|
|
32
32
|
|
|
33
33
|
```yaml
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
# ~/fleet/agents/Reviewer.yaml
|
|
35
|
+
role: { ref: reviewer }
|
|
36
|
+
brain: { inline: { harness: claude-code, session: acp } }
|
|
37
|
+
|
|
38
|
+
# ~/fleet/agents/Prototyper.yaml
|
|
39
|
+
role: { inline: { mission: Build prototypes } }
|
|
40
|
+
brain: { inline: { harness: codex, session: acp } }
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
## How it works
|
|
42
44
|
|
|
43
45
|
```
|
|
44
|
-
~/fleet.yaml + ~/fleet
|
|
46
|
+
~/fleet.yaml + ~/fleet/{agents,roles,brains}/*.yaml your declaration
|
|
45
47
|
│ ours-fleet up
|
|
46
48
|
▼
|
|
47
49
|
briefing.md per role ──► agent session adapter ──► ACP client/session ──► ACP agent
|
|
@@ -105,8 +107,9 @@ become that account and repeat.
|
|
|
105
107
|
## Quickstart
|
|
106
108
|
|
|
107
109
|
```sh
|
|
110
|
+
cp -R "$(npm root -g)/@ours.network/fleet/examples/fleet" ~/fleet
|
|
108
111
|
cp "$(npm root -g)/@ours.network/fleet/examples/fleet.yaml" ~/fleet.yaml
|
|
109
|
-
$EDITOR ~/fleet
|
|
112
|
+
$EDITOR ~/fleet/agents/*.yaml # compose Role + Brain and operational settings
|
|
110
113
|
ours-fleet up # boot the fleet (staggered)
|
|
111
114
|
ours-fleet ls # running consoles
|
|
112
115
|
ours-fleet attach Alice # watch one live (Ctrl-b d to leave)
|
|
@@ -123,19 +126,16 @@ ours-fleet spawn Worker --mission "own the worker repo" \
|
|
|
123
126
|
ours-fleet spawn --temp Scout --mission "one-off research" # gone on exit/reboot
|
|
124
127
|
|
|
125
128
|
# Codex role: ours-codex is preferred automatically; plain codex is the fallback
|
|
126
|
-
ours-fleet spawn Coder --
|
|
127
|
-
--
|
|
128
|
-
--profile fleet --search --monitor --coordinator FleetCoordinator
|
|
129
|
-
# Note: --monitor is legacy consent for Codex's native monitor. Choose the
|
|
130
|
-
# wake owner separately in fleet.yaml with monitor.mode: fleet|native.
|
|
129
|
+
ours-fleet spawn Coder --brain codex-fleet --role developer \
|
|
130
|
+
--approval ask --filesystem workspace --coordinator FleetCoordinator
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
Inside a managed ACP role, `ours-fleet spawn` is transparently routed through
|
|
134
|
-
that role's live supervisor. `ours-fleet spawn
|
|
135
|
-
minimal form: omitted
|
|
136
|
-
fleet monitor policy
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
that role's live supervisor. `ours-fleet spawn DeveloperX --temp` is the
|
|
135
|
+
minimal form: omitted Brain/Role selections, cwd, coordinator, neutral permissions,
|
|
136
|
+
and fleet monitor policy inherit from the caller. Identity, environment, owner routing,
|
|
137
|
+
room startup, and sensitive inline Brain values do not. Explicit flags win. After
|
|
138
|
+
creation succeeds, fleet can deterministically notify
|
|
139
139
|
the caller's owner channel with the caller and spawned-role details. This is an
|
|
140
140
|
honest-actor convenience and attribution path, not a security boundary; host
|
|
141
141
|
shells and deliberately bypassed absolute binaries retain direct behavior.
|
|
@@ -252,27 +252,19 @@ Security boundaries:
|
|
|
252
252
|
- tests use temporary fleet homes and fake supervisors/identity providers—never
|
|
253
253
|
active role state.
|
|
254
254
|
|
|
255
|
-
Permanent spawns
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
block sequence — adding or removing an entry under `watch:`, `oversee:`, `roles:`
|
|
269
|
-
or `wake_sources:` — rewrites that one collection as a whole, which drops inline
|
|
270
|
-
comments written on its individual items. The loss is confined to the collection
|
|
271
|
-
you edited, is shown in the diff before anything is written, and can be declined
|
|
272
|
-
by not saving. Each write is revision-guarded, reviewed as a diff of the real
|
|
273
|
-
file, validated with the real loader, and preceded by a timestamped backup.
|
|
274
|
-
Values under `env:` — and the `vars:` entries they interpolate — are masked in
|
|
275
|
-
the diff and never leave the host.
|
|
255
|
+
Permanent spawns write a bare Agent document to
|
|
256
|
+
`~/fleet/agents/<Name>.yaml`; generated-source ownership is recorded so removal
|
|
257
|
+
never deletes an unproven hand-written neighbor. `ours-fleet rm <Name>` unspawns.
|
|
258
|
+
|
|
259
|
+
The web editor reads and writes the split document model explicitly as
|
|
260
|
+
`{manifest, agents}`. Role and Brain presets are visible through Agent refs but
|
|
261
|
+
remain read-only. Saves use one aggregate revision over the manifest and all
|
|
262
|
+
Agent/Role/Brain sources, validate an exact-stem private staging tree, show a
|
|
263
|
+
deterministic redacted diff per document, and hold one manifest-root lock.
|
|
264
|
+
Changed/deleted files are backed up together; any partial failure restores every
|
|
265
|
+
document or reports the private recovery directory. An unchanged save is a
|
|
266
|
+
byte-for-byte no-op and creates no backup. Nested environment, authentication,
|
|
267
|
+
invite, and isolation secrets never leave the host.
|
|
276
268
|
|
|
277
269
|
From inside Claude Code, Codex, or Hermes with the core `ours` plugin installed,
|
|
278
270
|
say **"spawn an ours agent …"**. The core skill checks for `ours-fleet`, installs
|
|
@@ -290,16 +282,16 @@ ours-fleet peek Worker # what is it doing?
|
|
|
290
282
|
ours-fleet send Worker "continue with the tests, then report"
|
|
291
283
|
```
|
|
292
284
|
|
|
293
|
-
Declare standing assignments in
|
|
285
|
+
Declare standing assignments in the overseer's bare Agent document (rendered into its
|
|
294
286
|
briefing) — or just write "keep an eye on Alice and Bob every 5 minutes" in a
|
|
295
287
|
persona; the bundled `oversee-agents` skill defines what that means operationally:
|
|
296
288
|
|
|
297
289
|
```yaml
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
290
|
+
role: { ref: coordinator }
|
|
291
|
+
brain: { ref: claude-default }
|
|
292
|
+
oversee:
|
|
293
|
+
- { agent: Alice, interval: 5m }
|
|
294
|
+
- { agent: Bob, interval: 5m }
|
|
303
295
|
```
|
|
304
296
|
|
|
305
297
|
## Command reference
|
|
@@ -310,7 +302,7 @@ ours-fleet up|down|restart|force-restart [-c FILE] [Name...]
|
|
|
310
302
|
ours-fleet config [-c FILE] validate + print merged plan
|
|
311
303
|
ours-fleet ls | attach | peek | logs [-f] | status <Name>
|
|
312
304
|
ours-fleet send <Name> "text"
|
|
313
|
-
ours-fleet spawn [--temp] [<Name> | --
|
|
305
|
+
ours-fleet spawn [--temp] [<Name> | --name <Name>] --brain <ID|inline:{...}> --role <ID|inline:{...}> [--approval ...]
|
|
314
306
|
ours-fleet loops validate|list|status
|
|
315
307
|
ours-fleet loops reload <Role>
|
|
316
308
|
ours-fleet loops run-now|disable|enable <Role> <Loop>
|
|
@@ -342,33 +334,31 @@ in its state dir) across supervisor-triggered restarts — systemd/launchd re-in
|
|
|
342
334
|
agent process with no arguments, so without this the role would silently fall back to
|
|
343
335
|
the default `~/fleet.yaml` on its very first crash-restart and fail to resolve.
|
|
344
336
|
|
|
345
|
-
##
|
|
337
|
+
## split configuration field reference
|
|
346
338
|
|
|
347
|
-
|
|
339
|
+
The following is a schematic field inventory, not a single YAML document. Host
|
|
340
|
+
settings belong in `~/fleet.yaml`; reusable behavior and harness settings belong
|
|
341
|
+
in `~/fleet/roles/<id>.yaml` and `~/fleet/brains/<id>.yaml`; operational fields
|
|
342
|
+
belong in `~/fleet/agents/<id>.yaml`.
|
|
343
|
+
|
|
344
|
+
```text
|
|
348
345
|
vars: { work_root: /home/me/work } # ${var} substitution anywhere below
|
|
346
|
+
api_version: ours.network/fleet/v2
|
|
349
347
|
start_stagger_ms: 0 # delay between agent LAUNCHES (host-wide, ms); 0 = no stagger
|
|
350
348
|
defaults:
|
|
351
|
-
harness: claude-code # for roles that don't set one
|
|
352
|
-
session: acp # supported Fleet↔agent session path
|
|
353
349
|
permissions: # common intent, translated by each harness/backend
|
|
354
350
|
approval: ask # ask | auto | allow (`deny` is a deprecated alias)
|
|
355
351
|
filesystem: workspace # read-only | workspace | unrestricted
|
|
356
352
|
unattended: deny # deny | wait
|
|
357
|
-
model: claude-fable-5 # default model for roles that don't set one (per-role model / --model wins)
|
|
358
|
-
max_tokens: 500000 # session cap (harness-interpreted)
|
|
359
353
|
monitor:
|
|
360
354
|
mode: fleet # fleet (default) | native
|
|
361
355
|
worklog: # built-ins shown; set false to opt out
|
|
362
356
|
max_kb: 1024 # rotate only above this active-log size
|
|
363
357
|
keep_tail_kb: 256 # UTF-8 tail; line-aligned when one fits
|
|
364
358
|
max_archives: 12 # recent beside log; older preserved cold
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
session: acp # optional; ACP is the only supported session
|
|
369
|
-
session_options:
|
|
370
|
-
acp:
|
|
371
|
-
command: claude-agent-acp # optional advanced override
|
|
359
|
+
Agent document (\`~/fleet/agents/Name.yaml\`):
|
|
360
|
+
role: { ref: RoleID }
|
|
361
|
+
brain: { ref: BrainID } # Brain owns harness/model/session/reasoning
|
|
372
362
|
identity: "Display Name" # ours identity to bind (default: Name)
|
|
373
363
|
cwd: ${work_root}/repo # where the harness process runs
|
|
374
364
|
coordinator: FleetCoordinator # announce target on boot
|
|
@@ -400,7 +390,7 @@ roles:
|
|
|
400
390
|
bio: | # public card (published as bio)
|
|
401
391
|
briefing_file: curated.md # replaces the generated narrative
|
|
402
392
|
env: { KEY: value } # extra session env
|
|
403
|
-
oversee: [{
|
|
393
|
+
oversee: [{ agent: X, interval: 5m }]
|
|
404
394
|
harness_options: # adapter-owned, adapter-validated
|
|
405
395
|
plugins: { "name@marketplace": false } # claude-code: plugin overrides
|
|
406
396
|
# mem_palace: false # claude-code: disable memory plugin
|
|
@@ -438,13 +428,12 @@ loops: # trusted local scheduled ACP turns
|
|
|
438
428
|
If nothing material changed, complete silently without an owner report.
|
|
439
429
|
```
|
|
440
430
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
and
|
|
447
|
-
overrides `defaults.monitor` key-by-key.
|
|
431
|
+
The v2 loader reads the manifest, then sorted bare Role, Brain, and Agent
|
|
432
|
+
documents from its exact stem directory. Duplicate filename-derived IDs are a
|
|
433
|
+
hard error. Identities and Agents are decoupled—removing an Agent never deletes
|
|
434
|
+
an identity. Role, Brain, and operational ownership are composed explicitly;
|
|
435
|
+
there is no generic cross-kind merge. Manifest operational defaults such as
|
|
436
|
+
`monitor` and `permissions` merge only within their owning Agent fields.
|
|
448
437
|
|
|
449
438
|
Every supervised role is a client of the operator-configured ours daemon. Fleet strips the
|
|
450
439
|
obsolete, presence-sensitive `OURS_AUTOSTART` variable from ACP harness
|
|
@@ -1037,33 +1026,27 @@ the structured CLI watcher, because a detached process cannot wake a Codex turn.
|
|
|
1037
1026
|
wait is re-entered after each handled message; `ours-codex` instead wakes the idle
|
|
1038
1027
|
session through its App Server integration.
|
|
1039
1028
|
|
|
1040
|
-
The main Codex controls
|
|
1029
|
+
The main Codex controls are Brain-owned and also available when spawning:
|
|
1041
1030
|
|
|
1042
1031
|
```yaml
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
roles:
|
|
1058
|
-
Reviewer:
|
|
1059
|
-
harness_options:
|
|
1060
|
-
sandbox: read-only # overrides just this default key
|
|
1032
|
+
# ~/fleet/brains/codex-review.yaml
|
|
1033
|
+
harness: codex
|
|
1034
|
+
session: acp
|
|
1035
|
+
model: gpt-5.4
|
|
1036
|
+
harness_options:
|
|
1037
|
+
launcher: auto
|
|
1038
|
+
profile: fleet
|
|
1039
|
+
sandbox: read-only
|
|
1040
|
+
approval: on-request
|
|
1041
|
+
monitor: true
|
|
1042
|
+
search: true
|
|
1043
|
+
add_dirs: [/data/shared]
|
|
1044
|
+
config: { model_reasoning_effort: high }
|
|
1061
1045
|
```
|
|
1062
1046
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
`--codex-config key=value`, and repeatable `--add-dir`. Use `env.OURS_PORT`/`env.OURS_CONFIG` for a
|
|
1047
|
+
One-off/permanent spawn selects a Brain that owns model, reasoning, native permission,
|
|
1048
|
+
sandbox, profile, launcher, search, monitor consent, native config, and additional roots.
|
|
1049
|
+
Use `env.OURS_PORT`/`env.OURS_CONFIG` for a
|
|
1067
1050
|
role-specific ours daemon, or configure the host default in `~/.ours/config.json`.
|
|
1068
1051
|
|
|
1069
1052
|
## Agent isolation
|
|
@@ -5,21 +5,14 @@ import type { OpsDeps } from '../ops.js';
|
|
|
5
5
|
import type { ResolvedRole } from '../config.js';
|
|
6
6
|
import { FleetError } from './errors.js';
|
|
7
7
|
import { type ManagedFleetSpawnResult } from '../fleet-proxy.js';
|
|
8
|
-
import { type HarnessModelCatalog, type HarnessModelOption } from './model-catalog.js';
|
|
9
8
|
export interface CreateRoleSessionRequest {
|
|
10
9
|
name: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
model?: string | null;
|
|
14
|
-
reasoningEffort?: string | null;
|
|
15
|
-
session: 'acp';
|
|
10
|
+
brain: import('../config.js').AgentSelection;
|
|
11
|
+
role: import('../config.js').AgentSelection;
|
|
16
12
|
cwd?: string;
|
|
17
13
|
lifetime: 'permanent' | 'temporary';
|
|
18
|
-
mission?: string;
|
|
19
14
|
coordinator?: string;
|
|
20
15
|
permissions: CommonPermissions;
|
|
21
|
-
bio?: string;
|
|
22
|
-
persona?: string;
|
|
23
16
|
monitor?: WebCreationMonitor;
|
|
24
17
|
openAfterCreate: boolean;
|
|
25
18
|
highRiskAcknowledged?: boolean;
|
|
@@ -38,17 +31,6 @@ export type WebCreationMonitor = {
|
|
|
38
31
|
export interface CreationCapabilities {
|
|
39
32
|
available: boolean;
|
|
40
33
|
reasons: string[];
|
|
41
|
-
harnesses: Array<{
|
|
42
|
-
id: 'codex' | 'claude-code';
|
|
43
|
-
available: boolean;
|
|
44
|
-
sessions: Array<'acp'>;
|
|
45
|
-
defaultModel?: string;
|
|
46
|
-
models: string[];
|
|
47
|
-
modelOptions: HarnessModelOption[];
|
|
48
|
-
catalogSource: string;
|
|
49
|
-
customModelAllowed: true;
|
|
50
|
-
warnings: string[];
|
|
51
|
-
}>;
|
|
52
34
|
lifetimes: Array<'permanent' | 'temporary'>;
|
|
53
35
|
identityBootstrap: {
|
|
54
36
|
mode: 'current-fleet-first-boot';
|
|
@@ -123,7 +105,6 @@ export interface RoleCreationServiceOptions {
|
|
|
123
105
|
journalDir?: string;
|
|
124
106
|
probeReady?: (name: string, session: 'acp') => Promise<'ready' | 'attention' | 'unknown'>;
|
|
125
107
|
onProgress?: (action: CreationAction) => void;
|
|
126
|
-
modelCatalogs?: Partial<Record<'codex' | 'claude-code', () => HarnessModelCatalog>>;
|
|
127
108
|
/** Direct/managed callers must not create or restore the web action journal. */
|
|
128
109
|
journal?: boolean;
|
|
129
110
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
3
3
|
import { isAbsolute, relative } from 'node:path';
|
|
4
|
-
import { loadConfig, NOTIFY_EVENT_TYPES, resolveMonitorConfig,
|
|
4
|
+
import { loadConfig, NOTIFY_EVENT_TYPES, resolveMonitorConfig, resolvePermissions, ROLE_NAME_RE, validateMonitorConfig, } from '../config.js';
|
|
5
5
|
import { daemonIdentityProvisioner, } from '../creation.js';
|
|
6
6
|
import { replaceFileAtomically } from '../atomic-file.js';
|
|
7
7
|
import { stateRoot } from '../paths.js';
|
|
8
|
-
import {
|
|
8
|
+
import { spawnDryRun, spawnPermanent, spawnTemp, validateSpawnOpts, } from '../spawn.js';
|
|
9
9
|
import { FleetError, normalizeError } from './errors.js';
|
|
10
10
|
import { inheritCallerSpawnDefaults } from '../fleet-proxy.js';
|
|
11
11
|
import { effectivePermissionMode } from '../permissions.js';
|
|
12
12
|
import { effectiveRoleModel } from '../model-env.js';
|
|
13
|
-
import { claudeModelCatalog, codexModelCatalog, } from './model-catalog.js';
|
|
14
13
|
const canonical = (value) => {
|
|
15
14
|
if (Array.isArray(value))
|
|
16
15
|
return `[${value.map(canonical).join(',')}]`;
|
|
@@ -80,47 +79,16 @@ export class RoleCreationService {
|
|
|
80
79
|
async capabilities() {
|
|
81
80
|
const reasons = [];
|
|
82
81
|
let defaults = {};
|
|
83
|
-
let roles = [];
|
|
84
82
|
try {
|
|
85
83
|
const config = loadConfig(this.options.configPath);
|
|
86
84
|
defaults = config.defaults;
|
|
87
|
-
roles = config.roles;
|
|
88
85
|
}
|
|
89
86
|
catch (error) {
|
|
90
87
|
reasons.push(`configuration is invalid: ${error.message}`);
|
|
91
88
|
}
|
|
92
|
-
const modelsFor = (harness, catalog) => {
|
|
93
|
-
const configured = roles.filter(role => role.harness === harness)
|
|
94
|
-
.flatMap(role => [role.model, ...(role.model_chain ?? [])])
|
|
95
|
-
.filter((model) => Boolean(model));
|
|
96
|
-
const inherited = resolveRoleModel(undefined, harness, defaults);
|
|
97
|
-
const configuredOptions = [...new Set([...(inherited ? [inherited] : []), ...configured])]
|
|
98
|
-
.filter(id => !catalog.models.some(model => model.id === id))
|
|
99
|
-
.map(id => ({ id, label: `${id} (configured)`, reasoningEfforts: [], source: 'fleet-config' }));
|
|
100
|
-
const modelOptions = [...configuredOptions, ...catalog.models];
|
|
101
|
-
return { modelOptions, models: modelOptions.map(model => model.id) };
|
|
102
|
-
};
|
|
103
|
-
const codexCatalog = this.options.modelCatalogs?.codex?.() ?? codexModelCatalog();
|
|
104
|
-
const claudeCatalog = this.options.modelCatalogs?.['claude-code']?.() ?? claudeModelCatalog();
|
|
105
|
-
const codexModels = modelsFor('codex', codexCatalog);
|
|
106
|
-
const claudeModels = modelsFor('claude-code', claudeCatalog);
|
|
107
89
|
return {
|
|
108
90
|
available: reasons.length === 0,
|
|
109
91
|
reasons,
|
|
110
|
-
harnesses: [
|
|
111
|
-
{
|
|
112
|
-
id: 'codex', available: true, sessions: ['acp'],
|
|
113
|
-
defaultModel: resolveRoleModel(undefined, 'codex', defaults),
|
|
114
|
-
...codexModels, catalogSource: 'codex-runtime-catalog', customModelAllowed: true,
|
|
115
|
-
warnings: codexCatalog.warnings,
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
id: 'claude-code', available: true, sessions: ['acp'],
|
|
119
|
-
defaultModel: resolveRoleModel(undefined, 'claude-code', defaults),
|
|
120
|
-
...claudeModels, catalogSource: 'claude-adapter-2.1', customModelAllowed: true,
|
|
121
|
-
warnings: claudeCatalog.warnings,
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
92
|
lifetimes: ['permanent', 'temporary'],
|
|
125
93
|
identityBootstrap: {
|
|
126
94
|
mode: 'current-fleet-first-boot',
|
|
@@ -144,14 +112,12 @@ export class RoleCreationService {
|
|
|
144
112
|
const defaults = cfg.defaults;
|
|
145
113
|
const opts = this.spawnOptions(request);
|
|
146
114
|
validateSpawnOpts(opts);
|
|
147
|
-
|
|
115
|
+
const resolved = spawnDryRun(opts).resolvedRole;
|
|
148
116
|
const cwd = request.cwd ? this.resolveCwd(request.cwd) : undefined;
|
|
149
117
|
const effective = {
|
|
150
118
|
name: request.name, identity: request.name,
|
|
151
|
-
harness:
|
|
152
|
-
|
|
153
|
-
model: resolveRoleModel(request.model, request.harness, defaults),
|
|
154
|
-
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
119
|
+
harness: resolved.harness, session: resolved.session, model: resolved.model,
|
|
120
|
+
reasoningEffort: resolved.effort,
|
|
155
121
|
cwd, lifetime: request.lifetime,
|
|
156
122
|
permissions: resolvePermissions(defaults.permissions, request.permissions),
|
|
157
123
|
monitor: resolveMonitorConfig(defaults.monitor, request.monitor),
|
|
@@ -183,9 +149,7 @@ export class RoleCreationService {
|
|
|
183
149
|
if (existingIdentity === 'unknown' && !request.unverifiedIdentityAcknowledged)
|
|
184
150
|
prerequisites.push('confirm creation with an unverified identity preflight');
|
|
185
151
|
const provenance = {
|
|
186
|
-
|
|
187
|
-
model: request.model !== undefined ? 'request'
|
|
188
|
-
: resolveRoleModel(undefined, request.harness, defaults) ? 'fleet-default' : 'built-in',
|
|
152
|
+
brain: 'request', role: 'request', identity: 'built-in',
|
|
189
153
|
cwd: request.cwd ? 'request' : 'built-in', permissions: 'request',
|
|
190
154
|
monitor: request.monitor ? 'request' : defaults.monitor ? 'fleet-default' : 'built-in',
|
|
191
155
|
};
|
|
@@ -296,8 +260,8 @@ export class RoleCreationService {
|
|
|
296
260
|
}
|
|
297
261
|
validate(input) {
|
|
298
262
|
const allowed = new Set([
|
|
299
|
-
'name', '
|
|
300
|
-
'coordinator', 'permissions', '
|
|
263
|
+
'name', 'brain', 'role', 'cwd', 'lifetime',
|
|
264
|
+
'coordinator', 'permissions', 'monitor', 'openAfterCreate',
|
|
301
265
|
'highRiskAcknowledged', 'reuseExistingIdentityAcknowledged',
|
|
302
266
|
'unverifiedIdentityAcknowledged',
|
|
303
267
|
]);
|
|
@@ -307,20 +271,11 @@ export class RoleCreationService {
|
|
|
307
271
|
throw new FleetError('invalid_request', `unsupported web creation field: ${unsupported[0]}`);
|
|
308
272
|
if (!ROLE_NAME_RE.test(input.name))
|
|
309
273
|
throw new FleetError('invalid_request', 'invalid role name');
|
|
310
|
-
if (!
|
|
311
|
-
throw new FleetError('invalid_request', '
|
|
312
|
-
if (input.session !== 'acp')
|
|
313
|
-
throw new FleetError('invalid_request', 'unsupported session backend');
|
|
274
|
+
if (!input.brain || !input.role)
|
|
275
|
+
throw new FleetError('invalid_request', 'brain and role selections are required');
|
|
314
276
|
if (!['permanent', 'temporary'].includes(input.lifetime))
|
|
315
277
|
throw new FleetError('invalid_request', 'unsupported lifetime');
|
|
316
|
-
bounded(input.model ?? undefined, 'model', 128);
|
|
317
|
-
bounded(input.reasoningEffort ?? undefined, 'reasoning effort', 16);
|
|
318
|
-
if (input.reasoningEffort != null && !['low', 'medium', 'high', 'xhigh', 'max', 'ultra'].includes(input.reasoningEffort))
|
|
319
|
-
throw new FleetError('invalid_request', 'unsupported reasoning effort');
|
|
320
|
-
bounded(input.mission, 'mission', 4_096);
|
|
321
278
|
bounded(input.coordinator, 'coordinator', 128);
|
|
322
|
-
bounded(input.bio, 'bio', 8_192);
|
|
323
|
-
bounded(input.persona, 'persona', 16_384);
|
|
324
279
|
resolvePermissions(undefined, input.permissions);
|
|
325
280
|
if (input.monitor) {
|
|
326
281
|
const problems = validateMonitorConfig(input.monitor);
|
|
@@ -330,10 +285,8 @@ export class RoleCreationService {
|
|
|
330
285
|
throw new FleetError('invalid_request', 'web creation supports monitor.inject=notification only');
|
|
331
286
|
}
|
|
332
287
|
return {
|
|
333
|
-
...input,
|
|
334
|
-
|
|
335
|
-
mission: input.mission?.trim() || undefined, coordinator: input.coordinator?.trim() || undefined,
|
|
336
|
-
bio: input.bio?.trim() || undefined, persona: input.persona?.trim() || undefined,
|
|
288
|
+
...input, brain: structuredClone(input.brain), role: structuredClone(input.role),
|
|
289
|
+
coordinator: input.coordinator?.trim() || undefined,
|
|
337
290
|
monitor: input.monitor ? structuredClone(input.monitor) : undefined,
|
|
338
291
|
};
|
|
339
292
|
}
|
|
@@ -360,12 +313,10 @@ export class RoleCreationService {
|
|
|
360
313
|
spawnOptions(request, creationActionId) {
|
|
361
314
|
return {
|
|
362
315
|
name: request.name, temp: request.lifetime === 'temporary',
|
|
363
|
-
identity: request.name,
|
|
364
|
-
|
|
365
|
-
reasoningEffort: request.reasoningEffort,
|
|
366
|
-
mission: request.mission, coordinator: request.coordinator,
|
|
316
|
+
identity: request.name, brain: request.brain, role: request.role, cwd: request.cwd,
|
|
317
|
+
coordinator: request.coordinator,
|
|
367
318
|
approval: request.permissions.approval, filesystem: request.permissions.filesystem,
|
|
368
|
-
unattended: request.permissions.unattended,
|
|
319
|
+
unattended: request.permissions.unattended,
|
|
369
320
|
monitorConfig: request.monitor,
|
|
370
321
|
configPath: this.options.configPath,
|
|
371
322
|
surface: creationActionId ? 'web' : undefined, creationActionId,
|
|
@@ -4,6 +4,7 @@ import { loadConfig, ROLE_NAME_RE } from '../config.js';
|
|
|
4
4
|
import { agentDir, stateRoot } from '../paths.js';
|
|
5
5
|
import { rmRole } from '../ops.js';
|
|
6
6
|
import { FleetError } from './errors.js';
|
|
7
|
+
import { provesGeneratedAgentSource } from '../generated-agent-source.js';
|
|
7
8
|
export class RoleRemovalService {
|
|
8
9
|
options;
|
|
9
10
|
constructor(options) {
|
|
@@ -45,8 +46,9 @@ export class RoleRemovalService {
|
|
|
45
46
|
`Stop and uninstall the exact backend registration for '${requested}'.`,
|
|
46
47
|
`Archive then remove ${lifetime === 'temporary' ? temporaryState : permanentState}.`,
|
|
47
48
|
];
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
const generatedSource = role && provesGeneratedAgentSource(agentDir(requested), cfg.files[0], role.sourceFile);
|
|
50
|
+
if (generatedSource)
|
|
51
|
+
effects.push(`Remove generated Agent configuration ${role.sourceFile}.`);
|
|
50
52
|
else if (role)
|
|
51
53
|
effects.push(`Keep hand-written configuration ${role.sourceFile}; the role can be recreated from it.`);
|
|
52
54
|
else
|
|
@@ -76,7 +78,8 @@ export class RoleRemovalService {
|
|
|
76
78
|
mkdirSync(recoveryPath, { recursive: true, mode: 0o700 });
|
|
77
79
|
if (existsSync(sourceState))
|
|
78
80
|
cpSync(sourceState, join(recoveryPath, 'state'), { recursive: true });
|
|
79
|
-
if (role
|
|
81
|
+
if (role && provesGeneratedAgentSource(agentDir(preview.role), cfg.files[0], role.sourceFile)
|
|
82
|
+
&& existsSync(role.sourceFile))
|
|
80
83
|
cpSync(role.sourceFile, join(recoveryPath, basename(role.sourceFile)));
|
|
81
84
|
if (role || preview.lifetime === 'temporary')
|
|
82
85
|
await rmRole(cfg, preview.role, this.options.ops);
|
|
@@ -259,8 +259,13 @@ export class TaskRoomApplicationService {
|
|
|
259
259
|
validateTemplates() {
|
|
260
260
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
261
261
|
return listTemplates(cfg.roomTemplates ?? {}).flatMap(template => {
|
|
262
|
-
const issues = template.members.flatMap(member =>
|
|
263
|
-
|
|
262
|
+
const issues = template.members.flatMap(member => {
|
|
263
|
+
if (!('ref' in member.agent))
|
|
264
|
+
return [];
|
|
265
|
+
const ref = member.agent.ref;
|
|
266
|
+
return cfg.roles.some(role => role.name === ref)
|
|
267
|
+
? [] : [`member ${member.slot}: Agent ref '${ref}' not found`];
|
|
268
|
+
});
|
|
264
269
|
return issues.length ? [{ template: `${template.name}@${template.version}`, issues }] : [];
|
|
265
270
|
});
|
|
266
271
|
}
|
package/dist/briefing.js
CHANGED
|
@@ -174,10 +174,10 @@ export function generateBriefing(role, v, opts) {
|
|
|
174
174
|
L.push('This ACP role has a supervisor-scoped ours-fleet proxy. Use the ordinary');
|
|
175
175
|
L.push('`ours-fleet spawn` command; the CLI routes it through your live supervisor, which');
|
|
176
176
|
L.push('records you as the caller and reports successful creation to your owner channel.');
|
|
177
|
-
L.push('A minimal call is `ours-fleet spawn
|
|
178
|
-
L.push('For omitted
|
|
177
|
+
L.push('A minimal call is `ours-fleet spawn DeveloperName --temp`.');
|
|
178
|
+
L.push('For omitted settings, the supervisor inherits your canonical Brain and Role selections,');
|
|
179
179
|
L.push('working directory, neutral permissions, coordinator, and fleet monitor policy. Every');
|
|
180
|
-
L.push('explicit
|
|
180
|
+
L.push('explicit option wins; identity/session-local and secret material never inherit.');
|
|
181
181
|
L.push('This proxy is attribution and convenience, not a security boundary for unisolated roles.');
|
|
182
182
|
}
|
|
183
183
|
if (role.coordinator) {
|
|
@@ -196,12 +196,12 @@ export function generateBriefing(role, v, opts) {
|
|
|
196
196
|
L.push('', '## Oversight assignments');
|
|
197
197
|
L.push('These agents are your wards — you keep them unstuck:');
|
|
198
198
|
for (const o of role.oversee)
|
|
199
|
-
L.push(`- **${o.
|
|
199
|
+
L.push(`- **${o.agent}** — check every ${o.interval}`);
|
|
200
200
|
L.push('');
|
|
201
201
|
L.push('Procedure (see also the oversee-agents skill if available). On each tick, for each ward');
|
|
202
202
|
L.push('run BOTH — they answer different questions:');
|
|
203
203
|
for (const o of role.oversee)
|
|
204
|
-
L.push(`\`ours-fleet status ${o.
|
|
204
|
+
L.push(`\`ours-fleet status ${o.agent}\` then \`ours-fleet peek ${o.agent}\``);
|
|
205
205
|
L.push('');
|
|
206
206
|
L.push('**One console command is not a liveness verdict.** A `peek` or `send` that fails tells');
|
|
207
207
|
L.push('you what happened to YOUR REQUEST, and only one of its outcomes says the agent is gone.');
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.1.0-nightly.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "1.1.0-nightly.3",
|
|
3
|
+
"buildId": "ecb06fff91d3",
|
|
4
|
+
"commit": "87f6db6848b924074b76ce19f15a6b15866c4da3",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-08-
|
|
6
|
+
"builtAt": "2026-08-30T17:21:38.816Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"monitor.interrupt.after_tool"
|
|
9
9
|
]
|