@cohortapp/agent-sdk 2.16.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 (529) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +23 -7
  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/poller-daemon-setup.md +4 -1
  9. package/docs/guides/setup-wizard.md +1 -1
  10. package/docs/runbooks/fleet-rollout.md +156 -0
  11. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  12. package/lib/action-executor.js +19 -3
  13. package/lib/budget-guard.mjs +279 -3
  14. package/lib/channels/base-adapter.mjs +3 -1
  15. package/lib/channels/contract.mjs +2 -1
  16. package/lib/channels/inbox-item.mjs +8 -0
  17. package/lib/claude-bin.mjs +5 -6
  18. package/lib/cli/doctor-checks.mjs +141 -10
  19. package/lib/cli/global-setup-extras.mjs +5 -1
  20. package/lib/cli/inbox.mjs +100 -15
  21. package/lib/cli/seat-auth.mjs +463 -0
  22. package/lib/cli/session.mjs +80 -12
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/global-config.mjs +63 -1
  25. package/lib/collective/presence.mjs +142 -5
  26. package/lib/comms/send-gate.mjs +559 -1
  27. package/lib/context/budget.mjs +327 -0
  28. package/lib/context/history-scope.mjs +138 -0
  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/economics.mjs +9 -0
  138. package/lib/model-router/failover.mjs +67 -16
  139. package/lib/model-router/llm-task.mjs +39 -3
  140. package/lib/model-router/resolve.mjs +95 -3
  141. package/lib/model-router/spawn.mjs +46 -47
  142. package/lib/model-router/taxonomy.mjs +126 -4
  143. package/lib/org/cost-sync.mjs +141 -11
  144. package/lib/org/inbound/broadcast.mjs +289 -0
  145. package/lib/org/inbound/collective.mjs +375 -0
  146. package/lib/org/inbound/directedness.mjs +96 -8
  147. package/lib/org/inbound/facts.mjs +82 -4
  148. package/lib/org/inbound/hydrate.mjs +555 -51
  149. package/lib/org/inbound/project.mjs +22 -0
  150. package/lib/org/inbound/surfaces.mjs +14 -0
  151. package/lib/org/llm-token.mjs +879 -0
  152. package/lib/org/mesh.mjs +61 -0
  153. package/lib/org/messaging.mjs +3 -1
  154. package/lib/org/protocol.checksum +1 -1
  155. package/lib/org/protocol.mjs +15 -0
  156. package/lib/org/quota.mjs +520 -0
  157. package/lib/org/tool-surface.mjs +104 -16
  158. package/lib/org/ui-parity.mjs +16 -1
  159. package/lib/org/work-ledger.mjs +37 -6
  160. package/lib/rate-guard.mjs +114 -1
  161. package/lib/resource-governor.mjs +41 -6
  162. package/lib/runtime/adapter.mjs +823 -0
  163. package/lib/runtime/child-env.mjs +191 -0
  164. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  165. package/lib/runtime/seat-engine.mjs +162 -0
  166. package/lib/session/ask-ledger.mjs +271 -0
  167. package/lib/session/current-work.mjs +676 -0
  168. package/lib/session/feed-core.mjs +40 -3
  169. package/lib/session/launch-args.mjs +56 -4
  170. package/lib/session/status-summary.mjs +26 -9
  171. package/lib/session/upgrade-notice.mjs +42 -0
  172. package/lib/setup/claude-probe.mjs +117 -13
  173. package/lib/setup/enrich.mjs +13 -10
  174. package/lib/setup/sections/model.mjs +39 -13
  175. package/lib/telemetry/collect.mjs +208 -9
  176. package/lib/upgrade/ignored-drift.mjs +105 -0
  177. package/lib/voice/post-call-brief.mjs +30 -17
  178. package/package.json +15 -3
  179. package/plugins/maestro-skills/skills/board-work.md +5 -0
  180. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  181. package/plugins/maestro-skills/skills/main-session.md +18 -7
  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 +29 -2
  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 +104 -59
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +804 -157
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/lib/session-router.mjs +310 -42
  198. package/scripts/daemon/maestro-daemon.mjs +11 -0
  199. package/scripts/daemon/prompt-builder.mjs +121 -12
  200. package/scripts/daemon/responder.mjs +315 -146
  201. package/scripts/daemon/sdk-version.mjs +98 -16
  202. package/scripts/eval/probe-gateway.mjs +635 -0
  203. package/scripts/eval/replay/extract.mjs +270 -0
  204. package/scripts/eval/replay/grade.mjs +260 -0
  205. package/scripts/eval/replay/lib/config.mjs +50 -0
  206. package/scripts/eval/replay/lib/effects.mjs +65 -0
  207. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  208. package/scripts/eval/replay/lib/judge.mjs +72 -0
  209. package/scripts/eval/replay/lib/redact.mjs +136 -0
  210. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  211. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  212. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  213. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  214. package/scripts/eval/replay/report.mjs +185 -0
  215. package/scripts/eval/replay/run.mjs +404 -0
  216. package/scripts/fleet/rollout.mjs +1094 -0
  217. package/scripts/hooks/pre-send-audit.sh +36 -245
  218. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  219. package/scripts/hooks/validate-state-yaml.sh +190 -0
  220. package/scripts/huddle/huddle-llm.mjs +361 -0
  221. package/scripts/huddle/huddle-server.mjs +46 -121
  222. package/scripts/local-triggers/autoupdate.sh +448 -78
  223. package/scripts/local-triggers/run-trigger.sh +13 -0
  224. package/scripts/maintenance/pin-integrity.mjs +364 -0
  225. package/scripts/poll-slack-events.sh +41 -9
  226. package/scripts/poller/slack-socket-mode.mjs +28 -3
  227. package/scripts/session/supervisor.mjs +80 -13
  228. package/scripts/spawn-session.sh +13 -0
  229. package/bin/maestro.test.mjs +0 -1574
  230. package/lib/action-executor.test.mjs +0 -871
  231. package/lib/archetype.test.mjs +0 -132
  232. package/lib/assurance/plan-note.test.mjs +0 -234
  233. package/lib/assurance/room-budget.test.mjs +0 -486
  234. package/lib/assurance/tier.test.mjs +0 -174
  235. package/lib/autonomy.test.mjs +0 -66
  236. package/lib/backlog.test.mjs +0 -302
  237. package/lib/backup/policy.test.mjs +0 -305
  238. package/lib/budget-escalate.test.mjs +0 -232
  239. package/lib/budget-guard.envelope.test.mjs +0 -476
  240. package/lib/budget-guard.test.mjs +0 -427
  241. package/lib/cadence-bus-requeue.test.mjs +0 -83
  242. package/lib/cadence-bus-schedule.test.mjs +0 -194
  243. package/lib/cadence-bus.test.mjs +0 -720
  244. package/lib/cadences.test.mjs +0 -230
  245. package/lib/capability/inventory.test.mjs +0 -232
  246. package/lib/capability.test.mjs +0 -78
  247. package/lib/channels/base-adapter.test.mjs +0 -590
  248. package/lib/channels/channels.test.mjs +0 -371
  249. package/lib/channels/contract.test.mjs +0 -162
  250. package/lib/channels/inbox-item.test.mjs +0 -368
  251. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  252. package/lib/channels/pairing.test.mjs +0 -270
  253. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  254. package/lib/channels/slack-adapter.test.mjs +0 -212
  255. package/lib/channels/telegram-adapter.test.mjs +0 -306
  256. package/lib/channels/voice/adapter.test.mjs +0 -278
  257. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  258. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  259. package/lib/charter.test.mjs +0 -89
  260. package/lib/claude-bin.test.mjs +0 -131
  261. package/lib/cli/board.test.mjs +0 -227
  262. package/lib/cli/design.test.mjs +0 -270
  263. package/lib/cli/doctor-checks.test.mjs +0 -336
  264. package/lib/cli/global-setup-extras.test.mjs +0 -462
  265. package/lib/cli/inbox.test.mjs +0 -230
  266. package/lib/cli/session-ack.test.mjs +0 -63
  267. package/lib/cli/session.test.mjs +0 -613
  268. package/lib/collective/capture.test.mjs +0 -121
  269. package/lib/collective/cards.test.mjs +0 -114
  270. package/lib/collective/config.test.mjs +0 -123
  271. package/lib/collective/global-config.test.mjs +0 -220
  272. package/lib/collective/global-skills.test.mjs +0 -126
  273. package/lib/collective/presence.test.mjs +0 -95
  274. package/lib/collective/recall.test.mjs +0 -116
  275. package/lib/collective/vendor-skills.test.mjs +0 -306
  276. package/lib/comms/send-gate.test.mjs +0 -770
  277. package/lib/comms.test.mjs +0 -41
  278. package/lib/cost/ledger-row.test.mjs +0 -183
  279. package/lib/design/design-md.test.mjs +0 -318
  280. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  281. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  282. package/lib/design/fixtures/foundation.json +0 -133
  283. package/lib/design/refresh-gate.test.mjs +0 -144
  284. package/lib/design/write.test.mjs +0 -241
  285. package/lib/diagnostics/alerts.test.mjs +0 -318
  286. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  287. package/lib/diagnostics/counters.test.mjs +0 -206
  288. package/lib/diagnostics/events.test.mjs +0 -290
  289. package/lib/diagnostics/otel.test.mjs +0 -196
  290. package/lib/diagnostics/trace.test.mjs +0 -251
  291. package/lib/env-compat.test.mjs +0 -104
  292. package/lib/execution/disposition.test.mjs +0 -553
  293. package/lib/execution/drive.test.mjs +0 -270
  294. package/lib/execution/effects.test.mjs +0 -344
  295. package/lib/execution/intake.test.mjs +0 -389
  296. package/lib/execution/journal.test.mjs +0 -261
  297. package/lib/execution/match.test.mjs +0 -235
  298. package/lib/execution/pipeline.test.mjs +0 -392
  299. package/lib/execution/route.test.mjs +0 -186
  300. package/lib/execution/surface-policy.test.mjs +0 -162
  301. package/lib/fs-atomic.test.mjs +0 -72
  302. package/lib/fs-ownership.test.mjs +0 -158
  303. package/lib/goals/admission.test.mjs +0 -164
  304. package/lib/goals/classify.test.mjs +0 -167
  305. package/lib/goals/collaborate.test.mjs +0 -336
  306. package/lib/goals/gaps.test.mjs +0 -284
  307. package/lib/goals/loop.test.mjs +0 -845
  308. package/lib/hooks/bus.test.mjs +0 -387
  309. package/lib/identity/persona.test.mjs +0 -142
  310. package/lib/kpi-sensors.test.mjs +0 -278
  311. package/lib/kpi.test.mjs +0 -244
  312. package/lib/learning/config.test.mjs +0 -75
  313. package/lib/learning/counters.test.mjs +0 -69
  314. package/lib/learning/curator-consolidate.test.mjs +0 -238
  315. package/lib/learning/curator.test.mjs +0 -106
  316. package/lib/learning/reflect.test.mjs +0 -0
  317. package/lib/learning/session-index.test.mjs +0 -125
  318. package/lib/learning/skill-writer.test.mjs +0 -210
  319. package/lib/mandate/audit.test.mjs +0 -195
  320. package/lib/mandate/contract.test.mjs +0 -185
  321. package/lib/mandate/derive.test.mjs +0 -274
  322. package/lib/mandate/model.test.mjs +0 -164
  323. package/lib/mandate/refresh.test.mjs +0 -389
  324. package/lib/mcp/server.test.mjs +0 -426
  325. package/lib/model-router/auth-profiles.test.mjs +0 -580
  326. package/lib/model-router/catalog.test.mjs +0 -385
  327. package/lib/model-router/economics.test.mjs +0 -438
  328. package/lib/model-router/failover.test.mjs +0 -439
  329. package/lib/model-router/health.test.mjs +0 -338
  330. package/lib/model-router/integration-coverage.test.mjs +0 -831
  331. package/lib/model-router/integration.test.mjs +0 -564
  332. package/lib/model-router/ledger.test.mjs +0 -415
  333. package/lib/model-router/llm-task.test.mjs +0 -392
  334. package/lib/model-router/org-credentials.test.mjs +0 -265
  335. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  336. package/lib/model-router/reconcile.test.mjs +0 -316
  337. package/lib/model-router/repair.test.mjs +0 -180
  338. package/lib/model-router/spawn.test.mjs +0 -446
  339. package/lib/model-router/taxonomy.test.mjs +0 -410
  340. package/lib/model-router.test.mjs +0 -1207
  341. package/lib/org/activity.test.mjs +0 -134
  342. package/lib/org/approvals.test.mjs +0 -216
  343. package/lib/org/awareness.test.mjs +0 -159
  344. package/lib/org/board-mine-cache.test.mjs +0 -53
  345. package/lib/org/board.test.mjs +0 -187
  346. package/lib/org/bootstrap-context.test.mjs +0 -153
  347. package/lib/org/client.test.mjs +0 -1206
  348. package/lib/org/cohort-client.test.mjs +0 -126
  349. package/lib/org/cost-sync.test.mjs +0 -153
  350. package/lib/org/doctor.test.mjs +0 -346
  351. package/lib/org/engagement-ledger.test.mjs +0 -112
  352. package/lib/org/engagement.test.mjs +0 -739
  353. package/lib/org/handoff.test.mjs +0 -269
  354. package/lib/org/inbound/directedness.test.mjs +0 -668
  355. package/lib/org/inbound/facts.test.mjs +0 -471
  356. package/lib/org/inbound/hydrate.test.mjs +0 -453
  357. package/lib/org/inbound/index.test.mjs +0 -429
  358. package/lib/org/inbound/project.test.mjs +0 -287
  359. package/lib/org/integration-tools.test.mjs +0 -160
  360. package/lib/org/keys.test.mjs +0 -92
  361. package/lib/org/knowledge.test.mjs +0 -326
  362. package/lib/org/leases.test.mjs +0 -235
  363. package/lib/org/mesh-directives.test.mjs +0 -110
  364. package/lib/org/mesh-integration.test.mjs +0 -127
  365. package/lib/org/mesh.test.mjs +0 -400
  366. package/lib/org/messaging.test.mjs +0 -471
  367. package/lib/org/param-contract.test.mjs +0 -477
  368. package/lib/org/policy.test.mjs +0 -237
  369. package/lib/org/protocol.checksum.test.mjs +0 -90
  370. package/lib/org/protocol.test.mjs +0 -323
  371. package/lib/org/push.test.mjs +0 -792
  372. package/lib/org/registry.test.mjs +0 -100
  373. package/lib/org/resource-tools.test.mjs +0 -361
  374. package/lib/org/tool-access.test.mjs +0 -144
  375. package/lib/org/tool-surface-integration.test.mjs +0 -120
  376. package/lib/org/tool-surface.test.mjs +0 -1268
  377. package/lib/org/typing.test.mjs +0 -291
  378. package/lib/org/ui-parity.test.mjs +0 -560
  379. package/lib/org/verify.test.mjs +0 -194
  380. package/lib/org/work-ledger.test.mjs +0 -273
  381. package/lib/plan/adoption-e2e.test.mjs +0 -366
  382. package/lib/plan/budget-enforcement.test.mjs +0 -400
  383. package/lib/plan/compile.test.mjs +0 -382
  384. package/lib/plan/emit.test.mjs +0 -269
  385. package/lib/plan/explain.test.mjs +0 -188
  386. package/lib/prompts/parallelism.test.mjs +0 -177
  387. package/lib/rag/rag.test.mjs +0 -505
  388. package/lib/rate-guard.test.mjs +0 -272
  389. package/lib/reactive-gate.test.mjs +0 -57
  390. package/lib/render.test.mjs +0 -68
  391. package/lib/resource-governor.test.mjs +0 -488
  392. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  393. package/lib/scheduling/jitter.test.mjs +0 -140
  394. package/lib/secrets/broker.test.mjs +0 -280
  395. package/lib/secrets/providers.test.mjs +0 -274
  396. package/lib/security/audit-engine.test.mjs +0 -424
  397. package/lib/security/coerce-args.test.mjs +0 -281
  398. package/lib/security/dangerous-tools.test.mjs +0 -68
  399. package/lib/security/external-content.test.mjs +0 -84
  400. package/lib/security/redact.test.mjs +0 -441
  401. package/lib/security/secret-equal.test.mjs +0 -55
  402. package/lib/session/config.test.mjs +0 -92
  403. package/lib/session/feed-core.test.mjs +0 -198
  404. package/lib/session/first-run.test.mjs +0 -121
  405. package/lib/session/frontdoor.test.mjs +0 -205
  406. package/lib/session/handoffs.test.mjs +0 -183
  407. package/lib/session/identity.test.mjs +0 -180
  408. package/lib/session/inbox-claims.test.mjs +0 -286
  409. package/lib/session/launch-args.test.mjs +0 -157
  410. package/lib/session/liveness.test.mjs +0 -100
  411. package/lib/session/status-summary.test.mjs +0 -118
  412. package/lib/session-permissions.test.mjs +0 -120
  413. package/lib/setup/claude-probe.test.mjs +0 -187
  414. package/lib/setup/completeness.test.mjs +0 -110
  415. package/lib/setup/context-pack.test.mjs +0 -89
  416. package/lib/setup/enrich.test.mjs +0 -115
  417. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  418. package/lib/setup/integration.test.mjs +0 -162
  419. package/lib/setup/io.test.mjs +0 -77
  420. package/lib/setup/runner.test.mjs +0 -132
  421. package/lib/setup/sections/identity.test.mjs +0 -234
  422. package/lib/setup/sections/inventory.test.mjs +0 -198
  423. package/lib/setup/sections/learning.test.mjs +0 -81
  424. package/lib/setup/sections/mandate.test.mjs +0 -388
  425. package/lib/setup/sections/messaging.test.mjs +0 -127
  426. package/lib/setup/sections/model.test.mjs +0 -240
  427. package/lib/setup/sections/org.test.mjs +0 -346
  428. package/lib/setup/sections/orgmail.test.mjs +0 -118
  429. package/lib/setup/sections/recovery.test.mjs +0 -98
  430. package/lib/setup/sections/subagents.test.mjs +0 -429
  431. package/lib/setup/sections/verify.test.mjs +0 -175
  432. package/lib/setup/sot.test.mjs +0 -81
  433. package/lib/setup/state.test.mjs +0 -115
  434. package/lib/singleton.test.mjs +0 -151
  435. package/lib/subagents/cli.test.mjs +0 -389
  436. package/lib/subagents/client.test.mjs +0 -309
  437. package/lib/subagents/gap.test.mjs +0 -234
  438. package/lib/subagents/lock.test.mjs +0 -248
  439. package/lib/subagents/manifest.test.mjs +0 -175
  440. package/lib/subagents/refs.test.mjs +0 -204
  441. package/lib/subagents/resolve.test.mjs +0 -422
  442. package/lib/subagents/schema.test.mjs +0 -328
  443. package/lib/telemetry/alerts.test.mjs +0 -109
  444. package/lib/telemetry/collect.test.mjs +0 -1274
  445. package/lib/tool-definitions-integration.test.mjs +0 -83
  446. package/lib/tool-definitions.test.mjs +0 -437
  447. package/lib/upgrade/global-refresh.test.mjs +0 -65
  448. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  449. package/lib/upgrade/post-steps.test.mjs +0 -200
  450. package/lib/upgrade/verify.test.mjs +0 -164
  451. package/lib/util/fetch-timeout.test.mjs +0 -202
  452. package/lib/util/reconnect.test.mjs +0 -369
  453. package/lib/util/unhandled.test.mjs +0 -216
  454. package/lib/voice/outbound.test.mjs +0 -69
  455. package/lib/voice/session-rotation.test.mjs +0 -114
  456. package/lib/voice/stt.test.mjs +0 -226
  457. package/lib/voice/voice.test.mjs +0 -990
  458. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  459. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  460. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  461. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  462. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  463. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  464. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  465. package/scripts/ci/check.test.mjs +0 -194
  466. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  467. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  468. package/scripts/collective/hook-runner.test.mjs +0 -173
  469. package/scripts/cost/fleet-digest.test.mjs +0 -207
  470. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  471. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  472. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  473. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  474. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  475. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  476. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  477. package/scripts/daemon/assurance.test.mjs +0 -1791
  478. package/scripts/daemon/board-mirror.test.mjs +0 -165
  479. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  480. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  481. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  482. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  483. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  484. package/scripts/daemon/classifier.test.mjs +0 -266
  485. package/scripts/daemon/classify-kind.test.mjs +0 -40
  486. package/scripts/daemon/context-compiler.test.mjs +0 -300
  487. package/scripts/daemon/deliver.test.mjs +0 -564
  488. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  489. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  490. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  491. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  492. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  493. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  494. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  495. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  496. package/scripts/daemon/integration.test.mjs +0 -149
  497. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  498. package/scripts/daemon/lib/session-router.test.mjs +0 -295
  499. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  500. package/scripts/daemon/prompt-builder.test.mjs +0 -344
  501. package/scripts/daemon/responder-cost.test.mjs +0 -68
  502. package/scripts/daemon/responder-history.test.mjs +0 -185
  503. package/scripts/daemon/sdk-version.test.mjs +0 -31
  504. package/scripts/daemon/session-lock.test.mjs +0 -252
  505. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  506. package/scripts/daemon/typing-registry.test.mjs +0 -102
  507. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  508. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  509. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  510. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  511. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  512. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  513. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  514. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  515. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  516. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  517. package/scripts/poller-launchd/install.test.mjs +0 -243
  518. package/scripts/restore-from-backup.test.mjs +0 -181
  519. package/scripts/session/feed.test.mjs +0 -196
  520. package/scripts/session/supervisor-sh.test.mjs +0 -218
  521. package/scripts/session/supervisor.test.mjs +0 -482
  522. package/scripts/setup/configure-macos.test.mjs +0 -306
  523. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  524. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  525. package/scripts/setup/generate-capability.test.mjs +0 -134
  526. package/scripts/setup/init-agent.test.mjs +0 -370
  527. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  528. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  529. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -0,0 +1,364 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pin-integrity.mjs — did the local fixes survive, and is the daemon running
4
+ * them, and how far behind the release has each pin drifted?
5
+ *
6
+ * A `.maestroignore` pin is FILE-granular: `maestro upgrade` never touches a
7
+ * pinned path, so the seat keeps its hand-patched copy — and silently misses
8
+ * every upstream line that path will ever receive. Nothing alarmed on that
9
+ * debt, so a pin taken for a two-line fix could sit hundreds of release lines
10
+ * behind with no signal. This script measures it and is meant to run as a
11
+ * doctor check: exit 0 is "no pin is behind past the threshold".
12
+ *
13
+ * For every path listed in .maestroignore (blank lines and `#` comments
14
+ * skipped), report one of:
15
+ * identical the local file is byte-identical to the vendored SDK copy —
16
+ * the local fix is GONE (an upgrade/rollback rewrote it, or it
17
+ * was never applied).
18
+ * not-loaded the local file differs but its mtime post-dates the daemon
19
+ * boot (state/cadence-bus/health.json), so the RUNNING daemon
20
+ * still executes the old code. Only meaningful for
21
+ * daemon-loaded modules (scripts/daemon, scripts/collective,
22
+ * lib; never *.test.mjs).
23
+ * ok differs from vendored and predates the daemon boot.
24
+ * missing-local the pin names a file that does not exist locally.
25
+ * no-vendored the SDK ships no copy under that path (nothing can clobber it).
26
+ *
27
+ * Every differing pin also carries `delta {release_only, local_only, behind}`
28
+ * measured by `diff`: release_only = lines the release has that we lack (the
29
+ * debt), local_only = the patch we keep. `behind` = release_only > 0.
30
+ *
31
+ * A third reference, upstream main, closes the gap the vendored release cannot
32
+ * see: a seat at exactly the current release with a pin byte-identical to that
33
+ * release reads `identical` (fix lost) — or `ok` — while the fix it was taken
34
+ * for has already merged upstream and will only arrive with the next release.
35
+ * When an SDK source checkout is known (`--repo DIR`, else `MAESTRO_SDK_REPO`)
36
+ * each pin is also compared with `git -C DIR show <ref>:<path>` (`--upstream`,
37
+ * default `origin/main`) and carries `upstream_state` + `delta_upstream
38
+ * {upstream_only, local_only}`:
39
+ * identical byte-identical to the file at <ref>.
40
+ * ok differs, but <ref> has no line we lack.
41
+ * behind-main upstream_only exceeds the threshold — main already carries
42
+ * lines this pin will never receive through an upgrade.
43
+ * no-upstream the path does not exist at <ref> (a notice, never a failure).
44
+ * No checkout, or a ref that does not resolve, skips the comparison with a
45
+ * one-line notice; the vendored report and its exit code are unaffected.
46
+ *
47
+ * The unpinned walk over scripts/daemon, scripts/collective, scripts/org,
48
+ * scripts/session and lib classifies every other .mjs/.js/.py/.sh file:
49
+ * unpinned differs from the vendored copy and is NOT pinned — an
50
+ * upgrade/rollback will overwrite it.
51
+ * unshipped a local file with no release twin (seat authorship). An
52
+ * upgrade cannot overwrite what it does not ship, so it needs
53
+ * no pin; listed so it is not mistaken for drift.
54
+ *
55
+ * Usage: node scripts/maintenance/pin-integrity.mjs [--json] [--root DIR] [--threshold N]
56
+ * [--repo DIR] [--upstream REF]
57
+ * MAESTRO_PIN_BEHIND_MAX=N allowed release-only (and upstream-only) lines per
58
+ * pin (default 0: any such line alarms). --threshold wins.
59
+ * MAESTRO_SDK_REPO=DIR SDK source checkout for the upstream comparison;
60
+ * --repo wins. Unset = comparison skipped.
61
+ * Exit 1 when any pin's release_only OR upstream_only exceeds the threshold;
62
+ * 0 otherwise.
63
+ * `diff` is required (macOS and Linux ship it); if it cannot be spawned the
64
+ * delta falls back to a line-multiset comparison so the check still runs.
65
+ *
66
+ * @module scripts/maintenance/pin-integrity
67
+ */
68
+ import { readFileSync, writeFileSync, statSync, existsSync, readdirSync, mkdtempSync, rmSync } from "node:fs";
69
+ import { spawnSync } from "node:child_process";
70
+ import { join, relative, resolve } from "node:path";
71
+ import { tmpdir } from "node:os";
72
+ import { createHash } from "node:crypto";
73
+ import { fileURLToPath } from "node:url";
74
+
75
+ /** Roots whose files the unpinned/unshipped walk classifies. */
76
+ export const WALK_ROOTS = Object.freeze(["scripts/daemon", "scripts/collective", "scripts/org", "scripts/session", "lib"]);
77
+
78
+ /** Paths whose code the daemon holds in memory — "not-loaded" applies only here. */
79
+ const DAEMON_LOADED_RE = /^(scripts\/daemon|scripts\/collective|lib)\//;
80
+
81
+ function sha(p) { return createHash("sha256").update(readFileSync(p)).digest("hex").slice(0, 16); }
82
+
83
+ function daemonBoot(root) {
84
+ try {
85
+ const h = JSON.parse(readFileSync(join(root, "state", "cadence-bus", "health.json"), "utf8"));
86
+ const startedAt = h.stats && h.stats.started_at ? new Date(h.stats.started_at) : null;
87
+ return { pid: h.pid ?? null, startedAt: startedAt && !Number.isNaN(startedAt.getTime()) ? startedAt : null };
88
+ } catch { return { pid: null, startedAt: null }; }
89
+ }
90
+
91
+ /** The pinned paths from `<root>/.maestroignore` (blank + `#` lines skipped). */
92
+ export function readPins(root) {
93
+ const f = join(root, ".maestroignore");
94
+ if (!existsSync(f)) return [];
95
+ return readFileSync(f, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
96
+ }
97
+
98
+ function walk(dir, out = []) {
99
+ if (!existsSync(dir)) return out;
100
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
101
+ const p = join(dir, e.name);
102
+ if (e.isDirectory()) { if (e.name !== "node_modules") walk(p, out); }
103
+ else if (/\.(mjs|js|py|sh)$/.test(e.name)) out.push(p);
104
+ }
105
+ return out;
106
+ }
107
+
108
+ /** Pure-JS fallback: line multiset difference, used only when `diff` cannot spawn. */
109
+ function lineMultisetDelta(vendText, localText) {
110
+ const count = (t) => { const m = new Map(); for (const l of t.split("\n")) m.set(l, (m.get(l) || 0) + 1); return m; };
111
+ const a = count(vendText), b = count(localText);
112
+ let ro = 0, lo = 0;
113
+ for (const [l, n] of a) ro += Math.max(0, n - (b.get(l) || 0));
114
+ for (const [l, n] of b) lo += Math.max(0, n - (a.get(l) || 0));
115
+ return { release_only: ro, local_only: lo };
116
+ }
117
+
118
+ /** Lines only in `a` / only in `b`, by `diff` (multiset fallback when it cannot spawn). */
119
+ function countDiff(a, b) {
120
+ let ao = 0, bo = 0;
121
+ const d = spawnSync("diff", [a, b], { encoding: "utf8" });
122
+ if (!d.error && typeof d.stdout === "string") {
123
+ for (const ln of d.stdout.split("\n")) { if (ln.startsWith("< ")) ao++; else if (ln.startsWith("> ")) bo++; }
124
+ } else {
125
+ ({ release_only: ao, local_only: bo } = lineMultisetDelta(readFileSync(a, "utf8"), readFileSync(b, "utf8")));
126
+ }
127
+ return { a_only: ao, b_only: bo };
128
+ }
129
+
130
+ /** `{release_only, local_only, behind}` between the vendored and local copies. */
131
+ export function measureDelta(vend, local) {
132
+ const { a_only: ro, b_only: lo } = countDiff(vend, local);
133
+ return { release_only: ro, local_only: lo, behind: ro > 0 };
134
+ }
135
+
136
+ /** `{upstream_only, local_only}` between the file at the upstream ref and the local copy. */
137
+ export function measureUpstreamDelta(upstreamCopy, local) {
138
+ const { a_only: uo, b_only: lo } = countDiff(upstreamCopy, local);
139
+ return { upstream_only: uo, local_only: lo };
140
+ }
141
+
142
+ /** Default upstream ref for the third reference. */
143
+ export const DEFAULT_UPSTREAM_REF = "origin/main";
144
+
145
+ /**
146
+ * Resolve the SDK source checkout: explicit option → MAESTRO_SDK_REPO → null.
147
+ * @param {string|undefined} explicit
148
+ * @param {NodeJS.ProcessEnv} [env]
149
+ * @returns {string|null} absolute path, or null when nothing names a checkout
150
+ */
151
+ export function resolveUpstreamRepo(explicit, env = process.env) {
152
+ const cand = explicit !== undefined && explicit !== null && explicit !== "" ? explicit : env.MAESTRO_SDK_REPO;
153
+ return cand && String(cand).trim() ? resolve(String(cand).trim()) : null;
154
+ }
155
+
156
+ function git(repo, args, encoding = "utf8") {
157
+ return spawnSync("git", ["-C", repo, ...args], { encoding, maxBuffer: 64 * 1024 * 1024 });
158
+ }
159
+
160
+ /** First non-empty stderr line, for notices and per-file reasons. */
161
+ function firstLine(r) {
162
+ const t = r && r.error ? r.error.message : String((r && r.stderr) || "");
163
+ return (t.split("\n").map((l) => l.trim()).find(Boolean) || "").slice(0, 200);
164
+ }
165
+
166
+ /**
167
+ * Open the upstream reference: `{enabled, ref, repo, commit, notice}`. Never
168
+ * throws — a missing checkout, a non-repo or an unresolvable ref returns
169
+ * `enabled:false` with a one-line `notice`.
170
+ * @param {string|null} repo from resolveUpstreamRepo()
171
+ * @param {string} ref
172
+ */
173
+ export function openUpstream(repo, ref = DEFAULT_UPSTREAM_REF) {
174
+ const base = { enabled: false, ref, repo, commit: null, notice: null };
175
+ if (!repo) return { ...base, notice: "upstream comparison skipped: no --repo and MAESTRO_SDK_REPO unset" };
176
+ if (!existsSync(repo)) return { ...base, notice: `upstream comparison skipped: ${repo} does not exist` };
177
+ let rp;
178
+ try { rp = git(repo, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]); } catch (e) { rp = { error: e }; }
179
+ if (rp.error) return { ...base, notice: `upstream comparison skipped: git could not run in ${repo} (${firstLine(rp)})` };
180
+ if (rp.status !== 0 || !String(rp.stdout).trim()) return { ...base, notice: `upstream comparison skipped: ${ref} does not resolve in ${repo}${firstLine(rp) ? " (" + firstLine(rp) + ")" : ""}` };
181
+ return { ...base, enabled: true, commit: String(rp.stdout).trim() };
182
+ }
183
+
184
+ /**
185
+ * `git show <ref>:<path>` as a Buffer, or `{error}` when the path is absent at
186
+ * the ref (or git fails) — fail-open per file.
187
+ */
188
+ function upstreamBlob(repo, ref, rel) {
189
+ let r;
190
+ try { r = git(repo, ["show", `${ref}:${rel}`], "buffer"); } catch (e) { r = { error: e }; }
191
+ if (r.error || r.status !== 0) return { error: firstLine({ ...r, stderr: r.stderr ? r.stderr.toString() : "" }) || `not at ${ref}` };
192
+ return { buf: r.stdout };
193
+ }
194
+
195
+ /**
196
+ * Resolve the behind threshold: explicit option → MAESTRO_PIN_BEHIND_MAX → 0.
197
+ * @param {number|string|undefined} explicit
198
+ * @param {NodeJS.ProcessEnv} [env]
199
+ */
200
+ export function resolveThreshold(explicit, env = process.env) {
201
+ const cand = explicit !== undefined && explicit !== null && explicit !== "" ? explicit : env.MAESTRO_PIN_BEHIND_MAX;
202
+ const n = Number(cand);
203
+ return Number.isFinite(n) && n >= 0 ? n : 0;
204
+ }
205
+
206
+ /**
207
+ * Build the report for an agent root.
208
+ * @param {string} root the agent repo
209
+ * @param {object} [o]
210
+ * @param {number} [o.threshold] allowed release-only / upstream-only lines per pin (default env/0)
211
+ * @param {string} [o.repo] SDK source checkout for the upstream reference (default env MAESTRO_SDK_REPO)
212
+ * @param {string} [o.upstream] git ref in that checkout (default origin/main)
213
+ * @param {Date} [o.now] report timestamp (test seam)
214
+ * @returns {object} report with `.summary`, `.pinned`, `.unpinned_divergences`,
215
+ * `.unshipped_local`, `.threshold`, `.over_threshold[]`, `.upstream`,
216
+ * `.over_threshold_main[]`, `.exitCode`
217
+ */
218
+ export function pinIntegrity(root, o = {}) {
219
+ const threshold = resolveThreshold(o.threshold);
220
+ const vendored = join(root, "node_modules", "@cohortapp", "agent-sdk");
221
+ const boot = daemonBoot(root);
222
+ const pins = readPins(root);
223
+ const pinSet = new Set(pins);
224
+ const upstream = openUpstream(resolveUpstreamRepo(o.repo), o.upstream || DEFAULT_UPSTREAM_REF);
225
+ const scratch = upstream.enabled ? mkdtempSync(join(tmpdir(), "pin-integrity-upstream-")) : null;
226
+ const rows = [];
227
+ try {
228
+ for (const rel of pins) {
229
+ const local = join(root, rel), vend = join(vendored, rel);
230
+ if (!existsSync(local)) { rows.push({ path: rel, state: "missing-local" }); continue; }
231
+ const localSha = sha(local);
232
+ const row = { path: rel };
233
+ if (!existsSync(vend)) { Object.assign(row, { state: "no-vendored", local_sha: localSha }); }
234
+ else {
235
+ const vendSha = sha(vend);
236
+ const same = localSha === vendSha;
237
+ const mtime = statSync(local).mtime;
238
+ let state = same ? "identical" : "ok";
239
+ if (!same && boot.startedAt && mtime > boot.startedAt && DAEMON_LOADED_RE.test(rel) && !/\.test\.mjs$/.test(rel)) state = "not-loaded";
240
+ const delta = same ? null : measureDelta(vend, local);
241
+ Object.assign(row, { state, local_sha: localSha, vendored_sha: vendSha, mtime: mtime.toISOString(), delta });
242
+ }
243
+ if (upstream.enabled) Object.assign(row, upstreamRow(upstream, scratch, rel, local, localSha, threshold));
244
+ rows.push(row);
245
+ }
246
+ } finally { if (scratch) rmSync(scratch, { recursive: true, force: true }); }
247
+
248
+ const unpinned = [];
249
+ const unshipped = [];
250
+ for (const sub of WALK_ROOTS) {
251
+ for (const p of walk(join(root, sub))) {
252
+ const rel = relative(root, p);
253
+ if (pinSet.has(rel)) continue;
254
+ const vend = join(vendored, rel);
255
+ if (!existsSync(vend)) { unshipped.push({ path: rel }); continue; }
256
+ if (sha(p) !== sha(vend)) unpinned.push({ path: rel, mtime: statSync(p).mtime.toISOString() });
257
+ }
258
+ }
259
+
260
+ const over = rows.filter((r) => r.delta && r.delta.release_only > threshold).map((r) => ({ path: r.path, release_only: r.delta.release_only }));
261
+ const overMain = rows.filter((r) => r.upstream_state === "behind-main").map((r) => ({ path: r.path, upstream_only: r.delta_upstream.upstream_only }));
262
+ const summary = {
263
+ identical: rows.filter((r) => r.state === "identical").length,
264
+ not_loaded: rows.filter((r) => r.state === "not-loaded").length,
265
+ ok: rows.filter((r) => r.state === "ok").length,
266
+ unpinned: unpinned.length,
267
+ unshipped: unshipped.length,
268
+ behind: rows.filter((r) => r.delta && r.delta.behind).length,
269
+ release_only_lines: rows.reduce((n, r) => n + (r.delta ? r.delta.release_only : 0), 0),
270
+ };
271
+ if (upstream.enabled) {
272
+ summary.behind_main = overMain.length;
273
+ summary.upstream_only_lines = rows.reduce((n, r) => n + (r.delta_upstream ? r.delta_upstream.upstream_only : 0), 0);
274
+ }
275
+ return {
276
+ checked_at: (o.now || new Date()).toISOString(),
277
+ daemon: { pid: boot.pid, started_at: boot.startedAt ? boot.startedAt.toISOString() : null },
278
+ threshold,
279
+ pinned: rows,
280
+ unpinned_divergences: unpinned,
281
+ unshipped_local: unshipped,
282
+ over_threshold: over,
283
+ upstream,
284
+ over_threshold_main: overMain,
285
+ summary,
286
+ exitCode: over.length || overMain.length ? 1 : 0,
287
+ };
288
+ }
289
+
290
+ /**
291
+ * The upstream columns for one pin: `upstream_state`, `upstream_sha`,
292
+ * `delta_upstream {upstream_only, local_only}` (null when identical), and
293
+ * `upstream_reason` when the path is not at the ref.
294
+ */
295
+ function upstreamRow(upstream, scratch, rel, local, localSha, threshold) {
296
+ const blob = upstreamBlob(upstream.repo, upstream.ref, rel);
297
+ if (blob.error) return { upstream_state: "no-upstream", upstream_reason: blob.error, upstream_sha: null, delta_upstream: null };
298
+ const upSha = createHash("sha256").update(blob.buf).digest("hex").slice(0, 16);
299
+ if (upSha === localSha) return { upstream_state: "identical", upstream_sha: upSha, delta_upstream: null };
300
+ const tmp = join(scratch, upSha + "-" + rel.replace(/[^A-Za-z0-9._-]/g, "_"));
301
+ writeFileSync(tmp, blob.buf);
302
+ const delta_upstream = measureUpstreamDelta(tmp, local);
303
+ const upstream_state = delta_upstream.upstream_only > threshold ? "behind-main" : "ok";
304
+ return { upstream_state, upstream_sha: upSha, delta_upstream };
305
+ }
306
+
307
+ /** The one-line summary, in the shape the wrap log and doctor grep for. */
308
+ export function summaryLine(report) {
309
+ const s = report.summary;
310
+ const base = `summary: identical(fix lost)=${s.identical} not-loaded=${s.not_loaded} ok=${s.ok} unpinned=${s.unpinned} unshipped=${s.unshipped} behind=${s.behind} (release-only lines ${s.release_only_lines})`;
311
+ if (report.upstream && report.upstream.enabled) return `${base} behind_main=${s.behind_main} (upstream-only lines ${s.upstream_only_lines})`;
312
+ return base;
313
+ }
314
+
315
+ /** The upstream column for one pinned row, e.g. ` / behind-main (+37 upstream-only lines)`; empty when the comparison did not run. */
316
+ function formatUpstreamCol(r) {
317
+ if (!r.upstream_state) return "";
318
+ if (r.upstream_state === "no-upstream") return ` / no-upstream (${r.upstream_reason})`;
319
+ if (r.upstream_state === "identical") return " / identical (upstream)";
320
+ const d = r.delta_upstream;
321
+ return ` / ${r.upstream_state} (+${d.upstream_only} upstream-only lines, ${d.local_only} local-only)`;
322
+ }
323
+
324
+ /** Human-readable report text (non-JSON mode). */
325
+ export function formatReport(report) {
326
+ const out = [];
327
+ const up = report.upstream;
328
+ out.push(`pin-integrity ${report.checked_at} daemon pid ${report.daemon.pid} up since ${report.daemon.started_at} threshold ${report.threshold}`);
329
+ if (up && up.enabled) out.push(` upstream reference: ${up.ref} @ ${up.commit.slice(0, 12)} (${up.repo}) — each pin reads "<vendored state> / <upstream state>"`);
330
+ else if (up && up.notice) out.push(` ${up.notice}`);
331
+ for (const r of report.pinned) {
332
+ out.push(` ${r.state.padEnd(13)} ${r.path}${r.mtime ? " (mtime " + r.mtime + ")" : ""}${r.delta ? ` [local-only ${r.delta.local_only} / release-only ${r.delta.release_only}${r.delta.behind ? " BEHIND" : ""}]` : ""}${formatUpstreamCol(r)}`);
333
+ }
334
+ if (report.unpinned_divergences.length) {
335
+ out.push(" UNPINNED local divergences (an upgrade/rollback will overwrite these):");
336
+ for (const u of report.unpinned_divergences) out.push(` ${u.path} (mtime ${u.mtime})`);
337
+ }
338
+ if (report.unshipped_local.length) {
339
+ out.push(" unshipped local files (no release twin; nothing to pin):");
340
+ for (const u of report.unshipped_local) out.push(` ${u.path}`);
341
+ }
342
+ out.push(` ${summaryLine(report)}`);
343
+ if (report.over_threshold.length) {
344
+ out.push(` BEHIND past threshold (${report.threshold} release-only line(s) allowed per pin):`);
345
+ for (const p of report.over_threshold) out.push(` ${p.path} release-only ${p.release_only}`);
346
+ }
347
+ if (report.over_threshold_main && report.over_threshold_main.length) {
348
+ out.push(` BEHIND-MAIN past threshold (${report.threshold} upstream-only line(s) allowed per pin; fixes already on ${up.ref} that no upgrade will deliver until released):`);
349
+ for (const p of report.over_threshold_main) out.push(` ${p.path} upstream-only ${p.upstream_only}`);
350
+ }
351
+ return out.join("\n");
352
+ }
353
+
354
+ function main(argv) {
355
+ const args = argv.slice(2);
356
+ const flagVal = (name) => (args.includes(name) ? args[args.indexOf(name) + 1] : undefined);
357
+ const root = flagVal("--root") ? resolve(flagVal("--root")) : resolve(fileURLToPath(new URL(".", import.meta.url)), "..", "..");
358
+ const report = pinIntegrity(root, { threshold: flagVal("--threshold"), repo: flagVal("--repo"), upstream: flagVal("--upstream") });
359
+ if (args.includes("--json")) console.log(JSON.stringify(report));
360
+ else console.log(formatReport(report));
361
+ process.exit(report.exitCode);
362
+ }
363
+
364
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main(process.argv);
@@ -1,24 +1,56 @@
1
1
  #!/bin/bash
2
- # poll-slack-events.sh — Fetch buffered events from Railway relay server
3
- # Writes events to state/inbox/slack/ for the monitoring loop to process
4
- # Run via cron every 5-10 seconds for near-instant response
5
- set -e
2
+ # poll-slack-events.sh — Fetch buffered events from the Railway-hosted webhook
3
+ # relay and write them to state/inbox/slack/ for the monitoring loop to process.
4
+ # Run by ai.maestro.<first>-poll-relay every 5 s (scripts/local-triggers/
5
+ # generate-plists.sh §2). See docs/guides/webhook-relay-setup.md.
6
+ #
7
+ # A seat with NO relay configured exits 0 and does nothing. This script used to
8
+ # default SLACK_EVENTS_RELAY_URL to a hostname that has since gone away, and to
9
+ # run under `set -e` with a JSON parse of whatever came back — so on every seat
10
+ # without Slack it fetched a 404 page, failed to parse it, and exited 1, every
11
+ # five seconds, for weeks (one seat: 102,568 runs, `launchctl list` showing a
12
+ # standing "1"). Nothing was wrong on those seats; the job simply had no relay
13
+ # to poll. Configuration is the gate: no URL, no work, clean exit.
14
+ #
15
+ # The relay URL comes from the environment or from <agent>/.env
16
+ # (SLACK_EVENTS_RELAY_URL). No hardcoded default: a URL that nobody set is not
17
+ # a URL to poll.
18
+ set -u
6
19
 
7
20
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
- AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
9
- EVENTS_URL="${SLACK_EVENTS_RELAY_URL:-https://slack-events-server-production.up.railway.app/events}"
21
+ AGENT_REPO_DIR="${AGENT_DIR:-${AGENT_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}}"
10
22
  INBOX_DIR="$AGENT_REPO_DIR/state/inbox/slack"
23
+
24
+ # .env is the seat's configuration; launchd hands this script a bare env.
25
+ EVENTS_URL="${SLACK_EVENTS_RELAY_URL:-}"
26
+ if [ -z "$EVENTS_URL" ] && [ -r "$AGENT_REPO_DIR/.env" ]; then
27
+ EVENTS_URL="$(grep -E '^SLACK_EVENTS_RELAY_URL=' "$AGENT_REPO_DIR/.env" 2>/dev/null | tail -1 | cut -d= -f2- | tr -d "\"'[:space:]")"
28
+ fi
29
+ if [ -z "$EVENTS_URL" ]; then
30
+ # Not configured on this seat: nothing to poll, and nothing to say every 5 s.
31
+ exit 0
32
+ fi
33
+
11
34
  mkdir -p "$INBOX_DIR"
12
35
 
13
- RESPONSE=$(curl -s --max-time 5 "$EVENTS_URL" 2>/dev/null)
14
- COUNT=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null)
36
+ # A relay that is down or answers with something other than JSON is a transient
37
+ # fact about the relay, not a failure of this seat: log one line, exit 0, and
38
+ # the next tick tries again. Under `set -e` a bad body used to be exit 1.
39
+ RESPONSE="$(curl -s --fail --max-time 5 "$EVENTS_URL" 2>/dev/null)" || {
40
+ echo "[poll-slack-events $(date -u +%H:%M:%SZ)] relay unreachable or non-2xx: $EVENTS_URL" >&2
41
+ exit 0
42
+ }
43
+ COUNT="$(printf '%s' "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null)" || {
44
+ echo "[poll-slack-events $(date -u +%H:%M:%SZ)] relay answered with a body that is not JSON: $EVENTS_URL" >&2
45
+ exit 0
46
+ }
15
47
 
16
48
  if [ "$COUNT" = "0" ] || [ -z "$COUNT" ]; then
17
49
  exit 0
18
50
  fi
19
51
 
20
52
  # Write each event to a separate file in the inbox
21
- echo "$RESPONSE" | python3 -c "
53
+ printf '%s' "$RESPONSE" | python3 -c "
22
54
  import sys, json, os
23
55
 
24
56
  data = json.load(sys.stdin)
@@ -423,6 +423,20 @@ export function eventToInboxItem(envelope, { ownSlackId, principalSlackId, agent
423
423
  * Returns `{ url }` on success or `{ error }` on failure. The URL is
424
424
  * single-use and expires quickly — call this every time we open a socket.
425
425
  */
426
+ /**
427
+ * What the seat's SLACK_APP_LEVEL_TOKEN says about whether Socket Mode is
428
+ * meant to run here. "absent" is a seat without Slack (a clean no-op, exit
429
+ * 0); "malformed" is a configured token that cannot work (EX_CONFIG, 78);
430
+ * "ok" is an xapp- token.
431
+ * @param {string|undefined} token
432
+ * @returns {"absent"|"malformed"|"ok"}
433
+ */
434
+ export function classifyAppToken(token) {
435
+ const t = typeof token === "string" ? token.trim() : "";
436
+ if (!t) return "absent";
437
+ return t.startsWith("xapp-") ? "ok" : "malformed";
438
+ }
439
+
426
440
  export async function openSocketConnection({ appToken, fetchFn = fetch }) {
427
441
  if (!appToken) {
428
442
  return { error: "missing-app-token" };
@@ -883,10 +897,21 @@ async function main() {
883
897
  loadEnvFromFile(agentRoot);
884
898
 
885
899
  const appToken = process.env.SLACK_APP_LEVEL_TOKEN || "";
886
- if (!appToken || !appToken.startsWith("xapp-")) {
887
- error(agentRoot, "SLACK_APP_LEVEL_TOKEN missing or malformed (must start with xapp-). " +
900
+ const tokenState = classifyAppToken(appToken);
901
+ if (tokenState === "absent") {
902
+ // Not an error: a seat that does not use Slack has no app-level token and
903
+ // never will. Exiting non-zero here put every such seat into a KeepAlive
904
+ // crash loop — one seat logged 36,000 EX_CONFIG restarts over four weeks,
905
+ // one line each, and `launchctl list` showed a standing "78" beside a job
906
+ // that had nothing to do. A CLEAN exit is what the plist's KeepAlive
907
+ // {SuccessfulExit:false} does not restart: say so once and stop.
908
+ info(agentRoot, "Socket Mode not configured on this seat (no SLACK_APP_LEVEL_TOKEN in .env) — not starting; run scripts/setup/init-slack-socket-mode.mjs to enable it");
909
+ process.exit(0);
910
+ }
911
+ if (tokenState === "malformed") {
912
+ error(agentRoot, "SLACK_APP_LEVEL_TOKEN malformed (must start with xapp-). " +
888
913
  "Run: node scripts/setup/init-slack-socket-mode.mjs");
889
- process.exit(78); // EX_CONFIG
914
+ process.exit(78); // EX_CONFIG — a token IS configured and it is wrong: an operator error worth a loud, non-zero exit
890
915
  }
891
916
 
892
917
  const identity = loadAgentIdentity(agentRoot);
@@ -42,7 +42,9 @@
42
42
  * @module scripts/session/supervisor
43
43
  */
44
44
 
45
- import { existsSync as fsExistsSync, readFileSync as fsReadFileSync, mkdirSync as fsMkdirSync, unlinkSync as fsUnlinkSync } from "node:fs";
45
+ import { existsSync as fsExistsSync, readFileSync as fsReadFileSync, mkdirSync as fsMkdirSync, unlinkSync as fsUnlinkSync, writeFileSync as fsWriteFileSync, chmodSync as fsChmodSync } from "node:fs";
46
+ import { randomUUID } from "node:crypto";
47
+ import { join } from "node:path";
46
48
  import { spawn } from "node:child_process";
47
49
  import { homedir } from "node:os";
48
50
  import { pathToFileURL } from "node:url";
@@ -52,14 +54,23 @@ import { writeJsonAtomic as fsWriteJsonAtomic, writeFileAtomic as fsWriteFileAto
52
54
  import { parseHeartbeat } from "../../lib/session/liveness.mjs";
53
55
  import { ensureClaudeConfig, heartbeatSilence, attentionRecord, HEARTBEAT_GRACE_MS } from "../../lib/session/first-run.mjs";
54
56
  import { acquireLock as singletonAcquireLock } from "../../lib/singleton.js";
55
- import { resolveClaudeBin } from "../../lib/claude-bin.mjs";
57
+ import { buildSpawn } from "../../lib/runtime/adapter.mjs";
58
+ import { resolveSeatSpawn } from "../../lib/runtime/seat-engine.mjs";
56
59
  import { sessionPermissionArgs, MAIN_SESSION_SOURCE } from "../../lib/session-permissions.mjs";
57
60
  import { loadSessionConfig, sessionPaths } from "../../lib/session/config.mjs";
61
+ import { noticeHonouredBy } from "../../lib/session/upgrade-notice.mjs";
62
+ import { SDK_PACKAGE } from "../daemon/sdk-version.mjs";
58
63
  import {
59
64
  loadMainSession, saveMainSession, newMainSession, agentFirstName, muxSessionName,
60
65
  launchMode, recordLaunch, rotationDecision, rotateMainSession, BACKOFF_MS,
61
66
  } from "../../lib/session/identity.mjs";
62
- import { buildClaudeArgs, detectMux, chooseMux, buildMuxCommands, parseScreenList, parseExitFile } from "../../lib/session/launch-args.mjs";
67
+ import { detectMux, chooseMux, buildMuxCommands, parseScreenList, parseExitFile, renderEnvFile } from "../../lib/session/launch-args.mjs";
68
+
69
+ /** Write a private (0600) file that must not already exist. The engine env file carries the seat token. */
70
+ export function writePrivateFile(path, text) {
71
+ fsWriteFileSync(path, text, { mode: 0o600, flag: "wx" });
72
+ fsChmodSync(path, 0o600); // a umask cannot widen it, but be explicit
73
+ }
63
74
 
64
75
  /** Another supervisor holds the lock (or the front door is not the session). Clean. */
65
76
  export const EXIT_ANOTHER_HOLDS_LOCK = 0;
@@ -162,6 +173,10 @@ export async function runSupervisor(deps = {}) {
162
173
  permissionArgs: deps.permissionArgs || null,
163
174
  pollMs: deps.pollMs ?? DEFAULT_POLL_MS,
164
175
  onSignal: deps.onSignal || installSignalHandlers,
176
+ // W4-E1: the seat's engine and its spawn fields (config/agent.json runtime.engine + seat token).
177
+ seatSpawn: deps.seatSpawn || ((root) => resolveSeatSpawn(root)),
178
+ writePrivateFile: deps.writePrivateFile || writePrivateFile,
179
+ envFileId: deps.envFileId || randomUUID,
165
180
  };
166
181
  const fsDeps = { readFileSync: d.readFileSync, writeJsonAtomic: d.writeJsonAtomic };
167
182
  const agentRoot = d.agentRoot;
@@ -243,26 +258,73 @@ export async function runSupervisor(deps = {}) {
243
258
  d.log("MAESTRO_SCOPED_PERMISSIONS=1 but config/session.yaml has no allowedTools — the interactive main session runs with bypass (an unattended session cannot answer permission prompts)");
244
259
  }
245
260
  }
246
- const claudeArgs = buildClaudeArgs({ first, sessionId: record.sessionId, mode, permissionArgs });
247
- const cmds = buildMuxCommands({ mux, muxName, cwd: agentRoot, claudeBin: d.claudeBin || resolveClaudeBin(), claudeArgs, exitFile: paths.lastExitFile });
261
+ // Binary + argv from the runtime adapter (lib/runtime/adapter.mjs, lane
262
+ // "main-session"), which delegates the argv contract to launch-args.
263
+ // W4-E1 (row 5): the seat's engine, resolved once for this launch. A claude
264
+ // seat adds no fields (unchanged); an engine-cohort seat launches
265
+ // `cli.mjs session` with the seat token. Read on every launch, so flipping
266
+ // runtime.engine back to "claude" restores claude at the next relaunch.
267
+ let seat = { engine: "claude", fields: {} };
268
+ try { seat = (await d.seatSpawn(agentRoot)) || seat; } catch { seat = { engine: "claude", fields: {} }; }
269
+ const engineCohort = seat.engine === "cohort";
270
+ const spec = buildSpawn({ lane: "main-session", bin: d.claudeBin || undefined, first, sessionId: record.sessionId, resumeMode: mode, permissions: permissionArgs, env: d.env, ...seat.fields });
271
+ if (!spec.ok && engineCohort) {
272
+ // An engine-cohort seat never falls back to claude (it would spend the seat's
273
+ // Anthropic credential). No seat token yet: back off, then let launchd relaunch.
274
+ d.log(`engine cohort main session not launched: ${spec.error.message} — backing off ${BACKOFF_MS / 60000} min`);
275
+ await d.sleep(BACKOFF_MS);
276
+ return EXIT_RELAUNCH;
277
+ }
278
+ // A claude main session cannot fail to build: a failure is a defect.
279
+ if (!spec.ok) throw new Error(`supervisor: runtime adapter: ${spec.error.message}`);
280
+ const claudeArgs = spec.argv;
281
+ // W4-E1: an engine-cohort session takes its env from a 0600 file the session
282
+ // sources and deletes (launch-args ENGINE_ENV_SCRIPT), never from the mux: a
283
+ // tmux server that is already running would hand the session its own global
284
+ // env (no seat token, possibly Claude credentials) instead of ours.
285
+ const envFile = engineCohort ? join(paths.stateDir, `engine-env-${d.envFileId()}.env`) : null;
286
+ const cmds = buildMuxCommands({ mux, muxName, cwd: agentRoot, claudeBin: spec.bin, claudeArgs, exitFile: paths.lastExitFile, envFile });
248
287
 
249
288
  // 4b. First-run dialogs. Nobody is attached to press a key, so the two
250
289
  // one-time acknowledgements are recorded up front. Fail-open: a seat
251
290
  // still launches when this cannot be done — the watchdog below then
252
- // reports the blocked session instead of nothing at all.
253
- const seeded = ensureClaudeConfig({
254
- homeDir: d.homeDir, agentRoot, bypass: permissionArgs.includes("--dangerously-skip-permissions"),
255
- readFileSync: d.readFileSync, writeFileAtomic: d.writeFileAtomic,
256
- });
257
- if (seeded.changed) d.log(`${seeded.path} seeded for an unattended launch: ${seeded.applied.join(", ")}`);
258
- else if (!seeded.ok) d.log(`${seeded.path || "~/.claude.json"} not seeded (${seeded.error}) — a first-run dialog may block the session; the watchdog will report it`);
291
+ // reports the blocked session instead of nothing at all. The engine has
292
+ // no first-run dialogs, so an engine-cohort seat's ~/.claude.json is left alone.
293
+ if (!engineCohort) {
294
+ const seeded = ensureClaudeConfig({
295
+ homeDir: d.homeDir, agentRoot, bypass: permissionArgs.includes("--dangerously-skip-permissions"),
296
+ readFileSync: d.readFileSync, writeFileAtomic: d.writeFileAtomic,
297
+ });
298
+ if (seeded.changed) d.log(`${seeded.path} seeded for an unattended launch: ${seeded.applied.join(", ")}`);
299
+ else if (!seeded.ok) d.log(`${seeded.path || "~/.claude.json"} not seeded (${seeded.error}) — a first-run dialog may block the session; the watchdog will report it`);
300
+ }
259
301
 
260
302
  try { d.unlinkSync(paths.lastExitFile); } catch { /* none from a previous run */ }
261
303
  try { d.unlinkSync(paths.attentionFile); } catch { /* none outstanding */ }
304
+ // 4c. An upgrade notice asking for a restart onto the version THIS launch
305
+ // runs is honoured by this launch: retire it, so `maestro session
306
+ // status` stops flagging a hop the session has completed (and a notice
307
+ // that is still there means a restart that has not happened). A
308
+ // health report ({healthy:false}) is never touched here.
309
+ {
310
+ let noticeText = null;
311
+ try { noticeText = d.readFileSync(paths.upgradeNoticeFile, "utf8"); } catch { noticeText = null; }
312
+ if (noticeText != null) {
313
+ let installed = "";
314
+ try { installed = String(JSON.parse(d.readFileSync(join(agentRoot, "node_modules", SDK_PACKAGE, "package.json"), "utf8")).version || ""); } catch { installed = ""; }
315
+ const h = noticeHonouredBy(noticeText, installed);
316
+ if (h.honoured) {
317
+ try { d.unlinkSync(paths.upgradeNoticeFile); d.log(`upgrade notice ${h.from || "?"} -> ${h.to} honoured: this launch runs ${h.to}; notice retired`); }
318
+ catch (e) { d.log(`upgrade notice ${h.from || "?"} -> ${h.to} honoured but not retired: ${e && e.message ? e.message : e}`); }
319
+ } else if (h.to) {
320
+ d.log(`upgrade notice ${h.from || "?"} -> ${h.to} left in place (${h.reason})`);
321
+ }
322
+ }
323
+ }
262
324
  const launched = recordLaunch(record, { now: d.now });
263
325
  const saved = saveMainSession(agentRoot, launched, fsDeps);
264
326
  if (!saved.ok) d.log(`main-session.json not saved: ${saved.error}`);
265
- d.log(`launching ${mux.kind} session ${muxName}: claude ${mode === "resume" ? "--resume" : "--session-id"} ${record.sessionId} (resumes=${launched.resumes})`);
327
+ d.log(`launching ${mux.kind} session ${muxName}: ${engineCohort ? "cohort session" : "claude"} ${mode === "resume" ? "--resume" : "--session-id"} ${record.sessionId} (resumes=${launched.resumes})`);
266
328
 
267
329
  // 5. Run, in the foreground of this process. The watchdog fires once,
268
330
  // after the grace period: a session that has not beaten by then is
@@ -279,7 +341,11 @@ export async function runSupervisor(deps = {}) {
279
341
  try { d.writeJsonAtomic(paths.attentionFile, rec); } catch { /* the log line still says it */ }
280
342
  d.log(`session ${muxName} has been up ${Math.round(silence.runMs / 1000)} s with no heartbeat — probably blocked on a first-run dialog or a permission prompt; attach with \`${rec.attach}\` and answer it`);
281
343
  });
344
+ const dropEnvFile = () => { if (envFile) { try { d.unlinkSync(envFile); } catch { /* the session consumed it */ } } };
282
345
  try {
346
+ // The engine's env (seat token, every Claude credential scrubbed) rides the
347
+ // env file; the mux client itself gets the supervisor's env, as for claude.
348
+ if (envFile) d.writePrivateFile(envFile, renderEnvFile(spec.env));
283
349
  const r = await d.execFile(cmds.start.file, cmds.start.args, { cwd: agentRoot, env: d.env });
284
350
  if (r && r.code !== 0) { launchFailed = true; d.log(`${mux.kind} start exited ${r.code}${r.stderr ? `: ${String(r.stderr).trim()}` : ""}`); }
285
351
  else if (!cmds.blocking) await waitForProbe(cmds.waitProbe, d);
@@ -287,6 +353,7 @@ export async function runSupervisor(deps = {}) {
287
353
  launchFailed = true;
288
354
  d.log(`${mux.kind} start failed: ${err && err.message ? err.message : err}`);
289
355
  }
356
+ dropEnvFile(); // normally already gone; left only when the session never started
290
357
  const endedAt = Number(d.now());
291
358
  try { cancelWatchdog(); } catch { /* already fired */ }
292
359
  await stopIssued;
@@ -13,6 +13,19 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
  # alias of it), so honouring the environment first is the correct precedence.
14
14
  AGENT_DIR="${AGENT_DIR:-$(dirname "$SCRIPT_DIR")}"
15
15
 
16
+ # Retired on engine-cohort seats (W4-E1): this lane runs claude outside the
17
+ # runtime adapter. The guard exits 3 with the reason when runtime.engine is cohort.
18
+ # Fail closed: when the guard cannot run (no node on PATH — launchd's
19
+ # /usr/bin:/bin — or the guard file is missing), config/agent.json is checked
20
+ # here and an engine-cohort seat is refused all the same.
21
+ LEGACY_GUARD="$(dirname "$SCRIPT_DIR")/lib/runtime/legacy-shell-guard.mjs"
22
+ if [ -f "$LEGACY_GUARD" ] && command -v node >/dev/null 2>&1; then
23
+ node "$LEGACY_GUARD" spawn-session.sh "$AGENT_DIR" || exit $?
24
+ elif [ -f "$AGENT_DIR/config/agent.json" ] && grep -Eq '"engine"[[:space:]]*:[[:space:]]*"cohort"' "$AGENT_DIR/config/agent.json"; then
25
+ echo "spawn-session.sh is retired on this seat: runtime.engine is \"cohort\", and this legacy lane runs the claude binary directly, outside the runtime adapter (the engine guard could not run: node or $LEGACY_GUARD is unavailable)." >&2
26
+ exit 3
27
+ fi
28
+
16
29
  # Real identity for the sub-session prompt. config/agent.env is generated from
17
30
  # config/agent.json by scripts/setup/generate-agent-env.mjs on every
18
31
  # `maestro upgrade` — it exists precisely so shell scripts can read the SOT.