@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,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Slack Reaction Utility — Add emoji reactions to Slack messages.
|
|
4
|
+
*
|
|
5
|
+
* Used by the agent's Claude Code sessions to react to messages.
|
|
6
|
+
* This is a thin wrapper around the Slack reactions.add API.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/slack-react.mjs <channel_id> <message_ts> <emoji_name>
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* node scripts/slack-react.mjs C0ARASMQE1W 1775472843.039059 eyes
|
|
13
|
+
* node scripts/slack-react.mjs D099N1JGKRQ 1775472955.681459 white_check_mark
|
|
14
|
+
*
|
|
15
|
+
* Common emoji names:
|
|
16
|
+
* eyes — acknowledged / looking into it
|
|
17
|
+
* white_check_mark — done / approved
|
|
18
|
+
* thumbsup — agreed / positive
|
|
19
|
+
* thumbsdown — disagreed / negative
|
|
20
|
+
* hourglass_flowing_sand — working on it
|
|
21
|
+
* heavy_check_mark — completed
|
|
22
|
+
* warning — flagged for attention
|
|
23
|
+
* rocket — shipped / deployed
|
|
24
|
+
* brain — thinking / reviewing
|
|
25
|
+
*
|
|
26
|
+
* Exit codes:
|
|
27
|
+
* 0 — success
|
|
28
|
+
* 1 — error (missing args, API failure)
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { readFileSync, existsSync } from "fs";
|
|
32
|
+
import { join, dirname } from "path";
|
|
33
|
+
import { fileURLToPath } from "url";
|
|
34
|
+
|
|
35
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const ROOT = join(__dirname, "..");
|
|
37
|
+
|
|
38
|
+
// Load .env
|
|
39
|
+
const envPath = join(ROOT, ".env");
|
|
40
|
+
if (existsSync(envPath)) {
|
|
41
|
+
const envContent = readFileSync(envPath, "utf-8");
|
|
42
|
+
for (const line of envContent.split("\n")) {
|
|
43
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
44
|
+
if (match) {
|
|
45
|
+
let val = match[2].trim();
|
|
46
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
47
|
+
val = val.slice(1, -1);
|
|
48
|
+
}
|
|
49
|
+
process.env[match[1]] = val;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const [channel, timestamp, emoji] = process.argv.slice(2);
|
|
55
|
+
|
|
56
|
+
if (!channel || !timestamp || !emoji) {
|
|
57
|
+
console.error("Usage: node scripts/slack-react.mjs <channel_id> <message_ts> <emoji_name>");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const token = process.env.SLACK_BOT_TOKEN || process.env.SLACK_USER_TOKEN || process.env.SLACK_TOKEN;
|
|
62
|
+
if (!token) {
|
|
63
|
+
console.error("Error: No Slack token found (SLACK_BOT_TOKEN, SLACK_USER_TOKEN, or SLACK_TOKEN)");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch("https://slack.com/api/reactions.add", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: {
|
|
71
|
+
Authorization: `Bearer ${token}`,
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({ channel, timestamp, name: emoji }),
|
|
75
|
+
});
|
|
76
|
+
const body = await res.json();
|
|
77
|
+
|
|
78
|
+
if (body.ok) {
|
|
79
|
+
console.log(`Reacted with :${emoji}: on ${channel}/${timestamp}`);
|
|
80
|
+
} else if (body.error === "already_reacted") {
|
|
81
|
+
console.log(`Already reacted with :${emoji}: (no-op)`);
|
|
82
|
+
} else {
|
|
83
|
+
console.error(`Slack API error: ${body.error}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error(`Error: ${err.message}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# slack-responded.sh — Atomic response deduplication for Slack messages
|
|
3
|
+
# Prevents duplicate responses from concurrent sessions using mkdir-based locking
|
|
4
|
+
#
|
|
5
|
+
# The key insight: `mkdir` is atomic on POSIX systems. Two concurrent processes
|
|
6
|
+
# calling `mkdir` on the same path will have exactly one succeed and one fail.
|
|
7
|
+
# This eliminates the race condition in the old check-then-write approach.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./scripts/slack-responded.sh acquire <channel_id> <message_ts> [session_id]
|
|
11
|
+
# → exits 0 if lock acquired (you should send), 1 if already locked (skip)
|
|
12
|
+
# → On success, creates lock dir + metadata file atomically
|
|
13
|
+
#
|
|
14
|
+
# ./scripts/slack-responded.sh confirm <channel_id> <message_ts> [message_preview]
|
|
15
|
+
# → Called after successful send to record details in the lock
|
|
16
|
+
#
|
|
17
|
+
# ./scripts/slack-responded.sh check <message_ts> [channel_id]
|
|
18
|
+
# → exits 0 if already handled, 1 if not (backward-compatible)
|
|
19
|
+
#
|
|
20
|
+
# ./scripts/slack-responded.sh mark <message_ts>
|
|
21
|
+
# → marks message as handled (backward-compatible, legacy)
|
|
22
|
+
#
|
|
23
|
+
# ./scripts/slack-responded.sh clean
|
|
24
|
+
# → purge entries older than 24h
|
|
25
|
+
set -e
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
28
|
+
AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
29
|
+
|
|
30
|
+
LOCK_BASE_DIR="$AGENT_REPO_DIR/state/locks/slack-response"
|
|
31
|
+
LEGACY_DIR="$AGENT_REPO_DIR/state/slack-responded"
|
|
32
|
+
LOCK_TTL_MINUTES=1440 # 24 hours — thread-level dedup needs long TTL to prevent cross-session duplicates
|
|
33
|
+
|
|
34
|
+
mkdir -p "$LOCK_BASE_DIR"
|
|
35
|
+
mkdir -p "$LEGACY_DIR"
|
|
36
|
+
|
|
37
|
+
ACTION="${1:-}"
|
|
38
|
+
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
39
|
+
|
|
40
|
+
# Helper: check if a lock path (directory or file) is still valid (< LOCK_TTL_MINUTES old)
|
|
41
|
+
lock_is_valid() {
|
|
42
|
+
local lock_path="$1"
|
|
43
|
+
if [ ! -e "$lock_path" ]; then
|
|
44
|
+
return 1
|
|
45
|
+
fi
|
|
46
|
+
# Check age of lock path (works for both directories and files)
|
|
47
|
+
local lock_age
|
|
48
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
49
|
+
lock_age=$(( ( $(date +%s) - $(stat -f %m "$lock_path") ) / 60 ))
|
|
50
|
+
else
|
|
51
|
+
lock_age=$(( ( $(date +%s) - $(stat -c %Y "$lock_path") ) / 60 ))
|
|
52
|
+
fi
|
|
53
|
+
[ "$lock_age" -lt "$LOCK_TTL_MINUTES" ]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case "$ACTION" in
|
|
57
|
+
acquire)
|
|
58
|
+
# acquire <channel_id> <message_ts> [session_id]
|
|
59
|
+
# Attempts to atomically acquire a response lock.
|
|
60
|
+
# Returns 0 if lock acquired (caller should proceed to send).
|
|
61
|
+
# Returns 1 if lock already held (caller should skip sending).
|
|
62
|
+
CHANNEL="${2:?Channel ID required}"
|
|
63
|
+
MSG_TS="${3:?Message TS required}"
|
|
64
|
+
SESSION_ID="${4:-unknown}"
|
|
65
|
+
|
|
66
|
+
LOCK_DIR="$LOCK_BASE_DIR/${CHANNEL}-${MSG_TS}"
|
|
67
|
+
|
|
68
|
+
# Check for existing valid lock
|
|
69
|
+
if lock_is_valid "$LOCK_DIR"; then
|
|
70
|
+
echo "LOCKED: Response already claimed for $CHANNEL:$MSG_TS"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# If stale lock exists, remove it
|
|
75
|
+
if [ -d "$LOCK_DIR" ]; then
|
|
76
|
+
rm -rf "$LOCK_DIR"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Atomic lock acquisition via mkdir
|
|
80
|
+
# mkdir will fail if another process creates it between our check and this call
|
|
81
|
+
if mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
82
|
+
# Lock acquired — write metadata
|
|
83
|
+
cat > "$LOCK_DIR/meta" << EOF
|
|
84
|
+
acquired: "$TIMESTAMP"
|
|
85
|
+
session: "$SESSION_ID"
|
|
86
|
+
channel: "$CHANNEL"
|
|
87
|
+
message_ts: "$MSG_TS"
|
|
88
|
+
status: acquired
|
|
89
|
+
EOF
|
|
90
|
+
# Also write legacy marker for backward compatibility
|
|
91
|
+
echo "$TIMESTAMP" > "$LEGACY_DIR/$MSG_TS"
|
|
92
|
+
echo "ACQUIRED"
|
|
93
|
+
exit 0
|
|
94
|
+
else
|
|
95
|
+
# Another process beat us to it
|
|
96
|
+
echo "LOCKED: Response already claimed for $CHANNEL:$MSG_TS (race lost)"
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
;;
|
|
100
|
+
|
|
101
|
+
confirm)
|
|
102
|
+
# confirm <channel_id> <message_ts> [message_preview]
|
|
103
|
+
# Called after a successful send to record what was sent.
|
|
104
|
+
CHANNEL="${2:?Channel ID required}"
|
|
105
|
+
MSG_TS="${3:?Message TS required}"
|
|
106
|
+
PREVIEW="${4:-}"
|
|
107
|
+
|
|
108
|
+
LOCK_DIR="$LOCK_BASE_DIR/${CHANNEL}-${MSG_TS}"
|
|
109
|
+
|
|
110
|
+
if [ -d "$LOCK_DIR" ]; then
|
|
111
|
+
# Preserve the original acquisition timestamp
|
|
112
|
+
ORIG_ACQUIRED=$(grep 'acquired:' "$LOCK_DIR/meta" 2>/dev/null | head -1 | sed 's/^acquired: *//' | tr -d '"' || echo "$TIMESTAMP")
|
|
113
|
+
if [ -z "$ORIG_ACQUIRED" ]; then ORIG_ACQUIRED="$TIMESTAMP"; fi
|
|
114
|
+
SAFE_PREVIEW=$(echo "$PREVIEW" | head -c 200 | tr '"' "'")
|
|
115
|
+
cat > "$LOCK_DIR/meta" << EOF
|
|
116
|
+
acquired: "$ORIG_ACQUIRED"
|
|
117
|
+
confirmed: "$TIMESTAMP"
|
|
118
|
+
channel: "$CHANNEL"
|
|
119
|
+
message_ts: "$MSG_TS"
|
|
120
|
+
status: confirmed
|
|
121
|
+
preview: "$SAFE_PREVIEW"
|
|
122
|
+
EOF
|
|
123
|
+
fi
|
|
124
|
+
# Update legacy marker
|
|
125
|
+
echo "$TIMESTAMP" > "$LEGACY_DIR/$MSG_TS"
|
|
126
|
+
echo "CONFIRMED"
|
|
127
|
+
;;
|
|
128
|
+
|
|
129
|
+
check)
|
|
130
|
+
# check <message_ts> [channel_id]
|
|
131
|
+
# Backward-compatible check. Returns 0 if already handled, 1 if not.
|
|
132
|
+
MSG_TS="${2:?Message TS required}"
|
|
133
|
+
CHANNEL="${3:-}"
|
|
134
|
+
|
|
135
|
+
# If channel is provided, check the atomic lock first
|
|
136
|
+
if [ -n "$CHANNEL" ]; then
|
|
137
|
+
LOCK_DIR="$LOCK_BASE_DIR/${CHANNEL}-${MSG_TS}"
|
|
138
|
+
if lock_is_valid "$LOCK_DIR"; then
|
|
139
|
+
exit 0 # Already handled
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Also check any lock matching this message_ts (across all channels)
|
|
144
|
+
for lock in "$LOCK_BASE_DIR"/*-"${MSG_TS}"; do
|
|
145
|
+
if [ -d "$lock" ] && lock_is_valid "$lock"; then
|
|
146
|
+
exit 0 # Already handled
|
|
147
|
+
fi
|
|
148
|
+
done
|
|
149
|
+
|
|
150
|
+
# Fall back to legacy flat-file check
|
|
151
|
+
if [ -f "$LEGACY_DIR/$MSG_TS" ]; then
|
|
152
|
+
if lock_is_valid "$LEGACY_DIR/$MSG_TS"; then
|
|
153
|
+
exit 0 # Recently handled
|
|
154
|
+
fi
|
|
155
|
+
# Legacy file exists but is old — still treat as handled for backward compat
|
|
156
|
+
exit 0
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
exit 1 # Not yet handled
|
|
160
|
+
;;
|
|
161
|
+
|
|
162
|
+
release)
|
|
163
|
+
# release <channel_id> <message_ts> [session_id]
|
|
164
|
+
# Releases an unconfirmed lock so a legitimate retry isn't blocked by a
|
|
165
|
+
# 24h TTL. Called by slack-send.sh's exit trap when a send fails or the
|
|
166
|
+
# script crashes after acquiring the lock but before confirming the send.
|
|
167
|
+
#
|
|
168
|
+
# Safety:
|
|
169
|
+
# - Refuse to release if the lock has been confirmed (real send happened)
|
|
170
|
+
# - If session_id is provided, only release locks owned by that session
|
|
171
|
+
# (prevents a buggy caller from blowing away a sibling's in-flight lock)
|
|
172
|
+
#
|
|
173
|
+
# Lock metadata is stored in `meta` (acquire/confirm both write to it)
|
|
174
|
+
# using the format produced by the acquire/confirm sections above:
|
|
175
|
+
# session: "<id>"
|
|
176
|
+
# status: acquired (or confirmed)
|
|
177
|
+
CHANNEL="${2:?Channel ID required}"
|
|
178
|
+
MSG_TS="${3:?Message TS required}"
|
|
179
|
+
SESSION_ID="${4:-unknown}"
|
|
180
|
+
|
|
181
|
+
LOCK_DIR="$LOCK_BASE_DIR/${CHANNEL}-${MSG_TS}"
|
|
182
|
+
META="$LOCK_DIR/meta"
|
|
183
|
+
|
|
184
|
+
if [ ! -d "$LOCK_DIR" ]; then
|
|
185
|
+
echo "NOT_HELD"
|
|
186
|
+
exit 0
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if [ -f "$META" ] && grep -qE "^status:[[:space:]]*confirmed" "$META" 2>/dev/null; then
|
|
190
|
+
echo "ALREADY_CONFIRMED"
|
|
191
|
+
exit 0
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
if [ -f "$META" ] && [ "$SESSION_ID" != "unknown" ]; then
|
|
195
|
+
if ! grep -qE "^session:[[:space:]]*\"?${SESSION_ID}\"?[[:space:]]*$" "$META" 2>/dev/null; then
|
|
196
|
+
echo "NOT_OWNED"
|
|
197
|
+
exit 0
|
|
198
|
+
fi
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
rm -rf "$LOCK_DIR"
|
|
202
|
+
echo "RELEASED"
|
|
203
|
+
;;
|
|
204
|
+
|
|
205
|
+
mark)
|
|
206
|
+
# mark <message_ts> — backward-compatible mark
|
|
207
|
+
MSG_TS="${2:?Message TS required}"
|
|
208
|
+
echo "$TIMESTAMP" > "$LEGACY_DIR/$MSG_TS"
|
|
209
|
+
;;
|
|
210
|
+
|
|
211
|
+
clean)
|
|
212
|
+
# Purge entries older than 24 hours
|
|
213
|
+
# Clean lock directories
|
|
214
|
+
find "$LOCK_BASE_DIR" -maxdepth 1 -type d -mmin +1440 -exec rm -rf {} + 2>/dev/null || true
|
|
215
|
+
# Clean legacy flat files
|
|
216
|
+
find "$LEGACY_DIR" -type f -mmin +1440 -delete 2>/dev/null || true
|
|
217
|
+
echo "Cleaned entries older than 24h"
|
|
218
|
+
;;
|
|
219
|
+
|
|
220
|
+
*)
|
|
221
|
+
echo "Usage: slack-responded.sh {acquire|confirm|release|check|mark|clean} [args...]"
|
|
222
|
+
echo ""
|
|
223
|
+
echo "Commands:"
|
|
224
|
+
echo " acquire <channel> <msg_ts> [session_id] — Atomically claim a response lock"
|
|
225
|
+
echo " confirm <channel> <msg_ts> [preview] — Record successful send"
|
|
226
|
+
echo " release <channel> <msg_ts> [session_id] — Release unconfirmed lock (send failed)"
|
|
227
|
+
echo " check <msg_ts> [channel] — Check if already handled"
|
|
228
|
+
echo " mark <msg_ts> — Legacy: mark as handled"
|
|
229
|
+
echo " clean — Purge old entries"
|
|
230
|
+
exit 2
|
|
231
|
+
;;
|
|
232
|
+
esac
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# slack-send.sh — Send Slack message as the running agent via User OAuth Token (xoxp-)
|
|
3
|
+
# Messages appear as the agent's own Slack user, NOT the bot app.
|
|
4
|
+
# Usage: ./scripts/slack-send.sh <channel_id> <message> [--thread_ts <ts>]
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
# Resolve the agent repo dir so we can source env files regardless of cwd.
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
10
|
+
|
|
11
|
+
# Load identity vars (AGENT_FIRST_NAME, AGENT_FULL_NAME, AGENT_EMAIL, …) so
|
|
12
|
+
# nothing in this script needs to hardcode the agent's name or paths.
|
|
13
|
+
if [ -f "$AGENT_REPO_DIR/config/agent.env" ]; then
|
|
14
|
+
# shellcheck disable=SC1091
|
|
15
|
+
source "$AGENT_REPO_DIR/config/agent.env"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Load secrets from the agent's .env (must live alongside agent.env).
|
|
19
|
+
if [ -f "$AGENT_REPO_DIR/.env" ]; then
|
|
20
|
+
# shellcheck disable=SC1091
|
|
21
|
+
source "$AGENT_REPO_DIR/.env"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# ENFORCEMENT: Must use User Token (xoxp-), never Bot Token (xoxb-).
|
|
25
|
+
# Per CEO directive: all Slack sends must come from user scope so messages
|
|
26
|
+
# appear as the running agent, not a generic bot app.
|
|
27
|
+
SLACK_SEND_TOKEN="${SLACK_USER_TOKEN}"
|
|
28
|
+
if [ -z "$SLACK_SEND_TOKEN" ]; then
|
|
29
|
+
echo "ERROR: SLACK_USER_TOKEN not set in .env — cannot send as user scope" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
if [[ "$SLACK_SEND_TOKEN" == xoxb-* ]]; then
|
|
33
|
+
echo "ERROR: SLACK_SEND_TOKEN is a bot token (xoxb-). Must use user token (xoxp-)." >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
CHANNEL="$1"
|
|
38
|
+
MESSAGE="$2"
|
|
39
|
+
THREAD_TS=""
|
|
40
|
+
RESPONDING_TO=""
|
|
41
|
+
SKIP_TYPING=""
|
|
42
|
+
|
|
43
|
+
# Parse optional flags
|
|
44
|
+
shift 2 2>/dev/null || true
|
|
45
|
+
while [[ $# -gt 0 ]]; do
|
|
46
|
+
case "$1" in
|
|
47
|
+
--thread_ts)
|
|
48
|
+
THREAD_TS="$2"
|
|
49
|
+
shift 2
|
|
50
|
+
;;
|
|
51
|
+
--responding_to)
|
|
52
|
+
RESPONDING_TO="$2"
|
|
53
|
+
shift 2
|
|
54
|
+
;;
|
|
55
|
+
--no-typing)
|
|
56
|
+
SKIP_TYPING="1"
|
|
57
|
+
shift
|
|
58
|
+
;;
|
|
59
|
+
*)
|
|
60
|
+
shift
|
|
61
|
+
;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
# Allow env override to disable typing globally
|
|
66
|
+
if [ "${SLACK_TYPING_ENABLED:-1}" = "0" ]; then
|
|
67
|
+
SKIP_TYPING="1"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [ -z "$CHANNEL" ] || [ -z "$MESSAGE" ]; then
|
|
71
|
+
echo "Usage: slack-send.sh <channel_id> <message> [--thread_ts <ts>] [--responding_to <ts>]"
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Auto-detect --responding_to from the inbox if the caller didn't pass it.
|
|
76
|
+
# Protects against operator-vs-daemon duplicate replies: when a human-prompted
|
|
77
|
+
# session replies via this script, it should claim the dedup lock so the
|
|
78
|
+
# daemon's session (which always passes --responding_to) finds the lock
|
|
79
|
+
# already taken and skips its own send. Without auto-detect, two reply
|
|
80
|
+
# sources race and the user gets duplicate substantive responses (observed
|
|
81
|
+
# on 2026-05-12 when operator + daemon both replied to a CEO DM 3 min apart).
|
|
82
|
+
#
|
|
83
|
+
# Strategy: scan state/inbox/slack/ for the most recent unprocessed YAML
|
|
84
|
+
# whose body references this channel; extract its raw_ref ts as RESPONDING_TO.
|
|
85
|
+
# Operator can override by passing --responding_to explicitly, or skip auto-
|
|
86
|
+
# detect with --responding_to "" (proactive send not tied to an inbox item).
|
|
87
|
+
if [ -z "$RESPONDING_TO" ] && [[ "$CHANNEL" =~ ^[CD][A-Z0-9]+$ ]]; then
|
|
88
|
+
INBOX_DIR="$AGENT_REPO_DIR/state/inbox/slack"
|
|
89
|
+
if [ -d "$INBOX_DIR" ]; then
|
|
90
|
+
LATEST_INBOX=$(
|
|
91
|
+
ls -t "$INBOX_DIR"/*.yaml 2>/dev/null |
|
|
92
|
+
xargs -I{} grep -l "channel_id: \"$CHANNEL\"" {} 2>/dev/null |
|
|
93
|
+
head -1
|
|
94
|
+
)
|
|
95
|
+
if [ -n "$LATEST_INBOX" ]; then
|
|
96
|
+
AUTO_TS=$(grep "^raw_ref:" "$LATEST_INBOX" 2>/dev/null | sed -E 's/.*:([0-9]+\.[0-9]+)".*/\1/')
|
|
97
|
+
if [ -n "$AUTO_TS" ]; then
|
|
98
|
+
RESPONDING_TO="$AUTO_TS"
|
|
99
|
+
echo "[slack-send] auto-detected --responding_to=$RESPONDING_TO from $(basename "$LATEST_INBOX")" >&2
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# Dedup check: if --responding_to is set, use atomic locking to prevent concurrent sends
|
|
106
|
+
DEDUP_ACQUIRED=""
|
|
107
|
+
if [ -n "$RESPONDING_TO" ]; then
|
|
108
|
+
# Atomic lock acquisition (mkdir-based, race-safe).
|
|
109
|
+
# This is the primary dedup mechanism. mkdir is atomic on POSIX — if two sessions
|
|
110
|
+
# race to respond to the same message, exactly one will succeed and the other will
|
|
111
|
+
# get LOCKED and skip. This eliminates the check-then-write race condition.
|
|
112
|
+
# AGENT_SESSION_ID is the per-agent session identifier (used to be SOPHIE_SESSION_ID,
|
|
113
|
+
# RAVI_SESSION_ID, etc.); $$ is the fallback when running outside a daemon-spawned shell.
|
|
114
|
+
SESSION_ID="${AGENT_SESSION_ID:-${SOPHIE_SESSION_ID:-${RAVI_SESSION_ID:-$$}}}"
|
|
115
|
+
ACQUIRE_RESULT=$("$SCRIPT_DIR/slack-responded.sh" acquire "$CHANNEL" "$RESPONDING_TO" "$SESSION_ID" 2>/dev/null) || true
|
|
116
|
+
if [ "$ACQUIRE_RESULT" != "ACQUIRED" ]; then
|
|
117
|
+
echo "DEDUP_SKIP"
|
|
118
|
+
exit 0
|
|
119
|
+
fi
|
|
120
|
+
DEDUP_ACQUIRED="1"
|
|
121
|
+
|
|
122
|
+
# Lock-release safety net. If this script exits BEFORE confirming the
|
|
123
|
+
# lock (e.g. validation failed, Slack API errored, python script crashed
|
|
124
|
+
# during message conversion, caller killed us), we must release the lock
|
|
125
|
+
# so the next legitimate retry isn't blocked for 24 hours. The trap is
|
|
126
|
+
# cleared after the post-send confirm; if the confirm runs, we leave
|
|
127
|
+
# the lock in place (it's now a real send marker, not a placeholder).
|
|
128
|
+
cleanup_unconfirmed_lock() {
|
|
129
|
+
local exit_code=$?
|
|
130
|
+
if [ -n "$DEDUP_ACQUIRED" ] && [ -z "$DEDUP_CONFIRMED" ]; then
|
|
131
|
+
"$SCRIPT_DIR/slack-responded.sh" release "$CHANNEL" "$RESPONDING_TO" "$SESSION_ID" 2>/dev/null || true
|
|
132
|
+
fi
|
|
133
|
+
return "$exit_code"
|
|
134
|
+
}
|
|
135
|
+
trap cleanup_unconfirmed_lock EXIT
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Show typing indicator before sending (non-blocking, fire-and-forget)
|
|
139
|
+
# This makes the agent's presence feel human — the "X is typing..." dots appear
|
|
140
|
+
# before the message arrives. Requires rtm:stream scope on user token; degrades gracefully.
|
|
141
|
+
TYPING_DURATION=2500
|
|
142
|
+
MSG_LEN=${#MESSAGE}
|
|
143
|
+
if [ "$MSG_LEN" -gt 500 ]; then
|
|
144
|
+
TYPING_DURATION=4000
|
|
145
|
+
elif [ "$MSG_LEN" -gt 200 ]; then
|
|
146
|
+
TYPING_DURATION=3000
|
|
147
|
+
elif [ "$MSG_LEN" -lt 50 ]; then
|
|
148
|
+
TYPING_DURATION=1500
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Run typing indicator synchronously. The script connects via WebSocket, sends typing
|
|
152
|
+
# events for TYPING_DURATION ms, then exits. If rtm:stream scope is missing, it exits
|
|
153
|
+
# immediately with no delay — the message sends without the typing indicator.
|
|
154
|
+
if [ -z "$SKIP_TYPING" ] && [ -f "$SCRIPT_DIR/slack-typing.mjs" ]; then
|
|
155
|
+
/opt/homebrew/bin/node "$SCRIPT_DIR/slack-typing.mjs" "$CHANNEL" "$TYPING_DURATION" 2>/dev/null || true
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# ── Pre-draft context enrichment (Phase 7) ─────────────────────
|
|
159
|
+
# Look up recipient in entity index before sending. Advisory only — never blocks.
|
|
160
|
+
# Logs context to logs/audit/{date}-pre-draft-lookups.jsonl for audit trail.
|
|
161
|
+
if [ -f "$SCRIPT_DIR/pre_draft_lookup.py" ]; then
|
|
162
|
+
python3 "$SCRIPT_DIR/pre_draft_lookup.py" --recipient "$CHANNEL" --type slack --channel "$CHANNEL" 2>/dev/null >/dev/null || true
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# ── Pre-send factual validation ─────────────────────────────────
|
|
166
|
+
# Checks relationship claims, AI disclosure, in-person scheduling, title accuracy
|
|
167
|
+
# Blocks send if critical issues found. Fail-open on errors.
|
|
168
|
+
if [ ! -f "$SCRIPT_DIR/validate_outbound.py" ]; then
|
|
169
|
+
echo "BLOCKED: outbound validator (validate_outbound.py) missing — refusing Slack send (fail closed)." >&2
|
|
170
|
+
exit 1
|
|
171
|
+
fi
|
|
172
|
+
if true; then
|
|
173
|
+
VALIDATION_RESULT=$(echo "$MESSAGE" | python3 "$SCRIPT_DIR/validate_outbound.py" --type slack --recipient "$CHANNEL" --quiet 2>/dev/null) || VALIDATION_EXIT=$?
|
|
174
|
+
VALIDATION_EXIT=${VALIDATION_EXIT:-0}
|
|
175
|
+
if [ "$VALIDATION_EXIT" = "1" ]; then
|
|
176
|
+
BLOCKERS=$(echo "$VALIDATION_RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('metadata',{}).get('blockers',0))" 2>/dev/null) || BLOCKERS=0
|
|
177
|
+
if [ "$BLOCKERS" -gt 0 ]; then
|
|
178
|
+
echo "VALIDATION_BLOCK: $BLOCKERS issue(s) found in Slack message to $CHANNEL"
|
|
179
|
+
echo "$VALIDATION_RESULT" | python3 -c "
|
|
180
|
+
import json,sys
|
|
181
|
+
d=json.load(sys.stdin)
|
|
182
|
+
for i in d.get('issues',[]):
|
|
183
|
+
if i.get('severity')=='block':
|
|
184
|
+
print(f\" [{i['type']}] {i['description']}\")
|
|
185
|
+
if i.get('suggestion'): print(f\" -> {i['suggestion']}\")
|
|
186
|
+
" 2>/dev/null || true
|
|
187
|
+
echo "Fix the above issues before sending." >&2
|
|
188
|
+
exit 1
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# ── Pre-send policy gate (shared send-gate chokepoint) ──────────────
|
|
194
|
+
# Unified policy layer ABOVE validate_outbound.py (Layer 0): banned phrases,
|
|
195
|
+
# AI-disclosure, information barriers, and the recipient allowlist, all enforced
|
|
196
|
+
# in lib/comms/send-gate.mjs so every send path screens identically. Slack
|
|
197
|
+
# channel/DM ids are internal by construction, so an FS fault fails OPEN here;
|
|
198
|
+
# external recipients fail CLOSED (see the module header).
|
|
199
|
+
SEND_GATE="$AGENT_REPO_DIR/lib/comms/send-gate.mjs"
|
|
200
|
+
if [ ! -f "$SEND_GATE" ]; then
|
|
201
|
+
# Try the maestro package location if running from a consuming agent repo.
|
|
202
|
+
SEND_GATE="$AGENT_REPO_DIR/node_modules/@cohortapp/agent-sdk/lib/comms/send-gate.mjs"
|
|
203
|
+
fi
|
|
204
|
+
if [ -f "$SEND_GATE" ]; then
|
|
205
|
+
NODE_BIN="${MAESTRO_NODE_BIN:-/opt/homebrew/bin/node}"
|
|
206
|
+
command -v "$NODE_BIN" >/dev/null 2>&1 || NODE_BIN="node"
|
|
207
|
+
# Operational sends are replies by default (no proactive-disclosure duty);
|
|
208
|
+
# set MAESTRO_SEND_FIRST_CONTACT=true for a genuine first-contact send. The
|
|
209
|
+
# truthfulness invariant (never deny being an AI) applies regardless.
|
|
210
|
+
GATE_REASON=$(printf '%s' "$MESSAGE" | AGENT_DIR="$AGENT_REPO_DIR" "$NODE_BIN" "$SEND_GATE" \
|
|
211
|
+
--channel slack --recipient "$CHANNEL" --agent-root "$AGENT_REPO_DIR" \
|
|
212
|
+
--first-contact "${MAESTRO_SEND_FIRST_CONTACT:-false}" 2>&1) || GATE_EXIT=$?
|
|
213
|
+
GATE_EXIT=${GATE_EXIT:-0}
|
|
214
|
+
if [ "$GATE_EXIT" != "0" ]; then
|
|
215
|
+
echo "SEND_GATE_BLOCK: Slack message to $CHANNEL blocked by policy gate" >&2
|
|
216
|
+
echo "$GATE_REASON" >&2
|
|
217
|
+
exit 1
|
|
218
|
+
fi
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# Convert markdown to Slack mrkdwn format before sending
|
|
222
|
+
# Slack uses its own mrkdwn syntax which differs from standard markdown:
|
|
223
|
+
# Bold: *text* (not **text**)
|
|
224
|
+
# Italic: _text_ (same as markdown)
|
|
225
|
+
# Strikethrough: ~text~ (not ~~text~~)
|
|
226
|
+
# Links: <url|text> (not [text](url))
|
|
227
|
+
# Headings: not supported — converted to *bold* text
|
|
228
|
+
# Images: not supported — converted to <url|alt> links
|
|
229
|
+
# HR (---): converted to Unicode line separator
|
|
230
|
+
MESSAGE=$(python3 -c "
|
|
231
|
+
import sys, re
|
|
232
|
+
msg = sys.argv[1]
|
|
233
|
+
# 1. Convert markdown images  to <url|alt> (strip the !)
|
|
234
|
+
msg = re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', r'<\2|\1>', msg)
|
|
235
|
+
# 2. Convert ***bold italic*** to *_bold italic_* (must come before ** rule)
|
|
236
|
+
msg = re.sub(r'\*\*\*(.+?)\*\*\*', r'*_\1_*', msg)
|
|
237
|
+
# 3. Convert **bold** to *bold* (markdown bold to Slack bold)
|
|
238
|
+
msg = re.sub(r'\*\*(.+?)\*\*', r'*\1*', msg)
|
|
239
|
+
# 4. Convert __bold__ to *bold* (underscore bold variant)
|
|
240
|
+
msg = re.sub(r'__(.+?)__', r'*\1*', msg)
|
|
241
|
+
# 5. Convert ~~strikethrough~~ to ~strikethrough~ (Slack format)
|
|
242
|
+
msg = re.sub(r'~~(.+?)~~', r'~\1~', msg)
|
|
243
|
+
# 6. Convert [text](url) to <url|text> (markdown links to Slack links)
|
|
244
|
+
msg = re.sub(r'(?<!!)\[([^\]]+)\]\(([^)]+)\)', r'<\2|\1>', msg)
|
|
245
|
+
# 7. Convert headings to bold text (process longer prefixes first)
|
|
246
|
+
msg = re.sub(r'^####\s+(.+)$', r'*\1*', msg, flags=re.MULTILINE)
|
|
247
|
+
msg = re.sub(r'^###\s+(.+)$', r'*\1*', msg, flags=re.MULTILINE)
|
|
248
|
+
msg = re.sub(r'^##\s+(.+)$', r'*\1*', msg, flags=re.MULTILINE)
|
|
249
|
+
msg = re.sub(r'^#\s+(.+)$', r'*\1*', msg, flags=re.MULTILINE)
|
|
250
|
+
# 8. Convert horizontal rules (---, ***, ___) to Unicode line
|
|
251
|
+
msg = re.sub(r'^[-*_]{3,}\s*$', '───────────────────────────', msg, flags=re.MULTILINE)
|
|
252
|
+
print(msg)
|
|
253
|
+
" "$MESSAGE")
|
|
254
|
+
|
|
255
|
+
# Escape message for JSON
|
|
256
|
+
ESCAPED=$(python3 -c "import json,sys; print(json.dumps(sys.argv[1]))" "$MESSAGE")
|
|
257
|
+
|
|
258
|
+
# Build JSON payload
|
|
259
|
+
if [ -n "$THREAD_TS" ]; then
|
|
260
|
+
PAYLOAD="{\"channel\":\"$CHANNEL\",\"text\":$ESCAPED,\"thread_ts\":\"$THREAD_TS\"}"
|
|
261
|
+
else
|
|
262
|
+
PAYLOAD="{\"channel\":\"$CHANNEL\",\"text\":$ESCAPED}"
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
RESULT=$(curl -s -X POST "https://slack.com/api/chat.postMessage" \
|
|
266
|
+
-H "Authorization: Bearer $SLACK_SEND_TOKEN" \
|
|
267
|
+
-H "Content-Type: application/json" \
|
|
268
|
+
-d "$PAYLOAD")
|
|
269
|
+
|
|
270
|
+
STATUS=$(echo "$RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if d['ok'] else d.get('error','fail'))")
|
|
271
|
+
echo "$STATUS"
|
|
272
|
+
|
|
273
|
+
# If we get a token_revoked or invalid_auth error, surface it clearly
|
|
274
|
+
if [ "$STATUS" = "invalid_auth" ] || [ "$STATUS" = "token_revoked" ] || [ "$STATUS" = "not_authed" ]; then
|
|
275
|
+
echo "ERROR: User token auth failed ($STATUS). Check SLACK_USER_TOKEN in .env" >&2
|
|
276
|
+
exit 1
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
# Post-send: confirm the response lock with message details (audit trail)
|
|
280
|
+
# and mark the trap as fulfilled so the exit-handler does NOT release the
|
|
281
|
+
# now-legitimate confirmation marker. Any non-OK status leaves DEDUP_CONFIRMED
|
|
282
|
+
# unset, so the EXIT trap releases the lock for legitimate retry.
|
|
283
|
+
if [ "$STATUS" = "OK" ] && [ -n "$RESPONDING_TO" ] && [ -n "$DEDUP_ACQUIRED" ]; then
|
|
284
|
+
PREVIEW=$(echo "$MESSAGE" | head -c 200)
|
|
285
|
+
"$SCRIPT_DIR/slack-responded.sh" confirm "$CHANNEL" "$RESPONDING_TO" "$PREVIEW" 2>/dev/null || true
|
|
286
|
+
DEDUP_CONFIRMED="1"
|
|
287
|
+
fi
|