@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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sections/recovery.mjs — reboot/outage recovery + resource governance (WS4, order 85).
|
|
3
|
+
*
|
|
4
|
+
* Cross-workstream seam (design §3.2): registered by dropping this file into
|
|
5
|
+
* sections/ (no core edit). It installs the recovery config the resource
|
|
6
|
+
* governor / watchdog / budget guard read, and verifies the self-healing
|
|
7
|
+
* recovery wiring (watchdog script + heartbeat, governor sizing, pmset
|
|
8
|
+
* autorestart). Order 85 (after learning/80, before verify/90) per design §3.2.
|
|
9
|
+
*
|
|
10
|
+
* Cardinal rule surfaced to the operator: the ONLY thing that permanently
|
|
11
|
+
* stops the agent is a HUMAN writing .emergency-stop. Everything here is about
|
|
12
|
+
* degrading gracefully and recovering automatically — never bricking.
|
|
13
|
+
*
|
|
14
|
+
* Every hook is non-blocking under --headless (detect/apply/verify never read
|
|
15
|
+
* the TTY; prompt honours `io` headless resolution). Idempotent.
|
|
16
|
+
*
|
|
17
|
+
* @module lib/setup/sections/recovery
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, statSync } from "node:fs";
|
|
24
|
+
import { execFileSync } from "node:child_process";
|
|
25
|
+
import os from "node:os";
|
|
26
|
+
import * as io from "../io.mjs";
|
|
27
|
+
|
|
28
|
+
const CONFIG_REL = join("config", "recovery.yaml");
|
|
29
|
+
const WATCHDOG_REL = join("scripts", "watchdog", "memory-watchdog.sh");
|
|
30
|
+
|
|
31
|
+
/** Write config/recovery.yaml from the scaffold (or a minimal stub). Idempotent. */
|
|
32
|
+
function ensureConfig(ctx) {
|
|
33
|
+
const dst = join(ctx.agentRoot, CONFIG_REL);
|
|
34
|
+
if (existsSync(dst)) return; // never clobber an operator-customised file
|
|
35
|
+
mkdirSync(join(ctx.agentRoot, "config"), { recursive: true });
|
|
36
|
+
const scaffold = join(ctx.maestroRoot, "scaffold", "config", "recovery.yaml");
|
|
37
|
+
if (existsSync(scaffold)) {
|
|
38
|
+
try { copyFileSync(scaffold, dst); return; } catch { /* fall through to stub */ }
|
|
39
|
+
}
|
|
40
|
+
writeFileSync(dst, [
|
|
41
|
+
"# config/recovery.yaml — Reboot/outage recovery + resource governance (WS4)",
|
|
42
|
+
"# Defaults apply when keys are omitted. The watchdog NEVER writes",
|
|
43
|
+
"# .emergency-stop — that flag is human-only.",
|
|
44
|
+
"governor:",
|
|
45
|
+
" ram_fraction: 0.60",
|
|
46
|
+
"budget:",
|
|
47
|
+
" daily_spend_cap_usd: 25",
|
|
48
|
+
"pmset_autorestart_required: true",
|
|
49
|
+
"",
|
|
50
|
+
].join("\n"));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Is pmset autorestart enabled? Returns true/false/null (unknown / non-mac). */
|
|
54
|
+
function pmsetAutorestart() {
|
|
55
|
+
try {
|
|
56
|
+
const out = execFileSync("pmset", ["-g"], { encoding: "utf-8" });
|
|
57
|
+
const m = out.match(/\bautorestart\s+(\d)/);
|
|
58
|
+
if (!m) return null;
|
|
59
|
+
return m[1] === "1";
|
|
60
|
+
} catch {
|
|
61
|
+
return null; // pmset unavailable (non-mac / CI) — soft signal
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Compute the governor's dynamicMax for this host (sizing sanity in verify). */
|
|
66
|
+
function computeDynamicMax() {
|
|
67
|
+
try {
|
|
68
|
+
const totalmem = os.totalmem();
|
|
69
|
+
const cpus = Math.max(1, os.cpus().length || 1);
|
|
70
|
+
const byRam = Math.floor((totalmem * 0.6) / (750 * 1024 * 1024));
|
|
71
|
+
return Math.min(Math.min(10, cpus), Math.max(1, byRam));
|
|
72
|
+
} catch { return null; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
id: "recovery",
|
|
77
|
+
label: "Recovery & resource governance",
|
|
78
|
+
order: 85,
|
|
79
|
+
|
|
80
|
+
async detect(ctx) {
|
|
81
|
+
const missing = [];
|
|
82
|
+
if (!existsSync(join(ctx.agentRoot, CONFIG_REL))) missing.push("config/recovery.yaml");
|
|
83
|
+
if (!existsSync(join(ctx.agentRoot, WATCHDOG_REL))) missing.push("scripts/watchdog/memory-watchdog.sh");
|
|
84
|
+
if (missing.length === 0) return { status: "complete", summary: "recovery + governance configured" };
|
|
85
|
+
if (missing.length === 2) return { status: "unconfigured", missing };
|
|
86
|
+
return { status: "partial", summary: "recovery partially configured", missing };
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async prompt(ctx) {
|
|
90
|
+
// The only operator choice worth surfacing is the daily spend cap; the rest
|
|
91
|
+
// are sensible self-healing defaults. Headless → uses the pre-supplied
|
|
92
|
+
// answer or leaves the default.
|
|
93
|
+
const cap = await io.text(ctx, {
|
|
94
|
+
message: "Daily Claude spend cap (USD) before the agent goes essential-only (inbox-only)?",
|
|
95
|
+
key: "dailySpendCapUsd",
|
|
96
|
+
default: "25",
|
|
97
|
+
});
|
|
98
|
+
ctx.answers.dailySpendCapUsd = cap;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async apply(ctx) {
|
|
102
|
+
ensureConfig(ctx);
|
|
103
|
+
// If the operator set a cap, fold it into the config (best-effort line edit;
|
|
104
|
+
// the env var DAILY_SPEND_CAP_USD also overrides at runtime).
|
|
105
|
+
const cap = ctx.answers && ctx.answers.dailySpendCapUsd;
|
|
106
|
+
if (cap && /^\d+(\.\d+)?$/.test(String(cap))) {
|
|
107
|
+
try {
|
|
108
|
+
const p = join(ctx.agentRoot, CONFIG_REL);
|
|
109
|
+
let body = readFileSync(p, "utf-8");
|
|
110
|
+
if (/daily_spend_cap_usd:/.test(body)) {
|
|
111
|
+
body = body.replace(/(\n\s*daily_spend_cap_usd:\s*)\S+/, `$1${cap}`);
|
|
112
|
+
} else {
|
|
113
|
+
body += `\nbudget:\n daily_spend_cap_usd: ${cap}\n`;
|
|
114
|
+
}
|
|
115
|
+
writeFileSync(p, body);
|
|
116
|
+
} catch { /* fail-open */ }
|
|
117
|
+
}
|
|
118
|
+
// Ensure the runtime state dirs the governance layer writes to exist so the
|
|
119
|
+
// first watchdog/governor run doesn't race a missing directory.
|
|
120
|
+
for (const d of ["state", "state/sessions/resume-pending", "state/rate-limits", "state/cost-tracking"]) {
|
|
121
|
+
try { mkdirSync(join(ctx.agentRoot, d), { recursive: true }); } catch { /* */ }
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
async verify(ctx) {
|
|
126
|
+
const checks = [];
|
|
127
|
+
|
|
128
|
+
const cfgOk = existsSync(join(ctx.agentRoot, CONFIG_REL));
|
|
129
|
+
checks.push({ name: "config/recovery.yaml present", ok: cfgOk, remedy: cfgOk ? undefined : "run `maestro setup --only recovery`" });
|
|
130
|
+
|
|
131
|
+
const wdPath = join(ctx.agentRoot, WATCHDOG_REL);
|
|
132
|
+
const wdOk = existsSync(wdPath);
|
|
133
|
+
checks.push({ name: "memory-watchdog.sh installed", ok: wdOk, remedy: wdOk ? undefined : "run `maestro upgrade` to install the watchdog" });
|
|
134
|
+
|
|
135
|
+
// The watchdog must NOT write .emergency-stop on OOM (the brick fix). Cheap
|
|
136
|
+
// static guard: the OOM path's run_watchdog body must not echo into the flag.
|
|
137
|
+
if (wdOk) {
|
|
138
|
+
let safe = true;
|
|
139
|
+
try {
|
|
140
|
+
const body = readFileSync(wdPath, "utf-8");
|
|
141
|
+
// A *write* to .emergency-stop would look like `> "...emergency-stop"`.
|
|
142
|
+
safe = !/>\s*"?\$?\{?[A-Z_]*\}?[^"\n]*\.emergency-stop"?/.test(body);
|
|
143
|
+
} catch { /* unreadable — leave safe=true, doctor covers runtime */ }
|
|
144
|
+
checks.push({ name: "watchdog does not write .emergency-stop on OOM (human-only)", ok: safe, remedy: safe ? undefined : "watchdog still writes .emergency-stop — run `maestro upgrade`" });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Governor sizing sanity (informational; always ok).
|
|
148
|
+
const dMax = computeDynamicMax();
|
|
149
|
+
checks.push({ name: `governor dynamicMax=${dMax ?? "?"} for this host`, ok: true });
|
|
150
|
+
|
|
151
|
+
// pmset autorestart — SOFT check (null on non-mac/CI is tolerated).
|
|
152
|
+
const ar = pmsetAutorestart();
|
|
153
|
+
if (ar === true) checks.push({ name: "pmset autorestart=1 (recovers from power loss)", ok: true });
|
|
154
|
+
else if (ar === false) checks.push({ name: "pmset autorestart enabled", ok: false, remedy: "sudo pmset -a autorestart 1" });
|
|
155
|
+
else checks.push({ name: "pmset autorestart (skipped — non-mac/CI)", ok: true });
|
|
156
|
+
|
|
157
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recovery.test.mjs — WS4 setup section (order 85) tests.
|
|
3
|
+
* Run: node --test lib/setup/sections/recovery.test.mjs
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
import { test } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { mkdtempSync, existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join, dirname } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
import recovery from "./recovery.mjs";
|
|
15
|
+
import * as io from "../io.mjs";
|
|
16
|
+
|
|
17
|
+
const MAESTRO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
18
|
+
|
|
19
|
+
function tmpRoot() {
|
|
20
|
+
const root = mkdtempSync(join(tmpdir(), "maestro-recsec-"));
|
|
21
|
+
mkdirSync(join(root, "config"), { recursive: true });
|
|
22
|
+
return root;
|
|
23
|
+
}
|
|
24
|
+
function ctxFor(root, answers = {}) {
|
|
25
|
+
return { agentRoot: root, maestroRoot: MAESTRO_ROOT, flags: { headless: true }, answers, io };
|
|
26
|
+
}
|
|
27
|
+
/** Copy the real watchdog into the test root so verify checks the real body. */
|
|
28
|
+
function installWatchdog(root) {
|
|
29
|
+
mkdirSync(join(root, "scripts/watchdog"), { recursive: true });
|
|
30
|
+
copyFileSync(join(MAESTRO_ROOT, "scripts/watchdog/memory-watchdog.sh"), join(root, "scripts/watchdog/memory-watchdog.sh"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test("section shape: id/order=85/hooks", () => {
|
|
34
|
+
assert.equal(recovery.id, "recovery");
|
|
35
|
+
assert.equal(recovery.order, 85);
|
|
36
|
+
for (const h of ["detect", "prompt", "apply", "verify"]) assert.equal(typeof recovery[h], "function");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("detect: unconfigured before apply", async () => {
|
|
40
|
+
const root = tmpRoot();
|
|
41
|
+
const before = await recovery.detect(ctxFor(root));
|
|
42
|
+
assert.equal(before.status, "unconfigured");
|
|
43
|
+
assert.ok(before.missing.includes("config/recovery.yaml"));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("apply writes config/recovery.yaml from scaffold + seeds state dirs", async () => {
|
|
47
|
+
const root = tmpRoot();
|
|
48
|
+
await recovery.apply(ctxFor(root, {}));
|
|
49
|
+
assert.ok(existsSync(join(root, "config", "recovery.yaml")));
|
|
50
|
+
// Runtime state dirs the governance layer writes to are pre-created.
|
|
51
|
+
assert.ok(existsSync(join(root, "state/sessions/resume-pending")));
|
|
52
|
+
assert.ok(existsSync(join(root, "state/rate-limits")));
|
|
53
|
+
// The scaffold carries the governance knobs.
|
|
54
|
+
const body = readFileSync(join(root, "config", "recovery.yaml"), "utf8");
|
|
55
|
+
assert.match(body, /ram_fraction/);
|
|
56
|
+
assert.match(body, /daily_spend_cap_usd/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("apply folds the operator's daily spend cap into the config", async () => {
|
|
60
|
+
const root = tmpRoot();
|
|
61
|
+
await recovery.apply(ctxFor(root, { dailySpendCapUsd: "40" }));
|
|
62
|
+
const body = readFileSync(join(root, "config", "recovery.yaml"), "utf8");
|
|
63
|
+
assert.match(body, /daily_spend_cap_usd:\s*40/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("apply is idempotent — never clobbers an existing recovery.yaml", async () => {
|
|
67
|
+
const root = tmpRoot();
|
|
68
|
+
writeFileSync(join(root, "config", "recovery.yaml"), "# custom\nbudget:\n daily_spend_cap_usd: 99\n");
|
|
69
|
+
await recovery.apply(ctxFor(root, { dailySpendCapUsd: "40" }));
|
|
70
|
+
const body = readFileSync(join(root, "config", "recovery.yaml"), "utf8");
|
|
71
|
+
// The operator's existing file is preserved (cap line edited in place, custom comment kept).
|
|
72
|
+
assert.match(body, /# custom/);
|
|
73
|
+
} );
|
|
74
|
+
|
|
75
|
+
test("verify passes when config + watchdog present and watchdog is OOM-brick-safe", async () => {
|
|
76
|
+
const root = tmpRoot();
|
|
77
|
+
await recovery.apply(ctxFor(root, {}));
|
|
78
|
+
installWatchdog(root);
|
|
79
|
+
const res = await recovery.verify(ctxFor(root));
|
|
80
|
+
const byName = Object.fromEntries(res.checks.map((c) => [c.name, c.ok]));
|
|
81
|
+
assert.equal(byName["config/recovery.yaml present"], true);
|
|
82
|
+
assert.equal(byName["memory-watchdog.sh installed"], true);
|
|
83
|
+
// The crux: the installed watchdog must NOT write .emergency-stop on OOM.
|
|
84
|
+
assert.equal(byName["watchdog does not write .emergency-stop on OOM (human-only)"], true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("verify flags a watchdog that DOES write .emergency-stop on OOM (regression guard)", async () => {
|
|
88
|
+
const root = tmpRoot();
|
|
89
|
+
await recovery.apply(ctxFor(root, {}));
|
|
90
|
+
mkdirSync(join(root, "scripts/watchdog"), { recursive: true });
|
|
91
|
+
// A legacy-style watchdog that bricks on OOM.
|
|
92
|
+
writeFileSync(join(root, "scripts/watchdog/memory-watchdog.sh"),
|
|
93
|
+
`#!/bin/bash\nMAESTRO_DIR=/x\necho "OOM" > "$MAESTRO_DIR/.emergency-stop"\n`);
|
|
94
|
+
const res = await recovery.verify(ctxFor(root));
|
|
95
|
+
const check = res.checks.find((c) => /does not write .emergency-stop/.test(c.name));
|
|
96
|
+
assert.equal(check.ok, false, "must flag a watchdog that writes the human-only flag");
|
|
97
|
+
assert.ok(check.remedy);
|
|
98
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sections/tools.mjs — select channels + MCP servers to enable. (order 50)
|
|
3
|
+
*
|
|
4
|
+
* Records selections only (per WS1 scope): channel choices land as commented
|
|
5
|
+
* .env key stubs the operator fills in, and MCP selections are recorded in
|
|
6
|
+
* config/mcp-servers.yaml's `enabled` list. The per-channel deep wiring (Socket
|
|
7
|
+
* Mode, Baileys auth, relay) is WS2's comms section.
|
|
8
|
+
*
|
|
9
|
+
* @module lib/setup/sections/tools
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, appendFileSync, mkdirSync } from "node:fs";
|
|
16
|
+
import * as io from "../io.mjs";
|
|
17
|
+
|
|
18
|
+
const CHANNELS = [
|
|
19
|
+
{ value: "slack", label: "Slack", hint: "DMs, channels, threads" },
|
|
20
|
+
{ value: "gmail", label: "Email (Gmail)", hint: "IMAP + send" },
|
|
21
|
+
{ value: "whatsapp", label: "WhatsApp", hint: "Baileys" },
|
|
22
|
+
{ value: "telegram", label: "Telegram", hint: "grammY (WS2)" },
|
|
23
|
+
{ value: "sms", label: "SMS", hint: "Twilio" },
|
|
24
|
+
{ value: "voice", label: "Voice / phone", hint: "Realtime" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/** .env stub keys we drop (commented) per selected channel. */
|
|
28
|
+
const CHANNEL_ENV_STUBS = {
|
|
29
|
+
slack: ["# SLACK_USER_TOKEN=", "# SLACK_BOT_TOKEN=", "# SLACK_APP_LEVEL_TOKEN=xapp-1-…"],
|
|
30
|
+
gmail: ["# GMAIL_APP_PASSWORD="],
|
|
31
|
+
sms: ["# TWILIO_ACCOUNT_SID=", "# TWILIO_AUTH_TOKEN=", "# TWILIO_FROM_NUMBER="],
|
|
32
|
+
whatsapp: ["# WHATSAPP_ENABLED=1"],
|
|
33
|
+
telegram: ["# TELEGRAM_BOT_TOKEN="],
|
|
34
|
+
voice: ["# VOICE_ENABLED=1"],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function readMcpServers(agentRoot) {
|
|
38
|
+
const p = join(agentRoot, "config", "mcp-servers.yaml");
|
|
39
|
+
return existsSync(p) ? readFileSync(p, "utf-8") : "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
id: "tools",
|
|
44
|
+
label: "Channels & MCP servers",
|
|
45
|
+
order: 50,
|
|
46
|
+
|
|
47
|
+
async detect(ctx) {
|
|
48
|
+
const recorded = readSelections(ctx.agentRoot);
|
|
49
|
+
if (!recorded) return { status: "unconfigured", missing: ["no selections recorded"] };
|
|
50
|
+
return { status: "partial", summary: `channels: ${(recorded.channels || []).join(", ") || "none"}` };
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async prompt(ctx) {
|
|
54
|
+
const channels = await io.multiselect(ctx, {
|
|
55
|
+
message: "Which messaging channels will this agent use?",
|
|
56
|
+
key: "channels",
|
|
57
|
+
choices: CHANNELS,
|
|
58
|
+
default: [],
|
|
59
|
+
});
|
|
60
|
+
// Offer MCP servers the role declared (from generate-capability), if present.
|
|
61
|
+
const declared = parseDeclaredMcp(readMcpServers(ctx.agentRoot));
|
|
62
|
+
let mcp = [];
|
|
63
|
+
if (declared.length) {
|
|
64
|
+
mcp = await io.multiselect(ctx, {
|
|
65
|
+
message: "Enable which MCP servers now?",
|
|
66
|
+
key: "mcp",
|
|
67
|
+
choices: declared.map((m) => ({ value: m, label: m })),
|
|
68
|
+
default: [],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
ctx.answers.tools = { channels, mcp };
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
async apply(ctx) {
|
|
75
|
+
const a = (ctx.answers && ctx.answers.tools) || pull(ctx);
|
|
76
|
+
if (!a) return;
|
|
77
|
+
// 1. Record selections sidecar (durable, machine-readable).
|
|
78
|
+
writeSelections(ctx.agentRoot, { channels: a.channels || [], mcp: a.mcp || [], recordedAt: new Date().toISOString() });
|
|
79
|
+
// 2. Drop commented .env stubs for each selected channel (never overwrite a set key).
|
|
80
|
+
const envPath = join(ctx.agentRoot, ".env");
|
|
81
|
+
const env = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
82
|
+
const additions = [];
|
|
83
|
+
for (const ch of a.channels || []) {
|
|
84
|
+
for (const stub of CHANNEL_ENV_STUBS[ch] || []) {
|
|
85
|
+
const realKey = stub.replace(/^#\s*/, "").split("=")[0];
|
|
86
|
+
if (!new RegExp(`^${realKey}=.+`, "m").test(env) && !env.includes(stub)) additions.push(stub);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (additions.length) {
|
|
90
|
+
const prefix = env.endsWith("\n") || env === "" ? "" : "\n";
|
|
91
|
+
appendFileSync(envPath, `${prefix}# ── channel stubs (maestro setup; fill in) ──\n${additions.join("\n")}\n`);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async verify(ctx) {
|
|
96
|
+
const recorded = readSelections(ctx.agentRoot);
|
|
97
|
+
const ok = !!recorded;
|
|
98
|
+
return { ok: true, checks: [{ name: `channel/MCP selections recorded (${ok ? (recorded.channels || []).length : 0})`, ok: true }] };
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function selectionsPath(agentRoot) { return join(agentRoot, "state", "setup", "selections.json"); }
|
|
103
|
+
|
|
104
|
+
function readSelections(agentRoot) {
|
|
105
|
+
const p = selectionsPath(agentRoot);
|
|
106
|
+
try { return existsSync(p) ? JSON.parse(readFileSync(p, "utf-8")) : null; } catch { return null; }
|
|
107
|
+
}
|
|
108
|
+
function writeSelections(agentRoot, obj) {
|
|
109
|
+
const p = selectionsPath(agentRoot);
|
|
110
|
+
mkdirSync(join(agentRoot, "state", "setup"), { recursive: true });
|
|
111
|
+
const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
|
|
112
|
+
writeFileSync(tmp, JSON.stringify(obj, null, 2) + "\n", "utf-8");
|
|
113
|
+
renameSync(tmp, p);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Pull MCP server ids declared by generate-capability (best-effort line scan). */
|
|
117
|
+
function parseDeclaredMcp(body) {
|
|
118
|
+
if (!body) return [];
|
|
119
|
+
const out = [];
|
|
120
|
+
for (const line of body.split("\n")) {
|
|
121
|
+
const m = line.match(/^\s*-\s*(?:id|name):\s*([A-Za-z0-9_.-]+)/);
|
|
122
|
+
if (m) out.push(m[1]);
|
|
123
|
+
}
|
|
124
|
+
return [...new Set(out)];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function pull(ctx) {
|
|
128
|
+
const ans = ctx.answers || {};
|
|
129
|
+
if (ans.tools) return ans.tools;
|
|
130
|
+
if ("channels" in ans || "mcp" in ans) return { channels: ans.channels || [], mcp: ans.mcp || [] };
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sections/verify.mjs — final verification + capability summary table. (order 90)
|
|
3
|
+
*
|
|
4
|
+
* apply() is a no-op (verification writes nothing). verify() aggregates every
|
|
5
|
+
* other section's verify() result + the Claude API probe + the completeness
|
|
6
|
+
* gate, then builds an N/M capability table with a remedy line per failure.
|
|
7
|
+
*
|
|
8
|
+
* HARD failures (cause setup to exit non-zero) are limited to:
|
|
9
|
+
* - no identity / config/agent.json missing
|
|
10
|
+
* - unresolvable archetype
|
|
11
|
+
* - Claude API unreachable
|
|
12
|
+
* Everything else is reported but non-fatal (channels optional, enrichment
|
|
13
|
+
* best-effort), per design §4 "Exit non-zero only on hard failures".
|
|
14
|
+
*
|
|
15
|
+
* @module lib/setup/sections/verify
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
"use strict";
|
|
19
|
+
|
|
20
|
+
import { probeClaude } from "../claude-probe.mjs";
|
|
21
|
+
import { assertComplete } from "../completeness.mjs";
|
|
22
|
+
import * as io from "../io.mjs";
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
id: "verify",
|
|
26
|
+
label: "Verification & capability summary",
|
|
27
|
+
order: 90,
|
|
28
|
+
|
|
29
|
+
async detect() {
|
|
30
|
+
// verify always runs (it has no persisted "done" state of its own).
|
|
31
|
+
return { status: "partial", summary: "runs the live capability probe" };
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async prompt() { /* none */ },
|
|
35
|
+
|
|
36
|
+
async apply() { /* verification is read-only */ },
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Aggregate verification. Optionally pass the live section list via
|
|
40
|
+
* `ctx.sections` so we can run each section's verify(); otherwise we still
|
|
41
|
+
* run the core probes (completeness + Claude API).
|
|
42
|
+
*/
|
|
43
|
+
async verify(ctx) {
|
|
44
|
+
const rows = [];
|
|
45
|
+
|
|
46
|
+
// Per-section verify (skip self to avoid recursion).
|
|
47
|
+
const sections = (ctx.sections || []).filter((s) => s.id !== "verify");
|
|
48
|
+
for (const s of sections) {
|
|
49
|
+
try {
|
|
50
|
+
const r = await s.verify(ctx);
|
|
51
|
+
for (const c of r.checks || []) rows.push({ section: s.id, ...c });
|
|
52
|
+
} catch (e) {
|
|
53
|
+
rows.push({ section: s.id, name: `${s.id} verify`, ok: false, remedy: `verify threw: ${e && e.message ? e.message : e}` });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Core probes (always, even if no sections passed in).
|
|
58
|
+
const complete = await assertComplete(ctx.agentRoot);
|
|
59
|
+
const probe = probeClaude(ctx.agentRoot);
|
|
60
|
+
|
|
61
|
+
// Hard-fail conditions.
|
|
62
|
+
const noIdentity = complete.missing.some((m) => m.artifact === "config/agent.json");
|
|
63
|
+
const archetypeOk = complete.archetypeOk;
|
|
64
|
+
const hardFail = noIdentity || !archetypeOk || !probe.ok;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
ok: !hardFail,
|
|
68
|
+
hardFail,
|
|
69
|
+
rows,
|
|
70
|
+
complete,
|
|
71
|
+
probe,
|
|
72
|
+
checks: rows,
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Render the capability table to stdout. Separated from verify() so the
|
|
78
|
+
* runner can call it; pure presentation.
|
|
79
|
+
* @param {{rows:Array, complete:object, probe:object}} result
|
|
80
|
+
*/
|
|
81
|
+
renderTable(result) {
|
|
82
|
+
const rows = result.rows || [];
|
|
83
|
+
const passed = rows.filter((r) => r.ok).length;
|
|
84
|
+
io.note("");
|
|
85
|
+
io.note(`Capability summary — ${passed}/${rows.length} checks passing`);
|
|
86
|
+
for (const r of rows) {
|
|
87
|
+
const mark = r.ok ? " ✓" : " ✗";
|
|
88
|
+
const where = r.section ? ` [${r.section}]` : "";
|
|
89
|
+
io.note(`${mark} ${r.name}${where}`);
|
|
90
|
+
if (!r.ok && r.remedy) io.note(` ↳ ${r.remedy}`, { dim: true });
|
|
91
|
+
}
|
|
92
|
+
if (result.probe && !result.probe.ok && result.probe.remedy) {
|
|
93
|
+
io.note(` ✗ ${result.probe.name}`);
|
|
94
|
+
io.note(` ↳ ${result.probe.remedy}`, { dim: true });
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/setup/sot.mjs — single-source-of-truth helpers for agent identity.
|
|
3
|
+
*
|
|
4
|
+
* `config/agent.json` is the canonical store. `config/agent.ts` is a generated
|
|
5
|
+
* thin re-export wrapper over it (the migrate-agent-to-sot.mjs end-state); this
|
|
6
|
+
* module owns reading/merging the JSON and (re)writing the wrapper idempotently.
|
|
7
|
+
*
|
|
8
|
+
* readAgentJson(agentRoot) → object | null
|
|
9
|
+
* mergeAgentJson(agentRoot, patch) → merged object (deep merge-by-key, atomic)
|
|
10
|
+
* ensureSotWrapper(agentRoot) → { wrote, path } (writes config/agent.ts iff needed)
|
|
11
|
+
* isWrapper(tsSource) → boolean (does this .ts re-export agent.json?)
|
|
12
|
+
* WRAPPER_TEMPLATE → the canonical wrapper body
|
|
13
|
+
*
|
|
14
|
+
* Atomic writes (tmp + rename). `mergeAgentJson` deep-merges plain objects and
|
|
15
|
+
* never drops keys the patch did not mention.
|
|
16
|
+
*
|
|
17
|
+
* @module lib/setup/sot
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
"use strict";
|
|
21
|
+
|
|
22
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
|
|
23
|
+
import { join, dirname } from "node:path";
|
|
24
|
+
|
|
25
|
+
const JSON_REL = "config/agent.json";
|
|
26
|
+
const TS_REL = "config/agent.ts";
|
|
27
|
+
|
|
28
|
+
/** Path helpers. */
|
|
29
|
+
export function agentJsonPath(agentRoot) { return join(agentRoot, JSON_REL); }
|
|
30
|
+
export function agentTsPath(agentRoot) { return join(agentRoot, TS_REL); }
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read config/agent.json. Returns null when missing; throws on malformed JSON
|
|
34
|
+
* (a corrupt SoT is a real error the caller should surface, unlike the
|
|
35
|
+
* fail-soft progress store).
|
|
36
|
+
* @param {string} agentRoot
|
|
37
|
+
* @returns {object|null}
|
|
38
|
+
*/
|
|
39
|
+
export function readAgentJson(agentRoot) {
|
|
40
|
+
const p = agentJsonPath(agentRoot);
|
|
41
|
+
if (!existsSync(p)) return null;
|
|
42
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeAtomic(path, body) {
|
|
46
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
47
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
48
|
+
writeFileSync(tmp, body, "utf-8");
|
|
49
|
+
renameSync(tmp, path);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Write config/agent.json atomically (stable 2-space JSON + trailing newline). */
|
|
53
|
+
export function writeAgentJson(agentRoot, obj) {
|
|
54
|
+
writeAtomic(agentJsonPath(agentRoot), JSON.stringify(obj, null, 2) + "\n");
|
|
55
|
+
return obj;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Deep merge: plain objects merge key-by-key (patch wins per leaf), arrays and
|
|
60
|
+
* scalars are replaced wholesale by the patch. Unrelated existing keys are
|
|
61
|
+
* always preserved. Returns a new object; inputs are not mutated.
|
|
62
|
+
* @param {object} base @param {object} patch @returns {object}
|
|
63
|
+
*/
|
|
64
|
+
export function deepMergeByKey(base, patch) {
|
|
65
|
+
if (patch === null || patch === undefined) return clone(base);
|
|
66
|
+
if (Array.isArray(base) || Array.isArray(patch)) return clone(patch);
|
|
67
|
+
if (typeof base !== "object" || typeof patch !== "object") return clone(patch);
|
|
68
|
+
const out = clone(base);
|
|
69
|
+
for (const k of Object.keys(patch)) {
|
|
70
|
+
const b = out[k];
|
|
71
|
+
const p = patch[k];
|
|
72
|
+
if (b && p && typeof b === "object" && typeof p === "object" && !Array.isArray(b) && !Array.isArray(p)) {
|
|
73
|
+
out[k] = deepMergeByKey(b, p);
|
|
74
|
+
} else if (p !== undefined) {
|
|
75
|
+
out[k] = clone(p);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function clone(v) { return v === undefined ? undefined : JSON.parse(JSON.stringify(v)); }
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Merge a patch into config/agent.json (creating it from the patch if absent),
|
|
85
|
+
* writing atomically. Never drops unrelated fields.
|
|
86
|
+
* @param {string} agentRoot
|
|
87
|
+
* @param {object} patch
|
|
88
|
+
* @returns {object} the merged object
|
|
89
|
+
*/
|
|
90
|
+
export function mergeAgentJson(agentRoot, patch) {
|
|
91
|
+
const current = readAgentJson(agentRoot) || {};
|
|
92
|
+
const merged = deepMergeByKey(current, patch || {});
|
|
93
|
+
return writeAgentJson(agentRoot, merged);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Heuristic: is the supplied agent.ts source a thin wrapper that re-exports
|
|
98
|
+
* from ./agent.json (rather than a standalone literal `export const agent = {…}`)?
|
|
99
|
+
* @param {string} tsSource
|
|
100
|
+
* @returns {boolean}
|
|
101
|
+
*/
|
|
102
|
+
export function isWrapper(tsSource) {
|
|
103
|
+
if (typeof tsSource !== "string") return false;
|
|
104
|
+
const importsJson = /import\s+\w+\s+from\s+['"]\.\/agent\.json['"]/.test(tsSource);
|
|
105
|
+
// A standalone literal assigns an inline object to `agent`.
|
|
106
|
+
const inlineLiteral = /export\s+const\s+agent\s*(?::[^=]+)?=\s*\{/.test(tsSource);
|
|
107
|
+
return importsJson && !inlineLiteral;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Ensure config/agent.ts is the thin wrapper. Idempotent: if it already is a
|
|
112
|
+
* wrapper, no write occurs. Returns { wrote, path }.
|
|
113
|
+
* @param {string} agentRoot
|
|
114
|
+
* @returns {{wrote:boolean, path:string}}
|
|
115
|
+
*/
|
|
116
|
+
export function ensureSotWrapper(agentRoot) {
|
|
117
|
+
const p = agentTsPath(agentRoot);
|
|
118
|
+
if (existsSync(p)) {
|
|
119
|
+
const src = readFileSync(p, "utf-8");
|
|
120
|
+
if (isWrapper(src)) return { wrote: false, path: p };
|
|
121
|
+
}
|
|
122
|
+
writeAtomic(p, WRAPPER_TEMPLATE);
|
|
123
|
+
return { wrote: true, path: p };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The canonical thin-wrapper body for config/agent.ts. Mirrors
|
|
128
|
+
* scripts/setup/migrate-agent-to-sot.mjs `inlineWrapper()` so a repo migrated by
|
|
129
|
+
* either path ends up byte-identical.
|
|
130
|
+
*/
|
|
131
|
+
export const WRAPPER_TEMPLATE = `/**
|
|
132
|
+
* Maestro — Agent Configuration (TypeScript wrapper)
|
|
133
|
+
*
|
|
134
|
+
* The canonical source of truth is \`config/agent.json\`. Edit that file,
|
|
135
|
+
* then run \`node scripts/setup/generate-agent-env.mjs\` to regenerate the
|
|
136
|
+
* shell environment file.
|
|
137
|
+
*
|
|
138
|
+
* This wrapper exposes typed access plus a few computed convenience values.
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
import agentData from './agent.json' with { type: 'json' };
|
|
142
|
+
|
|
143
|
+
export interface VoiceMode {
|
|
144
|
+
id: string;
|
|
145
|
+
label: string;
|
|
146
|
+
description: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface PrincipalConfig {
|
|
150
|
+
firstName: string;
|
|
151
|
+
lastName: string;
|
|
152
|
+
fullName: string;
|
|
153
|
+
title: string;
|
|
154
|
+
email: string;
|
|
155
|
+
slackMemberId: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface AgentConfig {
|
|
159
|
+
firstName: string;
|
|
160
|
+
lastName: string;
|
|
161
|
+
fullName: string;
|
|
162
|
+
title: string;
|
|
163
|
+
archetype: string;
|
|
164
|
+
|
|
165
|
+
email: string;
|
|
166
|
+
phone: string;
|
|
167
|
+
slackMemberId: string;
|
|
168
|
+
|
|
169
|
+
company: string;
|
|
170
|
+
companyDomain: string;
|
|
171
|
+
companyDescription: string;
|
|
172
|
+
|
|
173
|
+
principal: PrincipalConfig;
|
|
174
|
+
|
|
175
|
+
machineName: string;
|
|
176
|
+
repoSlug: string;
|
|
177
|
+
launchdLabelPrefix: string;
|
|
178
|
+
|
|
179
|
+
timezone: string;
|
|
180
|
+
locale: string;
|
|
181
|
+
|
|
182
|
+
schedule: {
|
|
183
|
+
morningBrief: string;
|
|
184
|
+
commsTriage: string[];
|
|
185
|
+
eveningWrap: string;
|
|
186
|
+
overnightMonitoring: string;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
communication: {
|
|
190
|
+
defaultTone: string;
|
|
191
|
+
externalTone: string;
|
|
192
|
+
voiceModes: VoiceMode[];
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
responsibilities: string[];
|
|
196
|
+
operatingPrinciples: string[];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export const agent: AgentConfig = agentData as AgentConfig;
|
|
200
|
+
|
|
201
|
+
export const AGENT_UPPER = agent.firstName.toUpperCase();
|
|
202
|
+
export const AGENT_LOWER = agent.firstName.toLowerCase();
|
|
203
|
+
export const REPO_DIR = \`~/\${agent.repoSlug}\`;
|
|
204
|
+
export const EMAIL_SIGNATURE = \`\${agent.fullName} | \${agent.title} | \${agent.company}\`;
|
|
205
|
+
`;
|