@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.
Files changed (122) hide show
  1. package/README.md +97 -95
  2. package/dist/agent-recovery-gate.d.ts +17 -0
  3. package/dist/agent-recovery-gate.js +135 -0
  4. package/dist/application/fleet-query-service.js +13 -1
  5. package/dist/application/role-creation-service.d.ts +2 -21
  6. package/dist/application/role-creation-service.js +26 -64
  7. package/dist/application/role-removal-service.js +6 -3
  8. package/dist/application/task-room-service.d.ts +1 -1
  9. package/dist/application/task-room-service.js +154 -16
  10. package/dist/application/types.d.ts +1 -1
  11. package/dist/briefing.js +11 -7
  12. package/dist/build-info.json +4 -4
  13. package/dist/cli.js +136 -42
  14. package/dist/config-yaml.d.ts +1 -1
  15. package/dist/config.d.ts +65 -2
  16. package/dist/config.js +385 -26
  17. package/dist/creation.d.ts +1 -1
  18. package/dist/creation.js +2 -2
  19. package/dist/daemon-recovery.d.ts +93 -0
  20. package/dist/daemon-recovery.js +328 -0
  21. package/dist/docs.d.ts +1 -1
  22. package/dist/docs.js +84 -102
  23. package/dist/doctor.js +18 -4
  24. package/dist/fleet-command-audit.d.ts +160 -0
  25. package/dist/fleet-command-audit.js +433 -0
  26. package/dist/fleet-proxy.d.ts +2 -0
  27. package/dist/fleet-proxy.js +30 -8
  28. package/dist/generated-agent-source.d.ts +9 -0
  29. package/dist/generated-agent-source.js +53 -0
  30. package/dist/harness/agent-session.d.ts +18 -0
  31. package/dist/harness/claude-code-session.d.ts +6 -0
  32. package/dist/harness/claude-code-session.js +8 -0
  33. package/dist/harness/claude-code.js +12 -0
  34. package/dist/harness/codex-session.d.ts +6 -0
  35. package/dist/harness/codex-session.js +8 -0
  36. package/dist/harness/codex.js +20 -0
  37. package/dist/harness/registry.js +4 -0
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/init-guidance.d.ts +1 -0
  41. package/dist/init-guidance.js +6 -0
  42. package/dist/model-env.d.ts +2 -3
  43. package/dist/model-env.js +3 -5
  44. package/dist/ops.js +4 -2
  45. package/dist/owner-channel/channel.d.ts +56 -0
  46. package/dist/owner-channel/channel.js +269 -29
  47. package/dist/owner-channel/commands.d.ts +2 -0
  48. package/dist/owner-channel/commands.js +31 -13
  49. package/dist/owner-channel/state.d.ts +2 -0
  50. package/dist/owner-channel/state.js +8 -2
  51. package/dist/preset-bootstrap.d.ts +10 -0
  52. package/dist/preset-bootstrap.js +85 -0
  53. package/dist/reports/artifact.d.ts +10 -0
  54. package/dist/reports/artifact.js +32 -0
  55. package/dist/reports/index.d.ts +5 -0
  56. package/dist/reports/index.js +5 -0
  57. package/dist/reports/render.d.ts +6 -0
  58. package/dist/reports/render.js +240 -0
  59. package/dist/reports/service.d.ts +42 -0
  60. package/dist/reports/service.js +57 -0
  61. package/dist/reports/tasks.d.ts +52 -0
  62. package/dist/reports/tasks.js +177 -0
  63. package/dist/reports/types.d.ts +315 -0
  64. package/dist/reports/types.js +6 -0
  65. package/dist/resolved-plan.d.ts +3 -2
  66. package/dist/resolved-plan.js +21 -5
  67. package/dist/rooms-tasks/cli.js +225 -40
  68. package/dist/rooms-tasks/config.js +24 -13
  69. package/dist/rooms-tasks/external-worker.d.ts +1 -0
  70. package/dist/rooms-tasks/external-worker.js +14 -9
  71. package/dist/rooms-tasks/provision.js +98 -36
  72. package/dist/rooms-tasks/templates.d.ts +0 -1
  73. package/dist/rooms-tasks/templates.js +9 -76
  74. package/dist/rooms-tasks/types.d.ts +10 -16
  75. package/dist/rooms-tasks/types.js +1 -4
  76. package/dist/runner.d.ts +9 -2
  77. package/dist/runner.js +84 -20
  78. package/dist/sensitive-config.d.ts +2 -0
  79. package/dist/sensitive-config.js +4 -0
  80. package/dist/session/acp.d.ts +5 -0
  81. package/dist/session/acp.js +27 -3
  82. package/dist/session/control.d.ts +14 -1
  83. package/dist/session/control.js +35 -0
  84. package/dist/spawn.d.ts +8 -33
  85. package/dist/spawn.js +52 -211
  86. package/dist/temp-lifecycle.d.ts +1 -1
  87. package/dist/watchdog/config.d.ts +10 -7
  88. package/dist/watchdog/config.js +29 -19
  89. package/dist/watchdog/run.js +4 -13
  90. package/dist/web/fleet-config-service.d.ts +11 -8
  91. package/dist/web/fleet-config-service.js +384 -183
  92. package/dist/web/topology-promote.js +25 -15
  93. package/dist/web/topology.js +2 -2
  94. package/dist/web/yaml-document-edit.js +2 -0
  95. package/dist/web-app/assets/index-BbTc5KtZ.js +10 -0
  96. package/dist/web-app/index.html +1 -1
  97. package/examples/fleet/agents/Alice.yaml +11 -0
  98. package/examples/fleet/agents/FleetCoordinator.yaml +7 -0
  99. package/examples/fleet/brains/claude-default.yaml +5 -0
  100. package/examples/fleet/roles/coordinator.yaml +5 -0
  101. package/examples/fleet/roles/developer.yaml +3 -0
  102. package/examples/fleet.yaml +74 -0
  103. package/package.json +3 -1
  104. package/presets/fleet/agents/Agent.yaml +3 -0
  105. package/presets/fleet/agents/Architect.yaml +3 -0
  106. package/presets/fleet/agents/Critic.yaml +3 -0
  107. package/presets/fleet/agents/Developer.yaml +3 -0
  108. package/presets/fleet/agents/Secretary.yaml +3 -0
  109. package/presets/fleet/agents/Tester.yaml +3 -0
  110. package/presets/fleet/brains/claude-default.yaml +5 -0
  111. package/presets/fleet/roles/Agent.yaml +6 -0
  112. package/presets/fleet/roles/Architect.yaml +6 -0
  113. package/presets/fleet/roles/Critic.yaml +6 -0
  114. package/presets/fleet/roles/Developer.yaml +6 -0
  115. package/presets/fleet/roles/Secretary.yaml +6 -0
  116. package/presets/fleet/roles/Tester.yaml +6 -0
  117. package/presets/fleet/room_templates/pair.yaml +11 -0
  118. package/presets/fleet/room_templates/single.yaml +8 -0
  119. package/presets/fleet/room_templates/team.yaml +12 -0
  120. package/presets/fleet.yaml +7 -0
  121. package/presets/manifest.json +5 -0
  122. 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, resolveRoleModel, resolvePermissions, ROLE_NAME_RE, validateMonitorConfig, } from '../config.js';
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 { buildRoleConfig, spawnDryRun, spawnPermanent, spawnTemp, validateSpawnOpts, } from '../spawn.js';
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
- buildRoleConfig(opts, defaults.harness);
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: request.harness ?? defaults.harness ?? 'claude-code',
152
- session: request.session ?? defaults.session ?? 'acp',
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
- harness: 'request', session: 'request', identity: 'built-in',
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', 'harness', 'model', 'reasoningEffort', 'session', 'cwd', 'lifetime', 'mission',
300
- 'coordinator', 'permissions', 'bio', 'persona', 'monitor', 'openAfterCreate',
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 (!['codex', 'claude-code'].includes(input.harness))
311
- throw new FleetError('invalid_request', 'unsupported harness');
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, model: input.model === null ? null : input.model?.trim() || undefined,
334
- reasoningEffort: input.reasoningEffort === null ? null : input.reasoningEffort?.trim() || undefined,
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, harness: request.harness,
364
- model: request.model, session: request.session, cwd: request.cwd,
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, bio: request.bio, persona: request.persona,
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
- if (role?.sourceFile.includes('/fleet.d/'))
49
- effects.push(`Remove spawned configuration ${role.sourceFile}.`);
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?.sourceFile.includes('/fleet.d/') && existsSync(role.sourceFile))
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
- 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
  }
@@ -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 => cfg.roles.some(role => role.name === member.role_ref)
263
- ? [] : [`member ${member.slot}: role_ref '${member.role_ref}' not found in fleet roles`]);
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
- return (this.deps.provisionMembers ?? provisionMembers)({
562
- cfg, cowork, roomId: room.room_id, taskId: task.task_id, template,
563
- binPath: (this.deps.binPath ?? getBinPath)(), brief: task.brief,
564
- goal: task.task_id ? task.title : task.goal,
565
- });
566
- room = activateRoom(room.room_id);
567
- 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)
568
706
  activateTask(task.task_id);
569
707
  return room;
570
708
  }
@@ -18,7 +18,7 @@ export interface ResolvedRoleView {
18
18
  sandbox?: string;
19
19
  };
20
20
  oversee?: Array<{
21
- role: string;
21
+ agent: string;
22
22
  interval: string;
23
23
  }>;
24
24
  }
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 spawn` command; the CLI routes it through your live supervisor, which');
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 --role DeveloperName --temp`.');
178
- L.push('For omitted execution settings, the supervisor inherits your harness, session, model,');
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 spawn option wins. An explicit different harness does not inherit your model.');
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.role}** — check every ${o.interval}`);
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.role}\` then \`ours-fleet peek ${o.role}\``);
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.');
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "1.1.0-nightly.1",
3
- "buildId": "bf3c406ba57c",
4
- "commit": "10d6da8bfd9903d216bce80a9ef89a42c282f8d9",
2
+ "version": "1.1.0-nightly.10",
3
+ "buildId": "7fbfbd6e4119",
4
+ "commit": "006179e6e9ae9d9e846045a658a64686c8169ebe",
5
5
  "dirty": true,
6
- "builtAt": "2026-08-30T07:44:27.551Z",
6
+ "builtAt": "2026-08-31T14:07:48.123Z",
7
7
  "capabilities": [
8
8
  "monitor.interrupt.after_tool"
9
9
  ]