@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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check.test.mjs — node:test coverage for the maestro CI guard scripts.
|
|
3
|
+
*
|
|
4
|
+
* Exercises the pure helpers (so logic is verified deterministically without
|
|
5
|
+
* depending on repo state) and asserts the aggregator + each guard return 0
|
|
6
|
+
* on the current, known-clean repo.
|
|
7
|
+
*
|
|
8
|
+
* Run: `node --test scripts/ci/check.test.mjs`
|
|
9
|
+
*
|
|
10
|
+
* @module scripts/ci/check.test
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
"use strict";
|
|
14
|
+
|
|
15
|
+
import { test } from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { mkdtemp, writeFile, mkdir, rm } from "node:fs/promises";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
lineHasConflictMarker,
|
|
23
|
+
scanContentForMarkers,
|
|
24
|
+
findConflictMarkers,
|
|
25
|
+
run as runConflictMarkers,
|
|
26
|
+
} from "./check-no-conflict-markers.mjs";
|
|
27
|
+
import {
|
|
28
|
+
collectExportTargets,
|
|
29
|
+
flattenExports,
|
|
30
|
+
checkExportsExist,
|
|
31
|
+
run as runExportsExist,
|
|
32
|
+
} from "./check-exports-exist.mjs";
|
|
33
|
+
import {
|
|
34
|
+
isGlob,
|
|
35
|
+
checkFilesExist,
|
|
36
|
+
run as runFilesExist,
|
|
37
|
+
} from "./check-files-exist.mjs";
|
|
38
|
+
import {
|
|
39
|
+
scanText,
|
|
40
|
+
run as runNoConfidential,
|
|
41
|
+
} from "./check-no-confidential.mjs";
|
|
42
|
+
import { runAll, run as runAggregator, CHECKS } from "./check.mjs";
|
|
43
|
+
|
|
44
|
+
// --- conflict markers: pure line matcher ---
|
|
45
|
+
|
|
46
|
+
test("lineHasConflictMarker matches the four marker kinds", () => {
|
|
47
|
+
assert.equal(lineHasConflictMarker("<<<<<<< HEAD"), true);
|
|
48
|
+
assert.equal(lineHasConflictMarker("======="), true);
|
|
49
|
+
assert.equal(lineHasConflictMarker(">>>>>>> feature/x"), true);
|
|
50
|
+
assert.equal(lineHasConflictMarker("||||||| merged common ancestors"), true);
|
|
51
|
+
assert.equal(lineHasConflictMarker("======= \r".replace(" \r", "\r")), true); // CRLF bare marker
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("lineHasConflictMarker ignores look-alikes", () => {
|
|
55
|
+
assert.equal(lineHasConflictMarker("======== eight equals is a markdown rule"), false);
|
|
56
|
+
assert.equal(lineHasConflictMarker("===="), false);
|
|
57
|
+
assert.equal(lineHasConflictMarker("some =======text inline"), false);
|
|
58
|
+
assert.equal(lineHasConflictMarker(">>>>>>>>"), false);
|
|
59
|
+
assert.equal(lineHasConflictMarker(""), false);
|
|
60
|
+
assert.equal(lineHasConflictMarker(42), false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("scanContentForMarkers reports 1-based line numbers", () => {
|
|
64
|
+
const content = ["ok line", "<<<<<<< HEAD", "mine", "=======", "theirs", ">>>>>>> branch"].join("\n");
|
|
65
|
+
const hits = scanContentForMarkers(content);
|
|
66
|
+
assert.deepEqual(hits.map((h) => h.line), [2, 4, 6]);
|
|
67
|
+
assert.equal(hits[0].text, "<<<<<<< HEAD");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("scanContentForMarkers clean content yields nothing", () => {
|
|
71
|
+
assert.deepEqual(scanContentForMarkers("nothing\nto\nsee\n"), []);
|
|
72
|
+
assert.deepEqual(scanContentForMarkers(""), []);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// --- exports flattening ---
|
|
76
|
+
|
|
77
|
+
test("collectExportTargets handles string, array, nested conditions, and null", () => {
|
|
78
|
+
assert.deepEqual(collectExportTargets("./a.js", "./a"), [{ subpath: "./a", target: "./a.js" }]);
|
|
79
|
+
assert.deepEqual(collectExportTargets(null, "./blocked"), []);
|
|
80
|
+
const nested = { import: "./x.mjs", require: { default: "./x.cjs" } };
|
|
81
|
+
const got = collectExportTargets(nested, "./x").map((t) => t.target).sort();
|
|
82
|
+
assert.deepEqual(got, ["./x.cjs", "./x.mjs"]);
|
|
83
|
+
const arr = ["./first.js", { import: "./second.mjs" }];
|
|
84
|
+
const gotArr = collectExportTargets(arr, "./y").map((t) => t.target).sort();
|
|
85
|
+
assert.deepEqual(gotArr, ["./first.js", "./second.mjs"]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("flattenExports handles sugar string form and map form", () => {
|
|
89
|
+
assert.deepEqual(flattenExports("./index.js"), [{ subpath: ".", target: "./index.js" }]);
|
|
90
|
+
assert.deepEqual(flattenExports(undefined), []);
|
|
91
|
+
const pairs = flattenExports({ ".": "./i.js", "./t": "./t.js" });
|
|
92
|
+
assert.deepEqual(pairs.map((p) => p.target).sort(), ["./i.js", "./t.js"]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// --- exports/files existence against a synthetic fixture repo ---
|
|
96
|
+
|
|
97
|
+
async function makeFixtureRepo() {
|
|
98
|
+
const dir = await mkdtemp(path.join(tmpdir(), "maestro-ci-"));
|
|
99
|
+
await mkdir(path.join(dir, "lib"), { recursive: true });
|
|
100
|
+
await writeFile(path.join(dir, "lib", "real.js"), "export const x = 1;\n");
|
|
101
|
+
await writeFile(path.join(dir, "README.md"), "# fixture\n");
|
|
102
|
+
return dir;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
test("checkExportsExist passes when targets exist, fails when missing", async () => {
|
|
106
|
+
const dir = await makeFixtureRepo();
|
|
107
|
+
try {
|
|
108
|
+
const good = await checkExportsExist({ cwd: dir, pkg: { exports: { ".": "./lib/real.js" } } });
|
|
109
|
+
assert.equal(good.ok, true);
|
|
110
|
+
assert.equal(good.checked, 1);
|
|
111
|
+
|
|
112
|
+
const bad = await checkExportsExist({ cwd: dir, pkg: { exports: { "./tts": "./lib/missing.mjs" } } });
|
|
113
|
+
assert.equal(bad.ok, false);
|
|
114
|
+
assert.equal(bad.missing.length, 1);
|
|
115
|
+
assert.equal(bad.missing[0].subpath, "./tts");
|
|
116
|
+
|
|
117
|
+
// null target is a deliberate block, not a missing file.
|
|
118
|
+
const blocked = await checkExportsExist({ cwd: dir, pkg: { exports: { "./x": null } } });
|
|
119
|
+
assert.equal(blocked.ok, true);
|
|
120
|
+
assert.equal(blocked.checked, 0);
|
|
121
|
+
} finally {
|
|
122
|
+
await rm(dir, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("isGlob detects metacharacters", () => {
|
|
127
|
+
assert.equal(isGlob("lib/"), false);
|
|
128
|
+
assert.equal(isGlob("README.md"), false);
|
|
129
|
+
assert.equal(isGlob("lib/**/*.js"), true);
|
|
130
|
+
assert.equal(isGlob("file?.js"), true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("checkFilesExist passes for present entries, fails for missing, skips globs", async () => {
|
|
134
|
+
const dir = await makeFixtureRepo();
|
|
135
|
+
try {
|
|
136
|
+
const good = await checkFilesExist({ cwd: dir, pkg: { files: ["lib/", "README.md"] } });
|
|
137
|
+
assert.equal(good.ok, true);
|
|
138
|
+
assert.equal(good.checked, 2);
|
|
139
|
+
|
|
140
|
+
const bad = await checkFilesExist({ cwd: dir, pkg: { files: ["lib/", "nope/"] } });
|
|
141
|
+
assert.equal(bad.ok, false);
|
|
142
|
+
assert.equal(bad.missing[0].entry, "nope/");
|
|
143
|
+
|
|
144
|
+
const globby = await checkFilesExist({ cwd: dir, pkg: { files: ["lib/**/*.js"] } });
|
|
145
|
+
assert.equal(globby.ok, true);
|
|
146
|
+
assert.equal(globby.skipped.length, 1);
|
|
147
|
+
assert.equal(globby.checked, 0);
|
|
148
|
+
} finally {
|
|
149
|
+
await rm(dir, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// --- confidential-marker scanner: pure text matcher ---
|
|
154
|
+
|
|
155
|
+
test("scanText flags denylisted markers and ignores clean text", () => {
|
|
156
|
+
const patterns = [
|
|
157
|
+
{ label: "target", re: /\bAcme Capital\b/i },
|
|
158
|
+
{ label: "id", re: /\bZZ9999\b/ },
|
|
159
|
+
];
|
|
160
|
+
assert.deepEqual(scanText("nothing to see here", patterns), []);
|
|
161
|
+
assert.deepEqual(scanText("", patterns), []);
|
|
162
|
+
assert.deepEqual(scanText("some text", []), []);
|
|
163
|
+
assert.deepEqual(scanText(42, patterns), []);
|
|
164
|
+
const hits = scanText("we are pursuing acme capital (ref ZZ9999)", patterns);
|
|
165
|
+
assert.deepEqual(hits.map((h) => h.label).sort(), ["id", "target"]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// --- conflict scan against a synthetic git-less dir is not possible (needs git);
|
|
169
|
+
// instead verify findConflictMarkers throws gracefully only via real repo run below.
|
|
170
|
+
|
|
171
|
+
// --- live runs against the CURRENT maestro repo: must all be green ---
|
|
172
|
+
|
|
173
|
+
test("each guard run() returns 0 on the current repo", async () => {
|
|
174
|
+
assert.equal(await runConflictMarkers(), 0);
|
|
175
|
+
assert.equal(await runExportsExist(), 0);
|
|
176
|
+
assert.equal(await runFilesExist(), 0);
|
|
177
|
+
assert.equal(await runNoConfidential(), 0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("findConflictMarkers finds nothing in the current repo", async () => {
|
|
181
|
+
const offenders = await findConflictMarkers();
|
|
182
|
+
assert.deepEqual(offenders, []);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("aggregator runAll reports all checks passed on the current repo", async () => {
|
|
186
|
+
const { ok, results } = await runAll();
|
|
187
|
+
assert.equal(ok, true);
|
|
188
|
+
assert.equal(results.length, CHECKS.length);
|
|
189
|
+
assert.ok(results.every((r) => r.passed));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("aggregator run() exits 0 on the current repo", async () => {
|
|
193
|
+
assert.equal(await runAggregator(), 0);
|
|
194
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* run-coverage.mjs — coverage variant of the maestro test runner (audit L31).
|
|
4
|
+
*
|
|
5
|
+
* Uses the SAME test discovery as scripts/ci/run-tests.mjs (every `*.test.mjs`
|
|
6
|
+
* under lib/, scripts/, and bin/), but invokes `node --test` with
|
|
7
|
+
* `--experimental-test-coverage` so contributors can see line/branch/function
|
|
8
|
+
* coverage for the suite.
|
|
9
|
+
*
|
|
10
|
+
* This is intentionally NOT wired into `npm test` or the CI publish gate: the
|
|
11
|
+
* experimental coverage reporter is comparatively slow and its output format is
|
|
12
|
+
* not yet stable across Node minors, so gating CI on it would add flakiness for
|
|
13
|
+
* no safety gain. It is a developer convenience — run it locally with
|
|
14
|
+
* `npm run test:coverage`.
|
|
15
|
+
*
|
|
16
|
+
* Node builtins only (maestro is "type":"module", near-zero-dep by design).
|
|
17
|
+
* Requires Node 20+ (already enforced via package.json engines), which is where
|
|
18
|
+
* `--experimental-test-coverage` is available.
|
|
19
|
+
*
|
|
20
|
+
* Usage: `node scripts/ci/run-coverage.mjs`
|
|
21
|
+
* exit 0 → all tests passed; non-zero → at least one failed (or none found).
|
|
22
|
+
*
|
|
23
|
+
* @module scripts/ci/run-coverage
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readdirSync } from "node:fs";
|
|
27
|
+
import { join, dirname } from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
import { spawnSync } from "node:child_process";
|
|
30
|
+
|
|
31
|
+
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
32
|
+
const ROOTS = ["lib", "scripts", "bin"];
|
|
33
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", "state", "logs", "audit"]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Recursively collect `*.test.mjs` files under `dir` (absolute). Mirrors the
|
|
37
|
+
* discovery in run-tests.mjs so coverage runs over the exact same set.
|
|
38
|
+
* @param {string} dir
|
|
39
|
+
* @returns {string[]}
|
|
40
|
+
*/
|
|
41
|
+
function findTests(dir) {
|
|
42
|
+
let out = [];
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
46
|
+
} catch {
|
|
47
|
+
return out; // missing root dir is fine
|
|
48
|
+
}
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
52
|
+
out = out.concat(findTests(join(dir, entry.name)));
|
|
53
|
+
} else if (entry.isFile() && entry.name.endsWith(".test.mjs")) {
|
|
54
|
+
out.push(join(dir, entry.name));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const files = ROOTS.flatMap((r) => findTests(join(REPO_ROOT, r))).sort();
|
|
61
|
+
|
|
62
|
+
if (files.length === 0) {
|
|
63
|
+
console.error("run-coverage: no *.test.mjs files found — refusing to report success.");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`run-coverage: discovered ${files.length} test file(s).`);
|
|
68
|
+
|
|
69
|
+
const result = spawnSync(
|
|
70
|
+
process.execPath,
|
|
71
|
+
["--test", "--experimental-test-coverage", ...files],
|
|
72
|
+
{
|
|
73
|
+
cwd: REPO_ROOT,
|
|
74
|
+
stdio: "inherit",
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (result.error) {
|
|
79
|
+
console.error("run-coverage: failed to launch node --test:", result.error.message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* run-tests.mjs — discovery test runner for maestro.
|
|
4
|
+
*
|
|
5
|
+
* Recursively finds every `*.test.mjs` under lib/, scripts/, and bin/ and runs
|
|
6
|
+
* them with `node --test`. Replaces the previous hand-enumerated file list in
|
|
7
|
+
* package.json, which silently excluded any new test until it was manually
|
|
8
|
+
* appended (audit L25). A renamed/deleted file can no longer error the whole
|
|
9
|
+
* invocation, and a newly added test is picked up automatically.
|
|
10
|
+
*
|
|
11
|
+
* Node builtins only (maestro is "type":"module", near-zero-dep by design).
|
|
12
|
+
*
|
|
13
|
+
* Usage: `node scripts/ci/run-tests.mjs`
|
|
14
|
+
* exit 0 → all tests passed; non-zero → at least one failed (or none found).
|
|
15
|
+
*
|
|
16
|
+
* @module scripts/ci/run-tests
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readdirSync } from "node:fs";
|
|
20
|
+
import { join, dirname } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
23
|
+
|
|
24
|
+
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
25
|
+
const ROOTS = ["lib", "scripts", "bin", "services"];
|
|
26
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", "state", "logs", "audit"]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Recursively collect `*.test.mjs` files under `dir` (absolute), returning
|
|
30
|
+
* paths relative to REPO_ROOT for stable, readable output.
|
|
31
|
+
* @param {string} dir
|
|
32
|
+
* @returns {string[]}
|
|
33
|
+
*/
|
|
34
|
+
function findTests(dir) {
|
|
35
|
+
let out = [];
|
|
36
|
+
let entries;
|
|
37
|
+
try {
|
|
38
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
39
|
+
} catch {
|
|
40
|
+
return out; // missing root dir is fine
|
|
41
|
+
}
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (entry.isDirectory()) {
|
|
44
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
45
|
+
out = out.concat(findTests(join(dir, entry.name)));
|
|
46
|
+
} else if (entry.isFile() && entry.name.endsWith(".test.mjs")) {
|
|
47
|
+
out.push(join(dir, entry.name));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const files = ROOTS.flatMap((r) => findTests(join(REPO_ROOT, r))).sort();
|
|
54
|
+
|
|
55
|
+
if (files.length === 0) {
|
|
56
|
+
console.error("run-tests: no *.test.mjs files found — refusing to report success.");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`run-tests: discovered ${files.length} test file(s).`);
|
|
61
|
+
|
|
62
|
+
const result = spawnSync(process.execPath, ["--test", ...files], {
|
|
63
|
+
cwd: REPO_ROOT,
|
|
64
|
+
stdio: "inherit",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (result.error) {
|
|
68
|
+
console.error("run-tests: failed to launch node --test:", result.error.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Cloud Relay — Slack Events to Mac mini
|
|
2
|
+
|
|
3
|
+
Public HTTPS service that proxies Slack Events API webhooks into a
|
|
4
|
+
Mac-mini-resident maestro daemon over Server-Sent Events.
|
|
5
|
+
|
|
6
|
+
## Why
|
|
7
|
+
|
|
8
|
+
Slack Socket Mode only delivers events for the **bot user** that owns the
|
|
9
|
+
app. DMs sent to the agent's underlying **user account** (e.g.,
|
|
10
|
+
`U099N1JE0LA` for Ravi) are invisible to Socket Mode — the bot is not a
|
|
11
|
+
member of those conversations. To get real-time push for those DMs we
|
|
12
|
+
need user-scope event subscriptions, which require an HTTPS endpoint, and
|
|
13
|
+
Mac minis don't have public IPs. This relay closes the gap.
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Slack ──HTTPS POST──▶ Railway (this relay) ──SSE──▶ Mac mini (slack-cloud-relay-client.mjs)
|
|
19
|
+
│ │
|
|
20
|
+
│ └──▶ writeInboxItem() → state/inbox/slack/
|
|
21
|
+
│
|
|
22
|
+
└── verifies x-slack-signature, fans events to all SSE clients
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Deploy
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
cd scripts/cloud-relay
|
|
29
|
+
railway init
|
|
30
|
+
railway up
|
|
31
|
+
railway variables set \
|
|
32
|
+
SLACK_SIGNING_SECRET=<from Slack app Basic Information> \
|
|
33
|
+
RELAY_SHARED_SECRET=<long random string, share with Mac mini>
|
|
34
|
+
railway domain # → returns https://<project>.up.railway.app
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then in the Slack app config:
|
|
38
|
+
1. Event Subscriptions → Enable Events → set Request URL to
|
|
39
|
+
`https://<project>.up.railway.app/slack/events`.
|
|
40
|
+
2. Subscribe to **User events** (`message.im`, `message.mpim`,
|
|
41
|
+
`message.channels`, `message.groups`, `app_mention`).
|
|
42
|
+
3. Save changes and reinstall the app.
|
|
43
|
+
|
|
44
|
+
Set the Mac mini's `.env`:
|
|
45
|
+
```
|
|
46
|
+
CLOUD_RELAY_URL=https://<project>.up.railway.app
|
|
47
|
+
RELAY_SHARED_SECRET=<same string as on Railway>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Endpoints
|
|
51
|
+
|
|
52
|
+
- `POST /slack/events` — Slack Events API. Verifies `x-slack-signature`
|
|
53
|
+
using `SLACK_SIGNING_SECRET`; responds to `url_verification` challenges;
|
|
54
|
+
fans event callbacks out to connected SSE clients.
|
|
55
|
+
- `GET /events/stream` — SSE stream. Requires
|
|
56
|
+
`Authorization: Bearer <RELAY_SHARED_SECRET>`. New connections receive
|
|
57
|
+
the last 100 events as catch-up; heartbeats every 25 s keep the stream
|
|
58
|
+
alive through proxies.
|
|
59
|
+
- `GET /health` — Liveness probe (returns connection + queue counts).
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maestro — Slack Events Cloud Relay
|
|
3
|
+
*
|
|
4
|
+
* Public HTTPS service (deployed on Railway) that bridges Slack user-scope
|
|
5
|
+
* Events API webhooks into a Mac-mini-resident maestro daemon over SSE.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists: Socket Mode is bot-scoped, so DMs sent to the agent's
|
|
8
|
+
* USER account (not its bot user) are invisible to a Socket Mode listener.
|
|
9
|
+
* To get real-time push for those DMs, the Slack app needs user-scope event
|
|
10
|
+
* subscriptions, which require an HTTPS webhook URL — and Mac minis don't
|
|
11
|
+
* have public IPs. This relay sits in front: Slack POSTs events here, the
|
|
12
|
+
* Mac mini holds an authenticated SSE connection, and events get pushed
|
|
13
|
+
* back over the open stream in milliseconds.
|
|
14
|
+
*
|
|
15
|
+
* Endpoints:
|
|
16
|
+
* POST /slack/events — Slack Events API receiver. Verifies the v0
|
|
17
|
+
* HMAC signature, handles url_verification on
|
|
18
|
+
* registration, fans event_callback envelopes
|
|
19
|
+
* out to every connected SSE client.
|
|
20
|
+
* GET /events/stream — Server-Sent Events feed for Mac-mini clients
|
|
21
|
+
* holding an open connection. Bearer-token
|
|
22
|
+
* authenticated against RELAY_SHARED_SECRET.
|
|
23
|
+
* New connections receive the last ~100 events
|
|
24
|
+
* so a brief disconnect doesn't drop messages.
|
|
25
|
+
* GET /health — Liveness probe used by Railway / external
|
|
26
|
+
* uptime monitors.
|
|
27
|
+
*
|
|
28
|
+
* Env (all required):
|
|
29
|
+
* PORT — provided by Railway
|
|
30
|
+
* SLACK_SIGNING_SECRET — from Slack app "Basic Information" page
|
|
31
|
+
* RELAY_SHARED_SECRET — operator-chosen long random string; the Mac
|
|
32
|
+
* mini client must send the same value in its
|
|
33
|
+
* Authorization: Bearer <...> header
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import http from "node:http";
|
|
37
|
+
import crypto from "node:crypto";
|
|
38
|
+
|
|
39
|
+
const PORT = parseInt(process.env.PORT || "3100", 10);
|
|
40
|
+
const SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET || "";
|
|
41
|
+
const RELAY_SHARED_SECRET = process.env.RELAY_SHARED_SECRET || "";
|
|
42
|
+
|
|
43
|
+
if (!SIGNING_SECRET) {
|
|
44
|
+
console.error("FATAL: SLACK_SIGNING_SECRET env var is required");
|
|
45
|
+
process.exit(78);
|
|
46
|
+
}
|
|
47
|
+
if (!RELAY_SHARED_SECRET) {
|
|
48
|
+
console.error("FATAL: RELAY_SHARED_SECRET env var is required");
|
|
49
|
+
process.exit(78);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Connected Mac-mini SSE clients (typically 1, but designed for fan-out).
|
|
53
|
+
const sseClients = new Set();
|
|
54
|
+
|
|
55
|
+
// Bounded ring buffer of recent events for reconnect replay. Slack-side
|
|
56
|
+
// "retry on 5xx" already gives us at-least-once delivery, so this is just
|
|
57
|
+
// for the case where the Mac mini's SSE connection drops between an event
|
|
58
|
+
// arriving and being acked: when it reconnects within ~5 minutes it gets
|
|
59
|
+
// the missing events replayed instead of waiting for Slack's next retry.
|
|
60
|
+
const REPLAY_CAPACITY = 100;
|
|
61
|
+
const eventQueue = [];
|
|
62
|
+
|
|
63
|
+
function log(level, message, data) {
|
|
64
|
+
const ts = new Date().toISOString();
|
|
65
|
+
const line = JSON.stringify({ ts, level, message, ...(data || {}) });
|
|
66
|
+
if (level === "error") console.error(line);
|
|
67
|
+
else console.log(line);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Verify Slack's v0 HMAC signature. Per Slack docs:
|
|
72
|
+
* sig_basestring = "v0:" + timestamp + ":" + raw_body
|
|
73
|
+
* expected = "v0=" + hex(hmac_sha256(SIGNING_SECRET, sig_basestring))
|
|
74
|
+
* Also enforce the 5-minute timestamp window to block replay attacks.
|
|
75
|
+
*/
|
|
76
|
+
function verifySlackSignature(timestamp, body, signature) {
|
|
77
|
+
if (!timestamp || !signature) return false;
|
|
78
|
+
const tsNum = parseInt(timestamp, 10);
|
|
79
|
+
if (!Number.isFinite(tsNum)) return false;
|
|
80
|
+
const ageSec = Math.abs(Date.now() / 1000 - tsNum);
|
|
81
|
+
if (ageSec > 5 * 60) return false;
|
|
82
|
+
const basestring = `v0:${timestamp}:${body}`;
|
|
83
|
+
const expected = `v0=${crypto.createHmac("sha256", SIGNING_SECRET).update(basestring).digest("hex")}`;
|
|
84
|
+
if (expected.length !== signature.length) return false;
|
|
85
|
+
try {
|
|
86
|
+
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function fanOut(envelope) {
|
|
93
|
+
eventQueue.push(envelope);
|
|
94
|
+
if (eventQueue.length > REPLAY_CAPACITY) eventQueue.shift();
|
|
95
|
+
const data = `data: ${JSON.stringify(envelope)}\n\n`;
|
|
96
|
+
const dead = [];
|
|
97
|
+
for (const client of sseClients) {
|
|
98
|
+
try {
|
|
99
|
+
client.write(data);
|
|
100
|
+
} catch {
|
|
101
|
+
dead.push(client);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const c of dead) sseClients.delete(c);
|
|
105
|
+
log("info", "fanned-out", { sse_clients: sseClients.size, queue_size: eventQueue.length });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const server = http.createServer((req, res) => {
|
|
109
|
+
// Health probe
|
|
110
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
111
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
112
|
+
res.end(JSON.stringify({
|
|
113
|
+
ok: true,
|
|
114
|
+
sse_clients: sseClients.size,
|
|
115
|
+
queue_size: eventQueue.length,
|
|
116
|
+
uptime_s: Math.round(process.uptime()),
|
|
117
|
+
}));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Slack Events API endpoint
|
|
122
|
+
if (req.method === "POST" && req.url === "/slack/events") {
|
|
123
|
+
let body = "";
|
|
124
|
+
req.on("data", (chunk) => { body += chunk; });
|
|
125
|
+
req.on("end", () => {
|
|
126
|
+
const ts = req.headers["x-slack-request-timestamp"];
|
|
127
|
+
const sig = req.headers["x-slack-signature"];
|
|
128
|
+
|
|
129
|
+
if (!verifySlackSignature(ts, body, sig)) {
|
|
130
|
+
log("warn", "rejected-bad-signature", { ts_header: !!ts });
|
|
131
|
+
res.writeHead(401, { "Content-Type": "text/plain" });
|
|
132
|
+
res.end("invalid signature");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let payload;
|
|
137
|
+
try {
|
|
138
|
+
payload = JSON.parse(body);
|
|
139
|
+
} catch {
|
|
140
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
141
|
+
res.end("bad json");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// One-time URL verification when the operator first registers the
|
|
146
|
+
// webhook in the Slack app config. Slack expects an echo of the
|
|
147
|
+
// `challenge` field within 3 seconds.
|
|
148
|
+
if (payload.type === "url_verification") {
|
|
149
|
+
log("info", "url-verification-challenge", { challenge_len: (payload.challenge || "").length });
|
|
150
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
151
|
+
res.end(JSON.stringify({ challenge: payload.challenge }));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Event callback. Slack requires a 200 within 3 seconds or it
|
|
156
|
+
// retries, so ack immediately and fan out after.
|
|
157
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
158
|
+
res.end("ok");
|
|
159
|
+
if (payload.type === "event_callback") {
|
|
160
|
+
fanOut(payload);
|
|
161
|
+
} else {
|
|
162
|
+
log("info", "ignored-envelope-type", { type: payload.type });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
req.on("error", (err) => {
|
|
166
|
+
log("error", "request-error", { error: err.message });
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// SSE stream to Mac mini
|
|
172
|
+
if (req.method === "GET" && req.url === "/events/stream") {
|
|
173
|
+
const auth = req.headers.authorization || "";
|
|
174
|
+
const want = `Bearer ${RELAY_SHARED_SECRET}`;
|
|
175
|
+
if (auth.length !== want.length || !crypto.timingSafeEqual(Buffer.from(auth), Buffer.from(want))) {
|
|
176
|
+
res.writeHead(401, { "Content-Type": "text/plain" });
|
|
177
|
+
res.end("unauthorized");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
res.writeHead(200, {
|
|
182
|
+
"Content-Type": "text/event-stream",
|
|
183
|
+
"Cache-Control": "no-cache, no-transform",
|
|
184
|
+
"Connection": "keep-alive",
|
|
185
|
+
"X-Accel-Buffering": "no",
|
|
186
|
+
});
|
|
187
|
+
res.write(": connected\n\n");
|
|
188
|
+
sseClients.add(res);
|
|
189
|
+
log("info", "sse-client-connected", { sse_clients: sseClients.size, remote: req.socket.remoteAddress });
|
|
190
|
+
|
|
191
|
+
// Replay the recent backlog so a brief disconnect doesn't drop events.
|
|
192
|
+
for (const ev of eventQueue) {
|
|
193
|
+
try { res.write(`data: ${JSON.stringify(ev)}\n\n`); } catch { /* */ }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Heartbeat — many proxies idle-timeout long-lived streams; a comment
|
|
197
|
+
// line every 25s keeps the connection visibly alive without polluting
|
|
198
|
+
// the event payloads.
|
|
199
|
+
const hb = setInterval(() => {
|
|
200
|
+
try {
|
|
201
|
+
res.write(": hb\n\n");
|
|
202
|
+
} catch {
|
|
203
|
+
clearInterval(hb);
|
|
204
|
+
}
|
|
205
|
+
}, 25_000);
|
|
206
|
+
|
|
207
|
+
req.on("close", () => {
|
|
208
|
+
sseClients.delete(res);
|
|
209
|
+
clearInterval(hb);
|
|
210
|
+
log("info", "sse-client-disconnected", { sse_clients: sseClients.size });
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
216
|
+
res.end("not found");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
server.listen(PORT, () => {
|
|
220
|
+
log("info", "cloud-relay-listening", { port: PORT });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Graceful shutdown on SIGTERM (Railway sends this on redeploy / scale-down).
|
|
224
|
+
const shutdown = (signal) => {
|
|
225
|
+
log("info", "shutdown-requested", { signal });
|
|
226
|
+
for (const client of sseClients) {
|
|
227
|
+
try { client.end(); } catch { /* */ }
|
|
228
|
+
}
|
|
229
|
+
server.close(() => process.exit(0));
|
|
230
|
+
setTimeout(() => process.exit(0), 5_000).unref();
|
|
231
|
+
};
|
|
232
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
233
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cohortapp/agent-sdk-cloud-relay",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Public HTTPS relay that proxies Slack Events API into a Mac-mini-resident maestro daemon over SSE.",
|
|
7
|
+
"main": "index.mjs",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "node index.mjs"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=20"
|
|
13
|
+
},
|
|
14
|
+
"license": "UNLICENSED"
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://railway.com/railway.schema.json",
|
|
3
|
+
"build": {
|
|
4
|
+
"builder": "NIXPACKS"
|
|
5
|
+
},
|
|
6
|
+
"deploy": {
|
|
7
|
+
"startCommand": "node index.mjs",
|
|
8
|
+
"healthcheckPath": "/health",
|
|
9
|
+
"healthcheckTimeout": 100,
|
|
10
|
+
"restartPolicyType": "ON_FAILURE",
|
|
11
|
+
"restartPolicyMaxRetries": 10
|
|
12
|
+
}
|
|
13
|
+
}
|