@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.
- package/.claude/commands/init-agent.md +104 -0
- package/.claude/commands/init-maestro.md +1187 -0
- package/.claude/settings.json +161 -0
- package/.env.example +216 -0
- package/README.md +632 -0
- package/agents/browser-operator/agent.md +52 -0
- package/agents/calendar-ops/agent.md +50 -0
- package/agents/communications/agent.md +96 -0
- package/agents/decision-log/agent.md +65 -0
- package/agents/desktop-operator/agent.md +59 -0
- package/agents/gmail-operator/agent.md +62 -0
- package/agents/inbound-dispatcher/agent.md +66 -0
- package/agents/inbox-processor/agent.md +39 -0
- package/agents/pmo-execution/agent.md +60 -0
- package/agents/session-spawner/agent.md +64 -0
- package/agents/slack-operator/agent.md +60 -0
- package/agents/whatsapp-operator/agent.md +60 -0
- package/agents/workflow-automation/agent.md +61 -0
- package/archetypes/altitudes/c-suite.yaml +58 -0
- package/archetypes/altitudes/founder.yaml +68 -0
- package/archetypes/altitudes/senior-manager.yaml +63 -0
- package/archetypes/altitudes/svp.yaml +60 -0
- package/archetypes/altitudes/vp.yaml +50 -0
- package/archetypes/archetype.schema.json +77 -0
- package/archetypes/base.yaml +47 -0
- package/archetypes/capabilities/commercial-leader.yaml +159 -0
- package/archetypes/capabilities/compliance-officer.yaml +159 -0
- package/archetypes/capabilities/executive-operator.yaml +169 -0
- package/archetypes/capabilities/finance-leader.yaml +162 -0
- package/archetypes/capabilities/operations-leader.yaml +154 -0
- package/archetypes/capabilities/product-leader.yaml +148 -0
- package/archetypes/capabilities/technical-leader.yaml +146 -0
- package/archetypes/functions/commercial-leader.yaml +62 -0
- package/archetypes/functions/compliance-officer.yaml +64 -0
- package/archetypes/functions/executive-operator.yaml +70 -0
- package/archetypes/functions/finance-leader.yaml +70 -0
- package/archetypes/functions/operations-leader.yaml +62 -0
- package/archetypes/functions/product-leader.yaml +61 -0
- package/archetypes/functions/technical-leader.yaml +57 -0
- package/bin/cohort-mcp.mjs +81 -0
- package/bin/maestro.mjs +3516 -0
- package/bin/maestro.test.mjs +1015 -0
- package/desktop-control/README.md +56 -0
- package/desktop-control/app-profiles/gmail.yaml +120 -0
- package/desktop-control/app-profiles/slack.yaml +315 -0
- package/desktop-control/app-profiles/whatsapp.yaml +107 -0
- package/docs/architecture/agent-topology.md +2239 -0
- package/docs/architecture/archetype-agent-factory.md +110 -0
- package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
- package/docs/architecture/continuous-monitoring.md +221 -0
- package/docs/architecture/mcp-capability-map.md +585 -0
- package/docs/architecture/system-architecture.md +1272 -0
- package/docs/company-context/README.md +40 -0
- package/docs/guides/agent-persona-setup.md +600 -0
- package/docs/guides/agents-observe-setup.md +64 -0
- package/docs/guides/billing-console-keys.md +88 -0
- package/docs/guides/ccxray-diagnostics.md +65 -0
- package/docs/guides/channel-bus.md +127 -0
- package/docs/guides/claude-mem-setup.md +79 -0
- package/docs/guides/claude-pace-setup.md +56 -0
- package/docs/guides/claudraband-sessions.md +98 -0
- package/docs/guides/clawteam-swarm.md +116 -0
- package/docs/guides/code-review-graph-setup.md +86 -0
- package/docs/guides/email-setup.md +431 -0
- package/docs/guides/mac-mini.md +119 -0
- package/docs/guides/media-generation-setup.md +349 -0
- package/docs/guides/model-routing.md +162 -0
- package/docs/guides/observability-otel.md +265 -0
- package/docs/guides/org-onboarding.md +132 -0
- package/docs/guides/outbound-governance-setup.md +437 -0
- package/docs/guides/pdf-generation-setup.md +315 -0
- package/docs/guides/poller-daemon-setup.md +563 -0
- package/docs/guides/rag-context-setup.md +459 -0
- package/docs/guides/self-optimization-pattern.md +82 -0
- package/docs/guides/setup-wizard.md +178 -0
- package/docs/guides/slack-setup.md +350 -0
- package/docs/guides/telegram-setup.md +227 -0
- package/docs/guides/twilio-subaccounts-setup.md +223 -0
- package/docs/guides/verification.md +128 -0
- package/docs/guides/voice-mode.md +188 -0
- package/docs/guides/voice-sms-setup.md +698 -0
- package/docs/guides/webhook-relay-setup.md +349 -0
- package/docs/guides/whatsapp-setup.md +288 -0
- package/docs/prompts/board-pack-cover-template.md +36 -0
- package/docs/prompts/decision-recommendation-template.md +88 -0
- package/docs/prompts/followup-message-template.md +141 -0
- package/docs/prompts/investor-letter-template.md +52 -0
- package/docs/prompts/morning-brief-template.md +82 -0
- package/docs/prompts/presentation-template.md +58 -0
- package/docs/prompts/weekly-strategic-memo-template.md +104 -0
- package/docs/research/hallucinated-tool-output-investigation.md +151 -0
- package/docs/runbooks/backup-restore.md +205 -0
- package/docs/runbooks/cohort-cutover.md +129 -0
- package/docs/runbooks/fleet-operations.md +200 -0
- package/docs/runbooks/incident-response.md +226 -0
- package/docs/runbooks/mac-mini-bootstrap.md +431 -0
- package/docs/runbooks/perpetual-operations.md +509 -0
- package/docs/runbooks/recovery-and-failover.md +260 -0
- package/framework-features.json +267 -0
- package/ingest/README.md +87 -0
- package/lib/action-executor.js +689 -0
- package/lib/action-executor.test.mjs +871 -0
- package/lib/agent-root.mjs +37 -0
- package/lib/archetype.mjs +236 -0
- package/lib/archetype.test.mjs +132 -0
- package/lib/autonomy.mjs +114 -0
- package/lib/autonomy.test.mjs +66 -0
- package/lib/backlog.mjs +358 -0
- package/lib/backlog.test.mjs +266 -0
- package/lib/budget-guard.mjs +279 -0
- package/lib/budget-guard.test.mjs +291 -0
- package/lib/cadence-bus-schedule.test.mjs +194 -0
- package/lib/cadence-bus.mjs +1120 -0
- package/lib/cadence-bus.test.mjs +720 -0
- package/lib/cadences.mjs +205 -0
- package/lib/cadences.test.mjs +125 -0
- package/lib/capability.mjs +154 -0
- package/lib/capability.test.mjs +78 -0
- package/lib/channels/base-adapter.mjs +719 -0
- package/lib/channels/base-adapter.test.mjs +590 -0
- package/lib/channels/channel.mjs +128 -0
- package/lib/channels/channels.test.mjs +371 -0
- package/lib/channels/contract.mjs +215 -0
- package/lib/channels/contract.test.mjs +137 -0
- package/lib/channels/conversation-resolver.mjs +95 -0
- package/lib/channels/gmail/adapter.mjs +87 -0
- package/lib/channels/inbox-item.mjs +255 -0
- package/lib/channels/inbox-item.test.mjs +335 -0
- package/lib/channels/index.mjs +94 -0
- package/lib/channels/orgmail/adapter.mjs +353 -0
- package/lib/channels/orgmail/adapter.test.mjs +311 -0
- package/lib/channels/pairing.mjs +363 -0
- package/lib/channels/pairing.test.mjs +270 -0
- package/lib/channels/registry.mjs +164 -0
- package/lib/channels/slack/adapter.mjs +317 -0
- package/lib/channels/slack-adapter.test.mjs +212 -0
- package/lib/channels/sms/adapter.mjs +43 -0
- package/lib/channels/telegram/adapter.mjs +432 -0
- package/lib/channels/telegram-adapter.test.mjs +306 -0
- package/lib/channels/voice/adapter.mjs +301 -0
- package/lib/channels/voice/adapter.test.mjs +278 -0
- package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
- package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
- package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
- package/lib/charter.mjs +256 -0
- package/lib/charter.test.mjs +89 -0
- package/lib/claude-bin.mjs +134 -0
- package/lib/claude-bin.test.mjs +75 -0
- package/lib/collective/capture.mjs +185 -0
- package/lib/collective/capture.test.mjs +121 -0
- package/lib/collective/cards.mjs +201 -0
- package/lib/collective/cards.test.mjs +114 -0
- package/lib/collective/config.mjs +186 -0
- package/lib/collective/config.test.mjs +123 -0
- package/lib/collective/global-config.mjs +113 -0
- package/lib/collective/global-config.test.mjs +75 -0
- package/lib/collective/presence.mjs +201 -0
- package/lib/collective/presence.test.mjs +95 -0
- package/lib/collective/recall.mjs +215 -0
- package/lib/collective/recall.test.mjs +116 -0
- package/lib/comms/send-gate.mjs +554 -0
- package/lib/comms/send-gate.test.mjs +577 -0
- package/lib/comms.mjs +67 -0
- package/lib/comms.test.mjs +41 -0
- package/lib/diagnostics/alerts.mjs +424 -0
- package/lib/diagnostics/alerts.test.mjs +318 -0
- package/lib/diagnostics/backup-freshness.mjs +188 -0
- package/lib/diagnostics/backup-freshness.test.mjs +185 -0
- package/lib/diagnostics/counters.mjs +269 -0
- package/lib/diagnostics/counters.test.mjs +206 -0
- package/lib/diagnostics/events.mjs +188 -0
- package/lib/diagnostics/events.test.mjs +290 -0
- package/lib/diagnostics/otel.mjs +237 -0
- package/lib/diagnostics/otel.test.mjs +196 -0
- package/lib/diagnostics/trace.mjs +216 -0
- package/lib/diagnostics/trace.test.mjs +251 -0
- package/lib/env-compat.mjs +74 -0
- package/lib/env-compat.test.mjs +104 -0
- package/lib/feature-init.mjs +331 -0
- package/lib/fs-atomic.mjs +112 -0
- package/lib/fs-atomic.test.mjs +72 -0
- package/lib/fs-ownership.mjs +111 -0
- package/lib/fs-ownership.test.mjs +158 -0
- package/lib/hooks/bus.mjs +347 -0
- package/lib/hooks/bus.test.mjs +387 -0
- package/lib/index.js +16 -0
- package/lib/learning/config.mjs +106 -0
- package/lib/learning/config.test.mjs +75 -0
- package/lib/learning/counters.mjs +156 -0
- package/lib/learning/counters.test.mjs +69 -0
- package/lib/learning/curator-consolidate.test.mjs +238 -0
- package/lib/learning/curator.mjs +453 -0
- package/lib/learning/curator.test.mjs +106 -0
- package/lib/learning/log.mjs +40 -0
- package/lib/learning/reflect.mjs +534 -0
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.mjs +352 -0
- package/lib/learning/session-index.test.mjs +125 -0
- package/lib/learning/skill-writer.mjs +474 -0
- package/lib/learning/skill-writer.test.mjs +210 -0
- package/lib/mcp/server.mjs +328 -0
- package/lib/mcp/server.test.mjs +400 -0
- package/lib/model-router/auth-profiles.mjs +758 -0
- package/lib/model-router/auth-profiles.test.mjs +580 -0
- package/lib/model-router/catalog/anthropic.yaml +153 -0
- package/lib/model-router/catalog/deepseek.yaml +86 -0
- package/lib/model-router/catalog/moonshot.yaml +81 -0
- package/lib/model-router/catalog/qwen.yaml +114 -0
- package/lib/model-router/catalog.mjs +925 -0
- package/lib/model-router/catalog.test.mjs +385 -0
- package/lib/model-router/economics.mjs +564 -0
- package/lib/model-router/economics.test.mjs +344 -0
- package/lib/model-router/failover.mjs +298 -0
- package/lib/model-router/failover.test.mjs +439 -0
- package/lib/model-router/health.mjs +453 -0
- package/lib/model-router/health.test.mjs +338 -0
- package/lib/model-router/integration-coverage.test.mjs +829 -0
- package/lib/model-router/integration.test.mjs +564 -0
- package/lib/model-router/ledger.mjs +402 -0
- package/lib/model-router/ledger.test.mjs +382 -0
- package/lib/model-router/llm-task.mjs +515 -0
- package/lib/model-router/llm-task.test.mjs +392 -0
- package/lib/model-router/org-credentials.mjs +260 -0
- package/lib/model-router/org-credentials.test.mjs +265 -0
- package/lib/model-router/pricing-refresh.mjs +463 -0
- package/lib/model-router/pricing-refresh.test.mjs +286 -0
- package/lib/model-router/reconcile.mjs +429 -0
- package/lib/model-router/reconcile.test.mjs +316 -0
- package/lib/model-router/repair.mjs +471 -0
- package/lib/model-router/repair.test.mjs +180 -0
- package/lib/model-router/resolve.mjs +1206 -0
- package/lib/model-router/spawn.mjs +497 -0
- package/lib/model-router/spawn.test.mjs +425 -0
- package/lib/model-router/taxonomy.mjs +893 -0
- package/lib/model-router/taxonomy.test.mjs +410 -0
- package/lib/model-router.mjs +677 -0
- package/lib/model-router.test.mjs +907 -0
- package/lib/org/activity.mjs +211 -0
- package/lib/org/activity.test.mjs +134 -0
- package/lib/org/approvals.mjs +448 -0
- package/lib/org/approvals.test.mjs +216 -0
- package/lib/org/awareness.mjs +222 -0
- package/lib/org/awareness.test.mjs +159 -0
- package/lib/org/board.mjs +229 -0
- package/lib/org/board.test.mjs +177 -0
- package/lib/org/bootstrap-context.mjs +169 -0
- package/lib/org/bootstrap-context.test.mjs +153 -0
- package/lib/org/client.mjs +1628 -0
- package/lib/org/client.test.mjs +1107 -0
- package/lib/org/cohort-client.mjs +67 -0
- package/lib/org/cohort-client.test.mjs +126 -0
- package/lib/org/cost-sync.mjs +227 -0
- package/lib/org/cost-sync.test.mjs +153 -0
- package/lib/org/doctor.mjs +212 -0
- package/lib/org/doctor.test.mjs +212 -0
- package/lib/org/handoff.mjs +293 -0
- package/lib/org/handoff.test.mjs +269 -0
- package/lib/org/integration-tools.mjs +182 -0
- package/lib/org/integration-tools.test.mjs +160 -0
- package/lib/org/keys.mjs +131 -0
- package/lib/org/keys.test.mjs +92 -0
- package/lib/org/knowledge.mjs +463 -0
- package/lib/org/knowledge.test.mjs +319 -0
- package/lib/org/leases.mjs +335 -0
- package/lib/org/leases.test.mjs +235 -0
- package/lib/org/mesh-integration.test.mjs +127 -0
- package/lib/org/mesh.mjs +459 -0
- package/lib/org/mesh.test.mjs +345 -0
- package/lib/org/messaging.mjs +503 -0
- package/lib/org/messaging.test.mjs +238 -0
- package/lib/org/policy.mjs +345 -0
- package/lib/org/policy.test.mjs +237 -0
- package/lib/org/protocol.checksum +1 -0
- package/lib/org/protocol.checksum.test.mjs +90 -0
- package/lib/org/protocol.mjs +967 -0
- package/lib/org/protocol.test.mjs +264 -0
- package/lib/org/registry.mjs +194 -0
- package/lib/org/registry.test.mjs +100 -0
- package/lib/org/tool-surface-integration.test.mjs +120 -0
- package/lib/org/tool-surface.mjs +2535 -0
- package/lib/org/tool-surface.test.mjs +589 -0
- package/lib/org/ui-parity.mjs +3236 -0
- package/lib/org/ui-parity.test.mjs +348 -0
- package/lib/org/verify.mjs +176 -0
- package/lib/org/verify.test.mjs +194 -0
- package/lib/rag/embed.mjs +188 -0
- package/lib/rag/indexer.mjs +425 -0
- package/lib/rag/rag.test.mjs +505 -0
- package/lib/rag/search.mjs +475 -0
- package/lib/rate-guard.mjs +246 -0
- package/lib/rate-guard.test.mjs +201 -0
- package/lib/render.mjs +112 -0
- package/lib/render.test.mjs +68 -0
- package/lib/resource-governor.mjs +297 -0
- package/lib/resource-governor.test.mjs +262 -0
- package/lib/scheduling/dynamic-jobs.mjs +675 -0
- package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
- package/lib/scheduling/jitter.mjs +0 -0
- package/lib/scheduling/jitter.test.mjs +140 -0
- package/lib/secrets/broker.mjs +315 -0
- package/lib/secrets/broker.test.mjs +280 -0
- package/lib/secrets/providers.mjs +461 -0
- package/lib/secrets/providers.test.mjs +274 -0
- package/lib/security/audit-engine.mjs +684 -0
- package/lib/security/audit-engine.test.mjs +389 -0
- package/lib/security/coerce-args.mjs +552 -0
- package/lib/security/coerce-args.test.mjs +281 -0
- package/lib/security/dangerous-tools.mjs +97 -0
- package/lib/security/dangerous-tools.test.mjs +68 -0
- package/lib/security/external-content.mjs +145 -0
- package/lib/security/external-content.test.mjs +67 -0
- package/lib/security/redact.mjs +592 -0
- package/lib/security/redact.test.mjs +441 -0
- package/lib/security/secret-equal.mjs +73 -0
- package/lib/security/secret-equal.test.mjs +55 -0
- package/lib/session-permissions.mjs +101 -0
- package/lib/session-permissions.test.mjs +100 -0
- package/lib/setup/claude-probe.mjs +74 -0
- package/lib/setup/completeness.mjs +175 -0
- package/lib/setup/completeness.test.mjs +110 -0
- package/lib/setup/context-pack.mjs +173 -0
- package/lib/setup/context-pack.test.mjs +89 -0
- package/lib/setup/enrich.mjs +277 -0
- package/lib/setup/enrich.test.mjs +115 -0
- package/lib/setup/enroll-from-cohort.mjs +441 -0
- package/lib/setup/enroll-from-cohort.test.mjs +233 -0
- package/lib/setup/integration.test.mjs +162 -0
- package/lib/setup/io.mjs +360 -0
- package/lib/setup/io.test.mjs +77 -0
- package/lib/setup/run-generator.mjs +81 -0
- package/lib/setup/runner.mjs +244 -0
- package/lib/setup/runner.test.mjs +132 -0
- package/lib/setup/sections/comms.mjs +173 -0
- package/lib/setup/sections/company.mjs +120 -0
- package/lib/setup/sections/enrich.mjs +138 -0
- package/lib/setup/sections/identity.mjs +182 -0
- package/lib/setup/sections/identity.test.mjs +140 -0
- package/lib/setup/sections/learning.mjs +153 -0
- package/lib/setup/sections/learning.test.mjs +81 -0
- package/lib/setup/sections/messaging.mjs +219 -0
- package/lib/setup/sections/messaging.test.mjs +127 -0
- package/lib/setup/sections/model.mjs +102 -0
- package/lib/setup/sections/operating-model.mjs +78 -0
- package/lib/setup/sections/org.mjs +475 -0
- package/lib/setup/sections/org.test.mjs +313 -0
- package/lib/setup/sections/orgmail.mjs +173 -0
- package/lib/setup/sections/orgmail.test.mjs +118 -0
- package/lib/setup/sections/recovery.mjs +159 -0
- package/lib/setup/sections/recovery.test.mjs +98 -0
- package/lib/setup/sections/tools.mjs +132 -0
- package/lib/setup/sections/verify.mjs +97 -0
- package/lib/setup/sot.mjs +205 -0
- package/lib/setup/sot.test.mjs +81 -0
- package/lib/setup/state.mjs +151 -0
- package/lib/setup/state.test.mjs +92 -0
- package/lib/singleton.js +229 -0
- package/lib/singleton.test.mjs +135 -0
- package/lib/telemetry/alerts.mjs +216 -0
- package/lib/telemetry/alerts.test.mjs +109 -0
- package/lib/telemetry/collect.mjs +512 -0
- package/lib/telemetry/collect.test.mjs +202 -0
- package/lib/tool-definitions-integration.test.mjs +83 -0
- package/lib/tool-definitions.js +738 -0
- package/lib/tool-definitions.test.mjs +437 -0
- package/lib/util/fetch-timeout.mjs +136 -0
- package/lib/util/fetch-timeout.test.mjs +202 -0
- package/lib/util/reconnect.mjs +343 -0
- package/lib/util/reconnect.test.mjs +369 -0
- package/lib/util/unhandled.mjs +205 -0
- package/lib/util/unhandled.test.mjs +216 -0
- package/lib/voice/context-loader.mjs +466 -0
- package/lib/voice/index.mjs +100 -0
- package/lib/voice/openai-realtime.mjs +510 -0
- package/lib/voice/outbound.mjs +542 -0
- package/lib/voice/outbound.test.mjs +69 -0
- package/lib/voice/post-call-brief.mjs +428 -0
- package/lib/voice/provider.mjs +52 -0
- package/lib/voice/session-rotation.mjs +257 -0
- package/lib/voice/stt.mjs +161 -0
- package/lib/voice/stt.test.mjs +226 -0
- package/lib/voice/tool-bridge.mjs +370 -0
- package/lib/voice/tts.mjs +104 -0
- package/lib/voice/twilio-sip-bridge.mjs +288 -0
- package/lib/voice/voice.test.mjs +990 -0
- package/mcp/README.md +80 -0
- package/package.json +151 -0
- package/plugins/maestro-skills/plugin.json +139 -0
- package/plugins/maestro-skills/skills/agents-observe.md +110 -0
- package/plugins/maestro-skills/skills/board-deck.md +68 -0
- package/plugins/maestro-skills/skills/books-close.md +77 -0
- package/plugins/maestro-skills/skills/brand-steward.md +121 -0
- package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
- package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
- package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
- package/plugins/maestro-skills/skills/claude-pace.md +61 -0
- package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
- package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
- package/plugins/maestro-skills/skills/decision-brief.md +89 -0
- package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
- package/plugins/maestro-skills/skills/draft-comms.md +84 -0
- package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
- package/plugins/maestro-skills/skills/files-find.md +65 -0
- package/plugins/maestro-skills/skills/generative-ui.md +228 -0
- package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
- package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
- package/plugins/maestro-skills/skills/mail-triage.md +86 -0
- package/plugins/maestro-skills/skills/morning-brief.md +54 -0
- package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
- package/plugins/maestro-skills/skills/org-board.md +133 -0
- package/plugins/maestro-skills/skills/org-credential.md +68 -0
- package/plugins/maestro-skills/skills/org-recall.md +81 -0
- package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
- package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
- package/plugins/maestro-skills/skills/router-why.md +78 -0
- package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
- package/plugins/maestro-skills/skills/session-search.md +71 -0
- package/plugins/maestro-skills/skills/set-reminder.md +93 -0
- package/plugins/maestro-skills/skills/slack-followup.md +64 -0
- package/plugins/maestro-skills/skills/team-activity.md +86 -0
- package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
- package/policies/action-classification.yaml +114 -0
- package/policies/ai-disclosure.yaml +294 -0
- package/policies/communication-style.md +139 -0
- package/policies/information-barriers.yaml +118 -0
- package/policies/prompt-injection-defence.yaml +138 -0
- package/public/assets/icon-dark.png +0 -0
- package/public/assets/icon-dark.svg +9 -0
- package/public/assets/icon-light.svg +9 -0
- package/public/assets/logo-dark.svg +15 -0
- package/public/assets/logo-light.svg +15 -0
- package/scaffold/.mcp.json +7 -0
- package/scaffold/CLAUDE.md +368 -0
- package/scaffold/config/agent.json +55 -0
- package/scaffold/config/agent.ts +76 -0
- package/scaffold/config/agent.ts.example +89 -0
- package/scaffold/config/alerts.yaml +23 -0
- package/scaffold/config/allowlist.yaml.example +25 -0
- package/scaffold/config/caller-id-map.yaml +46 -0
- package/scaffold/config/collective.yaml +49 -0
- package/scaffold/config/company.json +20 -0
- package/scaffold/config/known-agents.json +6 -0
- package/scaffold/config/learning.yaml +55 -0
- package/scaffold/config/model-routing.yaml.example +104 -0
- package/scaffold/config/org.yaml +25 -0
- package/scaffold/config/orgmail.yaml.example +19 -0
- package/scaffold/config/recovery.yaml +72 -0
- package/scaffold/config/secrets.yaml +27 -0
- package/scaffold/config/slack.yaml.example +35 -0
- package/scaffold/config/telegram.yaml.example +38 -0
- package/scaffold/config/voice.yaml.example +89 -0
- package/scaffold/config/whatsapp.yaml.example +39 -0
- package/schedules/README.md +49 -0
- package/schedules/triggers/backlog-executor.md +102 -0
- package/schedules/triggers/brand-steward.md +72 -0
- package/schedules/triggers/daily-evening-wrap.md +159 -0
- package/schedules/triggers/daily-midday-sweep.md +58 -0
- package/schedules/triggers/daily-morning-brief.md +55 -0
- package/schedules/triggers/directory-hygiene.md +81 -0
- package/schedules/triggers/dynamic-jobs.md +40 -0
- package/schedules/triggers/inbox-processor.md +115 -0
- package/schedules/triggers/meeting-action-capture.md +60 -0
- package/schedules/triggers/meeting-prep.md +69 -0
- package/schedules/triggers/messaging-inbound.md +50 -0
- package/schedules/triggers/org-pulse.md +24 -0
- package/schedules/triggers/quarterly-self-assessment.md +54 -0
- package/schedules/triggers/weekly-engineering-health.md +37 -0
- package/schedules/triggers/weekly-execution.md +65 -0
- package/schedules/triggers/weekly-hiring.md +53 -0
- package/schedules/triggers/weekly-priorities.md +38 -0
- package/schedules/triggers/weekly-strategic-memo.md +124 -0
- package/scripts/archive-email.sh +55 -0
- package/scripts/cadence/cadence-status.mjs +36 -0
- package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
- package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
- package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
- package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
- package/scripts/ci/check-docs-accuracy.mjs +493 -0
- package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
- package/scripts/ci/check-exports-exist.mjs +140 -0
- package/scripts/ci/check-files-exist.mjs +107 -0
- package/scripts/ci/check-no-build-artifacts.mjs +111 -0
- package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
- package/scripts/ci/check-no-confidential.mjs +198 -0
- package/scripts/ci/check-no-conflict-markers.mjs +169 -0
- package/scripts/ci/check-no-residual-identity.mjs +163 -0
- package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
- package/scripts/ci/check-tarball-fidelity.mjs +205 -0
- package/scripts/ci/check-unresolved-tokens.mjs +83 -0
- package/scripts/ci/check.mjs +109 -0
- package/scripts/ci/check.test.mjs +194 -0
- package/scripts/ci/run-coverage.mjs +82 -0
- package/scripts/ci/run-tests.mjs +71 -0
- package/scripts/cloud-relay/README.md +59 -0
- package/scripts/cloud-relay/index.mjs +233 -0
- package/scripts/cloud-relay/package.json +15 -0
- package/scripts/cloud-relay/railway.json +13 -0
- package/scripts/cloud-relay/voice/README.md +94 -0
- package/scripts/cloud-relay/voice/package-lock.json +39 -0
- package/scripts/cloud-relay/voice/package.json +16 -0
- package/scripts/cloud-relay/voice/railway.json +13 -0
- package/scripts/cloud-relay/voice/server.mjs +532 -0
- package/scripts/collective/hook-runner.mjs +211 -0
- package/scripts/collective/hook-runner.test.mjs +90 -0
- package/scripts/collective/org-pulse.mjs +72 -0
- package/scripts/collective/org-sync.mjs +61 -0
- package/scripts/collective/recall.mjs +45 -0
- package/scripts/collective/who.mjs +30 -0
- package/scripts/comms-monitor.sh +288 -0
- package/scripts/configure-whatsapp-sandbox.sh +201 -0
- package/scripts/continuous-monitor.sh +91 -0
- package/scripts/cost/fleet-digest.mjs +407 -0
- package/scripts/cost/fleet-digest.test.mjs +207 -0
- package/scripts/cost/track-claude-usage.mjs +169 -0
- package/scripts/daemon/agent-daemon.mjs +989 -0
- package/scripts/daemon/agent-daemon.test.mjs +525 -0
- package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
- package/scripts/daemon/cadence-consumer.mjs +1080 -0
- package/scripts/daemon/cadence-consumer.test.mjs +770 -0
- package/scripts/daemon/cadence-handlers.mjs +1121 -0
- package/scripts/daemon/cadence-handlers.test.mjs +617 -0
- package/scripts/daemon/classifier.mjs +704 -0
- package/scripts/daemon/classifier.test.mjs +238 -0
- package/scripts/daemon/classify-kind.mjs +54 -0
- package/scripts/daemon/classify-kind.test.mjs +40 -0
- package/scripts/daemon/context-compiler.mjs +605 -0
- package/scripts/daemon/context-compiler.test.mjs +300 -0
- package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
- package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
- package/scripts/daemon/dispatcher.mjs +1516 -0
- package/scripts/daemon/health.mjs +72 -0
- package/scripts/daemon/inbox-deferral.mjs +210 -0
- package/scripts/daemon/inbox-deferral.test.mjs +242 -0
- package/scripts/daemon/integration.test.mjs +149 -0
- package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
- package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
- package/scripts/daemon/launchd-wrapper.sh +91 -0
- package/scripts/daemon/lib/session-router.mjs +274 -0
- package/scripts/daemon/lib/session-router.test.mjs +295 -0
- package/scripts/daemon/maestro-daemon.mjs +275 -0
- package/scripts/daemon/prompt-builder.mjs +685 -0
- package/scripts/daemon/prompt-builder.test.mjs +213 -0
- package/scripts/daemon/responder.mjs +854 -0
- package/scripts/daemon/session-lock.mjs +721 -0
- package/scripts/daemon/session-lock.test.mjs +252 -0
- package/scripts/daemon/session-outcomes.mjs +640 -0
- package/scripts/daemon/session-outcomes.test.mjs +533 -0
- package/scripts/daemon/typing-registry.mjs +90 -0
- package/scripts/daemon/typing-registry.test.mjs +77 -0
- package/scripts/daemon/voice-webhook-server.mjs +804 -0
- package/scripts/decisions/capture-decision.mjs +116 -0
- package/scripts/disclosure_assessment.py +873 -0
- package/scripts/disclosure_boundaries.py +562 -0
- package/scripts/email-signature-principal.html +52 -0
- package/scripts/email-signature.html +60 -0
- package/scripts/email_quote_thread.py +167 -0
- package/scripts/email_thread_dedup.py +362 -0
- package/scripts/emergency-stop.sh +81 -0
- package/scripts/healthcheck.sh +116 -0
- package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
- package/scripts/hooks/block-mcp-slack-send.sh +7 -0
- package/scripts/hooks/post-action-log.sh +126 -0
- package/scripts/hooks/pre-send-audit.sh +174 -0
- package/scripts/hooks/pre-send-audit.test.mjs +215 -0
- package/scripts/hooks/session-end-log.sh +27 -0
- package/scripts/hooks/session-start-banner.sh +115 -0
- package/scripts/huddle/audio-bridge.mjs +664 -0
- package/scripts/huddle/boot-slack-cdp.sh +102 -0
- package/scripts/huddle/huddle-controller.mjs +942 -0
- package/scripts/huddle/huddle-server.mjs +1229 -0
- package/scripts/huddle/launch-slack.sh +232 -0
- package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
- package/scripts/huddle/package-lock.json +62 -0
- package/scripts/huddle/package.json +22 -0
- package/scripts/huddle/setup-audio.sh +239 -0
- package/scripts/huddle/start-call.mjs +318 -0
- package/scripts/huddle/test-pipeline.mjs +263 -0
- package/scripts/learning/consolidate-skills.mjs +72 -0
- package/scripts/learning/session-search.mjs +125 -0
- package/scripts/llm_email_dedup.py +442 -0
- package/scripts/local-triggers/generate-plists.sh +432 -0
- package/scripts/local-triggers/generate-plists.test.mjs +413 -0
- package/scripts/local-triggers/install-all.sh +49 -0
- package/scripts/local-triggers/plists/.gitkeep +0 -0
- package/scripts/local-triggers/run-trigger.sh +63 -0
- package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
- package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
- package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
- package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
- package/scripts/maintenance/backup-to-cloud.sh +124 -0
- package/scripts/maintenance/health-check.sh +377 -0
- package/scripts/media-generation/README.md +105 -0
- package/scripts/media-generation/gemini-image-client.mjs +173 -0
- package/scripts/media-generation/generate-assets.mjs +289 -0
- package/scripts/media-generation/veo-video-client.mjs +219 -0
- package/scripts/org/send-orgmail.mjs +227 -0
- package/scripts/outbound-dedup-cleanup.sh +43 -0
- package/scripts/outbound-dedup.sh +477 -0
- package/scripts/outbound_dedup.py +115 -0
- package/scripts/parse-voice-transcript.mjs +481 -0
- package/scripts/pdf-generation/README.md +63 -0
- package/scripts/pdf-generation/build-document.mjs +247 -0
- package/scripts/pdf-generation/templates/board-pack.latex +136 -0
- package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
- package/scripts/pdf-generation/templates/memo.latex +114 -0
- package/scripts/poll-slack-events.sh +35 -0
- package/scripts/poller/calendar-poller.mjs +12 -0
- package/scripts/poller/gmail-poller.mjs +192 -0
- package/scripts/poller/imap-client.mjs +289 -0
- package/scripts/poller/inbox-scan-poller.mjs +156 -0
- package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
- package/scripts/poller/index.mjs +73 -0
- package/scripts/poller/intra-session-check.mjs +285 -0
- package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
- package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
- package/scripts/poller/secondary-gmail-poller.mjs +132 -0
- package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
- package/scripts/poller/slack-poller.mjs +854 -0
- package/scripts/poller/slack-socket-mode.mjs +917 -0
- package/scripts/poller/slack-socket-mode.test.mjs +753 -0
- package/scripts/poller/trigger.mjs +75 -0
- package/scripts/poller/utils.mjs +371 -0
- package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
- package/scripts/poller/voice-poller.mjs +236 -0
- package/scripts/poller-launchd/install.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +40 -0
- package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
- package/scripts/post-interaction-indexer.py +1598 -0
- package/scripts/pre-draft-context.py +994 -0
- package/scripts/pre_draft_lookup.py +258 -0
- package/scripts/rag/build-index.mjs +47 -0
- package/scripts/rag/ingest.mjs +111 -0
- package/scripts/rag/search.mjs +119 -0
- package/scripts/rag-indexer.py +629 -0
- package/scripts/restore-from-backup.sh +248 -0
- package/scripts/restore-from-backup.test.mjs +178 -0
- package/scripts/resume-operations.sh +80 -0
- package/scripts/search-secondary-inbox.py +181 -0
- package/scripts/secondary-inbox-poller.py +437 -0
- package/scripts/self-optimization/compute-metrics.py +398 -0
- package/scripts/send-email-as-principal.py +369 -0
- package/scripts/send-email-threaded.py +392 -0
- package/scripts/send-email-with-attachment.py +377 -0
- package/scripts/send-email.sh +131 -0
- package/scripts/send-sms.sh +175 -0
- package/scripts/send-whatsapp.sh +292 -0
- package/scripts/session-start.sh +106 -0
- package/scripts/setup/boot-claude-session.sh +94 -0
- package/scripts/setup/configure-macos.sh +674 -0
- package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
- package/scripts/setup/configure-voice-tunnel.mjs +182 -0
- package/scripts/setup/generate-agent-env.mjs +92 -0
- package/scripts/setup/generate-agent-package-json.mjs +222 -0
- package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
- package/scripts/setup/generate-autonomy.mjs +60 -0
- package/scripts/setup/generate-backlog.mjs +101 -0
- package/scripts/setup/generate-cadences.mjs +92 -0
- package/scripts/setup/generate-capability.mjs +162 -0
- package/scripts/setup/generate-charter.mjs +76 -0
- package/scripts/setup/generate-comms.mjs +59 -0
- package/scripts/setup/generate-company.mjs +92 -0
- package/scripts/setup/init-agent.sh +547 -0
- package/scripts/setup/init-agent.test.mjs +151 -0
- package/scripts/setup/init-archetype.mjs +74 -0
- package/scripts/setup/init-backup.mjs +54 -0
- package/scripts/setup/init-cadence-bus.mjs +60 -0
- package/scripts/setup/init-channel-bus.mjs +46 -0
- package/scripts/setup/init-cost-tracking.mjs +45 -0
- package/scripts/setup/init-decision-capture.mjs +66 -0
- package/scripts/setup/init-known-agents.mjs +57 -0
- package/scripts/setup/init-learning.mjs +70 -0
- package/scripts/setup/init-memory-executive.mjs +45 -0
- package/scripts/setup/init-model-router.mjs +124 -0
- package/scripts/setup/init-rag.mjs +174 -0
- package/scripts/setup/init-session-router.mjs +38 -0
- package/scripts/setup/init-slack-socket-mode.mjs +260 -0
- package/scripts/setup/init-telegram.mjs +165 -0
- package/scripts/setup/init-voice-realtime.mjs +204 -0
- package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
- package/scripts/setup/install-dev-tools.sh +150 -0
- package/scripts/setup/lib/install-plist.mjs +95 -0
- package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
- package/scripts/setup/render-environment-yaml.mjs +133 -0
- package/scripts/slack-events-ctl.sh +177 -0
- package/scripts/slack-events-server.mjs +1045 -0
- package/scripts/slack-react.mjs +89 -0
- package/scripts/slack-responded.sh +232 -0
- package/scripts/slack-send.sh +287 -0
- package/scripts/slack-typing.mjs +196 -0
- package/scripts/slack-upload-v2.py +95 -0
- package/scripts/sms-handler.mjs +450 -0
- package/scripts/spawn-session.sh +120 -0
- package/scripts/sync-protocol.mjs +217 -0
- package/scripts/system-verify.sh +184 -0
- package/scripts/test-email-thread-dedup.py +239 -0
- package/scripts/test-information-barriers.py +484 -0
- package/scripts/test-llm-email-dedup.py +251 -0
- package/scripts/test-pre-draft-integration.py +203 -0
- package/scripts/test-rag-phase2.sh +442 -0
- package/scripts/test-rag-search.sh +251 -0
- package/scripts/test-voice-parser.mjs +316 -0
- package/scripts/user-context-search.py +659 -0
- package/scripts/validate_outbound.py +1504 -0
- package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
- package/scripts/watchdog/force-reboot.sh +157 -0
- package/scripts/watchdog/memory-watchdog.sh +473 -0
- package/scripts/whatsapp-handler.mjs +538 -0
- package/teams/desktop-operations.yaml +34 -0
- package/teams/executive-office.yaml +27 -0
- package/teams/legal-and-regulatory.yaml +24 -0
- package/teams/platform-and-engineering.yaml +23 -0
- package/teams/strategy-and-growth.yaml +29 -0
- package/workflows/continuous/backlog-executor.yaml +141 -0
- package/workflows/continuous/inbound-monitor.yaml +168 -0
- package/workflows/daily/applicant-triage.yaml +197 -0
- package/workflows/daily/comms-triage.yaml +80 -0
- package/workflows/daily/evening-wrap.yaml +105 -0
- package/workflows/daily/morning-brief.yaml +164 -0
- package/workflows/daily/slack-followup-sweep.yaml +87 -0
- package/workflows/event-driven/README.md +50 -0
- package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
- package/workflows/event-driven/pr-review.yaml +107 -0
- package/workflows/monthly/board-readiness.yaml +76 -0
- package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
- package/workflows/session-protocol.md +171 -0
- package/workflows/weekly/engineering-health.yaml +154 -0
- package/workflows/weekly/hiring-review.yaml +169 -0
- package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
- 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}"
|