@ours.network/fleet 1.1.0-nightly.7 → 1.1.0-nightly.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +23 -4
  2. package/dist/application/role-creation-service.js +1 -1
  3. package/dist/application/task-room-service.d.ts +1 -1
  4. package/dist/application/task-room-service.js +147 -14
  5. package/dist/briefing.js +6 -6
  6. package/dist/build-info.json +4 -4
  7. package/dist/cli.js +8 -11
  8. package/dist/config-yaml.d.ts +1 -1
  9. package/dist/config.d.ts +1 -1
  10. package/dist/config.js +49 -2
  11. package/dist/docs.d.ts +1 -1
  12. package/dist/docs.js +39 -18
  13. package/dist/fleet-command-audit.d.ts +37 -8
  14. package/dist/fleet-command-audit.js +120 -62
  15. package/dist/init-guidance.d.ts +1 -1
  16. package/dist/init-guidance.js +4 -3
  17. package/dist/owner-channel/channel.d.ts +2 -2
  18. package/dist/owner-channel/channel.js +31 -38
  19. package/dist/owner-channel/commands.js +3 -9
  20. package/dist/owner-channel/state.d.ts +2 -0
  21. package/dist/owner-channel/state.js +8 -2
  22. package/dist/preset-bootstrap.d.ts +10 -0
  23. package/dist/preset-bootstrap.js +85 -0
  24. package/dist/rooms-tasks/cli.js +121 -42
  25. package/dist/rooms-tasks/config.js +0 -4
  26. package/dist/rooms-tasks/provision.js +28 -1
  27. package/dist/rooms-tasks/templates.d.ts +0 -1
  28. package/dist/rooms-tasks/templates.js +9 -76
  29. package/dist/rooms-tasks/types.d.ts +2 -1
  30. package/dist/runner.js +13 -1
  31. package/dist/session/control.d.ts +1 -1
  32. package/dist/session/control.js +1 -1
  33. package/dist/web/fleet-config-service.js +4 -3
  34. package/package.json +2 -1
  35. package/presets/fleet/agents/Agent.yaml +3 -0
  36. package/presets/fleet/agents/Architect.yaml +3 -0
  37. package/presets/fleet/agents/Critic.yaml +3 -0
  38. package/presets/fleet/agents/Developer.yaml +3 -0
  39. package/presets/fleet/agents/Secretary.yaml +3 -0
  40. package/presets/fleet/agents/Tester.yaml +3 -0
  41. package/presets/fleet/brains/claude-default.yaml +5 -0
  42. package/presets/fleet/roles/Agent.yaml +6 -0
  43. package/presets/fleet/roles/Architect.yaml +6 -0
  44. package/presets/fleet/roles/Critic.yaml +6 -0
  45. package/presets/fleet/roles/Developer.yaml +6 -0
  46. package/presets/fleet/roles/Secretary.yaml +6 -0
  47. package/presets/fleet/roles/Tester.yaml +6 -0
  48. package/presets/fleet/room_templates/pair.yaml +11 -0
  49. package/presets/fleet/room_templates/single.yaml +8 -0
  50. package/presets/fleet/room_templates/team.yaml +12 -0
  51. package/presets/fleet.yaml +7 -0
  52. package/presets/manifest.json +5 -0
package/README.md CHANGED
@@ -43,7 +43,7 @@ brain: { inline: { harness: codex, session: acp } }
43
43
  ## How it works
44
44
 
45
45
  ```
46
- ~/fleet.yaml + ~/fleet/{agents,roles,brains}/*.yaml your declaration
46
+ ~/fleet.yaml + ~/fleet/{agents,roles,brains,room_templates}/*.yaml your declaration
47
47
  │ ours-fleet up
48
48
  ▼
49
49
  briefing.md per role ──► agent session adapter ──► ACP client/session ──► ACP agent
@@ -91,7 +91,7 @@ equivalent); logs land in `~/.ours-fleet/logs/`.
91
91
 
92
92
  ```sh
93
93
  npm i -g @ours.network/fleet
94
- ours-fleet init # units/dirs/linger for this user
94
+ ours-fleet init # units/dirs/linger + missing standard presets
95
95
  ours-fleet doctor # verifies everything above, with actionable messages
96
96
  ```
97
97
 
@@ -107,8 +107,8 @@ become that account and repeat.
107
107
  ## Quickstart
108
108
 
109
109
  ```sh
110
- cp -R "$(npm root -g)/@ours.network/fleet/examples/fleet" ~/fleet
111
- cp "$(npm root -g)/@ours.network/fleet/examples/fleet.yaml" ~/fleet.yaml
110
+ ours-fleet init # safe to repeat: creates missing files, never replaces edits
111
+ ours-fleet config # validates Agents, Roles, Brains, and Room templates
112
112
  $EDITOR ~/fleet/agents/*.yaml # compose Role + Brain and operational settings
113
113
  ours-fleet up # boot the fleet (staggered)
114
114
  ours-fleet ls # running consoles
@@ -442,6 +442,25 @@ installer/setup flows remain responsible for starting it.
442
442
 
443
443
  ### Rooms and tasks
444
444
 
445
+ Init installs editable `single`, `pair`, and `team` definitions under
446
+ `~/fleet/room_templates/`, plus every exact-cased Agent, Role, and Brain they
447
+ reference. Inspect them with `ours-fleet template list` and
448
+ `ours-fleet template show team`. After configuring the authenticated owner below:
449
+
450
+ ```sh
451
+ ours-fleet task create --title "Solo task" --template single
452
+ ours-fleet task create --title "Reviewed change" --template pair
453
+ ours-fleet task create --title "Phased delivery" --template team
454
+ ```
455
+
456
+ `ours-fleet init -c /path/custom.yaml` seeds `/path/custom/` instead. Init reports
457
+ the packaged preset revision and source directory and only seeds missing files.
458
+ It never upgrades an edited preset. To adopt a newer packaged file explicitly,
459
+ copy the reported source file beside the existing target as `.new-default`, review
460
+ `diff -u`, then replace the target yourself. This is the sole adoption operation;
461
+ rerunning init is not an update. Users upgrading from hardcoded templates should
462
+ run init once (with the same `-c` selection they normally use).
463
+
445
464
  Rooms always use `ours-cowork`; there is no room-provider selector. Configure
446
465
  the cowork daemon connection and the room owner directly:
447
466
 
@@ -68,7 +68,7 @@ export class RoleCreationService {
68
68
  return `unresolved (${provenance})`;
69
69
  if ('ref' in value)
70
70
  return `ref:${value.ref} (${provenance})`;
71
- const fingerprint = createHash('sha256').update(JSON.stringify(value.inline)).digest('hex').slice(0, 16);
71
+ const fingerprint = hash(value.inline).slice(0, 16);
72
72
  return `inline:sha256:${fingerprint} (${provenance})`;
73
73
  };
74
74
  return {
@@ -179,7 +179,7 @@ export declare class TaskRoomApplicationService {
179
179
  name: string;
180
180
  version: number;
181
181
  description: string;
182
- builtin?: boolean;
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, room => {
49
- task = updateTaskRoom(task.task_id, room.room_id, room.room_identity_cid);
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
- await this.provisionRoom(cfg, task, template, room => {
81
- task = updateTaskRoom(task.task_id, room.room_id, room.room_identity_cid);
82
- });
83
- task = readTask(task.task_id);
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
  }
@@ -466,8 +551,13 @@ export class TaskRoomApplicationService {
466
551
  });
467
552
  if (!task.template || task.template.name !== snapshot.name || task.template.content_hash !== snapshot.content_hash)
468
553
  task = updateTaskTemplate(task.task_id, { name: snapshot.name, version: snapshot.version, content_hash: snapshot.content_hash });
469
- if (task.state === 'backlog')
554
+ if (task.state === 'backlog') {
470
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
+ }
471
561
  if (!task.room_id) {
472
562
  try {
473
563
  await this.provisionRoom(cfg, task, snapshot, created => {
@@ -478,6 +568,9 @@ export class TaskRoomApplicationService {
478
568
  catch (error) {
479
569
  if (error instanceof CoworkUnavailableError)
480
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 });
481
574
  throw error;
482
575
  }
483
576
  }
@@ -496,9 +589,23 @@ export class TaskRoomApplicationService {
496
589
  catch (error) {
497
590
  if (error instanceof CoworkUnavailableError)
498
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)) });
499
595
  throw error;
500
596
  }
501
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) });
502
609
  return { task, status: 'ready' };
503
610
  }
504
611
  createTemplate(cfg, requested, noRoom) {
@@ -544,6 +651,11 @@ export class TaskRoomApplicationService {
544
651
  room_id: created.room_id, room_name: task.title, room_identity_cid: created.identity_cid,
545
652
  task_id: task.task_id, template_snapshot: template,
546
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: [] });
547
659
  onCreated(room);
548
660
  room = advanceSaga(room.room_id, 'create_room', 1);
549
661
  if (attachOwner) {
@@ -558,18 +670,39 @@ export class TaskRoomApplicationService {
558
670
  const mismatch = error instanceof CoworkProtocolError && /CID|expected/i.test(error.message);
559
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.'
560
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) });
561
676
  throw error;
562
677
  }
563
678
  }
564
679
  room = advanceSaga(room.room_id, 'create_members', 3);
565
680
  if (template?.members.length)
566
- return (this.deps.provisionMembers ?? provisionMembers)({
567
- cfg, cowork, roomId: room.room_id, taskId: task.task_id, template,
568
- binPath: (this.deps.binPath ?? getBinPath)(), brief: task.brief,
569
- goal: task.task_id ? task.title : task.goal,
570
- });
571
- room = activateRoom(room.room_id);
572
- if (task.task_id)
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)
573
706
  activateTask(task.task_id);
574
707
  return room;
575
708
  }
package/dist/briefing.js CHANGED
@@ -172,16 +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` command; the CLI opens an authenticated audit attempt with your live');
176
- L.push('supervisor before parsing or side effects. Your existing OS sandbox remains the executor.');
177
- L.push('Every allowed attempt writes a redacted raw invocation and correlated outcome to your');
178
- L.push('Owner-visible channel; denied, invalid, failed, and read-only attempts are visible too.');
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
179
  L.push('A minimal call is `ours-fleet spawn DeveloperName --temp`.');
180
180
  L.push('For omitted settings, the supervisor inherits your canonical Brain and Role selections,');
181
181
  L.push('working directory, neutral permissions, coordinator, and fleet monitor policy. Every');
182
182
  L.push('explicit option wins; identity/session-local and secret material never inherit.');
183
- L.push('Operator lifecycle/control and hidden worker commands are denied through this route.');
184
- L.push('Invocation delivery failure denies execution; outcome uncertainty is reported without retrying effects.');
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.');
185
185
  L.push('This proxy is attribution and convenience, not a security boundary for unisolated roles.');
186
186
  }
187
187
  if (role.coordinator) {
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "1.1.0-nightly.7",
3
- "buildId": "a09aa98e92aa",
4
- "commit": "d79160ae66847305a109bed339d8c8830a20bba7",
2
+ "version": "1.1.0-nightly.9",
3
+ "buildId": "949ece807b97",
4
+ "commit": "c88941e2850b865432a850db957c806acf10406c",
5
5
  "dirty": true,
6
- "builtAt": "2026-08-31T07:32:26.852Z",
6
+ "builtAt": "2026-08-31T09:53:12.340Z",
7
7
  "capabilities": [
8
8
  "monitor.interrupt.after_tool"
9
9
  ]
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import { createInterface } from 'node:readline';
8
8
  import { Command } from 'commander';
9
9
  import { VERSION } from './version.js';
10
10
  import { INIT_COMPLETION_GUIDANCE } from './init-guidance.js';
11
+ import { bootstrapPresets } from './preset-bootstrap.js';
11
12
  import { analyzeInstalls, buildInfo, buildLabel, discoverInstalls, runningLabel, } from './provenance.js';
12
13
  import { agentDir, agentsRoot, tmpRoot, logsRoot, deriveXdgRuntimeDir } from './paths.js';
13
14
  import { findRole, loadConfig, ROLE_NAME_RE } from './config.js';
@@ -38,7 +39,7 @@ import { requestWebControl } from './web/control.js';
38
39
  import { WebServiceManager } from './web/service.js';
39
40
  import { WebAccessStore, passwordAccess, validatePublicOrigin } from './web/access.js';
40
41
  import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-proxy.js';
41
- import { beginFleetAuditCollection, consumeFleetAuditCollection, FleetCliExit, recordFleetAuditPresentation, recordFleetAuditResource, } from './fleet-command-audit.js';
42
+ import { beginFleetAuditCollection, consumeFleetAuditCollection, FleetCliExit, } from './fleet-command-audit.js';
42
43
  import './harness/claude-code.js'; // registers the claude-code adapter
43
44
  import './harness/codex.js'; // registers the codex adapter
44
45
  import { registerTemplateCommands, registerTaskCommands, registerRoomCommands } from './rooms-tasks/cli.js';
@@ -1198,13 +1199,6 @@ cOpt(program.command('spawn [name]').description('spawn a new agent (permanent b
1198
1199
  if (!response.ok)
1199
1200
  throw new SessionControlError(response.kind ?? 'backend', response.error ?? 'managed spawn failed');
1200
1201
  const result = response.result;
1201
- recordFleetAuditResource('agent', result.role);
1202
- recordFleetAuditPresentation({ kind: 'agent_started', name: result.role,
1203
- lifetime: result.lifetime, brain: result.brainSummary, role: result.roleSummary,
1204
- harness: result.harness, session: result.session, model: result.model,
1205
- permissions: result.permissionMode
1206
- ? `${result.permissionMode.fleetMode}/${result.permissionMode.nativeMode}` : undefined,
1207
- parent: result.caller, actionId: result.creationActionId, inherited: result.inherited });
1208
1202
  const expectedCaller = process.env[FLEET_PROXY_CALLER_ENV];
1209
1203
  if (expectedCaller && result.caller !== expectedCaller)
1210
1204
  throw new Error(`fleet proxy caller mismatch: expected '${expectedCaller}', got '${result.caller}'`);
@@ -1264,12 +1258,15 @@ cOpt(program.command('doctor').description('prerequisite report'))
1264
1258
  if (!rep.ok)
1265
1259
  throw new FleetCliExit(1);
1266
1260
  });
1267
- program.command('init').description('one-time host setup (units, dirs, linger)')
1268
- .action(async () => {
1261
+ cOpt(program.command('init').description('idempotent host setup plus missing packaged presets'))
1262
+ .action(async (opts) => {
1269
1263
  for (const d of [agentsRoot(), tmpRoot(), logsRoot()])
1270
1264
  mkdirSync(d, { recursive: true });
1271
1265
  for (const m of await pickBackend().init(binPath))
1272
1266
  console.log(m);
1267
+ const seeded = bootstrapPresets(opts.configuration);
1268
+ console.log(`Packaged preset revision ${seeded.revision}: ${seeded.sourceRoot}`);
1269
+ console.log(`Created ${seeded.created.length}; preserved ${seeded.preserved.length} existing file(s).`);
1273
1270
  console.log(INIT_COMPLETION_GUIDANCE);
1274
1271
  });
1275
1272
  const webCommand = cOpt(program.command('web').description('start or open the secure localhost fleet web console'))
@@ -1581,7 +1578,7 @@ async function runFleetCli() {
1581
1578
  const finished = await controlRequest(stateDir, { command: 'fleet_audit_finish', audit: {
1582
1579
  correlationId: attempt.correlationId, class: outcomeClass, exitCode, effect,
1583
1580
  ...(metadata.resourceIds ? { resourceIds: metadata.resourceIds } : {}),
1584
- ...(metadata.presentation ? { presentation: metadata.presentation } : {}),
1581
+ ...(metadata.presentations ? { presentations: metadata.presentations } : {}),
1585
1582
  } });
1586
1583
  if (!finished.ok)
1587
1584
  throw new SessionControlError(finished.kind ?? 'backend', finished.error ?? `audit delivery failed after execution; effect status=${effect}`);
@@ -1,5 +1,5 @@
1
1
  export type YamlMode = 'compat' | 'strict';
2
- export type ConfigDiagnosticKind = 'anchor' | 'alias' | 'explicit-tag' | 'non-scalar-key' | 'multiple-documents';
2
+ export type ConfigDiagnosticKind = 'anchor' | 'alias' | 'explicit-tag' | 'non-scalar-key' | 'multiple-documents' | 'deprecated-field';
3
3
  export interface ConfigDiagnostic {
4
4
  severity: 'warning';
5
5
  kind: ConfigDiagnosticKind;
package/dist/config.d.ts CHANGED
@@ -220,7 +220,7 @@ export interface FleetConfig {
220
220
  files: string[];
221
221
  configMode?: 'split-v2';
222
222
  sourceDocuments?: Array<{
223
- kind: 'Manifest' | 'Agent' | 'Role' | 'Brain';
223
+ kind: 'Manifest' | 'Agent' | 'Role' | 'Brain' | 'RoomTemplate';
224
224
  id?: string;
225
225
  path: string;
226
226
  }>;
package/dist/config.js CHANGED
@@ -265,6 +265,35 @@ function readKindDirectory(root, kind, required, yamlMode, diagnostics, files) {
265
265
  }
266
266
  return result;
267
267
  }
268
+ function readRoomTemplateDirectory(root, yamlMode, diagnostics, files) {
269
+ if (!existsSync(root))
270
+ return {};
271
+ assertTrustedPath(root, 'directory');
272
+ const result = {};
273
+ const sources = new Map();
274
+ const names = readdirSync(root)
275
+ .filter(name => ['.yaml', '.yml'].includes(extname(name).toLowerCase())).sort();
276
+ for (const filename of names) {
277
+ const file = join(root, filename);
278
+ assertTrustedPath(file, 'file');
279
+ const id = basename(filename, extname(filename));
280
+ const previous = sources.get(id);
281
+ if (previous)
282
+ throw new ConfigError(`E_DUPLICATE_ID: room template '${id}' defined by both ${previous} and ${file}`);
283
+ const parsed = parseFleetDocument(file, readFileSync(file, 'utf8'), yamlMode);
284
+ diagnostics.push(...parsed.diagnostics);
285
+ if (parsed.value.override_builtin === true)
286
+ diagnostics.push({
287
+ severity: 'warning', kind: 'deprecated-field', file, line: 1, column: 1,
288
+ message: `${file}: override_builtin is deprecated and ignored for a file-backed Room template`,
289
+ });
290
+ files.push(file);
291
+ const validated = validateRoomTemplatesConfig({ [id]: parsed.value }, file)[id];
292
+ result[id] = { ...validated, sourceFile: file };
293
+ sources.set(id, file);
294
+ }
295
+ return result;
296
+ }
268
297
  function selection(raw, kind, agentFile, presets, allowed) {
269
298
  if (!isPlainObject(raw))
270
299
  schemaError(agentFile, `/${kind}`, 'must be { ref } or { inline }');
@@ -586,6 +615,9 @@ export function loadConfig(configPath, options = {}) {
586
615
  let ownerInvite;
587
616
  let roomTemplates;
588
617
  let tasks;
618
+ const fileTemplates = readRoomTemplateDirectory(join(splitRootFor(base), 'room_templates'), options.yamlMode ?? 'compat', diagnostics, files);
619
+ if (Object.keys(fileTemplates).length)
620
+ roomTemplates = fileTemplates;
589
621
  for (const { file, doc } of docs) {
590
622
  if (doc.rooms !== undefined) {
591
623
  if (rooms)
@@ -598,7 +630,21 @@ export function loadConfig(configPath, options = {}) {
598
630
  }
599
631
  if (doc.room_templates !== undefined) {
600
632
  const validated = validateRoomTemplatesConfig(deepSub(doc.room_templates, vars), file);
601
- roomTemplates = { ...(roomTemplates ?? {}), ...validated };
633
+ const raw = doc.room_templates;
634
+ for (const [name, template] of Object.entries(validated)) {
635
+ const shadowed = roomTemplates?.[name];
636
+ const marker = raw[name]?.override_builtin;
637
+ if (shadowed && (marker !== true || template.version <= shadowed.version))
638
+ throw new ConfigError(`${file}: room_templates.${name} shadows file preset ${shadowed.sourceFile}; `
639
+ + 'set override_builtin: true and use a higher version');
640
+ if (marker === true)
641
+ diagnostics.push({
642
+ severity: 'warning', kind: 'deprecated-field', file, line: 1, column: 1,
643
+ message: `${file}: room_templates.${name}.override_builtin is deprecated; `
644
+ + 'it is retained only as an explicit manifest-over-file migration marker',
645
+ });
646
+ roomTemplates = { ...(roomTemplates ?? {}), [name]: { ...template, sourceFile: file } };
647
+ }
602
648
  }
603
649
  if (doc.tasks !== undefined) {
604
650
  if (tasks)
@@ -616,7 +662,8 @@ export function loadConfig(configPath, options = {}) {
616
662
  return { kind: 'Manifest', path };
617
663
  const parent = basename(dirname(path));
618
664
  const kind = parent === 'agents' ? 'Agent'
619
- : parent === 'roles' ? 'Role' : 'Brain';
665
+ : parent === 'roles' ? 'Role'
666
+ : parent === 'room_templates' ? 'RoomTemplate' : 'Brain';
620
667
  return { kind, id: basename(path, extname(path)), path };
621
668
  }),
622
669
  };