@cohortapp/agent-sdk 2.17.0 → 2.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (531) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +18 -5
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/setup-wizard.md +1 -1
  9. package/docs/runbooks/fleet-rollout.md +156 -0
  10. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  11. package/lib/action-executor.js +19 -3
  12. package/lib/budget-guard.mjs +279 -3
  13. package/lib/channels/base-adapter.mjs +3 -1
  14. package/lib/channels/contract.mjs +2 -1
  15. package/lib/channels/inbox-item.mjs +8 -0
  16. package/lib/claude-bin.mjs +5 -6
  17. package/lib/cli/doctor-checks.mjs +141 -10
  18. package/lib/cli/global-setup-extras.mjs +5 -1
  19. package/lib/cli/inbox.mjs +100 -15
  20. package/lib/cli/seat-auth.mjs +463 -0
  21. package/lib/cli/session.mjs +80 -12
  22. package/lib/collective/capture-slots.mjs +234 -0
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/config.mjs +2 -0
  25. package/lib/collective/global-config.mjs +63 -1
  26. package/lib/collective/loop-guard.mjs +155 -0
  27. package/lib/collective/presence.mjs +142 -5
  28. package/lib/comms/send-gate.mjs +559 -1
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/failover.mjs +67 -16
  138. package/lib/model-router/llm-task.mjs +39 -3
  139. package/lib/model-router/resolve.mjs +89 -3
  140. package/lib/model-router/spawn.mjs +46 -47
  141. package/lib/model-router/taxonomy.mjs +126 -4
  142. package/lib/org/cost-sync.mjs +141 -11
  143. package/lib/org/inbound/broadcast.mjs +289 -0
  144. package/lib/org/inbound/collective.mjs +375 -0
  145. package/lib/org/inbound/directedness.mjs +96 -8
  146. package/lib/org/inbound/facts.mjs +78 -2
  147. package/lib/org/inbound/project.mjs +22 -0
  148. package/lib/org/inbound/surfaces.mjs +14 -0
  149. package/lib/org/llm-token.mjs +879 -0
  150. package/lib/org/mesh.mjs +61 -0
  151. package/lib/org/messaging.mjs +3 -1
  152. package/lib/org/protocol.checksum +1 -1
  153. package/lib/org/protocol.mjs +15 -0
  154. package/lib/org/quota.mjs +520 -0
  155. package/lib/org/tool-surface.mjs +104 -16
  156. package/lib/org/ui-parity.mjs +16 -1
  157. package/lib/org/work-ledger.mjs +37 -6
  158. package/lib/rate-guard.mjs +114 -1
  159. package/lib/resource-governor.mjs +41 -6
  160. package/lib/runtime/adapter.mjs +833 -0
  161. package/lib/runtime/child-env.mjs +191 -0
  162. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  163. package/lib/runtime/seat-engine.mjs +162 -0
  164. package/lib/session/ask-ledger.mjs +271 -0
  165. package/lib/session/current-work.mjs +676 -0
  166. package/lib/session/feed-core.mjs +40 -3
  167. package/lib/session/launch-args.mjs +56 -4
  168. package/lib/session/status-summary.mjs +26 -9
  169. package/lib/session/upgrade-notice.mjs +42 -0
  170. package/lib/setup/claude-probe.mjs +117 -13
  171. package/lib/setup/enrich.mjs +13 -10
  172. package/lib/setup/sections/model.mjs +39 -13
  173. package/lib/telemetry/collect.mjs +229 -11
  174. package/lib/upgrade/ignored-drift.mjs +105 -0
  175. package/lib/voice/post-call-brief.mjs +30 -17
  176. package/package.json +13 -3
  177. package/plugins/maestro-skills/skills/board-work.md +5 -0
  178. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  179. package/plugins/maestro-skills/skills/main-session.md +18 -7
  180. package/scaffold/config/collective.yaml +7 -0
  181. package/scripts/ci/check-durable-write-seam.mjs +3 -1
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +142 -19
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +51 -37
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +595 -149
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/maestro-daemon.mjs +11 -0
  198. package/scripts/daemon/prompt-builder.mjs +24 -0
  199. package/scripts/daemon/responder.mjs +246 -79
  200. package/scripts/daemon/sdk-version.mjs +98 -16
  201. package/scripts/eval/probe-gateway.mjs +635 -0
  202. package/scripts/eval/replay/extract.mjs +270 -0
  203. package/scripts/eval/replay/grade.mjs +260 -0
  204. package/scripts/eval/replay/lib/config.mjs +50 -0
  205. package/scripts/eval/replay/lib/effects.mjs +65 -0
  206. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  207. package/scripts/eval/replay/lib/judge.mjs +72 -0
  208. package/scripts/eval/replay/lib/redact.mjs +136 -0
  209. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  210. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  211. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  212. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  213. package/scripts/eval/replay/report.mjs +185 -0
  214. package/scripts/eval/replay/run.mjs +404 -0
  215. package/scripts/fleet/rollout.mjs +1151 -0
  216. package/scripts/hooks/pre-send-audit.sh +36 -245
  217. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  218. package/scripts/hooks/validate-state-yaml.sh +190 -0
  219. package/scripts/huddle/huddle-llm.mjs +361 -0
  220. package/scripts/huddle/huddle-server.mjs +46 -121
  221. package/scripts/local-triggers/autoupdate.sh +465 -81
  222. package/scripts/local-triggers/run-trigger.sh +13 -0
  223. package/scripts/maintenance/pin-integrity.mjs +364 -0
  224. package/scripts/poll-slack-events.sh +41 -9
  225. package/scripts/poller/slack-socket-mode.mjs +28 -3
  226. package/scripts/session/supervisor.mjs +80 -13
  227. package/scripts/spawn-session.sh +13 -0
  228. package/bin/maestro.test.mjs +0 -1574
  229. package/lib/action-executor.test.mjs +0 -871
  230. package/lib/archetype.test.mjs +0 -132
  231. package/lib/assurance/plan-note.test.mjs +0 -234
  232. package/lib/assurance/room-budget.test.mjs +0 -486
  233. package/lib/assurance/tier.test.mjs +0 -174
  234. package/lib/autonomy.test.mjs +0 -66
  235. package/lib/backlog.test.mjs +0 -302
  236. package/lib/backup/policy.test.mjs +0 -305
  237. package/lib/budget-escalate.test.mjs +0 -232
  238. package/lib/budget-guard.envelope.test.mjs +0 -476
  239. package/lib/budget-guard.test.mjs +0 -427
  240. package/lib/cadence-bus-requeue.test.mjs +0 -83
  241. package/lib/cadence-bus-schedule.test.mjs +0 -194
  242. package/lib/cadence-bus.test.mjs +0 -720
  243. package/lib/cadences.test.mjs +0 -230
  244. package/lib/capability/inventory.test.mjs +0 -232
  245. package/lib/capability.test.mjs +0 -78
  246. package/lib/channels/base-adapter.test.mjs +0 -590
  247. package/lib/channels/channels.test.mjs +0 -371
  248. package/lib/channels/contract.test.mjs +0 -162
  249. package/lib/channels/inbox-item.test.mjs +0 -368
  250. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  251. package/lib/channels/pairing.test.mjs +0 -270
  252. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  253. package/lib/channels/slack-adapter.test.mjs +0 -212
  254. package/lib/channels/telegram-adapter.test.mjs +0 -306
  255. package/lib/channels/voice/adapter.test.mjs +0 -278
  256. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  257. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  258. package/lib/charter.test.mjs +0 -89
  259. package/lib/claude-bin.test.mjs +0 -131
  260. package/lib/cli/board.test.mjs +0 -227
  261. package/lib/cli/design.test.mjs +0 -270
  262. package/lib/cli/doctor-checks.test.mjs +0 -336
  263. package/lib/cli/global-setup-extras.test.mjs +0 -462
  264. package/lib/cli/inbox.test.mjs +0 -230
  265. package/lib/cli/session-ack.test.mjs +0 -63
  266. package/lib/cli/session.test.mjs +0 -613
  267. package/lib/collective/capture.test.mjs +0 -121
  268. package/lib/collective/cards.test.mjs +0 -114
  269. package/lib/collective/config.test.mjs +0 -123
  270. package/lib/collective/global-config.test.mjs +0 -220
  271. package/lib/collective/global-skills.test.mjs +0 -126
  272. package/lib/collective/presence.test.mjs +0 -95
  273. package/lib/collective/recall.test.mjs +0 -116
  274. package/lib/collective/vendor-skills.test.mjs +0 -306
  275. package/lib/comms/send-gate.test.mjs +0 -770
  276. package/lib/comms.test.mjs +0 -41
  277. package/lib/context/budget.test.mjs +0 -252
  278. package/lib/context/history-scope.test.mjs +0 -79
  279. package/lib/cost/ledger-row.test.mjs +0 -183
  280. package/lib/design/design-md.test.mjs +0 -318
  281. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  282. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  283. package/lib/design/fixtures/foundation.json +0 -133
  284. package/lib/design/refresh-gate.test.mjs +0 -144
  285. package/lib/design/write.test.mjs +0 -241
  286. package/lib/diagnostics/alerts.test.mjs +0 -318
  287. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  288. package/lib/diagnostics/counters.test.mjs +0 -206
  289. package/lib/diagnostics/events.test.mjs +0 -290
  290. package/lib/diagnostics/otel.test.mjs +0 -196
  291. package/lib/diagnostics/trace.test.mjs +0 -251
  292. package/lib/env-compat.test.mjs +0 -104
  293. package/lib/execution/disposition.test.mjs +0 -553
  294. package/lib/execution/drive.test.mjs +0 -270
  295. package/lib/execution/effects.test.mjs +0 -344
  296. package/lib/execution/intake.test.mjs +0 -389
  297. package/lib/execution/journal.test.mjs +0 -261
  298. package/lib/execution/match.test.mjs +0 -235
  299. package/lib/execution/pipeline.test.mjs +0 -392
  300. package/lib/execution/route.test.mjs +0 -186
  301. package/lib/execution/surface-policy.test.mjs +0 -162
  302. package/lib/fs-atomic.test.mjs +0 -72
  303. package/lib/fs-ownership.test.mjs +0 -158
  304. package/lib/goals/admission.test.mjs +0 -164
  305. package/lib/goals/classify.test.mjs +0 -167
  306. package/lib/goals/collaborate.test.mjs +0 -336
  307. package/lib/goals/gaps.test.mjs +0 -284
  308. package/lib/goals/loop.test.mjs +0 -845
  309. package/lib/hooks/bus.test.mjs +0 -387
  310. package/lib/identity/persona.test.mjs +0 -142
  311. package/lib/kpi-sensors.test.mjs +0 -278
  312. package/lib/kpi.test.mjs +0 -244
  313. package/lib/learning/config.test.mjs +0 -75
  314. package/lib/learning/counters.test.mjs +0 -69
  315. package/lib/learning/curator-consolidate.test.mjs +0 -238
  316. package/lib/learning/curator.test.mjs +0 -106
  317. package/lib/learning/reflect.test.mjs +0 -0
  318. package/lib/learning/session-index.test.mjs +0 -125
  319. package/lib/learning/skill-writer.test.mjs +0 -210
  320. package/lib/mandate/audit.test.mjs +0 -195
  321. package/lib/mandate/contract.test.mjs +0 -185
  322. package/lib/mandate/derive.test.mjs +0 -274
  323. package/lib/mandate/model.test.mjs +0 -164
  324. package/lib/mandate/refresh.test.mjs +0 -389
  325. package/lib/mcp/server.test.mjs +0 -426
  326. package/lib/model-router/auth-profiles.test.mjs +0 -580
  327. package/lib/model-router/catalog.test.mjs +0 -385
  328. package/lib/model-router/economics.test.mjs +0 -438
  329. package/lib/model-router/failover.test.mjs +0 -439
  330. package/lib/model-router/health.test.mjs +0 -338
  331. package/lib/model-router/integration-coverage.test.mjs +0 -831
  332. package/lib/model-router/integration.test.mjs +0 -564
  333. package/lib/model-router/ledger.test.mjs +0 -415
  334. package/lib/model-router/llm-task.test.mjs +0 -392
  335. package/lib/model-router/org-credentials.test.mjs +0 -265
  336. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  337. package/lib/model-router/reconcile.test.mjs +0 -316
  338. package/lib/model-router/repair.test.mjs +0 -180
  339. package/lib/model-router/spawn.test.mjs +0 -446
  340. package/lib/model-router/taxonomy.test.mjs +0 -410
  341. package/lib/model-router.test.mjs +0 -1207
  342. package/lib/org/activity.test.mjs +0 -134
  343. package/lib/org/approvals.test.mjs +0 -216
  344. package/lib/org/awareness.test.mjs +0 -159
  345. package/lib/org/board-mine-cache.test.mjs +0 -53
  346. package/lib/org/board.test.mjs +0 -187
  347. package/lib/org/bootstrap-context.test.mjs +0 -153
  348. package/lib/org/client.test.mjs +0 -1206
  349. package/lib/org/cohort-client.test.mjs +0 -126
  350. package/lib/org/cost-sync.test.mjs +0 -153
  351. package/lib/org/doctor.test.mjs +0 -346
  352. package/lib/org/engagement-ledger.test.mjs +0 -112
  353. package/lib/org/engagement.test.mjs +0 -739
  354. package/lib/org/handoff.test.mjs +0 -269
  355. package/lib/org/inbound/directedness.test.mjs +0 -668
  356. package/lib/org/inbound/facts.test.mjs +0 -471
  357. package/lib/org/inbound/hydrate.test.mjs +0 -908
  358. package/lib/org/inbound/index.test.mjs +0 -429
  359. package/lib/org/inbound/project.test.mjs +0 -287
  360. package/lib/org/integration-tools.test.mjs +0 -160
  361. package/lib/org/keys.test.mjs +0 -92
  362. package/lib/org/knowledge.test.mjs +0 -326
  363. package/lib/org/leases.test.mjs +0 -235
  364. package/lib/org/mesh-directives.test.mjs +0 -110
  365. package/lib/org/mesh-integration.test.mjs +0 -127
  366. package/lib/org/mesh.test.mjs +0 -400
  367. package/lib/org/messaging.test.mjs +0 -471
  368. package/lib/org/param-contract.test.mjs +0 -477
  369. package/lib/org/policy.test.mjs +0 -237
  370. package/lib/org/protocol.checksum.test.mjs +0 -90
  371. package/lib/org/protocol.test.mjs +0 -323
  372. package/lib/org/push.test.mjs +0 -792
  373. package/lib/org/registry.test.mjs +0 -100
  374. package/lib/org/resource-tools.test.mjs +0 -361
  375. package/lib/org/tool-access.test.mjs +0 -144
  376. package/lib/org/tool-surface-integration.test.mjs +0 -120
  377. package/lib/org/tool-surface.test.mjs +0 -1268
  378. package/lib/org/typing.test.mjs +0 -291
  379. package/lib/org/ui-parity.test.mjs +0 -560
  380. package/lib/org/verify.test.mjs +0 -194
  381. package/lib/org/work-ledger.test.mjs +0 -273
  382. package/lib/plan/adoption-e2e.test.mjs +0 -366
  383. package/lib/plan/budget-enforcement.test.mjs +0 -400
  384. package/lib/plan/compile.test.mjs +0 -382
  385. package/lib/plan/emit.test.mjs +0 -269
  386. package/lib/plan/explain.test.mjs +0 -188
  387. package/lib/prompts/parallelism.test.mjs +0 -177
  388. package/lib/rag/rag.test.mjs +0 -505
  389. package/lib/rate-guard.test.mjs +0 -272
  390. package/lib/reactive-gate.test.mjs +0 -57
  391. package/lib/render.test.mjs +0 -68
  392. package/lib/resource-governor.test.mjs +0 -488
  393. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  394. package/lib/scheduling/jitter.test.mjs +0 -140
  395. package/lib/secrets/broker.test.mjs +0 -280
  396. package/lib/secrets/providers.test.mjs +0 -274
  397. package/lib/security/audit-engine.test.mjs +0 -424
  398. package/lib/security/coerce-args.test.mjs +0 -281
  399. package/lib/security/dangerous-tools.test.mjs +0 -68
  400. package/lib/security/external-content.test.mjs +0 -84
  401. package/lib/security/redact.test.mjs +0 -441
  402. package/lib/security/secret-equal.test.mjs +0 -55
  403. package/lib/session/config.test.mjs +0 -92
  404. package/lib/session/feed-core.test.mjs +0 -198
  405. package/lib/session/first-run.test.mjs +0 -121
  406. package/lib/session/frontdoor.test.mjs +0 -205
  407. package/lib/session/handoffs.test.mjs +0 -183
  408. package/lib/session/identity.test.mjs +0 -180
  409. package/lib/session/inbox-claims.test.mjs +0 -286
  410. package/lib/session/launch-args.test.mjs +0 -157
  411. package/lib/session/liveness.test.mjs +0 -100
  412. package/lib/session/status-summary.test.mjs +0 -118
  413. package/lib/session-permissions.test.mjs +0 -120
  414. package/lib/setup/claude-probe.test.mjs +0 -187
  415. package/lib/setup/completeness.test.mjs +0 -110
  416. package/lib/setup/context-pack.test.mjs +0 -89
  417. package/lib/setup/enrich.test.mjs +0 -115
  418. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  419. package/lib/setup/integration.test.mjs +0 -162
  420. package/lib/setup/io.test.mjs +0 -77
  421. package/lib/setup/runner.test.mjs +0 -132
  422. package/lib/setup/sections/identity.test.mjs +0 -234
  423. package/lib/setup/sections/inventory.test.mjs +0 -198
  424. package/lib/setup/sections/learning.test.mjs +0 -81
  425. package/lib/setup/sections/mandate.test.mjs +0 -388
  426. package/lib/setup/sections/messaging.test.mjs +0 -127
  427. package/lib/setup/sections/model.test.mjs +0 -240
  428. package/lib/setup/sections/org.test.mjs +0 -346
  429. package/lib/setup/sections/orgmail.test.mjs +0 -118
  430. package/lib/setup/sections/recovery.test.mjs +0 -98
  431. package/lib/setup/sections/subagents.test.mjs +0 -429
  432. package/lib/setup/sections/verify.test.mjs +0 -175
  433. package/lib/setup/sot.test.mjs +0 -81
  434. package/lib/setup/state.test.mjs +0 -115
  435. package/lib/singleton.test.mjs +0 -151
  436. package/lib/subagents/cli.test.mjs +0 -389
  437. package/lib/subagents/client.test.mjs +0 -309
  438. package/lib/subagents/gap.test.mjs +0 -234
  439. package/lib/subagents/lock.test.mjs +0 -248
  440. package/lib/subagents/manifest.test.mjs +0 -175
  441. package/lib/subagents/refs.test.mjs +0 -204
  442. package/lib/subagents/resolve.test.mjs +0 -422
  443. package/lib/subagents/schema.test.mjs +0 -328
  444. package/lib/telemetry/alerts.test.mjs +0 -109
  445. package/lib/telemetry/collect.test.mjs +0 -1274
  446. package/lib/tool-definitions-integration.test.mjs +0 -83
  447. package/lib/tool-definitions.test.mjs +0 -437
  448. package/lib/upgrade/global-refresh.test.mjs +0 -65
  449. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  450. package/lib/upgrade/post-steps.test.mjs +0 -200
  451. package/lib/upgrade/verify.test.mjs +0 -164
  452. package/lib/util/fetch-timeout.test.mjs +0 -202
  453. package/lib/util/reconnect.test.mjs +0 -369
  454. package/lib/util/unhandled.test.mjs +0 -216
  455. package/lib/voice/outbound.test.mjs +0 -69
  456. package/lib/voice/session-rotation.test.mjs +0 -114
  457. package/lib/voice/stt.test.mjs +0 -226
  458. package/lib/voice/voice.test.mjs +0 -990
  459. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  460. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  461. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  462. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  463. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  464. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  465. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  466. package/scripts/ci/check.test.mjs +0 -194
  467. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  468. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  469. package/scripts/collective/hook-runner.test.mjs +0 -173
  470. package/scripts/cost/fleet-digest.test.mjs +0 -207
  471. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  472. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  473. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  474. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  475. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  476. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  477. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  478. package/scripts/daemon/assurance.test.mjs +0 -1791
  479. package/scripts/daemon/board-mirror.test.mjs +0 -165
  480. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  481. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  482. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  483. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  484. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  485. package/scripts/daemon/classifier.test.mjs +0 -266
  486. package/scripts/daemon/classify-kind.test.mjs +0 -40
  487. package/scripts/daemon/context-compiler.test.mjs +0 -406
  488. package/scripts/daemon/deliver.test.mjs +0 -564
  489. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  490. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  491. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  492. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  493. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  494. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  495. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  496. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  497. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  498. package/scripts/daemon/integration.test.mjs +0 -149
  499. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  500. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  501. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  502. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  503. package/scripts/daemon/responder-cost.test.mjs +0 -68
  504. package/scripts/daemon/responder-history.test.mjs +0 -221
  505. package/scripts/daemon/sdk-version.test.mjs +0 -31
  506. package/scripts/daemon/session-lock.test.mjs +0 -252
  507. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  508. package/scripts/daemon/typing-registry.test.mjs +0 -102
  509. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  510. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  511. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  512. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  513. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  514. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  515. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  516. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  517. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  518. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  519. package/scripts/poller-launchd/install.test.mjs +0 -243
  520. package/scripts/restore-from-backup.test.mjs +0 -181
  521. package/scripts/session/feed.test.mjs +0 -196
  522. package/scripts/session/supervisor-sh.test.mjs +0 -218
  523. package/scripts/session/supervisor.test.mjs +0 -482
  524. package/scripts/setup/configure-macos.test.mjs +0 -306
  525. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  526. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  527. package/scripts/setup/generate-capability.test.mjs +0 -134
  528. package/scripts/setup/init-agent.test.mjs +0 -370
  529. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  530. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  531. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -35,6 +35,19 @@ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
35
35
  DATE=$(date +%Y-%m-%d)
36
36
  LOG_FILE="$LOG_DIR/$DATE-$TRIGGER_NAME.jsonl"
37
37
 
38
+ # Retired on engine-cohort seats (W4-E1): this lane runs claude outside the
39
+ # runtime adapter. The guard exits 3 with the reason when runtime.engine is cohort.
40
+ # Fail closed: when the guard cannot run (no node on PATH — launchd's
41
+ # /usr/bin:/bin — or the guard file is missing), config/agent.json is checked
42
+ # here and an engine-cohort seat is refused all the same.
43
+ LEGACY_GUARD="$AGENT_REPO_DIR/lib/runtime/legacy-shell-guard.mjs"
44
+ if [ -f "$LEGACY_GUARD" ] && command -v node >/dev/null 2>&1; then
45
+ node "$LEGACY_GUARD" run-trigger.sh "$AGENT_REPO_DIR" || exit $?
46
+ elif [ -f "$AGENT_REPO_DIR/config/agent.json" ] && grep -Eq '"engine"[[:space:]]*:[[:space:]]*"cohort"' "$AGENT_REPO_DIR/config/agent.json"; then
47
+ echo "run-trigger.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
48
+ exit 3
49
+ fi
50
+
38
51
  # Check emergency stop
39
52
  if [ -f "$AGENT_REPO_DIR/.emergency-stop" ]; then
40
53
  echo "{\"timestamp\":\"$TIMESTAMP\",\"trigger\":\"$TRIGGER_NAME\",\"status\":\"blocked\",\"reason\":\"emergency-stop\"}" >> "$LOG_FILE"
@@ -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;