@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.21

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 (202) hide show
  1. package/README.md +204 -55
  2. package/dist/agent-recovery-gate.d.ts +17 -0
  3. package/dist/agent-recovery-gate.js +135 -0
  4. package/dist/application/capabilities.js +18 -6
  5. package/dist/application/fleet-query-service.js +20 -5
  6. package/dist/application/role-command-service.js +9 -0
  7. package/dist/application/role-creation-service.d.ts +6 -25
  8. package/dist/application/role-creation-service.js +34 -72
  9. package/dist/application/role-repository.d.ts +4 -2
  10. package/dist/application/role-repository.js +6 -3
  11. package/dist/application/session-control.d.ts +3 -1
  12. package/dist/application/session-control.js +4 -2
  13. package/dist/application/task-room-service.d.ts +58 -6
  14. package/dist/application/task-room-service.js +435 -79
  15. package/dist/briefing.js +74 -24
  16. package/dist/build-info.json +4 -4
  17. package/dist/canonical-json.d.ts +2 -0
  18. package/dist/canonical-json.js +10 -0
  19. package/dist/cli.js +241 -76
  20. package/dist/config-yaml.d.ts +1 -1
  21. package/dist/config.d.ts +75 -3
  22. package/dist/config.js +274 -34
  23. package/dist/creation.d.ts +1 -1
  24. package/dist/creation.js +3 -2
  25. package/dist/daemon-recovery.d.ts +93 -0
  26. package/dist/daemon-recovery.js +328 -0
  27. package/dist/docs.d.ts +1 -1
  28. package/dist/docs.js +192 -66
  29. package/dist/doctor.d.ts +2 -0
  30. package/dist/doctor.js +54 -4
  31. package/dist/fleet-command-audit.d.ts +165 -0
  32. package/dist/fleet-command-audit.js +555 -0
  33. package/dist/fleet-proxy.d.ts +7 -2
  34. package/dist/fleet-proxy.js +30 -8
  35. package/dist/harness/agent-session.d.ts +5 -0
  36. package/dist/harness/claude-code-session.d.ts +2 -0
  37. package/dist/harness/claude-code-session.js +6 -1
  38. package/dist/harness/claude-code.js +2 -0
  39. package/dist/harness/codex-app-server-proxy.d.ts +40 -0
  40. package/dist/harness/codex-app-server-proxy.js +348 -14
  41. package/dist/harness/codex-session.d.ts +11 -2
  42. package/dist/harness/codex-session.js +22 -2
  43. package/dist/harness/codex.d.ts +5 -1
  44. package/dist/harness/codex.js +110 -10
  45. package/dist/harness/types.d.ts +1 -1
  46. package/dist/index.d.ts +5 -4
  47. package/dist/index.js +4 -3
  48. package/dist/init-guidance.d.ts +1 -1
  49. package/dist/init-guidance.js +4 -3
  50. package/dist/init-wizard.d.ts +98 -0
  51. package/dist/init-wizard.js +629 -0
  52. package/dist/lifecycle-summary.d.ts +134 -0
  53. package/dist/lifecycle-summary.js +179 -0
  54. package/dist/loops/config.d.ts +10 -1
  55. package/dist/loops/config.js +38 -5
  56. package/dist/model-env.d.ts +2 -2
  57. package/dist/model-env.js +1 -1
  58. package/dist/model-recovery.d.ts +1 -1
  59. package/dist/ops.js +2 -17
  60. package/dist/owner-channel/channel.d.ts +58 -0
  61. package/dist/owner-channel/channel.js +321 -42
  62. package/dist/owner-channel/commands.d.ts +6 -1
  63. package/dist/owner-channel/commands.js +34 -19
  64. package/dist/owner-channel/state.d.ts +2 -0
  65. package/dist/owner-channel/state.js +8 -2
  66. package/dist/permissions.js +1 -1
  67. package/dist/preset-bootstrap.d.ts +10 -0
  68. package/dist/preset-bootstrap.js +85 -0
  69. package/dist/preset-migration.d.ts +42 -0
  70. package/dist/preset-migration.js +414 -0
  71. package/dist/reports/artifact.d.ts +10 -0
  72. package/dist/reports/artifact.js +32 -0
  73. package/dist/reports/index.d.ts +5 -0
  74. package/dist/reports/index.js +5 -0
  75. package/dist/reports/render.d.ts +6 -0
  76. package/dist/reports/render.js +240 -0
  77. package/dist/reports/service.d.ts +42 -0
  78. package/dist/reports/service.js +57 -0
  79. package/dist/reports/tasks.d.ts +52 -0
  80. package/dist/reports/tasks.js +177 -0
  81. package/dist/reports/types.d.ts +315 -0
  82. package/dist/reports/types.js +6 -0
  83. package/dist/resolved-plan.js +13 -1
  84. package/dist/rooms-tasks/cli.d.ts +2 -0
  85. package/dist/rooms-tasks/cli.js +514 -75
  86. package/dist/rooms-tasks/close.d.ts +9 -1
  87. package/dist/rooms-tasks/close.js +20 -4
  88. package/dist/rooms-tasks/config.js +16 -14
  89. package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
  90. package/dist/rooms-tasks/cowork-adapter.js +6 -0
  91. package/dist/rooms-tasks/deletion.d.ts +51 -0
  92. package/dist/rooms-tasks/deletion.js +217 -0
  93. package/dist/rooms-tasks/external-worker.d.ts +1 -0
  94. package/dist/rooms-tasks/external-worker.js +14 -9
  95. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  96. package/dist/rooms-tasks/launch-snapshot.js +181 -0
  97. package/dist/rooms-tasks/member-overrides.d.ts +30 -0
  98. package/dist/rooms-tasks/member-overrides.js +204 -0
  99. package/dist/rooms-tasks/provision.js +184 -49
  100. package/dist/rooms-tasks/room-state.d.ts +1 -0
  101. package/dist/rooms-tasks/room-state.js +12 -1
  102. package/dist/rooms-tasks/task-state.d.ts +115 -3
  103. package/dist/rooms-tasks/task-state.js +361 -11
  104. package/dist/rooms-tasks/templates.d.ts +4 -2
  105. package/dist/rooms-tasks/templates.js +42 -78
  106. package/dist/rooms-tasks/terminal.d.ts +3 -0
  107. package/dist/rooms-tasks/terminal.js +7 -3
  108. package/dist/rooms-tasks/types.d.ts +96 -17
  109. package/dist/rooms-tasks/types.js +14 -4
  110. package/dist/runner.d.ts +12 -2
  111. package/dist/runner.js +124 -43
  112. package/dist/session/acp.d.ts +9 -1
  113. package/dist/session/acp.js +35 -9
  114. package/dist/session/arbiter.d.ts +2 -1
  115. package/dist/session/arbiter.js +2 -0
  116. package/dist/session/codex-app-server-transport.d.ts +53 -0
  117. package/dist/session/codex-app-server-transport.js +184 -0
  118. package/dist/session/codex-app-server.d.ts +122 -0
  119. package/dist/session/codex-app-server.js +1031 -0
  120. package/dist/session/control.d.ts +14 -1
  121. package/dist/session/control.js +38 -0
  122. package/dist/session/conversation-types.d.ts +4 -4
  123. package/dist/session/types.d.ts +24 -6
  124. package/dist/session/types.js +28 -0
  125. package/dist/spawn.d.ts +15 -31
  126. package/dist/spawn.js +117 -237
  127. package/dist/temp-lifecycle.d.ts +1 -1
  128. package/dist/watchdog/config.d.ts +10 -7
  129. package/dist/watchdog/config.js +29 -19
  130. package/dist/watchdog/run.js +4 -13
  131. package/dist/web/fleet-config-service.d.ts +1 -0
  132. package/dist/web/fleet-config-service.js +27 -8
  133. package/dist/web/runtime.js +11 -6
  134. package/dist/web/server.js +28 -1
  135. package/dist/web/topology-promote.js +8 -1
  136. package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
  137. package/dist/web-app/index.html +1 -1
  138. package/package.json +3 -2
  139. package/presets/brain-catalog.json +16 -0
  140. package/presets/fleet/agent_templates/Critic.yaml +4 -0
  141. package/presets/fleet/agent_templates/Developer.yaml +4 -0
  142. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
  143. package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
  144. package/presets/fleet/brains/claude-default.yaml +5 -0
  145. package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
  146. package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
  147. package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
  148. package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
  149. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
  150. package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
  151. package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
  152. package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
  153. package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
  154. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
  155. package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
  156. package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
  157. package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
  158. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
  159. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
  160. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
  161. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
  162. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
  163. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
  164. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
  165. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
  166. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
  167. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
  168. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
  169. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
  170. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
  171. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
  172. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
  173. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
  174. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
  175. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
  176. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
  177. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
  178. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
  179. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
  180. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
  181. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
  182. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
  183. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
  184. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
  185. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
  186. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
  187. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
  188. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
  189. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
  190. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
  191. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
  192. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
  193. package/presets/fleet/roles/Coordinator.yaml +30 -0
  194. package/presets/fleet/roles/Critic.yaml +19 -0
  195. package/presets/fleet/roles/Developer.yaml +19 -0
  196. package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
  197. package/presets/fleet/room_templates/pair.yaml +10 -0
  198. package/presets/fleet/room_templates/single.yaml +7 -0
  199. package/presets/fleet/room_templates/team.yaml +12 -0
  200. package/presets/fleet.yaml +7 -0
  201. package/presets/manifest.json +6 -0
  202. package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
@@ -6,6 +6,16 @@ import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList,
6
6
  import { OWNER_COMMENT_LABEL, ownerNotices } from './notices.js';
7
7
  import { TaskRoomApplicationError } from '../application/task-room-service.js';
8
8
  import { TaskListError } from '../rooms-tasks/task-lists.js';
9
+ import { createTasksReport } from '../reports/index.js';
10
+ async function ownerTaskReport(ctx) {
11
+ return createTasksReport({
12
+ viewer: { surface: 'messenger', authenticatedCid: ctx.authenticatedCid ?? 'owner-command-context', roomCids: [] },
13
+ collect: () => {
14
+ return { lists: ctx.listTaskLists(), tasks: ctx.listTasks() };
15
+ },
16
+ source: { name: 'ours-fleet', version: ctx.version },
17
+ });
18
+ }
9
19
  /** A malformed invocation; the dispatcher answers it with annotated help. */
10
20
  class OwnerCommandUsageError extends Error {
11
21
  }
@@ -26,7 +36,7 @@ function isKnownOwnerTaskState(message) {
26
36
  /^task is not blocked$/u,
27
37
  new RegExp(`^task ${SAFE_ID_WORD} already has a conflicting '(?:done|cancelled)' terminal intent$`, 'u'),
28
38
  new RegExp(`^task ${SAFE_ID_WORD} is already in terminal state '${TASK_STATE_WORD}'$`, 'u'),
29
- new RegExp(`^cannot delete a '${TASK_STATE_WORD}' task; only 'done' tasks can be deleted$`, 'u'),
39
+ new RegExp(`^task ${SAFE_ID_WORD} is pending deletion; run 'ours-fleet task delete ${SAFE_ID_WORD} ${SAFE_ID_WORD}' to retry cleanup$`, 'u'),
30
40
  ].some(pattern => pattern.test(message));
31
41
  }
32
42
  function isKnownOwnerRoomState(message) {
@@ -242,10 +252,18 @@ export const ownerCommands = [
242
252
  execute: noArgs('/version', async (ctx) => ctx.reply(`ℹ️ ours-fleet ${ctx.version}`)),
243
253
  },
244
254
  {
245
- name: 'tasks', usage: '/tasks [state]',
246
- summary: 'list tasks (optionally filter by state)',
255
+ name: 'tasks', usage: '/tasks [state|html]',
256
+ summary: 'list tasks, or attach the HTML report with /tasks html',
247
257
  execute: async (ctx, args) => {
248
258
  const stateFilter = args?.trim() || undefined;
259
+ if (stateFilter === 'html') {
260
+ const artifact = await ownerTaskReport(ctx);
261
+ await ctx.replyHtml(artifact.metadata.filename, artifact.html);
262
+ await ctx.reply(`HTML report attached: ${artifact.metadata.filename}`);
263
+ return;
264
+ }
265
+ if (stateFilter && /\s/u.test(stateFilter))
266
+ throw new OwnerCommandUsageError('usage: /tasks [state|html]');
249
267
  const tasks = ctx.listTasks(stateFilter && stateFilter !== 'all' ? { state: stateFilter } : undefined);
250
268
  await ctx.reply(renderMarkdownList({
251
269
  icon: '📋', title: 'Tasks', empty: 'No tasks found.',
@@ -309,6 +327,9 @@ export const ownerCommands = [
309
327
  break;
310
328
  }
311
329
  case 'list': {
330
+ if (rest.some(value => value.startsWith('--')
331
+ && value !== '--group-by-list' && !value.startsWith('--list=')))
332
+ throw new OwnerCommandUsageError('usage: /task list [state] [--list=<name>] [--group-by-list]');
312
333
  const listFlag = rest.find(r => r.startsWith('--list='));
313
334
  const list = trailingLines ?? listFlag?.slice('--list='.length);
314
335
  const grouped = rest.includes('--group-by-list');
@@ -345,6 +366,8 @@ export const ownerCommands = [
345
366
  break;
346
367
  }
347
368
  case 'lists': {
369
+ if (rest.length || trailingLines)
370
+ throw new OwnerCommandUsageError('usage: /task lists');
348
371
  const lists = ctx.listTaskLists();
349
372
  await ctx.reply(renderMarkdownList({ icon: '📚', title: 'Task lists', empty: 'No task lists found.',
350
373
  records: lists.map(list => `${markdownCode(list.name)}${list.built_in ? ' — built-in' : ''}`) }));
@@ -388,7 +411,7 @@ export const ownerCommands = [
388
411
  break;
389
412
  }
390
413
  case 'show': {
391
- if (!rest[0])
414
+ if (rest.length !== 1 || rest[0].startsWith('--'))
392
415
  throw new OwnerCommandUsageError('usage: /task show <id>');
393
416
  await showTask(rest[0]);
394
417
  break;
@@ -438,11 +461,7 @@ export const ownerCommands = [
438
461
  case 'delete': {
439
462
  if (rest.length !== 2 || rest[0] !== rest[1])
440
463
  throw new OwnerCommandUsageError('destructive: /task delete <id> <id> — provide the task ID twice');
441
- const deleted = ctx.deleteTask(rest[0]);
442
- await ctx.reply(renderMarkdownResult({
443
- icon: '🗑️', title: deleted ? 'Task deleted' : 'Task already absent',
444
- fields: [{ label: 'ID', value: rest[0], kind: 'code' }],
445
- }));
464
+ await ctx.deleteTask(rest[0]);
446
465
  break;
447
466
  }
448
467
  case 'recover': {
@@ -598,10 +617,7 @@ export const ownerCommands = [
598
617
  const templates = ctx.listTemplateQueries();
599
618
  if (!templates.length)
600
619
  return ctx.reply('📐 No templates.');
601
- const lines = templates.map(t => {
602
- const tag = t.builtin ? ' (built-in)' : '';
603
- return `${t.name}@${t.version}${tag} ${t.description}`;
604
- });
620
+ const lines = templates.map(t => `${t.name}@${t.version} ${t.description} [file: ${t.sourceFile ?? 'manifest'}]`);
605
621
  await ctx.reply(`📐 Templates:\n${lines.join('\n')}`);
606
622
  }),
607
623
  },
@@ -627,10 +643,11 @@ export const ownerCommands = [
627
643
  const lines = [
628
644
  `📐 Template: ${t.name}@${t.version}`,
629
645
  `Description: ${t.description}`,
630
- ...(t.builtin ? ['Source: built-in'] : []),
646
+ `Source: ${t.sourceFile ?? 'manifest'}`,
631
647
  ...(t.contract ? [`Contract: ${t.contract}`] : []),
632
648
  'Members:',
633
- ...t.members.map(m => ` ${m.slot} (${m.role}) ×${m.count} → role_ref: ${m.role_ref}`),
649
+ ...t.members.map(m => ` ${m.slot} (${m.role}) ×${m.count} → `
650
+ + `Agent Template: ${m.agent_template}`),
634
651
  ];
635
652
  await ctx.reply(lines.join('\n'));
636
653
  };
@@ -645,10 +662,7 @@ export const ownerCommands = [
645
662
  const templates = ctx.listTemplateQueries();
646
663
  if (!templates.length)
647
664
  return ctx.reply('📐 No templates.');
648
- const lines = templates.map(t => {
649
- const tag = t.builtin ? ' (built-in)' : '';
650
- return `${t.name}@${t.version}${tag} ${t.description}`;
651
- });
665
+ const lines = templates.map(t => `${t.name}@${t.version} ${t.description} [file: ${t.sourceFile ?? 'manifest'}]`);
652
666
  await ctx.reply(`📐 Templates:\n${lines.join('\n')}`);
653
667
  break;
654
668
  }
@@ -716,6 +730,7 @@ export function fleetCliOps(role, configPath) {
716
730
  }),
717
731
  closeRoom: roomId => launchFleetWorker(['room', '_delete', roomId], `room-delete-${roomId}`, configPath),
718
732
  settleTask: taskId => launchFleetWorker(['task', '_settle', taskId], `task-settle-${taskId}`, configPath),
733
+ settleTaskDeletion: taskId => launchFleetWorker(['task', '_settle_delete', taskId], `task-delete-${taskId}`, configPath),
719
734
  recoverTask: taskId => launchFleetWorker(['task', '_recover', taskId], `task-recover-${taskId}`, configPath),
720
735
  };
721
736
  }
@@ -1,5 +1,7 @@
1
1
  /** A send whose (dedupe-scoped) digest was already recorded: it must not repeat. */
2
2
  export declare class DuplicateSendError extends Error {
3
+ readonly status: OwnerProactiveSend['status'];
4
+ constructor(status: OwnerProactiveSend['status']);
3
5
  }
4
6
  /** Durable bounded dedupe containing wire IDs only — never message or reply plaintext. */
5
7
  export declare class OwnerChannelState {
@@ -5,6 +5,11 @@ import { replaceFileAtomically } from '../atomic-file.js';
5
5
  import { canonicalCid } from '../config.js';
6
6
  /** A send whose (dedupe-scoped) digest was already recorded: it must not repeat. */
7
7
  export class DuplicateSendError extends Error {
8
+ status;
9
+ constructor(status) {
10
+ super('duplicate proactive owner message refused');
11
+ this.status = status;
12
+ }
8
13
  }
9
14
  /** Durable bounded dedupe containing wire IDs only — never message or reply plaintext. */
10
15
  export class OwnerChannelState {
@@ -184,8 +189,9 @@ export class OwnerConversationState {
184
189
  // 'all' scope serves wire-keyed idempotency: a crash replay must not
185
190
  // deliver the same wire to a second owner after the route moved.
186
191
  const scope = dedupe === 'all' ? this.sends : recent;
187
- if (scope.some(send => send.digest === digest))
188
- throw new DuplicateSendError('duplicate proactive owner message refused');
192
+ const duplicate = scope.find(send => send.digest === digest);
193
+ if (duplicate)
194
+ throw new DuplicateSendError(duplicate.status);
189
195
  const last = recent.at(-1);
190
196
  if (last && now - last.at < minIntervalMs)
191
197
  throw new Error(`proactive owner messages are rate-limited to one every ${minIntervalMs}ms`);
@@ -85,7 +85,7 @@ export function analyzeRolePermissions(role) {
85
85
  translation = { ...translation, capabilities: inspection.capabilities };
86
86
  }
87
87
  const conflicts = neutral.supported
88
- ? findConflicts(role, neutral.native, adapter.nativePermissionOverrides(role.harness_options))
88
+ ? findConflicts(role, neutral.native, adapter.nativePermissionOverrides(role.harness_options, role))
89
89
  : [];
90
90
  const floor = checkUnattendedFloor(translation.capabilities, requiredUnattendedFloor(role));
91
91
  const floorSeverity = role.permissions.unattended === 'deny' ? 'fail' : 'warn';
@@ -0,0 +1,10 @@
1
+ export interface BootstrapResult {
2
+ revision: number;
3
+ sourceRoot: string;
4
+ configPath: string;
5
+ created: string[];
6
+ preserved: string[];
7
+ }
8
+ export declare const packagedPresetRoot: () => string;
9
+ /** Seed packaged defaults without replacing a single existing byte. */
10
+ export declare function bootstrapPresets(configuration?: string): BootstrapResult;
@@ -0,0 +1,85 @@
1
+ import { constants, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, chmodSync, } from 'node:fs';
2
+ import { dirname, join, relative, resolve, sep } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { defaultConfigPath } from './paths.js';
5
+ import { splitRootFor } from './config.js';
6
+ export const packagedPresetRoot = () => fileURLToPath(new URL('../presets', import.meta.url));
7
+ function assertSafeExisting(path, kind) {
8
+ const stat = lstatSync(path);
9
+ if (stat.isSymbolicLink())
10
+ throw new Error(`preset bootstrap refuses symlink target: ${path}`);
11
+ if (kind === 'file' ? !stat.isFile() : !stat.isDirectory())
12
+ throw new Error(`preset bootstrap expected ${kind}: ${path}`);
13
+ const uid = process.getuid?.();
14
+ if (uid !== undefined && stat.uid !== uid)
15
+ throw new Error(`preset bootstrap refuses target owned by uid ${stat.uid}: ${path}`);
16
+ if ((stat.mode & 0o022) !== 0)
17
+ throw new Error(`preset bootstrap refuses group/world-writable target: ${path}`);
18
+ }
19
+ function ensureDirectory(path) {
20
+ const parent = dirname(path);
21
+ if (parent !== path && !existsSync(parent))
22
+ ensureDirectory(parent);
23
+ if (existsSync(path)) {
24
+ assertSafeExisting(path, 'directory');
25
+ return;
26
+ }
27
+ mkdirSync(path, { mode: 0o700 });
28
+ }
29
+ function seedFile(source, target, result) {
30
+ ensureDirectory(dirname(target));
31
+ if (existsSync(target)) {
32
+ assertSafeExisting(target, 'file');
33
+ result.preserved.push(target);
34
+ return;
35
+ }
36
+ try {
37
+ copyFileSync(source, target, constants.COPYFILE_EXCL);
38
+ chmodSync(target, 0o600);
39
+ result.created.push(target);
40
+ }
41
+ catch (error) {
42
+ if (error.code !== 'EEXIST')
43
+ throw error;
44
+ assertSafeExisting(target, 'file');
45
+ result.preserved.push(target);
46
+ }
47
+ }
48
+ function presetFiles(root) {
49
+ const files = [];
50
+ const visit = (directory) => {
51
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
52
+ const path = join(directory, entry.name);
53
+ if (entry.isDirectory())
54
+ visit(path);
55
+ else if (entry.isFile())
56
+ files.push(path);
57
+ }
58
+ };
59
+ visit(root);
60
+ return files.sort();
61
+ }
62
+ /** Seed packaged defaults without replacing a single existing byte. */
63
+ export function bootstrapPresets(configuration = defaultConfigPath()) {
64
+ const sourceRoot = packagedPresetRoot();
65
+ const manifest = JSON.parse(readFileSync(join(sourceRoot, 'manifest.json'), 'utf8'));
66
+ if (manifest.schema_version !== 1 || !Number.isInteger(manifest.preset_revision))
67
+ throw new Error(`invalid packaged preset manifest: ${join(sourceRoot, 'manifest.json')}`);
68
+ const configPath = resolve(configuration);
69
+ const result = {
70
+ revision: manifest.preset_revision, sourceRoot, configPath, created: [], preserved: [],
71
+ };
72
+ ensureDirectory(dirname(configPath));
73
+ seedFile(join(sourceRoot, 'fleet.yaml'), configPath, result);
74
+ const sourceSplit = join(sourceRoot, 'fleet');
75
+ const targetSplit = splitRootFor(configPath);
76
+ if (existsSync(targetSplit))
77
+ assertSafeExisting(targetSplit, 'directory');
78
+ for (const source of presetFiles(sourceSplit)) {
79
+ const rel = relative(sourceSplit, source);
80
+ if (rel.startsWith('..') || rel.includes(`..${sep}`))
81
+ throw new Error(`invalid packaged preset path: ${source}`);
82
+ seedFile(source, join(targetSplit, rel), result);
83
+ }
84
+ return result;
85
+ }
@@ -0,0 +1,42 @@
1
+ import { renameSync } from 'node:fs';
2
+ export declare const legacyStarterMigrationManifest: Readonly<{
3
+ schemaVersion: 1;
4
+ presetRevision: 2;
5
+ fingerprints: Readonly<Record<string, string>>;
6
+ }>;
7
+ export interface PresetMigrationResult {
8
+ write: boolean;
9
+ root: string;
10
+ stagingPath: string;
11
+ backupPath: string;
12
+ moves: Array<{
13
+ from: string;
14
+ to: string;
15
+ }>;
16
+ additions: string[];
17
+ }
18
+ export interface RoleDefaultMigrationResult {
19
+ write: boolean;
20
+ root: string;
21
+ stagingPath: string;
22
+ backupPath: string;
23
+ removals: string[];
24
+ replacements: string[];
25
+ additions: string[];
26
+ preserved: string[];
27
+ }
28
+ interface MigrationHooks {
29
+ rename?: typeof renameSync;
30
+ nonce?: string;
31
+ beforeLockClaim?: () => void;
32
+ beforeRoleDefaultPublish?: () => void;
33
+ }
34
+ /** Explicit adoption of exact packaged revision-3 role defaults; customized files are never changed. */
35
+ export declare function migratePackagedRoleDefaults(configuration: string, options?: {
36
+ write?: boolean;
37
+ }, hooks?: MigrationHooks): RoleDefaultMigrationResult;
38
+ /** Explicit safe migration of the exact revision-2 six-worker starter set. Dry-run unless write=true. */
39
+ export declare function migrateLegacyStarterPresets(configuration: string, options?: {
40
+ write?: boolean;
41
+ }, hooks?: MigrationHooks): PresetMigrationResult;
42
+ export {};