@cohortapp/agent-sdk 2.17.0 → 2.18.5

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 (531) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +18 -5
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/setup-wizard.md +1 -1
  9. package/docs/runbooks/fleet-rollout.md +156 -0
  10. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  11. package/lib/action-executor.js +19 -3
  12. package/lib/budget-guard.mjs +279 -3
  13. package/lib/channels/base-adapter.mjs +3 -1
  14. package/lib/channels/contract.mjs +2 -1
  15. package/lib/channels/inbox-item.mjs +8 -0
  16. package/lib/claude-bin.mjs +5 -6
  17. package/lib/cli/doctor-checks.mjs +141 -10
  18. package/lib/cli/global-setup-extras.mjs +5 -1
  19. package/lib/cli/inbox.mjs +100 -15
  20. package/lib/cli/seat-auth.mjs +463 -0
  21. package/lib/cli/session.mjs +80 -12
  22. package/lib/collective/capture-slots.mjs +234 -0
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/config.mjs +2 -0
  25. package/lib/collective/global-config.mjs +63 -1
  26. package/lib/collective/loop-guard.mjs +155 -0
  27. package/lib/collective/presence.mjs +142 -5
  28. package/lib/comms/send-gate.mjs +559 -1
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/failover.mjs +67 -16
  138. package/lib/model-router/llm-task.mjs +39 -3
  139. package/lib/model-router/resolve.mjs +89 -3
  140. package/lib/model-router/spawn.mjs +46 -47
  141. package/lib/model-router/taxonomy.mjs +126 -4
  142. package/lib/org/cost-sync.mjs +141 -11
  143. package/lib/org/inbound/broadcast.mjs +289 -0
  144. package/lib/org/inbound/collective.mjs +375 -0
  145. package/lib/org/inbound/directedness.mjs +96 -8
  146. package/lib/org/inbound/facts.mjs +78 -2
  147. package/lib/org/inbound/project.mjs +22 -0
  148. package/lib/org/inbound/surfaces.mjs +14 -0
  149. package/lib/org/llm-token.mjs +879 -0
  150. package/lib/org/mesh.mjs +61 -0
  151. package/lib/org/messaging.mjs +3 -1
  152. package/lib/org/protocol.checksum +1 -1
  153. package/lib/org/protocol.mjs +15 -0
  154. package/lib/org/quota.mjs +520 -0
  155. package/lib/org/tool-surface.mjs +104 -16
  156. package/lib/org/ui-parity.mjs +16 -1
  157. package/lib/org/work-ledger.mjs +37 -6
  158. package/lib/rate-guard.mjs +114 -1
  159. package/lib/resource-governor.mjs +41 -6
  160. package/lib/runtime/adapter.mjs +833 -0
  161. package/lib/runtime/child-env.mjs +191 -0
  162. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  163. package/lib/runtime/seat-engine.mjs +162 -0
  164. package/lib/session/ask-ledger.mjs +271 -0
  165. package/lib/session/current-work.mjs +676 -0
  166. package/lib/session/feed-core.mjs +40 -3
  167. package/lib/session/launch-args.mjs +56 -4
  168. package/lib/session/status-summary.mjs +26 -9
  169. package/lib/session/upgrade-notice.mjs +42 -0
  170. package/lib/setup/claude-probe.mjs +117 -13
  171. package/lib/setup/enrich.mjs +13 -10
  172. package/lib/setup/sections/model.mjs +39 -13
  173. package/lib/telemetry/collect.mjs +229 -11
  174. package/lib/upgrade/ignored-drift.mjs +105 -0
  175. package/lib/voice/post-call-brief.mjs +30 -17
  176. package/package.json +13 -3
  177. package/plugins/maestro-skills/skills/board-work.md +5 -0
  178. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  179. package/plugins/maestro-skills/skills/main-session.md +18 -7
  180. package/scaffold/config/collective.yaml +7 -0
  181. package/scripts/ci/check-durable-write-seam.mjs +3 -1
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +142 -19
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +51 -37
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +595 -149
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/maestro-daemon.mjs +11 -0
  198. package/scripts/daemon/prompt-builder.mjs +24 -0
  199. package/scripts/daemon/responder.mjs +246 -79
  200. package/scripts/daemon/sdk-version.mjs +98 -16
  201. package/scripts/eval/probe-gateway.mjs +635 -0
  202. package/scripts/eval/replay/extract.mjs +270 -0
  203. package/scripts/eval/replay/grade.mjs +260 -0
  204. package/scripts/eval/replay/lib/config.mjs +50 -0
  205. package/scripts/eval/replay/lib/effects.mjs +65 -0
  206. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  207. package/scripts/eval/replay/lib/judge.mjs +72 -0
  208. package/scripts/eval/replay/lib/redact.mjs +136 -0
  209. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  210. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  211. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  212. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  213. package/scripts/eval/replay/report.mjs +185 -0
  214. package/scripts/eval/replay/run.mjs +404 -0
  215. package/scripts/fleet/rollout.mjs +1151 -0
  216. package/scripts/hooks/pre-send-audit.sh +36 -245
  217. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  218. package/scripts/hooks/validate-state-yaml.sh +190 -0
  219. package/scripts/huddle/huddle-llm.mjs +361 -0
  220. package/scripts/huddle/huddle-server.mjs +46 -121
  221. package/scripts/local-triggers/autoupdate.sh +465 -81
  222. package/scripts/local-triggers/run-trigger.sh +13 -0
  223. package/scripts/maintenance/pin-integrity.mjs +364 -0
  224. package/scripts/poll-slack-events.sh +41 -9
  225. package/scripts/poller/slack-socket-mode.mjs +28 -3
  226. package/scripts/session/supervisor.mjs +80 -13
  227. package/scripts/spawn-session.sh +13 -0
  228. package/bin/maestro.test.mjs +0 -1574
  229. package/lib/action-executor.test.mjs +0 -871
  230. package/lib/archetype.test.mjs +0 -132
  231. package/lib/assurance/plan-note.test.mjs +0 -234
  232. package/lib/assurance/room-budget.test.mjs +0 -486
  233. package/lib/assurance/tier.test.mjs +0 -174
  234. package/lib/autonomy.test.mjs +0 -66
  235. package/lib/backlog.test.mjs +0 -302
  236. package/lib/backup/policy.test.mjs +0 -305
  237. package/lib/budget-escalate.test.mjs +0 -232
  238. package/lib/budget-guard.envelope.test.mjs +0 -476
  239. package/lib/budget-guard.test.mjs +0 -427
  240. package/lib/cadence-bus-requeue.test.mjs +0 -83
  241. package/lib/cadence-bus-schedule.test.mjs +0 -194
  242. package/lib/cadence-bus.test.mjs +0 -720
  243. package/lib/cadences.test.mjs +0 -230
  244. package/lib/capability/inventory.test.mjs +0 -232
  245. package/lib/capability.test.mjs +0 -78
  246. package/lib/channels/base-adapter.test.mjs +0 -590
  247. package/lib/channels/channels.test.mjs +0 -371
  248. package/lib/channels/contract.test.mjs +0 -162
  249. package/lib/channels/inbox-item.test.mjs +0 -368
  250. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  251. package/lib/channels/pairing.test.mjs +0 -270
  252. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  253. package/lib/channels/slack-adapter.test.mjs +0 -212
  254. package/lib/channels/telegram-adapter.test.mjs +0 -306
  255. package/lib/channels/voice/adapter.test.mjs +0 -278
  256. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  257. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  258. package/lib/charter.test.mjs +0 -89
  259. package/lib/claude-bin.test.mjs +0 -131
  260. package/lib/cli/board.test.mjs +0 -227
  261. package/lib/cli/design.test.mjs +0 -270
  262. package/lib/cli/doctor-checks.test.mjs +0 -336
  263. package/lib/cli/global-setup-extras.test.mjs +0 -462
  264. package/lib/cli/inbox.test.mjs +0 -230
  265. package/lib/cli/session-ack.test.mjs +0 -63
  266. package/lib/cli/session.test.mjs +0 -613
  267. package/lib/collective/capture.test.mjs +0 -121
  268. package/lib/collective/cards.test.mjs +0 -114
  269. package/lib/collective/config.test.mjs +0 -123
  270. package/lib/collective/global-config.test.mjs +0 -220
  271. package/lib/collective/global-skills.test.mjs +0 -126
  272. package/lib/collective/presence.test.mjs +0 -95
  273. package/lib/collective/recall.test.mjs +0 -116
  274. package/lib/collective/vendor-skills.test.mjs +0 -306
  275. package/lib/comms/send-gate.test.mjs +0 -770
  276. package/lib/comms.test.mjs +0 -41
  277. package/lib/context/budget.test.mjs +0 -252
  278. package/lib/context/history-scope.test.mjs +0 -79
  279. package/lib/cost/ledger-row.test.mjs +0 -183
  280. package/lib/design/design-md.test.mjs +0 -318
  281. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  282. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  283. package/lib/design/fixtures/foundation.json +0 -133
  284. package/lib/design/refresh-gate.test.mjs +0 -144
  285. package/lib/design/write.test.mjs +0 -241
  286. package/lib/diagnostics/alerts.test.mjs +0 -318
  287. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  288. package/lib/diagnostics/counters.test.mjs +0 -206
  289. package/lib/diagnostics/events.test.mjs +0 -290
  290. package/lib/diagnostics/otel.test.mjs +0 -196
  291. package/lib/diagnostics/trace.test.mjs +0 -251
  292. package/lib/env-compat.test.mjs +0 -104
  293. package/lib/execution/disposition.test.mjs +0 -553
  294. package/lib/execution/drive.test.mjs +0 -270
  295. package/lib/execution/effects.test.mjs +0 -344
  296. package/lib/execution/intake.test.mjs +0 -389
  297. package/lib/execution/journal.test.mjs +0 -261
  298. package/lib/execution/match.test.mjs +0 -235
  299. package/lib/execution/pipeline.test.mjs +0 -392
  300. package/lib/execution/route.test.mjs +0 -186
  301. package/lib/execution/surface-policy.test.mjs +0 -162
  302. package/lib/fs-atomic.test.mjs +0 -72
  303. package/lib/fs-ownership.test.mjs +0 -158
  304. package/lib/goals/admission.test.mjs +0 -164
  305. package/lib/goals/classify.test.mjs +0 -167
  306. package/lib/goals/collaborate.test.mjs +0 -336
  307. package/lib/goals/gaps.test.mjs +0 -284
  308. package/lib/goals/loop.test.mjs +0 -845
  309. package/lib/hooks/bus.test.mjs +0 -387
  310. package/lib/identity/persona.test.mjs +0 -142
  311. package/lib/kpi-sensors.test.mjs +0 -278
  312. package/lib/kpi.test.mjs +0 -244
  313. package/lib/learning/config.test.mjs +0 -75
  314. package/lib/learning/counters.test.mjs +0 -69
  315. package/lib/learning/curator-consolidate.test.mjs +0 -238
  316. package/lib/learning/curator.test.mjs +0 -106
  317. package/lib/learning/reflect.test.mjs +0 -0
  318. package/lib/learning/session-index.test.mjs +0 -125
  319. package/lib/learning/skill-writer.test.mjs +0 -210
  320. package/lib/mandate/audit.test.mjs +0 -195
  321. package/lib/mandate/contract.test.mjs +0 -185
  322. package/lib/mandate/derive.test.mjs +0 -274
  323. package/lib/mandate/model.test.mjs +0 -164
  324. package/lib/mandate/refresh.test.mjs +0 -389
  325. package/lib/mcp/server.test.mjs +0 -426
  326. package/lib/model-router/auth-profiles.test.mjs +0 -580
  327. package/lib/model-router/catalog.test.mjs +0 -385
  328. package/lib/model-router/economics.test.mjs +0 -438
  329. package/lib/model-router/failover.test.mjs +0 -439
  330. package/lib/model-router/health.test.mjs +0 -338
  331. package/lib/model-router/integration-coverage.test.mjs +0 -831
  332. package/lib/model-router/integration.test.mjs +0 -564
  333. package/lib/model-router/ledger.test.mjs +0 -415
  334. package/lib/model-router/llm-task.test.mjs +0 -392
  335. package/lib/model-router/org-credentials.test.mjs +0 -265
  336. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  337. package/lib/model-router/reconcile.test.mjs +0 -316
  338. package/lib/model-router/repair.test.mjs +0 -180
  339. package/lib/model-router/spawn.test.mjs +0 -446
  340. package/lib/model-router/taxonomy.test.mjs +0 -410
  341. package/lib/model-router.test.mjs +0 -1207
  342. package/lib/org/activity.test.mjs +0 -134
  343. package/lib/org/approvals.test.mjs +0 -216
  344. package/lib/org/awareness.test.mjs +0 -159
  345. package/lib/org/board-mine-cache.test.mjs +0 -53
  346. package/lib/org/board.test.mjs +0 -187
  347. package/lib/org/bootstrap-context.test.mjs +0 -153
  348. package/lib/org/client.test.mjs +0 -1206
  349. package/lib/org/cohort-client.test.mjs +0 -126
  350. package/lib/org/cost-sync.test.mjs +0 -153
  351. package/lib/org/doctor.test.mjs +0 -346
  352. package/lib/org/engagement-ledger.test.mjs +0 -112
  353. package/lib/org/engagement.test.mjs +0 -739
  354. package/lib/org/handoff.test.mjs +0 -269
  355. package/lib/org/inbound/directedness.test.mjs +0 -668
  356. package/lib/org/inbound/facts.test.mjs +0 -471
  357. package/lib/org/inbound/hydrate.test.mjs +0 -908
  358. package/lib/org/inbound/index.test.mjs +0 -429
  359. package/lib/org/inbound/project.test.mjs +0 -287
  360. package/lib/org/integration-tools.test.mjs +0 -160
  361. package/lib/org/keys.test.mjs +0 -92
  362. package/lib/org/knowledge.test.mjs +0 -326
  363. package/lib/org/leases.test.mjs +0 -235
  364. package/lib/org/mesh-directives.test.mjs +0 -110
  365. package/lib/org/mesh-integration.test.mjs +0 -127
  366. package/lib/org/mesh.test.mjs +0 -400
  367. package/lib/org/messaging.test.mjs +0 -471
  368. package/lib/org/param-contract.test.mjs +0 -477
  369. package/lib/org/policy.test.mjs +0 -237
  370. package/lib/org/protocol.checksum.test.mjs +0 -90
  371. package/lib/org/protocol.test.mjs +0 -323
  372. package/lib/org/push.test.mjs +0 -792
  373. package/lib/org/registry.test.mjs +0 -100
  374. package/lib/org/resource-tools.test.mjs +0 -361
  375. package/lib/org/tool-access.test.mjs +0 -144
  376. package/lib/org/tool-surface-integration.test.mjs +0 -120
  377. package/lib/org/tool-surface.test.mjs +0 -1268
  378. package/lib/org/typing.test.mjs +0 -291
  379. package/lib/org/ui-parity.test.mjs +0 -560
  380. package/lib/org/verify.test.mjs +0 -194
  381. package/lib/org/work-ledger.test.mjs +0 -273
  382. package/lib/plan/adoption-e2e.test.mjs +0 -366
  383. package/lib/plan/budget-enforcement.test.mjs +0 -400
  384. package/lib/plan/compile.test.mjs +0 -382
  385. package/lib/plan/emit.test.mjs +0 -269
  386. package/lib/plan/explain.test.mjs +0 -188
  387. package/lib/prompts/parallelism.test.mjs +0 -177
  388. package/lib/rag/rag.test.mjs +0 -505
  389. package/lib/rate-guard.test.mjs +0 -272
  390. package/lib/reactive-gate.test.mjs +0 -57
  391. package/lib/render.test.mjs +0 -68
  392. package/lib/resource-governor.test.mjs +0 -488
  393. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  394. package/lib/scheduling/jitter.test.mjs +0 -140
  395. package/lib/secrets/broker.test.mjs +0 -280
  396. package/lib/secrets/providers.test.mjs +0 -274
  397. package/lib/security/audit-engine.test.mjs +0 -424
  398. package/lib/security/coerce-args.test.mjs +0 -281
  399. package/lib/security/dangerous-tools.test.mjs +0 -68
  400. package/lib/security/external-content.test.mjs +0 -84
  401. package/lib/security/redact.test.mjs +0 -441
  402. package/lib/security/secret-equal.test.mjs +0 -55
  403. package/lib/session/config.test.mjs +0 -92
  404. package/lib/session/feed-core.test.mjs +0 -198
  405. package/lib/session/first-run.test.mjs +0 -121
  406. package/lib/session/frontdoor.test.mjs +0 -205
  407. package/lib/session/handoffs.test.mjs +0 -183
  408. package/lib/session/identity.test.mjs +0 -180
  409. package/lib/session/inbox-claims.test.mjs +0 -286
  410. package/lib/session/launch-args.test.mjs +0 -157
  411. package/lib/session/liveness.test.mjs +0 -100
  412. package/lib/session/status-summary.test.mjs +0 -118
  413. package/lib/session-permissions.test.mjs +0 -120
  414. package/lib/setup/claude-probe.test.mjs +0 -187
  415. package/lib/setup/completeness.test.mjs +0 -110
  416. package/lib/setup/context-pack.test.mjs +0 -89
  417. package/lib/setup/enrich.test.mjs +0 -115
  418. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  419. package/lib/setup/integration.test.mjs +0 -162
  420. package/lib/setup/io.test.mjs +0 -77
  421. package/lib/setup/runner.test.mjs +0 -132
  422. package/lib/setup/sections/identity.test.mjs +0 -234
  423. package/lib/setup/sections/inventory.test.mjs +0 -198
  424. package/lib/setup/sections/learning.test.mjs +0 -81
  425. package/lib/setup/sections/mandate.test.mjs +0 -388
  426. package/lib/setup/sections/messaging.test.mjs +0 -127
  427. package/lib/setup/sections/model.test.mjs +0 -240
  428. package/lib/setup/sections/org.test.mjs +0 -346
  429. package/lib/setup/sections/orgmail.test.mjs +0 -118
  430. package/lib/setup/sections/recovery.test.mjs +0 -98
  431. package/lib/setup/sections/subagents.test.mjs +0 -429
  432. package/lib/setup/sections/verify.test.mjs +0 -175
  433. package/lib/setup/sot.test.mjs +0 -81
  434. package/lib/setup/state.test.mjs +0 -115
  435. package/lib/singleton.test.mjs +0 -151
  436. package/lib/subagents/cli.test.mjs +0 -389
  437. package/lib/subagents/client.test.mjs +0 -309
  438. package/lib/subagents/gap.test.mjs +0 -234
  439. package/lib/subagents/lock.test.mjs +0 -248
  440. package/lib/subagents/manifest.test.mjs +0 -175
  441. package/lib/subagents/refs.test.mjs +0 -204
  442. package/lib/subagents/resolve.test.mjs +0 -422
  443. package/lib/subagents/schema.test.mjs +0 -328
  444. package/lib/telemetry/alerts.test.mjs +0 -109
  445. package/lib/telemetry/collect.test.mjs +0 -1274
  446. package/lib/tool-definitions-integration.test.mjs +0 -83
  447. package/lib/tool-definitions.test.mjs +0 -437
  448. package/lib/upgrade/global-refresh.test.mjs +0 -65
  449. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  450. package/lib/upgrade/post-steps.test.mjs +0 -200
  451. package/lib/upgrade/verify.test.mjs +0 -164
  452. package/lib/util/fetch-timeout.test.mjs +0 -202
  453. package/lib/util/reconnect.test.mjs +0 -369
  454. package/lib/util/unhandled.test.mjs +0 -216
  455. package/lib/voice/outbound.test.mjs +0 -69
  456. package/lib/voice/session-rotation.test.mjs +0 -114
  457. package/lib/voice/stt.test.mjs +0 -226
  458. package/lib/voice/voice.test.mjs +0 -990
  459. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  460. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  461. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  462. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  463. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  464. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  465. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  466. package/scripts/ci/check.test.mjs +0 -194
  467. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  468. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  469. package/scripts/collective/hook-runner.test.mjs +0 -173
  470. package/scripts/cost/fleet-digest.test.mjs +0 -207
  471. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  472. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  473. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  474. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  475. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  476. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  477. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  478. package/scripts/daemon/assurance.test.mjs +0 -1791
  479. package/scripts/daemon/board-mirror.test.mjs +0 -165
  480. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  481. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  482. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  483. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  484. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  485. package/scripts/daemon/classifier.test.mjs +0 -266
  486. package/scripts/daemon/classify-kind.test.mjs +0 -40
  487. package/scripts/daemon/context-compiler.test.mjs +0 -406
  488. package/scripts/daemon/deliver.test.mjs +0 -564
  489. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  490. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  491. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  492. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  493. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  494. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  495. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  496. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  497. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  498. package/scripts/daemon/integration.test.mjs +0 -149
  499. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  500. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  501. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  502. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  503. package/scripts/daemon/responder-cost.test.mjs +0 -68
  504. package/scripts/daemon/responder-history.test.mjs +0 -221
  505. package/scripts/daemon/sdk-version.test.mjs +0 -31
  506. package/scripts/daemon/session-lock.test.mjs +0 -252
  507. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  508. package/scripts/daemon/typing-registry.test.mjs +0 -102
  509. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  510. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  511. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  512. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  513. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  514. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  515. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  516. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  517. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  518. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  519. package/scripts/poller-launchd/install.test.mjs +0 -243
  520. package/scripts/restore-from-backup.test.mjs +0 -181
  521. package/scripts/session/feed.test.mjs +0 -196
  522. package/scripts/session/supervisor-sh.test.mjs +0 -218
  523. package/scripts/session/supervisor.test.mjs +0 -482
  524. package/scripts/setup/configure-macos.test.mjs +0 -306
  525. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  526. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  527. package/scripts/setup/generate-capability.test.mjs +0 -134
  528. package/scripts/setup/init-agent.test.mjs +0 -370
  529. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  530. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  531. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -1,240 +0,0 @@
1
- /**
2
- * model.test.mjs — model/provider setup section (order 30) tests.
3
- *
4
- * The oauth-token mode is the fleet DEFAULT (§3.9): the operator pastes the
5
- * output of `claude setup-token`, headless reads CLAUDE_CODE_OAUTH_TOKEN from
6
- * the environment, and .env ends up 0600 with the token + the prefer flag.
7
- *
8
- * Run: node --test lib/setup/sections/model.test.mjs
9
- */
10
- "use strict";
11
-
12
- import { test } from "node:test";
13
- import assert from "node:assert/strict";
14
- import { mkdtempSync, readFileSync, writeFileSync, statSync, existsSync, rmSync } from "node:fs";
15
- import { tmpdir } from "node:os";
16
- import { join, dirname } from "node:path";
17
- import { fileURLToPath } from "node:url";
18
-
19
- import model, { AUTH_MODES, DEFAULT_AUTH_MODE } from "./model.mjs";
20
- import * as io from "../io.mjs";
21
-
22
- const MAESTRO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
23
-
24
- function tmpRoot(envBody) {
25
- const root = mkdtempSync(join(tmpdir(), "maestro-modelsec-"));
26
- if (envBody !== undefined) writeFileSync(join(root, ".env"), envBody);
27
- return root;
28
- }
29
- function ctxFor(root, answers = {}, extra = {}) {
30
- return { agentRoot: root, maestroRoot: MAESTRO_ROOT, flags: { headless: true }, headless: true, answers, io, ...extra };
31
- }
32
- function readEnv(root) { return readFileSync(join(root, ".env"), "utf-8"); }
33
- function withEnv(patch, fn) {
34
- const saved = {};
35
- for (const k of Object.keys(patch)) { saved[k] = process.env[k]; if (patch[k] === undefined) delete process.env[k]; else process.env[k] = patch[k]; }
36
- return Promise.resolve().then(fn).finally(() => {
37
- for (const k of Object.keys(patch)) { if (saved[k] === undefined) delete process.env[k]; else process.env[k] = saved[k]; }
38
- });
39
- }
40
-
41
- test("section shape: id/order=30/hooks; oauth-token is the default of three modes", () => {
42
- assert.equal(model.id, "model");
43
- assert.equal(model.order, 30);
44
- for (const h of ["detect", "prompt", "apply", "verify"]) assert.equal(typeof model[h], "function");
45
- assert.deepEqual([...AUTH_MODES], ["oauth-token", "subscription", "api-key"]);
46
- assert.equal(DEFAULT_AUTH_MODE, "oauth-token");
47
- });
48
-
49
- test("headless with no answers: provider defaults to oauth-token and the token is read from CLAUDE_CODE_OAUTH_TOKEN", async () => {
50
- const root = tmpRoot("");
51
- try {
52
- await withEnv({ CLAUDE_CODE_OAUTH_TOKEN: "sk-ant-oat01-fromenv" }, async () => {
53
- const ctx = ctxFor(root);
54
- await model.prompt(ctx);
55
- assert.equal(ctx.answers.model.provider, "oauth-token");
56
- assert.equal(ctx.answers.model.hasToken, true);
57
- assert.equal(ctx.answers.claudeOauthToken, "sk-ant-oat01-fromenv", "the value lives under the key the checkpoint redacts, never under answers.model");
58
- await model.apply(ctx);
59
- });
60
- const env = readEnv(root);
61
- assert.match(env, /^CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-fromenv$/m);
62
- assert.match(env, /^MAESTRO_PREFER_SUBSCRIPTION_AUTH=1$/m);
63
- assert.doesNotMatch(env, /^ANTHROPIC_API_KEY=/m);
64
- assert.equal(statSync(join(root, ".env")).mode & 0o777, 0o600, ".env is chmod 600 after apply");
65
- const d = await model.detect(ctxFor(root));
66
- assert.equal(d.status, "complete");
67
- assert.match(d.summary, /OAuth token/i);
68
- } finally { rmSync(root, { recursive: true, force: true }); }
69
- });
70
-
71
- test("the prompt copy tells the operator to run `claude setup-token` on any machine and paste the result", async () => {
72
- const root = tmpRoot("");
73
- try {
74
- const seen = [];
75
- const spyIo = {
76
- ...io,
77
- select: async (_ctx, opts) => { seen.push(opts); return opts.default; },
78
- secret: async (_ctx, opts) => { seen.push(opts); return "pasted-token"; },
79
- confirm: async (_ctx, opts) => { seen.push(opts); return false; },
80
- };
81
- const ctx = ctxFor(root, {}, { io: spyIo });
82
- await model.prompt(ctx);
83
- const select = seen.find((o) => o.key === "provider");
84
- assert.ok(select, "the provider select ran");
85
- assert.equal(select.default, "oauth-token");
86
- const tokenChoice = select.choices.find((c) => c.value === "oauth-token");
87
- assert.match(tokenChoice.label, /claude setup-token/);
88
- const secret = seen.find((o) => o.key === "claudeOauthToken");
89
- assert.ok(secret, "the token secret prompt ran");
90
- assert.match(secret.message, /claude setup-token/);
91
- assert.match(secret.message, /any machine/i);
92
- assert.match(secret.message, /paste/i);
93
- assert.equal(secret.env, "CLAUDE_CODE_OAUTH_TOKEN");
94
- assert.equal(ctx.answers.claudeOauthToken, "pasted-token");
95
- assert.equal(ctx.answers.model.hasToken, true);
96
- } finally { rmSync(root, { recursive: true, force: true }); }
97
- });
98
-
99
- test("apply is idempotent and never clobbers an existing token", async () => {
100
- const root = tmpRoot("CLAUDE_CODE_OAUTH_TOKEN=old-token\n");
101
- try {
102
- const ctx = ctxFor(root, { provider: "oauth-token", claudeOauthToken: "new-token" });
103
- await model.prompt(ctx);
104
- await model.apply(ctx);
105
- await model.apply(ctx);
106
- const env = readEnv(root);
107
- assert.equal((env.match(/^CLAUDE_CODE_OAUTH_TOKEN=/gm) || []).length, 1);
108
- assert.match(env, /^CLAUDE_CODE_OAUTH_TOKEN=old-token$/m);
109
- assert.equal((env.match(/^MAESTRO_PREFER_SUBSCRIPTION_AUTH=1$/gm) || []).length, 1);
110
- } finally { rmSync(root, { recursive: true, force: true }); }
111
- });
112
-
113
- test("oauth-token chosen but no token available: still writes the prefer flag, detect reports keychain mode", async () => {
114
- const root = tmpRoot("");
115
- try {
116
- await withEnv({ CLAUDE_CODE_OAUTH_TOKEN: undefined }, async () => {
117
- const ctx = ctxFor(root);
118
- await model.prompt(ctx);
119
- await model.apply(ctx);
120
- });
121
- const env = readEnv(root);
122
- assert.doesNotMatch(env, /^CLAUDE_CODE_OAUTH_TOKEN=/m);
123
- assert.match(env, /^MAESTRO_PREFER_SUBSCRIPTION_AUTH=1$/m);
124
- const d = await model.detect(ctxFor(root));
125
- assert.equal(d.status, "complete");
126
- assert.match(d.summary, /keychain/i);
127
- } finally { rmSync(root, { recursive: true, force: true }); }
128
- });
129
-
130
- test("subscription (keychain) and api-key modes keep working", async () => {
131
- const a = tmpRoot("");
132
- const b = tmpRoot(undefined); // no .env at all → created 0600
133
- try {
134
- const ca = ctxFor(a, { provider: "subscription" });
135
- await model.prompt(ca); await model.apply(ca);
136
- assert.match(readEnv(a), /^MAESTRO_PREFER_SUBSCRIPTION_AUTH=1$/m);
137
- assert.doesNotMatch(readEnv(a), /^CLAUDE_CODE_OAUTH_TOKEN=/m);
138
-
139
- const cb = ctxFor(b, { provider: "api-key", anthropicApiKey: "sk-ant-api03-x" });
140
- await model.prompt(cb); await model.apply(cb);
141
- assert.ok(existsSync(join(b, ".env")));
142
- assert.match(readEnv(b), /^ANTHROPIC_API_KEY=sk-ant-api03-x$/m);
143
- assert.doesNotMatch(readEnv(b), /^MAESTRO_PREFER_SUBSCRIPTION_AUTH=/m);
144
- assert.equal(statSync(join(b, ".env")).mode & 0o777, 0o600);
145
- const d = await model.detect(ctxFor(b));
146
- assert.match(d.summary, /API key/);
147
- } finally { rmSync(a, { recursive: true, force: true }); rmSync(b, { recursive: true, force: true }); }
148
- });
149
-
150
- test("legacy flat answers (no answers.model) still resolve; a bare token answer implies oauth-token", async () => {
151
- const root = tmpRoot("");
152
- try {
153
- const ctx = ctxFor(root, { claudeOauthToken: "flat-token" });
154
- await model.apply(ctx); // no prompt() — the headless resume path
155
- const env = readEnv(root);
156
- assert.match(env, /^CLAUDE_CODE_OAUTH_TOKEN=flat-token$/m);
157
- assert.match(env, /^MAESTRO_PREFER_SUBSCRIPTION_AUTH=1$/m);
158
- } finally { rmSync(root, { recursive: true, force: true }); }
159
- });
160
-
161
- test("verify uses the injectable probe (ctx.deps.probeClaude) and surfaces auth + detail", async () => {
162
- const root = tmpRoot("CLAUDE_CODE_OAUTH_TOKEN=t\nMAESTRO_PREFER_SUBSCRIPTION_AUTH=1\n");
163
- try {
164
- const seen = [];
165
- const probeClaude = (agentRoot) => { seen.push(agentRoot); return { ok: true, mode: "subscription", auth: "unknown", name: "Claude auth", detail: "could not verify" }; };
166
- const r = await model.verify(ctxFor(root, {}, { deps: { probeClaude } }));
167
- assert.deepEqual(seen, [root]);
168
- assert.equal(r.ok, true);
169
- assert.equal(r.checks[0].auth, "unknown");
170
- assert.equal(r.checks[0].detail, "could not verify");
171
- } finally { rmSync(root, { recursive: true, force: true }); }
172
- });
173
-
174
- // ---------------------------------------------------------------------------
175
- // Review 2026-09-08 findings
176
- // ---------------------------------------------------------------------------
177
-
178
- test("a .env seeded from .env.example (empty `KEY=` lines) is FILLED IN, not treated as already set", async () => {
179
- // .env.example ships `CLAUDE_CODE_OAUTH_TOKEN=` and `MAESTRO_PREFER_SUBSCRIPTION_AUTH=`
180
- // as empty placeholders, and doctor's remedy is "copy from .env.example" —
181
- // so this is the common path, and it used to leave the seat with no auth.
182
- const root = tmpRoot(readFileSync(join(MAESTRO_ROOT, ".env.example"), "utf-8"));
183
- try {
184
- await withEnv({ CLAUDE_CODE_OAUTH_TOKEN: undefined }, async () => {
185
- const ctx = ctxFor(root, { provider: "oauth-token", claudeOauthToken: "sk-ant-oat01-REAL" });
186
- await model.prompt(ctx);
187
- await model.apply(ctx);
188
- });
189
- const env = readEnv(root);
190
- assert.match(env, /^CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-REAL$/m);
191
- assert.equal((env.match(/^CLAUDE_CODE_OAUTH_TOKEN=/gm) || []).length, 1, "the placeholder line is replaced in place, not duplicated");
192
- assert.match(env, /^MAESTRO_PREFER_SUBSCRIPTION_AUTH=1$/m);
193
- assert.equal((env.match(/^MAESTRO_PREFER_SUBSCRIPTION_AUTH=/gm) || []).length, 1);
194
- const d = await model.detect(ctxFor(root));
195
- assert.equal(d.status, "complete");
196
- assert.match(d.summary, /OAuth token/i);
197
- // The api-key lane had the same latent bug.
198
- const b = tmpRoot("ANTHROPIC_API_KEY=\n");
199
- try {
200
- const cb = ctxFor(b, { provider: "api-key", anthropicApiKey: "sk-ant-api03-x" });
201
- await model.prompt(cb); await model.apply(cb);
202
- assert.match(readEnv(b), /^ANTHROPIC_API_KEY=sk-ant-api03-x$/m);
203
- assert.equal((readEnv(b).match(/^ANTHROPIC_API_KEY=/gm) || []).length, 1);
204
- } finally { rmSync(b, { recursive: true, force: true }); }
205
- } finally { rmSync(root, { recursive: true, force: true }); }
206
- });
207
-
208
- test("the pasted token and API key never sit in ctx.answers.model — nothing for progress.json to leak", async () => {
209
- const root = tmpRoot("");
210
- try {
211
- const spyIo = {
212
- ...io,
213
- select: async (_ctx, opts) => opts.default,
214
- secret: async (_ctx, _opts) => "sk-ant-oat01-PASTED",
215
- confirm: async () => false,
216
- };
217
- const ctx = ctxFor(root, {}, { io: spyIo });
218
- await model.prompt(ctx);
219
- const flat = JSON.stringify(ctx.answers.model);
220
- assert.doesNotMatch(flat, /PASTED/, "answers.model carries no credential value");
221
- assert.equal(ctx.answers.model.provider, "oauth-token");
222
- assert.equal(ctx.answers.model.hasToken, true);
223
- // The secret lives under its own top-level key, which the checkpoint's
224
- // SECRET_ANSWER_KEY_RE already strips on every save.
225
- assert.equal(ctx.answers.claudeOauthToken, "sk-ant-oat01-PASTED");
226
- await model.apply(ctx);
227
- assert.match(readEnv(root), /^CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-PASTED$/m);
228
-
229
- const b = tmpRoot("");
230
- try {
231
- const spyB = { ...spyIo, select: async () => "api-key", secret: async () => "sk-ant-api03-PASTED" };
232
- const cb = ctxFor(b, {}, { io: spyB });
233
- await model.prompt(cb);
234
- assert.doesNotMatch(JSON.stringify(cb.answers.model), /PASTED/);
235
- assert.equal(cb.answers.anthropicApiKey, "sk-ant-api03-PASTED");
236
- await model.apply(cb);
237
- assert.match(readEnv(b), /^ANTHROPIC_API_KEY=sk-ant-api03-PASTED$/m);
238
- } finally { rmSync(b, { recursive: true, force: true }); }
239
- } finally { rmSync(root, { recursive: true, force: true }); }
240
- });
@@ -1,346 +0,0 @@
1
- /**
2
- * org.test.mjs — org-enrollment setup section (order 75) tests.
3
- * Run: node --test lib/setup/sections/org.test.mjs
4
- */
5
- "use strict";
6
-
7
- import { test } from "node:test";
8
- import assert from "node:assert/strict";
9
- import { mkdtempSync, existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
10
- import { tmpdir } from "node:os";
11
- import { join, dirname } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
- import yaml from "js-yaml";
14
-
15
- import org, { buildOrgConfig, readOrgConfig, pullProfileIntoAgentConfig, orgContextShapesBacklog, requestPairing, deriveAgentIdentity, orgIdWarning } from "./org.mjs";
16
- import { isEnabled } from "../../org/client.mjs";
17
- import { readAgentJson, writeAgentJson } from "../sot.mjs";
18
- import * as io from "../io.mjs";
19
-
20
- /**
21
- * A deterministic fake fetch for the pairing handshake: records the calls and
22
- * replies with a pending pairing frame (the shape hq's pairing.request returns).
23
- * Never touches the network so the pairing tests stay hermetic + fast.
24
- */
25
- function fakePairFetch(reply) {
26
- const calls = [];
27
- const fn = async (url, init) => {
28
- calls.push({ url: String(url), init, body: init && init.body ? JSON.parse(init.body) : null });
29
- const r = reply ? reply(url, init) : { ok: true, status: 200, body: { ok: true, result: { status: "pending", code: "PAIR-99" } } };
30
- return { ok: r.ok !== false, status: r.status || 200, headers: { get: () => undefined }, json: async () => r.body };
31
- };
32
- fn.calls = calls;
33
- return fn;
34
- }
35
-
36
- const MAESTRO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
37
-
38
- function tmpRoot() {
39
- const root = mkdtempSync(join(tmpdir(), "maestro-orgsec-"));
40
- mkdirSync(join(root, "config"), { recursive: true });
41
- return root;
42
- }
43
- function ctxFor(root, answers = {}) {
44
- return { agentRoot: root, maestroRoot: MAESTRO_ROOT, flags: { headless: true }, answers, io };
45
- }
46
- function readCfg(root) {
47
- return yaml.load(readFileSync(join(root, "config", "org.yaml"), "utf8"));
48
- }
49
-
50
- test("section shape: id/order=75/hooks", () => {
51
- assert.equal(org.id, "org");
52
- assert.equal(org.order, 75);
53
- for (const h of ["detect", "prompt", "apply", "verify"]) assert.equal(typeof org[h], "function");
54
- });
55
-
56
- test("orgContextShapesBacklog: true iff strategy streams or a ready board are present", () => {
57
- assert.equal(orgContextShapesBacklog(null), false);
58
- assert.equal(orgContextShapesBacklog({}), false);
59
- assert.equal(orgContextShapesBacklog({ strategy: {}, board: [] }), false);
60
- assert.equal(orgContextShapesBacklog({ strategy: { streams: [{ code: "GTM" }] } }), true);
61
- assert.equal(orgContextShapesBacklog({ board: [{ title: "do a thing" }] }), true);
62
- });
63
-
64
- test("detect: unconfigured before apply", async () => {
65
- const root = tmpRoot();
66
- const before = await org.detect(ctxFor(root));
67
- assert.equal(before.status, "unconfigured");
68
- assert.ok(before.missing.includes("config/org.yaml"));
69
- });
70
-
71
- test("apply with base + token writes a valid, ENABLED config/org.yaml", async () => {
72
- const root = tmpRoot();
73
- await org.apply(ctxFor(root, { orgBase: "https://cohort.example", orgId: "acme", orgToken: "tok-123" }));
74
- assert.ok(existsSync(join(root, "config", "org.yaml")));
75
- const cfg = readCfg(root);
76
- assert.equal(cfg.org.cohort.base, "https://cohort.example");
77
- assert.equal(cfg.org.cohort.orgId, "acme");
78
- assert.equal(cfg.org.cohort.token, "tok-123");
79
- assert.equal(cfg.org.cohort.enabled, true);
80
- // The shape is exactly what lib/org/client.isEnabled() consumes.
81
- assert.equal(isEnabled(cfg), true);
82
- });
83
-
84
- test("apply trims a trailing slash from the base URL", async () => {
85
- const root = tmpRoot();
86
- await org.apply(ctxFor(root, { orgBase: "https://cohort.example/", orgToken: "tok" }));
87
- assert.equal(readCfg(root).org.cohort.base, "https://cohort.example");
88
- });
89
-
90
- test("apply with a pairing code but no token records intent but stays DISABLED", async () => {
91
- const root = tmpRoot();
92
- const f = fakePairFetch();
93
- const ctx = { ...ctxFor(root, { orgBase: "https://cohort.example", orgPairingCode: "PAIR-99" }), pairFetchImpl: f };
94
- await org.apply(ctx);
95
- const cfg = readCfg(root);
96
- assert.equal(cfg.org.cohort.base, "https://cohort.example");
97
- assert.equal(cfg.org.cohort.enabled, false, "no token yet → fail-closed on the credential");
98
- assert.equal(isEnabled(cfg), false);
99
- // The handshake ran (pre-auth, no bearer) and the intent is persisted.
100
- assert.equal(f.calls.length, 1);
101
- assert.equal(f.calls[0].url, "https://cohort.example/api/v1/pairing.request");
102
- assert.equal(f.calls[0].init.headers.authorization, undefined, "pairing.request is UNAUTHENTICATED");
103
- assert.equal(f.calls[0].body.code, "PAIR-99");
104
- assert.ok(f.calls[0].body.pubkey, "the device public key rides in the handshake");
105
- assert.equal(cfg.org.cohort.pairing.code, "PAIR-99");
106
- assert.equal(cfg.org.cohort.pairing.status, "pending");
107
- assert.equal(ctx.answers.pairingRequested, true);
108
- });
109
-
110
- test("requestPairing: derives agentId from agent.json, persists pairing intent, stays disabled", async () => {
111
- const root = tmpRoot();
112
- writeAgentJson(root, { firstName: "Alex", lastName: "Rivera", email: "alex.rivera@acme.com", fullName: "Alex Rivera" });
113
- const f = fakePairFetch();
114
- const res = await requestPairing(root, { base: "https://cohort.example/", code: "TEAM-7", orgId: "acme", fetchImpl: f });
115
- assert.equal(res.ok, true);
116
- assert.equal(res.agentId, "alex-rivera");
117
- assert.equal(res.status, "pending");
118
- assert.equal(f.calls[0].body.agentId, "alex-rivera");
119
- assert.equal(f.calls[0].body.displayName, "Alex Rivera");
120
- const cfg = readCfg(root);
121
- assert.equal(cfg.org.cohort.pairing.code, "TEAM-7");
122
- assert.equal(cfg.org.cohort.orgId, "acme");
123
- assert.equal(isEnabled(cfg), false, "pairing alone never enables the plane");
124
- // A device key was materialised under state/org/.
125
- assert.ok(existsSync(join(root, "state", "org", "device-key.pem")));
126
- });
127
-
128
- test("requestPairing: short/absent code → fail-open error, no fetch", async () => {
129
- const root = tmpRoot();
130
- const f = fakePairFetch();
131
- assert.equal((await requestPairing(root, { base: "https://n", code: "ab", fetchImpl: f })).ok, false);
132
- assert.equal((await requestPairing(root, { code: "abcd", fetchImpl: f })).ok, false, "missing base");
133
- assert.equal(f.calls.length, 0);
134
- });
135
-
136
- test("requestPairing: unreachable server records intent + returns ok:false (fail-open)", async () => {
137
- const root = tmpRoot();
138
- const f = fakePairFetch(() => ({ ok: false, status: 0, body: null }));
139
- const res = await requestPairing(root, { base: "https://cohort.example", code: "DOWN-1", fetchImpl: f });
140
- assert.equal(res.ok, false);
141
- // The intent is still persisted so a later `maestro pair` / setup knows about it.
142
- const cfg = readCfg(root);
143
- assert.equal(cfg.org.cohort.pairing.code, "DOWN-1");
144
- assert.equal(cfg.org.cohort.pairing.status, "error");
145
- });
146
-
147
- test("deriveAgentIdentity: COHORT_AGENT_ID wins, else email local-part, else name", () => {
148
- const root = tmpRoot();
149
- writeAgentJson(root, { email: "robin.vega@x.io", fullName: "Robin Vega" });
150
- assert.equal(deriveAgentIdentity(root).agentId, "robin-vega");
151
- const saved = process.env.COHORT_AGENT_ID;
152
- process.env.COHORT_AGENT_ID = "Robin The Bot";
153
- try { assert.equal(deriveAgentIdentity(root).agentId, "robin-the-bot"); }
154
- finally { if (saved === undefined) delete process.env.COHORT_AGENT_ID; else process.env.COHORT_AGENT_ID = saved; }
155
- });
156
-
157
- test("detect: surfaces a pending pairing handshake honestly", async () => {
158
- const root = tmpRoot();
159
- const f = fakePairFetch();
160
- await org.apply({ ...ctxFor(root, { orgBase: "https://cohort.example", orgPairingCode: "SEE-ME" }), pairFetchImpl: f });
161
- const d = await org.detect(ctxFor(root));
162
- assert.equal(d.status, "partial");
163
- assert.match(d.summary, /pairing pending \(code SEE-ME\)/);
164
- });
165
-
166
- test("apply with nothing supplied writes a disabled stub (idempotent artefact)", async () => {
167
- const root = tmpRoot();
168
- await org.apply(ctxFor(root, {}));
169
- assert.ok(existsSync(join(root, "config", "org.yaml")));
170
- const cfg = readCfg(root);
171
- assert.equal(isEnabled(cfg), false);
172
- });
173
-
174
- test("apply is idempotent and preserves operator fields (trustedSignersPem)", async () => {
175
- const root = tmpRoot();
176
- // Operator pre-seeds pinned signer PEMs.
177
- writeFileSync(
178
- join(root, "config", "org.yaml"),
179
- yaml.dump({ org: { cohort: { base: "https://old", token: "old-tok", trustedSignersPem: ["-----PEM-----"] } } })
180
- );
181
- await org.apply(ctxFor(root, { orgBase: "https://cohort.new", orgToken: "new-tok" }));
182
- const cfg = readCfg(root);
183
- assert.equal(cfg.org.cohort.base, "https://cohort.new");
184
- assert.equal(cfg.org.cohort.token, "new-tok");
185
- // The pinned signer PEMs survive the merge.
186
- assert.deepEqual(cfg.org.cohort.trustedSignersPem, ["-----PEM-----"]);
187
- // Re-applying with no answers must not clobber the now-enabled config.
188
- await org.apply(ctxFor(root, {}));
189
- const cfg2 = readCfg(root);
190
- assert.equal(cfg2.org.cohort.base, "https://cohort.new");
191
- assert.equal(cfg2.org.cohort.token, "new-tok");
192
- assert.equal(isEnabled(cfg2), true);
193
- });
194
-
195
- test("buildOrgConfig: pure merge keeps existing cohort fields", () => {
196
- const existing = { org: { cohort: { base: "https://a", token: "t", orgId: "x" } }, other: 1 };
197
- const next = buildOrgConfig(existing, { orgId: undefined });
198
- assert.equal(next.other, 1, "unrelated top-level keys preserved");
199
- assert.equal(next.org.cohort.base, "https://a");
200
- assert.equal(next.org.cohort.orgId, "x");
201
- });
202
-
203
- test("readOrgConfig returns {} when file absent (never throws)", async () => {
204
- const root = tmpRoot();
205
- assert.deepEqual(await readOrgConfig(root), {});
206
- });
207
-
208
- test("detect: complete after an enabled apply", async () => {
209
- const root = tmpRoot();
210
- await org.apply(ctxFor(root, { orgBase: "https://cohort.example", orgToken: "tok" }));
211
- const d = await org.detect(ctxFor(root));
212
- assert.equal(d.status, "complete");
213
- });
214
-
215
- test("detect: partial when file present but not enabled", async () => {
216
- const root = tmpRoot();
217
- // A too-short code never opens a handshake (requestPairing fail-opens before
218
- // any fetch), so this exercises the plain not-enabled partial branch.
219
- const f = fakePairFetch();
220
- await org.apply({ ...ctxFor(root, { orgBase: "https://cohort.example", orgPairingCode: "P" }), pairFetchImpl: f });
221
- assert.equal(f.calls.length, 0, "a <4-char code does not hit the network");
222
- const d = await org.detect(ctxFor(root));
223
- assert.equal(d.status, "partial");
224
- });
225
-
226
- test("verify is NON-BLOCKING and never hard-fails when not enrolled", async () => {
227
- const root = tmpRoot();
228
- await org.apply(ctxFor(root, {})); // disabled stub
229
- const res = await org.verify(ctxFor(root));
230
- assert.equal(res.ok, true, "an un-enrolled agent must not fail setup");
231
- const byName = Object.fromEntries(res.checks.map((c) => [c.name, c.ok]));
232
- assert.equal(byName["config/org.yaml present"], true);
233
- // The optional-enrollment warn is present with a remedy.
234
- const warn = res.checks.find((c) => /not enrolled/.test(c.name));
235
- assert.ok(warn && warn.remedy);
236
- });
237
-
238
- // --- Cohort-driven setup pull (COHORT_API_KEY + COHORT_ORG_ID) ----------
239
-
240
- const PULL_PROFILE = {
241
- slug: "robin", kind: "agent", displayName: "Robin Vega", firstName: "Robin", lastName: "Vega",
242
- title: "VP Eng", team: "Platform", role: "engineering-lead", agentEmail: "robin@example.com",
243
- supervisor: { displayName: "Layla Al-Masri", title: "CTO", email: "layla@example.com" },
244
- };
245
-
246
- function withCohortEnv(vars, fn) {
247
- const keys = ["COHORT_API_KEY", "COHORT_ORG_ID", "COHORT_AGENT_ID", "COHORT_BASE"];
248
- const saved = Object.fromEntries(keys.map((k) => [k, process.env[k]]));
249
- for (const k of keys) delete process.env[k];
250
- Object.assign(process.env, vars);
251
- return Promise.resolve()
252
- .then(fn)
253
- .finally(() => { for (const k of keys) { if (saved[k] === undefined) delete process.env[k]; else process.env[k] = saved[k]; } });
254
- }
255
-
256
- test("pullProfileIntoAgentConfig: env + mocked profile populates config/agent.json", async () => {
257
- const root = tmpRoot();
258
- await withCohortEnv({ COHORT_API_KEY: "k", COHORT_ORG_ID: "acme", COHORT_AGENT_ID: "robin", COHORT_BASE: "https://cohort.example" }, async () => {
259
- const plan = await pullProfileIntoAgentConfig(root, { fetchSelfProfileImpl: async () => ({ ok: true, result: PULL_PROFILE }) });
260
- assert.equal(plan.pulled, true);
261
- });
262
- const id = readAgentJson(root);
263
- assert.equal(id.firstName, "Robin");
264
- assert.equal(id.email, "robin@example.com");
265
- assert.equal(id.function, "technical-leader");
266
- assert.equal(id.principal.fullName, "Layla Al-Masri");
267
- });
268
-
269
- test("apply: with COHORT_* env present, pulls the profile into config/agent.json", async () => {
270
- const root = tmpRoot();
271
- // The real fetchSelfProfile is exercised via an injected global fetch.
272
- const savedFetch = globalThis.fetch;
273
- const calls = [];
274
- globalThis.fetch = async (url) => {
275
- calls.push(String(url));
276
- return { ok: true, status: 200, headers: { get: () => undefined }, json: async () => PULL_PROFILE };
277
- };
278
- try {
279
- await withCohortEnv({ COHORT_API_KEY: "k", COHORT_ORG_ID: "acme", COHORT_AGENT_ID: "robin", COHORT_BASE: "https://cohort.example" }, async () => {
280
- const ctx = ctxFor(root, { orgBase: "https://cohort.example", orgToken: "tok" });
281
- await org.apply(ctx);
282
- assert.equal(ctx.answers.cohortPulled, true, "apply records that a pull happened");
283
- });
284
- } finally {
285
- globalThis.fetch = savedFetch;
286
- }
287
- assert.ok(calls.some((u) => u.includes("/api/v1/org/members/robin")), "hit the org-data members endpoint");
288
- const id = readAgentJson(root);
289
- assert.equal(id.firstName, "Robin");
290
- assert.equal(id.title, "VP Eng");
291
- });
292
-
293
- test("apply: absent COHORT_* env → no pull, config/agent.json untouched (manual path)", async () => {
294
- const root = tmpRoot();
295
- await withCohortEnv({}, async () => {
296
- const ctx = ctxFor(root, { orgBase: "https://cohort.example", orgToken: "tok" });
297
- await org.apply(ctx);
298
- assert.equal(ctx.answers.cohortPulled, undefined);
299
- });
300
- assert.equal(readAgentJson(root), null, "no agent.json written by the org section");
301
- });
302
-
303
- test("verify does NOT block / hard-fail on an unreachable enabled server (fail-open)", async () => {
304
- const root = tmpRoot();
305
- // Point at an unroutable endpoint; the client's fetch will fail fast.
306
- await org.apply(ctxFor(root, { orgBase: "http://127.0.0.1:0", orgToken: "tok" }));
307
- const res = await org.verify(ctxFor(root));
308
- // Reachability is best-effort: ok stays true so setup never blocks on the network.
309
- assert.equal(res.ok, true);
310
- const reach = res.checks.find((c) => /reachab/i.test(c.name));
311
- assert.ok(reach, "a reachability check is recorded");
312
- assert.equal(reach.ok, true, "reachability must never hard-fail setup");
313
- });
314
-
315
- // ---------------------------------------------------------------------------
316
- // F2 (audit 2026-09-08): the wizard must not silently accept the org SLUG as
317
- // COHORT_ORG_ID — hq compares the pin with strict equality against the org ID
318
- // (`org_default_<slug>`), so a slug 401s every call.
319
- // ---------------------------------------------------------------------------
320
-
321
- test("orgIdWarning: a slug-shaped value gets a warning naming the ID form; an ID or blank gets none", () => {
322
- assert.equal(orgIdWarning(""), null);
323
- assert.equal(orgIdWarning("org_default_acme"), null);
324
- const w = orgIdWarning("acme");
325
- assert.match(w, /slug/i);
326
- assert.match(w, /org_default_acme/);
327
- assert.match(w, /401/);
328
- });
329
-
330
- test("verify: a slug-shaped orgId in config/org.yaml is a failing check with the remedy; the ID form passes", async () => {
331
- const root = tmpRoot();
332
- try {
333
- await org.apply(ctxFor(root, { orgBase: "https://cohort.example", orgId: "acme", orgToken: "tok-123" }));
334
- const bad = await org.verify(ctxFor(root));
335
- const shape = bad.checks.find((c) => /COHORT_ORG_ID/.test(c.name));
336
- assert.ok(shape, "the shape check ran");
337
- assert.equal(shape.ok, false);
338
- assert.match(shape.remedy, /org_default_acme/);
339
- assert.equal(bad.ok, false);
340
-
341
- writeFileSync(join(root, "config", "org.yaml"), "org:\n cohort:\n enabled: true\n base: https://cohort.example\n orgId: org_default_acme\n token: tok-123\n");
342
- const good = await org.verify(ctxFor(root));
343
- const shape2 = good.checks.find((c) => /COHORT_ORG_ID/.test(c.name));
344
- assert.ok(shape2 && shape2.ok === true, "the ID form is accepted");
345
- } finally { rmSync(root, { recursive: true, force: true }); }
346
- });