@cohortapp/agent-sdk 2.17.0 → 2.18.4

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 (526) 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.mjs +8 -6
  23. package/lib/collective/global-config.mjs +63 -1
  24. package/lib/collective/presence.mjs +142 -5
  25. package/lib/comms/send-gate.mjs +559 -1
  26. package/lib/diagnostics/alerts.mjs +49 -0
  27. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  28. package/lib/engine/agents/definitions.mjs +343 -0
  29. package/lib/engine/agents/persist.mjs +275 -0
  30. package/lib/engine/agents/runtime.mjs +748 -0
  31. package/lib/engine/agents/usage.mjs +95 -0
  32. package/lib/engine/auth-status.mjs +139 -0
  33. package/lib/engine/budget.mjs +194 -0
  34. package/lib/engine/cli.mjs +1204 -0
  35. package/lib/engine/commands/index.mjs +269 -0
  36. package/lib/engine/context/budget.mjs +219 -0
  37. package/lib/engine/context/cache.mjs +125 -0
  38. package/lib/engine/context/child-env.mjs +215 -0
  39. package/lib/engine/context/compaction.mjs +342 -0
  40. package/lib/engine/context/images.mjs +90 -0
  41. package/lib/engine/context/instructions.mjs +327 -0
  42. package/lib/engine/context/lazy-instructions.mjs +169 -0
  43. package/lib/engine/context/manager.mjs +182 -0
  44. package/lib/engine/context/real-path.mjs +91 -0
  45. package/lib/engine/context/secret-values.mjs +163 -0
  46. package/lib/engine/context/settings.mjs +274 -0
  47. package/lib/engine/context/stream-input.mjs +159 -0
  48. package/lib/engine/guard.mjs +152 -0
  49. package/lib/engine/hooks.mjs +713 -0
  50. package/lib/engine/loop.mjs +560 -0
  51. package/lib/engine/mcp/client.mjs +254 -0
  52. package/lib/engine/mcp/config.mjs +301 -0
  53. package/lib/engine/mcp/http.mjs +201 -0
  54. package/lib/engine/mcp/index.mjs +146 -0
  55. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  56. package/lib/engine/mcp/naming.mjs +66 -0
  57. package/lib/engine/mcp/resources.mjs +89 -0
  58. package/lib/engine/mcp/results.mjs +133 -0
  59. package/lib/engine/mcp/stdio.mjs +137 -0
  60. package/lib/engine/mcp/supervisor.mjs +116 -0
  61. package/lib/engine/messages.mjs +104 -0
  62. package/lib/engine/output/json.mjs +164 -0
  63. package/lib/engine/output/stream-json.mjs +266 -0
  64. package/lib/engine/permissions.mjs +845 -0
  65. package/lib/engine/process-identity.mjs +164 -0
  66. package/lib/engine/process-tree.mjs +551 -0
  67. package/lib/engine/prompt.mjs +60 -0
  68. package/lib/engine/session/store.mjs +299 -0
  69. package/lib/engine/session-runtime/args.mjs +97 -0
  70. package/lib/engine/session-runtime/host.mjs +143 -0
  71. package/lib/engine/session-runtime/inbox.mjs +122 -0
  72. package/lib/engine/session-runtime/notifications.mjs +129 -0
  73. package/lib/engine/session-runtime/registry.mjs +328 -0
  74. package/lib/engine/session-runtime/runner.mjs +344 -0
  75. package/lib/engine/session-runtime/socket.mjs +212 -0
  76. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  77. package/lib/engine/skills/index.mjs +321 -0
  78. package/lib/engine/tools/bash-background.mjs +533 -0
  79. package/lib/engine/tools/bash.mjs +216 -0
  80. package/lib/engine/tools/edit.mjs +97 -0
  81. package/lib/engine/tools/glob.mjs +81 -0
  82. package/lib/engine/tools/grep.mjs +224 -0
  83. package/lib/engine/tools/index.mjs +84 -0
  84. package/lib/engine/tools/list-agents.mjs +32 -0
  85. package/lib/engine/tools/ls.mjs +127 -0
  86. package/lib/engine/tools/monitor.mjs +82 -0
  87. package/lib/engine/tools/notebook-edit.mjs +218 -0
  88. package/lib/engine/tools/read.mjs +103 -0
  89. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  90. package/lib/engine/tools/schema.mjs +144 -0
  91. package/lib/engine/tools/send-message.mjs +77 -0
  92. package/lib/engine/tools/session.mjs +70 -0
  93. package/lib/engine/tools/todo.mjs +144 -0
  94. package/lib/engine/tools/toolsearch.mjs +217 -0
  95. package/lib/engine/tools/walk.mjs +193 -0
  96. package/lib/engine/tools/web-switch.mjs +31 -0
  97. package/lib/engine/tools/webfetch-html.mjs +387 -0
  98. package/lib/engine/tools/webfetch-net.mjs +340 -0
  99. package/lib/engine/tools/webfetch.mjs +198 -0
  100. package/lib/engine/tools/websearch.mjs +91 -0
  101. package/lib/engine/tools/workflow.mjs +95 -0
  102. package/lib/engine/tools/write.mjs +76 -0
  103. package/lib/engine/tui/line-editor.mjs +137 -0
  104. package/lib/engine/tui/render.mjs +86 -0
  105. package/lib/engine/tui/tui.mjs +274 -0
  106. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  107. package/lib/engine/wire/effort.mjs +36 -0
  108. package/lib/engine/wire/errors.mjs +496 -0
  109. package/lib/engine/wire/http.mjs +441 -0
  110. package/lib/engine/wire/index.mjs +76 -0
  111. package/lib/engine/wire/openai-chat.mjs +332 -0
  112. package/lib/engine/wire/prompt-cache.mjs +79 -0
  113. package/lib/engine/wire/search.mjs +140 -0
  114. package/lib/engine/wire/sse.mjs +114 -0
  115. package/lib/engine/wire/stall.mjs +349 -0
  116. package/lib/engine/wire/token-provider.mjs +175 -0
  117. package/lib/engine/wire/usage.mjs +192 -0
  118. package/lib/engine/workflow/host.mjs +524 -0
  119. package/lib/engine/workflow/journal.mjs +188 -0
  120. package/lib/engine/workflow/json-schema.mjs +171 -0
  121. package/lib/engine/workflow/meta.mjs +329 -0
  122. package/lib/engine/workflow/notifications.mjs +52 -0
  123. package/lib/engine/workflow/runtime.mjs +447 -0
  124. package/lib/engine/workflow/sandbox.mjs +534 -0
  125. package/lib/engine/workflow/worker.mjs +141 -0
  126. package/lib/engine/workflow/worktree.mjs +74 -0
  127. package/lib/execution/disposition.mjs +1 -1
  128. package/lib/execution/intake.mjs +10 -0
  129. package/lib/execution/surface-policy.mjs +15 -0
  130. package/lib/learning/curator.mjs +8 -6
  131. package/lib/learning/reflect.mjs +8 -6
  132. package/lib/model-router/catalog/cohort.yaml +137 -0
  133. package/lib/model-router/catalog.mjs +118 -1
  134. package/lib/model-router/failover.mjs +67 -16
  135. package/lib/model-router/llm-task.mjs +39 -3
  136. package/lib/model-router/resolve.mjs +89 -3
  137. package/lib/model-router/spawn.mjs +46 -47
  138. package/lib/model-router/taxonomy.mjs +126 -4
  139. package/lib/org/cost-sync.mjs +141 -11
  140. package/lib/org/inbound/broadcast.mjs +289 -0
  141. package/lib/org/inbound/collective.mjs +375 -0
  142. package/lib/org/inbound/directedness.mjs +96 -8
  143. package/lib/org/inbound/facts.mjs +78 -2
  144. package/lib/org/inbound/project.mjs +22 -0
  145. package/lib/org/inbound/surfaces.mjs +14 -0
  146. package/lib/org/llm-token.mjs +879 -0
  147. package/lib/org/mesh.mjs +61 -0
  148. package/lib/org/messaging.mjs +3 -1
  149. package/lib/org/protocol.checksum +1 -1
  150. package/lib/org/protocol.mjs +15 -0
  151. package/lib/org/quota.mjs +520 -0
  152. package/lib/org/tool-surface.mjs +104 -16
  153. package/lib/org/ui-parity.mjs +16 -1
  154. package/lib/org/work-ledger.mjs +37 -6
  155. package/lib/rate-guard.mjs +114 -1
  156. package/lib/resource-governor.mjs +41 -6
  157. package/lib/runtime/adapter.mjs +823 -0
  158. package/lib/runtime/child-env.mjs +191 -0
  159. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  160. package/lib/runtime/seat-engine.mjs +162 -0
  161. package/lib/session/ask-ledger.mjs +271 -0
  162. package/lib/session/current-work.mjs +676 -0
  163. package/lib/session/feed-core.mjs +40 -3
  164. package/lib/session/launch-args.mjs +56 -4
  165. package/lib/session/status-summary.mjs +26 -9
  166. package/lib/session/upgrade-notice.mjs +42 -0
  167. package/lib/setup/claude-probe.mjs +117 -13
  168. package/lib/setup/enrich.mjs +13 -10
  169. package/lib/setup/sections/model.mjs +39 -13
  170. package/lib/telemetry/collect.mjs +208 -9
  171. package/lib/upgrade/ignored-drift.mjs +105 -0
  172. package/lib/voice/post-call-brief.mjs +30 -17
  173. package/package.json +13 -3
  174. package/plugins/maestro-skills/skills/board-work.md +5 -0
  175. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  176. package/plugins/maestro-skills/skills/main-session.md +18 -7
  177. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  178. package/scripts/ci/run-tests.mjs +47 -19
  179. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  180. package/scripts/collective/hook-runner.mjs +29 -2
  181. package/scripts/continuous-monitor.sh +13 -0
  182. package/scripts/cost/track-claude-usage.mjs +15 -0
  183. package/scripts/daemon/agent-daemon.mjs +408 -20
  184. package/scripts/daemon/assurance.mjs +48 -12
  185. package/scripts/daemon/cadence-consumer.mjs +218 -68
  186. package/scripts/daemon/cadence-handlers.mjs +73 -4
  187. package/scripts/daemon/classifier.mjs +75 -26
  188. package/scripts/daemon/context-compiler.mjs +51 -37
  189. package/scripts/daemon/deliver.mjs +30 -1
  190. package/scripts/daemon/dispatcher.mjs +595 -149
  191. package/scripts/daemon/health.mjs +14 -1
  192. package/scripts/daemon/maestro-daemon.mjs +11 -0
  193. package/scripts/daemon/prompt-builder.mjs +24 -0
  194. package/scripts/daemon/responder.mjs +246 -79
  195. package/scripts/daemon/sdk-version.mjs +98 -16
  196. package/scripts/eval/probe-gateway.mjs +635 -0
  197. package/scripts/eval/replay/extract.mjs +270 -0
  198. package/scripts/eval/replay/grade.mjs +260 -0
  199. package/scripts/eval/replay/lib/config.mjs +50 -0
  200. package/scripts/eval/replay/lib/effects.mjs +65 -0
  201. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  202. package/scripts/eval/replay/lib/judge.mjs +72 -0
  203. package/scripts/eval/replay/lib/redact.mjs +136 -0
  204. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  205. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  206. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  207. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  208. package/scripts/eval/replay/report.mjs +185 -0
  209. package/scripts/eval/replay/run.mjs +404 -0
  210. package/scripts/fleet/rollout.mjs +1094 -0
  211. package/scripts/hooks/pre-send-audit.sh +36 -245
  212. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  213. package/scripts/hooks/validate-state-yaml.sh +190 -0
  214. package/scripts/huddle/huddle-llm.mjs +361 -0
  215. package/scripts/huddle/huddle-server.mjs +46 -121
  216. package/scripts/local-triggers/autoupdate.sh +448 -78
  217. package/scripts/local-triggers/run-trigger.sh +13 -0
  218. package/scripts/maintenance/pin-integrity.mjs +364 -0
  219. package/scripts/poll-slack-events.sh +41 -9
  220. package/scripts/poller/slack-socket-mode.mjs +28 -3
  221. package/scripts/session/supervisor.mjs +80 -13
  222. package/scripts/spawn-session.sh +13 -0
  223. package/bin/maestro.test.mjs +0 -1574
  224. package/lib/action-executor.test.mjs +0 -871
  225. package/lib/archetype.test.mjs +0 -132
  226. package/lib/assurance/plan-note.test.mjs +0 -234
  227. package/lib/assurance/room-budget.test.mjs +0 -486
  228. package/lib/assurance/tier.test.mjs +0 -174
  229. package/lib/autonomy.test.mjs +0 -66
  230. package/lib/backlog.test.mjs +0 -302
  231. package/lib/backup/policy.test.mjs +0 -305
  232. package/lib/budget-escalate.test.mjs +0 -232
  233. package/lib/budget-guard.envelope.test.mjs +0 -476
  234. package/lib/budget-guard.test.mjs +0 -427
  235. package/lib/cadence-bus-requeue.test.mjs +0 -83
  236. package/lib/cadence-bus-schedule.test.mjs +0 -194
  237. package/lib/cadence-bus.test.mjs +0 -720
  238. package/lib/cadences.test.mjs +0 -230
  239. package/lib/capability/inventory.test.mjs +0 -232
  240. package/lib/capability.test.mjs +0 -78
  241. package/lib/channels/base-adapter.test.mjs +0 -590
  242. package/lib/channels/channels.test.mjs +0 -371
  243. package/lib/channels/contract.test.mjs +0 -162
  244. package/lib/channels/inbox-item.test.mjs +0 -368
  245. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  246. package/lib/channels/pairing.test.mjs +0 -270
  247. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  248. package/lib/channels/slack-adapter.test.mjs +0 -212
  249. package/lib/channels/telegram-adapter.test.mjs +0 -306
  250. package/lib/channels/voice/adapter.test.mjs +0 -278
  251. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  252. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  253. package/lib/charter.test.mjs +0 -89
  254. package/lib/claude-bin.test.mjs +0 -131
  255. package/lib/cli/board.test.mjs +0 -227
  256. package/lib/cli/design.test.mjs +0 -270
  257. package/lib/cli/doctor-checks.test.mjs +0 -336
  258. package/lib/cli/global-setup-extras.test.mjs +0 -462
  259. package/lib/cli/inbox.test.mjs +0 -230
  260. package/lib/cli/session-ack.test.mjs +0 -63
  261. package/lib/cli/session.test.mjs +0 -613
  262. package/lib/collective/capture.test.mjs +0 -121
  263. package/lib/collective/cards.test.mjs +0 -114
  264. package/lib/collective/config.test.mjs +0 -123
  265. package/lib/collective/global-config.test.mjs +0 -220
  266. package/lib/collective/global-skills.test.mjs +0 -126
  267. package/lib/collective/presence.test.mjs +0 -95
  268. package/lib/collective/recall.test.mjs +0 -116
  269. package/lib/collective/vendor-skills.test.mjs +0 -306
  270. package/lib/comms/send-gate.test.mjs +0 -770
  271. package/lib/comms.test.mjs +0 -41
  272. package/lib/context/budget.test.mjs +0 -252
  273. package/lib/context/history-scope.test.mjs +0 -79
  274. package/lib/cost/ledger-row.test.mjs +0 -183
  275. package/lib/design/design-md.test.mjs +0 -318
  276. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  277. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  278. package/lib/design/fixtures/foundation.json +0 -133
  279. package/lib/design/refresh-gate.test.mjs +0 -144
  280. package/lib/design/write.test.mjs +0 -241
  281. package/lib/diagnostics/alerts.test.mjs +0 -318
  282. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  283. package/lib/diagnostics/counters.test.mjs +0 -206
  284. package/lib/diagnostics/events.test.mjs +0 -290
  285. package/lib/diagnostics/otel.test.mjs +0 -196
  286. package/lib/diagnostics/trace.test.mjs +0 -251
  287. package/lib/env-compat.test.mjs +0 -104
  288. package/lib/execution/disposition.test.mjs +0 -553
  289. package/lib/execution/drive.test.mjs +0 -270
  290. package/lib/execution/effects.test.mjs +0 -344
  291. package/lib/execution/intake.test.mjs +0 -389
  292. package/lib/execution/journal.test.mjs +0 -261
  293. package/lib/execution/match.test.mjs +0 -235
  294. package/lib/execution/pipeline.test.mjs +0 -392
  295. package/lib/execution/route.test.mjs +0 -186
  296. package/lib/execution/surface-policy.test.mjs +0 -162
  297. package/lib/fs-atomic.test.mjs +0 -72
  298. package/lib/fs-ownership.test.mjs +0 -158
  299. package/lib/goals/admission.test.mjs +0 -164
  300. package/lib/goals/classify.test.mjs +0 -167
  301. package/lib/goals/collaborate.test.mjs +0 -336
  302. package/lib/goals/gaps.test.mjs +0 -284
  303. package/lib/goals/loop.test.mjs +0 -845
  304. package/lib/hooks/bus.test.mjs +0 -387
  305. package/lib/identity/persona.test.mjs +0 -142
  306. package/lib/kpi-sensors.test.mjs +0 -278
  307. package/lib/kpi.test.mjs +0 -244
  308. package/lib/learning/config.test.mjs +0 -75
  309. package/lib/learning/counters.test.mjs +0 -69
  310. package/lib/learning/curator-consolidate.test.mjs +0 -238
  311. package/lib/learning/curator.test.mjs +0 -106
  312. package/lib/learning/reflect.test.mjs +0 -0
  313. package/lib/learning/session-index.test.mjs +0 -125
  314. package/lib/learning/skill-writer.test.mjs +0 -210
  315. package/lib/mandate/audit.test.mjs +0 -195
  316. package/lib/mandate/contract.test.mjs +0 -185
  317. package/lib/mandate/derive.test.mjs +0 -274
  318. package/lib/mandate/model.test.mjs +0 -164
  319. package/lib/mandate/refresh.test.mjs +0 -389
  320. package/lib/mcp/server.test.mjs +0 -426
  321. package/lib/model-router/auth-profiles.test.mjs +0 -580
  322. package/lib/model-router/catalog.test.mjs +0 -385
  323. package/lib/model-router/economics.test.mjs +0 -438
  324. package/lib/model-router/failover.test.mjs +0 -439
  325. package/lib/model-router/health.test.mjs +0 -338
  326. package/lib/model-router/integration-coverage.test.mjs +0 -831
  327. package/lib/model-router/integration.test.mjs +0 -564
  328. package/lib/model-router/ledger.test.mjs +0 -415
  329. package/lib/model-router/llm-task.test.mjs +0 -392
  330. package/lib/model-router/org-credentials.test.mjs +0 -265
  331. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  332. package/lib/model-router/reconcile.test.mjs +0 -316
  333. package/lib/model-router/repair.test.mjs +0 -180
  334. package/lib/model-router/spawn.test.mjs +0 -446
  335. package/lib/model-router/taxonomy.test.mjs +0 -410
  336. package/lib/model-router.test.mjs +0 -1207
  337. package/lib/org/activity.test.mjs +0 -134
  338. package/lib/org/approvals.test.mjs +0 -216
  339. package/lib/org/awareness.test.mjs +0 -159
  340. package/lib/org/board-mine-cache.test.mjs +0 -53
  341. package/lib/org/board.test.mjs +0 -187
  342. package/lib/org/bootstrap-context.test.mjs +0 -153
  343. package/lib/org/client.test.mjs +0 -1206
  344. package/lib/org/cohort-client.test.mjs +0 -126
  345. package/lib/org/cost-sync.test.mjs +0 -153
  346. package/lib/org/doctor.test.mjs +0 -346
  347. package/lib/org/engagement-ledger.test.mjs +0 -112
  348. package/lib/org/engagement.test.mjs +0 -739
  349. package/lib/org/handoff.test.mjs +0 -269
  350. package/lib/org/inbound/directedness.test.mjs +0 -668
  351. package/lib/org/inbound/facts.test.mjs +0 -471
  352. package/lib/org/inbound/hydrate.test.mjs +0 -908
  353. package/lib/org/inbound/index.test.mjs +0 -429
  354. package/lib/org/inbound/project.test.mjs +0 -287
  355. package/lib/org/integration-tools.test.mjs +0 -160
  356. package/lib/org/keys.test.mjs +0 -92
  357. package/lib/org/knowledge.test.mjs +0 -326
  358. package/lib/org/leases.test.mjs +0 -235
  359. package/lib/org/mesh-directives.test.mjs +0 -110
  360. package/lib/org/mesh-integration.test.mjs +0 -127
  361. package/lib/org/mesh.test.mjs +0 -400
  362. package/lib/org/messaging.test.mjs +0 -471
  363. package/lib/org/param-contract.test.mjs +0 -477
  364. package/lib/org/policy.test.mjs +0 -237
  365. package/lib/org/protocol.checksum.test.mjs +0 -90
  366. package/lib/org/protocol.test.mjs +0 -323
  367. package/lib/org/push.test.mjs +0 -792
  368. package/lib/org/registry.test.mjs +0 -100
  369. package/lib/org/resource-tools.test.mjs +0 -361
  370. package/lib/org/tool-access.test.mjs +0 -144
  371. package/lib/org/tool-surface-integration.test.mjs +0 -120
  372. package/lib/org/tool-surface.test.mjs +0 -1268
  373. package/lib/org/typing.test.mjs +0 -291
  374. package/lib/org/ui-parity.test.mjs +0 -560
  375. package/lib/org/verify.test.mjs +0 -194
  376. package/lib/org/work-ledger.test.mjs +0 -273
  377. package/lib/plan/adoption-e2e.test.mjs +0 -366
  378. package/lib/plan/budget-enforcement.test.mjs +0 -400
  379. package/lib/plan/compile.test.mjs +0 -382
  380. package/lib/plan/emit.test.mjs +0 -269
  381. package/lib/plan/explain.test.mjs +0 -188
  382. package/lib/prompts/parallelism.test.mjs +0 -177
  383. package/lib/rag/rag.test.mjs +0 -505
  384. package/lib/rate-guard.test.mjs +0 -272
  385. package/lib/reactive-gate.test.mjs +0 -57
  386. package/lib/render.test.mjs +0 -68
  387. package/lib/resource-governor.test.mjs +0 -488
  388. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  389. package/lib/scheduling/jitter.test.mjs +0 -140
  390. package/lib/secrets/broker.test.mjs +0 -280
  391. package/lib/secrets/providers.test.mjs +0 -274
  392. package/lib/security/audit-engine.test.mjs +0 -424
  393. package/lib/security/coerce-args.test.mjs +0 -281
  394. package/lib/security/dangerous-tools.test.mjs +0 -68
  395. package/lib/security/external-content.test.mjs +0 -84
  396. package/lib/security/redact.test.mjs +0 -441
  397. package/lib/security/secret-equal.test.mjs +0 -55
  398. package/lib/session/config.test.mjs +0 -92
  399. package/lib/session/feed-core.test.mjs +0 -198
  400. package/lib/session/first-run.test.mjs +0 -121
  401. package/lib/session/frontdoor.test.mjs +0 -205
  402. package/lib/session/handoffs.test.mjs +0 -183
  403. package/lib/session/identity.test.mjs +0 -180
  404. package/lib/session/inbox-claims.test.mjs +0 -286
  405. package/lib/session/launch-args.test.mjs +0 -157
  406. package/lib/session/liveness.test.mjs +0 -100
  407. package/lib/session/status-summary.test.mjs +0 -118
  408. package/lib/session-permissions.test.mjs +0 -120
  409. package/lib/setup/claude-probe.test.mjs +0 -187
  410. package/lib/setup/completeness.test.mjs +0 -110
  411. package/lib/setup/context-pack.test.mjs +0 -89
  412. package/lib/setup/enrich.test.mjs +0 -115
  413. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  414. package/lib/setup/integration.test.mjs +0 -162
  415. package/lib/setup/io.test.mjs +0 -77
  416. package/lib/setup/runner.test.mjs +0 -132
  417. package/lib/setup/sections/identity.test.mjs +0 -234
  418. package/lib/setup/sections/inventory.test.mjs +0 -198
  419. package/lib/setup/sections/learning.test.mjs +0 -81
  420. package/lib/setup/sections/mandate.test.mjs +0 -388
  421. package/lib/setup/sections/messaging.test.mjs +0 -127
  422. package/lib/setup/sections/model.test.mjs +0 -240
  423. package/lib/setup/sections/org.test.mjs +0 -346
  424. package/lib/setup/sections/orgmail.test.mjs +0 -118
  425. package/lib/setup/sections/recovery.test.mjs +0 -98
  426. package/lib/setup/sections/subagents.test.mjs +0 -429
  427. package/lib/setup/sections/verify.test.mjs +0 -175
  428. package/lib/setup/sot.test.mjs +0 -81
  429. package/lib/setup/state.test.mjs +0 -115
  430. package/lib/singleton.test.mjs +0 -151
  431. package/lib/subagents/cli.test.mjs +0 -389
  432. package/lib/subagents/client.test.mjs +0 -309
  433. package/lib/subagents/gap.test.mjs +0 -234
  434. package/lib/subagents/lock.test.mjs +0 -248
  435. package/lib/subagents/manifest.test.mjs +0 -175
  436. package/lib/subagents/refs.test.mjs +0 -204
  437. package/lib/subagents/resolve.test.mjs +0 -422
  438. package/lib/subagents/schema.test.mjs +0 -328
  439. package/lib/telemetry/alerts.test.mjs +0 -109
  440. package/lib/telemetry/collect.test.mjs +0 -1274
  441. package/lib/tool-definitions-integration.test.mjs +0 -83
  442. package/lib/tool-definitions.test.mjs +0 -437
  443. package/lib/upgrade/global-refresh.test.mjs +0 -65
  444. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  445. package/lib/upgrade/post-steps.test.mjs +0 -200
  446. package/lib/upgrade/verify.test.mjs +0 -164
  447. package/lib/util/fetch-timeout.test.mjs +0 -202
  448. package/lib/util/reconnect.test.mjs +0 -369
  449. package/lib/util/unhandled.test.mjs +0 -216
  450. package/lib/voice/outbound.test.mjs +0 -69
  451. package/lib/voice/session-rotation.test.mjs +0 -114
  452. package/lib/voice/stt.test.mjs +0 -226
  453. package/lib/voice/voice.test.mjs +0 -990
  454. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  455. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  456. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  457. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  458. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  459. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  460. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  461. package/scripts/ci/check.test.mjs +0 -194
  462. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  463. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  464. package/scripts/collective/hook-runner.test.mjs +0 -173
  465. package/scripts/cost/fleet-digest.test.mjs +0 -207
  466. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  467. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  468. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  469. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  470. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  471. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  472. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  473. package/scripts/daemon/assurance.test.mjs +0 -1791
  474. package/scripts/daemon/board-mirror.test.mjs +0 -165
  475. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  476. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  477. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  478. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  479. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  480. package/scripts/daemon/classifier.test.mjs +0 -266
  481. package/scripts/daemon/classify-kind.test.mjs +0 -40
  482. package/scripts/daemon/context-compiler.test.mjs +0 -406
  483. package/scripts/daemon/deliver.test.mjs +0 -564
  484. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  485. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  486. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  487. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  488. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  489. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  490. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  491. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  492. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  493. package/scripts/daemon/integration.test.mjs +0 -149
  494. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  495. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  496. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  497. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  498. package/scripts/daemon/responder-cost.test.mjs +0 -68
  499. package/scripts/daemon/responder-history.test.mjs +0 -221
  500. package/scripts/daemon/sdk-version.test.mjs +0 -31
  501. package/scripts/daemon/session-lock.test.mjs +0 -252
  502. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  503. package/scripts/daemon/typing-registry.test.mjs +0 -102
  504. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  505. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  506. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  507. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  508. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  509. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  510. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  511. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  512. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  513. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  514. package/scripts/poller-launchd/install.test.mjs +0 -243
  515. package/scripts/restore-from-backup.test.mjs +0 -181
  516. package/scripts/session/feed.test.mjs +0 -196
  517. package/scripts/session/supervisor-sh.test.mjs +0 -218
  518. package/scripts/session/supervisor.test.mjs +0 -482
  519. package/scripts/setup/configure-macos.test.mjs +0 -306
  520. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  521. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  522. package/scripts/setup/generate-capability.test.mjs +0 -134
  523. package/scripts/setup/init-agent.test.mjs +0 -370
  524. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  525. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  526. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -0,0 +1,463 @@
1
+ /**
2
+ * lib/cli/seat-auth.mjs — G4: which Claude billing rail is this seat on, and
3
+ * is that the fleet posture?
4
+ *
5
+ * The fleet posture is a **per-agent Console API key in a workspace with a hard
6
+ * spend cap** (`docs/guides/billing-console-keys.md`). Subscription OAuth — the
7
+ * `MAESTRO_PREFER_SUBSCRIPTION_AUTH` flag, a `CLAUDE_CODE_OAUTH_TOKEN`, or the
8
+ * keychain login behind them — still RUNS, which is exactly why a seat can sit
9
+ * on it for months with nothing complaining. G4 is the gate that ends that, and
10
+ * this module is the tooling half: a verdict a seat can state about itself, an
11
+ * inventory row an operator can paste, and a fleet template to paste it into.
12
+ *
13
+ * Three deliberate constraints, each of which is a test:
14
+ *
15
+ * 1. **Names, never values.** Every verdict reports which variable NAMES are
16
+ * present. No value — masked, truncated or otherwise — ever reaches a
17
+ * message, a row or the JSON. A migration tool that prints a fragment of the
18
+ * credential it is migrating has created a second copy of the problem.
19
+ * 2. **Both-set is its own verdict.** A seat carrying a Console key AND a
20
+ * subscription credential is not "half migrated", it is ambiguous: the
21
+ * runtime adapter blanks `ANTHROPIC_API_KEY` on a seat that
22
+ * `subscriptionAuth()` calls true (`lib/runtime/adapter.mjs`), so the
23
+ * subscription rail wins the lanes that go through it while lanes that pass
24
+ * the environment straight through may use the key. Spend then lands in two
25
+ * places and neither is authoritative. It is called out loudly.
26
+ * 3. **Warn today, fail when told.** Seats are LIVE. A check that fails by
27
+ * default would turn every doctor run red the hour it ships and break the
28
+ * very seats it is trying to move. So `subscription` and `both` are `warn`
29
+ * until `MAESTRO_REQUIRE_CONSOLE_KEY=1` (documented in `.env.example` and the
30
+ * runbook) flips them to `fail`. `neither` is a failure regardless on a
31
+ * `claude` seat — a seat with no credential at all is broken, not lax — and
32
+ * is the EXPECTED state on an `engine=cohort` seat, which holds no Anthropic
33
+ * credential by design.
34
+ *
35
+ * Everything here is pure except `runSeatAuth`, whose every edge (the seat's
36
+ * `.env`, the hostname, the clock, the org directory) is injectable.
37
+ *
38
+ * What this module deliberately does NOT do: reach another machine. A seat's
39
+ * environment lives on the seat's own box; there is no remote inventory
40
+ * mechanism to invent. The fleet report is a TEMPLATE plus paste-able rows,
41
+ * which is the honest shape of the job.
42
+ *
43
+ * @module lib/cli/seat-auth
44
+ */
45
+
46
+ "use strict";
47
+
48
+ import { hostname as osHostname } from "node:os";
49
+
50
+ /** The three variables that decide the rail. Order is the report order. */
51
+ export const SEAT_AUTH_VARS = Object.freeze([
52
+ "ANTHROPIC_API_KEY",
53
+ "MAESTRO_PREFER_SUBSCRIPTION_AUTH",
54
+ "CLAUDE_CODE_OAUTH_TOKEN",
55
+ ]);
56
+
57
+ /** Set this to 1/true/yes to turn the G4 warning into a doctor failure. */
58
+ export const REQUIRE_CONSOLE_KEY_VAR = "MAESTRO_REQUIRE_CONSOLE_KEY";
59
+
60
+ /** The operator runbook every remedy points at. */
61
+ export const RUNBOOK = "docs/guides/billing-console-keys.md";
62
+
63
+ /**
64
+ * The one remedy every auth row gives (CF-13). Defined here — the G4 module —
65
+ * and re-exported by `lib/cli/doctor-checks.mjs` so the two surfaces cannot
66
+ * drift into saying different things.
67
+ */
68
+ export const CONSOLE_KEY_REMEDY =
69
+ "put a per-agent Console API key (a workspace with a spend cap) in .env as ANTHROPIC_API_KEY and remove MAESTRO_PREFER_SUBSCRIPTION_AUTH / CLAUDE_CODE_OAUTH_TOKEN — see docs/guides/billing-console-keys.md";
70
+
71
+ const TRUTHY = /^(1|true|yes)$/i;
72
+
73
+ /** Non-empty after trimming, for any value shape an env bag can hold. */
74
+ function nonEmpty(v) {
75
+ return v !== undefined && v !== null && String(v).trim() !== "";
76
+ }
77
+
78
+ /**
79
+ * Is the G4 guard armed in this environment?
80
+ * @param {Record<string,string>} [env]
81
+ * @returns {boolean}
82
+ */
83
+ export function enforcementEnabled(env = {}) {
84
+ return TRUTHY.test(String((env && env[REQUIRE_CONSOLE_KEY_VAR]) || "").trim());
85
+ }
86
+
87
+ /** `ANTHROPIC_API_KEY (.env)` when the caller knows where it came from. */
88
+ function labelled(name, sources) {
89
+ const src = sources && sources[name];
90
+ return src ? `${name} (${src})` : name;
91
+ }
92
+
93
+ /**
94
+ * Which billing rail is this seat on?
95
+ *
96
+ * @param {Record<string,string>} [env] the seat's effective env (`.env` merged
97
+ * over the ambient env, the way every consumer sees it). VALUES ARE NEVER
98
+ * REPORTED — only whether each name is non-empty.
99
+ * @param {object} [opts]
100
+ * @param {"claude"|"cohort"|string} [opts.seatEngine="claude"] a `cohort` seat
101
+ * talks to cohort-llm and is SUPPOSED to hold no Anthropic credential.
102
+ * @param {Record<string,string>} [opts.sources] optional `{VAR: ".env"|"env"}`
103
+ * provenance, surfaced in the message (a token arriving through launchd's
104
+ * environment looks identical to one in `.env` until you say which).
105
+ * @param {boolean} [opts.enforce] override the env-derived guard state.
106
+ * @returns {{verdict:"api-key"|"subscription"|"both"|"neither", level:"ok"|"warn"|"fail",
107
+ * compliant:boolean, enforced:boolean, seatEngine:string, present:string[],
108
+ * absent:string[], msg:string, remedy:string}}
109
+ */
110
+ export function seatAuthVerdict(env = {}, opts = {}) {
111
+ const e = env && typeof env === "object" ? env : {};
112
+ const sources = (opts && opts.sources) || {};
113
+ const seatEngine = String((opts && opts.seatEngine) || "claude");
114
+ const enforced = opts && opts.enforce !== undefined ? !!opts.enforce : enforcementEnabled(e);
115
+
116
+ const key = nonEmpty(e.ANTHROPIC_API_KEY);
117
+ const token = nonEmpty(e.CLAUDE_CODE_OAUTH_TOKEN);
118
+ const preferPresent = nonEmpty(e.MAESTRO_PREFER_SUBSCRIPTION_AUTH);
119
+ const preferOn = TRUTHY.test(String(e.MAESTRO_PREFER_SUBSCRIPTION_AUTH || "").trim());
120
+ // The same test the runtime adapter applies (lib/runtime/adapter.mjs
121
+ // subscriptionAuth): the prefer flag OR a token puts the seat on the
122
+ // subscription rail, and that is what decides whether a lane blanks the key.
123
+ const subscription = token || preferOn;
124
+
125
+ const present = SEAT_AUTH_VARS.filter((n) => nonEmpty(e[n]));
126
+ const absent = SEAT_AUTH_VARS.filter((n) => !nonEmpty(e[n]));
127
+ const names = present.length ? present.map((n) => labelled(n, sources)).join(", ") : "none";
128
+ const cohortSeat = seatEngine === "cohort";
129
+
130
+ const verdict = key && subscription ? "both" : key ? "api-key" : subscription ? "subscription" : "neither";
131
+ // A flag that is present but not truthy is ignored by every consumer, and
132
+ // "MAESTRO_PREFER_SUBSCRIPTION_AUTH is set" reads as the opposite. Say so.
133
+ const preferNote = preferPresent && !preferOn
134
+ ? ` MAESTRO_PREFER_SUBSCRIPTION_AUTH is present but not 1/true/yes, so every consumer ignores it.`
135
+ : "";
136
+
137
+ const head = `Seat auth (G4): ${verdict} — names present: ${names}`;
138
+ const enforceNote = enforced
139
+ ? ` ${REQUIRE_CONSOLE_KEY_VAR} is set, so this is a failure.`
140
+ : ` Warning only today; set ${REQUIRE_CONSOLE_KEY_VAR}=1 to make it a doctor failure once the fleet has migrated.`;
141
+
142
+ if (verdict === "api-key") {
143
+ const msg = cohortSeat
144
+ ? `${head}. This engine-cohort seat needs no Anthropic credential; the key is harmless but unused by the engine lanes.${preferNote}`
145
+ : `${head}. This is the fleet posture: a Console API key in a capped workspace.${preferNote}`;
146
+ return { verdict, level: "ok", compliant: true, enforced, seatEngine, present, absent, msg, remedy: "" };
147
+ }
148
+
149
+ if (verdict === "both") {
150
+ const msg =
151
+ `${head}. AMBIGUOUS — this seat carries BOTH rails. The runtime adapter blanks ANTHROPIC_API_KEY on a ` +
152
+ `seat that is on subscription auth (lib/runtime/adapter.mjs subscriptionAuth), so the subscription rail ` +
153
+ `wins the lanes that go through it while a lane that passes the environment straight through may use the ` +
154
+ `key: spend lands in two places and neither is authoritative. Migrate, do not stack — ${CONSOLE_KEY_REMEDY}.` +
155
+ preferNote + enforceNote;
156
+ return { verdict, level: enforced ? "fail" : "warn", compliant: false, enforced, seatEngine, present, absent, msg, remedy: CONSOLE_KEY_REMEDY };
157
+ }
158
+
159
+ if (verdict === "subscription") {
160
+ const msg =
161
+ `${head}. Subscription OAuth is NOT the fleet posture${cohortSeat ? " and an engine-cohort seat should hold no Anthropic credential at all" : ""} — ` +
162
+ `it has no per-agent ceiling, no central spend visibility, and it expires headlessly. ${CONSOLE_KEY_REMEDY}.` +
163
+ preferNote + enforceNote;
164
+ return { verdict, level: enforced ? "fail" : "warn", compliant: false, enforced, seatEngine, present, absent, msg, remedy: CONSOLE_KEY_REMEDY };
165
+ }
166
+
167
+ // neither
168
+ if (cohortSeat) {
169
+ return {
170
+ verdict, level: "ok", compliant: true, enforced, seatEngine, present, absent,
171
+ msg: `${head}. Expected on an engine-cohort seat: it reaches models through cohort-llm and holds no Anthropic credential.`,
172
+ remedy: "",
173
+ };
174
+ }
175
+ return {
176
+ verdict, level: "fail", compliant: false, enforced, seatEngine, present, absent,
177
+ msg: `${head}. This seat has NO Claude credential at all — every spawn will fail, not fall back. ${CONSOLE_KEY_REMEDY}.`,
178
+ remedy: CONSOLE_KEY_REMEDY,
179
+ };
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // The fleet inventory: a template, and rows that paste into it
184
+ // ---------------------------------------------------------------------------
185
+
186
+ /**
187
+ * The inventory columns, in order. The measured ones (Seat → Env names
188
+ * present) a seat can fill in about itself; the rest are Console-side facts
189
+ * only a human with Console access knows.
190
+ */
191
+ export const INVENTORY_COLUMNS = Object.freeze([
192
+ "Seat",
193
+ "Agent id",
194
+ "Machine",
195
+ "Auth verdict",
196
+ "Env names present",
197
+ "Checked",
198
+ "Operator",
199
+ "Anthropic account backing it",
200
+ "Console workspace",
201
+ "Monthly cap",
202
+ "Key id (last 4)",
203
+ ".env mode 600",
204
+ "Old token revoked",
205
+ "Verified (who / when)",
206
+ ]);
207
+
208
+ /** Keys of the columns a seat measures about itself, in `inventoryRow` order. */
209
+ const MEASURED = Object.freeze(["seat", "agentId", "machine", "verdict", "names", "checked"]);
210
+
211
+ /** A markdown table cell: never break the table, never invent content. */
212
+ function cell(v) {
213
+ const s = v === undefined || v === null ? "" : String(v);
214
+ return s.replace(/\|/g, "\\|").replace(/\r?\n/g, " ").trim();
215
+ }
216
+
217
+ /**
218
+ * One inventory row. The measured fields are filled; every Console-side field
219
+ * is left blank for the operator — a tool that guesses them teaches people to
220
+ * trust a guess.
221
+ *
222
+ * @param {{seat?:string, agentId?:string, machine?:string, verdict?:string,
223
+ * names?:string[]|string, checked?:string}} [fields]
224
+ * @returns {string} a markdown table row with `INVENTORY_COLUMNS.length` cells
225
+ */
226
+ export function inventoryRow(fields = {}) {
227
+ const f = fields || {};
228
+ const names = Array.isArray(f.names) ? f.names.join(" + ") : f.names || "";
229
+ const measured = { seat: f.seat, agentId: f.agentId, machine: f.machine, verdict: f.verdict, names, checked: f.checked };
230
+ const cells = INVENTORY_COLUMNS.map((_, i) => (i < MEASURED.length ? cell(measured[MEASURED[i]]) : ""));
231
+ return `| ${cells.join(" | ")} |`;
232
+ }
233
+
234
+ /**
235
+ * Turn the org directory into seat stubs for the template. Members that the
236
+ * directory marks human are dropped; anything ambiguous is KEPT, because a
237
+ * missing row is a seat nobody audits while a spurious row is one an operator
238
+ * deletes in a second.
239
+ *
240
+ * @param {object[]} [members]
241
+ * @returns {Array<{seat:string, agentId:string}>}
242
+ */
243
+ export function seatsFromDirectory(members = []) {
244
+ const out = [];
245
+ const seen = new Set();
246
+ for (const m of Array.isArray(members) ? members : []) {
247
+ if (!m || typeof m !== "object") continue;
248
+ const human =
249
+ m.isHuman === true ||
250
+ /^human$/i.test(String(m.memberType || m.kind || m.type || "")) ||
251
+ /^(human|person)$/i.test(String(m.category || ""));
252
+ if (human) continue;
253
+ const agentId = String(m.agentId || m.slug || m.memberId || m.id || "").trim();
254
+ const seat = String(m.name || m.displayName || m.firstName || agentId || "").trim();
255
+ if (!seat && !agentId) continue;
256
+ const dedupe = `${seat.toLowerCase()}::${agentId.toLowerCase()}`;
257
+ if (seen.has(dedupe)) continue;
258
+ seen.add(dedupe);
259
+ out.push({ seat, agentId });
260
+ }
261
+ out.sort((a, b) => (a.seat || a.agentId).localeCompare(b.seat || b.agentId));
262
+ return out;
263
+ }
264
+
265
+ /**
266
+ * The per-machine inventory an operator fills in, with one pre-filled row per
267
+ * known member. Pure: the caller supplies the seats and the timestamp.
268
+ *
269
+ * @param {{seats?:Array<{seat:string, agentId:string}>, generatedAt?:string,
270
+ * source?:string, note?:string}} [o]
271
+ * @returns {string} markdown
272
+ */
273
+ export function fleetInventoryTemplate(o = {}) {
274
+ const seats = Array.isArray(o.seats) ? o.seats : [];
275
+ const generatedAt = o.generatedAt || "";
276
+ const source = o.source || "the org directory";
277
+ const lines = [];
278
+ lines.push("# G4 seat-auth inventory — off subscription OAuth, onto Console API keys");
279
+ lines.push("");
280
+ lines.push(`Generated by \`maestro seat-auth --fleet\`${generatedAt ? ` at ${generatedAt}` : ""} from ${source}.`);
281
+ lines.push("");
282
+ lines.push("**How to fill this in.** A seat's environment lives on the seat's own machine — there is no");
283
+ lines.push("remote inventory and this tool does not pretend otherwise. On EACH machine, in the agent's");
284
+ lines.push("directory, run:");
285
+ lines.push("");
286
+ lines.push("```sh");
287
+ lines.push("maestro seat-auth --row");
288
+ lines.push("```");
289
+ lines.push("");
290
+ lines.push("and paste the line it prints over that seat's row below. It fills the measured columns");
291
+ lines.push("(seat, agent id, machine, verdict, which variable NAMES are present, when it was checked)");
292
+ lines.push("and leaves the Console-side columns blank, because only a human with Console access knows them.");
293
+ lines.push("");
294
+ lines.push("**Never record a key value here** — or in a ticket, a commit, or a chat message. The last four");
295
+ lines.push("characters of a key id identify it; nothing else about a credential belongs in a document.");
296
+ lines.push("");
297
+ lines.push(`Migration steps, per seat: \`${RUNBOOK}\`. When every row reads \`api-key\`, set`);
298
+ lines.push(`\`${REQUIRE_CONSOLE_KEY_VAR}=1\` on the fleet so a seat that regresses fails \`maestro doctor\``);
299
+ lines.push("instead of warning quietly.");
300
+ if (o.note) { lines.push(""); lines.push(String(o.note)); }
301
+ lines.push("");
302
+ lines.push(`| ${INVENTORY_COLUMNS.join(" | ")} |`);
303
+ lines.push(`| ${INVENTORY_COLUMNS.map(() => "---").join(" | ")} |`);
304
+ if (seats.length === 0) {
305
+ lines.push(inventoryRow({}));
306
+ } else {
307
+ for (const s of seats) lines.push(inventoryRow({ seat: s.seat, agentId: s.agentId }));
308
+ }
309
+ lines.push("");
310
+ lines.push("## Record once, for the fleet");
311
+ lines.push("");
312
+ lines.push(`- Total live seats (rows above: **${seats.length}** from ${source} — confirm against the live fleet).`);
313
+ lines.push("- How many distinct Anthropic accounts backed the fleet before migration, and whose. A personal");
314
+ lines.push(" account powering several 24/7 seats is the most exposed shape.");
315
+ lines.push("- The date the last subscription token was revoked. That is the date this gate closes.");
316
+ lines.push(`- The date \`${REQUIRE_CONSOLE_KEY_VAR}=1\` was set fleet-wide, which is what stops it regressing.`);
317
+ lines.push("");
318
+ return lines.join("\n");
319
+ }
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // The edge: `maestro seat-auth [--row] [--fleet] [--json]`
323
+ // ---------------------------------------------------------------------------
324
+
325
+ export const USAGE = `Usage:
326
+ maestro seat-auth This seat's billing rail: verdict, variable NAMES, remediation
327
+ maestro seat-auth --row One markdown row to paste into the G4 inventory
328
+ maestro seat-auth --fleet The per-machine inventory template, one row per org member
329
+ maestro seat-auth --json The same verdict as JSON
330
+
331
+ No value of any credential is ever printed. Exit 1 when the seat's verdict is a
332
+ failure (no credential at all, or a non-compliant rail with ${REQUIRE_CONSOLE_KEY_VAR}=1).
333
+ Runbook: ${RUNBOOK}
334
+ `;
335
+
336
+ /** config/agent.json, or {} — identity for the row, never a credential. */
337
+ function readAgentJson(agentRoot, deps) {
338
+ try {
339
+ const { readFileSync, existsSync } = deps.fs || {};
340
+ const p = `${agentRoot}/config/agent.json`;
341
+ if (existsSync && !existsSync(p)) return {};
342
+ return JSON.parse(String(readFileSync(p, "utf8")));
343
+ } catch {
344
+ return {};
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Run `maestro seat-auth …`. Every edge is injectable; nothing is written.
350
+ *
351
+ * @param {string[]} [argv]
352
+ * @param {{env?:Record<string,string>, agentRoot?:string, hostname?:()=>string,
353
+ * now?:number, fs?:object, readDotEnv?:(root:string)=>Record<string,string>,
354
+ * fetchDirectory?:(o:object)=>Promise<object[]>, orgConfig?:object,
355
+ * seatEngine?:string, stdout?:(s:string)=>void}} [deps]
356
+ * @returns {Promise<number>} exit code
357
+ */
358
+ export async function runSeatAuth(argv = [], deps = {}) {
359
+ const write = deps.stdout || ((s) => process.stdout.write(s));
360
+ const env = deps.env || process.env;
361
+ const agentRoot = deps.agentRoot || env.AGENT_ROOT || env.AGENT_DIR || process.cwd();
362
+ const now = Number.isFinite(deps.now) ? deps.now : Date.now();
363
+ const checked = new Date(now).toISOString().slice(0, 10);
364
+
365
+ let wantRow = false;
366
+ let wantFleet = false;
367
+ let wantJson = false;
368
+ for (const a of argv) {
369
+ if (a === "--row") wantRow = true;
370
+ else if (a === "--fleet") wantFleet = true;
371
+ else if (a === "--json") wantJson = true;
372
+ else if (a === "--help" || a === "-h") { write(USAGE); return 0; }
373
+ else if (a.startsWith("-")) { write(`seat-auth: unknown flag ${a}\n\n${USAGE}`); return 1; }
374
+ }
375
+
376
+ // The seat's effective env, exactly as every consumer sees it: .env wins over
377
+ // the ambient environment (dotenv never overrides an already-set variable is
378
+ // the LOADER's rule; the FILE is the seat's declared intent, which is what an
379
+ // audit is about — doctor merges the same way).
380
+ const readDotEnv = deps.readDotEnv || (async (root) => {
381
+ const { parseDotEnv } = await import("./doctor-checks.mjs");
382
+ const fs = deps.fs || (await import("node:fs"));
383
+ try { return parseDotEnv(fs.readFileSync(`${root}/.env`, "utf8")); } catch { return {}; }
384
+ });
385
+ const dotEnv = await readDotEnv(agentRoot);
386
+ const ambient = {};
387
+ for (const n of [...SEAT_AUTH_VARS, REQUIRE_CONSOLE_KEY_VAR]) if (nonEmpty(env[n])) ambient[n] = String(env[n]);
388
+ const effective = { ...ambient, ...dotEnv };
389
+ const sources = {};
390
+ for (const n of SEAT_AUTH_VARS) {
391
+ if (nonEmpty(dotEnv[n])) sources[n] = ".env";
392
+ else if (nonEmpty(ambient[n])) sources[n] = "env";
393
+ }
394
+
395
+ let seatEngine = deps.seatEngine;
396
+ if (!seatEngine) {
397
+ try {
398
+ const { readSeatEngine } = await import("../runtime/adapter.mjs");
399
+ seatEngine = readSeatEngine(agentRoot);
400
+ } catch { seatEngine = "claude"; }
401
+ }
402
+
403
+ const verdict = seatAuthVerdict(effective, { seatEngine, sources });
404
+ const agentJson = readAgentJson(agentRoot, { fs: deps.fs || (await import("node:fs")) });
405
+ const seat = String(agentJson.firstName || agentJson.name || "").split(/\s+/)[0] || String(agentRoot).split("/").pop();
406
+ const agentId = String(agentJson.agentId || agentJson.memberId || agentJson.slug || env.COHORT_AGENT_ID || "");
407
+ const hostname = (deps.hostname || (() => { try { return osHostname(); } catch { return ""; } }))();
408
+
409
+ if (wantFleet) {
410
+ let members = [];
411
+ let source = "the org directory";
412
+ try {
413
+ const fetchDirectory = deps.fetchDirectory || (await import("../org/client.mjs")).fetchDirectory;
414
+ const { loadOrgConfig } = await import("../org/client.mjs");
415
+ members = await fetchDirectory({ cfg: deps.orgConfig || loadOrgConfig(agentRoot) });
416
+ } catch {
417
+ members = [];
418
+ }
419
+ const seats = seatsFromDirectory(members);
420
+ if (seats.length === 0) source = "the org directory (EMPTY — this seat is not enrolled, or the directory was unreachable; add the rows by hand)";
421
+ const md = fleetInventoryTemplate({ seats, generatedAt: new Date(now).toISOString(), source });
422
+ if (wantJson) { write(JSON.stringify({ seats, generatedAt: new Date(now).toISOString() }, null, 2) + "\n"); return 0; }
423
+ write(md + "\n");
424
+ return 0;
425
+ }
426
+
427
+ if (wantRow) {
428
+ write(inventoryRow({ seat, agentId, machine: hostname, verdict: verdict.verdict, names: verdict.present, checked }) + "\n");
429
+ return verdict.level === "fail" ? 1 : 0;
430
+ }
431
+
432
+ if (wantJson) {
433
+ write(JSON.stringify({
434
+ seat, agentId, machine: hostname, checked,
435
+ verdict: verdict.verdict, level: verdict.level, compliant: verdict.compliant,
436
+ enforced: verdict.enforced, seatEngine: verdict.seatEngine,
437
+ present: verdict.present, absent: verdict.absent,
438
+ remedy: verdict.remedy, runbook: RUNBOOK,
439
+ }, null, 2) + "\n");
440
+ return verdict.level === "fail" ? 1 : 0;
441
+ }
442
+
443
+ const mark = verdict.level === "ok" ? "✓" : verdict.level === "warn" ? "!" : "✗";
444
+ write(`${mark} ${verdict.msg}\n`);
445
+ write(` seat ${seat}${agentId ? ` (${agentId})` : ""} on ${hostname || "this machine"}; engine ${verdict.seatEngine}\n`);
446
+ write(` absent: ${verdict.absent.length ? verdict.absent.join(", ") : "none"}\n`);
447
+ write(` paste into the G4 inventory: maestro seat-auth --row (template: maestro seat-auth --fleet)\n`);
448
+ return verdict.level === "fail" ? 1 : 0;
449
+ }
450
+
451
+ export default {
452
+ SEAT_AUTH_VARS,
453
+ REQUIRE_CONSOLE_KEY_VAR,
454
+ CONSOLE_KEY_REMEDY,
455
+ RUNBOOK,
456
+ INVENTORY_COLUMNS,
457
+ enforcementEnabled,
458
+ seatAuthVerdict,
459
+ inventoryRow,
460
+ seatsFromDirectory,
461
+ fleetInventoryTemplate,
462
+ runSeatAuth,
463
+ };
@@ -13,8 +13,10 @@
13
13
  * Commands:
14
14
  *
15
15
  * status [--brief|--json] lock holder · heartbeat age · main session id ·
16
- * mux session · plist installed/loaded · peers ·
17
- * open handoffs · restart/upgrade flags
16
+ * [--all] mux session · plist installed/loaded · peers ·
17
+ * open handoffs · restart/upgrade flags · the
18
+ * current-work block (lib/session/current-work;
19
+ * `--all` = the operator's unscoped view)
18
20
  * attach print the exact `tmux attach` / `screen -r` line;
19
21
  * exec it when stdin+stdout are a TTY
20
22
  * start bootstrap the plist if not loaded, then kickstart
@@ -55,7 +57,8 @@
55
57
 
56
58
  "use strict";
57
59
 
58
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
60
+ import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
61
+ import { randomUUID } from "node:crypto";
59
62
  import { basename, dirname, join } from "node:path";
60
63
  import { homedir } from "node:os";
61
64
  import { spawnSync } from "node:child_process";
@@ -63,9 +66,12 @@ import { parseArgs } from "node:util";
63
66
  import { pathToFileURL } from "node:url";
64
67
  import { writeJsonAtomic } from "../fs-atomic.mjs";
65
68
  import { resolveAgentRoot } from "../agent-root.mjs";
66
- import { resolveClaudeBin } from "../claude-bin.mjs";
69
+ import { buildSpawn } from "../runtime/adapter.mjs";
70
+ import { resolveSeatSpawn } from "../runtime/seat-engine.mjs";
67
71
  import { sessionPermissionArgs } from "../session-permissions.mjs";
68
72
  import { withParallelism } from "../prompts/parallelism.mjs";
73
+ import { envFileCommand, renderEnvFile } from "../session/launch-args.mjs";
74
+ import { currentWorkBlock } from "../session/current-work.mjs";
69
75
 
70
76
  /** The SDK's own package.json version — what "installed" means for an upgrade notice. */
71
77
  function ownVersion() {
@@ -229,7 +235,32 @@ export function peerPermissionArgs({ env = process.env, allowedTools } = {}) {
229
235
  * @returns {string[]}
230
236
  */
231
237
  export function buildSpawnArgs({ first, slug, prompt, env, allowedTools }) {
232
- return ["--name", `${first}-${slug}`, ...peerPermissionArgs({ env, allowedTools }), withParallelism(prompt)];
238
+ const spec = peerSpawn({ first, slug, prompt, env, allowedTools, bin: "claude" });
239
+ // Only the claude engine is wired for peers, so a failure is a defect.
240
+ if (!spec.ok) throw new Error(`buildSpawnArgs: ${spec.error.message}`);
241
+ return spec.argv;
242
+ }
243
+
244
+ /**
245
+ * The whole peer spawn — binary, argv, env — from the runtime adapter
246
+ * (lib/runtime/adapter.mjs, lane "peer"). `bin` is the caller's injected binary
247
+ * (deps.claudeBin); absent, the adapter resolves it.
248
+ *
249
+ * W4-E1 (row 5): `seat` is the seat's engine resolved once for this spawn
250
+ * (lib/runtime/seat-engine.mjs). A claude seat adds no fields (unchanged); an
251
+ * engine-cohort seat launches `cli.mjs session --name …` with the seat token.
252
+ * @param {{first:string, slug:string, prompt:string, env?:object, allowedTools?:string[], bin?:string, seat?:{fields:object}}} o
253
+ */
254
+ export function peerSpawn({ first, slug, prompt, env, allowedTools, bin, seat = { fields: {} } }) {
255
+ return buildSpawn({
256
+ lane: "peer",
257
+ bin: bin || undefined,
258
+ name: `${first}-${slug}`,
259
+ permissions: peerPermissionArgs({ env, allowedTools }),
260
+ prompt: withParallelism(prompt),
261
+ env,
262
+ ...seat.fields,
263
+ });
233
264
  }
234
265
 
235
266
  /**
@@ -280,6 +311,9 @@ function defaultDeps() {
280
311
  },
281
312
  /** The version the running SDK reports — an upgrade notice for it is already applied. */
282
313
  installedVersion: ownVersion(),
314
+ /** W4-E1: a private (0600) file that must not exist yet — the engine env file. */
315
+ writePrivateFile(path, text) { writeFileSync(path, text, { mode: 0o600, flag: "wx" }); chmodSync(path, 0o600); },
316
+ envFileId: () => randomUUID(),
283
317
  };
284
318
  }
285
319
 
@@ -542,7 +576,7 @@ function ensureLoaded(ctx, say) {
542
576
  export function usage() {
543
577
  return `maestro session <command>
544
578
 
545
- status [--brief|--json] lock holder, heartbeat age, mux session, plist, peers, handoffs
579
+ status [--brief|--json|--all] lock holder, heartbeat age, mux session, plist, peers, handoffs, current work (--all: unscoped)
546
580
  attach print the attach command (tmux attach / screen -r); exec it on a TTY
547
581
  start load the launchd job if needed, then kickstart it
548
582
  stop launchctl bootout the session job
@@ -557,6 +591,7 @@ export function usage() {
557
591
  const FLAGS = {
558
592
  json: { type: "boolean", default: false },
559
593
  brief: { type: "boolean", default: false },
594
+ all: { type: "boolean", default: false },
560
595
  force: { type: "boolean", default: false },
561
596
  name: { type: "string" },
562
597
  cwd: { type: "string" },
@@ -596,9 +631,21 @@ export async function run(argv = [], opts = {}) {
596
631
 
597
632
  case "status": {
598
633
  const s = snapshot(ctx);
634
+ // The "what am I doing right now" block (lib/session/current-work) —
635
+ // the same fact `session_status` returns and the daemon's replies carry.
636
+ // Scoped like every other reader: by default a live session from any
637
+ // conversation is reported as existing only; `--all` is the operator's
638
+ // own view (the files under state/ are theirs to read anyway — the flag
639
+ // only says the printout may carry what they hold, so it is not pasted
640
+ // onward by reflex). Fail-open: an unreadable state dir is an empty
641
+ // block, not a failed status.
642
+ try { s.currentWork = await (d.currentWorkBlock || currentWorkBlock)(agentRoot, { now: d.now(), audience: flags.all ? null : {} }); } catch { s.currentWork = ""; }
599
643
  if (flags.json) say(JSON.stringify(s, null, 2));
600
644
  else if (flags.brief) say(briefLine(s));
601
- else for (const l of humanStatus(s)) say(l);
645
+ else {
646
+ for (const l of humanStatus(s)) say(l);
647
+ if (s.currentWork) for (const l of s.currentWork.split("\n")) say(l);
648
+ }
602
649
  return { ok: true, code: 0, lines, data: s };
603
650
  }
604
651
 
@@ -679,12 +726,33 @@ export async function run(argv = [], opts = {}) {
679
726
 
680
727
  const cfg = await loadCfg(ctx);
681
728
  const kind = chooseMux(d, cfg);
682
- const bin = d.claudeBin || resolveClaudeBin();
683
- const args = buildSpawnArgs({ first, slug, prompt, env: d.env, allowedTools: cfg.allowedTools });
729
+ // W4-E1: the seat's engine, once for this spawn; an engine-cohort peer never falls back to claude.
730
+ let seat = { engine: "claude", fields: {} };
731
+ try { seat = (await (d.seatSpawn || ((root) => resolveSeatSpawn(root)))(agentRoot)) || seat; } catch { seat = { engine: "claude", fields: {} }; }
732
+ const spec = peerSpawn({ first, slug, prompt, env: d.env, allowedTools: cfg.allowedTools, bin: d.claudeBin, seat });
733
+ if (!spec.ok) return fail(`session spawn: ${spec.error.message}`);
734
+ // W4-E1: an engine-cohort peer's env (seat token, Claude credentials
735
+ // scrubbed) rides a 0600 file the session sources and deletes before exec
736
+ // (launch-args ENGINE_ENV_SCRIPT). The mux cannot carry it: a tmux server
737
+ // already running gives a new session its own global env, not the client's.
738
+ let envFile = null;
739
+ if (seat.engine === "cohort") {
740
+ envFile = join(paths.dir, `engine-env-${muxName}-${d.envFileId()}.env`);
741
+ try {
742
+ mkdirSync(paths.dir, { recursive: true });
743
+ d.writePrivateFile(envFile, renderEnvFile(spec.env));
744
+ } catch (e) {
745
+ return fail(`session spawn: could not write the engine env file ${envFile}: ${e && e.message ? e.message : e}`);
746
+ }
747
+ }
748
+ const command = envFile ? envFileCommand({ envFile, bin: spec.bin, args: spec.argv }) : [spec.bin, ...spec.argv];
684
749
  const r = kind === "tmux"
685
- ? d.exec("tmux", ["new-session", "-d", "-s", muxName, "-c", cwd, "--", bin, ...args], { cwd, env: d.env })
686
- : d.exec("screen", ["-dmS", muxName, bin, ...args], { cwd, env: d.env });
687
- if (r.status !== 0) return fail(`session spawn: ${kind} returned ${r.status}${r.stderr ? `: ${r.stderr.trim()}` : ""}`);
750
+ ? d.exec("tmux", ["new-session", "-d", "-s", muxName, "-c", cwd, "--", ...command], { cwd, env: d.env })
751
+ : d.exec("screen", ["-dmS", muxName, ...command], { cwd, env: d.env });
752
+ if (r.status !== 0) {
753
+ if (envFile) { try { unlinkSync(envFile); } catch { /* never written */ } }
754
+ return fail(`session spawn: ${kind} returned ${r.status}${r.stderr ? `: ${r.stderr.trim()}` : ""}`);
755
+ }
688
756
 
689
757
  const startedAt = new Date(d.now()).toISOString();
690
758
  const reg = updatePeers(paths, (cur) => [
@@ -22,7 +22,7 @@
22
22
  import { spawnSync } from "node:child_process";
23
23
  import { normalizeCard, appendCards, repoLabel } from "./cards.mjs";
24
24
  import { isRepoDenied } from "./config.mjs";
25
- import { resolveClaudeBin, daemonClaudeArgs, augmentedPath } from "../claude-bin.mjs";
25
+ import { buildSpawn } from "../runtime/adapter.mjs";
26
26
 
27
27
  const DISTILL_TIMEOUT_MS = 60_000;
28
28
  const MAX_TRANSCRIPT_CHARS = 16_000;
@@ -118,13 +118,15 @@ export function extractCardsFromModelOutput(text) {
118
118
  /** Default distiller: spawn a cheap `claude --print` pass. Fail-open → "". */
119
119
  export function defaultDistiller(prompt, opts = {}) {
120
120
  try {
121
- const model = opts.model || "haiku";
122
- const args = ["--print", "--model", model, ...daemonClaudeArgs()];
123
- const res = spawnSync(resolveClaudeBin(), args, {
124
- input: prompt,
121
+ // argv + env from the runtime adapter (lane "capture"): --print --model <m>
122
+ // <daemon MCP args>, prompt on stdin, PATH augmented.
123
+ const spec = buildSpawn({ lane: "capture", model: opts.model || "haiku", prompt, mcp: {}, env: process.env });
124
+ if (!spec.ok) return "";
125
+ const res = spawnSync(spec.bin, spec.argv, {
126
+ input: spec.stdin,
125
127
  encoding: "utf8",
126
128
  timeout: DISTILL_TIMEOUT_MS,
127
- env: { ...process.env, PATH: augmentedPath() },
129
+ env: spec.env,
128
130
  maxBuffer: 8 * 1024 * 1024,
129
131
  });
130
132
  if (res.status === 0 && res.stdout) return res.stdout;