@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,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.