@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,554 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/comms/send-gate.mjs — the ONE shared outbound send-gate chokepoint.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists
|
|
5
|
+
* ---------------
|
|
6
|
+
* The pre-send audit hook (scripts/hooks/pre-send-audit.sh) historically gated
|
|
7
|
+
* exactly one path: the `gmail_create_draft` tool. Every other real send path —
|
|
8
|
+
* scripts/slack-send.sh, scripts/send-whatsapp.sh, scripts/send-sms.sh,
|
|
9
|
+
* scripts/send-email.sh, the daemon responder, and BaseAdapter._send — bypassed
|
|
10
|
+
* it (gaps-product-quality P0-3). This module is the single policy layer those
|
|
11
|
+
* paths now share, so banned-phrase screening, AI-disclosure rules, information
|
|
12
|
+
* barriers, and the recipient allowlist are enforced in ONE place instead of
|
|
13
|
+
* being re-implemented (or skipped) per script.
|
|
14
|
+
*
|
|
15
|
+
* Relationship to validate_outbound.py (Layer 0)
|
|
16
|
+
* ----------------------------------------------
|
|
17
|
+
* The Python validator (relationship/title/possessive-claim checks) stays as the
|
|
18
|
+
* Layer-0 call inside each send script. send-gate is the unified POLICY layer
|
|
19
|
+
* ABOVE it: the two run in series and a block from either stops the send.
|
|
20
|
+
*
|
|
21
|
+
* Fail posture (mirrors scripts/validate_outbound semantics)
|
|
22
|
+
* ----------------------------------------------------------
|
|
23
|
+
* The house idiom is "fail-open + silence", but for outbound governance that is
|
|
24
|
+
* the wrong default for EXTERNAL recipients: an unreadable policy file must not
|
|
25
|
+
* silently open the perimeter to outsiders. So:
|
|
26
|
+
* - external recipient + a required policy file is unreadable/malformed →
|
|
27
|
+
* fail CLOSED (allow:false, reason explains which file).
|
|
28
|
+
* - internal recipient + the same fault → fail OPEN (allow:true) so an
|
|
29
|
+
* internal ops message is never blocked by a transient FS fault.
|
|
30
|
+
* A recipient is classified external unless it provably matches an internal
|
|
31
|
+
* signal (Slack/Telegram workspace ids, or an email whose domain is on the
|
|
32
|
+
* configured internal_domains list). Default-deny: unknown ⇒ external.
|
|
33
|
+
*
|
|
34
|
+
* Determinism / testability
|
|
35
|
+
* --------------------------
|
|
36
|
+
* screenOutbound is pure-ish: it reads only the policy files and never sends.
|
|
37
|
+
* The filesystem reader is injectable (`opts.fs = { readFileSync, existsSync }`)
|
|
38
|
+
* so tests drive every branch — including "unreadable policy" — without touching
|
|
39
|
+
* disk. js-yaml is loaded lazily (the package's only YAML dep), matching
|
|
40
|
+
* lib/learning/config.mjs and lib/collective/config.mjs.
|
|
41
|
+
*
|
|
42
|
+
* CLI form (consumed by the shell send scripts)
|
|
43
|
+
* ---------------------------------------------
|
|
44
|
+
* echo "message text" | node lib/comms/send-gate.mjs \
|
|
45
|
+
* --channel slack --recipient C0123 [--agent-root /path]
|
|
46
|
+
* exit 0 → allow; exit 1 → BLOCK (reason on stderr); prints redactedText (if
|
|
47
|
+
* any) on stdout. Any unexpected internal error for an EXTERNAL recipient also
|
|
48
|
+
* exits 1 (fail closed); for an internal recipient it exits 0 (fail open).
|
|
49
|
+
*
|
|
50
|
+
* @module lib/comms/send-gate
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
"use strict";
|
|
54
|
+
|
|
55
|
+
import {
|
|
56
|
+
readFileSync as nodeReadFileSync,
|
|
57
|
+
existsSync as nodeExistsSync,
|
|
58
|
+
} from "node:fs";
|
|
59
|
+
import { join } from "node:path";
|
|
60
|
+
|
|
61
|
+
/* ─────────────────────────── policy file locations ───────────────────────── */
|
|
62
|
+
|
|
63
|
+
const POLICY = Object.freeze({
|
|
64
|
+
style: "policies/communication-style.md",
|
|
65
|
+
disclosure: "policies/ai-disclosure.yaml",
|
|
66
|
+
barriers: "policies/information-barriers.yaml",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/* ───────────────────────────── banned phrases ────────────────────────────── */
|
|
70
|
+
// Source of truth: policies/communication-style.md §"Default tone" rule 2.
|
|
71
|
+
// Kept in sync with scripts/hooks/pre-send-audit.sh BANNED_SUBSTRINGS so both
|
|
72
|
+
// gates reject the same set. Matching is case-insensitive, substring-based.
|
|
73
|
+
export const BANNED_SUBSTRINGS = Object.freeze([
|
|
74
|
+
"I'm here to help",
|
|
75
|
+
"I am here to help",
|
|
76
|
+
"Let me know if you need anything else",
|
|
77
|
+
"I hope this helps",
|
|
78
|
+
"Feel free to ask if you have any questions",
|
|
79
|
+
"As an AI",
|
|
80
|
+
"As a language model",
|
|
81
|
+
"I should clarify that I am",
|
|
82
|
+
"Here's a comprehensive overview of",
|
|
83
|
+
"Here is a comprehensive overview of",
|
|
84
|
+
"I'd be happy to",
|
|
85
|
+
"I would be happy to",
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Opener-only fillers: legitimate mid-sentence, banned as a line/message opener.
|
|
89
|
+
export const BANNED_OPENERS = Object.freeze([
|
|
90
|
+
"Certainly!",
|
|
91
|
+
"Absolutely!",
|
|
92
|
+
"Great question!",
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
/* ────────────────────────── recipient classification ─────────────────────── */
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Decide whether a recipient is INTERNAL (workspace member / internal domain) or
|
|
99
|
+
* EXTERNAL (everyone else). Default-deny: anything unrecognised is external, so
|
|
100
|
+
* the fail-CLOSED branch covers the unknown case.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} channel slack|telegram|whatsapp|sms|email|voice|document
|
|
103
|
+
* @param {string} recipient
|
|
104
|
+
* @param {string[]} internalDomains lower-cased email domains treated internal
|
|
105
|
+
* @returns {"internal"|"external"}
|
|
106
|
+
*/
|
|
107
|
+
export function classifyRecipient(channel, recipient, internalDomains = []) {
|
|
108
|
+
const r = String(recipient || "").trim();
|
|
109
|
+
if (!r) return "external"; // no recipient ⇒ treat conservatively
|
|
110
|
+
|
|
111
|
+
switch (channel) {
|
|
112
|
+
case "slack":
|
|
113
|
+
case "telegram":
|
|
114
|
+
// Slack channel/DM ids (C…, D…, G…, U…) and Telegram chat ids live inside
|
|
115
|
+
// the agent's own workspace → internal by construction. A bare email/phone
|
|
116
|
+
// passed on these channels is ambiguous, so fall through to the domain test.
|
|
117
|
+
if (/^[CDGU][A-Z0-9]{6,}$/.test(r)) return "internal"; // Slack id
|
|
118
|
+
if (/^-?\d{5,}$/.test(r)) return "internal"; // Telegram numeric chat id
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Email: internal only if its domain is explicitly allow-listed as internal.
|
|
125
|
+
const at = r.lastIndexOf("@");
|
|
126
|
+
if (at > 0 && at < r.length - 1) {
|
|
127
|
+
const domain = r.slice(at + 1).toLowerCase();
|
|
128
|
+
for (const d of internalDomains) {
|
|
129
|
+
const dn = String(d || "").toLowerCase().replace(/^@/, "");
|
|
130
|
+
if (dn && (domain === dn || domain.endsWith("." + dn))) return "internal";
|
|
131
|
+
}
|
|
132
|
+
return "external";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Phone numbers (whatsapp/sms) and everything else: external by default.
|
|
136
|
+
return "external";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* ─────────────────────────────── helpers ─────────────────────────────────── */
|
|
140
|
+
|
|
141
|
+
function makeFs(injected) {
|
|
142
|
+
return {
|
|
143
|
+
readFileSync: injected?.readFileSync || nodeReadFileSync,
|
|
144
|
+
existsSync: injected?.existsSync || nodeExistsSync,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let _yaml = null;
|
|
149
|
+
async function getYaml() {
|
|
150
|
+
if (_yaml) return _yaml;
|
|
151
|
+
const mod = await import("js-yaml");
|
|
152
|
+
_yaml = mod.default || mod;
|
|
153
|
+
return _yaml;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Read a policy file. Returns { ok, text } — ok:false means unreadable/missing,
|
|
158
|
+
* which the caller maps to fail-open (internal) or fail-closed (external).
|
|
159
|
+
*/
|
|
160
|
+
function readPolicy(fs, agentRoot, rel) {
|
|
161
|
+
const path = join(agentRoot, rel);
|
|
162
|
+
try {
|
|
163
|
+
if (!fs.existsSync(path)) return { ok: false, text: "", path };
|
|
164
|
+
const text = fs.readFileSync(path, "utf-8");
|
|
165
|
+
return { ok: true, text, path };
|
|
166
|
+
} catch {
|
|
167
|
+
return { ok: false, text: "", path };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function blocked(reason, extra = {}) {
|
|
172
|
+
return { allow: false, reason, ...extra };
|
|
173
|
+
}
|
|
174
|
+
function allowed(extra = {}) {
|
|
175
|
+
return { allow: true, reason: "", ...extra };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ───────────────────────── individual screen layers ──────────────────────── */
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* (a) Banned-phrase / AI-tell screen. Pure; no IO. Returns a block reason string
|
|
182
|
+
* or null. Sourced from policies/communication-style.md (constant above).
|
|
183
|
+
*/
|
|
184
|
+
export function screenBannedPhrases(text) {
|
|
185
|
+
const lc = String(text || "").toLowerCase();
|
|
186
|
+
for (const phrase of BANNED_SUBSTRINGS) {
|
|
187
|
+
if (lc.includes(phrase.toLowerCase())) {
|
|
188
|
+
return `banned AI-tell phrase "${phrase}" (policies/communication-style.md)`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Opener-only: at message start or at the start of any line.
|
|
192
|
+
for (const opener of BANNED_OPENERS) {
|
|
193
|
+
const re = new RegExp(
|
|
194
|
+
"(^|[\\r\\n])\\s*" + opener.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
|
|
195
|
+
"i",
|
|
196
|
+
);
|
|
197
|
+
if (re.test(String(text || ""))) {
|
|
198
|
+
return `banned filler opener "${opener}" (policies/communication-style.md)`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* (b) AI-disclosure screen. Resolves the active posture for the recipient's
|
|
206
|
+
* jurisdiction from ai-disclosure.yaml and enforces two things:
|
|
207
|
+
* 1. The non-overridable truthfulness invariant: an outbound message must
|
|
208
|
+
* never DENY being an AI (e.g. "I am not a bot", "I'm a real person").
|
|
209
|
+
* This holds under every posture — concealment must never become a lie.
|
|
210
|
+
* 2. Where the active posture requires proactive disclosure / a machine-
|
|
211
|
+
* readable marking, a first-contact message that conceals it is blocked.
|
|
212
|
+
*
|
|
213
|
+
* @returns {string|null} block reason, or null if disclosure rules are satisfied
|
|
214
|
+
*/
|
|
215
|
+
export function screenDisclosure(text, { policy, jurisdiction, firstContact }) {
|
|
216
|
+
const lc = String(text || "").toLowerCase();
|
|
217
|
+
|
|
218
|
+
// 1. Truthfulness invariant — locked, applies under every posture. A message
|
|
219
|
+
// that explicitly denies AI nature is never allowed to leave.
|
|
220
|
+
const denials = [
|
|
221
|
+
"i am not an ai",
|
|
222
|
+
"i'm not an ai",
|
|
223
|
+
"i am not a bot",
|
|
224
|
+
"i'm not a bot",
|
|
225
|
+
"i am a real person",
|
|
226
|
+
"i'm a real person",
|
|
227
|
+
"i am a human",
|
|
228
|
+
"i'm a human",
|
|
229
|
+
"no, i'm not a bot",
|
|
230
|
+
"no i am not a bot",
|
|
231
|
+
"i am not a robot",
|
|
232
|
+
"i'm not a robot",
|
|
233
|
+
];
|
|
234
|
+
for (const d of denials) {
|
|
235
|
+
if (lc.includes(d)) {
|
|
236
|
+
return `AI-disclosure invariant: message denies AI nature ("${d}") — concealment may never become a lie (policies/ai-disclosure.yaml)`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!policy) return null; // policy already handled upstream (fail posture)
|
|
241
|
+
|
|
242
|
+
// 2. Resolve posture for this jurisdiction (fail-safe = default_posture).
|
|
243
|
+
const postures = policy.posture || {};
|
|
244
|
+
const jurisdictions = policy.jurisdictions || {};
|
|
245
|
+
const jKey = jurisdiction && jurisdictions[jurisdiction] ? jurisdiction : "default";
|
|
246
|
+
let postureName =
|
|
247
|
+
(jurisdictions[jKey] && jurisdictions[jKey].posture) ||
|
|
248
|
+
policy.default_posture ||
|
|
249
|
+
"disclose-always";
|
|
250
|
+
|
|
251
|
+
// concealed-where-legal collapses to disclose-always unless governance signed
|
|
252
|
+
// it off for this jurisdiction (resolution.unsigned_concealed_posture).
|
|
253
|
+
if (postureName === "concealed-where-legal") {
|
|
254
|
+
const signoff = policy.governance && policy.governance.sign_off;
|
|
255
|
+
const signedJur =
|
|
256
|
+
signoff &&
|
|
257
|
+
Array.isArray(signoff.jurisdictions) &&
|
|
258
|
+
signoff.approver &&
|
|
259
|
+
signoff.jurisdictions.includes(jurisdiction);
|
|
260
|
+
if (!signedJur) postureName = "disclose-always";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const posture = postures[postureName] || {};
|
|
264
|
+
|
|
265
|
+
// Only first-contact messages carry the proactive-disclosure duty; a reply in
|
|
266
|
+
// an ongoing thread does not have to re-disclose every turn.
|
|
267
|
+
if (posture.proactive_disclosure && firstContact) {
|
|
268
|
+
const discloses =
|
|
269
|
+
/\bai\b/i.test(String(text || "")) ||
|
|
270
|
+
lc.includes("automated") ||
|
|
271
|
+
lc.includes("assistant working") ||
|
|
272
|
+
lc.includes("on behalf of");
|
|
273
|
+
if (!discloses) {
|
|
274
|
+
return `AI-disclosure required: posture "${postureName}" (${jKey}) mandates proactive disclosure on first contact, message conceals it (policies/ai-disclosure.yaml)`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* (c) Information-barrier screen. Reads an OPTIONAL `barriers:` section from
|
|
282
|
+
* information-barriers.yaml. Each barrier wall is:
|
|
283
|
+
* - recipients: [exact recipient strings] (optional)
|
|
284
|
+
* - recipient_pattern: "regex" (optional)
|
|
285
|
+
* - recipient_class: "external"|"internal" (optional)
|
|
286
|
+
* - blocked_topics / deny_keywords: [case-insensitive substrings] (required)
|
|
287
|
+
* - reason: "human-readable wall name" (optional)
|
|
288
|
+
* A message to a matching recipient that contains a blocked topic is denied.
|
|
289
|
+
* When no `barriers:` section is present the screen is a no-op (no walls ⇒
|
|
290
|
+
* nothing to block) — the existing taxonomy-only file stays valid.
|
|
291
|
+
*
|
|
292
|
+
* @returns {string|null} block reason, or null
|
|
293
|
+
*/
|
|
294
|
+
export function screenBarriers(text, { policy, recipient, recipientClass }) {
|
|
295
|
+
if (!policy || !Array.isArray(policy.barriers)) return null;
|
|
296
|
+
const lc = String(text || "").toLowerCase();
|
|
297
|
+
const r = String(recipient || "");
|
|
298
|
+
|
|
299
|
+
for (const wall of policy.barriers) {
|
|
300
|
+
if (!wall || typeof wall !== "object") continue;
|
|
301
|
+
const topics = wall.blocked_topics || wall.deny_keywords || [];
|
|
302
|
+
if (!Array.isArray(topics) || topics.length === 0) continue;
|
|
303
|
+
|
|
304
|
+
let appliesToRecipient = false;
|
|
305
|
+
if (Array.isArray(wall.recipients) && wall.recipients.includes(r)) {
|
|
306
|
+
appliesToRecipient = true;
|
|
307
|
+
}
|
|
308
|
+
if (!appliesToRecipient && wall.recipient_pattern) {
|
|
309
|
+
try {
|
|
310
|
+
if (new RegExp(wall.recipient_pattern).test(r)) appliesToRecipient = true;
|
|
311
|
+
} catch {
|
|
312
|
+
// A malformed pattern is a policy authoring error; treat the wall as
|
|
313
|
+
// applying (fail toward blocking) so a broken wall can't silently open.
|
|
314
|
+
appliesToRecipient = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!appliesToRecipient && wall.recipient_class) {
|
|
318
|
+
if (wall.recipient_class === recipientClass) appliesToRecipient = true;
|
|
319
|
+
}
|
|
320
|
+
// A wall with NO recipient predicate at all applies to everyone.
|
|
321
|
+
if (
|
|
322
|
+
!appliesToRecipient &&
|
|
323
|
+
!Array.isArray(wall.recipients) &&
|
|
324
|
+
!wall.recipient_pattern &&
|
|
325
|
+
!wall.recipient_class
|
|
326
|
+
) {
|
|
327
|
+
appliesToRecipient = true;
|
|
328
|
+
}
|
|
329
|
+
if (!appliesToRecipient) continue;
|
|
330
|
+
|
|
331
|
+
for (const topic of topics) {
|
|
332
|
+
const t = String(topic || "").toLowerCase().trim();
|
|
333
|
+
if (t && lc.includes(t)) {
|
|
334
|
+
const name = wall.reason || wall.name || "information barrier";
|
|
335
|
+
return `information barrier "${name}": topic "${topic}" walled from recipient (policies/information-barriers.yaml)`;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* (d) Recipient allowlist. Only enforced when `allowlist` is a non-empty array.
|
|
344
|
+
* An empty / undefined allowlist means "not configured" → no restriction (the
|
|
345
|
+
* pre-send-audit hook has the same opt-in semantics).
|
|
346
|
+
*
|
|
347
|
+
* @returns {string|null} block reason, or null
|
|
348
|
+
*/
|
|
349
|
+
export function screenAllowlist(recipient, allowlist) {
|
|
350
|
+
if (!Array.isArray(allowlist) || allowlist.length === 0) return null;
|
|
351
|
+
const r = String(recipient || "").trim();
|
|
352
|
+
if (!r) return `recipient missing but a send allowlist is configured`;
|
|
353
|
+
for (const entry of allowlist) {
|
|
354
|
+
if (String(entry || "").trim() === r) return null;
|
|
355
|
+
}
|
|
356
|
+
return `recipient "${r}" is not on the configured send allowlist`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* ──────────────────────────── main entry point ───────────────────────────── */
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Screen an outbound message at the one shared chokepoint.
|
|
363
|
+
*
|
|
364
|
+
* @param {object} args
|
|
365
|
+
* @param {string} args.channel slack|telegram|whatsapp|sms|email|voice|document
|
|
366
|
+
* @param {string} args.recipient channel id / email / phone / handle
|
|
367
|
+
* @param {string} args.text the outbound message body
|
|
368
|
+
* @param {string} [args.agentRoot=process.cwd()] repo root holding policies/
|
|
369
|
+
* @param {string} [args.jurisdiction] recipient jurisdiction key (EU, US-CA, …)
|
|
370
|
+
* @param {boolean} [args.firstContact=true] is this the first message in the convo
|
|
371
|
+
* @param {string[]} [args.internalDomains] email domains treated as internal
|
|
372
|
+
* @param {string[]} [args.allowlist] recipient allowlist (opt-in)
|
|
373
|
+
* @param {object} [args.fs] injected { readFileSync, existsSync } for tests
|
|
374
|
+
* @returns {Promise<{allow:boolean, reason:string, redactedText?:string}>}
|
|
375
|
+
*/
|
|
376
|
+
export async function screenOutbound({
|
|
377
|
+
channel,
|
|
378
|
+
recipient,
|
|
379
|
+
text,
|
|
380
|
+
agentRoot = process.cwd(),
|
|
381
|
+
jurisdiction = undefined,
|
|
382
|
+
firstContact = true,
|
|
383
|
+
internalDomains = [],
|
|
384
|
+
allowlist = undefined,
|
|
385
|
+
fs: injectedFs = undefined,
|
|
386
|
+
} = {}) {
|
|
387
|
+
const fs = makeFs(injectedFs);
|
|
388
|
+
const recipientClass = classifyRecipient(channel, recipient, internalDomains);
|
|
389
|
+
const isExternal = recipientClass === "external";
|
|
390
|
+
|
|
391
|
+
// failPolicy: for a missing/unreadable REQUIRED policy file. External → block,
|
|
392
|
+
// internal → open (so transient FS faults never wall off internal ops chatter).
|
|
393
|
+
const failPolicy = (file) =>
|
|
394
|
+
isExternal
|
|
395
|
+
? blocked(
|
|
396
|
+
`outbound policy "${file}" unreadable — failing closed for external recipient (mirrors validate_outbound)`,
|
|
397
|
+
)
|
|
398
|
+
: allowed();
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
// (a) Banned phrases — pure, sourced from communication-style.md. If that
|
|
402
|
+
// file is unreadable we still apply the embedded constant list (the SoT
|
|
403
|
+
// is mirrored here), so a missing style file does NOT open the AI-tell
|
|
404
|
+
// gate; it only loses the ability to refresh the list.
|
|
405
|
+
const bannedReason = screenBannedPhrases(text);
|
|
406
|
+
if (bannedReason) return blocked(bannedReason);
|
|
407
|
+
|
|
408
|
+
// (b) AI-disclosure. The truthfulness invariant is enforced from the
|
|
409
|
+
// embedded denial list even without the file; posture resolution needs
|
|
410
|
+
// the file. Missing file → invariant still runs; posture defaults apply.
|
|
411
|
+
let disclosurePolicy = null;
|
|
412
|
+
const disc = readPolicy(fs, agentRoot, POLICY.disclosure);
|
|
413
|
+
if (disc.ok) {
|
|
414
|
+
try {
|
|
415
|
+
const yaml = await getYaml();
|
|
416
|
+
disclosurePolicy = yaml.load(disc.text) || null;
|
|
417
|
+
} catch {
|
|
418
|
+
// Malformed disclosure YAML: required policy → fail per posture.
|
|
419
|
+
const fp = failPolicy(POLICY.disclosure);
|
|
420
|
+
if (!fp.allow) return fp;
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
const fp = failPolicy(POLICY.disclosure);
|
|
424
|
+
if (!fp.allow) return fp;
|
|
425
|
+
}
|
|
426
|
+
const disclosureReason = screenDisclosure(text, {
|
|
427
|
+
policy: disclosurePolicy,
|
|
428
|
+
jurisdiction,
|
|
429
|
+
firstContact,
|
|
430
|
+
});
|
|
431
|
+
if (disclosureReason) return blocked(disclosureReason);
|
|
432
|
+
|
|
433
|
+
// (c) Information barriers (recipient/topic walls). Optional section.
|
|
434
|
+
let barriersPolicy = null;
|
|
435
|
+
const bar = readPolicy(fs, agentRoot, POLICY.barriers);
|
|
436
|
+
if (bar.ok) {
|
|
437
|
+
try {
|
|
438
|
+
const yaml = await getYaml();
|
|
439
|
+
barriersPolicy = yaml.load(bar.text) || null;
|
|
440
|
+
} catch {
|
|
441
|
+
const fp = failPolicy(POLICY.barriers);
|
|
442
|
+
if (!fp.allow) return fp;
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
const fp = failPolicy(POLICY.barriers);
|
|
446
|
+
if (!fp.allow) return fp;
|
|
447
|
+
}
|
|
448
|
+
const barrierReason = screenBarriers(text, {
|
|
449
|
+
policy: barriersPolicy,
|
|
450
|
+
recipient,
|
|
451
|
+
recipientClass,
|
|
452
|
+
});
|
|
453
|
+
if (barrierReason) return blocked(barrierReason);
|
|
454
|
+
|
|
455
|
+
// (d) Recipient allowlist (opt-in).
|
|
456
|
+
const allowReason = screenAllowlist(recipient, allowlist);
|
|
457
|
+
if (allowReason) return blocked(allowReason);
|
|
458
|
+
|
|
459
|
+
return allowed();
|
|
460
|
+
} catch (err) {
|
|
461
|
+
// Unexpected fault: fail closed for external, open for internal.
|
|
462
|
+
if (isExternal) {
|
|
463
|
+
return blocked(
|
|
464
|
+
`send-gate internal error — failing closed for external recipient: ${err && err.message ? err.message : String(err)}`,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
return allowed();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/* ─────────────────────────────────── CLI ─────────────────────────────────── */
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Parse `--flag value` / `--flag=value` argv into a plain object. Repeated flags
|
|
475
|
+
* (e.g. --internal-domain) accumulate into an array under the camelCase key.
|
|
476
|
+
*/
|
|
477
|
+
function parseArgs(argv) {
|
|
478
|
+
const out = {};
|
|
479
|
+
for (let i = 0; i < argv.length; i++) {
|
|
480
|
+
const a = argv[i];
|
|
481
|
+
if (!a.startsWith("--")) continue;
|
|
482
|
+
let key = a.slice(2);
|
|
483
|
+
let val;
|
|
484
|
+
const eq = key.indexOf("=");
|
|
485
|
+
if (eq >= 0) {
|
|
486
|
+
val = key.slice(eq + 1);
|
|
487
|
+
key = key.slice(0, eq);
|
|
488
|
+
} else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
|
|
489
|
+
val = argv[++i];
|
|
490
|
+
} else {
|
|
491
|
+
val = "true";
|
|
492
|
+
}
|
|
493
|
+
const camel = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
494
|
+
if (camel in out) {
|
|
495
|
+
out[camel] = Array.isArray(out[camel]) ? out[camel].concat(val) : [out[camel], val];
|
|
496
|
+
} else {
|
|
497
|
+
out[camel] = val;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function readStdin() {
|
|
504
|
+
try {
|
|
505
|
+
return nodeReadFileSync(0, "utf-8");
|
|
506
|
+
} catch {
|
|
507
|
+
return "";
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async function main(argv) {
|
|
512
|
+
const args = parseArgs(argv);
|
|
513
|
+
const channel = args.channel || "";
|
|
514
|
+
const recipient = args.recipient || "";
|
|
515
|
+
if (!channel) {
|
|
516
|
+
process.stderr.write("BLOCKED: --channel is required\n");
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
// text precedence: --text flag, else stdin.
|
|
520
|
+
const text = args.text != null ? args.text : readStdin();
|
|
521
|
+
const internalDomains = args.internalDomain
|
|
522
|
+
? [].concat(args.internalDomain)
|
|
523
|
+
: (process.env.MAESTRO_INTERNAL_DOMAINS || "")
|
|
524
|
+
.split(",")
|
|
525
|
+
.map((s) => s.trim())
|
|
526
|
+
.filter(Boolean);
|
|
527
|
+
const allowlist = args.allow ? [].concat(args.allow) : undefined;
|
|
528
|
+
const firstContact = args.firstContact === "false" ? false : true;
|
|
529
|
+
|
|
530
|
+
const result = await screenOutbound({
|
|
531
|
+
channel,
|
|
532
|
+
recipient,
|
|
533
|
+
text,
|
|
534
|
+
agentRoot: args.agentRoot || process.env.AGENT_DIR || process.cwd(),
|
|
535
|
+
jurisdiction: args.jurisdiction,
|
|
536
|
+
firstContact,
|
|
537
|
+
internalDomains,
|
|
538
|
+
allowlist,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
if (result.allow) {
|
|
542
|
+
if (result.redactedText) process.stdout.write(result.redactedText);
|
|
543
|
+
process.exit(0);
|
|
544
|
+
}
|
|
545
|
+
process.stderr.write(`BLOCKED: ${result.reason}\n`);
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Run as CLI only when invoked directly (not when imported).
|
|
550
|
+
const invokedDirectly =
|
|
551
|
+
process.argv[1] && process.argv[1].endsWith("send-gate.mjs");
|
|
552
|
+
if (invokedDirectly) {
|
|
553
|
+
main(process.argv.slice(2));
|
|
554
|
+
}
|