@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,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maestro — Channel contract (one type, one serialization)
|
|
3
|
+
*
|
|
4
|
+
* WS2 (Hermes integration §5) unifies every channel onto a single message
|
|
5
|
+
* type + a self-registering platform registry. This module is the SoT for:
|
|
6
|
+
*
|
|
7
|
+
* - `MessageEvent` — the reconciled, canonical inbound event. It is a
|
|
8
|
+
* superset of the legacy `ChannelMessage` (lib/channels/channel.mjs):
|
|
9
|
+
* same fields, plus `kind`, `source`, and `thread_context`. We export
|
|
10
|
+
* `ChannelMessage` as a back-compat alias so existing imports keep
|
|
11
|
+
* working unchanged.
|
|
12
|
+
* - `SessionSource` — the durable, channel-agnostic descriptor of *where*
|
|
13
|
+
* an event came from, used to reply / set typing / react without the
|
|
14
|
+
* adapter re-deriving channel-specific ids.
|
|
15
|
+
* - `SendResult` — the uniform return shape of every adapter `_send`.
|
|
16
|
+
* - `EventKind` — the closed set of inbound event kinds.
|
|
17
|
+
* - The platform registry: `definePlatform` / `getPlatform` /
|
|
18
|
+
* `listPlatforms`. Each concrete adapter file ends with a
|
|
19
|
+
* `definePlatform({ name, gateFile, factory, capabilities })` call so the
|
|
20
|
+
* daemon can discover channels by *dropping a file* (no core edit), the
|
|
21
|
+
* same pattern WS1 uses for setup sections.
|
|
22
|
+
*
|
|
23
|
+
* This module is pure data + a tiny registry — no runtime IO, no network.
|
|
24
|
+
*
|
|
25
|
+
* @module lib/channels/contract
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
"use strict";
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// EventKind — the closed set of inbound event kinds.
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {"message"|"reaction"|"channel_cc"|"voice_note"|"call"} EventKind
|
|
36
|
+
*
|
|
37
|
+
* - message a normal directed message (DM, @mention, thread reply)
|
|
38
|
+
* - reaction an emoji reaction add/remove (rarely action-required)
|
|
39
|
+
* - channel_cc a non-directed message in a watched/cc channel (observe, don't reply)
|
|
40
|
+
* - voice_note a voice note whose transcript becomes `text` (+ a voice_note attachment)
|
|
41
|
+
* - call a live phone/voice call turn
|
|
42
|
+
*/
|
|
43
|
+
export const EVENT_KINDS = Object.freeze([
|
|
44
|
+
"message",
|
|
45
|
+
"reaction",
|
|
46
|
+
"channel_cc",
|
|
47
|
+
"voice_note",
|
|
48
|
+
"call",
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
/** The default kind when an event/serialized item does not specify one. */
|
|
52
|
+
export const DEFAULT_EVENT_KIND = "message";
|
|
53
|
+
|
|
54
|
+
/** Normalise an arbitrary kind value to a known EventKind (defaults to "message"). */
|
|
55
|
+
export function normalizeKind(kind) {
|
|
56
|
+
return EVENT_KINDS.includes(kind) ? kind : DEFAULT_EVENT_KIND;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Typedefs (JSDoc only — no runtime cost).
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {Object} SessionSource
|
|
65
|
+
* @property {string} channel Channel name, e.g. "slack" | "telegram" | "whatsapp"
|
|
66
|
+
* @property {string} chatId The channel-native conversation/chat id (Slack channel id,
|
|
67
|
+
* Telegram chat id, WhatsApp jid, …). Stable per-conversation.
|
|
68
|
+
* @property {string} [threadRef] Thread / topic reference within the chat (Slack thread_ts,
|
|
69
|
+
* Telegram message_thread_id), when threaded.
|
|
70
|
+
* @property {string} [replyToRef] The specific message being replied to (for quoted replies).
|
|
71
|
+
* @property {string} userScope "dm" | "group" | "channel" — coarse audience scope, drives
|
|
72
|
+
* whether pairing (DMs) or membership+directed-gate (group)
|
|
73
|
+
* governs the event.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @typedef {Object} SendResult
|
|
78
|
+
* @property {boolean} ok Whether the send succeeded.
|
|
79
|
+
* @property {string} [message_id] Provider message id on success.
|
|
80
|
+
* @property {string} [error] Error detail on failure.
|
|
81
|
+
* @property {object} [raw] Untouched provider response for diagnostics.
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* MessageEvent — the canonical inbound event. Superset of ChannelMessage.
|
|
86
|
+
*
|
|
87
|
+
* @typedef {Object} MessageEvent
|
|
88
|
+
* @property {string} channel Channel name, e.g. "slack" | "telegram" | "whatsapp"
|
|
89
|
+
* @property {string} message_id Stable per-channel id (Slack ts, Telegram msg id, …)
|
|
90
|
+
* @property {string} [thread_id] Thread / conversation id within the channel
|
|
91
|
+
* @property {import("./channel.mjs").ChannelIdentity} from
|
|
92
|
+
* @property {import("./channel.mjs").ChannelIdentity} [to]
|
|
93
|
+
* @property {string} text Plain-text body (channel-specific formatting stripped)
|
|
94
|
+
* @property {string} [html] Original HTML body if applicable
|
|
95
|
+
* @property {Array<import("./channel.mjs").Attachment>} [attachments]
|
|
96
|
+
* @property {string} timestamp ISO 8601
|
|
97
|
+
* @property {string} [in_reply_to] Parent message id for threaded replies
|
|
98
|
+
* @property {object} [raw] Untouched provider payload for diagnostics
|
|
99
|
+
* @property {EventKind} kind WS2: the event kind (default "message")
|
|
100
|
+
* @property {SessionSource} [source] WS2: durable source descriptor for reply/typing/react
|
|
101
|
+
* @property {string|null} [thread_context] WS2: rendered prior-thread history (or null)
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build a MessageEvent with the WS2 defaults applied. Accepts a partial and
|
|
106
|
+
* fills `kind` (default "message") + leaves `source`/`thread_context` as
|
|
107
|
+
* provided. Adapters typically return a full event from `_toEvent`; this
|
|
108
|
+
* helper is for tests + callers that want the defaults guaranteed.
|
|
109
|
+
*
|
|
110
|
+
* @param {Partial<MessageEvent>} partial
|
|
111
|
+
* @returns {MessageEvent}
|
|
112
|
+
*/
|
|
113
|
+
export function makeMessageEvent(partial = {}) {
|
|
114
|
+
return {
|
|
115
|
+
...partial,
|
|
116
|
+
kind: normalizeKind(partial.kind),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Self-registering platform registry.
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @typedef {Object} PlatformDefinition
|
|
126
|
+
* @property {string} name The platform name (== channel name), e.g. "telegram".
|
|
127
|
+
* @property {string} gateFile Repo-relative gate file whose presence enables the
|
|
128
|
+
* platform, e.g. "config/telegram.yaml".
|
|
129
|
+
* @property {(opts:object)=>object} factory Factory returning an adapter (BaseAdapter subclass).
|
|
130
|
+
* @property {string[]} [capabilities] Advertised capability strings.
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/** @type {Map<string, PlatformDefinition>} */
|
|
134
|
+
const PLATFORMS = new Map();
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register a platform. Called at module-eval time at the end of each adapter
|
|
138
|
+
* file. Idempotent by name: re-defining the same name replaces the prior
|
|
139
|
+
* definition (so a re-import during tests / hot-reload doesn't throw). The
|
|
140
|
+
* registry holds *definitions* (factories), not live instances — the daemon
|
|
141
|
+
* decides which to instantiate based on gate-file presence.
|
|
142
|
+
*
|
|
143
|
+
* @param {PlatformDefinition} def
|
|
144
|
+
* @returns {PlatformDefinition}
|
|
145
|
+
*/
|
|
146
|
+
export function definePlatform(def) {
|
|
147
|
+
if (!def || typeof def !== "object") throw new Error("definePlatform: def object required");
|
|
148
|
+
if (!def.name || typeof def.name !== "string") throw new Error("definePlatform: name required");
|
|
149
|
+
if (!def.gateFile || typeof def.gateFile !== "string") throw new Error("definePlatform: gateFile required");
|
|
150
|
+
if (typeof def.factory !== "function") throw new Error("definePlatform: factory must be a function");
|
|
151
|
+
const normalized = {
|
|
152
|
+
name: def.name,
|
|
153
|
+
gateFile: def.gateFile,
|
|
154
|
+
factory: def.factory,
|
|
155
|
+
capabilities: Array.isArray(def.capabilities) ? def.capabilities.slice() : [],
|
|
156
|
+
};
|
|
157
|
+
PLATFORMS.set(def.name, normalized);
|
|
158
|
+
return normalized;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Look up a platform definition by name (undefined when not registered). */
|
|
162
|
+
export function getPlatform(name) {
|
|
163
|
+
return PLATFORMS.get(name);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** List every registered platform definition (sorted by name for determinism). */
|
|
167
|
+
export function listPlatforms() {
|
|
168
|
+
return Array.from(PLATFORMS.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Test/utility helper: drop a registration. */
|
|
172
|
+
export function clearPlatform(name) {
|
|
173
|
+
return PLATFORMS.delete(name);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Single-owner rule (H2).
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
//
|
|
180
|
+
// A platform whose real-time connection is owned by a DEDICATED launchd process
|
|
181
|
+
// must NOT also be started by the maestro daemon's generic channel loop —
|
|
182
|
+
// otherwise the channel is ingested TWICE (every DM answered twice).
|
|
183
|
+
//
|
|
184
|
+
// - "slack" is owned by `ai.maestro.<agent>-slack-socket`
|
|
185
|
+
// (generate-plists.sh → scripts/poller/slack-socket-mode.mjs), which reads
|
|
186
|
+
// config/slack.yaml itself for reactions / cc-channels / thread enrichment.
|
|
187
|
+
//
|
|
188
|
+
// This set is the SoT both the daemon (to skip) and any reviewer can consult.
|
|
189
|
+
// Platforms NOT in this set (telegram / whatsapp / voice) have no dedicated
|
|
190
|
+
// plist and ARE daemon-managed.
|
|
191
|
+
export const DEDICATED_PLIST_PLATFORMS = Object.freeze(new Set(["slack"]));
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Should the maestro daemon's generic channel loop start this platform itself?
|
|
195
|
+
* False for platforms with a dedicated launchd owner (see above). Pure.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} name platform name (== channel name)
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
export function isDaemonManagedPlatform(name) {
|
|
201
|
+
return !DEDICATED_PLIST_PLATFORMS.has(name);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default {
|
|
205
|
+
EVENT_KINDS,
|
|
206
|
+
DEFAULT_EVENT_KIND,
|
|
207
|
+
normalizeKind,
|
|
208
|
+
makeMessageEvent,
|
|
209
|
+
definePlatform,
|
|
210
|
+
getPlatform,
|
|
211
|
+
listPlatforms,
|
|
212
|
+
clearPlatform,
|
|
213
|
+
DEDICATED_PLIST_PLATFORMS,
|
|
214
|
+
isDaemonManagedPlatform,
|
|
215
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* contract.test.mjs — node:test coverage for lib/channels/contract.mjs
|
|
3
|
+
*
|
|
4
|
+
* Covers: EventKind set + normalization, makeMessageEvent defaults, and the
|
|
5
|
+
* self-registering platform registry (definePlatform / getPlatform /
|
|
6
|
+
* listPlatforms / clearPlatform). Pure, hermetic — no IO.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { test } from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
EVENT_KINDS,
|
|
14
|
+
DEFAULT_EVENT_KIND,
|
|
15
|
+
normalizeKind,
|
|
16
|
+
makeMessageEvent,
|
|
17
|
+
definePlatform,
|
|
18
|
+
getPlatform,
|
|
19
|
+
listPlatforms,
|
|
20
|
+
clearPlatform,
|
|
21
|
+
DEDICATED_PLIST_PLATFORMS,
|
|
22
|
+
isDaemonManagedPlatform,
|
|
23
|
+
} from "./contract.mjs";
|
|
24
|
+
|
|
25
|
+
import { ChannelMessage } from "./index.mjs";
|
|
26
|
+
|
|
27
|
+
test("EVENT_KINDS is the closed set from the design", () => {
|
|
28
|
+
assert.deepEqual([...EVENT_KINDS].sort(), ["call", "channel_cc", "message", "reaction", "voice_note"]);
|
|
29
|
+
assert.equal(DEFAULT_EVENT_KIND, "message");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("normalizeKind passes known kinds and defaults unknown to message", () => {
|
|
33
|
+
assert.equal(normalizeKind("reaction"), "reaction");
|
|
34
|
+
assert.equal(normalizeKind("channel_cc"), "channel_cc");
|
|
35
|
+
assert.equal(normalizeKind("voice_note"), "voice_note");
|
|
36
|
+
assert.equal(normalizeKind(undefined), "message");
|
|
37
|
+
assert.equal(normalizeKind("bogus"), "message");
|
|
38
|
+
assert.equal(normalizeKind(null), "message");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("makeMessageEvent fills kind default and preserves other fields", () => {
|
|
42
|
+
const ev = makeMessageEvent({ channel: "slack", message_id: "1.2", text: "hi" });
|
|
43
|
+
assert.equal(ev.kind, "message");
|
|
44
|
+
assert.equal(ev.channel, "slack");
|
|
45
|
+
assert.equal(ev.text, "hi");
|
|
46
|
+
|
|
47
|
+
const reaction = makeMessageEvent({ channel: "telegram", kind: "reaction", text: "" });
|
|
48
|
+
assert.equal(reaction.kind, "reaction");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("ChannelMessage is exported as a back-compat alias for MessageEvent", () => {
|
|
52
|
+
// The alias must be re-exported from the channels facade so existing
|
|
53
|
+
// `import { ChannelMessage }` sites resolve. It's a typedef carrier — the
|
|
54
|
+
// runtime value is a harmless namespace/sentinel, just assert it exists.
|
|
55
|
+
assert.notEqual(ChannelMessage, undefined, "ChannelMessage alias must be exported");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Platform registry
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
test("definePlatform + getPlatform + listPlatforms round-trip", () => {
|
|
63
|
+
clearPlatform("test-plat-a");
|
|
64
|
+
clearPlatform("test-plat-b");
|
|
65
|
+
|
|
66
|
+
const factoryA = () => ({ name: "test-plat-a" });
|
|
67
|
+
const def = definePlatform({
|
|
68
|
+
name: "test-plat-a",
|
|
69
|
+
gateFile: "config/test-plat-a.yaml",
|
|
70
|
+
factory: factoryA,
|
|
71
|
+
capabilities: ["send", "react"],
|
|
72
|
+
});
|
|
73
|
+
assert.equal(def.name, "test-plat-a");
|
|
74
|
+
assert.equal(def.gateFile, "config/test-plat-a.yaml");
|
|
75
|
+
assert.deepEqual(def.capabilities, ["send", "react"]);
|
|
76
|
+
|
|
77
|
+
const got = getPlatform("test-plat-a");
|
|
78
|
+
assert.equal(got.factory, factoryA);
|
|
79
|
+
assert.deepEqual(got.capabilities, ["send", "react"]);
|
|
80
|
+
|
|
81
|
+
definePlatform({ name: "test-plat-b", gateFile: "config/test-plat-b.yaml", factory: () => ({}) });
|
|
82
|
+
const names = listPlatforms().map((p) => p.name);
|
|
83
|
+
assert.ok(names.includes("test-plat-a"));
|
|
84
|
+
assert.ok(names.includes("test-plat-b"));
|
|
85
|
+
// Sorted deterministically.
|
|
86
|
+
assert.deepEqual([...names].sort(), names);
|
|
87
|
+
|
|
88
|
+
clearPlatform("test-plat-a");
|
|
89
|
+
clearPlatform("test-plat-b");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("definePlatform is idempotent by name (re-import safe)", () => {
|
|
93
|
+
clearPlatform("dup");
|
|
94
|
+
const f1 = () => ({ v: 1 });
|
|
95
|
+
const f2 = () => ({ v: 2 });
|
|
96
|
+
definePlatform({ name: "dup", gateFile: "config/dup.yaml", factory: f1 });
|
|
97
|
+
// Re-defining must NOT throw — it replaces (modules can re-eval in tests).
|
|
98
|
+
assert.doesNotThrow(() => definePlatform({ name: "dup", gateFile: "config/dup.yaml", factory: f2 }));
|
|
99
|
+
assert.equal(getPlatform("dup").factory, f2);
|
|
100
|
+
clearPlatform("dup");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("definePlatform validates required fields", () => {
|
|
104
|
+
assert.throws(() => definePlatform(null), /def object required/);
|
|
105
|
+
assert.throws(() => definePlatform({ gateFile: "x", factory: () => {} }), /name required/);
|
|
106
|
+
assert.throws(() => definePlatform({ name: "x", factory: () => {} }), /gateFile required/);
|
|
107
|
+
assert.throws(() => definePlatform({ name: "x", gateFile: "x" }), /factory must be a function/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("capabilities default to an empty array and are copied (not aliased)", () => {
|
|
111
|
+
clearPlatform("nocap");
|
|
112
|
+
const src = ["send"];
|
|
113
|
+
const def = definePlatform({ name: "nocap", gateFile: "config/nocap.yaml", factory: () => {}, capabilities: src });
|
|
114
|
+
src.push("mutated");
|
|
115
|
+
assert.deepEqual(def.capabilities, ["send"], "registry must not alias the caller's array");
|
|
116
|
+
clearPlatform("nocap");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Single-owner rule (H2) — Slack must NOT be daemon-managed, because its
|
|
121
|
+
// dedicated launchd `slack-socket` process owns the real-time connection.
|
|
122
|
+
// Starting it in BOTH places double-ingests every DM. This invariant is the
|
|
123
|
+
// guard that two Slack connections can't both be live.
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
test("H2: Slack is excluded from daemon-managed platforms (dedicated plist owns it)", () => {
|
|
127
|
+
assert.ok(DEDICATED_PLIST_PLATFORMS.has("slack"), "slack has a dedicated launchd owner");
|
|
128
|
+
assert.equal(isDaemonManagedPlatform("slack"), false,
|
|
129
|
+
"the daemon generic loop must NOT start Slack — its slack-socket plist is the single owner");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("H2: daemon-only channels (telegram/whatsapp/voice) remain daemon-managed", () => {
|
|
133
|
+
for (const name of ["telegram", "whatsapp", "voice"]) {
|
|
134
|
+
assert.equal(isDaemonManagedPlatform(name), true, `${name} has no dedicated plist → daemon-managed`);
|
|
135
|
+
assert.equal(DEDICATED_PLIST_PLATFORMS.has(name), false);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maestro — Conversation resolver
|
|
3
|
+
*
|
|
4
|
+
* Maps an inbound channel message → { agentId, sessionKey, threadId, channelName }
|
|
5
|
+
* so the dispatcher can deduplicate across channels and route a Slack-DM
|
|
6
|
+
* reply, a Gmail thread, a WhatsApp quoted-message, and a voice call to
|
|
7
|
+
* the same agent session if they belong to the same conversation.
|
|
8
|
+
*
|
|
9
|
+
* The resolver is pure (no IO). It accepts a ChannelMessage and returns
|
|
10
|
+
* a normalised key. Different inputs producing the same key SHOULD be
|
|
11
|
+
* treated as the same conversation by the daemon's session-lock layer.
|
|
12
|
+
*
|
|
13
|
+
* Drawn from the pattern in `openclaw/src/channels/conversation-resolution.ts`
|
|
14
|
+
* (MIT) but simplified to maestro's needs: we don't need a multi-agent
|
|
15
|
+
* routing layer because each maestro instance is single-agent.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a conversation key for an inbound message.
|
|
20
|
+
*
|
|
21
|
+
* @param {import("./channel.mjs").ChannelMessage} msg
|
|
22
|
+
* @returns {{ channel: string, sessionKey: string, threadId: string, peerId: string }}
|
|
23
|
+
*/
|
|
24
|
+
export function resolveConversation(msg) {
|
|
25
|
+
if (!msg || typeof msg !== "object") {
|
|
26
|
+
throw new Error("resolveConversation: msg required");
|
|
27
|
+
}
|
|
28
|
+
const channel = String(msg.channel || "unknown").trim().toLowerCase();
|
|
29
|
+
// Normalise the identity components feeding the session key so that
|
|
30
|
+
// case-variant or whitespace-padded ids do NOT fork the session.
|
|
31
|
+
// Emails and ids are case-insensitive for routing purposes; phones are
|
|
32
|
+
// only trimmed (case has no meaning, casing them is a no-op but trim
|
|
33
|
+
// strips stray whitespace from upstream adapters).
|
|
34
|
+
const rawId = msg.from?.id;
|
|
35
|
+
const rawEmail = msg.from?.email;
|
|
36
|
+
const rawPhone = msg.from?.phone;
|
|
37
|
+
const peerId =
|
|
38
|
+
(rawId != null && String(rawId).trim().toLowerCase()) ||
|
|
39
|
+
(rawEmail != null && String(rawEmail).trim().toLowerCase()) ||
|
|
40
|
+
(rawPhone != null && String(rawPhone).trim()) ||
|
|
41
|
+
"unknown";
|
|
42
|
+
// threadId logic per channel — preserves existing maestro thread semantics.
|
|
43
|
+
let threadId;
|
|
44
|
+
switch (channel) {
|
|
45
|
+
case "slack":
|
|
46
|
+
// For Slack we treat thread_ts as the conversation; DMs without a
|
|
47
|
+
// thread collapse onto `dm:<channel>` so successive messages in
|
|
48
|
+
// the same DM bind to one session. When the Slack channel id is
|
|
49
|
+
// absent (some adapter paths omit msg.raw.channel) we fall back to
|
|
50
|
+
// the peerId so two different DMs do NOT collide on `dm:unknown`.
|
|
51
|
+
threadId = msg.thread_id || `dm:${msg.raw?.channel || peerId}`;
|
|
52
|
+
break;
|
|
53
|
+
case "gmail":
|
|
54
|
+
threadId = msg.thread_id || msg.in_reply_to || msg.message_id;
|
|
55
|
+
break;
|
|
56
|
+
case "whatsapp":
|
|
57
|
+
threadId = msg.thread_id || msg.in_reply_to || `peer:${peerId}`;
|
|
58
|
+
break;
|
|
59
|
+
case "sms":
|
|
60
|
+
threadId = `peer:${peerId}`;
|
|
61
|
+
break;
|
|
62
|
+
case "voice":
|
|
63
|
+
threadId = msg.thread_id || `call:${msg.message_id}`;
|
|
64
|
+
break;
|
|
65
|
+
case "calendar":
|
|
66
|
+
threadId = msg.thread_id || `event:${msg.message_id}`;
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
threadId = msg.thread_id || msg.message_id || "unknown";
|
|
70
|
+
}
|
|
71
|
+
const sessionKey = `${channel}:${threadId}`;
|
|
72
|
+
return { channel, sessionKey, threadId, peerId };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Decide whether two messages belong to the same conversation. Useful
|
|
77
|
+
* for dedup logic in the daemon's inbox processor.
|
|
78
|
+
*/
|
|
79
|
+
export function sameConversation(msgA, msgB) {
|
|
80
|
+
const a = resolveConversation(msgA);
|
|
81
|
+
const b = resolveConversation(msgB);
|
|
82
|
+
return a.sessionKey === b.sessionKey;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build a deterministic session lock key from a conversation. Mirrors
|
|
87
|
+
* the keys used by scripts/daemon/session-lock.mjs so the channel bus
|
|
88
|
+
* can be wired in without breaking existing locks.
|
|
89
|
+
*/
|
|
90
|
+
export function sessionLockKey(msg) {
|
|
91
|
+
const { channel, threadId } = resolveConversation(msg);
|
|
92
|
+
return `${channel}:${threadId}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default { resolveConversation, sameConversation, sessionLockKey };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maestro — Gmail channel adapter
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing scripts/send-email.sh + Python email helpers in the
|
|
5
|
+
* Channel interface. Ingestion stays with scripts/poller/gmail-poller.mjs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { existsSync, writeFileSync, mkdirSync } from "node:fs";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { CHANNEL_CAPABILITIES } from "../channel.mjs";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_CAPABILITIES = [
|
|
15
|
+
CHANNEL_CAPABILITIES.SEND,
|
|
16
|
+
CHANNEL_CAPABILITIES.THREAD,
|
|
17
|
+
CHANNEL_CAPABILITIES.MEDIA_IMAGE,
|
|
18
|
+
CHANNEL_CAPABILITIES.MEDIA_DOCUMENT,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function createGmailChannel(opts = {}) {
|
|
22
|
+
const agentRoot = resolve(opts.agentRoot || process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd());
|
|
23
|
+
const sendScript = opts.sendScript || join(agentRoot, "scripts/send-email.sh");
|
|
24
|
+
const threadedScript = opts.threadedScript || join(agentRoot, "scripts/send-email-threaded.py");
|
|
25
|
+
const log = opts.log || ((lvl, m) => process.stdout.write(`[gmail-channel:${lvl}] ${m}\n`));
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
name: "gmail",
|
|
29
|
+
capabilities: DEFAULT_CAPABILITIES,
|
|
30
|
+
|
|
31
|
+
async start({ onInbound }) {
|
|
32
|
+
if (typeof onInbound !== "function") {
|
|
33
|
+
throw new Error("Gmail.start: onInbound required");
|
|
34
|
+
}
|
|
35
|
+
const inboxDir = join(agentRoot, "state/inbox/gmail");
|
|
36
|
+
if (!existsSync(inboxDir)) {
|
|
37
|
+
log("warn", `gmail inbox dir ${inboxDir} not present; running in send-only mode`);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async send(msg) {
|
|
42
|
+
if (!msg || !msg.to) throw new Error("Gmail.send: msg.to required");
|
|
43
|
+
if (msg.thread_ref && existsSync(threadedScript)) {
|
|
44
|
+
// Use the Python threaded sender that preserves Message-ID / In-Reply-To.
|
|
45
|
+
const args = [
|
|
46
|
+
threadedScript,
|
|
47
|
+
"--to", msg.to,
|
|
48
|
+
"--subject", msg.meta?.subject || "(no subject)",
|
|
49
|
+
"--body", msg.text || "",
|
|
50
|
+
"--in-reply-to", String(msg.thread_ref),
|
|
51
|
+
];
|
|
52
|
+
execFileSync("python3", args, {
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
timeout: 60_000,
|
|
55
|
+
env: { ...process.env, AGENT_ROOT: agentRoot, AGENT_DIR: agentRoot },
|
|
56
|
+
});
|
|
57
|
+
return { message_id: "", raw: { threaded: true } };
|
|
58
|
+
}
|
|
59
|
+
if (!existsSync(sendScript)) {
|
|
60
|
+
throw new Error(`Gmail.send: ${sendScript} not found`);
|
|
61
|
+
}
|
|
62
|
+
const args = [
|
|
63
|
+
msg.to,
|
|
64
|
+
"--subject", msg.meta?.subject || "(no subject)",
|
|
65
|
+
"--body", msg.text || "",
|
|
66
|
+
];
|
|
67
|
+
if (msg.meta?.cc) args.push("--cc", msg.meta.cc);
|
|
68
|
+
if (msg.meta?.bcc) args.push("--bcc", msg.meta.bcc);
|
|
69
|
+
const out = execFileSync(sendScript, args, {
|
|
70
|
+
encoding: "utf-8",
|
|
71
|
+
timeout: 60_000,
|
|
72
|
+
env: { ...process.env, AGENT_ROOT: agentRoot, AGENT_DIR: agentRoot },
|
|
73
|
+
});
|
|
74
|
+
const idMatch = out.match(/Message-ID:\s*<([^>]+)>/i);
|
|
75
|
+
return { message_id: idMatch?.[1] || "", raw: { stdout: out } };
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
async healthCheck() {
|
|
79
|
+
const present = existsSync(sendScript);
|
|
80
|
+
return { ok: present, detail: present ? "send-email.sh present" : `missing ${sendScript}` };
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async stop() {},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default createGmailChannel;
|