@ours.network/fleet 1.1.0-nightly.1 → 1.1.0-nightly.10
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 +97 -95
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/fleet-query-service.js +13 -1
- package/dist/application/role-creation-service.d.ts +2 -21
- package/dist/application/role-creation-service.js +26 -64
- package/dist/application/role-removal-service.js +6 -3
- package/dist/application/task-room-service.d.ts +1 -1
- package/dist/application/task-room-service.js +154 -16
- package/dist/application/types.d.ts +1 -1
- package/dist/briefing.js +11 -7
- package/dist/build-info.json +4 -4
- package/dist/cli.js +136 -42
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +65 -2
- package/dist/config.js +385 -26
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +2 -2
- package/dist/daemon-recovery.d.ts +93 -0
- package/dist/daemon-recovery.js +328 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +84 -102
- package/dist/doctor.js +18 -4
- package/dist/fleet-command-audit.d.ts +160 -0
- package/dist/fleet-command-audit.js +433 -0
- package/dist/fleet-proxy.d.ts +2 -0
- 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 +18 -0
- package/dist/harness/claude-code-session.d.ts +6 -0
- package/dist/harness/claude-code-session.js +8 -0
- package/dist/harness/claude-code.js +12 -0
- package/dist/harness/codex-session.d.ts +6 -0
- package/dist/harness/codex-session.js +8 -0
- package/dist/harness/codex.js +20 -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 +6 -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/channel.d.ts +56 -0
- package/dist/owner-channel/channel.js +269 -29
- package/dist/owner-channel/commands.d.ts +2 -0
- package/dist/owner-channel/commands.js +31 -13
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.d.ts +3 -2
- package/dist/resolved-plan.js +21 -5
- package/dist/rooms-tasks/cli.js +225 -40
- package/dist/rooms-tasks/config.js +24 -13
- package/dist/rooms-tasks/external-worker.d.ts +1 -0
- package/dist/rooms-tasks/external-worker.js +14 -9
- package/dist/rooms-tasks/provision.js +98 -36
- package/dist/rooms-tasks/templates.d.ts +0 -1
- package/dist/rooms-tasks/templates.js +9 -76
- package/dist/rooms-tasks/types.d.ts +10 -16
- package/dist/rooms-tasks/types.js +1 -4
- package/dist/runner.d.ts +9 -2
- package/dist/runner.js +84 -20
- package/dist/sensitive-config.d.ts +2 -0
- package/dist/sensitive-config.js +4 -0
- package/dist/session/acp.d.ts +5 -0
- package/dist/session/acp.js +27 -3
- package/dist/session/control.d.ts +14 -1
- package/dist/session/control.js +35 -0
- package/dist/spawn.d.ts +8 -33
- package/dist/spawn.js +52 -211
- package/dist/temp-lifecycle.d.ts +1 -1
- 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 +384 -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 +3 -1
- package/presets/fleet/agents/Agent.yaml +3 -0
- package/presets/fleet/agents/Architect.yaml +3 -0
- package/presets/fleet/agents/Critic.yaml +3 -0
- package/presets/fleet/agents/Developer.yaml +3 -0
- package/presets/fleet/agents/Secretary.yaml +3 -0
- package/presets/fleet/agents/Tester.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- package/presets/fleet/roles/Agent.yaml +6 -0
- package/presets/fleet/roles/Architect.yaml +6 -0
- package/presets/fleet/roles/Critic.yaml +6 -0
- package/presets/fleet/roles/Developer.yaml +6 -0
- package/presets/fleet/roles/Secretary.yaml +6 -0
- package/presets/fleet/roles/Tester.yaml +6 -0
- package/presets/fleet/room_templates/pair.yaml +11 -0
- package/presets/fleet/room_templates/single.yaml +8 -0
- package/presets/fleet/room_templates/team.yaml +12 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +5 -0
- package/dist/web-app/assets/index-DhMDVRU1.js +0 -10
|
@@ -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(',')}]`;
|
|
@@ -62,6 +61,16 @@ export class RoleCreationService {
|
|
|
62
61
|
const creationActionId = randomUUID();
|
|
63
62
|
plan.options.creationActionId = creationActionId;
|
|
64
63
|
const statePath = await this.launchSync(plan.options);
|
|
64
|
+
const selectionSummary = (kind) => {
|
|
65
|
+
const value = plan.options[kind];
|
|
66
|
+
const provenance = plan.inherited.includes(kind) ? 'inherited' : 'explicit';
|
|
67
|
+
if (!value)
|
|
68
|
+
return `unresolved (${provenance})`;
|
|
69
|
+
if ('ref' in value)
|
|
70
|
+
return `ref:${value.ref} (${provenance})`;
|
|
71
|
+
const fingerprint = hash(value.inline).slice(0, 16);
|
|
72
|
+
return `inline:sha256:${fingerprint} (${provenance})`;
|
|
73
|
+
};
|
|
65
74
|
return {
|
|
66
75
|
caller: plan.caller, role: plan.options.name,
|
|
67
76
|
lifetime: plan.options.temp ? 'temporary' : 'permanent', statePath,
|
|
@@ -70,6 +79,7 @@ export class RoleCreationService {
|
|
|
70
79
|
monitor: { mode: plan.preview.resolvedRole.monitor.mode, interrupt: plan.preview.resolvedRole.monitor.interrupt },
|
|
71
80
|
permissionMode: effectivePermissionMode(plan.preview.resolvedRole), inherited: plan.inherited,
|
|
72
81
|
creationActionId,
|
|
82
|
+
brainSummary: selectionSummary('brain'), roleSummary: selectionSummary('role'),
|
|
73
83
|
};
|
|
74
84
|
}
|
|
75
85
|
launchSync(options, creation) {
|
|
@@ -80,47 +90,16 @@ export class RoleCreationService {
|
|
|
80
90
|
async capabilities() {
|
|
81
91
|
const reasons = [];
|
|
82
92
|
let defaults = {};
|
|
83
|
-
let roles = [];
|
|
84
93
|
try {
|
|
85
94
|
const config = loadConfig(this.options.configPath);
|
|
86
95
|
defaults = config.defaults;
|
|
87
|
-
roles = config.roles;
|
|
88
96
|
}
|
|
89
97
|
catch (error) {
|
|
90
98
|
reasons.push(`configuration is invalid: ${error.message}`);
|
|
91
99
|
}
|
|
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
100
|
return {
|
|
108
101
|
available: reasons.length === 0,
|
|
109
102
|
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
103
|
lifetimes: ['permanent', 'temporary'],
|
|
125
104
|
identityBootstrap: {
|
|
126
105
|
mode: 'current-fleet-first-boot',
|
|
@@ -144,14 +123,12 @@ export class RoleCreationService {
|
|
|
144
123
|
const defaults = cfg.defaults;
|
|
145
124
|
const opts = this.spawnOptions(request);
|
|
146
125
|
validateSpawnOpts(opts);
|
|
147
|
-
|
|
126
|
+
const resolved = spawnDryRun(opts).resolvedRole;
|
|
148
127
|
const cwd = request.cwd ? this.resolveCwd(request.cwd) : undefined;
|
|
149
128
|
const effective = {
|
|
150
129
|
name: request.name, identity: request.name,
|
|
151
|
-
harness:
|
|
152
|
-
|
|
153
|
-
model: resolveRoleModel(request.model, request.harness, defaults),
|
|
154
|
-
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
130
|
+
harness: resolved.harness, session: resolved.session, model: resolved.model,
|
|
131
|
+
reasoningEffort: resolved.effort,
|
|
155
132
|
cwd, lifetime: request.lifetime,
|
|
156
133
|
permissions: resolvePermissions(defaults.permissions, request.permissions),
|
|
157
134
|
monitor: resolveMonitorConfig(defaults.monitor, request.monitor),
|
|
@@ -183,9 +160,7 @@ export class RoleCreationService {
|
|
|
183
160
|
if (existingIdentity === 'unknown' && !request.unverifiedIdentityAcknowledged)
|
|
184
161
|
prerequisites.push('confirm creation with an unverified identity preflight');
|
|
185
162
|
const provenance = {
|
|
186
|
-
|
|
187
|
-
model: request.model !== undefined ? 'request'
|
|
188
|
-
: resolveRoleModel(undefined, request.harness, defaults) ? 'fleet-default' : 'built-in',
|
|
163
|
+
brain: 'request', role: 'request', identity: 'built-in',
|
|
189
164
|
cwd: request.cwd ? 'request' : 'built-in', permissions: 'request',
|
|
190
165
|
monitor: request.monitor ? 'request' : defaults.monitor ? 'fleet-default' : 'built-in',
|
|
191
166
|
};
|
|
@@ -296,8 +271,8 @@ export class RoleCreationService {
|
|
|
296
271
|
}
|
|
297
272
|
validate(input) {
|
|
298
273
|
const allowed = new Set([
|
|
299
|
-
'name', '
|
|
300
|
-
'coordinator', 'permissions', '
|
|
274
|
+
'name', 'brain', 'role', 'cwd', 'lifetime',
|
|
275
|
+
'coordinator', 'permissions', 'monitor', 'openAfterCreate',
|
|
301
276
|
'highRiskAcknowledged', 'reuseExistingIdentityAcknowledged',
|
|
302
277
|
'unverifiedIdentityAcknowledged',
|
|
303
278
|
]);
|
|
@@ -307,20 +282,11 @@ export class RoleCreationService {
|
|
|
307
282
|
throw new FleetError('invalid_request', `unsupported web creation field: ${unsupported[0]}`);
|
|
308
283
|
if (!ROLE_NAME_RE.test(input.name))
|
|
309
284
|
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');
|
|
285
|
+
if (!input.brain || !input.role)
|
|
286
|
+
throw new FleetError('invalid_request', 'brain and role selections are required');
|
|
314
287
|
if (!['permanent', 'temporary'].includes(input.lifetime))
|
|
315
288
|
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
289
|
bounded(input.coordinator, 'coordinator', 128);
|
|
322
|
-
bounded(input.bio, 'bio', 8_192);
|
|
323
|
-
bounded(input.persona, 'persona', 16_384);
|
|
324
290
|
resolvePermissions(undefined, input.permissions);
|
|
325
291
|
if (input.monitor) {
|
|
326
292
|
const problems = validateMonitorConfig(input.monitor);
|
|
@@ -330,10 +296,8 @@ export class RoleCreationService {
|
|
|
330
296
|
throw new FleetError('invalid_request', 'web creation supports monitor.inject=notification only');
|
|
331
297
|
}
|
|
332
298
|
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,
|
|
299
|
+
...input, brain: structuredClone(input.brain), role: structuredClone(input.role),
|
|
300
|
+
coordinator: input.coordinator?.trim() || undefined,
|
|
337
301
|
monitor: input.monitor ? structuredClone(input.monitor) : undefined,
|
|
338
302
|
};
|
|
339
303
|
}
|
|
@@ -360,12 +324,10 @@ export class RoleCreationService {
|
|
|
360
324
|
spawnOptions(request, creationActionId) {
|
|
361
325
|
return {
|
|
362
326
|
name: request.name, temp: request.lifetime === 'temporary',
|
|
363
|
-
identity: request.name,
|
|
364
|
-
|
|
365
|
-
reasoningEffort: request.reasoningEffort,
|
|
366
|
-
mission: request.mission, coordinator: request.coordinator,
|
|
327
|
+
identity: request.name, brain: request.brain, role: request.role, cwd: request.cwd,
|
|
328
|
+
coordinator: request.coordinator,
|
|
367
329
|
approval: request.permissions.approval, filesystem: request.permissions.filesystem,
|
|
368
|
-
unattended: request.permissions.unattended,
|
|
330
|
+
unattended: request.permissions.unattended,
|
|
369
331
|
monitorConfig: request.monitor,
|
|
370
332
|
configPath: this.options.configPath,
|
|
371
333
|
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);
|
|
@@ -179,7 +179,7 @@ export declare class TaskRoomApplicationService {
|
|
|
179
179
|
name: string;
|
|
180
180
|
version: number;
|
|
181
181
|
description: string;
|
|
182
|
-
|
|
182
|
+
sourceFile?: string;
|
|
183
183
|
room?: import("../rooms-tasks/types.js").TemplateRoomConfig;
|
|
184
184
|
contract?: string;
|
|
185
185
|
members: import("../rooms-tasks/types.js").TemplateMemberSlot[];
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { ConfigError, loadConfig } from '../config.js';
|
|
3
4
|
import { CoworkProtocolError, CoworkUnavailableError, createCoworkAdapter, } from '../rooms-tasks/cowork-adapter.js';
|
|
4
5
|
import { getBinPath, provisionMembers } from '../rooms-tasks/provision.js';
|
|
5
6
|
import { activateRoom, advanceSaga, createRoomRecord, getRoomRecord, listRoomRecords, setOwnerSeat, setSagaError, } from '../rooms-tasks/room-state.js';
|
|
6
7
|
import { activateTask, blockTask as persistBlockTask, createTask as persistTask, deleteTask as persistDeleteTask, getTask as readTask, listTasks as readTasks, moveTaskToList, reviewTask as persistReviewTask, startTask as transitionTask, TaskStateError, unblockTask as persistUnblockTask, updateTaskRoom, updateTaskTemplate, } from '../rooms-tasks/task-state.js';
|
|
7
8
|
import { createTaskListLocked, DEFAULT_TASK_LIST_ID, deleteTaskListRecordLocked, readTaskLists, renameTaskListLocked, resolveTaskList, TaskListError, withTaskListsLock, } from '../rooms-tasks/task-lists.js';
|
|
9
|
+
import { recordFleetAuditPresentation } from '../fleet-command-audit.js';
|
|
8
10
|
import { hashTemplate, listTemplates, resolveTemplate, snapshotTemplate } from '../rooms-tasks/templates.js';
|
|
9
11
|
import { acceptManagedRoomClose, deleteLegacyClosedRooms, deleteManagedRoom, recordManagedRoomCloseError, } from '../rooms-tasks/close.js';
|
|
10
12
|
import { acceptTaskTerminalIntent, recordTaskTerminalIntentError, settleTaskTerminalIntent, } from '../rooms-tasks/terminal.js';
|
|
@@ -19,6 +21,47 @@ export class TaskRoomApplicationError extends Error {
|
|
|
19
21
|
this.name = 'TaskRoomApplicationError';
|
|
20
22
|
}
|
|
21
23
|
}
|
|
24
|
+
function launchSelection(definition, key) {
|
|
25
|
+
const selected = definition?.[key];
|
|
26
|
+
if (!selected || typeof selected !== 'object' || Array.isArray(selected))
|
|
27
|
+
return undefined;
|
|
28
|
+
const value = selected;
|
|
29
|
+
if (value.kind === 'ref' && typeof value.id === 'string')
|
|
30
|
+
return `ref:${value.id} (reference)`;
|
|
31
|
+
if (value.kind === 'inline' && typeof value.fingerprint === 'string')
|
|
32
|
+
return `inline:${value.fingerprint} (inline)`;
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function launchPermissions(definition) {
|
|
36
|
+
const selected = definition?.permissions;
|
|
37
|
+
if (!selected || typeof selected !== 'object' || Array.isArray(selected))
|
|
38
|
+
return undefined;
|
|
39
|
+
const value = selected;
|
|
40
|
+
const fields = ['approval', 'filesystem', 'unattended'].flatMap(key => typeof value[key] === 'string' ? [`${key}=${value[key]}`] : []);
|
|
41
|
+
return fields.length ? fields.join(',') : undefined;
|
|
42
|
+
}
|
|
43
|
+
function roomParticipants(room) {
|
|
44
|
+
return [...room.member_seats].sort((a, b) => a.role_name.localeCompare(b.role_name)).map(seat => ({
|
|
45
|
+
name: seat.role_name, id: seat.identity_cid,
|
|
46
|
+
brain: launchSelection(seat.launch?.agent_definition, 'brain'),
|
|
47
|
+
role: launchSelection(seat.launch?.agent_definition, 'role') ?? seat.cowork_role,
|
|
48
|
+
permissions: launchPermissions(seat.launch?.agent_definition),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
function taskAgents(task) {
|
|
52
|
+
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
53
|
+
const seats = new Map(room?.member_seats.map(seat => [seat.role_name, seat]) ?? []);
|
|
54
|
+
return [...task.member_roles].sort((a, b) => a.name.localeCompare(b.name)).map(member => {
|
|
55
|
+
const seat = seats.get(member.name);
|
|
56
|
+
return { name: member.name, brain: launchSelection(seat?.launch?.agent_definition, 'brain'),
|
|
57
|
+
role: launchSelection(seat?.launch?.agent_definition, 'role') ?? member.cowork_role,
|
|
58
|
+
permissions: launchPermissions(seat?.launch?.agent_definition) };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function roomFailureEventId(room) {
|
|
62
|
+
const evidence = `${room.created_at}\0${room.saga.phase}\0${room.saga.step_index}\0${room.saga.error ?? ''}`;
|
|
63
|
+
return `sha256:${createHash('sha256').update(evidence).digest('hex')}`;
|
|
64
|
+
}
|
|
22
65
|
/** Exact extraction of the previously CLI-owned task create/start behavior. */
|
|
23
66
|
export class TaskRoomApplicationService {
|
|
24
67
|
configurationPath;
|
|
@@ -43,16 +86,34 @@ export class TaskRoomApplicationService {
|
|
|
43
86
|
start: !request.backlog, no_room: request.noRoom, listId: list.list_id,
|
|
44
87
|
});
|
|
45
88
|
});
|
|
89
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'create', id: task.task_id,
|
|
90
|
+
title: task.title, previousState: 'none', newState: task.state, revision: task.created_at,
|
|
91
|
+
list: task.list_name ?? 'default', roomId: task.room_id,
|
|
92
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
93
|
+
agents: [] });
|
|
46
94
|
if (!request.backlog && !request.noRoom && ref && !task.room_id) {
|
|
47
95
|
try {
|
|
48
|
-
await this.provisionRoom(cfg, task, template,
|
|
49
|
-
task = updateTaskRoom(task.task_id,
|
|
96
|
+
const room = await this.provisionRoom(cfg, task, template, created => {
|
|
97
|
+
task = updateTaskRoom(task.task_id, created.room_id, created.room_identity_cid);
|
|
50
98
|
});
|
|
51
99
|
task = readTask(task.task_id);
|
|
100
|
+
if (task.state === 'active' && room.state === 'active')
|
|
101
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'work', id: task.task_id,
|
|
102
|
+
title: task.title, previousState: 'provisioning', newState: task.state,
|
|
103
|
+
revision: getRoomRecord(task.room_id)?.activated_at ?? task.started_at ?? task.created_at,
|
|
104
|
+
list: task.list_name ?? 'default', roomId: task.room_id,
|
|
105
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
106
|
+
agents: taskAgents(task) });
|
|
107
|
+
else
|
|
108
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: task.task_id,
|
|
109
|
+
state: task.state, category: 'provision_pending', eventId: roomFailureEventId(room) });
|
|
52
110
|
}
|
|
53
111
|
catch (error) {
|
|
54
112
|
if (error instanceof CoworkUnavailableError)
|
|
55
113
|
persistBlockTask(task.task_id, 'Cowork management socket is unavailable');
|
|
114
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task',
|
|
115
|
+
id: task.task_id, state: readTask(task.task_id).state, category: 'provision_failed',
|
|
116
|
+
eventId: task.room_id ? roomFailureEventId(getRoomRecord(task.room_id)) : task.created_at });
|
|
56
117
|
throw error;
|
|
57
118
|
}
|
|
58
119
|
}
|
|
@@ -75,12 +136,36 @@ export class TaskRoomApplicationService {
|
|
|
75
136
|
async startTask(input) {
|
|
76
137
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
77
138
|
let task = transitionTask(input.taskId);
|
|
139
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'start', id: task.task_id,
|
|
140
|
+
title: task.title, previousState: 'backlog', newState: task.state,
|
|
141
|
+
revision: task.started_at ?? task.created_at, list: task.list_name ?? 'default',
|
|
142
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
143
|
+
agents: [] });
|
|
78
144
|
if (task.template && !task.room_id) {
|
|
79
145
|
const template = this.existingTemplate(cfg, task);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
146
|
+
try {
|
|
147
|
+
const room = await this.provisionRoom(cfg, task, template, created => {
|
|
148
|
+
task = updateTaskRoom(task.task_id, created.room_id, created.room_identity_cid);
|
|
149
|
+
});
|
|
150
|
+
task = readTask(task.task_id);
|
|
151
|
+
if (task.state === 'active' && room.state === 'active')
|
|
152
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'work', id: task.task_id,
|
|
153
|
+
title: task.title, previousState: 'provisioning', newState: task.state,
|
|
154
|
+
revision: getRoomRecord(task.room_id)?.activated_at ?? task.started_at ?? task.created_at,
|
|
155
|
+
list: task.list_name ?? 'default', roomId: task.room_id,
|
|
156
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
157
|
+
agents: taskAgents(task) });
|
|
158
|
+
else
|
|
159
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: task.task_id,
|
|
160
|
+
state: task.state, category: 'provision_pending', eventId: roomFailureEventId(room) });
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
const failed = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
164
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task', id: task.task_id,
|
|
165
|
+
state: readTask(task.task_id).state, category: 'provision_failed',
|
|
166
|
+
eventId: failed ? roomFailureEventId(failed) : task.created_at });
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
84
169
|
}
|
|
85
170
|
return task;
|
|
86
171
|
}
|
|
@@ -259,8 +344,13 @@ export class TaskRoomApplicationService {
|
|
|
259
344
|
validateTemplates() {
|
|
260
345
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
261
346
|
return listTemplates(cfg.roomTemplates ?? {}).flatMap(template => {
|
|
262
|
-
const issues = template.members.flatMap(member =>
|
|
263
|
-
|
|
347
|
+
const issues = template.members.flatMap(member => {
|
|
348
|
+
if (!('ref' in member.agent))
|
|
349
|
+
return [];
|
|
350
|
+
const ref = member.agent.ref;
|
|
351
|
+
return cfg.roles.some(role => role.name === ref)
|
|
352
|
+
? [] : [`member ${member.slot}: Agent ref '${ref}' not found`];
|
|
353
|
+
});
|
|
264
354
|
return issues.length ? [{ template: `${template.name}@${template.version}`, issues }] : [];
|
|
265
355
|
});
|
|
266
356
|
}
|
|
@@ -461,8 +551,13 @@ export class TaskRoomApplicationService {
|
|
|
461
551
|
});
|
|
462
552
|
if (!task.template || task.template.name !== snapshot.name || task.template.content_hash !== snapshot.content_hash)
|
|
463
553
|
task = updateTaskTemplate(task.task_id, { name: snapshot.name, version: snapshot.version, content_hash: snapshot.content_hash });
|
|
464
|
-
if (task.state === 'backlog')
|
|
554
|
+
if (task.state === 'backlog') {
|
|
465
555
|
task = transitionTask(task.task_id);
|
|
556
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'start', id: task.task_id,
|
|
557
|
+
title: task.title, previousState: 'backlog', newState: task.state,
|
|
558
|
+
revision: task.started_at ?? task.created_at, list: task.list_name ?? 'default',
|
|
559
|
+
template: `${snapshot.name}@${snapshot.version}`, agents: [] });
|
|
560
|
+
}
|
|
466
561
|
if (!task.room_id) {
|
|
467
562
|
try {
|
|
468
563
|
await this.provisionRoom(cfg, task, snapshot, created => {
|
|
@@ -473,6 +568,9 @@ export class TaskRoomApplicationService {
|
|
|
473
568
|
catch (error) {
|
|
474
569
|
if (error instanceof CoworkUnavailableError)
|
|
475
570
|
persistBlockTask(task.task_id, 'Cowork management socket is unavailable');
|
|
571
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task',
|
|
572
|
+
id: task.task_id, state: readTask(task.task_id).state, category: 'provision_failed',
|
|
573
|
+
eventId: task.room_id ? roomFailureEventId(getRoomRecord(task.room_id)) : task.created_at });
|
|
476
574
|
throw error;
|
|
477
575
|
}
|
|
478
576
|
}
|
|
@@ -491,9 +589,23 @@ export class TaskRoomApplicationService {
|
|
|
491
589
|
catch (error) {
|
|
492
590
|
if (error instanceof CoworkUnavailableError)
|
|
493
591
|
persistBlockTask(task.task_id, 'Cowork management socket is unavailable');
|
|
592
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task',
|
|
593
|
+
id: task.task_id, state: readTask(task.task_id).state, category: 'provision_failed',
|
|
594
|
+
eventId: roomFailureEventId(getRoomRecord(task.room_id)) });
|
|
494
595
|
throw error;
|
|
495
596
|
}
|
|
496
597
|
}
|
|
598
|
+
const resultingRoom = getRoomRecord(task.room_id);
|
|
599
|
+
if (task.state === 'active' && resultingRoom?.state === 'active')
|
|
600
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'work', id: task.task_id,
|
|
601
|
+
title: task.title, previousState: 'provisioning', newState: task.state,
|
|
602
|
+
revision: getRoomRecord(task.room_id)?.activated_at ?? task.started_at ?? task.created_at,
|
|
603
|
+
list: task.list_name ?? 'default', roomId: task.room_id,
|
|
604
|
+
template: task.template ? `${task.template.name}@${task.template.version}` : undefined,
|
|
605
|
+
agents: taskAgents(task) });
|
|
606
|
+
else if (resultingRoom)
|
|
607
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Task',
|
|
608
|
+
id: task.task_id, state: task.state, category: 'provision_pending', eventId: roomFailureEventId(resultingRoom) });
|
|
497
609
|
return { task, status: 'ready' };
|
|
498
610
|
}
|
|
499
611
|
createTemplate(cfg, requested, noRoom) {
|
|
@@ -539,6 +651,11 @@ export class TaskRoomApplicationService {
|
|
|
539
651
|
room_id: created.room_id, room_name: task.title, room_identity_cid: created.identity_cid,
|
|
540
652
|
task_id: task.task_id, template_snapshot: template,
|
|
541
653
|
});
|
|
654
|
+
recordFleetAuditPresentation({ kind: 'room', operation: 'create', id: room.room_id,
|
|
655
|
+
name: room.room_name, previousState: 'none', newState: 'provisioning',
|
|
656
|
+
revision: room.created_at, taskId: room.task_id,
|
|
657
|
+
template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
|
|
658
|
+
participants: [] });
|
|
542
659
|
onCreated(room);
|
|
543
660
|
room = advanceSaga(room.room_id, 'create_room', 1);
|
|
544
661
|
if (attachOwner) {
|
|
@@ -553,18 +670,39 @@ export class TaskRoomApplicationService {
|
|
|
553
670
|
const mismatch = error instanceof CoworkProtocolError && /CID|expected/i.test(error.message);
|
|
554
671
|
setSagaError(room.room_id, error instanceof Error ? error.message : String(error), mismatch ? 'Verify rooms.owner.expected_cid and rotate the configured invite if necessary.'
|
|
555
672
|
: 'Rotate rooms.owner.public_invite or public_invite_file, then run room recover.', mismatch ? 'owner_cid_mismatch' : 'waiting_owner_invite');
|
|
673
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room',
|
|
674
|
+
id: room.room_id, state: getRoomRecord(room.room_id)?.state ?? 'provisioning',
|
|
675
|
+
category: 'provision_failed', eventId: roomFailureEventId(getRoomRecord(room.room_id) ?? room) });
|
|
556
676
|
throw error;
|
|
557
677
|
}
|
|
558
678
|
}
|
|
559
679
|
room = advanceSaga(room.room_id, 'create_members', 3);
|
|
560
680
|
if (template?.members.length)
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
681
|
+
try {
|
|
682
|
+
room = await (this.deps.provisionMembers ?? provisionMembers)({
|
|
683
|
+
cfg, cowork, roomId: room.room_id, taskId: task.task_id, template,
|
|
684
|
+
binPath: (this.deps.binPath ?? getBinPath)(), brief: task.brief,
|
|
685
|
+
goal: task.task_id ? task.title : task.goal,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
const failed = getRoomRecord(room.room_id) ?? room;
|
|
690
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: failed.room_id,
|
|
691
|
+
state: failed.state, category: 'provision_failed', eventId: roomFailureEventId(failed) });
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
else
|
|
695
|
+
room = activateRoom(room.room_id);
|
|
696
|
+
if (room.state === 'active')
|
|
697
|
+
recordFleetAuditPresentation({ kind: 'room', operation: 'activate', id: room.room_id,
|
|
698
|
+
name: room.room_name, previousState: 'provisioning', newState: 'active',
|
|
699
|
+
revision: room.activated_at ?? room.created_at, taskId: room.task_id,
|
|
700
|
+
template: room.template_snapshot ? `${room.template_snapshot.name}@${room.template_snapshot.version}` : undefined,
|
|
701
|
+
participants: roomParticipants(room) });
|
|
702
|
+
else
|
|
703
|
+
recordFleetAuditPresentation({ kind: 'lifecycle_failure', resource: 'Room', id: room.room_id,
|
|
704
|
+
state: room.state, category: 'provision_pending', eventId: roomFailureEventId(room) });
|
|
705
|
+
if (task.task_id && !template?.members.length)
|
|
568
706
|
activateTask(task.task_id);
|
|
569
707
|
return room;
|
|
570
708
|
}
|
package/dist/briefing.js
CHANGED
|
@@ -172,12 +172,16 @@ export function generateBriefing(role, v, opts) {
|
|
|
172
172
|
if (role.session === 'acp') {
|
|
173
173
|
L.push('', '### Managed fleet commands');
|
|
174
174
|
L.push('This ACP role has a supervisor-scoped ours-fleet proxy. Use the ordinary');
|
|
175
|
-
L.push('`ours-fleet
|
|
176
|
-
L.push('
|
|
177
|
-
L.push('
|
|
178
|
-
L.push('
|
|
175
|
+
L.push('`ours-fleet` command; the CLI routes public commands through your live supervisor.');
|
|
176
|
+
L.push('Your existing OS sandbox remains the executor and ordinary CLI validation still applies.');
|
|
177
|
+
L.push('Reads, help, validation failures, retries, and command invocations are silent in the');
|
|
178
|
+
L.push('Owner channel. Only confirmed Agent, Task, and Room lifecycle changes are announced.');
|
|
179
|
+
L.push('A minimal call is `ours-fleet spawn DeveloperName --temp`.');
|
|
180
|
+
L.push('For omitted settings, the supervisor inherits your canonical Brain and Role selections,');
|
|
179
181
|
L.push('working directory, neutral permissions, coordinator, and fleet monitor policy. Every');
|
|
180
|
-
L.push('explicit
|
|
182
|
+
L.push('explicit option wins; identity/session-local and secret material never inherit.');
|
|
183
|
+
L.push('All public CLI surfaces are available; hidden worker entry points remain internal.');
|
|
184
|
+
L.push('Lifecycle notice delivery failures stay in service diagnostics and never rerun effects.');
|
|
181
185
|
L.push('This proxy is attribution and convenience, not a security boundary for unisolated roles.');
|
|
182
186
|
}
|
|
183
187
|
if (role.coordinator) {
|
|
@@ -196,12 +200,12 @@ export function generateBriefing(role, v, opts) {
|
|
|
196
200
|
L.push('', '## Oversight assignments');
|
|
197
201
|
L.push('These agents are your wards — you keep them unstuck:');
|
|
198
202
|
for (const o of role.oversee)
|
|
199
|
-
L.push(`- **${o.
|
|
203
|
+
L.push(`- **${o.agent}** — check every ${o.interval}`);
|
|
200
204
|
L.push('');
|
|
201
205
|
L.push('Procedure (see also the oversee-agents skill if available). On each tick, for each ward');
|
|
202
206
|
L.push('run BOTH — they answer different questions:');
|
|
203
207
|
for (const o of role.oversee)
|
|
204
|
-
L.push(`\`ours-fleet status ${o.
|
|
208
|
+
L.push(`\`ours-fleet status ${o.agent}\` then \`ours-fleet peek ${o.agent}\``);
|
|
205
209
|
L.push('');
|
|
206
210
|
L.push('**One console command is not a liveness verdict.** A `peek` or `send` that fails tells');
|
|
207
211
|
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.10",
|
|
3
|
+
"buildId": "7fbfbd6e4119",
|
|
4
|
+
"commit": "006179e6e9ae9d9e846045a658a64686c8169ebe",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-08-
|
|
6
|
+
"builtAt": "2026-08-31T14:07:48.123Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"monitor.interrupt.after_tool"
|
|
9
9
|
]
|