@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,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-surface.test.mjs — the curated org tool table + shared executor.
|
|
3
|
+
*
|
|
4
|
+
* Hermetic: every network call rides an injected fetchImpl; the send-gate is an
|
|
5
|
+
* injected stub; env is snapshotted/cleared so a developer's COHORT_* vars
|
|
6
|
+
* can't leak in. Run: node --test lib/org/tool-surface.test.mjs
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
import { test, before, after } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
ORG_TOOLS,
|
|
16
|
+
getOrgTools,
|
|
17
|
+
isOrgTool,
|
|
18
|
+
orgToolDef,
|
|
19
|
+
OUTBOUND_METHODS,
|
|
20
|
+
emailFamilyAvailable,
|
|
21
|
+
artifactFamilyAvailable,
|
|
22
|
+
deskFamilyAvailable,
|
|
23
|
+
resolveOrgToolConfig,
|
|
24
|
+
executeOrgTool,
|
|
25
|
+
DEFAULT_COHORT_BASE,
|
|
26
|
+
} from "./tool-surface.mjs";
|
|
27
|
+
import { methodDef, PROTOCOL_VERSION } from "./protocol.mjs";
|
|
28
|
+
|
|
29
|
+
// ── env hygiene ────────────────────────────────────────────────────────────
|
|
30
|
+
const ENV_KEYS = [
|
|
31
|
+
"COHORT_API_TOKEN", "COHORT_TOKEN", "COHORT_API_KEY", "COHORT_ORG_ID",
|
|
32
|
+
"COHORT_BASE", "COHORT_API_URL", "COHORT_AGENT_ROOT", "AGENT_ROOT",
|
|
33
|
+
"COHORT_AGENT_ID", "COHORT_AGENT_EMAIL",
|
|
34
|
+
];
|
|
35
|
+
const saved = {};
|
|
36
|
+
before(() => {
|
|
37
|
+
for (const k of ENV_KEYS) { saved[k] = process.env[k]; delete process.env[k]; }
|
|
38
|
+
});
|
|
39
|
+
after(() => {
|
|
40
|
+
for (const k of ENV_KEYS) {
|
|
41
|
+
if (saved[k] === undefined) delete process.env[k];
|
|
42
|
+
else process.env[k] = saved[k];
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ── helpers ────────────────────────────────────────────────────────────────
|
|
47
|
+
const CFG = { org: { cohort: { enabled: true, base: "https://org.example", orgId: "acme", token: "nlk_secret" } } };
|
|
48
|
+
|
|
49
|
+
/** fetch stub: records calls, returns a res-frame body. */
|
|
50
|
+
function fakeFetch(body = { ok: true, result: { fine: true } }, { status = 200, ok = true } = {}) {
|
|
51
|
+
const calls = [];
|
|
52
|
+
const fn = async (url, init) => {
|
|
53
|
+
calls.push({ url: String(url), init: init || {} });
|
|
54
|
+
return { ok, status, json: async () => body, headers: { get: () => undefined } };
|
|
55
|
+
};
|
|
56
|
+
fn.calls = calls;
|
|
57
|
+
return fn;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── table shape ────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
test("table: curated 28 + 5 email + 5 artifact + 69 desk tools, snake_case names, valid access + schemas", () => {
|
|
63
|
+
// 2026-08 mobile-parity delta: +5 mail (report_spam/react/move_mailbox/
|
|
64
|
+
// summarise/ask) and +3 asks (files/calendar/crm) → desk 56 → 64; the
|
|
65
|
+
// call-working-sessions delta adds the 5 calls-desk tools → 69.
|
|
66
|
+
assert.equal(ORG_TOOLS.length, 107, "28 curated + 5 email + 5 artifact + 69 desk");
|
|
67
|
+
assert.equal(ORG_TOOLS.filter((t) => t.desk).length, 69, "exactly sixty-nine desk tools");
|
|
68
|
+
assert.equal(ORG_TOOLS.filter((t) => t.email).length, 5, "exactly five email tools");
|
|
69
|
+
assert.equal(ORG_TOOLS.filter((t) => t.artifact).length, 5, "exactly five artifact tools");
|
|
70
|
+
for (const t of ORG_TOOLS) {
|
|
71
|
+
assert.match(t.name, /^[a-z0-9_]+$/, `${t.name} snake_case`);
|
|
72
|
+
assert.ok(["read", "write", "admin"].includes(t.access), `${t.name} access`);
|
|
73
|
+
assert.ok(t.description && t.description.length > 10, `${t.name} described`);
|
|
74
|
+
assert.equal(t.input_schema.type, "object", `${t.name} schema object`);
|
|
75
|
+
assert.ok(t.binding && ["rpc", "read", "local"].includes(t.binding.kind), `${t.name} binding`);
|
|
76
|
+
if (t.binding.kind === "rpc") {
|
|
77
|
+
assert.ok(methodDef(t.binding.method), `${t.name} binds a real protocol method (${t.binding.method})`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("OUTBOUND_METHODS is DERIVED from outbound:true tools (messaging/email/mail-desk/share-step sends)", () => {
|
|
83
|
+
assert.deepEqual([...OUTBOUND_METHODS].sort(), ["calling.appShareAct", "email.draftSend", "email.send", "messaging.send"]);
|
|
84
|
+
const outboundTools = ORG_TOOLS.filter((t) => t.outbound).map((t) => t.name).sort();
|
|
85
|
+
assert.deepEqual(outboundTools, ["email_draft_send", "email_send", "messaging_send", "org_call_share_step"]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("email gating: family present in the vendored protocol → tools active; override excludes", () => {
|
|
89
|
+
// The email family landed in the vendored protocol (sync-protocol phase 1).
|
|
90
|
+
assert.equal(emailFamilyAvailable(), true, "vendored protocol carries email.send");
|
|
91
|
+
assert.equal(getOrgTools().length, 107);
|
|
92
|
+
const without = getOrgTools({ emailAvailable: false });
|
|
93
|
+
assert.equal(without.length, 102);
|
|
94
|
+
assert.ok(!without.some((t) => t.email), "email tools excluded when family absent");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("artifact gating: family present → tools active; override excludes (email precedent)", () => {
|
|
98
|
+
assert.equal(artifactFamilyAvailable(), true, "vendored protocol carries artifact.act");
|
|
99
|
+
const without = getOrgTools({ artifactAvailable: false });
|
|
100
|
+
assert.equal(without.length, 102);
|
|
101
|
+
assert.ok(!without.some((t) => t.artifact), "artifact tools excluded when family absent");
|
|
102
|
+
const neither = getOrgTools({ emailAvailable: false, artifactAvailable: false, desksAvailable: false });
|
|
103
|
+
assert.equal(neither.length, 28, "all additive families off → the 28 always-on tools");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("isOrgTool / orgToolDef cover the full table; unknown names rejected", () => {
|
|
107
|
+
for (const t of ORG_TOOLS) assert.equal(isOrgTool(t.name), true);
|
|
108
|
+
assert.equal(isOrgTool("slack_send"), false);
|
|
109
|
+
assert.equal(isOrgTool(""), false);
|
|
110
|
+
assert.equal(orgToolDef("org_rpc").access, "admin");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── config resolution (§1.4) ───────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
test("resolveOrgToolConfig: config base/token honoured; default base when nothing names one", () => {
|
|
116
|
+
const cfg = resolveOrgToolConfig({ orgConfig: CFG, agentRoot: "/tmp/none" });
|
|
117
|
+
assert.equal(cfg.base, "https://org.example");
|
|
118
|
+
assert.equal(cfg.token, "nlk_secret");
|
|
119
|
+
assert.equal(cfg.orgId, "acme");
|
|
120
|
+
const bare = resolveOrgToolConfig({ orgConfig: {}, agentRoot: "/tmp/none" });
|
|
121
|
+
assert.equal(bare.base, DEFAULT_COHORT_BASE);
|
|
122
|
+
assert.equal(bare.token, "");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("resolveOrgToolConfig: COHORT_BASE env wins over config base (env-first)", () => {
|
|
126
|
+
process.env.COHORT_BASE = "https://env.example/";
|
|
127
|
+
try {
|
|
128
|
+
const cfg = resolveOrgToolConfig({ orgConfig: CFG, agentRoot: "/tmp/none" });
|
|
129
|
+
assert.equal(cfg.base, "https://env.example", "env base wins, trailing slash stripped");
|
|
130
|
+
} finally {
|
|
131
|
+
delete process.env.COHORT_BASE;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ── executor: basics ───────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
test("executeOrgTool: unknown tool → NOT_FOUND frame, never a throw", async () => {
|
|
138
|
+
const frame = await executeOrgTool("nope_tool", {}, { orgConfig: CFG, agentRoot: "/tmp/none" });
|
|
139
|
+
assert.equal(frame.ok, false);
|
|
140
|
+
assert.equal(frame.error.code, "NOT_FOUND");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("executeOrgTool: missing token → clear UNAUTHORIZED frame for network tools", async () => {
|
|
144
|
+
const fetchImpl = fakeFetch();
|
|
145
|
+
const frame = await executeOrgTool("org_directory", {}, { orgConfig: {}, agentRoot: "/tmp/none", fetchImpl });
|
|
146
|
+
assert.equal(frame.ok, false);
|
|
147
|
+
assert.equal(frame.error.code, "UNAUTHORIZED");
|
|
148
|
+
assert.match(frame.error.message, /COHORT_API_TOKEN/);
|
|
149
|
+
assert.equal(fetchImpl.calls.length, 0, "no network attempted");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("org_describe: offline, no network, family filter works", async () => {
|
|
153
|
+
const fetchImpl = fakeFetch();
|
|
154
|
+
const frame = await executeOrgTool("org_describe", {}, { orgConfig: {}, agentRoot: "/tmp/none", fetchImpl });
|
|
155
|
+
assert.equal(frame.ok, true);
|
|
156
|
+
assert.equal(frame.result.protocolVersion, PROTOCOL_VERSION);
|
|
157
|
+
assert.ok(frame.result.methods.length >= 200, "full method table listed");
|
|
158
|
+
assert.ok(frame.result.reads.length >= 13, "reads listed");
|
|
159
|
+
const fam = await executeOrgTool("org_describe", { family: "board" }, { orgConfig: {}, agentRoot: "/tmp/none" });
|
|
160
|
+
assert.ok(fam.result.methods.every((m) => m.family === "board"));
|
|
161
|
+
assert.deepEqual(fam.result.families, ["board"]);
|
|
162
|
+
assert.equal(fetchImpl.calls.length, 0);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ── executor: rpc + read bindings ──────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
test("rpc binding: messaging_history POSTs /api/v1/messaging.history with Bearer + org pin", async () => {
|
|
168
|
+
const fetchImpl = fakeFetch({ ok: true, result: { messages: [] } });
|
|
169
|
+
const frame = await executeOrgTool("messaging_history", { channelId: "C1", limit: 5 }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
170
|
+
assert.equal(frame.ok, true);
|
|
171
|
+
assert.equal(fetchImpl.calls.length, 1);
|
|
172
|
+
const { url, init } = fetchImpl.calls[0];
|
|
173
|
+
assert.equal(url, "https://org.example/api/v1/messaging.history");
|
|
174
|
+
assert.equal(init.method, "POST");
|
|
175
|
+
assert.equal(init.headers.authorization, "Bearer nlk_secret");
|
|
176
|
+
assert.equal(init.headers["x-org-id"], "acme");
|
|
177
|
+
assert.deepEqual(JSON.parse(init.body), { channelId: "C1", limit: 5 });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("read binding: board_ready GETs /api/v1/board.ready and normalises the bare payload", async () => {
|
|
181
|
+
const fetchImpl = fakeFetch({ items: [{ id: "w1" }] });
|
|
182
|
+
const frame = await executeOrgTool("board_ready", {}, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
183
|
+
assert.equal(frame.ok, true);
|
|
184
|
+
assert.deepEqual(frame.result, { items: [{ id: "w1" }] });
|
|
185
|
+
assert.equal(fetchImpl.calls[0].init.method, "GET");
|
|
186
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/board\.ready$/);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("org_events_tail: cursor + clamped limit ride the query string", async () => {
|
|
190
|
+
const fetchImpl = fakeFetch({ events: [] });
|
|
191
|
+
await executeOrgTool("org_events_tail", { cursor: 7, limit: 9999 }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
192
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/events\?cursor=7&limit=500$/, "limit clamped to 500");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("approval_wait: requires approvalId; dispatches the bounded long-poll", async () => {
|
|
196
|
+
const missing = await executeOrgTool("approval_wait", {}, { orgConfig: CFG, agentRoot: "/tmp/none" });
|
|
197
|
+
assert.equal(missing.error.code, "BAD_REQUEST");
|
|
198
|
+
const fetchImpl = fakeFetch({ id: "a1", status: "approved" });
|
|
199
|
+
const frame = await executeOrgTool("approval_wait", { approvalId: "a1" }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
200
|
+
assert.equal(frame.ok, true);
|
|
201
|
+
assert.match(fetchImpl.calls[0].url, /approval\.wait\?id=a1&timeoutMs=55000/);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// ── executor: send-gate parity (§1.5a) ─────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
test("messaging_send: screened through the send-gate, clientMsgId auto-minted", async () => {
|
|
207
|
+
const screened = [];
|
|
208
|
+
const fetchImpl = fakeFetch({ ok: true, result: { messageId: "m1" } });
|
|
209
|
+
const frame = await executeOrgTool(
|
|
210
|
+
"messaging_send",
|
|
211
|
+
{ channelId: "C9", body: "hello org" },
|
|
212
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, screenImpl: async (o) => { screened.push(o); return { allow: true }; } },
|
|
213
|
+
);
|
|
214
|
+
assert.equal(frame.ok, true);
|
|
215
|
+
assert.equal(screened.length, 1, "screenOutbound ran");
|
|
216
|
+
assert.equal(screened[0].recipient, "C9");
|
|
217
|
+
assert.equal(screened[0].text, "hello org");
|
|
218
|
+
const body = JSON.parse(fetchImpl.calls[0].init.body);
|
|
219
|
+
assert.ok(body.clientMsgId && body.clientMsgId.length >= 8, "clientMsgId auto-minted");
|
|
220
|
+
assert.ok(fetchImpl.calls[0].init.headers["x-idempotency-key"], "dispatcher idempotency key present");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("messaging_send: a vetoing gate blocks BEFORE any network", async () => {
|
|
224
|
+
const fetchImpl = fakeFetch();
|
|
225
|
+
const frame = await executeOrgTool(
|
|
226
|
+
"messaging_send",
|
|
227
|
+
{ channelId: "C9", body: "As an AI I love this" },
|
|
228
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, screenImpl: async () => ({ allow: false, reason: "banned-phrase" }) },
|
|
229
|
+
);
|
|
230
|
+
assert.equal(frame.ok, false);
|
|
231
|
+
assert.equal(frame.error.code, "FORBIDDEN_SCOPE");
|
|
232
|
+
assert.match(frame.error.message, /banned-phrase/);
|
|
233
|
+
assert.equal(fetchImpl.calls.length, 0, "blocked before dispatch");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("a THROWING gate fails open with a counted diagnostic (adapter parity)", async () => {
|
|
237
|
+
const counted = [];
|
|
238
|
+
const fetchImpl = fakeFetch({ ok: true, result: {} });
|
|
239
|
+
const frame = await executeOrgTool(
|
|
240
|
+
"messaging_send",
|
|
241
|
+
{ channelId: "C9", body: "hi" },
|
|
242
|
+
{
|
|
243
|
+
orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl,
|
|
244
|
+
screenImpl: async () => { throw new Error("gate exploded"); },
|
|
245
|
+
bumpImpl: (name, attrs) => counted.push({ name, attrs }),
|
|
246
|
+
},
|
|
247
|
+
);
|
|
248
|
+
assert.equal(frame.ok, true, "fail-open");
|
|
249
|
+
assert.equal(counted[0].name, "channel.send_gate.fail_open");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// ── executor: escape hatches ───────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
test("org_rpc: unknown method rejected against the protocol table, NO network", async () => {
|
|
255
|
+
const fetchImpl = fakeFetch();
|
|
256
|
+
const frame = await executeOrgTool("org_rpc", { method: "nope.nothing" }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
257
|
+
assert.equal(frame.ok, false);
|
|
258
|
+
assert.equal(frame.error.code, "NOT_FOUND");
|
|
259
|
+
assert.equal(fetchImpl.calls.length, 0, "validated before any network");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("org_rpc: escape-hatch parity — messaging.send through org_rpc is ALSO screened", async () => {
|
|
263
|
+
const screened = [];
|
|
264
|
+
const fetchImpl = fakeFetch({ ok: true, result: {} });
|
|
265
|
+
await executeOrgTool(
|
|
266
|
+
"org_rpc",
|
|
267
|
+
{ method: "messaging.send", params: { channelId: "C2", body: "raw lane" } },
|
|
268
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, screenImpl: async (o) => { screened.push(o); return { allow: true }; } },
|
|
269
|
+
);
|
|
270
|
+
assert.equal(screened.length, 1, "escape hatch screened");
|
|
271
|
+
assert.match(screened[0].text, /raw lane/, "params blob screened");
|
|
272
|
+
// and a blocked verdict stops the dispatch
|
|
273
|
+
const fetch2 = fakeFetch();
|
|
274
|
+
const blocked = await executeOrgTool(
|
|
275
|
+
"org_rpc",
|
|
276
|
+
{ method: "email.send", params: { to: ["x@y.z"], text: "leak" } },
|
|
277
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl: fetch2, screenImpl: async () => ({ allow: false, reason: "no" }) },
|
|
278
|
+
);
|
|
279
|
+
assert.equal(blocked.ok, false);
|
|
280
|
+
assert.equal(fetch2.calls.length, 0);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("org_rpc: non-outbound method dispatches unscreened with optional idempotencyKey", async () => {
|
|
284
|
+
const screened = [];
|
|
285
|
+
const fetchImpl = fakeFetch({ ok: true, result: { got: 1 } });
|
|
286
|
+
const frame = await executeOrgTool(
|
|
287
|
+
"org_rpc",
|
|
288
|
+
{ method: "member.get", params: { memberId: "m-1" }, idempotencyKey: "k-1" },
|
|
289
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, screenImpl: async (o) => { screened.push(o); return { allow: true }; } },
|
|
290
|
+
);
|
|
291
|
+
assert.equal(frame.ok, true);
|
|
292
|
+
assert.equal(screened.length, 0, "reads/non-outbound never screened");
|
|
293
|
+
assert.match(fetchImpl.calls[0].url, /member\.get$/);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("org_read: validated against READS; query params encoded; unknown path rejected", async () => {
|
|
297
|
+
const fetchImpl = fakeFetch({ events: [] });
|
|
298
|
+
const frame = await executeOrgTool("org_read", { path: "events", query: { cursor: 3, limit: 10 } }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
299
|
+
assert.equal(frame.ok, true);
|
|
300
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/events\?cursor=3&limit=10$/);
|
|
301
|
+
const bad = await executeOrgTool("org_read", { path: "not-a-read" }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
302
|
+
assert.equal(bad.error.code, "NOT_FOUND");
|
|
303
|
+
assert.equal(fetchImpl.calls.length, 1, "unknown path never dispatched");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ── executor: email tools ──────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
test("email_send: idempotencyId auto-minted; screened; rides email.send", async () => {
|
|
309
|
+
const screened = [];
|
|
310
|
+
const fetchImpl = fakeFetch({ ok: true, result: { status: "queued", messageId: "e1" } });
|
|
311
|
+
const frame = await executeOrgTool(
|
|
312
|
+
"email_send",
|
|
313
|
+
{ to: ["a@ext.com"], subject: "Hi", text: "Body" },
|
|
314
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, screenImpl: async (o) => { screened.push(o); return { allow: true }; } },
|
|
315
|
+
);
|
|
316
|
+
assert.equal(frame.ok, true);
|
|
317
|
+
assert.equal(screened[0].recipient, "a@ext.com");
|
|
318
|
+
assert.match(screened[0].text, /Subject: Hi/);
|
|
319
|
+
const body = JSON.parse(fetchImpl.calls[0].init.body);
|
|
320
|
+
assert.ok(body.idempotencyId && body.idempotencyId.length >= 8, "idempotencyId auto-minted");
|
|
321
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/email\.send$/);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("email tools degrade to NOT_FOUND when the family is gated off (pre-sync install)", async () => {
|
|
325
|
+
const fetchImpl = fakeFetch();
|
|
326
|
+
const frame = await executeOrgTool("email_inbox", {}, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, emailAvailable: false });
|
|
327
|
+
assert.equal(frame.ok, false);
|
|
328
|
+
assert.equal(frame.error.code, "NOT_FOUND");
|
|
329
|
+
assert.match(frame.error.message, /upgrade/i);
|
|
330
|
+
assert.equal(fetchImpl.calls.length, 0);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// ── executor: artifact tools ───────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
test("artifact_create: clientMsgId auto-minted; unscreened (in-thread, not outbound); rides artifact.create", async () => {
|
|
336
|
+
const screened = [];
|
|
337
|
+
const envelope = { genui: "v2", artifact: { class: "ops.status", version: 1, state: "complete", source: { kind: "native" } }, actions: [], root: { type: "card", tone: "neutral", blocks: [] } };
|
|
338
|
+
const fetchImpl = fakeFetch({ ok: true, result: { artifactId: "a1", messageId: "m1" } });
|
|
339
|
+
const frame = await executeOrgTool(
|
|
340
|
+
"artifact_create",
|
|
341
|
+
{ channelId: "C1", envelope },
|
|
342
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, screenImpl: async (o) => { screened.push(o); return { allow: true }; } },
|
|
343
|
+
);
|
|
344
|
+
assert.equal(frame.ok, true);
|
|
345
|
+
assert.deepEqual(frame.result, { artifactId: "a1", messageId: "m1" });
|
|
346
|
+
assert.equal(screened.length, 0, "artifact posts are in-thread — never send-gate screened");
|
|
347
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/artifact\.create$/);
|
|
348
|
+
const body = JSON.parse(fetchImpl.calls[0].init.body);
|
|
349
|
+
assert.ok(body.clientMsgId && body.clientMsgId.length >= 8, "clientMsgId auto-minted");
|
|
350
|
+
assert.deepEqual(body.envelope, envelope, "envelope posted verbatim");
|
|
351
|
+
assert.equal(fetchImpl.calls[0].init.headers["x-idempotency-key"], undefined, "sideEffecting:false — no dispatcher key; the service dedupes on clientMsgId");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("artifact_act: idempotencyKey auto-minted when absent, caller's key preserved; held result is a NORMAL frame", async () => {
|
|
355
|
+
const fetchImpl = fakeFetch({ ok: true, result: { held: true, approvalId: "ap-1" } });
|
|
356
|
+
const frame = await executeOrgTool(
|
|
357
|
+
"artifact_act",
|
|
358
|
+
{ artifactId: "a1", action: "approve" },
|
|
359
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl },
|
|
360
|
+
);
|
|
361
|
+
assert.equal(frame.ok, true, "approval-held is a result, not an error");
|
|
362
|
+
assert.equal(frame.result.held, true);
|
|
363
|
+
assert.equal(frame.result.approvalId, "ap-1");
|
|
364
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/artifact\.act$/);
|
|
365
|
+
const minted = JSON.parse(fetchImpl.calls[0].init.body);
|
|
366
|
+
assert.ok(minted.idempotencyKey && minted.idempotencyKey.length >= 8, "idempotencyKey auto-minted");
|
|
367
|
+
// A caller-supplied key (retry / post-approval re-invoke) rides through untouched.
|
|
368
|
+
await executeOrgTool(
|
|
369
|
+
"artifact_act",
|
|
370
|
+
{ artifactId: "a1", action: "approve", idempotencyKey: "approve-a1" },
|
|
371
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl },
|
|
372
|
+
);
|
|
373
|
+
assert.equal(JSON.parse(fetchImpl.calls[1].init.body).idempotencyKey, "approve-a1");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("artifact reads ride their rpc methods; tools degrade to NOT_FOUND when the family is gated off", async () => {
|
|
377
|
+
const fetchImpl = fakeFetch({ ok: true, result: { items: [] } });
|
|
378
|
+
await executeOrgTool("artifact_list", { channelId: "C1", state: "complete" }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
379
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/artifact\.list$/);
|
|
380
|
+
await executeOrgTool("artifact_catalog", {}, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
381
|
+
assert.match(fetchImpl.calls[1].url, /\/api\/v1\/artifact\.catalog$/);
|
|
382
|
+
const gated = fakeFetch();
|
|
383
|
+
const frame = await executeOrgTool("artifact_get", { artifactId: "a1" }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl: gated, artifactAvailable: false });
|
|
384
|
+
assert.equal(frame.ok, false);
|
|
385
|
+
assert.equal(frame.error.code, "NOT_FOUND");
|
|
386
|
+
assert.match(frame.error.message, /upgrade/i);
|
|
387
|
+
assert.equal(gated.calls.length, 0);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ── executor: directory + design desks (2026-07) ───────────────────────────
|
|
391
|
+
|
|
392
|
+
test("directory/design desks gate on their OWN probe, independently of the other desks", () => {
|
|
393
|
+
// One DESK_PROBES line lights a desk up on BOTH planes; a desk whose probe
|
|
394
|
+
// method is absent from the vendored protocol must not register.
|
|
395
|
+
assert.equal(deskFamilyAvailable("directory"), true, "vendored protocol carries directory.search");
|
|
396
|
+
assert.equal(deskFamilyAvailable("design"), true, "vendored protocol carries branding.getFoundation");
|
|
397
|
+
assert.equal(deskFamilyAvailable("nope"), false, "unknown desk → default-deny");
|
|
398
|
+
assert.equal(ORG_TOOLS.filter((t) => t.desk === "directory").length, 14);
|
|
399
|
+
assert.equal(ORG_TOOLS.filter((t) => t.desk === "design").length, 8);
|
|
400
|
+
// Neither desk smuggles an outbound lane in: no directory/design tool sends.
|
|
401
|
+
assert.equal(ORG_TOOLS.filter((t) => (t.desk === "directory" || t.desk === "design") && t.outbound).length, 0);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("directory reads/writes ride their rpc methods; a side-effecting write carries a dispatcher key", async () => {
|
|
405
|
+
const fetchImpl = fakeFetch({ ok: true, result: { people: [] } });
|
|
406
|
+
const o = { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl };
|
|
407
|
+
await executeOrgTool("directory_search", { q: "hartmann" }, o);
|
|
408
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/directory\.search$/);
|
|
409
|
+
assert.deepEqual(JSON.parse(fetchImpl.calls[0].init.body), { q: "hartmann" });
|
|
410
|
+
assert.equal(fetchImpl.calls[0].init.headers["x-idempotency-key"], undefined, "sideEffecting:false read sends no key");
|
|
411
|
+
|
|
412
|
+
await executeOrgTool("directory_list_people", { relationship: "client", sort: "touch" }, o);
|
|
413
|
+
assert.match(fetchImpl.calls[1].url, /\/api\/v1\/directory\.listPeople$/);
|
|
414
|
+
|
|
415
|
+
await executeOrgTool(
|
|
416
|
+
"directory_propose_capture",
|
|
417
|
+
{ kind: "PERSON", payload: { person: { displayName: "Dr. Lena Hartmann" } }, source: "MAIL", sourceFingerprint: "sig:abc" },
|
|
418
|
+
o,
|
|
419
|
+
);
|
|
420
|
+
assert.match(fetchImpl.calls[2].url, /\/api\/v1\/directory\.proposeCapture$/);
|
|
421
|
+
assert.ok(
|
|
422
|
+
fetchImpl.calls[2].init.headers["x-idempotency-key"],
|
|
423
|
+
"sideEffecting write gets a fresh dispatcher key",
|
|
424
|
+
);
|
|
425
|
+
assert.equal(JSON.parse(fetchImpl.calls[2].init.body).sourceFingerprint, "sig:abc");
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("design tools ride branding.*; the costed quote call posts budgetCents:0 verbatim", async () => {
|
|
429
|
+
const fetchImpl = fakeFetch({ ok: true, result: { slots: [], costCents: 0 } });
|
|
430
|
+
const o = { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl };
|
|
431
|
+
await executeOrgTool("design_foundation", { historyLimit: 5 }, o);
|
|
432
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/branding\.getFoundation$/);
|
|
433
|
+
await executeOrgTool("design_voice", {}, o);
|
|
434
|
+
assert.match(fetchImpl.calls[1].url, /\/api\/v1\/branding\.getVoice$/);
|
|
435
|
+
await executeOrgTool("design_generate_image", { budgetCents: 0 }, o);
|
|
436
|
+
assert.match(fetchImpl.calls[2].url, /\/api\/v1\/branding\.generateImage$/);
|
|
437
|
+
assert.equal(JSON.parse(fetchImpl.calls[2].init.body).budgetCents, 0, "0 is the QUOTE call, not a falsy drop");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("design_rewrite_in_voice produces copy and is NEVER send-gate screened", async () => {
|
|
441
|
+
// It returns a rewrite; it sends nothing. Whatever the caller does with the
|
|
442
|
+
// result is screened at the send verb, not here.
|
|
443
|
+
const screened = [];
|
|
444
|
+
const fetchImpl = fakeFetch({ ok: true, result: { text: "Rewritten.", costCents: 3 } });
|
|
445
|
+
const frame = await executeOrgTool(
|
|
446
|
+
"design_rewrite_in_voice",
|
|
447
|
+
{ text: "we are excited to announce" },
|
|
448
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, screenImpl: async (x) => { screened.push(x); return { allow: true }; } },
|
|
449
|
+
);
|
|
450
|
+
assert.equal(frame.ok, true);
|
|
451
|
+
assert.equal(screened.length, 0);
|
|
452
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/branding\.rewriteInVoice$/);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("directory/design tools degrade to NOT_FOUND when their desk is gated off (pre-sync install)", async () => {
|
|
456
|
+
for (const name of ["directory_search", "design_foundation"]) {
|
|
457
|
+
const gated = fakeFetch();
|
|
458
|
+
const frame = await executeOrgTool(name, { q: "x" }, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl: gated, desksAvailable: false });
|
|
459
|
+
assert.equal(frame.ok, false, `${name} gated`);
|
|
460
|
+
assert.equal(frame.error.code, "NOT_FOUND");
|
|
461
|
+
assert.match(frame.error.message, /upgrade/i);
|
|
462
|
+
assert.equal(gated.calls.length, 0, `${name} never dispatched`);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// ── executor: calls desk (call working sessions, 2026-08) ──────────────────
|
|
467
|
+
|
|
468
|
+
test("calls desk gates on calling.appShareStart; five tools, one outbound verb", () => {
|
|
469
|
+
assert.equal(deskFamilyAvailable("calls"), true, "vendored protocol carries calling.appShareStart");
|
|
470
|
+
const calls = ORG_TOOLS.filter((t) => t.desk === "calls");
|
|
471
|
+
assert.equal(calls.length, 5);
|
|
472
|
+
// The share-step verb is the block's ONE outbound lane: its highlight notes
|
|
473
|
+
// and typed text are read by every human on the call.
|
|
474
|
+
assert.deepEqual(calls.filter((t) => t.outbound).map((t) => t.name), ["org_call_share_step"]);
|
|
475
|
+
assert.deepEqual(
|
|
476
|
+
calls.filter((t) => t.access === "read").map((t) => t.name).sort(),
|
|
477
|
+
["org_call_events", "org_call_visual_context"],
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test("org_call_share_start/end ride their rpc methods with a fresh dispatcher key", async () => {
|
|
482
|
+
const fetchImpl = fakeFetch({ ok: true, result: { sessionId: "s1" } });
|
|
483
|
+
const o = { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl };
|
|
484
|
+
const frame = await executeOrgTool(
|
|
485
|
+
"org_call_share_start",
|
|
486
|
+
{ callId: "call-1", surface: { kind: "doc", ref: "f-1", title: "Q3 plan" } },
|
|
487
|
+
o,
|
|
488
|
+
);
|
|
489
|
+
assert.equal(frame.ok, true);
|
|
490
|
+
assert.equal(frame.result.sessionId, "s1");
|
|
491
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/calling\.appShareStart$/);
|
|
492
|
+
assert.ok(fetchImpl.calls[0].init.headers["x-idempotency-key"], "sideEffecting write gets a fresh dispatcher key");
|
|
493
|
+
assert.deepEqual(JSON.parse(fetchImpl.calls[0].init.body).surface, { kind: "doc", ref: "f-1", title: "Q3 plan" });
|
|
494
|
+
|
|
495
|
+
await executeOrgTool("org_call_share_end", { callId: "call-1", sessionId: "s1" }, o);
|
|
496
|
+
assert.match(fetchImpl.calls[1].url, /\/api\/v1\/calling\.appShareEnd$/);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("org_call_share_step: screened through the send-gate (notes + typed text), then rides calling.appShareAct", async () => {
|
|
500
|
+
const screened = [];
|
|
501
|
+
const fetchImpl = fakeFetch({ ok: true, result: { seq: 4 } });
|
|
502
|
+
const steps = [
|
|
503
|
+
{ k: "scroll", anchor: "block:3" },
|
|
504
|
+
{ k: "highlight", anchor: "block:3", note: "revenue line" },
|
|
505
|
+
{ k: "type", anchor: "block:4", text: "Draft summary" },
|
|
506
|
+
];
|
|
507
|
+
const frame = await executeOrgTool(
|
|
508
|
+
"org_call_share_step",
|
|
509
|
+
{ callId: "call-1", sessionId: "s1", steps },
|
|
510
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, screenImpl: async (x) => { screened.push(x); return { allow: true }; } },
|
|
511
|
+
);
|
|
512
|
+
assert.equal(frame.ok, true);
|
|
513
|
+
assert.equal(screened.length, 1, "screenOutbound ran");
|
|
514
|
+
assert.equal(screened[0].recipient, "call-1");
|
|
515
|
+
assert.match(screened[0].text, /revenue line/);
|
|
516
|
+
assert.match(screened[0].text, /Draft summary/);
|
|
517
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/calling\.appShareAct$/);
|
|
518
|
+
assert.deepEqual(JSON.parse(fetchImpl.calls[0].init.body).steps, steps);
|
|
519
|
+
|
|
520
|
+
// A vetoing gate blocks BEFORE any network — messaging_send parity.
|
|
521
|
+
const fetch2 = fakeFetch();
|
|
522
|
+
const blocked = await executeOrgTool(
|
|
523
|
+
"org_call_share_step",
|
|
524
|
+
{ callId: "call-1", sessionId: "s1", steps: [{ k: "highlight", anchor: "block:1", note: "As an AI" }] },
|
|
525
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl: fetch2, screenImpl: async () => ({ allow: false, reason: "banned-phrase" }) },
|
|
526
|
+
);
|
|
527
|
+
assert.equal(blocked.ok, false);
|
|
528
|
+
assert.equal(blocked.error.code, "FORBIDDEN_SCOPE");
|
|
529
|
+
assert.equal(fetch2.calls.length, 0, "blocked before dispatch");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("calls reads ride their rpc methods without a dispatcher key", async () => {
|
|
533
|
+
const fetchImpl = fakeFetch({ ok: true, result: { events: [] } });
|
|
534
|
+
const o = { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl };
|
|
535
|
+
await executeOrgTool("org_call_visual_context", { callId: "call-1", limit: 10 }, o);
|
|
536
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/calling\.getVisualContext$/);
|
|
537
|
+
assert.equal(fetchImpl.calls[0].init.headers["x-idempotency-key"], undefined, "sideEffecting:false read sends no key");
|
|
538
|
+
await executeOrgTool("org_call_events", { callId: "call-1" }, o);
|
|
539
|
+
assert.match(fetchImpl.calls[1].url, /\/api\/v1\/calling\.getCallEvents$/);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test("calls tools degrade to NOT_FOUND when the desk is gated off (pre-sync install)", async () => {
|
|
543
|
+
const gated = fakeFetch();
|
|
544
|
+
const frame = await executeOrgTool(
|
|
545
|
+
"org_call_share_start",
|
|
546
|
+
{ callId: "call-1", surface: { kind: "doc", ref: "f-1" } },
|
|
547
|
+
{ orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl: gated, desksAvailable: false },
|
|
548
|
+
);
|
|
549
|
+
assert.equal(frame.ok, false);
|
|
550
|
+
assert.equal(frame.error.code, "NOT_FOUND");
|
|
551
|
+
assert.match(frame.error.message, /upgrade/i);
|
|
552
|
+
assert.equal(gated.calls.length, 0, "never dispatched");
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// ── executor: org_whoami ───────────────────────────────────────────────────
|
|
556
|
+
|
|
557
|
+
test("org_whoami: unenrolled → config summary with member:null + note, never an error", async () => {
|
|
558
|
+
const frame = await executeOrgTool("org_whoami", {}, { orgConfig: {}, agentRoot: "/tmp/none" });
|
|
559
|
+
assert.equal(frame.ok, true);
|
|
560
|
+
assert.equal(frame.result.member, null);
|
|
561
|
+
assert.equal(frame.result.tokenPresent, false);
|
|
562
|
+
assert.match(frame.result.note, /setup --only org|COHORT_API_TOKEN|COHORT_ORG_ID/);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test("org_whoami: resolves the member via the org-data whoami lane; token never in the result", async () => {
|
|
566
|
+
const fetchImpl = fakeFetch({ slug: "astra", displayName: "Astra", agentEmail: "astra@agents.example" });
|
|
567
|
+
const frame = await executeOrgTool("org_whoami", {}, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
568
|
+
assert.equal(frame.ok, true);
|
|
569
|
+
assert.equal(frame.result.member.slug, "astra");
|
|
570
|
+
assert.equal(frame.result.tokenPresent, true);
|
|
571
|
+
assert.equal(frame.result.protocolVersion, PROTOCOL_VERSION);
|
|
572
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/org\/whoami\?orgId=acme/);
|
|
573
|
+
assert.ok(!JSON.stringify(frame).includes("nlk_secret"), "token never surfaces");
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test("org_whoami: unresolvable member degrades to summary + hint note (no error frame)", async () => {
|
|
577
|
+
const fetchImpl = fakeFetch({ error: { code: "NOT_FOUND", message: "ambiguous" } }, { status: 404, ok: false });
|
|
578
|
+
const frame = await executeOrgTool("org_whoami", {}, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl });
|
|
579
|
+
assert.equal(frame.ok, true);
|
|
580
|
+
assert.equal(frame.result.member, null);
|
|
581
|
+
assert.match(frame.result.note, /COHORT_AGENT_ID/);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("org_whoami: COHORT_AGENT_ID hint routes to the direct member GET", async () => {
|
|
585
|
+
const fetchImpl = fakeFetch({ slug: "astra", displayName: "Astra" });
|
|
586
|
+
const frame = await executeOrgTool("org_whoami", {}, { orgConfig: CFG, agentRoot: "/tmp/none", fetchImpl, env: { COHORT_AGENT_ID: "astra" } });
|
|
587
|
+
assert.equal(frame.ok, true);
|
|
588
|
+
assert.match(fetchImpl.calls[0].url, /\/api\/v1\/org\/members\/astra\?orgId=acme/);
|
|
589
|
+
});
|