@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,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-router.test.mjs — node:test coverage for the session router.
|
|
3
|
+
*
|
|
4
|
+
* No real subprocess spawns, no real disk writes outside os.tmpdir().
|
|
5
|
+
* Each test creates its own registry path under tmpdir and cleans up.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { promises as fsp } from "fs";
|
|
11
|
+
import { tmpdir } from "os";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
|
|
14
|
+
import { routingKey, createRouter } from "./session-router.mjs";
|
|
15
|
+
|
|
16
|
+
function tmpRegistryPath(suffix = "") {
|
|
17
|
+
const name = `session-router-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${suffix}.json`;
|
|
18
|
+
return join(tmpdir(), name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function safeUnlink(path) {
|
|
22
|
+
try {
|
|
23
|
+
await fsp.unlink(path);
|
|
24
|
+
} catch {
|
|
25
|
+
/* ignore */
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// 1. routingKey — one assertion per row of memo §4.2.
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
test("routingKey — slack threaded message", () => {
|
|
34
|
+
const k = routingKey({
|
|
35
|
+
source: "slack",
|
|
36
|
+
channel: "C0123ABC",
|
|
37
|
+
thread_ts: "1777283277.000100",
|
|
38
|
+
});
|
|
39
|
+
assert.equal(k, "slack:C0123ABC:1777283277.000100");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("routingKey — slack DM (no thread)", () => {
|
|
43
|
+
const k = routingKey({
|
|
44
|
+
source: "slack",
|
|
45
|
+
channel: "D099N1JGKRQ",
|
|
46
|
+
});
|
|
47
|
+
assert.equal(k, "slack:D099N1JGKRQ");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("routingKey — slack channel non-DM (no thread)", () => {
|
|
51
|
+
const k = routingKey({
|
|
52
|
+
source: "slack",
|
|
53
|
+
channel: "C0123ABC",
|
|
54
|
+
ts: "1777283277.000200",
|
|
55
|
+
});
|
|
56
|
+
assert.equal(k, "slack:C0123ABC:1777283277.000200");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("routingKey — gmail", () => {
|
|
60
|
+
const k = routingKey({ source: "gmail", thread_id: "1818abc" });
|
|
61
|
+
assert.equal(k, "gmail:1818abc");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("routingKey — calendar", () => {
|
|
65
|
+
const k = routingKey({ source: "calendar", event_id: "evt_42" });
|
|
66
|
+
assert.equal(k, "calendar:evt_42");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("routingKey — internal queue item", () => {
|
|
70
|
+
const k = routingKey({ source: "internal", id: "ar-20260427-001" });
|
|
71
|
+
assert.equal(k, "internal:ar-20260427-001");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("routingKey — backlog with topic_slug", () => {
|
|
75
|
+
const k = routingKey({
|
|
76
|
+
source: "backlog",
|
|
77
|
+
topic_slug: "session-router-step2",
|
|
78
|
+
id: "as-20260427-x",
|
|
79
|
+
});
|
|
80
|
+
assert.equal(k, "backlog:session-router-step2");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("routingKey — backlog without topic_slug falls back to internal:<id>", () => {
|
|
84
|
+
const k = routingKey({ source: "backlog", id: "as-20260427-fallback" });
|
|
85
|
+
assert.equal(k, "internal:as-20260427-fallback");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// 2. route() — missing key returns EPHEMERAL.
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
test("route — missing key returns EPHEMERAL", async (t) => {
|
|
93
|
+
const path = tmpRegistryPath("-missing");
|
|
94
|
+
t.after(() => safeUnlink(path));
|
|
95
|
+
const router = await createRouter({ registryPath: path });
|
|
96
|
+
const decision = router.route("slack:CNEVERSEEN:0");
|
|
97
|
+
assert.deepEqual(decision, { decision: "EPHEMERAL", resumeId: null });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// 3. route() — past-TTL entry returns EPHEMERAL_REPLACE.
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
test("route — past-TTL entry returns EPHEMERAL_REPLACE", async (t) => {
|
|
105
|
+
const path = tmpRegistryPath("-ttl");
|
|
106
|
+
t.after(() => safeUnlink(path));
|
|
107
|
+
let clock = 1_000_000_000_000;
|
|
108
|
+
const router = await createRouter({
|
|
109
|
+
registryPath: path,
|
|
110
|
+
ttlSeconds: 60,
|
|
111
|
+
now: () => clock,
|
|
112
|
+
});
|
|
113
|
+
await router.touch("slack:DAAA:1", { claudeSessionId: "cli-1", model: "sonnet" });
|
|
114
|
+
// Advance clock past TTL.
|
|
115
|
+
clock += 61 * 1000;
|
|
116
|
+
const decision = router.route("slack:DAAA:1");
|
|
117
|
+
assert.equal(decision.decision, "EPHEMERAL_REPLACE");
|
|
118
|
+
assert.equal(decision.resumeId, null);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// 4. route() — live, fresh, exit_code 0 returns RESUME with the stored id.
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
test("route — live fresh entry returns RESUME with stored claude_session_id", async (t) => {
|
|
126
|
+
const path = tmpRegistryPath("-resume");
|
|
127
|
+
t.after(() => safeUnlink(path));
|
|
128
|
+
let clock = 2_000_000_000_000;
|
|
129
|
+
const router = await createRouter({
|
|
130
|
+
registryPath: path,
|
|
131
|
+
ttlSeconds: 600,
|
|
132
|
+
now: () => clock,
|
|
133
|
+
});
|
|
134
|
+
await router.touch("slack:DRESUME:1", {
|
|
135
|
+
claudeSessionId: "cli-resume-xyz",
|
|
136
|
+
model: "sonnet",
|
|
137
|
+
});
|
|
138
|
+
clock += 30_000; // 30s — well within TTL.
|
|
139
|
+
const decision = router.route("slack:DRESUME:1");
|
|
140
|
+
assert.equal(decision.decision, "RESUME");
|
|
141
|
+
assert.equal(decision.resumeId, "cli-resume-xyz");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// 5. route() — non-zero last_exit_code returns EPHEMERAL_REPLACE.
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
test("route — non-zero last_exit_code returns EPHEMERAL_REPLACE", async (t) => {
|
|
149
|
+
const path = tmpRegistryPath("-exitcode");
|
|
150
|
+
t.after(() => safeUnlink(path));
|
|
151
|
+
let clock = 3_000_000_000_000;
|
|
152
|
+
const router = await createRouter({
|
|
153
|
+
registryPath: path,
|
|
154
|
+
ttlSeconds: 600,
|
|
155
|
+
now: () => clock,
|
|
156
|
+
});
|
|
157
|
+
await router.touch("slack:DEXIT:1", {
|
|
158
|
+
claudeSessionId: "cli-exit",
|
|
159
|
+
model: "sonnet",
|
|
160
|
+
});
|
|
161
|
+
await router.recordExit("slack:DEXIT:1", 1);
|
|
162
|
+
clock += 5_000;
|
|
163
|
+
const decision = router.route("slack:DEXIT:1");
|
|
164
|
+
assert.equal(decision.decision, "EPHEMERAL_REPLACE");
|
|
165
|
+
assert.equal(decision.resumeId, null);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// 6. touch() — LRU cap enforced; oldest entry evicted on overflow.
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
test("touch — LRU cap evicts oldest when at capacity", async (t) => {
|
|
173
|
+
const path = tmpRegistryPath("-lru");
|
|
174
|
+
t.after(() => safeUnlink(path));
|
|
175
|
+
let clock = 4_000_000_000_000;
|
|
176
|
+
const router = await createRouter({
|
|
177
|
+
registryPath: path,
|
|
178
|
+
ttlSeconds: 3600,
|
|
179
|
+
maxLiveSessions: 3,
|
|
180
|
+
now: () => clock,
|
|
181
|
+
});
|
|
182
|
+
for (let i = 1; i <= 3; i++) {
|
|
183
|
+
await router.touch(`slack:DLRU${i}`, {
|
|
184
|
+
claudeSessionId: `cli-${i}`,
|
|
185
|
+
model: "sonnet",
|
|
186
|
+
});
|
|
187
|
+
clock += 1000;
|
|
188
|
+
}
|
|
189
|
+
// At cap. Add a fourth.
|
|
190
|
+
await router.touch("slack:DLRU4", {
|
|
191
|
+
claudeSessionId: "cli-4",
|
|
192
|
+
model: "sonnet",
|
|
193
|
+
});
|
|
194
|
+
const snap = router._readForTests();
|
|
195
|
+
assert.equal(snap.lru.length, 3, "lru should still hold 3 keys");
|
|
196
|
+
assert.ok(!("slack:DLRU1" in snap.sessions), "oldest (DLRU1) evicted");
|
|
197
|
+
assert.ok("slack:DLRU2" in snap.sessions);
|
|
198
|
+
assert.ok("slack:DLRU3" in snap.sessions);
|
|
199
|
+
assert.ok("slack:DLRU4" in snap.sessions);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// 7. recordExit() — non-zero code flips status to "killed".
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
test("recordExit — non-zero code flips status to killed", async (t) => {
|
|
207
|
+
const path = tmpRegistryPath("-killed");
|
|
208
|
+
t.after(() => safeUnlink(path));
|
|
209
|
+
const router = await createRouter({ registryPath: path });
|
|
210
|
+
await router.touch("slack:DKILL:1", {
|
|
211
|
+
claudeSessionId: "cli-kill",
|
|
212
|
+
model: "sonnet",
|
|
213
|
+
});
|
|
214
|
+
await router.recordExit("slack:DKILL:1", 137);
|
|
215
|
+
const snap = router._readForTests();
|
|
216
|
+
assert.equal(snap.sessions["slack:DKILL:1"].status, "killed");
|
|
217
|
+
assert.equal(snap.sessions["slack:DKILL:1"].last_exit_code, 137);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// 8. evictExpired() — removes only stale entries, returns count.
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
test("evictExpired — removes only stale entries and returns count", async (t) => {
|
|
225
|
+
const path = tmpRegistryPath("-evict");
|
|
226
|
+
t.after(() => safeUnlink(path));
|
|
227
|
+
let clock = 5_000_000_000_000;
|
|
228
|
+
const router = await createRouter({
|
|
229
|
+
registryPath: path,
|
|
230
|
+
ttlSeconds: 60,
|
|
231
|
+
now: () => clock,
|
|
232
|
+
});
|
|
233
|
+
await router.touch("slack:DEVICT:OLD", {
|
|
234
|
+
claudeSessionId: "cli-old",
|
|
235
|
+
model: "sonnet",
|
|
236
|
+
});
|
|
237
|
+
// Advance past TTL.
|
|
238
|
+
clock += 120_000;
|
|
239
|
+
await router.touch("slack:DEVICT:NEW", {
|
|
240
|
+
claudeSessionId: "cli-new",
|
|
241
|
+
model: "sonnet",
|
|
242
|
+
});
|
|
243
|
+
// Now: OLD is stale (last_used_at = clock - 120s, ttl = 60s),
|
|
244
|
+
// NEW is fresh.
|
|
245
|
+
const evicted = await router.evictExpired();
|
|
246
|
+
assert.equal(evicted, 1, "one entry should have been evicted");
|
|
247
|
+
const snap = router._readForTests();
|
|
248
|
+
assert.ok(!("slack:DEVICT:OLD" in snap.sessions));
|
|
249
|
+
assert.ok("slack:DEVICT:NEW" in snap.sessions);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// 9. Registry round-trip — touch two keys, re-create router, state restored.
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
test("registry round-trip — second router instance reads persisted state", async (t) => {
|
|
257
|
+
const path = tmpRegistryPath("-roundtrip");
|
|
258
|
+
t.after(() => safeUnlink(path));
|
|
259
|
+
let clock = 6_000_000_000_000;
|
|
260
|
+
|
|
261
|
+
const r1 = await createRouter({
|
|
262
|
+
registryPath: path,
|
|
263
|
+
ttlSeconds: 3600,
|
|
264
|
+
now: () => clock,
|
|
265
|
+
});
|
|
266
|
+
await r1.touch("gmail:thread-A", {
|
|
267
|
+
claudeSessionId: "cli-A",
|
|
268
|
+
model: "sonnet",
|
|
269
|
+
});
|
|
270
|
+
clock += 1000;
|
|
271
|
+
await r1.touch("gmail:thread-B", {
|
|
272
|
+
claudeSessionId: "cli-B",
|
|
273
|
+
model: "opus",
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Fresh router instance against the same path.
|
|
277
|
+
const r2 = await createRouter({
|
|
278
|
+
registryPath: path,
|
|
279
|
+
ttlSeconds: 3600,
|
|
280
|
+
now: () => clock,
|
|
281
|
+
});
|
|
282
|
+
const snap = r2._readForTests();
|
|
283
|
+
assert.equal(Object.keys(snap.sessions).length, 2);
|
|
284
|
+
assert.equal(snap.sessions["gmail:thread-A"].claude_session_id, "cli-A");
|
|
285
|
+
assert.equal(snap.sessions["gmail:thread-B"].claude_session_id, "cli-B");
|
|
286
|
+
assert.equal(snap.sessions["gmail:thread-B"].model, "opus");
|
|
287
|
+
// Both keys present in the LRU array.
|
|
288
|
+
assert.ok(snap.lru.includes("gmail:thread-A"));
|
|
289
|
+
assert.ok(snap.lru.includes("gmail:thread-B"));
|
|
290
|
+
|
|
291
|
+
// route() should return RESUME for the persisted entry.
|
|
292
|
+
const dec = r2.route("gmail:thread-A");
|
|
293
|
+
assert.equal(dec.decision, "RESUME");
|
|
294
|
+
assert.equal(dec.resumeId, "cli-A");
|
|
295
|
+
});
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Maestro Daemon — Reactive event-driven message processor
|
|
4
|
+
// =============================================================================
|
|
5
|
+
//
|
|
6
|
+
// Generic entry point that loads agent identity from config/agent.json and
|
|
7
|
+
// delegates to the core daemon logic. This file is agent-name-agnostic.
|
|
8
|
+
//
|
|
9
|
+
// Lifecycle:
|
|
10
|
+
// 1. Honour .emergency-stop BEFORE doing anything (don't acquire singleton
|
|
11
|
+
// lock, don't start consumer, don't import the core daemon). Stops the
|
|
12
|
+
// launchd restart treadmill cold.
|
|
13
|
+
// 2. Acquire the daemon singleton lock so only one instance runs.
|
|
14
|
+
// 3. Start the cadence consumer (state/cadence-bus/ drain loop).
|
|
15
|
+
// 4. Import the core daemon (agent-daemon.mjs canonical, with fallbacks
|
|
16
|
+
// to a legacy <firstName>-daemon.mjs for back-compat).
|
|
17
|
+
//
|
|
18
|
+
// Run: node scripts/daemon/maestro-daemon.mjs
|
|
19
|
+
// Install: launchd plist with KeepAlive.SuccessfulExit: false (clean exits
|
|
20
|
+
// during emergency-stop should NOT trigger restart; non-zero exits should).
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
import { resolve, dirname, join } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
26
|
+
import { installUnhandledHandlers } from "../../lib/util/unhandled.mjs";
|
|
27
|
+
import { readPeers, readIdentity } from "../../lib/org/registry.mjs";
|
|
28
|
+
import { bump as bumpCounter } from "../../lib/diagnostics/counters.mjs";
|
|
29
|
+
|
|
30
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const AGENT_DIR = process.env.AGENT_DIR || resolve(__dirname, "../..");
|
|
32
|
+
|
|
33
|
+
process.env.AGENT_DIR = AGENT_DIR;
|
|
34
|
+
process.env.AGENT_ROOT = AGENT_DIR;
|
|
35
|
+
|
|
36
|
+
// Never die silently (P0-6): a stray unhandled rejection / uncaught exception is
|
|
37
|
+
// logged + counted, not a silent process death. Rejections keep the daemon
|
|
38
|
+
// running; an uncaught exception logs+flushes then lets launchd restart us.
|
|
39
|
+
installUnhandledHandlers({
|
|
40
|
+
bump: (name, attrs) => { try { bumpCounter(name, { ...attrs, agentRoot: AGENT_DIR }); } catch { /* never throw from the handler */ } },
|
|
41
|
+
onEvent: (e) => console.error(`[DAEMON] ${e.kind}: ${e.error && e.error.message}`),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// 1. Emergency-stop gate
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// If the operator dropped .emergency-stop, we exit cleanly (code 0). With
|
|
48
|
+
// KeepAlive.SuccessfulExit:false this means launchd will NOT restart us
|
|
49
|
+
// until the flag is lifted + the plist is reloaded by resume-operations.sh.
|
|
50
|
+
// Older plists with plain KeepAlive:true will still restart, so we hold
|
|
51
|
+
// the process for 30s before exiting to give launchd's ThrottleInterval
|
|
52
|
+
// something to throttle.
|
|
53
|
+
if (existsSync(join(AGENT_DIR, ".emergency-stop"))) {
|
|
54
|
+
console.error("[DAEMON] .emergency-stop flag present — refusing to start.");
|
|
55
|
+
console.error("[DAEMON] Lift with: scripts/resume-operations.sh");
|
|
56
|
+
await new Promise((r) => setTimeout(r, 30_000));
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// 2. Singleton guard
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
try {
|
|
64
|
+
const { acquireLock } = await import("../../lib/singleton.js");
|
|
65
|
+
if (!acquireLock("daemon")) {
|
|
66
|
+
console.log("[DAEMON] Already running — exiting");
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
} catch { /* singleton lib missing — proceed without lock */ }
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// 3. Cadence consumer (cadence bus drain loop)
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Start alongside the reactive event loop. This is the single persistent
|
|
75
|
+
// owner of cadence housekeeping — launchd plists enqueue cadence ticks
|
|
76
|
+
// onto state/cadence-bus/ and the consumer drains them here, either
|
|
77
|
+
// handling them inline or escalating to a sub-session when warranted.
|
|
78
|
+
// Failure to start the consumer must NOT take the reactive daemon down.
|
|
79
|
+
try {
|
|
80
|
+
const { startConsumer } = await import("./cadence-consumer.mjs");
|
|
81
|
+
const consumer = startConsumer({ agentRoot: AGENT_DIR });
|
|
82
|
+
const shutdown = async (sig) => {
|
|
83
|
+
try { await consumer.stop(); } catch { /* ignore */ }
|
|
84
|
+
console.log(`[DAEMON] cadence consumer stopped (${sig})`);
|
|
85
|
+
};
|
|
86
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
87
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(`[DAEMON] cadence consumer failed to start: ${err.message}`);
|
|
90
|
+
// Reactive daemon continues. Doctor / healthcheck will surface this.
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// 3b. Channel bus (1.12.0+; generic platform loop in 1.15.0/WS2)
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// WS2: instead of a hardcoded voice+whatsapp block, we iterate the
|
|
97
|
+
// self-registered PLATFORMS (lib/channels/contract.mjs definePlatform) and
|
|
98
|
+
// start every platform whose gate file exists under the agent dir. Each
|
|
99
|
+
// channel's onInbound writes a byte-compatible inbox item via
|
|
100
|
+
// `writeInboxItem(service, eventToInboxItem(ev))` so new channels flow
|
|
101
|
+
// through the unchanged poll()/classify/dispatch path. Failures here MUST
|
|
102
|
+
// NOT take the reactive daemon down.
|
|
103
|
+
try {
|
|
104
|
+
const channelsModule = resolve(__dirname, "../../lib/channels/index.mjs");
|
|
105
|
+
if (existsSync(channelsModule)) {
|
|
106
|
+
const { listPlatforms, eventToInboxItem } = await import(`file://${channelsModule}`);
|
|
107
|
+
const { writeInboxItem } = await import(`file://${resolve(__dirname, "../poller/utils.mjs")}`);
|
|
108
|
+
// Single-owner rule (H2): the shared SoT for which platforms the daemon must
|
|
109
|
+
// NOT start (they have a dedicated launchd owner — e.g. slack-socket).
|
|
110
|
+
const { isDaemonManagedPlatform } = await import(`file://${resolve(__dirname, "../../lib/channels/contract.mjs")}`);
|
|
111
|
+
|
|
112
|
+
// Importing the adapters runs their definePlatform() side-effects. Each is
|
|
113
|
+
// optional (dynamic deps); a failed import just means that platform isn't
|
|
114
|
+
// available and is skipped.
|
|
115
|
+
for (const mod of ["./slack/adapter.mjs", "./telegram/adapter.mjs", "./whatsapp/adapter-baileys.mjs", "./orgmail/adapter.mjs"]) {
|
|
116
|
+
try { await import(`file://${resolve(__dirname, "../../lib/channels", mod)}`); }
|
|
117
|
+
catch (err) { console.warn(`[DAEMON] channel-bus: ${mod} not loadable — ${err.message}`); }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const makeOnInbound = (service) => (ev) => {
|
|
121
|
+
try {
|
|
122
|
+
writeInboxItem(service, eventToInboxItem(ev));
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.warn(`[DAEMON] channel-bus inbox write failed (${service}): ${err.message}`);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Register started adapters with the typing registry so the dispatcher can
|
|
129
|
+
// drive a "typing…" heartbeat on the originating channel during a session.
|
|
130
|
+
let registerTypingAdapter = null;
|
|
131
|
+
try {
|
|
132
|
+
({ registerTypingAdapter } = await import(`file://${resolve(__dirname, "./typing-registry.mjs")}`));
|
|
133
|
+
} catch { /* typing is optional */ }
|
|
134
|
+
|
|
135
|
+
// SINGLE-OWNER RULE (H2): some platforms have a DEDICATED launchd process
|
|
136
|
+
// that owns their real-time connection — the daemon must NOT also start
|
|
137
|
+
// them here, or the channel is ingested twice (every DM answered twice).
|
|
138
|
+
// Slack's owner is `ai.maestro.<agent>-slack-socket` (generate-plists.sh),
|
|
139
|
+
// which runs scripts/poller/slack-socket-mode.mjs and reads config/slack.yaml
|
|
140
|
+
// itself for reactions/cc. This generic loop is only for daemon-owned
|
|
141
|
+
// channels (telegram / whatsapp / voice) that have NO dedicated plist.
|
|
142
|
+
// The exclusion SoT is lib/channels/contract.mjs (isDaemonManagedPlatform).
|
|
143
|
+
for (const platform of listPlatforms()) {
|
|
144
|
+
if (!isDaemonManagedPlatform(platform.name)) {
|
|
145
|
+
console.log(`[DAEMON] channel-bus: ${platform.name} skipped — owned by its dedicated launchd process (single-owner rule)`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const gatePath = join(AGENT_DIR, platform.gateFile);
|
|
149
|
+
if (!existsSync(gatePath)) continue;
|
|
150
|
+
try {
|
|
151
|
+
// Peer registry → bot-loop guard (P0-5): pass this agent's own id + the
|
|
152
|
+
// set of peer-agent ids so BaseAdapter._isOwnOrPeer won't auto-reply to a
|
|
153
|
+
// sibling agent in a shared channel (reply-storm prevention). Best-effort:
|
|
154
|
+
// ids are matched per-platform where known; a no-match is simply not guarded.
|
|
155
|
+
const peers = readPeers(AGENT_DIR) || [];
|
|
156
|
+
const self = readIdentity(AGENT_DIR) || {};
|
|
157
|
+
const peerAgentIds = new Set(
|
|
158
|
+
peers.flatMap((p) => [p.slackId, p.telegramId, p.id, p.whatsapp, p.handle].filter(Boolean)),
|
|
159
|
+
);
|
|
160
|
+
const ownId = self.slackId || self.id || self.repoSlug || "";
|
|
161
|
+
const channel = platform.factory({ agentRoot: AGENT_DIR, ownId, peerAgentIds });
|
|
162
|
+
if (registerTypingAdapter) { try { registerTypingAdapter(platform.name, channel); } catch { /* */ } }
|
|
163
|
+
// start() returns once the underlying transport is constructed (socket
|
|
164
|
+
// built / long-poll launched) — not when fully authenticated — so we
|
|
165
|
+
// don't block the daemon boot on it.
|
|
166
|
+
Promise.resolve(channel.start({ onInbound: makeOnInbound(platform.name) }))
|
|
167
|
+
.catch((err) => console.warn(`[DAEMON] channel-bus: ${platform.name}.start() failed — ${err.message}`));
|
|
168
|
+
console.log(`[DAEMON] channel-bus: ${platform.name} channel started (gate: ${platform.gateFile})`);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.warn(`[DAEMON] channel-bus: ${platform.name} channel skipped — ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.warn(`[DAEMON] channel-bus initialization failed: ${err.message}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// 3b'. Org mesh runtime membership (org-connect)
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Make this agent actually JOIN the Cohort org mesh at runtime: register at
|
|
182
|
+
// boot, presence-beat (~30s), and act on the server's directives — the kill
|
|
183
|
+
// switch ("halt" → .emergency-stop + stop), policy-stale refetch, directory
|
|
184
|
+
// resync, token rotation. A no-op when the org integration is disabled, and
|
|
185
|
+
// fully fail-open (org down ⇒ the daemon runs standalone). Failure here MUST NOT
|
|
186
|
+
// take the reactive daemon down.
|
|
187
|
+
try {
|
|
188
|
+
// Org enrollment lives in config/org.yaml (written by `maestro setup` /
|
|
189
|
+
// lib/setup/sections/org.mjs, read by lib/org/client.mjs) — NOT
|
|
190
|
+
// config/collective.yaml. Use the canonical org-config loader so an
|
|
191
|
+
// enrolled agent's mesh actually connects.
|
|
192
|
+
const { connectOrgMesh } = await import("../../lib/org/mesh.mjs");
|
|
193
|
+
const { loadOrgConfig } = await import("../../lib/org/client.mjs");
|
|
194
|
+
const cfg = loadOrgConfig(AGENT_DIR);
|
|
195
|
+
const orgMesh = await connectOrgMesh({ agentRoot: AGENT_DIR, cfg });
|
|
196
|
+
if (orgMesh && orgMesh.isEnabled) console.log("[DAEMON] org-mesh connected");
|
|
197
|
+
const stopMesh = (sig) => {
|
|
198
|
+
try { orgMesh.stop(); } catch { /* ignore */ }
|
|
199
|
+
if (orgMesh && orgMesh.isEnabled) console.log(`[DAEMON] org-mesh stopped (${sig})`);
|
|
200
|
+
};
|
|
201
|
+
process.on("SIGTERM", () => stopMesh("SIGTERM"));
|
|
202
|
+
process.on("SIGINT", () => stopMesh("SIGINT"));
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.warn(`[DAEMON] org-mesh init failed (running standalone): ${err.message}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// 3c. Model router config validation (1.12.0+, advisory)
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// If config/model-routing.yaml is active, validate it loads cleanly at
|
|
211
|
+
// daemon startup so a typo doesn't silently break every spawn. We don't
|
|
212
|
+
// reject startup on failure — dispatcher's resolver also catches this
|
|
213
|
+
// and falls back to the Anthropic CLI default.
|
|
214
|
+
try {
|
|
215
|
+
if (existsSync(join(AGENT_DIR, "config/model-routing.yaml"))) {
|
|
216
|
+
const routerModule = resolve(__dirname, "../../lib/model-router.mjs");
|
|
217
|
+
if (existsSync(routerModule)) {
|
|
218
|
+
const { loadRoutingConfig, listBackends } = await import(`file://${routerModule}`);
|
|
219
|
+
const cfg = loadRoutingConfig(AGENT_DIR);
|
|
220
|
+
if (cfg) {
|
|
221
|
+
const backends = listBackends({ config: cfg });
|
|
222
|
+
console.log(`[DAEMON] model-router active (${backends.length} backend(s): ${backends.join(", ")})`);
|
|
223
|
+
} else {
|
|
224
|
+
console.warn("[DAEMON] model-router config present but loader returned null");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
console.warn(`[DAEMON] model-router validation failed (spawn will fall back to default): ${err.message}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// 4. Core daemon import
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Resolve the core daemon module. Try, in order:
|
|
236
|
+
// 1. ./agent-daemon.mjs — canonical filename (1.9.4+)
|
|
237
|
+
// 2. ./<firstName>-daemon.mjs — legacy per-agent rename (pre-1.9.4)
|
|
238
|
+
// 3. The first scripts/daemon/*-daemon.mjs that isn't this file (catches any
|
|
239
|
+
// other legacy-named core daemon still on disk in an old agent repo)
|
|
240
|
+
function resolveCoreDaemon() {
|
|
241
|
+
const localCandidates = [
|
|
242
|
+
resolve(__dirname, "agent-daemon.mjs"),
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const agentJson = join(AGENT_DIR, "config", "agent.json");
|
|
247
|
+
if (existsSync(agentJson)) {
|
|
248
|
+
const { firstName } = JSON.parse(readFileSync(agentJson, "utf-8"));
|
|
249
|
+
if (firstName && typeof firstName === "string") {
|
|
250
|
+
const lower = firstName.toLowerCase();
|
|
251
|
+
localCandidates.push(resolve(__dirname, `${lower}-daemon.mjs`));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch { /* identity unavailable — fall through */ }
|
|
255
|
+
|
|
256
|
+
for (const p of localCandidates) if (existsSync(p)) return p;
|
|
257
|
+
|
|
258
|
+
// Final fallback: scan the directory for *-daemon.mjs (excluding self).
|
|
259
|
+
try {
|
|
260
|
+
for (const name of readdirSync(__dirname)) {
|
|
261
|
+
if (name === "maestro-daemon.mjs") continue;
|
|
262
|
+
if (name.endsWith("-daemon.mjs")) return resolve(__dirname, name);
|
|
263
|
+
}
|
|
264
|
+
} catch { /* */ }
|
|
265
|
+
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const coreDaemon = resolveCoreDaemon();
|
|
270
|
+
if (!coreDaemon) {
|
|
271
|
+
console.error("[DAEMON] could not locate a core daemon module under scripts/daemon/. Expected agent-daemon.mjs (canonical) or a legacy <firstName>-daemon.mjs.");
|
|
272
|
+
process.exit(78);
|
|
273
|
+
}
|
|
274
|
+
// Import and run the daemon (handles its own .env loading).
|
|
275
|
+
await import(coreDaemon);
|