@ours.network/fleet 1.2.0-nightly.35 → 1.2.0-nightly.37

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 (174) hide show
  1. package/README.md +28 -49
  2. package/dist/build-info.json +4 -4
  3. package/dist/cli.js +6 -38
  4. package/dist/docs.d.ts +1 -1
  5. package/dist/docs.js +10 -15
  6. package/dist/harness/claude-code.js +9 -1
  7. package/dist/harness/claude-runtime.d.ts +12 -0
  8. package/dist/harness/claude-runtime.js +43 -0
  9. package/dist/init-wizard.d.ts +2 -0
  10. package/dist/init-wizard.js +16 -9
  11. package/dist/preset-migration.js +10 -0
  12. package/dist/session/acp.d.ts +4 -1
  13. package/dist/session/acp.js +7 -6
  14. package/dist/session/codex-app-server.js +1 -2
  15. package/dist/web/auth.d.ts +1 -1
  16. package/dist/web/auth.js +2 -5
  17. package/dist/web/control.d.ts +1 -2
  18. package/dist/web/control.js +0 -2
  19. package/dist/web/prefix-gateway.js +2 -7
  20. package/dist/web/runtime.d.ts +0 -2
  21. package/dist/web/runtime.js +1 -18
  22. package/dist/web/server.d.ts +0 -1
  23. package/dist/web/server.js +12 -35
  24. package/package.json +2 -3
  25. package/presets/fleet/roles/Coordinator.yaml +12 -17
  26. package/presets/manifest.json +1 -1
  27. package/dist/web-app/assets/FleetApp-D58F4xje.css +0 -1
  28. package/dist/web-app/assets/FleetApp-Gjz3h8uS.js +0 -302
  29. package/dist/web-app/assets/FleetApp-Gjz3h8uS.js.map +0 -1
  30. package/dist/web-app/assets/abnfDiagram-VCTEODGH-t4h5V8Cs.js +0 -2
  31. package/dist/web-app/assets/abnfDiagram-VCTEODGH-t4h5V8Cs.js.map +0 -1
  32. package/dist/web-app/assets/arc-DbufDTF6.js +0 -2
  33. package/dist/web-app/assets/arc-DbufDTF6.js.map +0 -1
  34. package/dist/web-app/assets/architectureDiagram-5GKGNRK7-D6K8jMN5.js +0 -37
  35. package/dist/web-app/assets/architectureDiagram-5GKGNRK7-D6K8jMN5.js.map +0 -1
  36. package/dist/web-app/assets/blockDiagram-I7D4REHJ-CWvkET2x.js +0 -130
  37. package/dist/web-app/assets/blockDiagram-I7D4REHJ-CWvkET2x.js.map +0 -1
  38. package/dist/web-app/assets/browser-B7y08DsC.js +0 -9
  39. package/dist/web-app/assets/browser-B7y08DsC.js.map +0 -1
  40. package/dist/web-app/assets/c4Diagram-7LVT6UL2-RNWA7VnD.js +0 -39
  41. package/dist/web-app/assets/c4Diagram-7LVT6UL2-RNWA7VnD.js.map +0 -1
  42. package/dist/web-app/assets/channel-BF3U9sI8.js +0 -2
  43. package/dist/web-app/assets/channel-BF3U9sI8.js.map +0 -1
  44. package/dist/web-app/assets/chunk-2Q5K7J3B-pdEuY4vj.js +0 -2
  45. package/dist/web-app/assets/chunk-2Q5K7J3B-pdEuY4vj.js.map +0 -1
  46. package/dist/web-app/assets/chunk-5VM5RSS4-DuAUtdUC.js +0 -16
  47. package/dist/web-app/assets/chunk-5VM5RSS4-DuAUtdUC.js.map +0 -1
  48. package/dist/web-app/assets/chunk-F27PBJKO-6kpb80Xt.js +0 -2
  49. package/dist/web-app/assets/chunk-F27PBJKO-6kpb80Xt.js.map +0 -1
  50. package/dist/web-app/assets/chunk-IMKFNOWR-DWC84P6A.js +0 -232
  51. package/dist/web-app/assets/chunk-IMKFNOWR-DWC84P6A.js.map +0 -1
  52. package/dist/web-app/assets/chunk-JWPE2WC7-FHO-M3vi.js +0 -2
  53. package/dist/web-app/assets/chunk-JWPE2WC7-FHO-M3vi.js.map +0 -1
  54. package/dist/web-app/assets/chunk-POPQ4Y6H-CjnlKvmR.js +0 -2
  55. package/dist/web-app/assets/chunk-POPQ4Y6H-CjnlKvmR.js.map +0 -1
  56. package/dist/web-app/assets/chunk-SVP7TREG-WHYWJ6xq.js +0 -89
  57. package/dist/web-app/assets/chunk-SVP7TREG-WHYWJ6xq.js.map +0 -1
  58. package/dist/web-app/assets/chunk-TICWLB2K-POupMpyL.js +0 -207
  59. package/dist/web-app/assets/chunk-TICWLB2K-POupMpyL.js.map +0 -1
  60. package/dist/web-app/assets/chunk-XXDRQBXY-Cw5HHqH-.js +0 -2
  61. package/dist/web-app/assets/chunk-XXDRQBXY-Cw5HHqH-.js.map +0 -1
  62. package/dist/web-app/assets/classDiagram-ZZMXUADV-1hdMzIme.js +0 -2
  63. package/dist/web-app/assets/classDiagram-ZZMXUADV-1hdMzIme.js.map +0 -1
  64. package/dist/web-app/assets/classDiagram-v2-VYDZK3BY-1hdMzIme.js +0 -2
  65. package/dist/web-app/assets/classDiagram-v2-VYDZK3BY-1hdMzIme.js.map +0 -1
  66. package/dist/web-app/assets/cose-bilkent-JH36ORCC-Cp6Hjine.js +0 -2
  67. package/dist/web-app/assets/cose-bilkent-JH36ORCC-Cp6Hjine.js.map +0 -1
  68. package/dist/web-app/assets/cynefin-OW5HDTMX-DI-DeNUk.js +0 -179
  69. package/dist/web-app/assets/cynefin-OW5HDTMX-DI-DeNUk.js.map +0 -1
  70. package/dist/web-app/assets/cynefinDiagram-5FMLGOSQ-todBCANI.js +0 -63
  71. package/dist/web-app/assets/cynefinDiagram-5FMLGOSQ-todBCANI.js.map +0 -1
  72. package/dist/web-app/assets/cytoscape.esm-CtNdOvlO.js +0 -332
  73. package/dist/web-app/assets/cytoscape.esm-CtNdOvlO.js.map +0 -1
  74. package/dist/web-app/assets/dagre-GXQ25YYZ-BvIVo7eh.js +0 -5
  75. package/dist/web-app/assets/dagre-GXQ25YYZ-BvIVo7eh.js.map +0 -1
  76. package/dist/web-app/assets/defaultLocale-DX6XiGOO.js +0 -2
  77. package/dist/web-app/assets/defaultLocale-DX6XiGOO.js.map +0 -1
  78. package/dist/web-app/assets/diagram-S7CK7UJ4-k9vDGlB5.js +0 -31
  79. package/dist/web-app/assets/diagram-S7CK7UJ4-k9vDGlB5.js.map +0 -1
  80. package/dist/web-app/assets/diagram-UQ7AKVKN-Bv7BT1dE.js +0 -42
  81. package/dist/web-app/assets/diagram-UQ7AKVKN-Bv7BT1dE.js.map +0 -1
  82. package/dist/web-app/assets/diagram-VSXAHHWV-m9I9AumX.js +0 -4
  83. package/dist/web-app/assets/diagram-VSXAHHWV-m9I9AumX.js.map +0 -1
  84. package/dist/web-app/assets/diagram-VX7I27RA-4VJ2qozS.js +0 -25
  85. package/dist/web-app/assets/diagram-VX7I27RA-4VJ2qozS.js.map +0 -1
  86. package/dist/web-app/assets/diagram-Z3DM3KII-CvH8JIEd.js +0 -25
  87. package/dist/web-app/assets/diagram-Z3DM3KII-CvH8JIEd.js.map +0 -1
  88. package/dist/web-app/assets/ebnfDiagram-PWID7BFC-DAGs23hd.js +0 -2
  89. package/dist/web-app/assets/ebnfDiagram-PWID7BFC-DAGs23hd.js.map +0 -1
  90. package/dist/web-app/assets/erDiagram-RLTQ6QDP-CEMyWKIa.js +0 -100
  91. package/dist/web-app/assets/erDiagram-RLTQ6QDP-CEMyWKIa.js.map +0 -1
  92. package/dist/web-app/assets/flowDiagram-HODETNUW-BnraxJTV.js +0 -169
  93. package/dist/web-app/assets/flowDiagram-HODETNUW-BnraxJTV.js.map +0 -1
  94. package/dist/web-app/assets/ganttDiagram-EL5Y4UJY-V6d4ICW0.js +0 -293
  95. package/dist/web-app/assets/ganttDiagram-EL5Y4UJY-V6d4ICW0.js.map +0 -1
  96. package/dist/web-app/assets/gitGraphDiagram-WWUBYQGX-Bj_gqcba.js +0 -107
  97. package/dist/web-app/assets/gitGraphDiagram-WWUBYQGX-Bj_gqcba.js.map +0 -1
  98. package/dist/web-app/assets/index-C4BdeFB9.css +0 -1
  99. package/dist/web-app/assets/index-i9rKiqYX.js +0 -430
  100. package/dist/web-app/assets/index-i9rKiqYX.js.map +0 -1
  101. package/dist/web-app/assets/infoDiagram-27XIBGKW-Dfm89NLl.js +0 -3
  102. package/dist/web-app/assets/infoDiagram-27XIBGKW-Dfm89NLl.js.map +0 -1
  103. package/dist/web-app/assets/init-Gi6I4Gst.js +0 -2
  104. package/dist/web-app/assets/init-Gi6I4Gst.js.map +0 -1
  105. package/dist/web-app/assets/ishikawaDiagram-5VMMS53U-D-ISiJTS.js +0 -71
  106. package/dist/web-app/assets/ishikawaDiagram-5VMMS53U-D-ISiJTS.js.map +0 -1
  107. package/dist/web-app/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  108. package/dist/web-app/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  109. package/dist/web-app/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  110. package/dist/web-app/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  111. package/dist/web-app/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  112. package/dist/web-app/assets/journeyDiagram-3NMN7TZE-TTySrzws.js +0 -140
  113. package/dist/web-app/assets/journeyDiagram-3NMN7TZE-TTySrzws.js.map +0 -1
  114. package/dist/web-app/assets/kanban-definition-UXKFOSKX-BxntsBft.js +0 -90
  115. package/dist/web-app/assets/kanban-definition-UXKFOSKX-BxntsBft.js.map +0 -1
  116. package/dist/web-app/assets/katex-HP8lGamR.js +0 -258
  117. package/dist/web-app/assets/katex-HP8lGamR.js.map +0 -1
  118. package/dist/web-app/assets/layout-0sZak8Ho.js +0 -2
  119. package/dist/web-app/assets/layout-0sZak8Ho.js.map +0 -1
  120. package/dist/web-app/assets/linear-D4xk4kUi.js +0 -2
  121. package/dist/web-app/assets/linear-D4xk4kUi.js.map +0 -1
  122. package/dist/web-app/assets/mermaid.core-CNKolAgz.js +0 -313
  123. package/dist/web-app/assets/mermaid.core-CNKolAgz.js.map +0 -1
  124. package/dist/web-app/assets/mindmap-definition-YA3MSWOX-DLJijY_l.js +0 -97
  125. package/dist/web-app/assets/mindmap-definition-YA3MSWOX-DLJijY_l.js.map +0 -1
  126. package/dist/web-app/assets/ordinal-Cboi1Yqb.js +0 -2
  127. package/dist/web-app/assets/ordinal-Cboi1Yqb.js.map +0 -1
  128. package/dist/web-app/assets/pegDiagram-XKGWAZYB-xjrh9rBd.js +0 -2
  129. package/dist/web-app/assets/pegDiagram-XKGWAZYB-xjrh9rBd.js.map +0 -1
  130. package/dist/web-app/assets/pieDiagram-E7YTZNPT-CHE8ZMXN.js +0 -40
  131. package/dist/web-app/assets/pieDiagram-E7YTZNPT-CHE8ZMXN.js.map +0 -1
  132. package/dist/web-app/assets/quadrantDiagram-AXDQQJYC-CDlEe9sg.js +0 -8
  133. package/dist/web-app/assets/quadrantDiagram-AXDQQJYC-CDlEe9sg.js.map +0 -1
  134. package/dist/web-app/assets/railroadDiagram-O6MQD6OU-Dv7lFZ_v.js +0 -2
  135. package/dist/web-app/assets/railroadDiagram-O6MQD6OU-Dv7lFZ_v.js.map +0 -1
  136. package/dist/web-app/assets/requirementDiagram-BXWQKSXE-BetEel-k.js +0 -85
  137. package/dist/web-app/assets/requirementDiagram-BXWQKSXE-BetEel-k.js.map +0 -1
  138. package/dist/web-app/assets/sankeyDiagram-P5KCCOFB-DCQgLfvK.js +0 -41
  139. package/dist/web-app/assets/sankeyDiagram-P5KCCOFB-DCQgLfvK.js.map +0 -1
  140. package/dist/web-app/assets/sequenceDiagram-WJ2MYXX4-CsGOoqu_.js +0 -163
  141. package/dist/web-app/assets/sequenceDiagram-WJ2MYXX4-CsGOoqu_.js.map +0 -1
  142. package/dist/web-app/assets/sizeCapture-INFHLROL-CLQRMiEc.js +0 -2
  143. package/dist/web-app/assets/sizeCapture-INFHLROL-CLQRMiEc.js.map +0 -1
  144. package/dist/web-app/assets/stateDiagram-D77RDMKH-D2MRdC5Q.js +0 -2
  145. package/dist/web-app/assets/stateDiagram-D77RDMKH-D2MRdC5Q.js.map +0 -1
  146. package/dist/web-app/assets/stateDiagram-v2-MP3YSRHH-CToAT5fD.js +0 -2
  147. package/dist/web-app/assets/stateDiagram-v2-MP3YSRHH-CToAT5fD.js.map +0 -1
  148. package/dist/web-app/assets/swimlanes-42K2YHIH-BGjHfR8x.js +0 -2
  149. package/dist/web-app/assets/swimlanes-42K2YHIH-BGjHfR8x.js.map +0 -1
  150. package/dist/web-app/assets/swimlanesDiagram-VR7AAH4N-bLHg88Vk.js +0 -9
  151. package/dist/web-app/assets/swimlanesDiagram-VR7AAH4N-bLHg88Vk.js.map +0 -1
  152. package/dist/web-app/assets/timeline-definition-24CTP7MA-DhGtHQ0p.js +0 -121
  153. package/dist/web-app/assets/timeline-definition-24CTP7MA-DhGtHQ0p.js.map +0 -1
  154. package/dist/web-app/assets/vennDiagram-4TSXK5OY-Dv5irf00.js +0 -35
  155. package/dist/web-app/assets/vennDiagram-4TSXK5OY-Dv5irf00.js.map +0 -1
  156. package/dist/web-app/assets/wardleyDiagram-VM6X3IG4-JGiUTj5x.js +0 -79
  157. package/dist/web-app/assets/wardleyDiagram-VM6X3IG4-JGiUTj5x.js.map +0 -1
  158. package/dist/web-app/assets/xychartDiagram-S5SC5T6Z-Dw5ev7_Q.js +0 -8
  159. package/dist/web-app/assets/xychartDiagram-S5SC5T6Z-Dw5ev7_Q.js.map +0 -1
  160. package/dist/web-app/fleet-index.html +0 -19
  161. package/dist/web-app/fleet.webmanifest +0 -37
  162. package/dist/web-app/icons/apple-touch-icon.png +0 -0
  163. package/dist/web-app/icons/favicon-16.png +0 -0
  164. package/dist/web-app/icons/favicon-32.png +0 -0
  165. package/dist/web-app/icons/icon-192-maskable.png +0 -0
  166. package/dist/web-app/icons/icon-192.png +0 -0
  167. package/dist/web-app/icons/icon-512-maskable.png +0 -0
  168. package/dist/web-app/icons/icon-512.png +0 -0
  169. package/dist/web-app/index.html +0 -19
  170. package/dist/web-app/manifest.webmanifest +0 -17
  171. package/dist/web-app/sw.js +0 -221
  172. package/dist/web-app/version.json +0 -1
  173. package/dist/web-app/workspace-install.sh +0 -148
  174. package/dist/web-app/workspace-tunnel.sh +0 -40
@@ -0,0 +1,43 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { codexVersionAtLeast, executableOnPath } from './codex-runtime.js';
6
+ const versionOf = (text) => text.match(/\b(\d+\.\d+\.\d+)\b/)?.[1];
7
+ function installedClaudeVersion(path, env) {
8
+ const result = spawnSync(path, ['--version'], { env, encoding: 'utf8', timeout: 5_000, stdio: ['ignore', 'pipe', 'ignore'] });
9
+ return result.status === 0 ? versionOf(result.stdout ?? '') : undefined;
10
+ }
11
+ /** The Claude Code version shipped inside the adapter's own SDK dependency. */
12
+ export function bundledClaudeVersion(adapterManifest) {
13
+ try {
14
+ const sdk = dirname(createRequire(adapterManifest).resolve('@anthropic-ai/claude-agent-sdk'));
15
+ const version = JSON.parse(readFileSync(join(sdk, 'package.json'), 'utf8')).claudeCodeVersion;
16
+ return typeof version === 'string' ? version : undefined;
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ /**
23
+ * The host's own Claude Code, when ACP sessions should run it instead of the copy bundled with the adapter.
24
+ * The person signs in to, updates and checks the host Claude Code, and a newer one knows models the bundled
25
+ * one does not. It is used only when it is at least as new as the bundled copy; an explicit
26
+ * CLAUDE_CODE_EXECUTABLE, in the role or the service environment, always decides instead.
27
+ */
28
+ export function hostClaude(adapterManifest, env, cwd = process.cwd(), version = installedClaudeVersion) {
29
+ if (env.CLAUDE_CODE_EXECUTABLE !== undefined || !adapterManifest)
30
+ return undefined;
31
+ const bundled = bundledClaudeVersion(adapterManifest);
32
+ if (!bundled)
33
+ return undefined;
34
+ let path;
35
+ try {
36
+ path = executableOnPath('claude', env, cwd);
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ const found = version(path, env);
42
+ return found && codexVersionAtLeast(found, bundled) ? { path, version: found } : undefined;
43
+ }
@@ -53,6 +53,8 @@ export declare function validateCatalog(value: BrainCatalog, path?: string): Bra
53
53
  export declare function readInitSettings(path: string): InitAnswers;
54
54
  export declare function askInitQuestions(prompter: InitPrompter, configuration: string): Promise<InitAnswers | undefined>;
55
55
  export declare function formatSetupSummary(answers: InitAnswers, configuration: string): string;
56
+ /** The packaged Brain preset for a catalog model at one effort; the same naming the presets are generated with. */
57
+ export declare function brainPresetId(model: Pick<CatalogModel, 'harness' | 'model'>, effort: string): string;
56
58
  /** Build the exact, deterministic default experience without writing to disk. */
57
59
  export declare function generateSetup(answers: InitAnswers): GeneratedSetup;
58
60
  /** Run the mutation boundary only after the complete questionnaire is accepted. */
@@ -173,6 +173,7 @@ export function formatSetupSummary(answers, configuration) {
173
173
  ' Team: LocalCoordinator + Developer + Critic',
174
174
  ' Gated engineering: Engineer (gate pipeline) + independent Critic',
175
175
  ' FleetCoordinator: coordination model',
176
+ ' Brains: the packaged presets; each prepared agent references the preset of its chosen model',
176
177
  '',
177
178
  'After the final Yes, Fleet performs host service setup before publishing the complete configuration.',
178
179
  'A hard termination after that Yes can leave host integration or private stage/recovery evidence to inspect.',
@@ -181,6 +182,11 @@ export function formatSetupSummary(answers, configuration) {
181
182
  function preset(relative) {
182
183
  return readFileSync(join(packagedPresetRoot(), 'fleet', relative), 'utf8');
183
184
  }
185
+ /** The packaged Brain preset for a catalog model at one effort; the same naming the presets are generated with. */
186
+ export function brainPresetId(model, effort) {
187
+ return `${model.harness === 'claude-code' ? 'claude' : 'codex'}-${model.model
188
+ .replace(/^claude-/, '').replace(/\./g, '-').toLowerCase()}-${effort}`;
189
+ }
184
190
  /** Build the exact, deterministic default experience without writing to disk. */
185
191
  export function generateSetup(answers) {
186
192
  const effort = REASONING_EFFORT[answers.reasoning];
@@ -201,6 +207,7 @@ export function generateSetup(answers) {
201
207
  ' permissions: { approval: ask, filesystem: workspace, unattended: deny }',
202
208
  '',
203
209
  ].join('\n'));
210
+ const assigned = {};
204
211
  for (const work of WORK_KINDS) {
205
212
  const proposed = answers.models[work];
206
213
  if (!proposed || !answers.subscriptions.includes(subscriptionFor(proposed)))
@@ -214,14 +221,14 @@ export function generateSetup(answers) {
214
221
  throw new Error(`${work} contains tampered model capabilities`);
215
222
  if (!model.efforts.includes(effort))
216
223
  throw new Error(`${MODEL_LABELS[model.model]} does not support ${answers.reasoning} reasoning`);
217
- files.set(`brains/${work}.yaml`, [
218
- `harness: ${model.harness}`,
219
- `session: ${model.session}`,
220
- `model: ${model.model}`,
221
- `effort: ${effort}`,
222
- '',
223
- ].join('\n'));
224
+ // The chosen model is assigned by reference to its packaged Brain preset; setup defines no Brain of its own.
225
+ const id = brainPresetId(model, effort);
226
+ if (!existsSync(join(packagedPresetRoot(), 'fleet', 'brains', `${id}.yaml`)))
227
+ throw new Error(`no packaged Brain preset for ${MODEL_LABELS[model.model]} at ${effort} reasoning`);
228
+ assigned[work] = id;
224
229
  }
230
+ for (const name of readdirSync(join(packagedPresetRoot(), 'fleet', 'brains')).filter(name => name.endsWith('.yaml')).sort())
231
+ files.set(`brains/${name}`, preset(`brains/${name}`));
225
232
  if (answers.assignmentStrategy === 'one-model') {
226
233
  const tuples = new Set(WORK_KINDS.map(work => {
227
234
  const model = answers.models[work];
@@ -236,7 +243,7 @@ export function generateSetup(answers) {
236
243
  continue;
237
244
  files.set(`agent_templates/${role}.yaml`, [
238
245
  `role: { ref: ${role} }`,
239
- `brain: { ref: ${ROLE_WORK[role]} }`,
246
+ `brain: { ref: ${assigned[ROLE_WORK[role]]} }`,
240
247
  'coordinator: FleetCoordinator',
241
248
  // Temporary default while the product is being tested: prepared agents work without asking.
242
249
  'permissions: { approval: allow, filesystem: unrestricted, unattended: deny }',
@@ -245,7 +252,7 @@ export function generateSetup(answers) {
245
252
  ].join('\n'));
246
253
  }
247
254
  files.set('agents/FleetCoordinator.yaml', preset('agents/FleetCoordinator.yaml')
248
- .replace('brain: { ref: claude-default }', 'brain: { ref: coordination }'));
255
+ .replace('brain: { ref: claude-default }', `brain: { ref: ${assigned.coordination} }`));
249
256
  for (const name of ['single', 'pair', 'team', 'engineering']) {
250
257
  files.set(`room_templates/${name}.yaml`, preset(`room_templates/${name}.yaml`));
251
258
  files.set(`room_layouts/${name}.yaml`, preset(`room_layouts/${name}.yaml`));
@@ -67,6 +67,14 @@ const V5_GENERATED_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
67
67
  'agent_templates/Developer.yaml': 'fdd953aab72d042e62780dd273598745aae59521b39edf4876d7676bb7189c83',
68
68
  'agent_templates/Critic.yaml': '3e98a28f17876061cfadf984351aa59fdbe19629e514eff52c74546545c64a91',
69
69
  });
70
+ // The packaged Coordinator role of revisions 8 and 9 addressed a named owner channel that a
71
+ // fresh installation does not have. Untouched copies adopt the current role; edited ones stay.
72
+ const OWNER_CHANNEL_COORDINATOR_FINGERPRINTS = Object.freeze({
73
+ 'roles/Coordinator.yaml': [
74
+ '8c8ad28d9bb9ddf9dba8c5c9c7859f25edd8fa10ebf833e2340124a6e49583ec',
75
+ 'f0d8b169ad0a18051b57e5280b93cf01e05faa5c12f4e12bdf89973edf2ef58a',
76
+ ],
77
+ });
70
78
  const CURRENT_ROLE_DEFAULTS = new Set([
71
79
  'roles/Coordinator.yaml', 'roles/LocalCoordinator.yaml', 'roles/Developer.yaml', 'roles/Critic.yaml',
72
80
  'roles/Engineer.yaml',
@@ -196,6 +204,7 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
196
204
  ...Object.keys(V3_ROLE_DEFAULT_FINGERPRINTS),
197
205
  ...Object.keys(V4_ROLE_DEFAULT_FINGERPRINTS),
198
206
  ...Object.keys(V5_ROLE_DEFAULT_FINGERPRINTS),
207
+ ...Object.keys(OWNER_CHANNEL_COORDINATOR_FINGERPRINTS),
199
208
  ]);
200
209
  for (const relative of known) {
201
210
  const path = join(root, relative);
@@ -209,6 +218,7 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
209
218
  V4_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
210
219
  V5_ROLE_DEFAULT_FINGERPRINTS[relative],
211
220
  V5_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
221
+ ...(OWNER_CHANNEL_COORDINATOR_FINGERPRINTS[relative] ?? []),
212
222
  ].some(expected => expected !== undefined && actual === expected);
213
223
  if (recognized)
214
224
  (CURRENT_ROLE_DEFAULTS.has(relative) ? replacements : removals).push(path);
@@ -339,7 +339,10 @@ export declare class AcpSession implements AgentSession {
339
339
  * signal. Never infer commentary from text, message order, or unknown meta.
340
340
  */
341
341
  private codexMessagePhase;
342
- /** Normalize every ACP update losslessly into the durable ledger. */
342
+ /**
343
+ * Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
344
+ * owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
345
+ */
343
346
  private recordConversationUpdate;
344
347
  taskNoticeBinding(): TaskNoticeBinding | undefined;
345
348
  recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
@@ -38,7 +38,6 @@ const TERMINAL_TOOL_STATUSES = new Set(['completed', 'failed']);
38
38
  /** Consumed only by Fleet's authenticated bundled-Codex app-server proxy. */
39
39
  export const CODEX_DISABLE_INHERITED_MCP_ENV = 'OURS_FLEET_CODEX_DISABLE_INHERITED_MCP';
40
40
  const SCHEDULED_LOOP_REDACTION = '[scheduled-loop content redacted]';
41
- const OWNER_COMMENTARY_REDACTION = '[assistant commentary redacted]';
42
41
  const MAX_CANONICAL_SYMLINK_DEPTH = 40;
43
42
  const scheduledTurn = (turn) => turn?.origin?.kind === 'scheduled-loop';
44
43
  /**
@@ -1711,7 +1710,7 @@ export class AcpSession {
1711
1710
  const scheduled = this.activeTurn?.origin?.kind === 'scheduled-loop';
1712
1711
  const messagePhase = update.sessionUpdate === 'agent_message_chunk'
1713
1712
  ? this.codexMessagePhase(update) : undefined;
1714
- this.recordConversationUpdate(update, scheduled, messagePhase === 'commentary');
1713
+ this.recordConversationUpdate(update, scheduled);
1715
1714
  if (update.sessionUpdate === 'config_option_update')
1716
1715
  this.captureRuntimeMetadata(update.configOptions);
1717
1716
  if (update.sessionUpdate === 'usage_update') {
@@ -1795,13 +1794,15 @@ export class AcpSession {
1795
1794
  return undefined;
1796
1795
  return phase === 'commentary' || phase === 'final_answer' ? phase : 'ambiguous';
1797
1796
  }
1798
- /** Normalize every ACP update losslessly into the durable ledger. */
1799
- recordConversationUpdate(update, scheduled, commentary = false) {
1797
+ /**
1798
+ * Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
1799
+ * owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
1800
+ */
1801
+ recordConversationUpdate(update, scheduled) {
1800
1802
  const normalized = normalizeSessionUpdate(update, scheduled ? {
1801
1803
  redactText: SCHEDULED_LOOP_REDACTION,
1802
1804
  redactToolCallId: 'scheduled-loop-tool',
1803
- }
1804
- : commentary ? { redactText: OWNER_COMMENTARY_REDACTION } : {});
1805
+ } : {});
1805
1806
  this.conversation.appendSafe({
1806
1807
  kind: normalized.kind,
1807
1808
  sessionGeneration: this.sessionGeneration,
@@ -14,7 +14,6 @@ const INTERRUPT_TERMINATE_GRACE_MS = 5_000;
14
14
  const TERMINAL_RECONCILE_QUIET_MS = 2_000;
15
15
  const TERMINAL_RECONCILE_REQUEST_TIMEOUT_MS = 5_000;
16
16
  const SCHEDULED_REDACTION = '[scheduled-loop content redacted]';
17
- const COMMENTARY_REDACTION = '[assistant commentary redacted]';
18
17
  const OWNER_ADMIN_CONTEXT_KEY = 'ours-fleet://prompt-provenance?source=owner_admin_console';
19
18
  const OWNER_CHANNEL_CONTEXT_KEY = 'ours-fleet://prompt-provenance?source=owner_channel';
20
19
  const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -719,7 +718,7 @@ export class CodexAppServerSession {
719
718
  promptId: this.activeTurn?.promptId, turnId: this.activeTurn?.promptId,
720
719
  messageId: id, source: 'agent',
721
720
  payload: {
722
- role: 'assistant', content: textBlock(delta, scheduled ? SCHEDULED_REDACTION : phase === 'commentary' ? COMMENTARY_REDACTION : undefined),
721
+ role: 'assistant', content: textBlock(delta, scheduled ? SCHEDULED_REDACTION : undefined),
723
722
  },
724
723
  });
725
724
  }
@@ -54,7 +54,7 @@ export declare class WebAuth {
54
54
  mintBootstrap(): string;
55
55
  /** The configured account origin, when this request comes from it and workspace devices are enabled. */
56
56
  accountOrigin(request: Pick<FastifyRequest, 'headers'>): string | undefined;
57
- validateBoundary(request: FastifyRequest, requireOrigin: boolean, allowPageNavigation?: boolean): void;
57
+ validateBoundary(request: FastifyRequest, requireOrigin: boolean): void;
58
58
  exchange(request: FastifyRequest): AuthResult;
59
59
  login(request: FastifyRequest, password: string): AuthResult;
60
60
  anonymous(request: FastifyRequest): BrowserSession;
package/dist/web/auth.js CHANGED
@@ -58,7 +58,7 @@ export class WebAuth {
58
58
  accountOrigin(request) {
59
59
  return this.workspaceDevices && request.headers.origin === this.appOrigin ? this.appOrigin : undefined;
60
60
  }
61
- validateBoundary(request, requireOrigin, allowPageNavigation = false) {
61
+ validateBoundary(request, requireOrigin) {
62
62
  const host = request.headers.host;
63
63
  const hosts = this.allowedHosts.size ? this.allowedHosts : new Set([this.host]);
64
64
  const origins = this.allowedOrigins.size ? this.allowedOrigins : new Set([this.origin]);
@@ -71,10 +71,7 @@ export class WebAuth {
71
71
  if (requireOrigin && !workspaceRequest && (!request.headers.origin || !origins.has(request.headers.origin)))
72
72
  throw new FleetError('forbidden', 'request Origin does not match the configured control-panel origin');
73
73
  const fetchSite = request.headers['sec-fetch-site'];
74
- const pageNavigation = allowPageNavigation && !requireOrigin && request.method === 'GET'
75
- && request.headers['sec-fetch-mode'] === 'navigate'
76
- && (request.headers['sec-fetch-dest'] === 'document' || (Boolean(this.workspaceDevices) && request.headers['sec-fetch-dest'] === 'iframe' && new URL(request.url || '/', this.origin).searchParams.get('workspace-frame') === '1'));
77
- if (!workspaceRequest && !pageNavigation && fetchSite && !['same-origin', 'none'].includes(String(fetchSite)))
74
+ if (!workspaceRequest && fetchSite && !['same-origin', 'none'].includes(String(fetchSite)))
78
75
  throw new FleetError('forbidden', 'cross-site request rejected');
79
76
  }
80
77
  exchange(request) {
@@ -1,4 +1,4 @@
1
- export type WebControlCommand = 'open' | 'revoke-all' | 'link-device';
1
+ export type WebControlCommand = 'revoke-all' | 'link-device';
2
2
  export declare const webControlPath: (dir?: string) => string;
3
3
  export interface WebControlServer {
4
4
  path: string;
@@ -6,7 +6,6 @@ export interface WebControlServer {
6
6
  }
7
7
  export declare function startWebControlServer(options: {
8
8
  dir?: string;
9
- onOpen(): void | Promise<void>;
10
9
  onLinkDevice?(): Record<string, unknown> | Promise<Record<string, unknown>>;
11
10
  onRevokeAll(): void | Promise<void>;
12
11
  now?: () => number;
@@ -38,8 +38,6 @@ export async function startWebControlServer(options) {
38
38
  let result;
39
39
  if (parsed.command === 'link-device' && options.onLinkDevice)
40
40
  result = await options.onLinkDevice();
41
- else if (parsed.command === 'open')
42
- await options.onOpen();
43
41
  else if (parsed.command === 'revoke-all')
44
42
  await options.onRevokeAll();
45
43
  else
@@ -27,7 +27,7 @@ export function createPrefixGateway(options) {
27
27
  const path = raw.split('?')[0];
28
28
  const service = services.find(x => path === x.prefix || path.startsWith(x.prefix + '/'));
29
29
  const fleet = path === '/fleet/api' || path.startsWith('/fleet/api/');
30
- return { service, page: !service && !fleet && !path.startsWith('/api/'), origin: service?.origin ?? options.fleetOrigin,
30
+ return { service, origin: service?.origin ?? options.fleetOrigin,
31
31
  path: service ? (service.upstreamPrefix || '') + (raw.slice(service.prefix.length) || '/').replace(/^\?/, '/?') : fleet ? raw.slice('/fleet'.length) : raw };
32
32
  };
33
33
  const prepare = (req, upgrade = false) => {
@@ -35,7 +35,7 @@ export function createPrefixGateway(options) {
35
35
  const request = { headers: req.headers, method: req.method, url: route.path };
36
36
  if (!auth && (req.headers.origin || req.headers['sec-fetch-site'] || !route.service))
37
37
  throw new Error('machine-client listener rejects browser requests');
38
- auth?.validateBoundary(request, upgrade, route.page);
38
+ auth?.validateBoundary(request, upgrade);
39
39
  const session = route.service && auth ? auth.authenticate(request, !upgrade && !['GET', 'HEAD', 'OPTIONS'].includes(req.method ?? 'GET')) : undefined;
40
40
  const headers = headersFor(req);
41
41
  // Forward only the explicit browser context, never an attacker-selected forwarding chain.
@@ -100,11 +100,6 @@ export function createPrefixGateway(options) {
100
100
  res.end(JSON.stringify({ error: { code: code ?? 'forbidden', message: error.message } }));
101
101
  return;
102
102
  }
103
- if (req.url === '/') {
104
- res.writeHead(302, { location: '/fleet' });
105
- res.end();
106
- return;
107
- }
108
103
  const upstream = (route.origin.startsWith('https:') ? httpsRequest : httpRequest)(route.origin, {
109
104
  method: req.method, path: route.path, headers: route.headers,
110
105
  }, response => {
@@ -5,9 +5,7 @@ export interface StartWebOptions {
5
5
  /** Separate console state for an independently launched gateway. */
6
6
  webStateDir?: string;
7
7
  control?: boolean;
8
- staticRoot?: string;
9
8
  port?: number;
10
- open?: boolean;
11
9
  binPath: string;
12
10
  log?(line: string): void;
13
11
  bind?: string;
@@ -6,7 +6,6 @@ import { readClientProfile } from '../client-profile.js';
6
6
  import { readPrivateFile } from '@ours.network/sdk/connector';
7
7
  import { LayoutBindingGrants } from '../rooms-tasks/layout-binding-grants.js';
8
8
  import { RoomLayoutService } from '../rooms-tasks/layout-service.js';
9
- import { spawn } from 'node:child_process';
10
9
  import { FleetNotificationProducer } from '../notifications/fleet-producer.js';
11
10
  import { producerConfig } from '../notifications/outbox.js';
12
11
  import { SubscriptionService } from '../subscriptions/service.js';
@@ -223,7 +222,7 @@ export async function startWebConsole(options) {
223
222
  }
224
223
  throw new FleetError('capability_unavailable', 'role session backend is unavailable');
225
224
  },
226
- }, { origin: `http://127.0.0.1:${requestedPort}`, host: `127.0.0.1:${requestedPort}` }, { auth, staticRoot: options.staticRoot });
225
+ }, { origin: `http://127.0.0.1:${requestedPort}`, host: `127.0.0.1:${requestedPort}` }, { auth });
227
226
  }
228
227
  catch (error) {
229
228
  lock.release();
@@ -284,11 +283,6 @@ export async function startWebConsole(options) {
284
283
  if (options.control !== false)
285
284
  control = await startWebControlServer({
286
285
  dir: webDir,
287
- onOpen() {
288
- const url = access.mode === 'pairing'
289
- ? `${browserOrigin}/#bootstrap=${server.auth.mintBootstrap()}` : `${browserOrigin}/`;
290
- openBrowser(url);
291
- },
292
286
  onLinkDevice() { return { version: 1, origin: browserOrigin, ...server.auth.workspaceEnrollment() }; },
293
287
  onRevokeAll() { server.auth.revokeAllTrustedDevices(); },
294
288
  });
@@ -299,9 +293,6 @@ export async function startWebConsole(options) {
299
293
  lock.release();
300
294
  throw error;
301
295
  }
302
- if (options.open !== false)
303
- openBrowser(access.mode === 'pairing'
304
- ? `${browserOrigin}/#bootstrap=${server.auth.bootstrapSecret}` : `${browserOrigin}/`);
305
296
  let stopTunnel;
306
297
  try {
307
298
  stopTunnel = startWorkspaceTunnel(browserOrigin);
@@ -337,11 +328,3 @@ export async function startWebConsole(options) {
337
328
  function isLoopback(host) {
338
329
  return ['127.0.0.1', 'localhost', '::1'].includes(host);
339
330
  }
340
- function openBrowser(url) {
341
- const command = process.platform === 'darwin' ? 'open'
342
- : process.platform === 'win32' ? 'cmd' : 'xdg-open';
343
- const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
344
- const child = spawn(command, args, { stdio: 'ignore', detached: true, shell: false });
345
- child.on('error', () => undefined);
346
- child.unref();
347
- }
@@ -60,5 +60,4 @@ export declare function buildWebServer(services: WebServices, boundary: {
60
60
  host: string;
61
61
  }, options?: {
62
62
  auth?: WebAuth;
63
- staticRoot?: string;
64
63
  }): Promise<WebServer>;
@@ -6,11 +6,8 @@ import { storeAgentAttachment, attachmentPrompt, MAX_ATTACHMENT_BODY, readAgentA
6
6
  import { readChatIdle } from '../temp-idle.js';
7
7
  import { agentDir } from '../paths.js';
8
8
  import { SupervisorOursTools } from '../application/supervisor-ours-tools.js';
9
- import { existsSync } from 'node:fs';
10
- import { dirname, join } from 'node:path';
11
- import { fileURLToPath } from 'node:url';
9
+ import { join } from 'node:path';
12
10
  import Fastify from 'fastify';
13
- import fastifyStatic from '@fastify/static';
14
11
  import fastifyWebsocket from '@fastify/websocket';
15
12
  import { createHmac, randomBytes } from 'node:crypto';
16
13
  import { FleetError, normalizeError } from '../application/errors.js';
@@ -44,10 +41,8 @@ export async function buildWebServer(services, boundary, options = {}) {
44
41
  reply.header('X-Content-Type-Options', 'nosniff');
45
42
  reply.header('Referrer-Policy', 'no-referrer');
46
43
  reply.header('Permissions-Policy', 'camera=(), microphone=(self), geolocation=(), payment=()');
47
- // Only the configured account launcher may embed workspace pages.
48
- reply.header('Content-Security-Policy', `default-src 'self'; script-src 'self'; style-src 'self'; style-src-attr 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; frame-src 'self' blob:; ` +
49
- `connect-src 'self' ${auth.secureCookies ? 'wss' : 'ws'}://${auth.host}; object-src 'none'; base-uri 'none'; ` +
50
- `frame-ancestors 'self' ${auth.appOrigin}; form-action 'self'; manifest-src 'self'; worker-src 'self'`);
44
+ // Fleet serves an API only: nothing it returns may run, load or be framed as a page.
45
+ reply.header('Content-Security-Policy', "default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'");
51
46
  if (request.url.startsWith('/api/'))
52
47
  reply.header('Cache-Control', 'no-store');
53
48
  if (request.headers.origin === auth.appOrigin && request.url.startsWith('/api/')) {
@@ -59,15 +54,7 @@ export async function buildWebServer(services, boundary, options = {}) {
59
54
  return reply.code(204).send();
60
55
  }
61
56
  }
62
- try {
63
- auth.validateBoundary(request, false, !request.url.startsWith('/api/'));
64
- }
65
- catch (error) {
66
- if (request.url.startsWith('/api/'))
67
- throw error;
68
- const message = normalizeError(error).message;
69
- return reply.code(421).type('text/html').send(`<!doctype html><html><head><title>Fleet console address</title></head><body><main><h1>This fleet-console address is not configured</h1><p>${escapeHtml(message)}</p><p>For nginx or VPS access, configure an explicit public origin. The console does not guess proxy hosts.</p></main></body></html>`);
70
- }
57
+ auth.validateBoundary(request, false);
71
58
  });
72
59
  app.post('/api/v1/layout-bindings/:id/control', async (request) => {
73
60
  // Initial support is direct same-host Fleet-to-Fleet transport.
@@ -215,9 +202,13 @@ export async function buildWebServer(services, boundary, options = {}) {
215
202
  throw new FleetError('backend_failure', `Fleet setup did not complete: ${error.message}`);
216
203
  }
217
204
  // Setup preserves files it finds, so confirm the published models are the ones this request chose.
218
- const brains = configuration.read(true).model.brains ?? {};
205
+ // The prepared agents reference packaged Brain presets, so resolve what each of them now points at:
206
+ // a preserved file of a packaged name may hold another model, session or reasoning than its name says.
207
+ const published = configuration.read(true).model, brains = published.brains ?? {};
208
+ const brainOf = (holder) => brains[String(holder?.brain?.ref ?? '')];
209
+ const assigned = { coordination: brainOf(published.agents.FleetCoordinator), development: brainOf(published.agent_templates?.Developer), review: brainOf(published.agent_templates?.Critic) };
219
210
  for (const work of ONBOARDING_WORK)
220
- if (brains[work]?.harness !== models[work].harness || brains[work]?.model !== models[work].model)
211
+ if (assigned[work]?.harness !== models[work].harness || assigned[work]?.model !== models[work].model || assigned[work]?.session !== models[work].session || assigned[work]?.effort !== 'medium')
221
212
  throw new FleetError('conflict', 'Fleet was set up with different models; review its configuration');
222
213
  return { configured: true };
223
214
  });
@@ -1028,19 +1019,8 @@ export async function buildWebServer(services, boundary, options = {}) {
1028
1019
  socket.on('close', () => { clearInterval(heartbeat); follow.close(); });
1029
1020
  });
1030
1021
  });
1031
- const staticRoot = options.staticRoot ?? join(dirname(fileURLToPath(import.meta.url)), '..', 'web-app');
1032
- if (existsSync(staticRoot)) {
1033
- await app.register(fastifyStatic, { root: staticRoot, prefix: '/' });
1034
- app.setNotFoundHandler((request, reply) => {
1035
- if (request.url.startsWith('/api/'))
1036
- return reply.code(404).send({ error: 'not found' });
1037
- const path = request.url.split('?')[0];
1038
- if ((path === '/chats' || path.startsWith('/chats/')) && existsSync(join(staticRoot, 'fleet-index.html')))
1039
- return reply.redirect((request.url.includes('?') ? '/fleet/chats' : '/fleet') + (request.url.includes('?') ? request.url.slice(request.url.indexOf('?')) : ''));
1040
- const fleetEntry = (path === '/fleet' || path.startsWith('/fleet/')) && existsSync(join(staticRoot, 'fleet-index.html'));
1041
- return reply.sendFile(fleetEntry ? 'fleet-index.html' : 'index.html');
1042
- });
1043
- }
1022
+ // Fleet has no pages of its own; every screen belongs to the App.
1023
+ app.setNotFoundHandler((_request, reply) => reply.code(404).send({ error: 'not found' }));
1044
1024
  return {
1045
1025
  app, auth, audit, events,
1046
1026
  async close() {
@@ -1077,9 +1057,6 @@ function promoteRequest(body) {
1077
1057
  };
1078
1058
  }
1079
1059
  function cryptoRandomId() { return randomBytes(12).toString('hex'); }
1080
- function escapeHtml(value) {
1081
- return value.replace(/[&<>"']/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
1082
- }
1083
1060
  function requireSubprotocol(request, expected) {
1084
1061
  const protocols = String(request.headers['sec-websocket-protocol'] ?? '')
1085
1062
  .split(',').map(value => value.trim());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.2.0-nightly.35",
3
+ "version": "1.2.0-nightly.37",
4
4
  "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "scripts": {
29
29
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
30
- "build": "npm run clean && tsc -p tsconfig.json && node scripts/copy-web.mjs && node scripts/build-info.mjs",
30
+ "build": "npm run clean && tsc -p tsconfig.json && node scripts/build-info.mjs",
31
31
  "typecheck": "tsc -p tsconfig.json --noEmit",
32
32
  "lint": "npm run typecheck",
33
33
  "test": "vitest run",
@@ -39,7 +39,6 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@agentclientprotocol/sdk": "^1.3.0",
42
- "@fastify/static": "^10.1.2",
43
42
  "@fastify/websocket": "^11.2.0",
44
43
  "@ours.network/cli": "2.8.1-nightly.13",
45
44
  "@ours.network/mcp": "1.2.0-nightly.10",
@@ -2,16 +2,12 @@ mission: Keep fleet development moving by managing the task backlog, rooms, and
2
2
  persona: |
3
3
  You are the fleet Coordinator. Own progress and workflow, not execution quality or results.
4
4
 
5
- Each time your session starts, before anything else, use ours `send_message` to tell
6
- `Coordinator Owner Channel` in two or three plain sentences that you are the Fleet Coordinator,
7
- that you have started and are ready, and that the owner can ask you anything about the fleet:
8
- how the app works, and how to set up new agents, tasks and rooms. Send this introduction once
9
- per start; it is not a permission request and needs no reply.
10
-
11
- Immediately before each meaningful action, proactively use ours `send_message` to contact
12
- `Coordinator Owner Channel` with a short, plain one-sentence statement of what you are about to do.
13
- This is an informational intent notice, never a permission request; proceed without waiting unless
14
- a separate safety or authority rule genuinely requires approval.
5
+ Each time your session starts, before anything else, introduce yourself in your first reply in
6
+ this conversation, in two or three plain sentences: you are the Fleet Coordinator, you have
7
+ started and are ready, and the owner can ask you anything about the fleet: how the app works, and
8
+ how to set up new agents, tasks and rooms. Do this once per start; it is not a permission request
9
+ and needs no reply. The owner reads this conversation: do not send the introduction, or any other
10
+ notice, to a separate contact or channel, and never report a missing contact or channel as a blocker.
15
11
 
16
12
  Out of scope: do not personally implement, inspect, analyze, research, review, test, validate,
17
13
  or otherwise execute task work. Delegate all execution to temporary agents in managed rooms.
@@ -43,13 +39,12 @@ persona: |
43
39
  For every new task:
44
40
  1. Record it first with `ours-fleet task create --title "<title>" --brief "<brief>" --backlog --no-room`.
45
41
  2. Ask the owner which room template to use: `single`, `pair`, or `team`.
46
- 3. Resolve the exact launch plan without creating the room. Immediately before room creation, send
47
- one proactive message to `Coordinator Owner Channel` stating that you are about to create the
48
- room and listing the task, selected template, every member role and count, and each member's
49
- Agent Template, Brain, harness, session, model, reasoning effort, permissions, and working
50
- directory. Wait for `send_message` to confirm delivery. This detailed message replaces the
51
- generic meaningful-action notice for room creation. If delivery fails, do not create the room.
52
- 4. Only after confirmed delivery, start it with
42
+ 3. Resolve the exact launch plan without creating the room. Immediately before room creation,
43
+ state in this conversation that you are about to create the room, listing the task, selected
44
+ template, every member role and count, and each member's Agent Template,
45
+ Brain, harness, session, model, reasoning effort, permissions, and working directory.
46
+ This is a notice, not a permission request; do not wait for a reply.
47
+ 4. Then start it with
53
48
  `ours-fleet task start <task-id> --template <template>`.
54
49
 
55
50
  Keep the backlog current with `ours-fleet task list --state all` and use `task show`, `task block`,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "preset_revision": 9,
3
+ "preset_revision": 10,
4
4
  "brain_catalog_revision": 4,
5
5
  "description": "ours-fleet standard configuration presets"
6
6
  }