@ours.network/fleet 1.1.0-nightly.8 → 1.1.0

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 +231 -46
  2. package/dist/application/capabilities.js +18 -6
  3. package/dist/application/fleet-query-service.js +7 -4
  4. package/dist/application/role-command-service.js +9 -0
  5. package/dist/application/role-creation-service.d.ts +4 -4
  6. package/dist/application/role-creation-service.js +9 -2
  7. package/dist/application/role-repository.d.ts +4 -2
  8. package/dist/application/role-repository.js +6 -3
  9. package/dist/application/session-control.d.ts +3 -1
  10. package/dist/application/session-control.js +4 -2
  11. package/dist/application/task-room-service.d.ts +92 -39
  12. package/dist/application/task-room-service.js +579 -173
  13. package/dist/briefing.js +85 -28
  14. package/dist/build-info.json +5 -5
  15. package/dist/canonical-json.d.ts +2 -0
  16. package/dist/canonical-json.js +10 -0
  17. package/dist/cli.js +130 -55
  18. package/dist/config.d.ts +38 -3
  19. package/dist/config.js +193 -28
  20. package/dist/creation.d.ts +1 -1
  21. package/dist/creation.js +7 -4
  22. package/dist/docs.d.ts +1 -1
  23. package/dist/docs.js +193 -55
  24. package/dist/doctor.d.ts +2 -0
  25. package/dist/doctor.js +40 -4
  26. package/dist/fleet-command-audit.d.ts +59 -9
  27. package/dist/fleet-command-audit.js +426 -63
  28. package/dist/fleet-proxy.d.ts +5 -2
  29. package/dist/harness/claude-code-session.js +2 -1
  30. package/dist/harness/codex-app-server-proxy.d.ts +40 -0
  31. package/dist/harness/codex-app-server-proxy.js +348 -14
  32. package/dist/harness/codex-session.d.ts +9 -2
  33. package/dist/harness/codex-session.js +18 -2
  34. package/dist/harness/codex.d.ts +5 -1
  35. package/dist/harness/codex.js +104 -10
  36. package/dist/harness/types.d.ts +1 -1
  37. package/dist/index.d.ts +4 -3
  38. package/dist/index.js +3 -2
  39. package/dist/init-guidance.d.ts +1 -1
  40. package/dist/init-guidance.js +1 -1
  41. package/dist/init-wizard.d.ts +98 -0
  42. package/dist/init-wizard.js +631 -0
  43. package/dist/lifecycle-summary.d.ts +134 -0
  44. package/dist/lifecycle-summary.js +179 -0
  45. package/dist/loops/config.d.ts +10 -1
  46. package/dist/loops/config.js +38 -5
  47. package/dist/model-recovery.d.ts +1 -1
  48. package/dist/ops.js +2 -17
  49. package/dist/owner-channel/channel.d.ts +21 -5
  50. package/dist/owner-channel/channel.js +312 -138
  51. package/dist/owner-channel/commands.d.ts +28 -6
  52. package/dist/owner-channel/commands.js +109 -27
  53. package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
  54. package/dist/owner-channel/lifecycle-outbox.js +87 -0
  55. package/dist/owner-channel/ours-client.d.ts +19 -0
  56. package/dist/owner-channel/ours-client.js +3 -0
  57. package/dist/owner-channel/state.d.ts +2 -0
  58. package/dist/owner-channel/state.js +8 -2
  59. package/dist/permissions.js +1 -1
  60. package/dist/preset-migration.d.ts +42 -0
  61. package/dist/preset-migration.js +450 -0
  62. package/dist/reports/artifact.d.ts +10 -0
  63. package/dist/reports/artifact.js +32 -0
  64. package/dist/reports/index.d.ts +5 -0
  65. package/dist/reports/index.js +5 -0
  66. package/dist/reports/render.d.ts +6 -0
  67. package/dist/reports/render.js +240 -0
  68. package/dist/reports/service.d.ts +42 -0
  69. package/dist/reports/service.js +57 -0
  70. package/dist/reports/tasks.d.ts +52 -0
  71. package/dist/reports/tasks.js +177 -0
  72. package/dist/reports/types.d.ts +315 -0
  73. package/dist/reports/types.js +6 -0
  74. package/dist/resolved-plan.js +13 -1
  75. package/dist/rooms-tasks/cli.d.ts +2 -0
  76. package/dist/rooms-tasks/cli.js +434 -279
  77. package/dist/rooms-tasks/close.d.ts +9 -1
  78. package/dist/rooms-tasks/close.js +23 -7
  79. package/dist/rooms-tasks/config.js +16 -25
  80. package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
  81. package/dist/rooms-tasks/cowork-adapter.js +13 -0
  82. package/dist/rooms-tasks/deletion.d.ts +51 -0
  83. package/dist/rooms-tasks/deletion.js +217 -0
  84. package/dist/rooms-tasks/external-worker.d.ts +8 -0
  85. package/dist/rooms-tasks/external-worker.js +38 -0
  86. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  87. package/dist/rooms-tasks/launch-snapshot.js +181 -0
  88. package/dist/rooms-tasks/member-overrides.d.ts +30 -0
  89. package/dist/rooms-tasks/member-overrides.js +204 -0
  90. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  91. package/dist/rooms-tasks/member-startup.js +9 -2
  92. package/dist/rooms-tasks/provision.js +146 -56
  93. package/dist/rooms-tasks/room-state.d.ts +1 -0
  94. package/dist/rooms-tasks/room-state.js +12 -1
  95. package/dist/rooms-tasks/task-room-name.d.ts +12 -0
  96. package/dist/rooms-tasks/task-room-name.js +61 -0
  97. package/dist/rooms-tasks/task-state.d.ts +115 -3
  98. package/dist/rooms-tasks/task-state.js +361 -11
  99. package/dist/rooms-tasks/templates.d.ts +4 -1
  100. package/dist/rooms-tasks/templates.js +34 -3
  101. package/dist/rooms-tasks/terminal.d.ts +3 -0
  102. package/dist/rooms-tasks/terminal.js +10 -4
  103. package/dist/rooms-tasks/types.d.ts +92 -7
  104. package/dist/rooms-tasks/types.js +14 -1
  105. package/dist/runner.d.ts +3 -0
  106. package/dist/runner.js +92 -33
  107. package/dist/session/acp.d.ts +4 -1
  108. package/dist/session/acp.js +29 -13
  109. package/dist/session/arbiter.d.ts +2 -1
  110. package/dist/session/arbiter.js +2 -0
  111. package/dist/session/codex-app-server-transport.d.ts +53 -0
  112. package/dist/session/codex-app-server-transport.js +184 -0
  113. package/dist/session/codex-app-server.d.ts +122 -0
  114. package/dist/session/codex-app-server.js +1031 -0
  115. package/dist/session/control.d.ts +2 -2
  116. package/dist/session/control.js +18 -2
  117. package/dist/session/conversation-types.d.ts +4 -4
  118. package/dist/session/types.d.ts +24 -6
  119. package/dist/session/types.js +28 -0
  120. package/dist/spawn.d.ts +9 -0
  121. package/dist/spawn.js +77 -9
  122. package/dist/web/fleet-config-service.d.ts +1 -0
  123. package/dist/web/fleet-config-service.js +25 -7
  124. package/dist/web/runtime.js +11 -6
  125. package/dist/web/server.js +28 -1
  126. package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
  127. package/dist/web-app/index.html +1 -1
  128. package/package.json +4 -4
  129. package/presets/brain-catalog.json +17 -0
  130. package/presets/fleet/{agents → agent_templates}/Critic.yaml +2 -0
  131. package/presets/fleet/{agents/Agent.yaml → agent_templates/Developer.yaml} +3 -1
  132. package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
  133. package/presets/fleet/agents/{Developer.yaml → FleetCoordinator.yaml} +1 -1
  134. package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
  135. package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
  136. package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
  137. package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
  138. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
  139. package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
  140. package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
  141. package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
  142. package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
  143. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
  144. package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
  145. package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
  146. package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
  147. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
  148. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
  149. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
  150. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
  151. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
  152. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
  153. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
  154. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
  155. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
  156. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
  157. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
  158. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
  159. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
  160. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
  161. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
  162. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
  163. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
  164. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
  165. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
  166. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
  167. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
  168. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
  169. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
  170. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
  171. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
  172. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
  173. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
  174. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
  175. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
  176. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
  177. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
  178. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
  179. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
  180. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
  181. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
  182. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
  183. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
  184. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
  185. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
  186. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
  187. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
  188. package/presets/fleet/roles/Coordinator.yaml +37 -0
  189. package/presets/fleet/roles/Critic.yaml +23 -1
  190. package/presets/fleet/roles/Developer.yaml +27 -5
  191. package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
  192. package/presets/fleet/room_templates/pair.yaml +9 -7
  193. package/presets/fleet/room_templates/single.yaml +5 -4
  194. package/presets/fleet/room_templates/team.yaml +12 -8
  195. package/presets/manifest.json +2 -1
  196. package/presets/fleet/agents/Architect.yaml +0 -3
  197. package/presets/fleet/agents/Secretary.yaml +0 -3
  198. package/presets/fleet/agents/Tester.yaml +0 -3
  199. package/presets/fleet/roles/Agent.yaml +0 -6
  200. package/presets/fleet/roles/Architect.yaml +0 -6
  201. package/presets/fleet/roles/Secretary.yaml +0 -6
  202. package/presets/fleet/roles/Tester.yaml +0 -6
@@ -0,0 +1,450 @@
1
+ import { closeSync, constants, copyFileSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, chmodSync, fstatSync, utimesSync, } from 'node:fs';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { canonicalJson } from './canonical-json.js';
5
+ import { parseFleetDocument } from './config-yaml.js';
6
+ import { packagedPresetRoot } from './preset-bootstrap.js';
7
+ import { loadConfig, splitRootFor } from './config.js';
8
+ import { listTemplates } from './rooms-tasks/templates.js';
9
+ const STARTER_FINGERPRINTS = Object.freeze({
10
+ Agent: '0673294fd59f62dd37468d8249231f1730261c9f8a92a60ef10d848115952c26',
11
+ Architect: '08f658d44dda571847e164b3fae59118f5b142cafdb2443372b5c99a29d80bf0',
12
+ Critic: 'd29bd5188ad7de360180f2e30d454f113267898b3de156c5c01787526cacb5a3',
13
+ Developer: '7f70feddd6d2a70ccb844db19dde5b7596d97771d340b36be384a912e1ccf0f1',
14
+ Secretary: '405a45e5be9ea19c80a9b0aa8a06c36d1db2428909e1ca7cf885ace65f6cd954',
15
+ Tester: '87403335d194d98ee905bd005f0d90824f0cf49f8e1ebffbaaebb0d61f324c75',
16
+ });
17
+ const V3_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
18
+ 'roles/Agent.yaml': 'f3c739ee7e8e83787e34bf53e0a520f803fb722809c04b99f06c1ef27fd39063',
19
+ 'roles/Architect.yaml': '33e6723943acaf10a4f06f2c9c40644573e94b3835af030feec22867066637e7',
20
+ 'roles/Critic.yaml': '1b9bdbfa01049c2b9e35a82fcbe0c93d976823e19ec61c02e7a74f655f7ee5fd',
21
+ 'roles/Developer.yaml': '15316c13ab9862d394d1812482e92815a570d1e6c151cd8ec744e6cf37aa34c1',
22
+ 'roles/Secretary.yaml': '1339ccfafe88c3322370cf953c74d7a3f7d8e3dc51ff504ab1259409e41a0d49',
23
+ 'roles/Tester.yaml': 'e03ca047990f09776fd17e97e348bb8014cdaef6c0c2829a37884e5c471fb575',
24
+ 'agent_templates/Agent.yaml': '0673294fd59f62dd37468d8249231f1730261c9f8a92a60ef10d848115952c26',
25
+ 'agent_templates/Architect.yaml': '08f658d44dda571847e164b3fae59118f5b142cafdb2443372b5c99a29d80bf0',
26
+ 'agent_templates/Critic.yaml': 'd29bd5188ad7de360180f2e30d454f113267898b3de156c5c01787526cacb5a3',
27
+ 'agent_templates/Developer.yaml': '7f70feddd6d2a70ccb844db19dde5b7596d97771d340b36be384a912e1ccf0f1',
28
+ 'agent_templates/Secretary.yaml': '405a45e5be9ea19c80a9b0aa8a06c36d1db2428909e1ca7cf885ace65f6cd954',
29
+ 'agent_templates/Tester.yaml': '87403335d194d98ee905bd005f0d90824f0cf49f8e1ebffbaaebb0d61f324c75',
30
+ 'room_templates/single.yaml': 'f039eee04ff387642d172e972536e16311601d051691ed9faa091b614e82afbb',
31
+ 'room_templates/pair.yaml': '4bd145f5aba0edcec99e24c48792de5276d08eee0cd55f100f724aaa44cdaafb',
32
+ 'room_templates/team.yaml': '866202599f84b591336000b7b4278bc85754a82b7016cd2c82751af3b6e25116',
33
+ 'agents/FleetCoordinator.yaml': '35eb8c055c32262240c216bf57c6f85f8ac6c6ef5d3338587ea4ccf1f591c516',
34
+ });
35
+ // The revision-3 interactive generator selected purpose-specific Brains while
36
+ // bootstrap copied claude-default. These are exact semantic hashes of that
37
+ // generated form, not relaxed structural matches, so nearby user edits remain custom.
38
+ const V3_GENERATED_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
39
+ 'agent_templates/Agent.yaml': 'aa35d661b31f9d4408e208c946f5b669a21a02ecc2b96848478c426529a1507d',
40
+ 'agent_templates/Architect.yaml': 'f292e66df7b805f5fff0e9fbe0256e994ea4e3bd1b8429876f2f1360d27c50cd',
41
+ 'agent_templates/Critic.yaml': '9ef29e588c5d37d1fc1a3362e0e4b83bd806f41f5a34e1b04c92d57dce49334b',
42
+ 'agent_templates/Developer.yaml': 'ad5c56d92607867954649d342c71d68830c461772255229af9c53204fa2d2f3a',
43
+ 'agent_templates/Secretary.yaml': 'bfc229e01c42c9288cef7fb3706091ce5c369e77a797c7b14c6d76e930537c01',
44
+ 'agent_templates/Tester.yaml': '3733db187951353f855dc998f1dd1f66d883eb556a3e6431a7abf4e267f63071',
45
+ 'agents/FleetCoordinator.yaml': '70f90797899828c3669c3914e8f873314e48cb9502aff187d2f1fbc9177f6df9',
46
+ });
47
+ const V4_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
48
+ 'agent_templates/LocalCoordinator.yaml': 'f04a88b541e655e0039e4dc9834293b5180f6d488a473a6c51b50d0b72dae83b',
49
+ 'room_templates/team.yaml': 'accaa3569781f7acde3f15c89f447f4132f580f936513379c704aad27481e13f',
50
+ });
51
+ const V4_GENERATED_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
52
+ 'agent_templates/LocalCoordinator.yaml': 'cb7c83a835dde9d45bac42cadce2f196f561d2460321c690cdef435f7076f97f',
53
+ });
54
+ const V5_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
55
+ 'roles/LocalCoordinator.yaml': '52714be635060adca2bc7fe99c27c3184be6c78e3874a77e61a2fb135bac1278',
56
+ 'roles/Developer.yaml': '04278f325c366035e82d8e7f4aff72badb297eaf2fb5afae38a28a5aaf9260c9',
57
+ 'roles/Critic.yaml': 'bcb96c84534ca6fc466a8ee63f679785d347ed1cdf223efcc36ca5ed4ca7f3de',
58
+ 'agent_templates/LocalCoordinator.yaml': 'c59980ab7d94dad4d4961ba308cf62342291d8f1c57cf2faf900e80136fa2e7d',
59
+ 'agent_templates/Developer.yaml': '36f1d010a9c8591ae5ba422ef1ac26335b9f7f3382b6b8532844df754a56287e',
60
+ 'agent_templates/Critic.yaml': '7025b842e260a9c91ebc64dd3a11162a82b025b287457c6cb6f6c8964b38cdfd',
61
+ 'room_templates/single.yaml': '61051917b1f4f53a13687cc69d3f9ad98f1b02558f82b236b22aac942850395f',
62
+ 'room_templates/pair.yaml': 'a20b2145fa84e5d61266385e629892734b98fc61be9d0c7647c3778ec1bb40a8',
63
+ 'room_templates/team.yaml': 'c455f54a1f86106bfac7192299130dabfb91fa6179d2dedcc0573a384a902411',
64
+ });
65
+ const V5_GENERATED_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
66
+ 'agent_templates/LocalCoordinator.yaml': 'cb7c83a835dde9d45bac42cadce2f196f561d2460321c690cdef435f7076f97f',
67
+ 'agent_templates/Developer.yaml': 'fdd953aab72d042e62780dd273598745aae59521b39edf4876d7676bb7189c83',
68
+ 'agent_templates/Critic.yaml': '3e98a28f17876061cfadf984351aa59fdbe19629e514eff52c74546545c64a91',
69
+ });
70
+ const CURRENT_ROLE_DEFAULTS = new Set([
71
+ 'roles/Coordinator.yaml', 'roles/LocalCoordinator.yaml', 'roles/Developer.yaml', 'roles/Critic.yaml',
72
+ 'agent_templates/LocalCoordinator.yaml', 'agent_templates/Developer.yaml', 'agent_templates/Critic.yaml',
73
+ 'room_templates/single.yaml', 'room_templates/pair.yaml', 'room_templates/team.yaml',
74
+ 'agents/FleetCoordinator.yaml',
75
+ ]);
76
+ export const legacyStarterMigrationManifest = Object.freeze({
77
+ schemaVersion: 1,
78
+ presetRevision: 2,
79
+ fingerprints: STARTER_FINGERPRINTS,
80
+ });
81
+ function fingerprint(path) {
82
+ const value = parseFleetDocument(path, readFileSync(path, 'utf8'), 'strict').value;
83
+ return createHash('sha256').update(canonicalJson(value)).digest('hex');
84
+ }
85
+ function pathProof(path) {
86
+ const stat = lstatSync(path);
87
+ if (stat.isSymbolicLink() || (!stat.isFile() && !stat.isDirectory()))
88
+ throw new Error(`migration proof refuses symlink or special file: ${path}`);
89
+ return {
90
+ dev: stat.dev, ino: stat.ino, mode: stat.mode, uid: stat.uid, gid: stat.gid,
91
+ size: stat.size,
92
+ ...(stat.isFile() ? {
93
+ sha256: createHash('sha256').update(readFileSync(path)).digest('hex'),
94
+ } : {}),
95
+ };
96
+ }
97
+ /** Stable proof of every path, byte, and preservation-relevant identity in a trusted tree. */
98
+ function treeProof(root) {
99
+ const entries = [];
100
+ const visit = (path, relative) => {
101
+ const stat = lstatSync(path);
102
+ entries.push({ path: relative, type: stat.isDirectory() ? 'directory' : 'file', ...pathProof(path) });
103
+ if (stat.isDirectory())
104
+ for (const name of readdirSync(path).sort())
105
+ visit(join(path, name), relative ? `${relative}/${name}` : name);
106
+ };
107
+ visit(root, '.');
108
+ return createHash('sha256').update(canonicalJson(entries)).digest('hex');
109
+ }
110
+ function manifestProof(path) {
111
+ const stat = lstatSync(path);
112
+ if (stat.isSymbolicLink() || !stat.isFile())
113
+ throw new Error(`migration proof requires a regular manifest: ${path}`);
114
+ return canonicalJson(pathProof(path));
115
+ }
116
+ function assertPrivateTree(root) {
117
+ const uid = process.getuid?.();
118
+ const visit = (path) => {
119
+ const stat = lstatSync(path);
120
+ if (stat.isSymbolicLink() || (!stat.isFile() && !stat.isDirectory()))
121
+ throw new Error(`migration refuses symlink or special file: ${path}`);
122
+ if (uid !== undefined && stat.uid !== uid)
123
+ throw new Error(`migration refuses path owned by uid ${stat.uid}: ${path}`);
124
+ if ((stat.mode & 0o077) !== 0 || (stat.mode & 0o022) !== 0)
125
+ throw new Error(`migration requires owner-private, non-group/world-writable mode: ${path}`);
126
+ if (stat.isDirectory())
127
+ for (const entry of readdirSync(path))
128
+ visit(join(path, entry));
129
+ };
130
+ visit(root);
131
+ }
132
+ function fsyncPath(path) {
133
+ const fd = openSync(path, constants.O_RDONLY);
134
+ try {
135
+ fsyncSync(fd);
136
+ }
137
+ finally {
138
+ closeSync(fd);
139
+ }
140
+ }
141
+ function copyTree(source, target) {
142
+ const stat = lstatSync(source);
143
+ if (stat.isDirectory()) {
144
+ mkdirSync(target, { mode: stat.mode & 0o777 });
145
+ for (const entry of readdirSync(source))
146
+ copyTree(join(source, entry), join(target, entry));
147
+ chmodSync(target, stat.mode & 0o777);
148
+ utimesSync(target, stat.atime, stat.mtime);
149
+ fsyncPath(target);
150
+ return;
151
+ }
152
+ copyFileSync(source, target, constants.COPYFILE_EXCL);
153
+ chmodSync(target, stat.mode & 0o777);
154
+ utimesSync(target, stat.atime, stat.mtime);
155
+ const fd = openSync(target, constants.O_RDONLY);
156
+ try {
157
+ fsyncSync(fd);
158
+ }
159
+ finally {
160
+ closeSync(fd);
161
+ }
162
+ }
163
+ function validateRoleDefaultMigration(stageManifest) {
164
+ const config = loadConfig(stageManifest, { yamlMode: 'strict' });
165
+ for (const template of listTemplates(config.roomTemplates ?? {})) {
166
+ for (const member of template.members)
167
+ if (!config.agentTemplates?.[member.agent_template])
168
+ throw new Error(`Room Template '${template.name}' references missing Agent Template '${member.agent_template}'`);
169
+ }
170
+ }
171
+ /** Explicit adoption of exact known packaged role defaults; customized files are never changed. */
172
+ export function migratePackagedRoleDefaults(configuration, options = {}, hooks = {}) {
173
+ const configPath = resolve(configuration);
174
+ const root = splitRootFor(configPath);
175
+ if (!existsSync(configPath) || !existsSync(root))
176
+ throw new Error('packaged role-default migration requires an existing split configuration');
177
+ const uid = process.getuid?.();
178
+ const configStat = lstatSync(configPath);
179
+ if (!configStat.isFile() || configStat.isSymbolicLink() || (configStat.mode & 0o077) !== 0
180
+ || (uid !== undefined && configStat.uid !== uid))
181
+ throw new Error('packaged role-default migration requires an owner-private regular config');
182
+ assertPrivateTree(root);
183
+ const parent = dirname(root);
184
+ const parentStat = lstatSync(parent);
185
+ if (!parentStat.isDirectory() || parentStat.isSymbolicLink() || (parentStat.mode & 0o022) !== 0
186
+ || (uid !== undefined && parentStat.uid !== uid) || statSync(root).dev !== parentStat.dev)
187
+ throw new Error('packaged role-default migration requires an owner-controlled same-filesystem parent');
188
+ const classify = () => {
189
+ const removals = [];
190
+ const replacements = [];
191
+ const preserved = [];
192
+ const known = new Set([
193
+ ...Object.keys(V3_ROLE_DEFAULT_FINGERPRINTS),
194
+ ...Object.keys(V4_ROLE_DEFAULT_FINGERPRINTS),
195
+ ...Object.keys(V5_ROLE_DEFAULT_FINGERPRINTS),
196
+ ]);
197
+ for (const relative of known) {
198
+ const path = join(root, relative);
199
+ if (!existsSync(path))
200
+ continue;
201
+ const actual = fingerprint(path);
202
+ const recognized = [
203
+ V3_ROLE_DEFAULT_FINGERPRINTS[relative],
204
+ V3_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
205
+ V4_ROLE_DEFAULT_FINGERPRINTS[relative],
206
+ V4_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
207
+ V5_ROLE_DEFAULT_FINGERPRINTS[relative],
208
+ V5_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
209
+ ].some(expected => expected !== undefined && actual === expected);
210
+ if (recognized)
211
+ (CURRENT_ROLE_DEFAULTS.has(relative) ? replacements : removals).push(path);
212
+ else
213
+ preserved.push(path);
214
+ }
215
+ const additions = [];
216
+ for (const relative of CURRENT_ROLE_DEFAULTS) {
217
+ const path = join(root, relative);
218
+ if (!existsSync(path))
219
+ additions.push(path);
220
+ else if (!replacements.includes(path) && !preserved.includes(path))
221
+ preserved.push(path);
222
+ }
223
+ return { removals: removals.sort(), replacements: replacements.sort(),
224
+ additions: additions.sort(), preserved: preserved.sort() };
225
+ };
226
+ const classified = classify();
227
+ const nonce = hooks.nonce ?? `${new Date().toISOString().replace(/[^0-9]/g, '')}-${process.pid}`;
228
+ const stagingPath = join(parent, `.${basename(root)}.role-defaults-stage-${nonce}`);
229
+ const backupPath = join(parent, `.${basename(root)}.role-defaults-backup-${nonce}`);
230
+ const stageManifest = `${stagingPath}.yaml`;
231
+ const lockPath = join(parent, `.${basename(root)}.role-defaults-migration.lock`);
232
+ const result = { write: options.write === true, root, stagingPath, backupPath,
233
+ ...classified };
234
+ if (!options.write || (!classified.removals.length && !classified.replacements.length && !classified.additions.length))
235
+ return result;
236
+ for (const path of [stagingPath, backupPath, stageManifest, lockPath])
237
+ if (existsSync(path))
238
+ throw new Error('packaged role-default migration refuses existing staging, backup, or lock path');
239
+ const lockFd = openSync(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
240
+ const lockToken = `${process.pid}:${randomUUID()}\n`;
241
+ writeFileSync(lockFd, lockToken);
242
+ fsyncSync(lockFd);
243
+ try {
244
+ assertPrivateTree(root);
245
+ if (canonicalJson(classify()) !== canonicalJson(classified))
246
+ throw new Error('packaged role-default migration inputs changed after lock acquisition');
247
+ const sourceTreeProof = treeProof(root);
248
+ const sourceManifestProof = manifestProof(configPath);
249
+ copyTree(root, stagingPath);
250
+ for (const path of classified.removals)
251
+ unlinkSync(join(stagingPath, path.slice(root.length + 1)));
252
+ const source = join(packagedPresetRoot(), 'fleet');
253
+ for (const path of [...classified.replacements, ...classified.additions]) {
254
+ const relative = path.slice(root.length + 1);
255
+ const target = join(stagingPath, relative);
256
+ mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
257
+ if (existsSync(target))
258
+ unlinkSync(target);
259
+ copyFileSync(join(source, relative), target, constants.COPYFILE_EXCL);
260
+ chmodSync(target, 0o600);
261
+ fsyncPath(target);
262
+ }
263
+ for (const kind of ['roles', 'agent_templates', 'room_templates', 'agents'])
264
+ fsyncPath(join(stagingPath, kind));
265
+ assertPrivateTree(stagingPath);
266
+ copyFileSync(configPath, stageManifest, constants.COPYFILE_EXCL);
267
+ chmodSync(stageManifest, 0o600);
268
+ try {
269
+ validateRoleDefaultMigration(stageManifest);
270
+ }
271
+ catch (error) {
272
+ throw new Error(`staged configuration is invalid: ${error.message}`, { cause: error });
273
+ }
274
+ unlinkSync(stageManifest);
275
+ const rename = hooks.rename ?? renameSync;
276
+ hooks.beforeRoleDefaultPublish?.();
277
+ rename(root, backupPath);
278
+ fsyncPath(parent);
279
+ let verificationFailure;
280
+ try {
281
+ assertPrivateTree(backupPath);
282
+ if (treeProof(backupPath) !== sourceTreeProof || manifestProof(configPath) !== sourceManifestProof)
283
+ throw new Error('migration inputs changed during staging');
284
+ }
285
+ catch (error) {
286
+ verificationFailure = error;
287
+ }
288
+ if (verificationFailure) {
289
+ try {
290
+ rename(backupPath, root);
291
+ fsyncPath(parent);
292
+ }
293
+ catch (rollbackError) {
294
+ throw new Error(`post-rename verification failed and rollback failed; recovery backup retained at ${backupPath}: ${rollbackError.message}`, { cause: verificationFailure });
295
+ }
296
+ throw new Error(`post-rename verification failed; live root restored and staged recovery retained at ${stagingPath}: ${verificationFailure.message}`, { cause: verificationFailure });
297
+ }
298
+ try {
299
+ rename(stagingPath, root);
300
+ fsyncPath(parent);
301
+ }
302
+ catch (publishError) {
303
+ try {
304
+ rename(backupPath, root);
305
+ fsyncPath(parent);
306
+ }
307
+ catch (rollbackError) {
308
+ throw new Error(`publication failed and rollback failed; recovery backup retained at ${backupPath}: ${rollbackError.message}`, { cause: publishError });
309
+ }
310
+ throw new Error(`publication failed; original restored and staged recovery retained at ${stagingPath}: ${publishError.message}`, { cause: publishError });
311
+ }
312
+ return result;
313
+ }
314
+ finally {
315
+ closeSync(lockFd);
316
+ if (existsSync(stageManifest))
317
+ unlinkSync(stageManifest);
318
+ if (existsSync(lockPath) && readFileSync(lockPath, 'utf8') === lockToken)
319
+ unlinkSync(lockPath);
320
+ }
321
+ }
322
+ function verifyStarters(root) {
323
+ return Object.entries(STARTER_FINGERPRINTS).map(([name, expected]) => {
324
+ const path = join(root, 'agents', `${name}.yaml`);
325
+ if (!existsSync(path))
326
+ throw new Error(`legacy starter migration is ambiguous: missing ${path}`);
327
+ const actual = fingerprint(path);
328
+ if (actual !== expected)
329
+ throw new Error(`legacy starter migration refuses customized known starter ${path}`);
330
+ return { name, path };
331
+ });
332
+ }
333
+ /** Explicit safe migration of the exact revision-2 six-worker starter set. Dry-run unless write=true. */
334
+ export function migrateLegacyStarterPresets(configuration, options = {}, hooks = {}) {
335
+ const configPath = resolve(configuration);
336
+ const root = splitRootFor(configPath);
337
+ const configStat = lstatSync(configPath);
338
+ const uid = process.getuid?.();
339
+ if (!configStat.isFile() || configStat.isSymbolicLink() || (configStat.mode & 0o077) !== 0
340
+ || (uid !== undefined && configStat.uid !== uid))
341
+ throw new Error(`migration requires an owner-private regular v2 configuration file: ${configPath}`);
342
+ const config = parseFleetDocument(configPath, readFileSync(configPath, 'utf8'), 'strict').value;
343
+ if (config.api_version !== 'ours.network/fleet/v2')
344
+ throw new Error(`legacy starter migration requires api_version ours.network/fleet/v2: ${configPath}`);
345
+ if (!existsSync(root))
346
+ throw new Error(`legacy starter split root does not exist: ${root}`);
347
+ assertPrivateTree(root);
348
+ const rootStat = statSync(root);
349
+ const parent = dirname(root);
350
+ const parentStat = lstatSync(parent);
351
+ if (!parentStat.isDirectory() || parentStat.isSymbolicLink() || (parentStat.mode & 0o022) !== 0
352
+ || (uid !== undefined && parentStat.uid !== uid))
353
+ throw new Error(`migration requires an owner-controlled, non-group/world-writable parent directory: ${parent}`);
354
+ if (rootStat.dev !== statSync(parent).dev)
355
+ throw new Error(`migration requires same-filesystem atomic rename: ${root} and ${parent}`);
356
+ const starters = verifyStarters(root);
357
+ const templateDir = join(root, 'agent_templates');
358
+ const coordinator = join(root, 'agents', 'FleetCoordinator.yaml');
359
+ for (const { name } of starters) {
360
+ const target = join(templateDir, `${name}.yaml`);
361
+ if (existsSync(target))
362
+ throw new Error(`legacy starter migration destination collides: ${target}`);
363
+ }
364
+ if (existsSync(coordinator))
365
+ throw new Error(`legacy starter migration destination collides: ${coordinator}`);
366
+ const nonce = hooks.nonce ?? `${new Date().toISOString().replace(/[^0-9]/g, '')}-${process.pid}`;
367
+ const stagingPath = join(parent, `.${basename(root)}.agent-templates-stage-${nonce}`);
368
+ const backupPath = join(parent, `.${basename(root)}.legacy-backup-${nonce}`);
369
+ const lockPath = join(parent, `.${basename(root)}.agent-templates-migration.lock`);
370
+ const lockReleasePath = `${lockPath}.release-${randomUUID()}`;
371
+ for (const path of [stagingPath, backupPath, lockPath, lockReleasePath])
372
+ if (existsSync(path))
373
+ throw new Error(`migration refuses existing staging, backup, or lock path: ${path}`);
374
+ const result = {
375
+ write: options.write === true, root, stagingPath, backupPath,
376
+ moves: starters.map(({ path, name }) => ({ from: path, to: join(templateDir, `${name}.yaml`) })),
377
+ additions: [coordinator],
378
+ };
379
+ if (!options.write)
380
+ return result;
381
+ const lockFd = openSync(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
382
+ const lockToken = `${process.pid}:${randomUUID()}\n`;
383
+ try {
384
+ writeFileSync(lockFd, lockToken);
385
+ fsyncSync(lockFd);
386
+ // The lock closes the race, but preflight occurred before acquiring it so repeat every safety assertion.
387
+ for (const path of [stagingPath, backupPath])
388
+ if (existsSync(path))
389
+ throw new Error(`migration refuses existing staging or backup path: ${path}`);
390
+ assertPrivateTree(root);
391
+ verifyStarters(root);
392
+ copyTree(root, stagingPath);
393
+ const stagedTemplates = join(stagingPath, 'agent_templates');
394
+ if (!existsSync(stagedTemplates))
395
+ mkdirSync(stagedTemplates, { mode: 0o700 });
396
+ for (const { name } of starters)
397
+ renameSync(join(stagingPath, 'agents', `${name}.yaml`), join(stagedTemplates, `${name}.yaml`));
398
+ copyFileSync(join(packagedPresetRoot(), 'fleet', 'agents', 'FleetCoordinator.yaml'), join(stagingPath, 'agents', 'FleetCoordinator.yaml'), constants.COPYFILE_EXCL);
399
+ chmodSync(join(stagingPath, 'agents', 'FleetCoordinator.yaml'), 0o600);
400
+ fsyncPath(join(stagingPath, 'agents', 'FleetCoordinator.yaml'));
401
+ for (const path of [stagedTemplates, join(stagingPath, 'agents'), stagingPath])
402
+ fsyncPath(path);
403
+ assertPrivateTree(stagingPath);
404
+ verifyStarters(root); // immediate fingerprint revalidation before publication
405
+ const rename = hooks.rename ?? renameSync;
406
+ try {
407
+ rename(root, backupPath);
408
+ }
409
+ catch (error) {
410
+ throw new Error(`migration publication failed; staged recovery tree retained at ${stagingPath}: ${error.message}`, { cause: error });
411
+ }
412
+ fsyncPath(parent);
413
+ try {
414
+ rename(stagingPath, root);
415
+ fsyncPath(parent);
416
+ }
417
+ catch (publishError) {
418
+ try {
419
+ rename(backupPath, root);
420
+ fsyncPath(parent);
421
+ }
422
+ catch (rollbackError) {
423
+ throw new Error(`migration publication failed and rollback failed; manually recover ${backupPath} to ${root}; staged recovery tree is ${stagingPath}: ${rollbackError.message}`, { cause: publishError });
424
+ }
425
+ throw new Error(`migration publication failed; original restored and staged recovery tree retained at ${stagingPath}: ${publishError.message}`, { cause: publishError });
426
+ }
427
+ return result;
428
+ }
429
+ finally {
430
+ const held = fstatSync(lockFd);
431
+ closeSync(lockFd);
432
+ if (existsSync(lockPath)) {
433
+ const current = lstatSync(lockPath);
434
+ if (current.dev !== held.dev || current.ino !== held.ino || readFileSync(lockPath, 'utf8') !== lockToken)
435
+ throw new Error(`migration lock ownership changed; refusing to remove foreign lock: ${lockPath}`);
436
+ hooks.beforeLockClaim?.();
437
+ renameSync(lockPath, lockReleasePath);
438
+ const claimed = lstatSync(lockReleasePath);
439
+ if (claimed.dev !== held.dev || claimed.ino !== held.ino || readFileSync(lockReleasePath, 'utf8') !== lockToken) {
440
+ if (!existsSync(lockPath))
441
+ renameSync(lockReleasePath, lockPath);
442
+ throw new Error(`migration lock changed during atomic release; foreign lock preserved at ${existsSync(lockReleasePath) ? lockReleasePath : lockPath}`);
443
+ }
444
+ unlinkSync(lockReleasePath);
445
+ fsyncPath(parent);
446
+ }
447
+ else
448
+ throw new Error(`migration lock vanished before release: ${lockPath}`);
449
+ }
450
+ }
@@ -0,0 +1,10 @@
1
+ import type { ReportArtifact } from './types.js';
2
+ export interface WriteArtifactOptions {
3
+ output: string;
4
+ overwrite?: boolean;
5
+ }
6
+ /** Atomic file delivery. Existing files are preserved unless overwrite is explicit. */
7
+ export declare function writeReportArtifact(artifact: ReportArtifact, options: WriteArtifactOptions): Promise<{
8
+ path: string;
9
+ overwritten: boolean;
10
+ }>;
@@ -0,0 +1,32 @@
1
+ import { link, lstat, open, rename, unlink } from 'node:fs/promises';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ /** Atomic file delivery. Existing files are preserved unless overwrite is explicit. */
5
+ export async function writeReportArtifact(artifact, options) {
6
+ const target = resolve(options.output);
7
+ if (!target.toLowerCase().endsWith('.html'))
8
+ throw new Error('report output must use the .html extension');
9
+ const temporary = join(dirname(target), `.${basename(target)}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
10
+ let handle;
11
+ let existed = false;
12
+ try {
13
+ handle = await open(temporary, 'wx', 0o600);
14
+ await handle.writeFile(artifact.html, 'utf8');
15
+ await handle.sync();
16
+ await handle.close();
17
+ handle = undefined;
18
+ if (!options.overwrite) {
19
+ await link(temporary, target);
20
+ await unlink(temporary);
21
+ return { path: target, overwritten: false };
22
+ }
23
+ existed = await lstat(target).then(() => true, () => false);
24
+ await rename(temporary, target);
25
+ return { path: target, overwritten: existed };
26
+ }
27
+ catch (error) {
28
+ await handle?.close().catch(() => { });
29
+ await unlink(temporary).catch(() => { });
30
+ throw error;
31
+ }
32
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './render.js';
3
+ export * from './artifact.js';
4
+ export * from './service.js';
5
+ export * from './tasks.js';
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './render.js';
3
+ export * from './artifact.js';
4
+ export * from './service.js';
5
+ export * from './tasks.js';
@@ -0,0 +1,6 @@
1
+ import type { ReportArtifact, ReportViewModel } from './types.js';
2
+ export declare const escapeReportHtml: (value: unknown) => string;
3
+ export declare function reportAnchor(prefix: string, raw: string): string;
4
+ export declare function reportCompare(a: string, b: string): number;
5
+ export declare function renderReportHtml(model: ReportViewModel): string;
6
+ export declare function createReportArtifact(model: ReportViewModel, resourceId?: string): ReportArtifact;