@cohortapp/agent-sdk 2.3.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 (731) hide show
  1. package/.claude/commands/init-agent.md +104 -0
  2. package/.claude/commands/init-maestro.md +1187 -0
  3. package/.claude/settings.json +161 -0
  4. package/.env.example +216 -0
  5. package/README.md +632 -0
  6. package/agents/browser-operator/agent.md +52 -0
  7. package/agents/calendar-ops/agent.md +50 -0
  8. package/agents/communications/agent.md +96 -0
  9. package/agents/decision-log/agent.md +65 -0
  10. package/agents/desktop-operator/agent.md +59 -0
  11. package/agents/gmail-operator/agent.md +62 -0
  12. package/agents/inbound-dispatcher/agent.md +66 -0
  13. package/agents/inbox-processor/agent.md +39 -0
  14. package/agents/pmo-execution/agent.md +60 -0
  15. package/agents/session-spawner/agent.md +64 -0
  16. package/agents/slack-operator/agent.md +60 -0
  17. package/agents/whatsapp-operator/agent.md +60 -0
  18. package/agents/workflow-automation/agent.md +61 -0
  19. package/archetypes/altitudes/c-suite.yaml +58 -0
  20. package/archetypes/altitudes/founder.yaml +68 -0
  21. package/archetypes/altitudes/senior-manager.yaml +63 -0
  22. package/archetypes/altitudes/svp.yaml +60 -0
  23. package/archetypes/altitudes/vp.yaml +50 -0
  24. package/archetypes/archetype.schema.json +77 -0
  25. package/archetypes/base.yaml +47 -0
  26. package/archetypes/capabilities/commercial-leader.yaml +159 -0
  27. package/archetypes/capabilities/compliance-officer.yaml +159 -0
  28. package/archetypes/capabilities/executive-operator.yaml +169 -0
  29. package/archetypes/capabilities/finance-leader.yaml +162 -0
  30. package/archetypes/capabilities/operations-leader.yaml +154 -0
  31. package/archetypes/capabilities/product-leader.yaml +148 -0
  32. package/archetypes/capabilities/technical-leader.yaml +146 -0
  33. package/archetypes/functions/commercial-leader.yaml +62 -0
  34. package/archetypes/functions/compliance-officer.yaml +64 -0
  35. package/archetypes/functions/executive-operator.yaml +70 -0
  36. package/archetypes/functions/finance-leader.yaml +70 -0
  37. package/archetypes/functions/operations-leader.yaml +62 -0
  38. package/archetypes/functions/product-leader.yaml +61 -0
  39. package/archetypes/functions/technical-leader.yaml +57 -0
  40. package/bin/cohort-mcp.mjs +81 -0
  41. package/bin/maestro.mjs +3516 -0
  42. package/bin/maestro.test.mjs +1015 -0
  43. package/desktop-control/README.md +56 -0
  44. package/desktop-control/app-profiles/gmail.yaml +120 -0
  45. package/desktop-control/app-profiles/slack.yaml +315 -0
  46. package/desktop-control/app-profiles/whatsapp.yaml +107 -0
  47. package/docs/architecture/agent-topology.md +2239 -0
  48. package/docs/architecture/archetype-agent-factory.md +110 -0
  49. package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
  50. package/docs/architecture/continuous-monitoring.md +221 -0
  51. package/docs/architecture/mcp-capability-map.md +585 -0
  52. package/docs/architecture/system-architecture.md +1272 -0
  53. package/docs/company-context/README.md +40 -0
  54. package/docs/guides/agent-persona-setup.md +600 -0
  55. package/docs/guides/agents-observe-setup.md +64 -0
  56. package/docs/guides/billing-console-keys.md +88 -0
  57. package/docs/guides/ccxray-diagnostics.md +65 -0
  58. package/docs/guides/channel-bus.md +127 -0
  59. package/docs/guides/claude-mem-setup.md +79 -0
  60. package/docs/guides/claude-pace-setup.md +56 -0
  61. package/docs/guides/claudraband-sessions.md +98 -0
  62. package/docs/guides/clawteam-swarm.md +116 -0
  63. package/docs/guides/code-review-graph-setup.md +86 -0
  64. package/docs/guides/email-setup.md +431 -0
  65. package/docs/guides/mac-mini.md +119 -0
  66. package/docs/guides/media-generation-setup.md +349 -0
  67. package/docs/guides/model-routing.md +162 -0
  68. package/docs/guides/observability-otel.md +265 -0
  69. package/docs/guides/org-onboarding.md +132 -0
  70. package/docs/guides/outbound-governance-setup.md +437 -0
  71. package/docs/guides/pdf-generation-setup.md +315 -0
  72. package/docs/guides/poller-daemon-setup.md +563 -0
  73. package/docs/guides/rag-context-setup.md +459 -0
  74. package/docs/guides/self-optimization-pattern.md +82 -0
  75. package/docs/guides/setup-wizard.md +178 -0
  76. package/docs/guides/slack-setup.md +350 -0
  77. package/docs/guides/telegram-setup.md +227 -0
  78. package/docs/guides/twilio-subaccounts-setup.md +223 -0
  79. package/docs/guides/verification.md +128 -0
  80. package/docs/guides/voice-mode.md +188 -0
  81. package/docs/guides/voice-sms-setup.md +698 -0
  82. package/docs/guides/webhook-relay-setup.md +349 -0
  83. package/docs/guides/whatsapp-setup.md +288 -0
  84. package/docs/prompts/board-pack-cover-template.md +36 -0
  85. package/docs/prompts/decision-recommendation-template.md +88 -0
  86. package/docs/prompts/followup-message-template.md +141 -0
  87. package/docs/prompts/investor-letter-template.md +52 -0
  88. package/docs/prompts/morning-brief-template.md +82 -0
  89. package/docs/prompts/presentation-template.md +58 -0
  90. package/docs/prompts/weekly-strategic-memo-template.md +104 -0
  91. package/docs/research/hallucinated-tool-output-investigation.md +151 -0
  92. package/docs/runbooks/backup-restore.md +205 -0
  93. package/docs/runbooks/cohort-cutover.md +129 -0
  94. package/docs/runbooks/fleet-operations.md +200 -0
  95. package/docs/runbooks/incident-response.md +226 -0
  96. package/docs/runbooks/mac-mini-bootstrap.md +431 -0
  97. package/docs/runbooks/perpetual-operations.md +509 -0
  98. package/docs/runbooks/recovery-and-failover.md +260 -0
  99. package/framework-features.json +267 -0
  100. package/ingest/README.md +87 -0
  101. package/lib/action-executor.js +689 -0
  102. package/lib/action-executor.test.mjs +871 -0
  103. package/lib/agent-root.mjs +37 -0
  104. package/lib/archetype.mjs +236 -0
  105. package/lib/archetype.test.mjs +132 -0
  106. package/lib/autonomy.mjs +114 -0
  107. package/lib/autonomy.test.mjs +66 -0
  108. package/lib/backlog.mjs +358 -0
  109. package/lib/backlog.test.mjs +266 -0
  110. package/lib/budget-guard.mjs +279 -0
  111. package/lib/budget-guard.test.mjs +291 -0
  112. package/lib/cadence-bus-schedule.test.mjs +194 -0
  113. package/lib/cadence-bus.mjs +1120 -0
  114. package/lib/cadence-bus.test.mjs +720 -0
  115. package/lib/cadences.mjs +205 -0
  116. package/lib/cadences.test.mjs +125 -0
  117. package/lib/capability.mjs +154 -0
  118. package/lib/capability.test.mjs +78 -0
  119. package/lib/channels/base-adapter.mjs +719 -0
  120. package/lib/channels/base-adapter.test.mjs +590 -0
  121. package/lib/channels/channel.mjs +128 -0
  122. package/lib/channels/channels.test.mjs +371 -0
  123. package/lib/channels/contract.mjs +215 -0
  124. package/lib/channels/contract.test.mjs +137 -0
  125. package/lib/channels/conversation-resolver.mjs +95 -0
  126. package/lib/channels/gmail/adapter.mjs +87 -0
  127. package/lib/channels/inbox-item.mjs +255 -0
  128. package/lib/channels/inbox-item.test.mjs +335 -0
  129. package/lib/channels/index.mjs +94 -0
  130. package/lib/channels/orgmail/adapter.mjs +353 -0
  131. package/lib/channels/orgmail/adapter.test.mjs +311 -0
  132. package/lib/channels/pairing.mjs +363 -0
  133. package/lib/channels/pairing.test.mjs +270 -0
  134. package/lib/channels/registry.mjs +164 -0
  135. package/lib/channels/slack/adapter.mjs +317 -0
  136. package/lib/channels/slack-adapter.test.mjs +212 -0
  137. package/lib/channels/sms/adapter.mjs +43 -0
  138. package/lib/channels/telegram/adapter.mjs +432 -0
  139. package/lib/channels/telegram-adapter.test.mjs +306 -0
  140. package/lib/channels/voice/adapter.mjs +301 -0
  141. package/lib/channels/voice/adapter.test.mjs +278 -0
  142. package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
  143. package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
  144. package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
  145. package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
  146. package/lib/charter.mjs +256 -0
  147. package/lib/charter.test.mjs +89 -0
  148. package/lib/claude-bin.mjs +134 -0
  149. package/lib/claude-bin.test.mjs +75 -0
  150. package/lib/collective/capture.mjs +185 -0
  151. package/lib/collective/capture.test.mjs +121 -0
  152. package/lib/collective/cards.mjs +201 -0
  153. package/lib/collective/cards.test.mjs +114 -0
  154. package/lib/collective/config.mjs +186 -0
  155. package/lib/collective/config.test.mjs +123 -0
  156. package/lib/collective/global-config.mjs +113 -0
  157. package/lib/collective/global-config.test.mjs +75 -0
  158. package/lib/collective/presence.mjs +201 -0
  159. package/lib/collective/presence.test.mjs +95 -0
  160. package/lib/collective/recall.mjs +215 -0
  161. package/lib/collective/recall.test.mjs +116 -0
  162. package/lib/comms/send-gate.mjs +554 -0
  163. package/lib/comms/send-gate.test.mjs +577 -0
  164. package/lib/comms.mjs +67 -0
  165. package/lib/comms.test.mjs +41 -0
  166. package/lib/diagnostics/alerts.mjs +424 -0
  167. package/lib/diagnostics/alerts.test.mjs +318 -0
  168. package/lib/diagnostics/backup-freshness.mjs +188 -0
  169. package/lib/diagnostics/backup-freshness.test.mjs +185 -0
  170. package/lib/diagnostics/counters.mjs +269 -0
  171. package/lib/diagnostics/counters.test.mjs +206 -0
  172. package/lib/diagnostics/events.mjs +188 -0
  173. package/lib/diagnostics/events.test.mjs +290 -0
  174. package/lib/diagnostics/otel.mjs +237 -0
  175. package/lib/diagnostics/otel.test.mjs +196 -0
  176. package/lib/diagnostics/trace.mjs +216 -0
  177. package/lib/diagnostics/trace.test.mjs +251 -0
  178. package/lib/env-compat.mjs +74 -0
  179. package/lib/env-compat.test.mjs +104 -0
  180. package/lib/feature-init.mjs +331 -0
  181. package/lib/fs-atomic.mjs +112 -0
  182. package/lib/fs-atomic.test.mjs +72 -0
  183. package/lib/fs-ownership.mjs +111 -0
  184. package/lib/fs-ownership.test.mjs +158 -0
  185. package/lib/hooks/bus.mjs +347 -0
  186. package/lib/hooks/bus.test.mjs +387 -0
  187. package/lib/index.js +16 -0
  188. package/lib/learning/config.mjs +106 -0
  189. package/lib/learning/config.test.mjs +75 -0
  190. package/lib/learning/counters.mjs +156 -0
  191. package/lib/learning/counters.test.mjs +69 -0
  192. package/lib/learning/curator-consolidate.test.mjs +238 -0
  193. package/lib/learning/curator.mjs +453 -0
  194. package/lib/learning/curator.test.mjs +106 -0
  195. package/lib/learning/log.mjs +40 -0
  196. package/lib/learning/reflect.mjs +534 -0
  197. package/lib/learning/reflect.test.mjs +0 -0
  198. package/lib/learning/session-index.mjs +352 -0
  199. package/lib/learning/session-index.test.mjs +125 -0
  200. package/lib/learning/skill-writer.mjs +474 -0
  201. package/lib/learning/skill-writer.test.mjs +210 -0
  202. package/lib/mcp/server.mjs +328 -0
  203. package/lib/mcp/server.test.mjs +400 -0
  204. package/lib/model-router/auth-profiles.mjs +758 -0
  205. package/lib/model-router/auth-profiles.test.mjs +580 -0
  206. package/lib/model-router/catalog/anthropic.yaml +153 -0
  207. package/lib/model-router/catalog/deepseek.yaml +86 -0
  208. package/lib/model-router/catalog/moonshot.yaml +81 -0
  209. package/lib/model-router/catalog/qwen.yaml +114 -0
  210. package/lib/model-router/catalog.mjs +925 -0
  211. package/lib/model-router/catalog.test.mjs +385 -0
  212. package/lib/model-router/economics.mjs +564 -0
  213. package/lib/model-router/economics.test.mjs +344 -0
  214. package/lib/model-router/failover.mjs +298 -0
  215. package/lib/model-router/failover.test.mjs +439 -0
  216. package/lib/model-router/health.mjs +453 -0
  217. package/lib/model-router/health.test.mjs +338 -0
  218. package/lib/model-router/integration-coverage.test.mjs +829 -0
  219. package/lib/model-router/integration.test.mjs +564 -0
  220. package/lib/model-router/ledger.mjs +402 -0
  221. package/lib/model-router/ledger.test.mjs +382 -0
  222. package/lib/model-router/llm-task.mjs +515 -0
  223. package/lib/model-router/llm-task.test.mjs +392 -0
  224. package/lib/model-router/org-credentials.mjs +260 -0
  225. package/lib/model-router/org-credentials.test.mjs +265 -0
  226. package/lib/model-router/pricing-refresh.mjs +463 -0
  227. package/lib/model-router/pricing-refresh.test.mjs +286 -0
  228. package/lib/model-router/reconcile.mjs +429 -0
  229. package/lib/model-router/reconcile.test.mjs +316 -0
  230. package/lib/model-router/repair.mjs +471 -0
  231. package/lib/model-router/repair.test.mjs +180 -0
  232. package/lib/model-router/resolve.mjs +1206 -0
  233. package/lib/model-router/spawn.mjs +497 -0
  234. package/lib/model-router/spawn.test.mjs +425 -0
  235. package/lib/model-router/taxonomy.mjs +893 -0
  236. package/lib/model-router/taxonomy.test.mjs +410 -0
  237. package/lib/model-router.mjs +677 -0
  238. package/lib/model-router.test.mjs +907 -0
  239. package/lib/org/activity.mjs +211 -0
  240. package/lib/org/activity.test.mjs +134 -0
  241. package/lib/org/approvals.mjs +448 -0
  242. package/lib/org/approvals.test.mjs +216 -0
  243. package/lib/org/awareness.mjs +222 -0
  244. package/lib/org/awareness.test.mjs +159 -0
  245. package/lib/org/board.mjs +229 -0
  246. package/lib/org/board.test.mjs +177 -0
  247. package/lib/org/bootstrap-context.mjs +169 -0
  248. package/lib/org/bootstrap-context.test.mjs +153 -0
  249. package/lib/org/client.mjs +1628 -0
  250. package/lib/org/client.test.mjs +1107 -0
  251. package/lib/org/cohort-client.mjs +67 -0
  252. package/lib/org/cohort-client.test.mjs +126 -0
  253. package/lib/org/cost-sync.mjs +227 -0
  254. package/lib/org/cost-sync.test.mjs +153 -0
  255. package/lib/org/doctor.mjs +212 -0
  256. package/lib/org/doctor.test.mjs +212 -0
  257. package/lib/org/handoff.mjs +293 -0
  258. package/lib/org/handoff.test.mjs +269 -0
  259. package/lib/org/integration-tools.mjs +182 -0
  260. package/lib/org/integration-tools.test.mjs +160 -0
  261. package/lib/org/keys.mjs +131 -0
  262. package/lib/org/keys.test.mjs +92 -0
  263. package/lib/org/knowledge.mjs +463 -0
  264. package/lib/org/knowledge.test.mjs +319 -0
  265. package/lib/org/leases.mjs +335 -0
  266. package/lib/org/leases.test.mjs +235 -0
  267. package/lib/org/mesh-integration.test.mjs +127 -0
  268. package/lib/org/mesh.mjs +459 -0
  269. package/lib/org/mesh.test.mjs +345 -0
  270. package/lib/org/messaging.mjs +503 -0
  271. package/lib/org/messaging.test.mjs +238 -0
  272. package/lib/org/policy.mjs +345 -0
  273. package/lib/org/policy.test.mjs +237 -0
  274. package/lib/org/protocol.checksum +1 -0
  275. package/lib/org/protocol.checksum.test.mjs +90 -0
  276. package/lib/org/protocol.mjs +967 -0
  277. package/lib/org/protocol.test.mjs +264 -0
  278. package/lib/org/registry.mjs +194 -0
  279. package/lib/org/registry.test.mjs +100 -0
  280. package/lib/org/tool-surface-integration.test.mjs +120 -0
  281. package/lib/org/tool-surface.mjs +2535 -0
  282. package/lib/org/tool-surface.test.mjs +589 -0
  283. package/lib/org/ui-parity.mjs +3236 -0
  284. package/lib/org/ui-parity.test.mjs +348 -0
  285. package/lib/org/verify.mjs +176 -0
  286. package/lib/org/verify.test.mjs +194 -0
  287. package/lib/rag/embed.mjs +188 -0
  288. package/lib/rag/indexer.mjs +425 -0
  289. package/lib/rag/rag.test.mjs +505 -0
  290. package/lib/rag/search.mjs +475 -0
  291. package/lib/rate-guard.mjs +246 -0
  292. package/lib/rate-guard.test.mjs +201 -0
  293. package/lib/render.mjs +112 -0
  294. package/lib/render.test.mjs +68 -0
  295. package/lib/resource-governor.mjs +297 -0
  296. package/lib/resource-governor.test.mjs +262 -0
  297. package/lib/scheduling/dynamic-jobs.mjs +675 -0
  298. package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
  299. package/lib/scheduling/jitter.mjs +0 -0
  300. package/lib/scheduling/jitter.test.mjs +140 -0
  301. package/lib/secrets/broker.mjs +315 -0
  302. package/lib/secrets/broker.test.mjs +280 -0
  303. package/lib/secrets/providers.mjs +461 -0
  304. package/lib/secrets/providers.test.mjs +274 -0
  305. package/lib/security/audit-engine.mjs +684 -0
  306. package/lib/security/audit-engine.test.mjs +389 -0
  307. package/lib/security/coerce-args.mjs +552 -0
  308. package/lib/security/coerce-args.test.mjs +281 -0
  309. package/lib/security/dangerous-tools.mjs +97 -0
  310. package/lib/security/dangerous-tools.test.mjs +68 -0
  311. package/lib/security/external-content.mjs +145 -0
  312. package/lib/security/external-content.test.mjs +67 -0
  313. package/lib/security/redact.mjs +592 -0
  314. package/lib/security/redact.test.mjs +441 -0
  315. package/lib/security/secret-equal.mjs +73 -0
  316. package/lib/security/secret-equal.test.mjs +55 -0
  317. package/lib/session-permissions.mjs +101 -0
  318. package/lib/session-permissions.test.mjs +100 -0
  319. package/lib/setup/claude-probe.mjs +74 -0
  320. package/lib/setup/completeness.mjs +175 -0
  321. package/lib/setup/completeness.test.mjs +110 -0
  322. package/lib/setup/context-pack.mjs +173 -0
  323. package/lib/setup/context-pack.test.mjs +89 -0
  324. package/lib/setup/enrich.mjs +277 -0
  325. package/lib/setup/enrich.test.mjs +115 -0
  326. package/lib/setup/enroll-from-cohort.mjs +441 -0
  327. package/lib/setup/enroll-from-cohort.test.mjs +233 -0
  328. package/lib/setup/integration.test.mjs +162 -0
  329. package/lib/setup/io.mjs +360 -0
  330. package/lib/setup/io.test.mjs +77 -0
  331. package/lib/setup/run-generator.mjs +81 -0
  332. package/lib/setup/runner.mjs +244 -0
  333. package/lib/setup/runner.test.mjs +132 -0
  334. package/lib/setup/sections/comms.mjs +173 -0
  335. package/lib/setup/sections/company.mjs +120 -0
  336. package/lib/setup/sections/enrich.mjs +138 -0
  337. package/lib/setup/sections/identity.mjs +182 -0
  338. package/lib/setup/sections/identity.test.mjs +140 -0
  339. package/lib/setup/sections/learning.mjs +153 -0
  340. package/lib/setup/sections/learning.test.mjs +81 -0
  341. package/lib/setup/sections/messaging.mjs +219 -0
  342. package/lib/setup/sections/messaging.test.mjs +127 -0
  343. package/lib/setup/sections/model.mjs +102 -0
  344. package/lib/setup/sections/operating-model.mjs +78 -0
  345. package/lib/setup/sections/org.mjs +475 -0
  346. package/lib/setup/sections/org.test.mjs +313 -0
  347. package/lib/setup/sections/orgmail.mjs +173 -0
  348. package/lib/setup/sections/orgmail.test.mjs +118 -0
  349. package/lib/setup/sections/recovery.mjs +159 -0
  350. package/lib/setup/sections/recovery.test.mjs +98 -0
  351. package/lib/setup/sections/tools.mjs +132 -0
  352. package/lib/setup/sections/verify.mjs +97 -0
  353. package/lib/setup/sot.mjs +205 -0
  354. package/lib/setup/sot.test.mjs +81 -0
  355. package/lib/setup/state.mjs +151 -0
  356. package/lib/setup/state.test.mjs +92 -0
  357. package/lib/singleton.js +229 -0
  358. package/lib/singleton.test.mjs +135 -0
  359. package/lib/telemetry/alerts.mjs +216 -0
  360. package/lib/telemetry/alerts.test.mjs +109 -0
  361. package/lib/telemetry/collect.mjs +512 -0
  362. package/lib/telemetry/collect.test.mjs +202 -0
  363. package/lib/tool-definitions-integration.test.mjs +83 -0
  364. package/lib/tool-definitions.js +738 -0
  365. package/lib/tool-definitions.test.mjs +437 -0
  366. package/lib/util/fetch-timeout.mjs +136 -0
  367. package/lib/util/fetch-timeout.test.mjs +202 -0
  368. package/lib/util/reconnect.mjs +343 -0
  369. package/lib/util/reconnect.test.mjs +369 -0
  370. package/lib/util/unhandled.mjs +205 -0
  371. package/lib/util/unhandled.test.mjs +216 -0
  372. package/lib/voice/context-loader.mjs +466 -0
  373. package/lib/voice/index.mjs +100 -0
  374. package/lib/voice/openai-realtime.mjs +510 -0
  375. package/lib/voice/outbound.mjs +542 -0
  376. package/lib/voice/outbound.test.mjs +69 -0
  377. package/lib/voice/post-call-brief.mjs +428 -0
  378. package/lib/voice/provider.mjs +52 -0
  379. package/lib/voice/session-rotation.mjs +257 -0
  380. package/lib/voice/stt.mjs +161 -0
  381. package/lib/voice/stt.test.mjs +226 -0
  382. package/lib/voice/tool-bridge.mjs +370 -0
  383. package/lib/voice/tts.mjs +104 -0
  384. package/lib/voice/twilio-sip-bridge.mjs +288 -0
  385. package/lib/voice/voice.test.mjs +990 -0
  386. package/mcp/README.md +80 -0
  387. package/package.json +151 -0
  388. package/plugins/maestro-skills/plugin.json +139 -0
  389. package/plugins/maestro-skills/skills/agents-observe.md +110 -0
  390. package/plugins/maestro-skills/skills/board-deck.md +68 -0
  391. package/plugins/maestro-skills/skills/books-close.md +77 -0
  392. package/plugins/maestro-skills/skills/brand-steward.md +121 -0
  393. package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
  394. package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
  395. package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
  396. package/plugins/maestro-skills/skills/claude-pace.md +61 -0
  397. package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
  398. package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
  399. package/plugins/maestro-skills/skills/decision-brief.md +89 -0
  400. package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
  401. package/plugins/maestro-skills/skills/draft-comms.md +84 -0
  402. package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
  403. package/plugins/maestro-skills/skills/files-find.md +65 -0
  404. package/plugins/maestro-skills/skills/generative-ui.md +228 -0
  405. package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
  406. package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
  407. package/plugins/maestro-skills/skills/mail-triage.md +86 -0
  408. package/plugins/maestro-skills/skills/morning-brief.md +54 -0
  409. package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
  410. package/plugins/maestro-skills/skills/org-board.md +133 -0
  411. package/plugins/maestro-skills/skills/org-credential.md +68 -0
  412. package/plugins/maestro-skills/skills/org-recall.md +81 -0
  413. package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
  414. package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
  415. package/plugins/maestro-skills/skills/router-why.md +78 -0
  416. package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
  417. package/plugins/maestro-skills/skills/session-search.md +71 -0
  418. package/plugins/maestro-skills/skills/set-reminder.md +93 -0
  419. package/plugins/maestro-skills/skills/slack-followup.md +64 -0
  420. package/plugins/maestro-skills/skills/team-activity.md +86 -0
  421. package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
  422. package/policies/action-classification.yaml +114 -0
  423. package/policies/ai-disclosure.yaml +294 -0
  424. package/policies/communication-style.md +139 -0
  425. package/policies/information-barriers.yaml +118 -0
  426. package/policies/prompt-injection-defence.yaml +138 -0
  427. package/public/assets/icon-dark.png +0 -0
  428. package/public/assets/icon-dark.svg +9 -0
  429. package/public/assets/icon-light.svg +9 -0
  430. package/public/assets/logo-dark.svg +15 -0
  431. package/public/assets/logo-light.svg +15 -0
  432. package/scaffold/.mcp.json +7 -0
  433. package/scaffold/CLAUDE.md +368 -0
  434. package/scaffold/config/agent.json +55 -0
  435. package/scaffold/config/agent.ts +76 -0
  436. package/scaffold/config/agent.ts.example +89 -0
  437. package/scaffold/config/alerts.yaml +23 -0
  438. package/scaffold/config/allowlist.yaml.example +25 -0
  439. package/scaffold/config/caller-id-map.yaml +46 -0
  440. package/scaffold/config/collective.yaml +49 -0
  441. package/scaffold/config/company.json +20 -0
  442. package/scaffold/config/known-agents.json +6 -0
  443. package/scaffold/config/learning.yaml +55 -0
  444. package/scaffold/config/model-routing.yaml.example +104 -0
  445. package/scaffold/config/org.yaml +25 -0
  446. package/scaffold/config/orgmail.yaml.example +19 -0
  447. package/scaffold/config/recovery.yaml +72 -0
  448. package/scaffold/config/secrets.yaml +27 -0
  449. package/scaffold/config/slack.yaml.example +35 -0
  450. package/scaffold/config/telegram.yaml.example +38 -0
  451. package/scaffold/config/voice.yaml.example +89 -0
  452. package/scaffold/config/whatsapp.yaml.example +39 -0
  453. package/schedules/README.md +49 -0
  454. package/schedules/triggers/backlog-executor.md +102 -0
  455. package/schedules/triggers/brand-steward.md +72 -0
  456. package/schedules/triggers/daily-evening-wrap.md +159 -0
  457. package/schedules/triggers/daily-midday-sweep.md +58 -0
  458. package/schedules/triggers/daily-morning-brief.md +55 -0
  459. package/schedules/triggers/directory-hygiene.md +81 -0
  460. package/schedules/triggers/dynamic-jobs.md +40 -0
  461. package/schedules/triggers/inbox-processor.md +115 -0
  462. package/schedules/triggers/meeting-action-capture.md +60 -0
  463. package/schedules/triggers/meeting-prep.md +69 -0
  464. package/schedules/triggers/messaging-inbound.md +50 -0
  465. package/schedules/triggers/org-pulse.md +24 -0
  466. package/schedules/triggers/quarterly-self-assessment.md +54 -0
  467. package/schedules/triggers/weekly-engineering-health.md +37 -0
  468. package/schedules/triggers/weekly-execution.md +65 -0
  469. package/schedules/triggers/weekly-hiring.md +53 -0
  470. package/schedules/triggers/weekly-priorities.md +38 -0
  471. package/schedules/triggers/weekly-strategic-memo.md +124 -0
  472. package/scripts/archive-email.sh +55 -0
  473. package/scripts/cadence/cadence-status.mjs +36 -0
  474. package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
  475. package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
  476. package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
  477. package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
  478. package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
  479. package/scripts/ci/check-docs-accuracy.mjs +493 -0
  480. package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
  481. package/scripts/ci/check-exports-exist.mjs +140 -0
  482. package/scripts/ci/check-files-exist.mjs +107 -0
  483. package/scripts/ci/check-no-build-artifacts.mjs +111 -0
  484. package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
  485. package/scripts/ci/check-no-confidential.mjs +198 -0
  486. package/scripts/ci/check-no-conflict-markers.mjs +169 -0
  487. package/scripts/ci/check-no-residual-identity.mjs +163 -0
  488. package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
  489. package/scripts/ci/check-tarball-fidelity.mjs +205 -0
  490. package/scripts/ci/check-unresolved-tokens.mjs +83 -0
  491. package/scripts/ci/check.mjs +109 -0
  492. package/scripts/ci/check.test.mjs +194 -0
  493. package/scripts/ci/run-coverage.mjs +82 -0
  494. package/scripts/ci/run-tests.mjs +71 -0
  495. package/scripts/cloud-relay/README.md +59 -0
  496. package/scripts/cloud-relay/index.mjs +233 -0
  497. package/scripts/cloud-relay/package.json +15 -0
  498. package/scripts/cloud-relay/railway.json +13 -0
  499. package/scripts/cloud-relay/voice/README.md +94 -0
  500. package/scripts/cloud-relay/voice/package-lock.json +39 -0
  501. package/scripts/cloud-relay/voice/package.json +16 -0
  502. package/scripts/cloud-relay/voice/railway.json +13 -0
  503. package/scripts/cloud-relay/voice/server.mjs +532 -0
  504. package/scripts/collective/hook-runner.mjs +211 -0
  505. package/scripts/collective/hook-runner.test.mjs +90 -0
  506. package/scripts/collective/org-pulse.mjs +72 -0
  507. package/scripts/collective/org-sync.mjs +61 -0
  508. package/scripts/collective/recall.mjs +45 -0
  509. package/scripts/collective/who.mjs +30 -0
  510. package/scripts/comms-monitor.sh +288 -0
  511. package/scripts/configure-whatsapp-sandbox.sh +201 -0
  512. package/scripts/continuous-monitor.sh +91 -0
  513. package/scripts/cost/fleet-digest.mjs +407 -0
  514. package/scripts/cost/fleet-digest.test.mjs +207 -0
  515. package/scripts/cost/track-claude-usage.mjs +169 -0
  516. package/scripts/daemon/agent-daemon.mjs +989 -0
  517. package/scripts/daemon/agent-daemon.test.mjs +525 -0
  518. package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
  519. package/scripts/daemon/cadence-consumer.mjs +1080 -0
  520. package/scripts/daemon/cadence-consumer.test.mjs +770 -0
  521. package/scripts/daemon/cadence-handlers.mjs +1121 -0
  522. package/scripts/daemon/cadence-handlers.test.mjs +617 -0
  523. package/scripts/daemon/classifier.mjs +704 -0
  524. package/scripts/daemon/classifier.test.mjs +238 -0
  525. package/scripts/daemon/classify-kind.mjs +54 -0
  526. package/scripts/daemon/classify-kind.test.mjs +40 -0
  527. package/scripts/daemon/context-compiler.mjs +605 -0
  528. package/scripts/daemon/context-compiler.test.mjs +300 -0
  529. package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
  530. package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
  531. package/scripts/daemon/dispatcher.mjs +1516 -0
  532. package/scripts/daemon/health.mjs +72 -0
  533. package/scripts/daemon/inbox-deferral.mjs +210 -0
  534. package/scripts/daemon/inbox-deferral.test.mjs +242 -0
  535. package/scripts/daemon/integration.test.mjs +149 -0
  536. package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
  537. package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
  538. package/scripts/daemon/launchd-wrapper.sh +91 -0
  539. package/scripts/daemon/lib/session-router.mjs +274 -0
  540. package/scripts/daemon/lib/session-router.test.mjs +295 -0
  541. package/scripts/daemon/maestro-daemon.mjs +275 -0
  542. package/scripts/daemon/prompt-builder.mjs +685 -0
  543. package/scripts/daemon/prompt-builder.test.mjs +213 -0
  544. package/scripts/daemon/responder.mjs +854 -0
  545. package/scripts/daemon/session-lock.mjs +721 -0
  546. package/scripts/daemon/session-lock.test.mjs +252 -0
  547. package/scripts/daemon/session-outcomes.mjs +640 -0
  548. package/scripts/daemon/session-outcomes.test.mjs +533 -0
  549. package/scripts/daemon/typing-registry.mjs +90 -0
  550. package/scripts/daemon/typing-registry.test.mjs +77 -0
  551. package/scripts/daemon/voice-webhook-server.mjs +804 -0
  552. package/scripts/decisions/capture-decision.mjs +116 -0
  553. package/scripts/disclosure_assessment.py +873 -0
  554. package/scripts/disclosure_boundaries.py +562 -0
  555. package/scripts/email-signature-principal.html +52 -0
  556. package/scripts/email-signature.html +60 -0
  557. package/scripts/email_quote_thread.py +167 -0
  558. package/scripts/email_thread_dedup.py +362 -0
  559. package/scripts/emergency-stop.sh +81 -0
  560. package/scripts/healthcheck.sh +116 -0
  561. package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
  562. package/scripts/hooks/block-mcp-slack-send.sh +7 -0
  563. package/scripts/hooks/post-action-log.sh +126 -0
  564. package/scripts/hooks/pre-send-audit.sh +174 -0
  565. package/scripts/hooks/pre-send-audit.test.mjs +215 -0
  566. package/scripts/hooks/session-end-log.sh +27 -0
  567. package/scripts/hooks/session-start-banner.sh +115 -0
  568. package/scripts/huddle/audio-bridge.mjs +664 -0
  569. package/scripts/huddle/boot-slack-cdp.sh +102 -0
  570. package/scripts/huddle/huddle-controller.mjs +942 -0
  571. package/scripts/huddle/huddle-server.mjs +1229 -0
  572. package/scripts/huddle/launch-slack.sh +232 -0
  573. package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
  574. package/scripts/huddle/package-lock.json +62 -0
  575. package/scripts/huddle/package.json +22 -0
  576. package/scripts/huddle/setup-audio.sh +239 -0
  577. package/scripts/huddle/start-call.mjs +318 -0
  578. package/scripts/huddle/test-pipeline.mjs +263 -0
  579. package/scripts/learning/consolidate-skills.mjs +72 -0
  580. package/scripts/learning/session-search.mjs +125 -0
  581. package/scripts/llm_email_dedup.py +442 -0
  582. package/scripts/local-triggers/generate-plists.sh +432 -0
  583. package/scripts/local-triggers/generate-plists.test.mjs +413 -0
  584. package/scripts/local-triggers/install-all.sh +49 -0
  585. package/scripts/local-triggers/plists/.gitkeep +0 -0
  586. package/scripts/local-triggers/run-trigger.sh +63 -0
  587. package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
  588. package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
  589. package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
  590. package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
  591. package/scripts/maintenance/backup-to-cloud.sh +124 -0
  592. package/scripts/maintenance/health-check.sh +377 -0
  593. package/scripts/media-generation/README.md +105 -0
  594. package/scripts/media-generation/gemini-image-client.mjs +173 -0
  595. package/scripts/media-generation/generate-assets.mjs +289 -0
  596. package/scripts/media-generation/veo-video-client.mjs +219 -0
  597. package/scripts/org/send-orgmail.mjs +227 -0
  598. package/scripts/outbound-dedup-cleanup.sh +43 -0
  599. package/scripts/outbound-dedup.sh +477 -0
  600. package/scripts/outbound_dedup.py +115 -0
  601. package/scripts/parse-voice-transcript.mjs +481 -0
  602. package/scripts/pdf-generation/README.md +63 -0
  603. package/scripts/pdf-generation/build-document.mjs +247 -0
  604. package/scripts/pdf-generation/templates/board-pack.latex +136 -0
  605. package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
  606. package/scripts/pdf-generation/templates/memo.latex +114 -0
  607. package/scripts/poll-slack-events.sh +35 -0
  608. package/scripts/poller/calendar-poller.mjs +12 -0
  609. package/scripts/poller/gmail-poller.mjs +192 -0
  610. package/scripts/poller/imap-client.mjs +289 -0
  611. package/scripts/poller/inbox-scan-poller.mjs +156 -0
  612. package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
  613. package/scripts/poller/index.mjs +73 -0
  614. package/scripts/poller/intra-session-check.mjs +285 -0
  615. package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
  616. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
  617. package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
  618. package/scripts/poller/secondary-gmail-poller.mjs +132 -0
  619. package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
  620. package/scripts/poller/slack-poller.mjs +854 -0
  621. package/scripts/poller/slack-socket-mode.mjs +917 -0
  622. package/scripts/poller/slack-socket-mode.test.mjs +753 -0
  623. package/scripts/poller/trigger.mjs +75 -0
  624. package/scripts/poller/utils.mjs +371 -0
  625. package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
  626. package/scripts/poller/voice-poller.mjs +236 -0
  627. package/scripts/poller-launchd/install.sh +66 -0
  628. package/scripts/poller-launchd/poller.plist.template +40 -0
  629. package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
  630. package/scripts/post-interaction-indexer.py +1598 -0
  631. package/scripts/pre-draft-context.py +994 -0
  632. package/scripts/pre_draft_lookup.py +258 -0
  633. package/scripts/rag/build-index.mjs +47 -0
  634. package/scripts/rag/ingest.mjs +111 -0
  635. package/scripts/rag/search.mjs +119 -0
  636. package/scripts/rag-indexer.py +629 -0
  637. package/scripts/restore-from-backup.sh +248 -0
  638. package/scripts/restore-from-backup.test.mjs +178 -0
  639. package/scripts/resume-operations.sh +80 -0
  640. package/scripts/search-secondary-inbox.py +181 -0
  641. package/scripts/secondary-inbox-poller.py +437 -0
  642. package/scripts/self-optimization/compute-metrics.py +398 -0
  643. package/scripts/send-email-as-principal.py +369 -0
  644. package/scripts/send-email-threaded.py +392 -0
  645. package/scripts/send-email-with-attachment.py +377 -0
  646. package/scripts/send-email.sh +131 -0
  647. package/scripts/send-sms.sh +175 -0
  648. package/scripts/send-whatsapp.sh +292 -0
  649. package/scripts/session-start.sh +106 -0
  650. package/scripts/setup/boot-claude-session.sh +94 -0
  651. package/scripts/setup/configure-macos.sh +674 -0
  652. package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
  653. package/scripts/setup/configure-voice-tunnel.mjs +182 -0
  654. package/scripts/setup/generate-agent-env.mjs +92 -0
  655. package/scripts/setup/generate-agent-package-json.mjs +222 -0
  656. package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
  657. package/scripts/setup/generate-autonomy.mjs +60 -0
  658. package/scripts/setup/generate-backlog.mjs +101 -0
  659. package/scripts/setup/generate-cadences.mjs +92 -0
  660. package/scripts/setup/generate-capability.mjs +162 -0
  661. package/scripts/setup/generate-charter.mjs +76 -0
  662. package/scripts/setup/generate-comms.mjs +59 -0
  663. package/scripts/setup/generate-company.mjs +92 -0
  664. package/scripts/setup/init-agent.sh +547 -0
  665. package/scripts/setup/init-agent.test.mjs +151 -0
  666. package/scripts/setup/init-archetype.mjs +74 -0
  667. package/scripts/setup/init-backup.mjs +54 -0
  668. package/scripts/setup/init-cadence-bus.mjs +60 -0
  669. package/scripts/setup/init-channel-bus.mjs +46 -0
  670. package/scripts/setup/init-cost-tracking.mjs +45 -0
  671. package/scripts/setup/init-decision-capture.mjs +66 -0
  672. package/scripts/setup/init-known-agents.mjs +57 -0
  673. package/scripts/setup/init-learning.mjs +70 -0
  674. package/scripts/setup/init-memory-executive.mjs +45 -0
  675. package/scripts/setup/init-model-router.mjs +124 -0
  676. package/scripts/setup/init-rag.mjs +174 -0
  677. package/scripts/setup/init-session-router.mjs +38 -0
  678. package/scripts/setup/init-slack-socket-mode.mjs +260 -0
  679. package/scripts/setup/init-telegram.mjs +165 -0
  680. package/scripts/setup/init-voice-realtime.mjs +204 -0
  681. package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
  682. package/scripts/setup/install-dev-tools.sh +150 -0
  683. package/scripts/setup/lib/install-plist.mjs +95 -0
  684. package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
  685. package/scripts/setup/render-environment-yaml.mjs +133 -0
  686. package/scripts/slack-events-ctl.sh +177 -0
  687. package/scripts/slack-events-server.mjs +1045 -0
  688. package/scripts/slack-react.mjs +89 -0
  689. package/scripts/slack-responded.sh +232 -0
  690. package/scripts/slack-send.sh +287 -0
  691. package/scripts/slack-typing.mjs +196 -0
  692. package/scripts/slack-upload-v2.py +95 -0
  693. package/scripts/sms-handler.mjs +450 -0
  694. package/scripts/spawn-session.sh +120 -0
  695. package/scripts/sync-protocol.mjs +217 -0
  696. package/scripts/system-verify.sh +184 -0
  697. package/scripts/test-email-thread-dedup.py +239 -0
  698. package/scripts/test-information-barriers.py +484 -0
  699. package/scripts/test-llm-email-dedup.py +251 -0
  700. package/scripts/test-pre-draft-integration.py +203 -0
  701. package/scripts/test-rag-phase2.sh +442 -0
  702. package/scripts/test-rag-search.sh +251 -0
  703. package/scripts/test-voice-parser.mjs +316 -0
  704. package/scripts/user-context-search.py +659 -0
  705. package/scripts/validate_outbound.py +1504 -0
  706. package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
  707. package/scripts/watchdog/force-reboot.sh +157 -0
  708. package/scripts/watchdog/memory-watchdog.sh +473 -0
  709. package/scripts/whatsapp-handler.mjs +538 -0
  710. package/teams/desktop-operations.yaml +34 -0
  711. package/teams/executive-office.yaml +27 -0
  712. package/teams/legal-and-regulatory.yaml +24 -0
  713. package/teams/platform-and-engineering.yaml +23 -0
  714. package/teams/strategy-and-growth.yaml +29 -0
  715. package/workflows/continuous/backlog-executor.yaml +141 -0
  716. package/workflows/continuous/inbound-monitor.yaml +168 -0
  717. package/workflows/daily/applicant-triage.yaml +197 -0
  718. package/workflows/daily/comms-triage.yaml +80 -0
  719. package/workflows/daily/evening-wrap.yaml +105 -0
  720. package/workflows/daily/morning-brief.yaml +164 -0
  721. package/workflows/daily/slack-followup-sweep.yaml +87 -0
  722. package/workflows/event-driven/README.md +50 -0
  723. package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
  724. package/workflows/event-driven/pr-review.yaml +107 -0
  725. package/workflows/monthly/board-readiness.yaml +76 -0
  726. package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
  727. package/workflows/session-protocol.md +171 -0
  728. package/workflows/weekly/engineering-health.yaml +154 -0
  729. package/workflows/weekly/hiring-review.yaml +169 -0
  730. package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
  731. package/workflows/weekly/strategic-memo.yaml +79 -0
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>ai.maestro.memory-watchdog</string>
7
+
8
+ <key>ProgramArguments</key>
9
+ <array>
10
+ <string>/bin/bash</string>
11
+ <string>__AGENT_DIR__/scripts/watchdog/memory-watchdog.sh</string>
12
+ </array>
13
+
14
+ <key>StartInterval</key>
15
+ <integer>30</integer>
16
+
17
+ <key>RunAtLoad</key>
18
+ <true/>
19
+
20
+ <key>StandardOutPath</key>
21
+ <string>__AGENT_DIR__/logs/watchdog/launchd-stdout.log</string>
22
+
23
+ <key>StandardErrorPath</key>
24
+ <string>__AGENT_DIR__/logs/watchdog/launchd-stderr.log</string>
25
+
26
+ <key>EnvironmentVariables</key>
27
+ <dict>
28
+ <key>PATH</key>
29
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
30
+ </dict>
31
+
32
+ <key>Nice</key>
33
+ <integer>10</integer>
34
+
35
+ <key>ProcessType</key>
36
+ <string>Background</string>
37
+
38
+ <key>ThrottleInterval</key>
39
+ <integer>10</integer>
40
+ </dict>
41
+ </plist>
@@ -0,0 +1,157 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # force-reboot.sh — Remote/Automated Force Reboot for Frozen Mac Mini
4
+ # =============================================================================
5
+ #
6
+ # This script provides multiple escalation levels for rebooting a frozen
7
+ # or unresponsive Mac mini running the Maestro agent system.
8
+ #
9
+ # Usage:
10
+ # ./scripts/watchdog/force-reboot.sh --graceful # Try graceful shutdown first
11
+ # ./scripts/watchdog/force-reboot.sh --force # Immediate reboot (requires sudo)
12
+ # ./scripts/watchdog/force-reboot.sh --status # Check heartbeat and uptime
13
+ #
14
+ # Remote usage (from another machine via SSH):
15
+ # ssh <user>@<mac-mini> "~/maestro/scripts/watchdog/force-reboot.sh --graceful"
16
+ #
17
+ # =============================================================================
18
+ #
19
+ # FORCE-REBOOT OPTIONS WHEN THE MACHINE IS COMPLETELY FROZEN:
20
+ #
21
+ # If SSH is unresponsive and the GUI is frozen, the only options are:
22
+ #
23
+ # 1. SMART PLUG (recommended for headless servers):
24
+ # - Install a WiFi smart plug (TP-Link Kasa, Shelly, etc.) on the Mac mini power
25
+ # - Power cycle via the smart plug's app or API
26
+ # - Combined with `pmset autorestart 1` (already configured), the Mac mini
27
+ # will boot automatically when power is restored
28
+ # - Example (TP-Link Kasa):
29
+ # kasa --host <plug-ip> --type plug off && sleep 5 && kasa --host <plug-ip> --type plug on
30
+ # - Example (Shelly):
31
+ # curl -s "http://<shelly-ip>/relay/0?turn=off" && sleep 5 && curl -s "http://<shelly-ip>/relay/0?turn=on"
32
+ #
33
+ # 2. PARSEC (if display is frozen but network is up):
34
+ # - Parsec may still be responsive even if the GUI is frozen
35
+ # - Connect via Parsec and use Cmd+Ctrl+Power to force restart
36
+ #
37
+ # 3. APPLE REMOTE MANAGEMENT (ARD):
38
+ # - If enabled, use "Send UNIX Command" from another Mac:
39
+ # sudo shutdown -r now
40
+ #
41
+ # 4. PHYSICAL POWER CYCLE:
42
+ # - Unplug and replug the Mac mini power cable
43
+ # - With `pmset autorestart 1`, it will boot automatically
44
+ #
45
+ # 5. SSH + SYSDIAGNOSE RESET (partial freeze, SSH still works):
46
+ # - ssh <user>@<mac-mini> "sudo reboot"
47
+ # - Or: ssh <user>@<mac-mini> "sudo shutdown -r now"
48
+ #
49
+ # =============================================================================
50
+
51
+ set -euo pipefail
52
+
53
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
54
+ MAESTRO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
55
+
56
+ HEARTBEAT_FILE="$MAESTRO_DIR/state/heartbeat"
57
+ LOG_FILE="$MAESTRO_DIR/logs/watchdog/$(date +%Y-%m-%d)-reboot.log"
58
+
59
+ log() {
60
+ local ts
61
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
62
+ echo "[$ts] $1" | tee -a "$LOG_FILE"
63
+ }
64
+
65
+ check_heartbeat() {
66
+ if [ ! -f "$HEARTBEAT_FILE" ]; then
67
+ echo "No heartbeat file found — watchdog may not be running"
68
+ return 1
69
+ fi
70
+
71
+ local hb_time hb_age
72
+ hb_time=$(cat "$HEARTBEAT_FILE")
73
+ hb_age=$(( $(date +%s) - $(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$hb_time" +%s 2>/dev/null || echo 0) ))
74
+
75
+ echo "Last heartbeat: $hb_time (${hb_age}s ago)"
76
+ echo "System uptime: $(uptime)"
77
+
78
+ if [ "$hb_age" -gt 120 ]; then
79
+ echo "WARNING: Heartbeat is stale (>2 min) — watchdog may be stuck"
80
+ return 1
81
+ fi
82
+ return 0
83
+ }
84
+
85
+ graceful_reboot() {
86
+ log "Initiating graceful reboot (WS4 self-heal escalation)..."
87
+
88
+ # IMPORTANT (WS4 invariant): a graceful reboot is a RECOVERY action, not a
89
+ # halt. We deliberately do NOT write .emergency-stop — that flag is HUMAN-ONLY
90
+ # now. After the reboot, `pmset autorestart 1` powers the box back on and the
91
+ # RunAtLoad daemon plist restarts the agent automatically. Writing
92
+ # .emergency-stop here would brick the agent until a human cleared it —
93
+ # exactly the failure mode this rewrite removes.
94
+
95
+ # 1. Drain: give running sessions a moment to wrap up.
96
+ log "Waiting 30s for running sessions to wind down..."
97
+ sleep 30
98
+
99
+ # 2. SIGTERM remaining claude subprocesses so they exit cleanly (their close
100
+ # handlers release locks + leave resume markers the daemon reconciles on
101
+ # the next boot — no work dropped).
102
+ log "Sending SIGTERM to remaining claude processes..."
103
+ pkill -TERM -f "claude" 2>/dev/null || true
104
+ sleep 5
105
+
106
+ # 3. Best-effort durable state snapshot (queues live on disk regardless).
107
+ log "Attempting to snapshot state..."
108
+ (
109
+ cd "$MAESTRO_DIR"
110
+ git add -A state/ logs/ 2>/dev/null || true
111
+ git commit -m "chore: pre-reboot state save (watchdog-initiated, WS4)" 2>/dev/null || true
112
+ git push origin main 2>/dev/null || true
113
+ ) || true
114
+
115
+ # 4. Reboot. pmset autorestart brings the box back; the daemon restarts and
116
+ # reconciles in-flight work from state/sessions/resume-pending/.
117
+ log "Rebooting now..."
118
+ sudo shutdown -r now 2>/dev/null || {
119
+ log "sudo shutdown failed — trying osascript..."
120
+ osascript -e 'tell application "System Events" to restart' 2>/dev/null || {
121
+ log "All reboot methods failed — manual intervention required"
122
+ exit 1
123
+ }
124
+ }
125
+ }
126
+
127
+ force_reboot() {
128
+ log "FORCE REBOOT initiated"
129
+ # WS4: a reboot is recovery, not a halt — do NOT write .emergency-stop (that
130
+ # flag is human-only and would keep the agent down after the box comes back
131
+ # via pmset autorestart). An operator who wants the agent to STAY down should
132
+ # write .emergency-stop themselves, separately.
133
+ sudo reboot 2>/dev/null || sudo shutdown -r now 2>/dev/null || {
134
+ log "sudo reboot failed — requires password or system is frozen"
135
+ echo "ERROR: Cannot force reboot without sudo. Options:"
136
+ echo " 1. Run: sudo reboot"
137
+ echo " 2. Power cycle via smart plug"
138
+ echo " 3. Physical power cycle"
139
+ exit 1
140
+ }
141
+ }
142
+
143
+ case "${1:---status}" in
144
+ --status)
145
+ check_heartbeat
146
+ ;;
147
+ --graceful)
148
+ graceful_reboot
149
+ ;;
150
+ --force)
151
+ force_reboot
152
+ ;;
153
+ *)
154
+ echo "Usage: $0 [--status|--graceful|--force]"
155
+ exit 1
156
+ ;;
157
+ esac
@@ -0,0 +1,473 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # memory-watchdog.sh — System Resource Watchdog for Maestro Agent Deployment
4
+ # =============================================================================
5
+ #
6
+ # Monitors memory pressure + Claude Code subprocess RSS and DEGRADES GRACEFULLY
7
+ # instead of bricking the agent. The cardinal rule (WS4 invariant): every
8
+ # automated terminal action ends in "the agent is running again." The ONLY
9
+ # thing that permanently stops an agent is a HUMAN writing .emergency-stop —
10
+ # this watchdog NEVER writes it.
11
+ #
12
+ # Runs every 30 seconds via launchd (ai.maestro.memory-watchdog.plist).
13
+ #
14
+ # Tiers (escalating, self-healing):
15
+ # SOFT (sys 70–80% / claude ≥60%) → write state/throttle.json with an
16
+ # auto-expiring ceiling = liveCount-2 that the resource governor
17
+ # reads to admit fewer sessions. NO kills.
18
+ # HARD (sys 80–90% / claude ≥75%) → SIGTERM the youngest/biggest
19
+ # subagents, sparing the oldest "main" session (MIN_AGE_SECONDS).
20
+ # CRITICAL (sys ≥90%, sustained 2 consecutive runs) → clean daemon RESTART
21
+ # via `launchctl kickstart -k` (the daemon re-reads state/queues +
22
+ # cooldowns + sweeps stale claims). NOT a halt. Only after 3 failed
23
+ # CRITICAL restarts within 15 min do we escalate to
24
+ # force-reboot.sh --graceful (pmset autorestart brings the box back).
25
+ #
26
+ # The legacy emergency-stop-on-OOM behaviour is GONE: it bricked agents on a
27
+ # transient memory spike and required a human to clear the flag.
28
+ #
29
+ # Usage:
30
+ # ./scripts/watchdog/memory-watchdog.sh # Normal run
31
+ # ./scripts/watchdog/memory-watchdog.sh --check # Report status only
32
+ # ./scripts/watchdog/memory-watchdog.sh --dry-run # Show what would be done
33
+ #
34
+ # =============================================================================
35
+
36
+ set -euo pipefail
37
+
38
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
39
+ MAESTRO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
40
+
41
+ # Auto-detect agent directory (any ~/<name>-ai or override via AGENT_DIR env).
42
+ # The watchdog runs from maestro but protects the whole machine.
43
+ AGENT_DIR="${AGENT_DIR:-}"
44
+
45
+ # --- Ensure system paths are available ----------------------------------------
46
+ # launchd PATH may not include /usr/sbin where sysctl lives
47
+ export PATH="/usr/sbin:/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin:$PATH"
48
+
49
+ # --- Configuration -----------------------------------------------------------
50
+
51
+ # Total physical RAM in KB
52
+ TOTAL_RAM_KB=$(sysctl -n hw.memsize | awk '{printf "%.0f", $1 / 1024}')
53
+
54
+ # Claude-process RSS thresholds (percentage of total RAM).
55
+ CLAUDE_SOFT_PERCENT="${WATCHDOG_SOFT_PERCENT:-60}" # SOFT tier (throttle, no kills)
56
+ CLAUDE_HARD_PERCENT="${WATCHDOG_HARD_PERCENT:-75}" # HARD tier (kill youngest/biggest)
57
+
58
+ # System-wide memory pressure thresholds (percentage used).
59
+ SYSTEM_SOFT_PERCENT="${WATCHDOG_SYS_SOFT_PERCENT:-70}" # SOFT tier
60
+ SYSTEM_HARD_PERCENT="${WATCHDOG_SYS_HARD_PERCENT:-80}" # HARD tier
61
+ SYSTEM_CRITICAL_PERCENT="${WATCHDOG_CRITICAL_PERCENT:-90}" # CRITICAL tier (restart)
62
+
63
+ # Max concurrent claude CLI processes (excluding the main interactive session)
64
+ MAX_CLAUDE_PROCS="${WATCHDOG_MAX_CLAUDE_PROCS:-8}"
65
+
66
+ # Minimum age (seconds) before a claude process can be killed — protects
67
+ # freshly-spawned sessions (and, by sparing the oldest, the "main" session).
68
+ MIN_AGE_SECONDS="${WATCHDOG_MIN_AGE:-60}"
69
+
70
+ # Soft-throttle auto-expiry (seconds). The governor ignores an expired ceiling,
71
+ # so even if the watchdog dies the throttle lifts on its own.
72
+ THROTTLE_TTL_SECONDS="${WATCHDOG_THROTTLE_TTL:-300}" # 5 min
73
+
74
+ # CRITICAL must persist this many consecutive runs before we restart the daemon.
75
+ CRITICAL_SUSTAIN_RUNS="${WATCHDOG_CRITICAL_SUSTAIN:-2}"
76
+
77
+ # Failed-restart escalation: more than this many CRITICAL restarts within the
78
+ # window → escalate to a graceful reboot.
79
+ RESTART_FAIL_LIMIT="${WATCHDOG_RESTART_FAIL_LIMIT:-3}"
80
+ RESTART_FAIL_WINDOW_SECONDS="${WATCHDOG_RESTART_FAIL_WINDOW:-900}" # 15 min
81
+
82
+ # Files
83
+ LOG_DIR="$MAESTRO_DIR/logs/watchdog"
84
+ LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d)-watchdog.jsonl"
85
+ STATE_FILE="$MAESTRO_DIR/state/watchdog-state.yaml"
86
+ THROTTLE_FILE="$MAESTRO_DIR/state/throttle.json"
87
+ # Counter/journal for CRITICAL sustain + restart-failure tracking.
88
+ WD_COUNTER_FILE="$MAESTRO_DIR/state/watchdog-critical.json"
89
+
90
+ # --- Helpers -----------------------------------------------------------------
91
+
92
+ mkdir -p "$LOG_DIR" "$(dirname "$STATE_FILE")"
93
+
94
+ log_event() {
95
+ local level="$1" event="$2" detail="${3:-}"
96
+ local ts
97
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
98
+ printf '{"ts":"%s","level":"%s","event":"%s","detail":%s}\n' \
99
+ "$ts" "$level" "$event" "$(echo "$detail" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))' 2>/dev/null || echo '""')" \
100
+ >> "$LOG_FILE"
101
+ }
102
+
103
+ now_epoch() { date +%s; }
104
+
105
+ # --- Memory Metrics ----------------------------------------------------------
106
+
107
+ get_system_memory_pressure() {
108
+ # macOS memory_pressure returns a percentage of total memory free.
109
+ local free_pct
110
+ free_pct=$(memory_pressure 2>/dev/null | grep "System-wide memory free percentage:" | awk '{print $NF}' | tr -d '%')
111
+ if [ -z "$free_pct" ]; then
112
+ local page_size free_pages
113
+ page_size=$(vm_stat | head -1 | grep -o '[0-9]*')
114
+ free_pages=$(vm_stat | awk '/Pages free/ {print $NF}' | tr -d '.')
115
+ local free_kb=$(( (free_pages * page_size) / 1024 ))
116
+ free_pct=$(( (free_kb * 100) / TOTAL_RAM_KB ))
117
+ fi
118
+ echo $(( 100 - free_pct ))
119
+ }
120
+
121
+ get_claude_processes() {
122
+ # Returns: PID RSS_KB ELAPSED_SECONDS COMMAND, sorted by RSS descending.
123
+ /bin/ps -eo pid,rss,etime,comm | \
124
+ grep -i "claude" | \
125
+ grep -v "Claude.app" | \
126
+ grep -v "Claude Helper" | \
127
+ grep -v "grep" | \
128
+ grep -v "watchdog" | \
129
+ awk '{
130
+ pid = $1
131
+ rss = $2
132
+ etime = $3
133
+ comm = $4
134
+ n = split(etime, parts, ":")
135
+ if (n == 1) { secs = parts[1] }
136
+ else if (n == 2) { secs = parts[1]*60 + parts[2] }
137
+ else if (n == 3) {
138
+ if (index(parts[1], "-") > 0) {
139
+ split(parts[1], dp, "-")
140
+ secs = dp[1]*86400 + dp[2]*3600 + parts[2]*60 + parts[3]
141
+ } else {
142
+ secs = parts[1]*3600 + parts[2]*60 + parts[3]
143
+ }
144
+ }
145
+ printf "%s %s %s %s\n", pid, rss, secs, comm
146
+ }' | sort -k2 -rn
147
+ }
148
+
149
+ get_total_claude_rss_kb() {
150
+ get_claude_processes | awk '{sum += $2} END {print sum+0}'
151
+ }
152
+
153
+ count_claude_processes() {
154
+ get_claude_processes | wc -l | tr -d ' '
155
+ }
156
+
157
+ # --- Identification ----------------------------------------------------------
158
+
159
+ identify_main_session() {
160
+ # The oldest claude process is treated as the "main" session and spared by
161
+ # HARD-tier kills. (longest-running heuristic)
162
+ get_claude_processes | tail -1 | awk '{print $1}'
163
+ }
164
+
165
+ # --- SOFT tier: write the auto-expiring throttle ceiling ----------------------
166
+
167
+ write_throttle() {
168
+ local ceiling="$1" reason="$2"
169
+ [ "$ceiling" -lt 1 ] && ceiling=1
170
+ local set_at expires_at
171
+ set_at=$(now_epoch)
172
+ expires_at=$(( set_at + THROTTLE_TTL_SECONDS ))
173
+ # The governor reads set_at/expires_at in EPOCH MILLISECONDS (Date.now()).
174
+ local set_ms=$(( set_at * 1000 ))
175
+ local exp_ms=$(( expires_at * 1000 ))
176
+ mkdir -p "$(dirname "$THROTTLE_FILE")"
177
+ local tmp="$THROTTLE_FILE.tmp.$$"
178
+ printf '{"ceiling":%s,"reason":"%s","set_at":%s,"expires_at":%s}\n' \
179
+ "$ceiling" "$reason" "$set_ms" "$exp_ms" > "$tmp"
180
+ mv -f "$tmp" "$THROTTLE_FILE"
181
+ log_event "warn" "throttle_set" "ceiling=$ceiling reason=$reason ttl=${THROTTLE_TTL_SECONDS}s"
182
+ }
183
+
184
+ # --- HARD tier: kill youngest/biggest subagents, spare the oldest "main" ------
185
+
186
+ kill_youngest_biggest_subagents() {
187
+ local target_count="$1"
188
+ local dry_run="${2:-false}"
189
+ local main_pid
190
+ main_pid=$(identify_main_session)
191
+ local killed=0
192
+
193
+ # get_claude_processes is sorted by RSS descending → biggest first. We skip
194
+ # the main session and anything younger than MIN_AGE_SECONDS.
195
+ while IFS=' ' read -r pid rss age comm; do
196
+ [ -z "$pid" ] && continue
197
+ if [ "$pid" = "$main_pid" ]; then
198
+ continue
199
+ fi
200
+ if [ "$age" -lt "$MIN_AGE_SECONDS" ]; then
201
+ log_event "info" "skip_young" "PID $pid age ${age}s < ${MIN_AGE_SECONDS}s minimum"
202
+ continue
203
+ fi
204
+ if [ "$killed" -ge "$target_count" ]; then
205
+ break
206
+ fi
207
+
208
+ local rss_mb=$(( rss / 1024 ))
209
+ if [ "$dry_run" = "true" ]; then
210
+ echo " [DRY RUN] Would kill PID $pid (${rss_mb}MB, age ${age}s)"
211
+ log_event "info" "dry_run_kill" "PID=$pid RSS=${rss_mb}MB age=${age}s"
212
+ else
213
+ echo " Killing PID $pid (${rss_mb}MB, age ${age}s)"
214
+ kill -TERM "$pid" 2>/dev/null || true
215
+ log_event "warn" "killed_subagent" "PID=$pid RSS=${rss_mb}MB age=${age}s signal=TERM"
216
+ killed=$((killed + 1))
217
+ (
218
+ sleep 5
219
+ if kill -0 "$pid" 2>/dev/null; then
220
+ kill -9 "$pid" 2>/dev/null || true
221
+ printf '{"ts":"%s","level":"warn","event":"force_killed","detail":"PID=%s did not exit after SIGTERM"}\n' \
222
+ "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "$pid" >> "$LOG_FILE"
223
+ fi
224
+ ) &
225
+ fi
226
+ done <<< "$(get_claude_processes)"
227
+
228
+ echo "$killed"
229
+ }
230
+
231
+ # --- CRITICAL tier: clean daemon restart, then graceful reboot if it keeps failing
232
+
233
+ # Resolve the agent's daemon launchd label so we can kickstart it.
234
+ resolve_daemon_label() {
235
+ local first=""
236
+ if [ -n "$AGENT_DIR" ] && [ -f "$AGENT_DIR/config/agent.json" ]; then
237
+ first=$(awk -F'"' '/"firstName"[[:space:]]*:/ { print tolower($4); exit }' "$AGENT_DIR/config/agent.json" 2>/dev/null || true)
238
+ fi
239
+ if [ -z "$first" ]; then
240
+ for agent_dir in "$HOME"/*-ai; do
241
+ [ -f "$agent_dir/config/agent.json" ] || continue
242
+ first=$(awk -F'"' '/"firstName"[[:space:]]*:/ { print tolower($4); exit }' "$agent_dir/config/agent.json" 2>/dev/null || true)
243
+ [ -n "$first" ] && break
244
+ done
245
+ fi
246
+ [ -z "$first" ] && return 1
247
+ echo "ai.maestro.${first}-daemon"
248
+ }
249
+
250
+ # Read/append the restart-failure journal; echoes the count of restarts within
251
+ # the rolling window AFTER recording this one.
252
+ record_critical_restart() {
253
+ local ts; ts=$(now_epoch)
254
+ local cutoff=$(( ts - RESTART_FAIL_WINDOW_SECONDS ))
255
+ local existing=""
256
+ if [ -f "$WD_COUNTER_FILE" ]; then
257
+ existing=$(python3 - "$WD_COUNTER_FILE" "$cutoff" "$ts" <<'PY' 2>/dev/null || echo ""
258
+ import sys, json
259
+ path, cutoff, ts = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
260
+ try:
261
+ d = json.load(open(path))
262
+ except Exception:
263
+ d = {}
264
+ restarts = [t for t in d.get("restarts", []) if isinstance(t,(int,float)) and t >= cutoff]
265
+ restarts.append(ts)
266
+ d["restarts"] = restarts
267
+ json.dump(d, open(path, "w"))
268
+ print(len(restarts))
269
+ PY
270
+ )
271
+ fi
272
+ if [ -z "$existing" ]; then
273
+ # python3 missing/failed — fall back to a single-entry file (no windowing).
274
+ printf '{"restarts":[%s]}\n' "$ts" > "$WD_COUNTER_FILE"
275
+ echo 1
276
+ else
277
+ echo "$existing"
278
+ fi
279
+ }
280
+
281
+ critical_restart() {
282
+ local dry_run="${1:-false}"
283
+ local label
284
+ if ! label=$(resolve_daemon_label); then
285
+ log_event "error" "critical_no_daemon_label" "could not resolve daemon launchd label; skipping restart"
286
+ return 0
287
+ fi
288
+
289
+ local fails
290
+ fails=$(record_critical_restart)
291
+ if [ "$fails" -gt "$RESTART_FAIL_LIMIT" ]; then
292
+ log_event "critical" "critical_restart_escalation" "label=$label restarts=$fails within ${RESTART_FAIL_WINDOW_SECONDS}s > limit=$RESTART_FAIL_LIMIT — escalating to graceful reboot"
293
+ if [ "$dry_run" = "true" ]; then
294
+ echo " [DRY RUN] Would run force-reboot.sh --graceful (restart loop)"
295
+ return 0
296
+ fi
297
+ "$SCRIPT_DIR/force-reboot.sh" --graceful || log_event "error" "graceful_reboot_failed" "force-reboot.sh --graceful returned non-zero"
298
+ return 0
299
+ fi
300
+
301
+ log_event "critical" "critical_daemon_restart" "label=$label kickstart -k (restart #$fails in window)"
302
+ if [ "$dry_run" = "true" ]; then
303
+ echo " [DRY RUN] Would: launchctl kickstart -k gui/$(id -u)/$label"
304
+ return 0
305
+ fi
306
+ # Clean restart: re-reads state/queues + cooldowns + sweeps stale claims on
307
+ # boot. This is self-healing, NOT a halt.
308
+ local uid; uid=$(id -u)
309
+ if ! launchctl kickstart -k "gui/${uid}/${label}" 2>/dev/null; then
310
+ # Fallback for older launchd: unload+load the installed plist.
311
+ local plist="$HOME/Library/LaunchAgents/${label}.plist"
312
+ if [ -f "$plist" ]; then
313
+ launchctl unload "$plist" 2>/dev/null || true
314
+ launchctl load "$plist" 2>/dev/null || true
315
+ else
316
+ log_event "warn" "kickstart_failed_no_plist" "label=$label plist not found at $plist"
317
+ fi
318
+ fi
319
+ }
320
+
321
+ # --- CRITICAL sustain counter (in the counter JSON) ---------------------------
322
+
323
+ # Increments (or resets) the consecutive-CRITICAL counter; echoes the new value.
324
+ bump_critical_sustain() {
325
+ local crit="$1" # "true" to increment, "false" to reset
326
+ python3 - "$WD_COUNTER_FILE" "$crit" <<'PY' 2>/dev/null || { [ "$crit" = "true" ] && echo 1 || echo 0; }
327
+ import sys, json
328
+ path, crit = sys.argv[1], sys.argv[2]
329
+ try:
330
+ d = json.load(open(path))
331
+ except Exception:
332
+ d = {}
333
+ if crit == "true":
334
+ d["sustain"] = int(d.get("sustain", 0)) + 1
335
+ else:
336
+ d["sustain"] = 0
337
+ json.dump(d, open(path, "w"))
338
+ print(d["sustain"])
339
+ PY
340
+ }
341
+
342
+ # --- Main Logic --------------------------------------------------------------
343
+
344
+ run_watchdog() {
345
+ local mode="${1:-run}"
346
+ local dry_run="false"
347
+ [ "$mode" = "--dry-run" ] && dry_run="true"
348
+ [ "$mode" = "--check" ] && dry_run="true"
349
+
350
+ # Collect metrics
351
+ local claude_rss_kb claude_count system_pressure
352
+ claude_rss_kb=$(get_total_claude_rss_kb)
353
+ claude_count=$(count_claude_processes)
354
+ system_pressure=$(get_system_memory_pressure)
355
+
356
+ local claude_rss_mb=$(( claude_rss_kb / 1024 ))
357
+ local claude_pct=$(( (claude_rss_kb * 100) / TOTAL_RAM_KB ))
358
+ local soft_threshold_mb=$(( (TOTAL_RAM_KB * CLAUDE_SOFT_PERCENT) / 100 / 1024 ))
359
+ local hard_threshold_mb=$(( (TOTAL_RAM_KB * CLAUDE_HARD_PERCENT) / 100 / 1024 ))
360
+
361
+ # Report
362
+ if [ "$mode" = "--check" ]; then
363
+ echo "=== Memory Watchdog Status ==="
364
+ echo "Total RAM: $(( TOTAL_RAM_KB / 1024 ))MB"
365
+ echo "Claude processes: $claude_count (max: $MAX_CLAUDE_PROCS)"
366
+ echo "Claude RSS total: ${claude_rss_mb}MB (${claude_pct}% of RAM)"
367
+ echo " SOFT threshold: ${soft_threshold_mb}MB (${CLAUDE_SOFT_PERCENT}%)"
368
+ echo " HARD threshold: ${hard_threshold_mb}MB (${CLAUDE_HARD_PERCENT}%)"
369
+ echo "System mem pressure: ${system_pressure}% (SOFT ${SYSTEM_SOFT_PERCENT}% / HARD ${SYSTEM_HARD_PERCENT}% / CRITICAL ${SYSTEM_CRITICAL_PERCENT}%)"
370
+ if [ -f "$THROTTLE_FILE" ]; then
371
+ echo "Throttle: active — $(cat "$THROTTLE_FILE")"
372
+ else
373
+ echo "Throttle: none"
374
+ fi
375
+ echo ""
376
+ echo "Claude processes:"
377
+ get_claude_processes | while IFS=' ' read -r pid rss age comm; do
378
+ echo " PID $pid: $(( rss / 1024 ))MB, age ${age}s — $comm"
379
+ done
380
+ echo "=============================="
381
+ return 0
382
+ fi
383
+
384
+ # --- CRITICAL: system pressure ≥90% sustained 2 runs → clean daemon restart -
385
+ if [ "$system_pressure" -ge "$SYSTEM_CRITICAL_PERCENT" ]; then
386
+ local sustain
387
+ sustain=$(bump_critical_sustain "true")
388
+ log_event "critical" "system_memory_critical" "pressure=${system_pressure}% >= ${SYSTEM_CRITICAL_PERCENT}% (sustain ${sustain}/${CRITICAL_SUSTAIN_RUNS})"
389
+ if [ "$sustain" -ge "$CRITICAL_SUSTAIN_RUNS" ]; then
390
+ echo "CRITICAL: memory pressure ${system_pressure}% sustained — restarting daemon (self-heal)"
391
+ critical_restart "$dry_run"
392
+ # Reset the sustain counter after acting so we don't restart every run.
393
+ bump_critical_sustain "false" >/dev/null 2>&1 || true
394
+ else
395
+ echo "CRITICAL: memory pressure ${system_pressure}% (run ${sustain}/${CRITICAL_SUSTAIN_RUNS} before restart)"
396
+ # Also throttle hard while we wait for the second confirming run.
397
+ [ "$dry_run" = "false" ] && write_throttle 1 "critical-pending"
398
+ fi
399
+ return 0
400
+ else
401
+ # Pressure recovered below CRITICAL — reset the sustain counter.
402
+ bump_critical_sustain "false" >/dev/null 2>&1 || true
403
+ fi
404
+
405
+ # --- HARD: kill youngest/biggest subagents (spare the oldest main) ----------
406
+ if [ "$system_pressure" -ge "$SYSTEM_HARD_PERCENT" ] || [ "$claude_pct" -ge "$CLAUDE_HARD_PERCENT" ]; then
407
+ log_event "warn" "hard_tier" "sys=${system_pressure}% claude=${claude_pct}% (RSS ${claude_rss_mb}MB)"
408
+ echo "HARD: trimming subagents (sys ${system_pressure}%, claude ${claude_pct}%)"
409
+ # Kill enough to drop the claude RSS back toward the SOFT threshold.
410
+ local target_rss_kb=$(( (TOTAL_RAM_KB * CLAUDE_SOFT_PERCENT) / 100 ))
411
+ local excess_kb=$(( claude_rss_kb - target_rss_kb ))
412
+ [ "$excess_kb" -lt 0 ] && excess_kb=0
413
+ local procs_to_kill=$(( (excess_kb / 614400) + 1 )) # ~600MB/proc estimate
414
+ [ "$procs_to_kill" -lt 1 ] && procs_to_kill=1
415
+ echo " Killing $procs_to_kill subagent(s) to free ~$(( excess_kb / 1024 ))MB..."
416
+ kill_youngest_biggest_subagents "$procs_to_kill" "$dry_run" >/dev/null
417
+ # Also lower the ceiling so the governor doesn't immediately refill the slots.
418
+ if [ "$dry_run" = "false" ]; then
419
+ local remaining=$(( claude_count - procs_to_kill ))
420
+ [ "$remaining" -lt 1 ] && remaining=1
421
+ write_throttle "$remaining" "hard-tier"
422
+ fi
423
+ return 0
424
+ fi
425
+
426
+ # --- Too many concurrent processes (count-based HARD) -----------------------
427
+ if [ "$claude_count" -gt "$MAX_CLAUDE_PROCS" ]; then
428
+ local excess=$(( claude_count - MAX_CLAUDE_PROCS ))
429
+ log_event "warn" "too_many_claude_procs" "count=${claude_count} > max=${MAX_CLAUDE_PROCS}"
430
+ echo "WARNING: $claude_count claude processes running (max: $MAX_CLAUDE_PROCS)"
431
+ echo " Killing $excess oldest subagent(s)..."
432
+ kill_youngest_biggest_subagents "$excess" "$dry_run" >/dev/null
433
+ return 0
434
+ fi
435
+
436
+ # --- SOFT: write the throttle ceiling (no kills) ----------------------------
437
+ if [ "$system_pressure" -ge "$SYSTEM_SOFT_PERCENT" ] || [ "$claude_pct" -ge "$CLAUDE_SOFT_PERCENT" ]; then
438
+ log_event "warn" "soft_tier" "sys=${system_pressure}% claude=${claude_pct}% (RSS ${claude_rss_mb}MB, procs ${claude_count})"
439
+ echo "SOFT: throttling (sys ${system_pressure}%, claude ${claude_pct}%) — no kills"
440
+ if [ "$dry_run" = "false" ]; then
441
+ # Ceiling = liveCount - 2 (let two sessions drain naturally).
442
+ local ceiling=$(( claude_count - 2 ))
443
+ [ "$ceiling" -lt 1 ] && ceiling=1
444
+ write_throttle "$ceiling" "soft-tier"
445
+ else
446
+ echo " [DRY RUN] Would write throttle ceiling=$(( claude_count - 2 ))"
447
+ fi
448
+ return 0
449
+ fi
450
+
451
+ # --- All clear: let any stale throttle expire on its own (governor checks TTL)
452
+ local run_count_file="$MAESTRO_DIR/state/watchdog-run-count"
453
+ local run_count=0
454
+ [ -f "$run_count_file" ] && run_count=$(cat "$run_count_file")
455
+ run_count=$(( (run_count + 1) % 10 ))
456
+ echo "$run_count" > "$run_count_file"
457
+
458
+ if [ "$run_count" -eq 0 ]; then
459
+ log_event "info" "healthy" "claude_procs=${claude_count} rss=${claude_rss_mb}MB (${claude_pct}%) pressure=${system_pressure}%"
460
+ fi
461
+ }
462
+
463
+ # --- Write heartbeat ---------------------------------------------------------
464
+
465
+ write_heartbeat() {
466
+ local heartbeat_file="$MAESTRO_DIR/state/heartbeat"
467
+ date -u +"%Y-%m-%dT%H:%M:%SZ" > "$heartbeat_file"
468
+ }
469
+
470
+ # --- Entry point -------------------------------------------------------------
471
+
472
+ write_heartbeat
473
+ run_watchdog "${1:-run}"