@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,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/diagnostics/alerts.mjs — behavioral alert rules over the diagnostics spine.
|
|
3
|
+
*
|
|
4
|
+
* The alerting half of gaps-enterprise-ops G14. The audit's load-bearing
|
|
5
|
+
* insight: "Alert on behavioral PATTERNS, not single failures: agents fail
|
|
6
|
+
* silently by completing with wrong output." A single guard fail-open, one
|
|
7
|
+
* refusal, one expensive session — none of those is an incident. A *spike* of
|
|
8
|
+
* refusals, a guard that has fallen open dozens of times today, a cost-per-session
|
|
9
|
+
* p99 blowing past its ceiling, an agent that stopped heartbeating, a daemon
|
|
10
|
+
* whose health file went cold — those are. This module evaluates that taxonomy
|
|
11
|
+
* over the already-collected signals and hands the firing alerts to an INJECTED
|
|
12
|
+
* sink (a webhook / Alertmanager poster) — it never makes a live call itself, so
|
|
13
|
+
* it stays pure, testable, and impossible to wedge on a network hang.
|
|
14
|
+
*
|
|
15
|
+
* Inputs (all injected; nothing is read from the network here):
|
|
16
|
+
* - snapshot the diagnostics counters snapshot (lib/diagnostics/counters.mjs
|
|
17
|
+
* `snapshot()`): `{ "<counter>": <total-today>, ... }`. Refusal +
|
|
18
|
+
* guard fail-open spikes are read from here.
|
|
19
|
+
* - ledger today's cost rollup (lib/model-router/ledger.mjs `sumLedgerToday`
|
|
20
|
+
* shape `{ spentUSD, sessions, byModel }`) PLUS an optional
|
|
21
|
+
* `perSessionUSD: number[]` for the p99 rule. When only the rollup
|
|
22
|
+
* is present we fall back to mean-per-session.
|
|
23
|
+
* - presence optional liveness facts: `{ heartbeatAgeSec, daemonHealthAgeSec }`
|
|
24
|
+
* (or `daemonDown:true`). Absent-agent staleness + daemon-down read
|
|
25
|
+
* from here. Caller derives the ages from presence.mjs / readHealth.
|
|
26
|
+
* - thresholds per-rule knobs (see DEFAULT_THRESHOLDS); operator overrides
|
|
27
|
+
* from config/alerts.yaml via `loadThresholds`.
|
|
28
|
+
* - now clock in epoch ms (default Date.now()).
|
|
29
|
+
*
|
|
30
|
+
* Output: a stable, deterministic array `[{ id, severity, detail }, ...]` sorted
|
|
31
|
+
* by severity then id, so the same inputs always produce the same alert list (a
|
|
32
|
+
* de-dupe upstream can key on `id`). Severity is one of "warning" | "critical".
|
|
33
|
+
*
|
|
34
|
+
* Invariant: NEITHER `evaluateAlerts` NOR `emitAlerts` EVER throws. A bad
|
|
35
|
+
* snapshot, a missing ledger, a sink that rejects/throws — all degrade to a
|
|
36
|
+
* return value, never an exception. An alerting path that can crash the daemon it
|
|
37
|
+
* watches is worse than no alerting at all (the same fail-open contract as
|
|
38
|
+
* counters.mjs / events.mjs). emitAlerts is best-effort delivery: it reports
|
|
39
|
+
* per-alert success and swallows sink failures.
|
|
40
|
+
*
|
|
41
|
+
* Constraints (CLAUDE.md): ESM, Node built-ins + js-yaml (lazy, optional) only,
|
|
42
|
+
* everything injectable so tests are hermetic and make no live calls.
|
|
43
|
+
*
|
|
44
|
+
* @module lib/diagnostics/alerts
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
48
|
+
import { join, resolve } from "node:path";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Built-in alert thresholds. Each rule is documented inline. These mirror
|
|
52
|
+
* config/alerts.yaml; an operator override merges over these (loadThresholds).
|
|
53
|
+
* Tuned to be quiet by default — alert on a sustained pattern, not noise.
|
|
54
|
+
* @type {Readonly<object>}
|
|
55
|
+
*/
|
|
56
|
+
export const DEFAULT_THRESHOLDS = Object.freeze({
|
|
57
|
+
refusalSpike: Object.freeze({
|
|
58
|
+
// Counter name(s) that count refusals (a guard or model declined to act).
|
|
59
|
+
// Summed across the listed counters for the day.
|
|
60
|
+
counters: Object.freeze(["refusal", "policy.refusal", "classifier.refusal"]),
|
|
61
|
+
warnCount: 10, // warn at this many refusals today
|
|
62
|
+
critCount: 25, // critical at this many
|
|
63
|
+
}),
|
|
64
|
+
guardFailSpike: Object.freeze({
|
|
65
|
+
// Any counter ending ".fail_open" / ".persist_failed" / ".degraded" is a
|
|
66
|
+
// guard that fell open silently. Named counters are summed; the suffix match
|
|
67
|
+
// catches new guards without a config change. "guard.fail" is the name the
|
|
68
|
+
// live daemon onGuardFail hook-bus subscriber bumps (diag-pipeline wiring),
|
|
69
|
+
// so it is listed explicitly to keep the wired signal and the rule aligned.
|
|
70
|
+
counters: Object.freeze(["guard.fail"]),
|
|
71
|
+
suffixes: Object.freeze([".fail_open", ".persist_failed", ".degraded"]),
|
|
72
|
+
warnCount: 5,
|
|
73
|
+
critCount: 20,
|
|
74
|
+
}),
|
|
75
|
+
costPerSessionP99: Object.freeze({
|
|
76
|
+
// Cost-per-session p99 (USD). Falls back to mean-per-session when no
|
|
77
|
+
// per-session distribution is supplied.
|
|
78
|
+
warnUSD: 1.5,
|
|
79
|
+
critUSD: 3.0,
|
|
80
|
+
minSessions: 5, // don't fire on a tiny sample
|
|
81
|
+
}),
|
|
82
|
+
absentAgent: Object.freeze({
|
|
83
|
+
// No session/agent heartbeat for this long → the agent is silently absent.
|
|
84
|
+
warnSec: 600, // 10 min
|
|
85
|
+
critSec: 1800, // 30 min
|
|
86
|
+
}),
|
|
87
|
+
daemonDown: Object.freeze({
|
|
88
|
+
// The consumer's health.json heartbeat (state/cadence-bus/health.json) went
|
|
89
|
+
// cold. The daemon is the sole cadence orchestrator — a cold heartbeat is a
|
|
90
|
+
// dark agent.
|
|
91
|
+
warnSec: 120, // 2 min (heartbeat refreshes ~15s)
|
|
92
|
+
critSec: 300, // 5 min
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const SEVERITY_RANK = { critical: 0, warning: 1 };
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Config
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve config/alerts.yaml for the agent (explicit path wins for tests).
|
|
104
|
+
* @param {object} cfg
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
function alertsConfigPath(cfg = {}) {
|
|
108
|
+
if (cfg.configPath) return resolve(cfg.configPath);
|
|
109
|
+
const root = resolve(
|
|
110
|
+
cfg.agentRoot ||
|
|
111
|
+
process.env.AGENT_ROOT ||
|
|
112
|
+
process.env.AGENT_DIR ||
|
|
113
|
+
process.cwd()
|
|
114
|
+
);
|
|
115
|
+
return join(root, "config", "alerts.yaml");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Deep-clone a JSON-safe value (the frozen defaults must not leak by reference). */
|
|
119
|
+
function clone(v) {
|
|
120
|
+
return JSON.parse(JSON.stringify(v));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Recursively merge `override` onto `base` (plain objects deep-merge; arrays and
|
|
125
|
+
* scalars are last-wins). Returns a new object; inputs are not mutated. Mirrors
|
|
126
|
+
* lib/collective/config.mjs `mergeConfig`.
|
|
127
|
+
*/
|
|
128
|
+
export function mergeThresholds(base, override) {
|
|
129
|
+
if (override == null) return clone(base);
|
|
130
|
+
if (Array.isArray(base) || Array.isArray(override)) return clone(override);
|
|
131
|
+
if (typeof base !== "object" || typeof override !== "object") return clone(override);
|
|
132
|
+
const out = clone(base);
|
|
133
|
+
for (const k of Object.keys(override)) {
|
|
134
|
+
const b = out[k];
|
|
135
|
+
const o = override[k];
|
|
136
|
+
if (b && o && typeof b === "object" && typeof o === "object" && !Array.isArray(b) && !Array.isArray(o)) {
|
|
137
|
+
out[k] = mergeThresholds(b, o);
|
|
138
|
+
} else if (o !== undefined) {
|
|
139
|
+
out[k] = clone(o);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Load alert thresholds: DEFAULT_THRESHOLDS merged with config/alerts.yaml when
|
|
147
|
+
* present. NEVER throws — a missing/malformed file yields the defaults. js-yaml
|
|
148
|
+
* is loaded lazily (already a maestro dep) so the module imports cleanly even if
|
|
149
|
+
* yaml is unavailable.
|
|
150
|
+
*
|
|
151
|
+
* @param {object} [cfg] { agentRoot?, configPath? }
|
|
152
|
+
* @returns {Promise<object>} merged thresholds
|
|
153
|
+
*/
|
|
154
|
+
export async function loadThresholds(cfg = {}) {
|
|
155
|
+
const defaults = clone(DEFAULT_THRESHOLDS);
|
|
156
|
+
try {
|
|
157
|
+
const p = alertsConfigPath(cfg);
|
|
158
|
+
if (!existsSync(p)) return defaults;
|
|
159
|
+
const body = readFileSync(p, "utf-8");
|
|
160
|
+
const mod = await import("js-yaml");
|
|
161
|
+
const yaml = mod.default || mod;
|
|
162
|
+
const raw = yaml.load(body);
|
|
163
|
+
if (!raw || typeof raw !== "object") return defaults;
|
|
164
|
+
// Operators write knobs under an `alerts:` key (matching the file name) or at
|
|
165
|
+
// top level; accept either so the file is forgiving.
|
|
166
|
+
const override = raw.alerts && typeof raw.alerts === "object" ? raw.alerts : raw;
|
|
167
|
+
return mergeThresholds(defaults, override);
|
|
168
|
+
} catch {
|
|
169
|
+
return defaults;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Rule helpers
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
function num(v, fallback = 0) {
|
|
178
|
+
const n = Number(v);
|
|
179
|
+
return Number.isFinite(n) ? n : fallback;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Sum the listed counters from a snapshot (missing counters contribute 0). */
|
|
183
|
+
function sumCounters(snapshot, names) {
|
|
184
|
+
if (!snapshot || typeof snapshot !== "object") return 0;
|
|
185
|
+
if (!Array.isArray(names)) return 0;
|
|
186
|
+
let total = 0;
|
|
187
|
+
for (const name of names) {
|
|
188
|
+
if (typeof name !== "string") continue;
|
|
189
|
+
total += num(snapshot[name]);
|
|
190
|
+
}
|
|
191
|
+
return total;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Sum every snapshot counter whose name ends with one of `suffixes`. */
|
|
195
|
+
function sumBySuffix(snapshot, suffixes) {
|
|
196
|
+
if (!snapshot || typeof snapshot !== "object") return 0;
|
|
197
|
+
if (!Array.isArray(suffixes) || suffixes.length === 0) return 0;
|
|
198
|
+
let total = 0;
|
|
199
|
+
for (const [name, val] of Object.entries(snapshot)) {
|
|
200
|
+
if (typeof name !== "string") continue;
|
|
201
|
+
if (suffixes.some((s) => typeof s === "string" && s && name.endsWith(s))) {
|
|
202
|
+
total += num(val);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return total;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Nearest-rank p99 of a numeric sample. Returns null on an empty/invalid sample.
|
|
210
|
+
* Pure; tolerates non-numeric entries (they're dropped).
|
|
211
|
+
*/
|
|
212
|
+
function p99(values) {
|
|
213
|
+
if (!Array.isArray(values)) return null;
|
|
214
|
+
const nums = values.map((v) => Number(v)).filter((n) => Number.isFinite(n));
|
|
215
|
+
if (nums.length === 0) return null;
|
|
216
|
+
nums.sort((a, b) => a - b);
|
|
217
|
+
// Nearest-rank: ceil(p/100 * N), 1-indexed.
|
|
218
|
+
const rank = Math.max(1, Math.ceil(0.99 * nums.length));
|
|
219
|
+
return nums[rank - 1];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Map a value to a severity given warn/crit thresholds (higher = worse). */
|
|
223
|
+
function severityFor(value, warn, crit) {
|
|
224
|
+
if (Number.isFinite(crit) && value >= crit) return "critical";
|
|
225
|
+
if (Number.isFinite(warn) && value >= warn) return "warning";
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function round2(n) {
|
|
230
|
+
return Math.round(n * 100) / 100;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// evaluateAlerts
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Evaluate the behavioral alert rules and return the firing alerts.
|
|
239
|
+
*
|
|
240
|
+
* @param {object} [args]
|
|
241
|
+
* @param {object} [args.snapshot] diagnostics counters snapshot (today's totals)
|
|
242
|
+
* @param {object} [args.ledger] cost rollup `{ spentUSD, sessions, byModel }`
|
|
243
|
+
* optionally with `perSessionUSD: number[]`
|
|
244
|
+
* @param {object} [args.presence] `{ heartbeatAgeSec?, daemonHealthAgeSec?,
|
|
245
|
+
* daemonDown? }`
|
|
246
|
+
* @param {object} [args.thresholds] merged thresholds (default DEFAULT_THRESHOLDS)
|
|
247
|
+
* @param {number} [args.now] clock in epoch ms (reserved; rules are
|
|
248
|
+
* age-based via injected `*AgeSec`)
|
|
249
|
+
* @returns {Array<{id:string, severity:"warning"|"critical", detail:string}>}
|
|
250
|
+
*/
|
|
251
|
+
export function evaluateAlerts(args = {}) {
|
|
252
|
+
const alerts = [];
|
|
253
|
+
try {
|
|
254
|
+
const snapshot = args.snapshot && typeof args.snapshot === "object" ? args.snapshot : {};
|
|
255
|
+
const ledger = args.ledger && typeof args.ledger === "object" ? args.ledger : {};
|
|
256
|
+
const presence = args.presence && typeof args.presence === "object" ? args.presence : {};
|
|
257
|
+
const t = mergeThresholds(DEFAULT_THRESHOLDS, args.thresholds || {});
|
|
258
|
+
|
|
259
|
+
push(alerts, refusalSpikeRule(snapshot, t.refusalSpike));
|
|
260
|
+
push(alerts, guardFailSpikeRule(snapshot, t.guardFailSpike));
|
|
261
|
+
push(alerts, costPerSessionRule(ledger, t.costPerSessionP99));
|
|
262
|
+
push(alerts, absentAgentRule(presence, t.absentAgent));
|
|
263
|
+
push(alerts, daemonDownRule(presence, t.daemonDown));
|
|
264
|
+
|
|
265
|
+
// Deterministic order: critical first, then by id, so identical inputs always
|
|
266
|
+
// produce identical output (a de-dupe upstream can key on id).
|
|
267
|
+
alerts.sort((a, b) => {
|
|
268
|
+
const s = (SEVERITY_RANK[a.severity] ?? 9) - (SEVERITY_RANK[b.severity] ?? 9);
|
|
269
|
+
return s !== 0 ? s : a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
270
|
+
});
|
|
271
|
+
return alerts;
|
|
272
|
+
} catch {
|
|
273
|
+
// Never throw: whatever we accumulated before the failure is still valid.
|
|
274
|
+
return alerts;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function push(arr, alert) {
|
|
279
|
+
if (alert) arr.push(alert);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function refusalSpikeRule(snapshot, cfg = {}) {
|
|
283
|
+
const count = sumCounters(snapshot, cfg.counters);
|
|
284
|
+
const sev = severityFor(count, num(cfg.warnCount, Infinity), num(cfg.critCount, Infinity));
|
|
285
|
+
if (!sev) return null;
|
|
286
|
+
return {
|
|
287
|
+
id: "refusal_spike",
|
|
288
|
+
severity: sev,
|
|
289
|
+
detail: `${count} refusals today (warn ${num(cfg.warnCount)}, crit ${num(cfg.critCount)}) — behavioral spike, not a single decline.`,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function guardFailSpikeRule(snapshot, cfg = {}) {
|
|
294
|
+
const named = sumCounters(snapshot, cfg.counters);
|
|
295
|
+
const bySuffix = sumBySuffix(snapshot, cfg.suffixes);
|
|
296
|
+
const count = named + bySuffix;
|
|
297
|
+
const sev = severityFor(count, num(cfg.warnCount, Infinity), num(cfg.critCount, Infinity));
|
|
298
|
+
if (!sev) return null;
|
|
299
|
+
return {
|
|
300
|
+
id: "guard_fail_spike",
|
|
301
|
+
severity: sev,
|
|
302
|
+
detail: `${count} guard fail-opens today (warn ${num(cfg.warnCount)}, crit ${num(cfg.critCount)}) — a guard is silently degrading, not erroring.`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function costPerSessionRule(ledger, cfg = {}) {
|
|
307
|
+
const sessions = num(ledger.sessions);
|
|
308
|
+
const minSessions = num(cfg.minSessions, 0);
|
|
309
|
+
if (sessions < Math.max(1, minSessions)) return null; // sample too small / no work
|
|
310
|
+
|
|
311
|
+
// Prefer a real per-session distribution for the p99; fall back to the mean
|
|
312
|
+
// when only the rollup is available (still a useful "spend per session" signal).
|
|
313
|
+
let value;
|
|
314
|
+
let basis;
|
|
315
|
+
const dist = Array.isArray(ledger.perSessionUSD) ? ledger.perSessionUSD : null;
|
|
316
|
+
const p = dist ? p99(dist) : null;
|
|
317
|
+
if (p != null) {
|
|
318
|
+
value = p;
|
|
319
|
+
basis = "p99";
|
|
320
|
+
} else {
|
|
321
|
+
value = num(ledger.spentUSD) / sessions;
|
|
322
|
+
basis = "mean";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const sev = severityFor(value, num(cfg.warnUSD, Infinity), num(cfg.critUSD, Infinity));
|
|
326
|
+
if (!sev) return null;
|
|
327
|
+
return {
|
|
328
|
+
id: "cost_per_session_p99",
|
|
329
|
+
severity: sev,
|
|
330
|
+
detail: `cost-per-session ${basis} $${round2(value)} over ${sessions} sessions (warn $${num(cfg.warnUSD)}, crit $${num(cfg.critUSD)}).`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function absentAgentRule(presence, cfg = {}) {
|
|
335
|
+
// Only fire when we actually have a heartbeat age to judge — a missing signal
|
|
336
|
+
// is not "absent" here (daemonDownRule covers the cold-daemon case).
|
|
337
|
+
if (!Number.isFinite(presence.heartbeatAgeSec)) return null;
|
|
338
|
+
const age = presence.heartbeatAgeSec;
|
|
339
|
+
const sev = severityFor(age, num(cfg.warnSec, Infinity), num(cfg.critSec, Infinity));
|
|
340
|
+
if (!sev) return null;
|
|
341
|
+
return {
|
|
342
|
+
id: "absent_agent",
|
|
343
|
+
severity: sev,
|
|
344
|
+
detail: `no agent heartbeat for ${Math.round(age)}s (warn ${num(cfg.warnSec)}s, crit ${num(cfg.critSec)}s) — agent silently absent.`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function daemonDownRule(presence, cfg = {}) {
|
|
349
|
+
// Explicit daemonDown flag is the unambiguous signal (no health file at all).
|
|
350
|
+
if (presence.daemonDown === true) {
|
|
351
|
+
return {
|
|
352
|
+
id: "daemon_down",
|
|
353
|
+
severity: "critical",
|
|
354
|
+
detail: "daemon health heartbeat absent — the cadence orchestrator is down (no health.json).",
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (!Number.isFinite(presence.daemonHealthAgeSec)) return null;
|
|
358
|
+
const age = presence.daemonHealthAgeSec;
|
|
359
|
+
const sev = severityFor(age, num(cfg.warnSec, Infinity), num(cfg.critSec, Infinity));
|
|
360
|
+
if (!sev) return null;
|
|
361
|
+
return {
|
|
362
|
+
id: "daemon_down",
|
|
363
|
+
severity: sev,
|
|
364
|
+
detail: `daemon health heartbeat ${Math.round(age)}s stale (warn ${num(cfg.warnSec)}s, crit ${num(cfg.critSec)}s) — cadence orchestrator may be wedged.`,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
// emitAlerts
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Deliver alerts to an INJECTED sink. The sink is a function
|
|
374
|
+
* `(alert) => boolean | Promise<boolean>` — typically a webhook / Alertmanager
|
|
375
|
+
* poster supplied by the caller; this module never makes a live network call, so
|
|
376
|
+
* tests pass a spy and CI never reaches the wire.
|
|
377
|
+
*
|
|
378
|
+
* Best-effort and never-throwing: each alert is delivered independently; a sink
|
|
379
|
+
* that throws or rejects for one alert counts as a failure for that alert and
|
|
380
|
+
* does not abort the rest. Returns a summary `{ attempted, delivered, failed,
|
|
381
|
+
* results }` so the caller (doctor, a cadence handler) can log delivery health.
|
|
382
|
+
*
|
|
383
|
+
* @param {Array<object>} alerts the alerts from evaluateAlerts
|
|
384
|
+
* @param {object} [opts]
|
|
385
|
+
* @param {(alert:object) => boolean|Promise<boolean>} [opts.sink] delivery fn
|
|
386
|
+
* @returns {Promise<{attempted:number, delivered:number, failed:number,
|
|
387
|
+
* results: Array<{id:string, ok:boolean}>}>}
|
|
388
|
+
*/
|
|
389
|
+
export async function emitAlerts(alerts, opts = {}) {
|
|
390
|
+
const summary = { attempted: 0, delivered: 0, failed: 0, results: [] };
|
|
391
|
+
try {
|
|
392
|
+
const list = Array.isArray(alerts) ? alerts : [];
|
|
393
|
+
const sink = typeof opts.sink === "function" ? opts.sink : null;
|
|
394
|
+
for (const alert of list) {
|
|
395
|
+
if (!alert || typeof alert !== "object") continue;
|
|
396
|
+
summary.attempted += 1;
|
|
397
|
+
const id = typeof alert.id === "string" ? alert.id : "unknown";
|
|
398
|
+
if (!sink) {
|
|
399
|
+
// No sink wired: count as a failed delivery so the caller can tell the
|
|
400
|
+
// alert was raised but went nowhere (a misconfiguration, not silence).
|
|
401
|
+
summary.failed += 1;
|
|
402
|
+
summary.results.push({ id, ok: false });
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
let ok = false;
|
|
406
|
+
try {
|
|
407
|
+
const r = await sink(alert);
|
|
408
|
+
ok = r !== false; // a sink that returns undefined/true is a success
|
|
409
|
+
} catch {
|
|
410
|
+
ok = false; // a throwing/rejecting sink is a per-alert failure, not fatal
|
|
411
|
+
}
|
|
412
|
+
if (ok) summary.delivered += 1;
|
|
413
|
+
else summary.failed += 1;
|
|
414
|
+
summary.results.push({ id, ok });
|
|
415
|
+
}
|
|
416
|
+
return summary;
|
|
417
|
+
} catch {
|
|
418
|
+
return summary;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export const _internals = { p99, sumCounters, sumBySuffix, severityFor };
|
|
423
|
+
|
|
424
|
+
export default { evaluateAlerts, emitAlerts, loadThresholds, mergeThresholds, DEFAULT_THRESHOLDS };
|