@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,392 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Send a properly threaded HTML email reply via Gmail SMTP.
|
|
3
|
+
|
|
4
|
+
Usage: python3 send-email-threaded.py <to> <subject> <body> [--reply-to-subject "original subject"] [--attachment file] [--force]
|
|
5
|
+
|
|
6
|
+
If --reply-to-subject is provided, fetches the real Message-IDs from the
|
|
7
|
+
thread via IMAP and includes them in In-Reply-To/References for proper Gmail threading.
|
|
8
|
+
|
|
9
|
+
If --attachment is provided, attaches the specified file (can be repeated for multiple files).
|
|
10
|
+
|
|
11
|
+
If --force is provided, bypasses dedup checks (use only for exceptional cases).
|
|
12
|
+
|
|
13
|
+
Pre-send dedup layers:
|
|
14
|
+
1. LLM-based dedup (llm_email_dedup.py) — reads recent conversation history with
|
|
15
|
+
recipient, asks Claude Haiku if the topic has already been addressed.
|
|
16
|
+
Replaces the old subject+recipient lock per CEO directive ib-20260406-001.
|
|
17
|
+
2. Gmail Sent folder check (IMAP) — double-check against actual sent mail
|
|
18
|
+
3. Content-hash dedup (outbound_dedup.py) — catch identical concurrent sends
|
|
19
|
+
"""
|
|
20
|
+
import sys
|
|
21
|
+
import os
|
|
22
|
+
import imaplib
|
|
23
|
+
import email
|
|
24
|
+
import smtplib
|
|
25
|
+
import mimetypes
|
|
26
|
+
from email.mime.multipart import MIMEMultipart
|
|
27
|
+
from email.mime.text import MIMEText
|
|
28
|
+
from email.mime.base import MIMEBase
|
|
29
|
+
from email import encoders
|
|
30
|
+
from email.utils import formatdate
|
|
31
|
+
|
|
32
|
+
# LLM-based dedup (Layer 1) — replaces old subject+recipient lock per CEO directive ib-20260406-001
|
|
33
|
+
try:
|
|
34
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
35
|
+
from llm_email_dedup import llm_dedup_check
|
|
36
|
+
_LLM_DEDUP_AVAILABLE = True
|
|
37
|
+
except ImportError:
|
|
38
|
+
_LLM_DEDUP_AVAILABLE = False
|
|
39
|
+
|
|
40
|
+
# Content-hash dedup (Layer 3) — fail-open if module not available
|
|
41
|
+
try:
|
|
42
|
+
from outbound_dedup import acquire_email_lock, confirm_email_lock
|
|
43
|
+
_DEDUP_AVAILABLE = True
|
|
44
|
+
except ImportError:
|
|
45
|
+
_DEDUP_AVAILABLE = False
|
|
46
|
+
|
|
47
|
+
# Pre-send factual validation (Layer 0 — runs before all dedup) — fail-open
|
|
48
|
+
try:
|
|
49
|
+
from validate_outbound import validate_message, log_validation_result
|
|
50
|
+
_VALIDATION_AVAILABLE = True
|
|
51
|
+
except ImportError:
|
|
52
|
+
_VALIDATION_AVAILABLE = False
|
|
53
|
+
|
|
54
|
+
# Thread quoting — include original email chain in replies — fail-open
|
|
55
|
+
try:
|
|
56
|
+
from email_quote_thread import fetch_thread_body, format_quoted_html
|
|
57
|
+
_QUOTE_AVAILABLE = True
|
|
58
|
+
except ImportError:
|
|
59
|
+
_QUOTE_AVAILABLE = False
|
|
60
|
+
|
|
61
|
+
# Pre-draft context enrichment (Phase 7) — advisory, never blocks — fail-open
|
|
62
|
+
try:
|
|
63
|
+
from pre_draft_lookup import pre_draft_lookup
|
|
64
|
+
_PRE_DRAFT_AVAILABLE = True
|
|
65
|
+
except ImportError:
|
|
66
|
+
_PRE_DRAFT_AVAILABLE = False
|
|
67
|
+
|
|
68
|
+
# ── Agent identity from config/agent.json (canonical SOT) ──────────────
|
|
69
|
+
# Loaded once at module load so every call picks up the running agent's
|
|
70
|
+
# email, full name, title, and phone without hardcoded values.
|
|
71
|
+
import json
|
|
72
|
+
from pathlib import Path
|
|
73
|
+
_AGENT_REPO_DIR = Path(os.environ.get("AGENT_DIR", str(Path(__file__).resolve().parent.parent)))
|
|
74
|
+
try:
|
|
75
|
+
_AGENT = json.loads((_AGENT_REPO_DIR / "config" / "agent.json").read_text())
|
|
76
|
+
except Exception:
|
|
77
|
+
_AGENT = {"firstName": "Agent", "fullName": "Agent", "email": "agent@example.com", "title": "Agent", "phone": ""}
|
|
78
|
+
|
|
79
|
+
USER = _AGENT.get("email", "agent@example.com")
|
|
80
|
+
PASSWORD = os.environ.get("GMAIL_APP_PASSWORD", "")
|
|
81
|
+
LOGO_URL = os.environ.get(
|
|
82
|
+
"EMAIL_SIGNATURE_LOGO_URL",
|
|
83
|
+
"https://ci3.googleusercontent.com/mail-sig/AIorK4xNDnPN6a0MjSHV5urb9g11u2CzHIzwdJbwN21quGjzQFdbOdegCq1Wp6lq3NCqsARbi-ooqPfE9E1D",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def _is_external(recipient: str) -> bool:
|
|
87
|
+
"""True if `recipient` is outside the agent's own email domain.
|
|
88
|
+
|
|
89
|
+
Used to fail Layer-0 validation CLOSED for outbound mail leaving the org
|
|
90
|
+
while keeping internal automation fail-open (Phase 0 item 0.5). Anything we
|
|
91
|
+
cannot confidently classify as internal is treated as external (safe
|
|
92
|
+
default): no '@', a different domain, or an unparseable address all return
|
|
93
|
+
True.
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
addr = (recipient or "").strip().lower()
|
|
97
|
+
if "@" not in addr:
|
|
98
|
+
return True
|
|
99
|
+
recipient_domain = addr.rsplit("@", 1)[1]
|
|
100
|
+
own = (USER or "").strip().lower()
|
|
101
|
+
own_domain = own.rsplit("@", 1)[1] if "@" in own else ""
|
|
102
|
+
if not own_domain:
|
|
103
|
+
return True # cannot establish our own domain → treat as external
|
|
104
|
+
return recipient_domain != own_domain
|
|
105
|
+
except Exception:
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_thread_message_ids(subject_search):
|
|
110
|
+
"""Fetch real Message-IDs from Gmail via IMAP for threading."""
|
|
111
|
+
mail = imaplib.IMAP4_SSL("imap.gmail.com")
|
|
112
|
+
mail.login(USER, PASSWORD)
|
|
113
|
+
mail.select("INBOX")
|
|
114
|
+
|
|
115
|
+
# Search by subject — normalize non-ASCII chars for IMAP compatibility
|
|
116
|
+
clean_subject = subject_search.replace("Re: ", "").replace("RE: ", "").strip()
|
|
117
|
+
# Replace common non-ASCII characters that break IMAP search
|
|
118
|
+
ascii_subject = clean_subject.replace('\u2014', '-').replace('\u2013', '-').replace('\u2018', "'").replace('\u2019', "'").replace('\u201c', '"').replace('\u201d', '"')
|
|
119
|
+
status, messages = mail.search(None, f'(SUBJECT "{ascii_subject}")')
|
|
120
|
+
msg_ids = []
|
|
121
|
+
if status == "OK" and messages[0]:
|
|
122
|
+
for num in messages[0].split():
|
|
123
|
+
status, data = mail.fetch(num, '(RFC822.HEADER)')
|
|
124
|
+
if status == "OK":
|
|
125
|
+
hdr = email.message_from_string(data[0][1].decode('utf-8', errors='replace'))
|
|
126
|
+
mid = hdr.get('Message-ID', '')
|
|
127
|
+
if mid:
|
|
128
|
+
msg_ids.append(mid)
|
|
129
|
+
mail.logout()
|
|
130
|
+
return msg_ids
|
|
131
|
+
|
|
132
|
+
def mark_as_read(sender):
|
|
133
|
+
"""Mark emails from sender as read."""
|
|
134
|
+
mail = imaplib.IMAP4_SSL("imap.gmail.com")
|
|
135
|
+
mail.login(USER, PASSWORD)
|
|
136
|
+
mail.select("INBOX")
|
|
137
|
+
status, messages = mail.search(None, f'(UNSEEN FROM "{sender}")')
|
|
138
|
+
if status == "OK" and messages[0]:
|
|
139
|
+
for num in messages[0].split():
|
|
140
|
+
mail.store(num, '+FLAGS', '\\Seen')
|
|
141
|
+
mail.logout()
|
|
142
|
+
|
|
143
|
+
def build_signature_html():
|
|
144
|
+
full_name = _AGENT.get("fullName", "Agent")
|
|
145
|
+
title = _AGENT.get("title", "")
|
|
146
|
+
phone = _AGENT.get("phone", "")
|
|
147
|
+
phone_line = f'<div style="font-size:x-small;color:#000;">{phone}</div>\n<div><br></div>\n' if phone else ""
|
|
148
|
+
address = os.environ.get(
|
|
149
|
+
"EMAIL_SIGNATURE_ADDRESS",
|
|
150
|
+
"Level 1, Innovation One, Dubai International Financial Centre, Dubai, UAE",
|
|
151
|
+
)
|
|
152
|
+
return f"""<div>
|
|
153
|
+
<div style="font-size:small;"><b>{full_name}</b></div>
|
|
154
|
+
<div style="font-size:small;">{title}</div>
|
|
155
|
+
<div><br></div>
|
|
156
|
+
<div><img src="{LOGO_URL}" width="125"></div>
|
|
157
|
+
<div><br></div>
|
|
158
|
+
{phone_line}<div style="font-size:x-small;color:#000;">{address}</div>
|
|
159
|
+
<div style="font-size:x-small;color:#666;">_________________________________________</div>
|
|
160
|
+
<div style="font-size:x-small;color:#999;">This email and any files transmitted with it are confidential and intended solely for the use of the individual or entity to whom they are addressed. If you have received this email in error please notify the sender. This message contains confidential information and is intended only for the individual named. If you are not the named addressee you should not disseminate, distribute or copy this email. Please notify the sender immediately by email if you have received this email by mistake and delete this email from your system. If you are not the intended recipient you are notified that disclosing, copying, distributing, or taking any action in reliance on the contents of this information is strictly prohibited.</div>
|
|
161
|
+
</div>"""
|
|
162
|
+
|
|
163
|
+
def attach_file(msg, filepath):
|
|
164
|
+
"""Attach a file to the email message."""
|
|
165
|
+
if not os.path.exists(filepath):
|
|
166
|
+
print(f"WARNING: Attachment not found: {filepath}", file=sys.stderr)
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
filename = os.path.basename(filepath)
|
|
170
|
+
content_type, encoding = mimetypes.guess_type(filepath)
|
|
171
|
+
if content_type is None:
|
|
172
|
+
content_type = "application/octet-stream"
|
|
173
|
+
main_type, sub_type = content_type.split("/", 1)
|
|
174
|
+
|
|
175
|
+
with open(filepath, "rb") as f:
|
|
176
|
+
file_data = f.read()
|
|
177
|
+
|
|
178
|
+
attachment = MIMEBase(main_type, sub_type)
|
|
179
|
+
attachment.set_payload(file_data)
|
|
180
|
+
encoders.encode_base64(attachment)
|
|
181
|
+
attachment.add_header("Content-Disposition", "attachment", filename=filename)
|
|
182
|
+
msg.attach(attachment)
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def check_already_sent(to, subject):
|
|
187
|
+
"""Check Gmail Sent Mail for a recent email to the same recipient with same subject.
|
|
188
|
+
Returns True if a matching email was sent in the last 24 hours."""
|
|
189
|
+
try:
|
|
190
|
+
mail = imaplib.IMAP4_SSL("imap.gmail.com")
|
|
191
|
+
mail.login(USER, PASSWORD)
|
|
192
|
+
mail.select('"[Gmail]/Sent Mail"')
|
|
193
|
+
# Search for emails to this recipient with this subject
|
|
194
|
+
clean_subject = subject.replace("RE: ", "").replace("Re: ", "").strip()
|
|
195
|
+
# Normalize non-ASCII characters for IMAP compatibility
|
|
196
|
+
clean_subject = clean_subject.replace('\u2014', '-').replace('\u2013', '-').replace('\u2018', "'").replace('\u2019', "'").replace('\u201c', '"').replace('\u201d', '"')
|
|
197
|
+
# Search sent mail for matching subject in last 24 hours
|
|
198
|
+
from datetime import datetime, timedelta
|
|
199
|
+
since_date = (datetime.now() - timedelta(hours=24)).strftime("%d-%b-%Y")
|
|
200
|
+
status, messages = mail.search(None, f'(SINCE "{since_date}" TO "{to}" SUBJECT "{clean_subject}")')
|
|
201
|
+
found = status == "OK" and messages[0] and len(messages[0].split()) > 0
|
|
202
|
+
count = len(messages[0].split()) if found else 0
|
|
203
|
+
mail.logout()
|
|
204
|
+
if found:
|
|
205
|
+
print(f"SENT_MAIL_DEDUP: Found {count} matching email(s) to {to} with subject containing '{clean_subject}' in last 24h", file=sys.stderr)
|
|
206
|
+
return found
|
|
207
|
+
except Exception as e:
|
|
208
|
+
print(f"SENT_MAIL_CHECK_ERROR: {e}", file=sys.stderr)
|
|
209
|
+
return False # fail-open
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def send_email(to, subject, body, reply_subject=None, attachments=None, cc=None, force=False):
|
|
213
|
+
# ── Pre-draft context enrichment (Phase 7) ─────────────────────
|
|
214
|
+
# Look up recipient in entity index. Advisory only — never blocks sending.
|
|
215
|
+
# Logs context to logs/audit/{date}-pre-draft-lookups.jsonl
|
|
216
|
+
if _PRE_DRAFT_AVAILABLE:
|
|
217
|
+
try:
|
|
218
|
+
_ctx = pre_draft_lookup(to, message_type="email")
|
|
219
|
+
if _ctx and _ctx.get("communication", {}).get("standing_instructions"):
|
|
220
|
+
print(f"PRE_DRAFT: {len(_ctx['communication']['standing_instructions'])} standing instruction(s) for {to}", file=sys.stderr)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
print(f"PRE_DRAFT_WARN (fail-open): {e}", file=sys.stderr)
|
|
223
|
+
|
|
224
|
+
# ── Layer 0: Pre-send factual validation ────────────────────────
|
|
225
|
+
# Checks relationship claims, title accuracy, AI disclosure, in-person scheduling.
|
|
226
|
+
# Blocks send if critical issues found (severity=block).
|
|
227
|
+
# Fail-CLOSED for external recipients (Phase 0 item 0.5): if the validator
|
|
228
|
+
# is unavailable or errors, an external send is refused rather than silently
|
|
229
|
+
# skipping Layer-0 compliance. Internal/forced sends remain fail-open.
|
|
230
|
+
_external = _is_external(to)
|
|
231
|
+
if body and not _VALIDATION_AVAILABLE:
|
|
232
|
+
if _external and not force:
|
|
233
|
+
print(f"BLOCKED: outbound validator unavailable — refusing external send to {to} (fail closed). Use --force to override.", file=sys.stderr)
|
|
234
|
+
return
|
|
235
|
+
print("VALIDATION_SKIP: validator unavailable (internal/forced send — fail-open)", file=sys.stderr)
|
|
236
|
+
if _VALIDATION_AVAILABLE and body:
|
|
237
|
+
try:
|
|
238
|
+
validation_result = validate_message(body, "email", to)
|
|
239
|
+
log_validation_result(validation_result)
|
|
240
|
+
if validation_result["metadata"]["blockers"] > 0:
|
|
241
|
+
print(f"VALIDATION_BLOCK: {validation_result['metadata']['blockers']} issue(s) found in message to {to}")
|
|
242
|
+
for issue in validation_result["issues"]:
|
|
243
|
+
if issue["severity"] == "block":
|
|
244
|
+
print(f" [{issue['type']}] {issue['description']}")
|
|
245
|
+
if issue.get("suggestion"):
|
|
246
|
+
print(f" -> {issue['suggestion']}")
|
|
247
|
+
print("Fix the above issues before sending. Use --force to override (not recommended).", file=sys.stderr)
|
|
248
|
+
if not force:
|
|
249
|
+
return
|
|
250
|
+
else:
|
|
251
|
+
print("FORCE_OVERRIDE: Bypassing validation block", file=sys.stderr)
|
|
252
|
+
elif validation_result["metadata"]["warnings"] > 0:
|
|
253
|
+
print(f"VALIDATION_WARN: {validation_result['metadata']['warnings']} warning(s) for message to {to}", file=sys.stderr)
|
|
254
|
+
for issue in validation_result["issues"]:
|
|
255
|
+
if issue["severity"] == "warn":
|
|
256
|
+
print(f" [{issue['type']}] {issue['description']}", file=sys.stderr)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
print(f"VALIDATION_ERROR: {e}", file=sys.stderr)
|
|
259
|
+
if _external and not force:
|
|
260
|
+
print(f"BLOCKED: validator error on external send to {to} (fail closed). Use --force to override.", file=sys.stderr)
|
|
261
|
+
return
|
|
262
|
+
print("VALIDATION_ERROR (fail-open: internal/forced send)", file=sys.stderr)
|
|
263
|
+
|
|
264
|
+
# ── Layer 1: LLM-based dedup ──────────────────────────────────────
|
|
265
|
+
# Reads recent conversation history with the recipient, asks Claude Haiku
|
|
266
|
+
# whether this topic has already been addressed. Replaces the old
|
|
267
|
+
# subject+recipient lock per CEO directive ib-20260406-001.
|
|
268
|
+
if _LLM_DEDUP_AVAILABLE and not force:
|
|
269
|
+
try:
|
|
270
|
+
llm_result = llm_dedup_check(to, subject, body)
|
|
271
|
+
if llm_result["skip"]:
|
|
272
|
+
print(f"DEDUP_SKIP (LLM): {llm_result['reason']} [confidence={llm_result['confidence']}, msgs_checked={llm_result['recent_messages']}]")
|
|
273
|
+
return
|
|
274
|
+
else:
|
|
275
|
+
print(f"LLM_DEDUP_OK: {llm_result['reason'][:100]} [msgs_checked={llm_result['recent_messages']}]", file=sys.stderr)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
print(f"LLM_DEDUP_WARN (fail-open): {e}", file=sys.stderr)
|
|
278
|
+
elif force and _LLM_DEDUP_AVAILABLE:
|
|
279
|
+
print(f"FORCE_OVERRIDE: Bypassing LLM dedup for '{subject}' to {to}", file=sys.stderr)
|
|
280
|
+
|
|
281
|
+
# ── Layer 2: Gmail Sent folder check ─────────────────────────────
|
|
282
|
+
# Double-check against actual sent mail via IMAP
|
|
283
|
+
if not force and check_already_sent(to, subject):
|
|
284
|
+
print(f"DEDUP_SKIP (sent mail check): Already sent to {to} with subject '{subject}' in last 24h")
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
# ── Layer 3: Content-hash dedup ──────────────────────────────────
|
|
288
|
+
# Catches identical concurrent sends (same content hash)
|
|
289
|
+
_dedup_key = None
|
|
290
|
+
if _DEDUP_AVAILABLE and not force:
|
|
291
|
+
try:
|
|
292
|
+
_dedup_key, _dedup_result = acquire_email_lock(to, subject, body)
|
|
293
|
+
if _dedup_result == "DEDUP_SKIP":
|
|
294
|
+
print("DEDUP_SKIP (content hash)")
|
|
295
|
+
return
|
|
296
|
+
except Exception:
|
|
297
|
+
pass # fail-open
|
|
298
|
+
|
|
299
|
+
has_attachments = attachments and len(attachments) > 0
|
|
300
|
+
|
|
301
|
+
if has_attachments:
|
|
302
|
+
msg = MIMEMultipart('mixed')
|
|
303
|
+
else:
|
|
304
|
+
msg = MIMEMultipart('alternative')
|
|
305
|
+
|
|
306
|
+
msg['From'] = f"{_AGENT.get('fullName', 'Agent')} <{USER}>"
|
|
307
|
+
msg['To'] = to
|
|
308
|
+
if cc:
|
|
309
|
+
msg['Cc'] = cc
|
|
310
|
+
msg['Subject'] = subject
|
|
311
|
+
msg['Date'] = formatdate(localtime=True)
|
|
312
|
+
|
|
313
|
+
# Add threading headers if replying
|
|
314
|
+
if reply_subject:
|
|
315
|
+
thread_ids = get_thread_message_ids(reply_subject)
|
|
316
|
+
if thread_ids:
|
|
317
|
+
msg['In-Reply-To'] = thread_ids[-1]
|
|
318
|
+
msg['References'] = ' '.join(thread_ids)
|
|
319
|
+
|
|
320
|
+
# Fetch quoted thread content for replies
|
|
321
|
+
quoted_html = ""
|
|
322
|
+
if reply_subject and _QUOTE_AVAILABLE:
|
|
323
|
+
try:
|
|
324
|
+
thread_msg = fetch_thread_body(USER, PASSWORD, reply_subject, exclude_from=USER)
|
|
325
|
+
quoted_html = format_quoted_html(thread_msg)
|
|
326
|
+
except Exception as e:
|
|
327
|
+
print(f"QUOTE_WARN: {e}", file=sys.stderr)
|
|
328
|
+
|
|
329
|
+
# Build HTML
|
|
330
|
+
body_html = body.replace('\n', '<br>')
|
|
331
|
+
html = f"""<html><body style="font-family:Arial,Helvetica,sans-serif;font-size:small;color:#000;">
|
|
332
|
+
<div>{body_html}</div>
|
|
333
|
+
<br>
|
|
334
|
+
{build_signature_html()}
|
|
335
|
+
{quoted_html}
|
|
336
|
+
</body></html>"""
|
|
337
|
+
|
|
338
|
+
if has_attachments:
|
|
339
|
+
html_part = MIMEMultipart('alternative')
|
|
340
|
+
html_part.attach(MIMEText(html, 'html'))
|
|
341
|
+
msg.attach(html_part)
|
|
342
|
+
for filepath in attachments:
|
|
343
|
+
if attach_file(msg, filepath):
|
|
344
|
+
print(f"Attached: {os.path.basename(filepath)}")
|
|
345
|
+
else:
|
|
346
|
+
msg.attach(MIMEText(html, 'html'))
|
|
347
|
+
|
|
348
|
+
# Send
|
|
349
|
+
with smtplib.SMTP('smtp.gmail.com', 587) as server:
|
|
350
|
+
server.starttls()
|
|
351
|
+
server.login(USER, PASSWORD)
|
|
352
|
+
server.send_message(msg)
|
|
353
|
+
|
|
354
|
+
# Mark sender's emails as read
|
|
355
|
+
sender_domain = to.split('@')[-1] if '@' in to else ''
|
|
356
|
+
if sender_domain:
|
|
357
|
+
mark_as_read(to)
|
|
358
|
+
|
|
359
|
+
print(f"Sent to {to}, subject: {subject}")
|
|
360
|
+
if cc:
|
|
361
|
+
print(f" CC: {cc}")
|
|
362
|
+
if has_attachments:
|
|
363
|
+
print(f" Attachments: {len(attachments)}")
|
|
364
|
+
|
|
365
|
+
# Post-send: confirm content-hash lock (audit trail)
|
|
366
|
+
if _DEDUP_AVAILABLE:
|
|
367
|
+
try:
|
|
368
|
+
confirm_email_lock(_dedup_key, f"To:{to} Subj:{subject}", to=to, subject=subject)
|
|
369
|
+
except Exception:
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
if __name__ == '__main__':
|
|
373
|
+
if len(sys.argv) < 4 and '--help' not in sys.argv and '-h' not in sys.argv:
|
|
374
|
+
print("Usage: send-email-threaded.py <to> <subject> <body> [--reply-to-subject 'original subject'] [--attachment file]")
|
|
375
|
+
sys.exit(1)
|
|
376
|
+
|
|
377
|
+
import argparse
|
|
378
|
+
parser = argparse.ArgumentParser(description=f"Send threaded HTML email as {_AGENT.get('fullName', 'the agent')} via Gmail SMTP")
|
|
379
|
+
parser.add_argument("to", help="Recipient email address")
|
|
380
|
+
parser.add_argument("subject", help="Email subject line")
|
|
381
|
+
parser.add_argument("body", help="Email body text (newlines become <br> in HTML)")
|
|
382
|
+
parser.add_argument("--reply-to-subject", dest="reply_subject",
|
|
383
|
+
help="Original subject for threading (fetches Message-IDs via IMAP)")
|
|
384
|
+
parser.add_argument("--cc", dest="cc",
|
|
385
|
+
help="CC recipient email address(es)")
|
|
386
|
+
parser.add_argument("--attachment", action="append", dest="attachments",
|
|
387
|
+
help="File to attach (can be specified multiple times)")
|
|
388
|
+
parser.add_argument("--force", action="store_true", default=False,
|
|
389
|
+
help="Override thread dedup — use only for exceptional resends")
|
|
390
|
+
args = parser.parse_args()
|
|
391
|
+
|
|
392
|
+
send_email(args.to, args.subject, args.body, args.reply_subject, args.attachments, args.cc, args.force)
|