@cohortapp/agent-sdk 2.11.15 → 2.13.0

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 (280) hide show
  1. package/.env.example +37 -22
  2. package/README.md +2 -0
  3. package/bin/maestro.mjs +117 -39
  4. package/bin/maestro.test.mjs +175 -5
  5. package/docs/guides/front-door-session.md +313 -0
  6. package/docs/guides/mac-mini.md +100 -28
  7. package/docs/guides/org-onboarding.md +1 -1
  8. package/docs/guides/setup-wizard.md +9 -5
  9. package/docs/runbooks/cohort-cutover.md +11 -1
  10. package/docs/runbooks/mac-mini-bootstrap.md +38 -63
  11. package/lib/cadence-bus-requeue.test.mjs +83 -0
  12. package/lib/cadence-bus.mjs +43 -7
  13. package/lib/channels/inbox-item.mjs +59 -2
  14. package/lib/cli/board.mjs +285 -0
  15. package/lib/cli/board.test.mjs +227 -0
  16. package/lib/cli/design.mjs +185 -0
  17. package/lib/cli/design.test.mjs +270 -0
  18. package/lib/cli/doctor-checks.mjs +441 -0
  19. package/lib/cli/doctor-checks.test.mjs +336 -0
  20. package/lib/cli/global-setup-extras.mjs +454 -0
  21. package/lib/cli/global-setup-extras.test.mjs +462 -0
  22. package/lib/cli/inbox.mjs +304 -0
  23. package/lib/cli/inbox.test.mjs +230 -0
  24. package/lib/cli/session-ack.mjs +63 -0
  25. package/lib/cli/session-ack.test.mjs +63 -0
  26. package/lib/cli/session.mjs +760 -0
  27. package/lib/cli/session.test.mjs +613 -0
  28. package/lib/collective/global-config.mjs +209 -6
  29. package/lib/collective/global-config.test.mjs +145 -0
  30. package/lib/collective/global-skills.mjs +145 -0
  31. package/lib/collective/global-skills.test.mjs +126 -0
  32. package/lib/collective/presence.mjs +4 -3
  33. package/lib/collective/vendor-skills.mjs +305 -0
  34. package/lib/collective/vendor-skills.test.mjs +306 -0
  35. package/lib/comms/send-gate.mjs +115 -0
  36. package/lib/comms/send-gate.test.mjs +113 -0
  37. package/lib/design/design-md.mjs +793 -0
  38. package/lib/design/design-md.test.mjs +318 -0
  39. package/lib/design/fixtures/DESIGN.golden.md +238 -0
  40. package/lib/design/fixtures/PRODUCT.golden.md +67 -0
  41. package/lib/design/fixtures/foundation.json +133 -0
  42. package/lib/design/refresh-gate.mjs +154 -0
  43. package/lib/design/refresh-gate.test.mjs +144 -0
  44. package/lib/design/write.mjs +275 -0
  45. package/lib/design/write.test.mjs +241 -0
  46. package/lib/feature-init.mjs +2 -2
  47. package/lib/mcp/server.test.mjs +9 -4
  48. package/lib/model-router/spawn.test.mjs +21 -0
  49. package/lib/org/board-mine-cache.mjs +99 -0
  50. package/lib/org/board-mine-cache.test.mjs +53 -0
  51. package/lib/org/board.mjs +11 -0
  52. package/lib/org/board.test.mjs +11 -1
  53. package/lib/org/client.mjs +36 -0
  54. package/lib/org/client.test.mjs +46 -0
  55. package/lib/org/inbound/directedness.mjs +18 -2
  56. package/lib/org/inbound/directedness.test.mjs +58 -0
  57. package/lib/org/inbound/index.mjs +8 -1
  58. package/lib/org/inbound/index.test.mjs +22 -0
  59. package/lib/org/mesh-directives.test.mjs +110 -0
  60. package/lib/org/mesh.mjs +61 -1
  61. package/lib/org/protocol.checksum +1 -1
  62. package/lib/org/protocol.mjs +52 -0
  63. package/lib/org/protocol.test.mjs +12 -1
  64. package/lib/org/registry.mjs +3 -2
  65. package/lib/org/tool-surface.mjs +120 -0
  66. package/lib/org/tool-surface.test.mjs +118 -5
  67. package/lib/prompts/parallelism.mjs +79 -0
  68. package/lib/prompts/parallelism.test.mjs +177 -0
  69. package/lib/security/external-content.mjs +1 -1
  70. package/lib/security/external-content.test.mjs +17 -0
  71. package/lib/session/config.mjs +137 -0
  72. package/lib/session/config.test.mjs +92 -0
  73. package/lib/session/feed-core.mjs +229 -0
  74. package/lib/session/feed-core.test.mjs +198 -0
  75. package/lib/session/first-run.mjs +126 -0
  76. package/lib/session/first-run.test.mjs +121 -0
  77. package/lib/session/frontdoor.mjs +266 -0
  78. package/lib/session/frontdoor.test.mjs +205 -0
  79. package/lib/session/handoffs.mjs +295 -0
  80. package/lib/session/handoffs.test.mjs +183 -0
  81. package/lib/session/identity.mjs +220 -0
  82. package/lib/session/identity.test.mjs +180 -0
  83. package/lib/session/inbox-claims.mjs +434 -0
  84. package/lib/session/inbox-claims.test.mjs +286 -0
  85. package/lib/session/launch-args.mjs +161 -0
  86. package/lib/session/launch-args.test.mjs +157 -0
  87. package/lib/session/liveness.mjs +174 -0
  88. package/lib/session/liveness.test.mjs +100 -0
  89. package/lib/session/status-summary.mjs +172 -0
  90. package/lib/session/status-summary.test.mjs +118 -0
  91. package/lib/session-permissions.mjs +39 -3
  92. package/lib/session-permissions.test.mjs +20 -0
  93. package/lib/setup/claude-probe.mjs +161 -24
  94. package/lib/setup/claude-probe.test.mjs +187 -0
  95. package/lib/setup/sections/learning.mjs +2 -1
  96. package/lib/setup/sections/model.mjs +104 -24
  97. package/lib/setup/sections/model.test.mjs +240 -0
  98. package/lib/setup/sections/org.mjs +27 -2
  99. package/lib/setup/sections/org.test.mjs +35 -2
  100. package/lib/setup/sections/verify.mjs +5 -0
  101. package/lib/setup/state.mjs +30 -10
  102. package/lib/setup/state.test.mjs +24 -1
  103. package/lib/singleton.js +11 -3
  104. package/lib/singleton.test.mjs +16 -0
  105. package/lib/subagents/lock.mjs +1 -1
  106. package/lib/telemetry/collect.mjs +270 -6
  107. package/lib/telemetry/collect.test.mjs +196 -1
  108. package/lib/upgrade/global-refresh.mjs +108 -0
  109. package/lib/upgrade/global-refresh.test.mjs +65 -0
  110. package/lib/upgrade/launchd-reconcile.mjs +327 -0
  111. package/lib/upgrade/launchd-reconcile.test.mjs +272 -0
  112. package/lib/upgrade/post-steps.mjs +151 -0
  113. package/lib/upgrade/post-steps.test.mjs +200 -0
  114. package/lib/upgrade/verify.mjs +215 -0
  115. package/lib/upgrade/verify.test.mjs +164 -0
  116. package/lib/voice/outbound.mjs +3 -2
  117. package/lib/voice/post-call-brief.mjs +2 -1
  118. package/lib/voice/session-rotation.mjs +6 -1
  119. package/lib/voice/session-rotation.test.mjs +114 -0
  120. package/package.json +3 -3
  121. package/plugins/maestro-skills/plugin.json +25 -1
  122. package/plugins/maestro-skills/skills/board-work.md +63 -0
  123. package/plugins/maestro-skills/skills/cohort-design.md +153 -0
  124. package/plugins/maestro-skills/skills/inbound-triage.md +80 -0
  125. package/plugins/maestro-skills/skills/main-session.md +102 -0
  126. package/plugins/maestro-skills/skills/peer-sessions.md +65 -0
  127. package/plugins/maestro-skills/skills/persona-discipline.md +75 -0
  128. package/plugins/maestro-skills/vendor/emilkowalski/LICENSE +21 -0
  129. package/plugins/maestro-skills/vendor/emilkowalski/UPSTREAM.json +70 -0
  130. package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/RECIPES.md +324 -0
  131. package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/SKILL.md +199 -0
  132. package/plugins/maestro-skills/vendor/emilkowalski/skills/animation-vocabulary/SKILL.md +173 -0
  133. package/plugins/maestro-skills/vendor/emilkowalski/skills/apple-design/SKILL.md +282 -0
  134. package/plugins/maestro-skills/vendor/emilkowalski/skills/emil-design-eng/SKILL.md +674 -0
  135. package/plugins/maestro-skills/vendor/emilkowalski/skills/find-animation-opportunities/SKILL.md +132 -0
  136. package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/AUDIT.md +115 -0
  137. package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
  138. package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/SKILL.md +101 -0
  139. package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/PICKER.md +197 -0
  140. package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/SKILL.md +90 -0
  141. package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/SKILL.md +112 -0
  142. package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/STANDARDS.md +187 -0
  143. package/plugins/maestro-skills/vendor/impeccable/LICENSE +191 -0
  144. package/plugins/maestro-skills/vendor/impeccable/NOTICE.md +11 -0
  145. package/plugins/maestro-skills/vendor/impeccable/SKILL.md +86 -0
  146. package/plugins/maestro-skills/vendor/impeccable/UPSTREAM.json +201 -0
  147. package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-asset-producer.md +42 -0
  148. package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-documenter.md +29 -0
  149. package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-finish-reviewer.md +43 -0
  150. package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-manual-edit-applier.md +97 -0
  151. package/plugins/maestro-skills/vendor/impeccable/reference/adapt.md +312 -0
  152. package/plugins/maestro-skills/vendor/impeccable/reference/adapt.native.md +58 -0
  153. package/plugins/maestro-skills/vendor/impeccable/reference/android.md +46 -0
  154. package/plugins/maestro-skills/vendor/impeccable/reference/animate.md +89 -0
  155. package/plugins/maestro-skills/vendor/impeccable/reference/audit.md +136 -0
  156. package/plugins/maestro-skills/vendor/impeccable/reference/audit.native.md +139 -0
  157. package/plugins/maestro-skills/vendor/impeccable/reference/bolder.md +33 -0
  158. package/plugins/maestro-skills/vendor/impeccable/reference/clarify.md +94 -0
  159. package/plugins/maestro-skills/vendor/impeccable/reference/colorize.md +86 -0
  160. package/plugins/maestro-skills/vendor/impeccable/reference/craft-floor.md +44 -0
  161. package/plugins/maestro-skills/vendor/impeccable/reference/craft.md +5 -0
  162. package/plugins/maestro-skills/vendor/impeccable/reference/critique.md +806 -0
  163. package/plugins/maestro-skills/vendor/impeccable/reference/degraded/asset-producer.md +37 -0
  164. package/plugins/maestro-skills/vendor/impeccable/reference/degraded/documenter.md +24 -0
  165. package/plugins/maestro-skills/vendor/impeccable/reference/degraded/finish-reviewer.md +38 -0
  166. package/plugins/maestro-skills/vendor/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  167. package/plugins/maestro-skills/vendor/impeccable/reference/delight.md +70 -0
  168. package/plugins/maestro-skills/vendor/impeccable/reference/distill.md +111 -0
  169. package/plugins/maestro-skills/vendor/impeccable/reference/doctor.md +54 -0
  170. package/plugins/maestro-skills/vendor/impeccable/reference/document.md +416 -0
  171. package/plugins/maestro-skills/vendor/impeccable/reference/extract.md +69 -0
  172. package/plugins/maestro-skills/vendor/impeccable/reference/harden.md +336 -0
  173. package/plugins/maestro-skills/vendor/impeccable/reference/hooks.md +111 -0
  174. package/plugins/maestro-skills/vendor/impeccable/reference/init.md +131 -0
  175. package/plugins/maestro-skills/vendor/impeccable/reference/ios.md +51 -0
  176. package/plugins/maestro-skills/vendor/impeccable/reference/layout.md +84 -0
  177. package/plugins/maestro-skills/vendor/impeccable/reference/live-setup.md +104 -0
  178. package/plugins/maestro-skills/vendor/impeccable/reference/live.md +325 -0
  179. package/plugins/maestro-skills/vendor/impeccable/reference/new-work.md +147 -0
  180. package/plugins/maestro-skills/vendor/impeccable/reference/onboard.md +234 -0
  181. package/plugins/maestro-skills/vendor/impeccable/reference/operate.md +61 -0
  182. package/plugins/maestro-skills/vendor/impeccable/reference/optimize.md +258 -0
  183. package/plugins/maestro-skills/vendor/impeccable/reference/overdrive.md +127 -0
  184. package/plugins/maestro-skills/vendor/impeccable/reference/polish.md +105 -0
  185. package/plugins/maestro-skills/vendor/impeccable/reference/quieter.md +99 -0
  186. package/plugins/maestro-skills/vendor/impeccable/reference/routing.md +24 -0
  187. package/plugins/maestro-skills/vendor/impeccable/reference/shape.md +59 -0
  188. package/plugins/maestro-skills/vendor/impeccable/reference/typeset.md +80 -0
  189. package/plugins/maestro-skills/vendor/impeccable/reference/visualize.md +46 -0
  190. package/plugins/maestro-skills/vendor/taste-skill/LICENSE +21 -0
  191. package/plugins/maestro-skills/vendor/taste-skill/UPSTREAM.json +37 -0
  192. package/plugins/maestro-skills/vendor/taste-skill/skills/minimalist-skill/SKILL.md +85 -0
  193. package/plugins/maestro-skills/vendor/taste-skill/skills/redesign-skill/SKILL.md +178 -0
  194. package/plugins/maestro-skills/vendor/taste-skill/skills/soft-skill/SKILL.md +98 -0
  195. package/plugins/maestro-skills/vendor/taste-skill/skills/taste-skill/SKILL.md +1206 -0
  196. package/plugins/maestro-skills/vendor/unlazy/LICENSE +21 -0
  197. package/plugins/maestro-skills/vendor/unlazy/SECURITY.md +72 -0
  198. package/plugins/maestro-skills/vendor/unlazy/SKILL.md +104 -0
  199. package/plugins/maestro-skills/vendor/unlazy/UPSTREAM.json +94 -0
  200. package/plugins/maestro-skills/vendor/unlazy/references/dispatch.md +82 -0
  201. package/plugins/maestro-skills/vendor/unlazy/references/gates.md +149 -0
  202. package/plugins/maestro-skills/vendor/unlazy/references/method.md +49 -0
  203. package/plugins/maestro-skills/vendor/unlazy/references/orchestration.md +107 -0
  204. package/plugins/maestro-skills/vendor/unlazy/references/parallel.md +133 -0
  205. package/plugins/maestro-skills/vendor/unlazy/references/token-economy.md +48 -0
  206. package/plugins/maestro-skills/vendor/unlazy/scripts/dispatch-check.mjs +139 -0
  207. package/plugins/maestro-skills/vendor/unlazy/scripts/gate-check.mjs +960 -0
  208. package/plugins/maestro-skills/vendor/unlazy/scripts/gate-lint.mjs +245 -0
  209. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/check-supervisor.mjs +46 -0
  210. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/dispatch.mjs +293 -0
  211. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/gates.mjs +953 -0
  212. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/process-tree.mjs +161 -0
  213. package/plugins/maestro-skills/vendor/unlazy/scripts/lib/regex-worker.mjs +9 -0
  214. package/plugins/maestro-skills/vendor/unlazy/templates/PLAN.md +116 -0
  215. package/plugins/maestro-skills/vendor/unlazy/templates/gates-leaf.md +51 -0
  216. package/plugins/maestro-skills/vendor/unlazy/templates/gates-node.md +51 -0
  217. package/scaffold/CLAUDE.md +24 -0
  218. package/scripts/ci/check-durable-write-seam.mjs +147 -0
  219. package/scripts/ci/check-durable-write-seam.test.mjs +90 -0
  220. package/scripts/ci/check-skill-packs.mjs +388 -0
  221. package/scripts/ci/check-skill-packs.test.mjs +495 -0
  222. package/scripts/ci/check.mjs +6 -0
  223. package/scripts/collective/hook-runner.mjs +39 -4
  224. package/scripts/collective/hook-runner.test.mjs +85 -2
  225. package/scripts/daemon/agent-daemon-board-mine.test.mjs +96 -0
  226. package/scripts/daemon/agent-daemon-design.test.mjs +238 -0
  227. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +60 -0
  228. package/scripts/daemon/agent-daemon.mjs +249 -10
  229. package/scripts/daemon/agent-daemon.test.mjs +73 -0
  230. package/scripts/daemon/assurance-e2e.test.mjs +141 -6
  231. package/scripts/daemon/assurance.mjs +461 -37
  232. package/scripts/daemon/assurance.test.mjs +408 -43
  233. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +393 -0
  234. package/scripts/daemon/cadence-consumer.mjs +289 -89
  235. package/scripts/daemon/cadence-handlers.mjs +53 -0
  236. package/scripts/daemon/classifier.mjs +1 -1
  237. package/scripts/daemon/dispatcher-resume.test.mjs +166 -0
  238. package/scripts/daemon/dispatcher.mjs +127 -19
  239. package/scripts/daemon/health.mjs +12 -1
  240. package/scripts/daemon/inbox-deferral-session.test.mjs +49 -0
  241. package/scripts/daemon/inbox-deferral.mjs +6 -0
  242. package/scripts/daemon/lib/self-echo.mjs +201 -0
  243. package/scripts/daemon/lib/self-echo.test.mjs +153 -0
  244. package/scripts/daemon/maestro-daemon.mjs +3 -0
  245. package/scripts/daemon/prompt-builder.mjs +19 -3
  246. package/scripts/daemon/responder.mjs +51 -40
  247. package/scripts/daemon/sdk-version.mjs +51 -0
  248. package/scripts/daemon/sdk-version.test.mjs +31 -0
  249. package/scripts/hooks/pre-send-audit.sh +97 -4
  250. package/scripts/hooks/pre-send-audit.test.mjs +140 -1
  251. package/scripts/local-triggers/autoupdate.sh +243 -19
  252. package/scripts/local-triggers/autoupdate.test.mjs +518 -0
  253. package/scripts/local-triggers/generate-plists.sh +24 -1
  254. package/scripts/local-triggers/generate-plists.test.mjs +49 -11
  255. package/scripts/org/send-orgmail.first-contact.test.mjs +102 -0
  256. package/scripts/org/send-orgmail.mjs +27 -3
  257. package/scripts/poller/inbox-privilege-injection.test.mjs +167 -0
  258. package/scripts/poller/slack-poller.mjs +13 -1
  259. package/scripts/poller/utils.mjs +46 -1
  260. package/scripts/poller-launchd/install.sh +19 -11
  261. package/scripts/poller-launchd/install.test.mjs +243 -0
  262. package/scripts/poller-launchd/launchd-poller-wrapper.sh +92 -0
  263. package/scripts/poller-launchd/migrate.sh +66 -0
  264. package/scripts/poller-launchd/poller.plist.template +4 -2
  265. package/scripts/session/feed.mjs +237 -0
  266. package/scripts/session/feed.test.mjs +196 -0
  267. package/scripts/session/supervisor-sh.test.mjs +218 -0
  268. package/scripts/session/supervisor.mjs +328 -0
  269. package/scripts/session/supervisor.sh +141 -0
  270. package/scripts/session/supervisor.test.mjs +482 -0
  271. package/scripts/setup/configure-macos.sh +250 -55
  272. package/scripts/setup/configure-macos.test.mjs +306 -0
  273. package/scripts/setup/init-agent.sh +112 -7
  274. package/scripts/setup/init-agent.test.mjs +220 -1
  275. package/scripts/vendor/skill-packs.mjs +354 -0
  276. package/scripts/vendor/sync-skill-packs.mjs +242 -0
  277. package/scripts/vendor/sync-skill-packs.test.mjs +103 -0
  278. package/scripts/watchdog/memory-watchdog.sh +37 -1
  279. package/scripts/watchdog/memory-watchdog.test.mjs +64 -0
  280. package/scripts/setup/boot-claude-session.sh +0 -94
@@ -11,11 +11,21 @@
11
11
  # 5. Sets up BlackHole virtual audio for voice integration
12
12
  # 6. Configures Login Items for all required apps
13
13
  # 7. Installs launchd agents for boot-time service startup
14
+ # 8. Retires the legacy Terminal.app boot-claude-session job (the front-door
15
+ # session is now the launchd `ai.maestro.<first>-session` supervisor job
16
+ # that scripts/local-triggers/generate-plists.sh emits)
17
+ # 9. `--tailscale-ssh`: advertises Tailscale SSH + turns Remote Login on so
18
+ # an operator can reach the seat without a console (audit F4)
14
19
  #
15
20
  # Usage:
16
21
  # sudo ./scripts/setup/configure-macos.sh # Full configuration
17
22
  # sudo ./scripts/setup/configure-macos.sh --check # Verify current config
18
23
  # ./scripts/setup/configure-macos.sh --apps-only # Non-sudo app setup only
24
+ # ./scripts/setup/configure-macos.sh --tailscale-ssh # Operator SSH over the tailnet
25
+ #
26
+ # Testing seam: `MAESTRO_CONFIGURE_MACOS_NO_MAIN=1 source configure-macos.sh`
27
+ # defines the functions without running main(), so configure-macos.test.mjs can
28
+ # exercise one function at a time against stub binaries.
19
29
  #
20
30
  # IMPORTANT: Some steps require sudo (auto-login, power management).
21
31
  # Run with sudo for full configuration.
@@ -335,63 +345,207 @@ configure_app_launches() {
335
345
  done
336
346
  ok "Log and state directories verified (owned by $real_user)"
337
347
 
338
- # --- Claude Code boot session (opens Terminal with interactive claude) ------
339
- local BOOT_SCRIPT="$AGENT_DIR/scripts/setup/boot-claude-session.sh"
340
- if [ -f "$BOOT_SCRIPT" ]; then
341
- chmod +x "$BOOT_SCRIPT"
348
+ # --- Legacy Terminal.app boot session: retired ------------------------------
349
+ # The front-door session is now a launchd KeepAlive job
350
+ # (ai.maestro.<first>-session → scripts/session/supervisor.sh) generated by
351
+ # scripts/local-triggers/generate-plists.sh. Unload + remove the old job so
352
+ # two sessions can never race on the same machine.
353
+ retire_boot_claude_session
354
+ }
342
355
 
343
- local PLIST_NAME="ai.maestro.boot-claude-session"
344
- local PLIST_PATH="$real_home/Library/LaunchAgents/${PLIST_NAME}.plist"
345
- local real_home
346
- real_home=$(eval echo "~$real_user")
356
+ # =============================================================================
357
+ # 6b. RETIRE THE LEGACY boot-claude-session LAUNCHD JOB
358
+ # =============================================================================
359
+ #
360
+ # F15: the Terminal.app boot script had no KeepAlive, no lock, no headless mode and
361
+ # needed Terminal.app. It is replaced by the supervisor job. This is idempotent:
362
+ # absent plist → nothing to do, exit 0. Runs as the real user when invoked
363
+ # under sudo so launchctl addresses the right (gui) domain.
347
364
 
348
- # Use the directory from which configure-macos.sh was invoked
349
- local boot_agent_dir="$CALLER_DIR"
365
+ retire_boot_claude_session() {
366
+ local real_user="${SUDO_USER:-$CURRENT_USER}"
367
+ # Under sudo the plist lives in the invoking user's home; otherwise $HOME is
368
+ # authoritative (a `~user` expansion would bypass an overridden HOME).
369
+ local real_home="$HOME"
370
+ if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ]; then
371
+ real_home=$(eval echo "~$SUDO_USER")
372
+ fi
373
+ local LABEL="ai.maestro.boot-claude-session"
374
+ local PLIST_PATH="$real_home/Library/LaunchAgents/${LABEL}.plist"
350
375
 
351
- cat > "$PLIST_PATH" << PLIST_EOF
352
- <?xml version="1.0" encoding="UTF-8"?>
353
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
354
- <plist version="1.0">
355
- <dict>
356
- <key>Label</key>
357
- <string>${PLIST_NAME}</string>
358
- <key>ProgramArguments</key>
359
- <array>
360
- <string>/bin/bash</string>
361
- <string>${BOOT_SCRIPT}</string>
362
- <string>${boot_agent_dir}</string>
363
- </array>
364
- <key>RunAtLoad</key>
365
- <true/>
366
- <key>StandardOutPath</key>
367
- <string>${AGENT_DIR}/logs/watchdog/boot-session-stdout.log</string>
368
- <key>StandardErrorPath</key>
369
- <string>${AGENT_DIR}/logs/watchdog/boot-session-stderr.log</string>
370
- <key>EnvironmentVariables</key>
371
- <dict>
372
- <key>PATH</key>
373
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:${real_home}/.local/bin</string>
374
- <key>HOME</key>
375
- <string>${real_home}</string>
376
- </dict>
377
- </dict>
378
- </plist>
379
- PLIST_EOF
376
+ if [ ! -f "$PLIST_PATH" ]; then
377
+ log "Legacy $LABEL job not present — nothing to retire"
378
+ return 0
379
+ fi
380
+
381
+ if [ "$(id -u)" -eq 0 ] && [ "$real_user" != "root" ]; then
382
+ su "$real_user" -c "launchctl unload '$PLIST_PATH' 2>/dev/null || launchctl bootout gui/\$(id -u)/$LABEL 2>/dev/null" 2>/dev/null || true
383
+ else
384
+ launchctl unload "$PLIST_PATH" 2>/dev/null || launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
385
+ fi
386
+ rm -f "$PLIST_PATH"
387
+ ok "Retired legacy $LABEL job (replaced by the ai.maestro.<first>-session supervisor)"
388
+ }
380
389
 
381
- chown "$real_user:staff" "$PLIST_PATH" 2>/dev/null || true
390
+ # =============================================================================
391
+ # 6c. TAILSCALE SSH (operator access without a console)
392
+ # =============================================================================
393
+ #
394
+ # Audit F4: no operator could reach any of 41 seats over SSH. Tailscale SSH
395
+ # (`tailscale set --ssh`) advertises the host to the tailnet; macOS still gates
396
+ # inbound SSH behind Remote Login (`systemsetup -setremotelogin on`, root).
397
+ # Neither is reachable until the tailnet ACL grants an `ssh` rule, which only a
398
+ # tailnet admin can write — hence the reminder printed at the end.
399
+ #
400
+ # MAESTRO_TAILSCALE_BIN overrides the CLI path (tests; non-standard installs).
382
401
 
383
- # Load as the real user
384
- if [ "$(id -u)" -eq 0 ]; then
385
- su "$real_user" -c "launchctl unload '$PLIST_PATH' 2>/dev/null; launchctl load '$PLIST_PATH'" 2>/dev/null || true
402
+ resolve_tailscale_bin() {
403
+ if [ -n "${MAESTRO_TAILSCALE_BIN:-}" ]; then
404
+ [ -x "$MAESTRO_TAILSCALE_BIN" ] && echo "$MAESTRO_TAILSCALE_BIN"
405
+ return 0
406
+ fi
407
+ local c
408
+ for c in "$(command -v tailscale 2>/dev/null)" \
409
+ /Applications/Tailscale.app/Contents/MacOS/Tailscale \
410
+ /usr/local/bin/tailscale /opt/homebrew/bin/tailscale; do
411
+ if [ -n "$c" ] && [ -x "$c" ]; then echo "$c"; return 0; fi
412
+ done
413
+ return 0
414
+ }
415
+
416
+ # Which Tailscale is this? The Tailscale SSH server ships ONLY in the
417
+ # open-source `tailscaled` variant; Tailscale.app (Mac App Store / standalone
418
+ # GUI) cannot honour RunSSH no matter what `tailscale set --ssh` says. The
419
+ # fleet runs the GUI app, whose /usr/local/bin/tailscale is a two-line sh shim
420
+ # onto /Applications/Tailscale.app/Contents/MacOS/tailscale.
421
+ # gui — Tailscale.app (path under the bundle, or the shim naming it)
422
+ # open-source — a tailscaled is installed next to the CLI
423
+ # unknown — cannot tell; proceed as before
424
+ # MAESTRO_TAILSCALED_BIN overrides the daemon path (tests; non-standard installs).
425
+ tailscale_variant() {
426
+ local TS="$1" c
427
+ [ -z "$TS" ] && { echo "unknown"; return 0; }
428
+ case "$TS" in */Tailscale.app/*) echo "gui"; return 0 ;; esac
429
+ if head -c 512 "$TS" 2>/dev/null | grep -aq "Tailscale.app"; then echo "gui"; return 0; fi
430
+ for c in "${MAESTRO_TAILSCALED_BIN:-}" "$(command -v tailscaled 2>/dev/null)" \
431
+ /opt/homebrew/bin/tailscaled /usr/local/bin/tailscaled \
432
+ /opt/homebrew/opt/tailscale/bin/tailscaled; do
433
+ if [ -n "$c" ] && [ -x "$c" ]; then echo "open-source"; return 0; fi
434
+ done
435
+ echo "unknown"
436
+ }
437
+
438
+ # The seat's tailnet IPv4, for the sshd-over-tailnet fallback line. Empty when unknown.
439
+ tailscale_ip4() {
440
+ local TS="$1"
441
+ [ -n "$TS" ] && "$TS" ip -4 2>/dev/null | head -1 || true
442
+ }
443
+
444
+ print_tailscale_gui_remedy() { # $1 = tailscale bin
445
+ local ip
446
+ ip="$(tailscale_ip4 "$1")"
447
+ warn "Tailscale SSH is NOT available on Tailscale.app (GUI): only the open-source tailscaled carries the SSH server."
448
+ log "Two ways to reach this seat:"
449
+ log " 1. Switch to the open-source variant (Tailscale SSH):"
450
+ log " quit Tailscale.app, then: brew install tailscale && sudo brew services start tailscale"
451
+ log " sign in: tailscale up --ssh — then re-run: ./scripts/setup/configure-macos.sh --tailscale-ssh"
452
+ log " 2. Keep the GUI app and use Remote Login (sshd) over the tailnet IP, enabled below:"
453
+ log " ssh $(whoami)@${ip:-<tailnet-ip>} (the operator's key must be in ~/.ssh/authorized_keys)"
454
+ }
455
+
456
+ print_tailscale_acl_reminder() {
457
+ echo ""
458
+ log "Tailnet ACL reminder — SSH is advertised but NOT reachable until an admin"
459
+ log "adds an ssh rule at https://login.tailscale.com/admin/acls, e.g.:"
460
+ echo ' "ssh": [{'
461
+ echo ' "action": "check",'
462
+ echo ' "src": ["autogroup:admin"],'
463
+ echo ' "dst": ["autogroup:self", "tag:agent-seat"],'
464
+ echo ' "users": ["autogroup:nonroot"]'
465
+ echo ' }]'
466
+ log "Then from an operator machine: tailscale ssh $(whoami)@$(hostname -s 2>/dev/null || hostname)"
467
+ }
468
+
469
+ configure_tailscale_ssh() {
470
+ section "Tailscale SSH (operator access)"
471
+
472
+ local TS
473
+ TS="$(resolve_tailscale_bin)"
474
+ if [ -z "$TS" ]; then
475
+ fail "tailscale CLI not found — install Tailscale (brew install --cask tailscale, sign in), then re-run --tailscale-ssh"
476
+ return 1
477
+ fi
478
+ ok "tailscale CLI: $TS"
479
+
480
+ local variant
481
+ variant="$(tailscale_variant "$TS")"
482
+ if [ "$variant" = "gui" ]; then
483
+ print_tailscale_gui_remedy "$TS"
484
+ elif "$TS" set --ssh; then
485
+ ok "Tailscale SSH advertised (tailscale set --ssh)"
486
+ else
487
+ warn "tailscale set --ssh failed — is the node up and signed in? Check: $TS status"
488
+ fi
489
+
490
+ # Remote Login (sshd). systemsetup needs root; under sudo -n a missing
491
+ # credential fails fast instead of prompting from an unattended run.
492
+ if [ "$(id -u)" -eq 0 ]; then
493
+ systemsetup -setremotelogin on >/dev/null 2>&1 && ok "Remote Login enabled (sshd)" \
494
+ || warn "systemsetup -setremotelogin on failed — enable Remote Login in System Settings > General > Sharing"
495
+ else
496
+ if sudo -n systemsetup -setremotelogin on >/dev/null 2>&1; then
497
+ ok "Remote Login enabled (sshd)"
386
498
  else
387
- launchctl unload "$PLIST_PATH" 2>/dev/null || true
388
- launchctl load "$PLIST_PATH" 2>/dev/null || true
499
+ warn "Remote Login needs sudo: run sudo systemsetup -setremotelogin on"
389
500
  fi
501
+ fi
390
502
 
391
- ok "Claude Code boot session agent installed (opens Terminal + claude on login)"
503
+ if [ "$variant" = "gui" ]; then
504
+ log "Tailnet ACL reminder does not apply until Tailscale SSH is available on this seat."
392
505
  else
393
- warn "Boot session script not found at $BOOT_SCRIPT"
506
+ print_tailscale_acl_reminder
394
507
  fi
508
+ return 0
509
+ }
510
+
511
+ # --check helpers: "on" / "off" / "unavailable" / "unknown" for each half of
512
+ # operator access. "unavailable" = the GUI app, where RunSSH can never be true.
513
+ tailscale_ssh_state() {
514
+ local TS
515
+ TS="$(resolve_tailscale_bin)"
516
+ [ -z "$TS" ] && { echo "unknown"; return 0; }
517
+ local prefs
518
+ prefs="$("$TS" debug prefs 2>/dev/null || true)"
519
+ if printf '%s' "$prefs" | grep -Eq '"RunSSH":[[:space:]]*true'; then echo "on"
520
+ elif [ "$(tailscale_variant "$TS")" = "gui" ]; then echo "unavailable"
521
+ elif printf '%s' "$prefs" | grep -Eq '"RunSSH":[[:space:]]*false'; then echo "off"
522
+ else echo "unknown"; fi
523
+ }
524
+
525
+ # `launchctl list` in the CALLER's gui domain. Under `sudo … --check` the
526
+ # shell is root, whose `launchctl list` cannot see gui/<uid> jobs — every
527
+ # ai.maestro.* agent would read as "not loaded" on a healthy seat. As root
528
+ # with SUDO_USER set, ask launchd on that user's behalf.
529
+ gui_launchctl_list() {
530
+ if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ]; then
531
+ launchctl asuser "$(id -u "$SUDO_USER")" launchctl list 2>/dev/null
532
+ else
533
+ launchctl list 2>/dev/null
534
+ fi
535
+ }
536
+
537
+ remote_login_state() {
538
+ local out
539
+ if [ "$(id -u)" -eq 0 ]; then
540
+ out="$(systemsetup -getremotelogin 2>/dev/null || true)"
541
+ else
542
+ out="$(sudo -n systemsetup -getremotelogin 2>/dev/null || true)"
543
+ fi
544
+ case "$out" in
545
+ *On*|*on*) echo "on" ;;
546
+ *Off*|*off*) echo "off" ;;
547
+ *) echo "unknown" ;;
548
+ esac
395
549
  }
396
550
 
397
551
  # =============================================================================
@@ -470,7 +624,7 @@ configure_boot_services() {
470
624
 
471
625
  # Check which are loaded
472
626
  local loaded_count
473
- loaded_count=$(launchctl list 2>/dev/null | grep -cE 'ai\.(maestro|adaptic)' || true)
627
+ loaded_count=$(gui_launchctl_list | grep -cE 'ai\.(maestro|adaptic)' || true)
474
628
  if [ "$loaded_count" -gt 0 ]; then
475
629
  ok "$loaded_count agents currently loaded"
476
630
  else
@@ -481,7 +635,7 @@ configure_boot_services() {
481
635
  fi
482
636
 
483
637
  # Check if the daemon is running
484
- if launchctl list 2>/dev/null | grep -q "daemon"; then
638
+ if gui_launchctl_list | grep -q "daemon"; then
485
639
  ok "Main daemon is loaded"
486
640
  else
487
641
  warn "Main daemon not loaded — the orchestrator won't start on boot"
@@ -580,7 +734,7 @@ verify_all() {
580
734
  fi
581
735
 
582
736
  # Check memory watchdog
583
- if launchctl list 2>/dev/null | grep -q "ai.maestro.memory-watchdog"; then
737
+ if gui_launchctl_list | grep -q "ai.maestro.memory-watchdog"; then
584
738
  ok "Memory watchdog: running"
585
739
  elif [ -f "$HOME/Library/LaunchAgents/ai.maestro.memory-watchdog.plist" ]; then
586
740
  warn "Memory watchdog: installed but not loaded"
@@ -603,6 +757,40 @@ verify_all() {
603
757
  warn "Heartbeat: no heartbeat file found"
604
758
  fi
605
759
 
760
+ # Front-door session job (ai.maestro.<first>-session, KeepAlive)
761
+ if gui_launchctl_list | grep -Eq 'ai\.maestro\.[^[:space:]]+-session$'; then
762
+ ok "Front-door session job: loaded"
763
+ elif ls "$HOME"/Library/LaunchAgents/ai.maestro.*-session.plist >/dev/null 2>&1; then
764
+ warn "Front-door session job: installed but not loaded — maestro session start"
765
+ else
766
+ warn "Front-door session job: not installed — run: npm run init-agent"
767
+ fi
768
+ if [ -f "$HOME/Library/LaunchAgents/ai.maestro.boot-claude-session.plist" ]; then
769
+ warn "Legacy boot-claude-session job still present — re-run this script to retire it"
770
+ fi
771
+
772
+ # Operator access over the tailnet (audit F4): both halves must be on.
773
+ local ts_state rl_state
774
+ ts_state="$(tailscale_ssh_state)"
775
+ rl_state="$(remote_login_state)"
776
+ case "$ts_state" in
777
+ on) ok "Tailscale SSH: on (advertised)" ;;
778
+ off) fail "Tailscale SSH: off — run: ./scripts/setup/configure-macos.sh --tailscale-ssh"; issues=$((issues + 1)) ;;
779
+ unavailable)
780
+ if [ "$rl_state" = "on" ]; then
781
+ warn "Tailscale SSH: unavailable on Tailscale.app (GUI) — Remote Login over the tailnet IP is the access path; for Tailscale SSH: brew install tailscale (open-source tailscaled)"
782
+ else
783
+ fail "Tailscale SSH: unavailable on Tailscale.app (GUI) and Remote Login is off — run: sudo ./scripts/setup/configure-macos.sh --tailscale-ssh (or brew install tailscale for the open-source tailscaled)"
784
+ issues=$((issues + 1))
785
+ fi ;;
786
+ *) warn "Tailscale SSH: unknown (tailscale CLI missing or node not up)" ;;
787
+ esac
788
+ case "$rl_state" in
789
+ on) ok "Remote Login: on (sshd)" ;;
790
+ off) fail "Remote Login: off — run: sudo ./scripts/setup/configure-macos.sh --tailscale-ssh"; issues=$((issues + 1)) ;;
791
+ *) warn "Remote Login: unknown (needs sudo to read: sudo systemsetup -getremotelogin)" ;;
792
+ esac
793
+
606
794
  # Summary
607
795
  echo ""
608
796
  if [ "$issues" -eq 0 ]; then
@@ -632,6 +820,9 @@ main() {
632
820
  --check|check)
633
821
  verify_all
634
822
  ;;
823
+ --tailscale-ssh|tailscale-ssh)
824
+ configure_tailscale_ssh
825
+ ;;
635
826
  --apps-only|apps)
636
827
  configure_parsec
637
828
  configure_slack_cdp
@@ -661,14 +852,18 @@ main() {
661
852
  echo ""
662
853
  ;;
663
854
  *)
664
- echo "Usage: $0 [--full|--check|--apps-only]"
855
+ echo "Usage: $0 [--full|--check|--apps-only|--tailscale-ssh]"
665
856
  echo ""
666
- echo " --full Full configuration (requires sudo for auto-login and power)"
667
- echo " --check Verify current configuration"
668
- echo " --apps-only Configure apps and services only (no sudo needed)"
857
+ echo " --full Full configuration (requires sudo for auto-login and power)"
858
+ echo " --check Verify current configuration"
859
+ echo " --apps-only Configure apps and services only (no sudo needed)"
860
+ echo " --tailscale-ssh Advertise Tailscale SSH + enable Remote Login (operator access)"
669
861
  exit 1
670
862
  ;;
671
863
  esac
672
864
  }
673
865
 
674
- main "$@"
866
+ # Sourcing seam for tests: define everything, run nothing.
867
+ if [ "${MAESTRO_CONFIGURE_MACOS_NO_MAIN:-0}" != "1" ]; then
868
+ main "$@"
869
+ fi