@ours.network/fleet 1.1.0-nightly.2 → 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 +14 -22
- package/dist/application/role-creation-service.d.ts +2 -21
- package/dist/application/role-creation-service.js +15 -71
- package/dist/application/task-room-service.js +7 -2
- package/dist/briefing.js +3 -3
- package/dist/build-info.json +4 -4
- package/dist/cli.js +40 -33
- package/dist/config.d.ts +37 -0
- package/dist/config.js +40 -12
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +19 -30
- package/dist/doctor.js +3 -3
- package/dist/fleet-proxy.js +30 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/model-env.d.ts +2 -2
- package/dist/model-env.js +1 -1
- package/dist/owner-channel/commands.js +2 -1
- 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/spawn.d.ts +6 -31
- package/dist/spawn.js +45 -233
- 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/topology-promote.js +8 -1
- package/dist/web-app/assets/index-BbTc5KtZ.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/package.json +1 -1
- package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
package/README.md
CHANGED
|
@@ -126,19 +126,16 @@ ours-fleet spawn Worker --mission "own the worker repo" \
|
|
|
126
126
|
ours-fleet spawn --temp Scout --mission "one-off research" # gone on exit/reboot
|
|
127
127
|
|
|
128
128
|
# Codex role: ours-codex is preferred automatically; plain codex is the fallback
|
|
129
|
-
ours-fleet spawn Coder --
|
|
130
|
-
--
|
|
131
|
-
--profile fleet --search --monitor --coordinator FleetCoordinator
|
|
132
|
-
# Note: --monitor is legacy consent for Codex's native monitor. Choose the
|
|
133
|
-
# 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
|
|
134
131
|
```
|
|
135
132
|
|
|
136
133
|
Inside a managed ACP role, `ours-fleet spawn` is transparently routed through
|
|
137
|
-
that role's live supervisor. `ours-fleet spawn
|
|
138
|
-
minimal form: omitted
|
|
139
|
-
fleet monitor policy
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
142
139
|
the caller's owner channel with the caller and spawned-role details. This is an
|
|
143
140
|
honest-actor convenience and attribution path, not a security boundary; host
|
|
144
141
|
shells and deliberately bypassed absolute binaries retain direct behavior.
|
|
@@ -305,7 +302,7 @@ ours-fleet up|down|restart|force-restart [-c FILE] [Name...]
|
|
|
305
302
|
ours-fleet config [-c FILE] validate + print merged plan
|
|
306
303
|
ours-fleet ls | attach | peek | logs [-f] | status <Name>
|
|
307
304
|
ours-fleet send <Name> "text"
|
|
308
|
-
ours-fleet spawn [--temp] [<Name> | --
|
|
305
|
+
ours-fleet spawn [--temp] [<Name> | --name <Name>] --brain <ID|inline:{...}> --role <ID|inline:{...}> [--approval ...]
|
|
309
306
|
ours-fleet loops validate|list|status
|
|
310
307
|
ours-fleet loops reload <Role>
|
|
311
308
|
ours-fleet loops run-now|disable|enable <Role> <Loop>
|
|
@@ -359,13 +356,9 @@ defaults:
|
|
|
359
356
|
max_kb: 1024 # rotate only above this active-log size
|
|
360
357
|
keep_tail_kb: 256 # UTF-8 tail; line-aligned when one fits
|
|
361
358
|
max_archives: 12 # recent beside log; older preserved cold
|
|
362
|
-
Agent document:
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
session: acp # optional; ACP is the only supported session
|
|
366
|
-
session_options:
|
|
367
|
-
acp:
|
|
368
|
-
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
|
|
369
362
|
identity: "Display Name" # ours identity to bind (default: Name)
|
|
370
363
|
cwd: ${work_root}/repo # where the harness process runs
|
|
371
364
|
coordinator: FleetCoordinator # announce target on boot
|
|
@@ -1051,10 +1044,9 @@ harness_options:
|
|
|
1051
1044
|
config: { model_reasoning_effort: high }
|
|
1052
1045
|
```
|
|
1053
1046
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
`--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
|
|
1058
1050
|
role-specific ours daemon, or configure the host default in `~/.ours/config.json`.
|
|
1059
1051
|
|
|
1060
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,17 +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 { getAdapter } from '../harness/registry.js';
|
|
14
|
-
import { claudeModelCatalog, codexModelCatalog, } from './model-catalog.js';
|
|
15
13
|
const canonical = (value) => {
|
|
16
14
|
if (Array.isArray(value))
|
|
17
15
|
return `[${value.map(canonical).join(',')}]`;
|
|
@@ -81,47 +79,16 @@ export class RoleCreationService {
|
|
|
81
79
|
async capabilities() {
|
|
82
80
|
const reasons = [];
|
|
83
81
|
let defaults = {};
|
|
84
|
-
let roles = [];
|
|
85
82
|
try {
|
|
86
83
|
const config = loadConfig(this.options.configPath);
|
|
87
84
|
defaults = config.defaults;
|
|
88
|
-
roles = config.roles;
|
|
89
85
|
}
|
|
90
86
|
catch (error) {
|
|
91
87
|
reasons.push(`configuration is invalid: ${error.message}`);
|
|
92
88
|
}
|
|
93
|
-
const modelsFor = (harness, catalog) => {
|
|
94
|
-
const configured = roles.filter(role => role.harness === harness)
|
|
95
|
-
.flatMap(role => [role.model, ...(role.model_chain ?? [])])
|
|
96
|
-
.filter((model) => Boolean(model));
|
|
97
|
-
const inherited = resolveRoleModel(undefined, harness, defaults);
|
|
98
|
-
const configuredOptions = [...new Set([...(inherited ? [inherited] : []), ...configured])]
|
|
99
|
-
.filter(id => !catalog.models.some(model => model.id === id))
|
|
100
|
-
.map(id => ({ id, label: `${id} (configured)`, reasoningEfforts: [], source: 'fleet-config' }));
|
|
101
|
-
const modelOptions = [...configuredOptions, ...catalog.models];
|
|
102
|
-
return { modelOptions, models: modelOptions.map(model => model.id) };
|
|
103
|
-
};
|
|
104
|
-
const codexCatalog = this.options.modelCatalogs?.codex?.() ?? codexModelCatalog();
|
|
105
|
-
const claudeCatalog = this.options.modelCatalogs?.['claude-code']?.() ?? claudeModelCatalog();
|
|
106
|
-
const codexModels = modelsFor('codex', codexCatalog);
|
|
107
|
-
const claudeModels = modelsFor('claude-code', claudeCatalog);
|
|
108
89
|
return {
|
|
109
90
|
available: reasons.length === 0,
|
|
110
91
|
reasons,
|
|
111
|
-
harnesses: [
|
|
112
|
-
{
|
|
113
|
-
id: 'codex', available: true, sessions: ['acp'],
|
|
114
|
-
defaultModel: resolveRoleModel(undefined, 'codex', defaults),
|
|
115
|
-
...codexModels, catalogSource: 'codex-runtime-catalog', customModelAllowed: true,
|
|
116
|
-
warnings: codexCatalog.warnings,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
id: 'claude-code', available: true, sessions: ['acp'],
|
|
120
|
-
defaultModel: resolveRoleModel(undefined, 'claude-code', defaults),
|
|
121
|
-
...claudeModels, catalogSource: 'claude-adapter-2.1', customModelAllowed: true,
|
|
122
|
-
warnings: claudeCatalog.warnings,
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
92
|
lifetimes: ['permanent', 'temporary'],
|
|
126
93
|
identityBootstrap: {
|
|
127
94
|
mode: 'current-fleet-first-boot',
|
|
@@ -145,14 +112,12 @@ export class RoleCreationService {
|
|
|
145
112
|
const defaults = cfg.defaults;
|
|
146
113
|
const opts = this.spawnOptions(request);
|
|
147
114
|
validateSpawnOpts(opts);
|
|
148
|
-
|
|
115
|
+
const resolved = spawnDryRun(opts).resolvedRole;
|
|
149
116
|
const cwd = request.cwd ? this.resolveCwd(request.cwd) : undefined;
|
|
150
117
|
const effective = {
|
|
151
118
|
name: request.name, identity: request.name,
|
|
152
|
-
harness:
|
|
153
|
-
|
|
154
|
-
model: resolveRoleModel(request.model, request.harness, defaults),
|
|
155
|
-
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
119
|
+
harness: resolved.harness, session: resolved.session, model: resolved.model,
|
|
120
|
+
reasoningEffort: resolved.effort,
|
|
156
121
|
cwd, lifetime: request.lifetime,
|
|
157
122
|
permissions: resolvePermissions(defaults.permissions, request.permissions),
|
|
158
123
|
monitor: resolveMonitorConfig(defaults.monitor, request.monitor),
|
|
@@ -184,9 +149,7 @@ export class RoleCreationService {
|
|
|
184
149
|
if (existingIdentity === 'unknown' && !request.unverifiedIdentityAcknowledged)
|
|
185
150
|
prerequisites.push('confirm creation with an unverified identity preflight');
|
|
186
151
|
const provenance = {
|
|
187
|
-
|
|
188
|
-
model: request.model !== undefined ? 'request'
|
|
189
|
-
: resolveRoleModel(undefined, request.harness, defaults) ? 'fleet-default' : 'built-in',
|
|
152
|
+
brain: 'request', role: 'request', identity: 'built-in',
|
|
190
153
|
cwd: request.cwd ? 'request' : 'built-in', permissions: 'request',
|
|
191
154
|
monitor: request.monitor ? 'request' : defaults.monitor ? 'fleet-default' : 'built-in',
|
|
192
155
|
};
|
|
@@ -297,8 +260,8 @@ export class RoleCreationService {
|
|
|
297
260
|
}
|
|
298
261
|
validate(input) {
|
|
299
262
|
const allowed = new Set([
|
|
300
|
-
'name', '
|
|
301
|
-
'coordinator', 'permissions', '
|
|
263
|
+
'name', 'brain', 'role', 'cwd', 'lifetime',
|
|
264
|
+
'coordinator', 'permissions', 'monitor', 'openAfterCreate',
|
|
302
265
|
'highRiskAcknowledged', 'reuseExistingIdentityAcknowledged',
|
|
303
266
|
'unverifiedIdentityAcknowledged',
|
|
304
267
|
]);
|
|
@@ -308,26 +271,11 @@ export class RoleCreationService {
|
|
|
308
271
|
throw new FleetError('invalid_request', `unsupported web creation field: ${unsupported[0]}`);
|
|
309
272
|
if (!ROLE_NAME_RE.test(input.name))
|
|
310
273
|
throw new FleetError('invalid_request', 'invalid role name');
|
|
311
|
-
if (!
|
|
312
|
-
throw new FleetError('invalid_request', '
|
|
313
|
-
if (input.session !== 'acp')
|
|
314
|
-
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');
|
|
315
276
|
if (!['permanent', 'temporary'].includes(input.lifetime))
|
|
316
277
|
throw new FleetError('invalid_request', 'unsupported lifetime');
|
|
317
|
-
bounded(input.model ?? undefined, 'model', 128);
|
|
318
|
-
bounded(input.reasoningEffort ?? undefined, 'reasoning effort', 16);
|
|
319
|
-
if (input.reasoningEffort != null) {
|
|
320
|
-
try {
|
|
321
|
-
getAdapter(input.harness).agentSession.resolveBrain({ effort: input.reasoningEffort });
|
|
322
|
-
}
|
|
323
|
-
catch (error) {
|
|
324
|
-
throw new FleetError('invalid_request', error.message);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
bounded(input.mission, 'mission', 4_096);
|
|
328
278
|
bounded(input.coordinator, 'coordinator', 128);
|
|
329
|
-
bounded(input.bio, 'bio', 8_192);
|
|
330
|
-
bounded(input.persona, 'persona', 16_384);
|
|
331
279
|
resolvePermissions(undefined, input.permissions);
|
|
332
280
|
if (input.monitor) {
|
|
333
281
|
const problems = validateMonitorConfig(input.monitor);
|
|
@@ -337,10 +285,8 @@ export class RoleCreationService {
|
|
|
337
285
|
throw new FleetError('invalid_request', 'web creation supports monitor.inject=notification only');
|
|
338
286
|
}
|
|
339
287
|
return {
|
|
340
|
-
...input,
|
|
341
|
-
|
|
342
|
-
mission: input.mission?.trim() || undefined, coordinator: input.coordinator?.trim() || undefined,
|
|
343
|
-
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,
|
|
344
290
|
monitor: input.monitor ? structuredClone(input.monitor) : undefined,
|
|
345
291
|
};
|
|
346
292
|
}
|
|
@@ -367,12 +313,10 @@ export class RoleCreationService {
|
|
|
367
313
|
spawnOptions(request, creationActionId) {
|
|
368
314
|
return {
|
|
369
315
|
name: request.name, temp: request.lifetime === 'temporary',
|
|
370
|
-
identity: request.name,
|
|
371
|
-
|
|
372
|
-
reasoningEffort: request.reasoningEffort,
|
|
373
|
-
mission: request.mission, coordinator: request.coordinator,
|
|
316
|
+
identity: request.name, brain: request.brain, role: request.role, cwd: request.cwd,
|
|
317
|
+
coordinator: request.coordinator,
|
|
374
318
|
approval: request.permissions.approval, filesystem: request.permissions.filesystem,
|
|
375
|
-
unattended: request.permissions.unattended,
|
|
319
|
+
unattended: request.permissions.unattended,
|
|
376
320
|
monitorConfig: request.monitor,
|
|
377
321
|
configPath: this.options.configPath,
|
|
378
322
|
surface: creationActionId ? 'web' : undefined, creationActionId,
|
|
@@ -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) {
|
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
|
]
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { VERSION } from './version.js';
|
|
|
9
9
|
import { INIT_COMPLETION_GUIDANCE } from './init-guidance.js';
|
|
10
10
|
import { analyzeInstalls, buildInfo, buildLabel, discoverInstalls, runningLabel, } from './provenance.js';
|
|
11
11
|
import { agentDir, agentsRoot, tmpRoot, logsRoot, deriveXdgRuntimeDir } from './paths.js';
|
|
12
|
-
import { findRole, loadConfig } from './config.js';
|
|
12
|
+
import { findRole, loadConfig, ROLE_NAME_RE } from './config.js';
|
|
13
13
|
import { formatDuration } from './duration.js';
|
|
14
14
|
import { redactSensitive, resolvedPlan } from './resolved-plan.js';
|
|
15
15
|
import { pickBackend } from './supervisor/index.js';
|
|
@@ -74,7 +74,7 @@ const passthrough = (cmd, args) => new Promise(resolve => {
|
|
|
74
74
|
});
|
|
75
75
|
const program = new Command()
|
|
76
76
|
.name('ours-fleet')
|
|
77
|
-
.description('Fleet of persistent, identity-bound AI agents —
|
|
77
|
+
.description('Fleet of persistent, identity-bound AI agents — canonical Brain + Role definitions over ACP sessions.')
|
|
78
78
|
.enablePositionalOptions()
|
|
79
79
|
.version(VERSION);
|
|
80
80
|
const cOpt = (cmd) => cmd.option('-c, --configuration <file>', 'manifest (default: ~/fleet.yaml; documents under ~/fleet/)');
|
|
@@ -218,6 +218,24 @@ function parseCodexConfig(values) {
|
|
|
218
218
|
}
|
|
219
219
|
return out;
|
|
220
220
|
}
|
|
221
|
+
function parseAgentSelection(raw, kind) {
|
|
222
|
+
if (raw === undefined)
|
|
223
|
+
return undefined;
|
|
224
|
+
if (ROLE_NAME_RE.test(raw))
|
|
225
|
+
return { ref: raw };
|
|
226
|
+
if (!raw.startsWith('inline:'))
|
|
227
|
+
throw new Error(`invalid --${kind}; use a declared ID or inline:{...}`);
|
|
228
|
+
let value;
|
|
229
|
+
try {
|
|
230
|
+
value = JSON.parse(raw.slice('inline:'.length));
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
throw new Error(`invalid --${kind} inline mapping`);
|
|
234
|
+
}
|
|
235
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
236
|
+
throw new Error(`invalid --${kind} inline mapping`);
|
|
237
|
+
return { inline: value };
|
|
238
|
+
}
|
|
221
239
|
cOpt(program.command('config').description('validate + print the merged plan (no side effects)'))
|
|
222
240
|
.option('--json', 'emit the stable, versioned, secret-safe resolved plan')
|
|
223
241
|
.option('--yaml-mode <mode>', 'non-plain YAML policy: compat|strict', 'compat')
|
|
@@ -1112,50 +1130,32 @@ cOpt(program.command('rm <name>').description('stop + remove a role (temporary e
|
|
|
1112
1130
|
}
|
|
1113
1131
|
});
|
|
1114
1132
|
cOpt(program.command('spawn [name]').description('spawn a new agent (permanent by default)'))
|
|
1115
|
-
.option('--
|
|
1133
|
+
.option('--name <name>', 'agent name (alternative to the positional name)')
|
|
1134
|
+
.option('--brain <selection>', 'Brain ID or inline:{...} definition')
|
|
1135
|
+
.option('--role <selection>', 'Role ID or inline:{...} definition')
|
|
1116
1136
|
.option('--temp', 'temporary: independent transient supervisor, archived on retirement, gone on reboot')
|
|
1117
|
-
.option('--
|
|
1118
|
-
.option('--session <backend>', 'session backend: acp (default: acp)')
|
|
1119
|
-
.option('--mission <text>', 'one-line mission')
|
|
1120
|
-
.option('--mission-file <path>', 'UTF-8 mission text (mutually exclusive with --mission)')
|
|
1121
|
-
.option('--identity <name>', 'ours identity to bind (default: role name)')
|
|
1137
|
+
.option('--identity <name>', 'ours identity to bind (default: Agent name)')
|
|
1122
1138
|
.option('--cwd <dir>', 'working directory')
|
|
1123
1139
|
.option('--coordinator <name>', 'announce target')
|
|
1124
|
-
.option('--model <id>', 'model id to launch on (e.g. claude-fable-5); default: launcher default')
|
|
1125
|
-
.option('--permission-mode <mode>', 'harness permission mode (Codex: untrusted|on-request|never; Claude: native values)')
|
|
1126
1140
|
.option('--approval <mode>', 'fleet permission mode: ask|auto|allow (Codex ACP allow selects agent-full-access; deny is deprecated)')
|
|
1127
1141
|
.option('--filesystem <mode>', 'common filesystem intent: read-only|workspace|unrestricted')
|
|
1128
1142
|
.option('--unattended <mode>', 'permission behavior without a console: deny|wait')
|
|
1129
|
-
.option('--sandbox <mode>', 'Codex sandbox: read-only|workspace-write|danger-full-access')
|
|
1130
|
-
.option('--profile <name>', 'Codex profile file name ($CODEX_HOME/<name>.config.toml)')
|
|
1131
|
-
.option('--launcher <mode>', 'Codex launcher: auto|ours-codex|codex (default: auto)')
|
|
1132
|
-
.option('--search', 'enable Codex live web search')
|
|
1133
|
-
.option('--codex-config <key=value>', 'Codex config override (repeatable)', collect, [])
|
|
1134
|
-
.option('--add-dir <dir>', 'additional Codex writable directory (repeatable)', collect, [])
|
|
1135
|
-
.option('--monitor', 'legacy: consent to arm Codex\'s native monitor (wake owner is monitor.mode in YAML)')
|
|
1136
|
-
.option('--bio-file <file>', 'public bio (file)')
|
|
1137
|
-
.option('--persona-file <file>', 'persona / operating contract (file)')
|
|
1138
1143
|
.option('--isolation-file <path>', 'file holding an isolation: mapping (same schema as fleet.yaml)')
|
|
1139
1144
|
.option('--dry-run', 'validate and print without reserving or creating anything')
|
|
1140
1145
|
.option('--json', 'with --dry-run, emit a stable secret-safe JSON result')
|
|
1141
1146
|
.action(async (name, opts) => {
|
|
1142
1147
|
try {
|
|
1143
|
-
const roleName = String(name ?? opts.
|
|
1148
|
+
const roleName = String(name ?? opts.name ?? '');
|
|
1144
1149
|
if (!roleName)
|
|
1145
|
-
throw new Error('
|
|
1146
|
-
if (name && opts.
|
|
1147
|
-
throw new Error(`positional
|
|
1150
|
+
throw new Error('agent name is required (positional or --name)');
|
|
1151
|
+
if (name && opts.name && name !== opts.name)
|
|
1152
|
+
throw new Error(`positional agent name conflicts with --name`);
|
|
1148
1153
|
const o = {
|
|
1149
|
-
name: roleName, temp: opts.temp,
|
|
1150
|
-
|
|
1154
|
+
name: roleName, temp: opts.temp,
|
|
1155
|
+
brain: parseAgentSelection(opts.brain, 'brain'), role: parseAgentSelection(opts.role, 'role'),
|
|
1151
1156
|
identity: opts.identity, cwd: opts.cwd, coordinator: opts.coordinator,
|
|
1152
|
-
|
|
1153
|
-
permissionMode: opts.permissionMode, approval: opts.approval,
|
|
1157
|
+
approval: opts.approval,
|
|
1154
1158
|
filesystem: opts.filesystem, unattended: opts.unattended,
|
|
1155
|
-
sandbox: opts.sandbox, profile: opts.profile,
|
|
1156
|
-
launcher: opts.launcher, search: opts.search,
|
|
1157
|
-
codexConfig: parseCodexConfig(opts.codexConfig), addDirs: opts.addDir, monitor: opts.monitor,
|
|
1158
|
-
bioFile: opts.bioFile, personaFile: opts.personaFile,
|
|
1159
1159
|
isolationFile: opts.isolationFile, configPath: opts.configuration,
|
|
1160
1160
|
dryRun: opts.dryRun, json: opts.json,
|
|
1161
1161
|
};
|
|
@@ -1184,7 +1184,7 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
|
|
|
1184
1184
|
if (proxyStateDir) {
|
|
1185
1185
|
// Paths entered in the agent shell belong to that shell's cwd, not the
|
|
1186
1186
|
// supervisor process. Normalize before crossing the control boundary.
|
|
1187
|
-
for (const key of ['
|
|
1187
|
+
for (const key of ['isolationFile']) {
|
|
1188
1188
|
if (o[key])
|
|
1189
1189
|
o[key] = resolvePath(o[key]);
|
|
1190
1190
|
}
|
|
@@ -1497,4 +1497,11 @@ cOpt(program.command('_run-watchdogs', { hidden: true }))
|
|
|
1497
1497
|
die(e);
|
|
1498
1498
|
}
|
|
1499
1499
|
});
|
|
1500
|
-
|
|
1500
|
+
const spawnIndex = process.argv.indexOf('spawn');
|
|
1501
|
+
if (spawnIndex >= 0 && process.argv.slice(spawnIndex + 1).some(arg => arg === '--harness' || arg.startsWith('--harness=') || arg === '--model' || arg.startsWith('--model='))) {
|
|
1502
|
+
console.error('error: --harness and --model were removed; select a Brain with --brain');
|
|
1503
|
+
process.exitCode = 1;
|
|
1504
|
+
}
|
|
1505
|
+
else {
|
|
1506
|
+
program.parseAsync(process.argv);
|
|
1507
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -137,6 +137,27 @@ export interface RoleConfig {
|
|
|
137
137
|
worklog?: WorklogPolicyInput;
|
|
138
138
|
auth_proxy?: Partial<AuthProxyConfig>;
|
|
139
139
|
}
|
|
140
|
+
export type AgentSelection<T extends Record<string, unknown> = Record<string, unknown>> = {
|
|
141
|
+
ref: string;
|
|
142
|
+
} | {
|
|
143
|
+
inline: T;
|
|
144
|
+
};
|
|
145
|
+
/** Canonical authoring contract shared by configured, spawned, and room agents. */
|
|
146
|
+
export interface AgentDefinition {
|
|
147
|
+
role: AgentSelection;
|
|
148
|
+
brain: AgentSelection;
|
|
149
|
+
permissions?: Partial<CommonPermissions>;
|
|
150
|
+
identity?: string;
|
|
151
|
+
cwd?: string;
|
|
152
|
+
coordinator?: string;
|
|
153
|
+
env?: Record<string, string>;
|
|
154
|
+
oversee?: OverseeEntry[];
|
|
155
|
+
isolation?: IsolationConfig;
|
|
156
|
+
monitor?: Partial<MonitorConfig>;
|
|
157
|
+
owner_channel?: OwnerChannelConfigInput;
|
|
158
|
+
worklog?: WorklogPolicyInput;
|
|
159
|
+
auth_proxy?: Partial<AuthProxyConfig>;
|
|
160
|
+
}
|
|
140
161
|
/** Internal first-boot payload for a Fleet-provisioned Cowork room member. */
|
|
141
162
|
export interface RoomMemberStartup {
|
|
142
163
|
room_id: string;
|
|
@@ -171,6 +192,8 @@ export interface ResolvedRole extends Omit<RoleConfig, 'model' | 'owner_channel'
|
|
|
171
192
|
provenance?: Record<string, FieldProvenance>;
|
|
172
193
|
/** Internal/transient: never accepted as a user-authored RoleConfig key. */
|
|
173
194
|
roomMemberStartup?: RoomMemberStartup;
|
|
195
|
+
/** Original unresolved selections only; denied operational fields never enter inheritance state. */
|
|
196
|
+
agentSelections?: Pick<AgentDefinition, 'role' | 'brain'>;
|
|
174
197
|
}
|
|
175
198
|
export interface FieldProvenance {
|
|
176
199
|
sourceFile: string;
|
|
@@ -190,6 +213,8 @@ export interface FieldProvenance {
|
|
|
190
213
|
}
|
|
191
214
|
export interface FleetConfig {
|
|
192
215
|
roles: ResolvedRole[];
|
|
216
|
+
/** Canonical, variable-resolved Agent authoring documents keyed by stable Agent ID. */
|
|
217
|
+
agentDefinitions?: Record<string, AgentDefinition>;
|
|
193
218
|
vars: Record<string, string>;
|
|
194
219
|
defaults: Record<string, unknown>;
|
|
195
220
|
files: string[];
|
|
@@ -229,10 +254,22 @@ export declare function resolveRoleModel(model: string | null | undefined, harne
|
|
|
229
254
|
*/
|
|
230
255
|
export declare function isolationContextFor(role: ResolvedRole): WrapContext;
|
|
231
256
|
export declare const ROLE_NAME_RE: RegExp;
|
|
257
|
+
export declare const ROLE_PRESET_KEYS: string[];
|
|
258
|
+
export declare const BRAIN_PRESET_KEYS: string[];
|
|
259
|
+
export declare const AGENT_KEYS: string[];
|
|
260
|
+
type BarePreset = Record<string, unknown>;
|
|
261
|
+
export declare function validateRoleValue(value: BarePreset, file: string, pointer: string): void;
|
|
262
|
+
export declare function validateBrainValue(value: BarePreset, file: string, pointer: string): void;
|
|
263
|
+
export declare function assertBareKeys(value: unknown, allowed: string[], label: string): BarePreset;
|
|
232
264
|
export declare function splitRootFor(base: string): string;
|
|
233
265
|
/** Load a v2 manifest and bare Agent/Role/Brain documents from its stem directory. */
|
|
234
266
|
export declare function loadConfig(configPath?: string, options?: {
|
|
235
267
|
yamlMode?: YamlMode;
|
|
268
|
+
additionalAgent?: {
|
|
269
|
+
id: string;
|
|
270
|
+
definition: AgentDefinition;
|
|
271
|
+
};
|
|
272
|
+
skipWatchdogs?: boolean;
|
|
236
273
|
}): FleetConfig;
|
|
237
274
|
/**
|
|
238
275
|
* Canonical form of a 64-hex container ID for authorization decisions. Hex
|