@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,1628 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/org/client.mjs — maestro-side Cohort org client over the /v1 HTTP binding.
|
|
3
|
+
*
|
|
4
|
+
* The agent half of the Cohort org protocol (SPEC-org-server §2.4, phase-1 HTTP
|
|
5
|
+
* binding). Grows out of lib/org/cohort-client.mjs — it keeps the original
|
|
6
|
+
* fail-open contract (`isEnabled/publishEntry/heartbeat/fetchDirectory` are
|
|
7
|
+
* re-exported with their old signatures so existing callers don't break) but
|
|
8
|
+
* routes everything through the frozen wire contract in lib/org/protocol.mjs.
|
|
9
|
+
*
|
|
10
|
+
* Wire shape (must match the server byte-for-byte — see protocol.mjs):
|
|
11
|
+
* - RPC: POST {base}/v1/{method} body=params JSON, res frame {ok,result|error}
|
|
12
|
+
* headers: authorization: Bearer, x-org-protocol: 1, content-type,
|
|
13
|
+
* and x-idempotency-key for side-effecting methods.
|
|
14
|
+
* - Reads: GET {base}/v1/{snapshot|directory|events|hierarchy|board.ready},
|
|
15
|
+
* Bearer auth, bare payload JSON on 200, error frame + status on fail.
|
|
16
|
+
* - Pairing (pre-auth): POST /v1/pairing.request, POST /v1/pairing.approve.
|
|
17
|
+
*
|
|
18
|
+
* Fail-open by design: a network/parse error or a disabled integration yields a
|
|
19
|
+
* benign value (an error res frame, [] for directory, etc.) — never a throw —
|
|
20
|
+
* so the org binding can never take down the agent's main path. `fetchImpl` is
|
|
21
|
+
* injectable everywhere for deterministic tests.
|
|
22
|
+
*
|
|
23
|
+
* Node builtins only (uses global fetch on Node 20). ESM.
|
|
24
|
+
*
|
|
25
|
+
* @module lib/org/client
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
"use strict";
|
|
29
|
+
|
|
30
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
31
|
+
import { join } from "node:path";
|
|
32
|
+
import { createRequire } from "node:module";
|
|
33
|
+
import {
|
|
34
|
+
PROTOCOL_VERSION,
|
|
35
|
+
METHODS,
|
|
36
|
+
methodDef,
|
|
37
|
+
READS,
|
|
38
|
+
okFrame,
|
|
39
|
+
errFrame,
|
|
40
|
+
} from "./protocol.mjs";
|
|
41
|
+
import { applyBrandEnvCompat } from "../env-compat.mjs";
|
|
42
|
+
|
|
43
|
+
// SDK self-bridge: agent instances import this module directly from their own
|
|
44
|
+
// bins — they never pass through maestro's process entrypoints — so the client
|
|
45
|
+
// itself must alias NEOLITH_* ⇄ COHORT_* before the env-first token/org
|
|
46
|
+
// resolution below. Idempotent and never clobbers an explicitly-set name.
|
|
47
|
+
applyBrandEnvCompat();
|
|
48
|
+
|
|
49
|
+
const DEFAULT_TIMEOUT_MS = 8000;
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Config
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Load the agent's org config from disk — `config/org.yaml` (the canonical file
|
|
57
|
+
* `lib/setup/sections/org.mjs` writes and the wizard captures). Returns the
|
|
58
|
+
* parsed object ({ org: { cohort: {...} } }) or {} when absent/unparseable.
|
|
59
|
+
* THE single disk loader for org config, so the daemon and any caller resolve
|
|
60
|
+
* org enrollment from the same place (fixes the daemon reading collective.yaml
|
|
61
|
+
* by mistake). Fail-open: never throws.
|
|
62
|
+
* @param {string} agentRoot
|
|
63
|
+
* @returns {object}
|
|
64
|
+
*/
|
|
65
|
+
export function loadOrgConfig(agentRoot) {
|
|
66
|
+
try {
|
|
67
|
+
const p = join(agentRoot, "config", "org.yaml");
|
|
68
|
+
if (!existsSync(p)) return {};
|
|
69
|
+
const require = createRequire(import.meta.url);
|
|
70
|
+
const yaml = require("js-yaml");
|
|
71
|
+
return yaml.load(readFileSync(p, "utf8")) || {};
|
|
72
|
+
} catch {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Is the cohort integration configured + enabled? Back-compat shape: requires
|
|
79
|
+
* `org.cohort.enabled` plus a reachable endpoint (apiUrl or base).
|
|
80
|
+
* @param {object} cfg
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
83
|
+
export function isEnabled(cfg) {
|
|
84
|
+
const n = cfg && cfg.org && cfg.org.cohort;
|
|
85
|
+
if (!n || !n.enabled) return false;
|
|
86
|
+
return !!(n.base || (n.apiUrl && n.orgId));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Distil the org client config from an agent config object. Resolves:
|
|
91
|
+
* - base: the server origin for the /v1 binding (explicit `org.cohort.base`,
|
|
92
|
+
* else derived from `apiUrl` [+ `orgId` path] for back-compat).
|
|
93
|
+
* - orgId: the org slug — config `org.cohort.orgId` else COHORT_ORG_ID env
|
|
94
|
+
* (hq's documented var). Used by the legacy directory-path derivation
|
|
95
|
+
* and, when known, sent as the `x-org-id` pin header (defence-in-depth).
|
|
96
|
+
* - token: bearer credential — the RAW OrgApiKey. Resolution order:
|
|
97
|
+
* config `token` → COHORT_API_TOKEN → COHORT_TOKEN → COHORT_API_KEY.
|
|
98
|
+
* hq's auth doc names the agent var COHORT_API_KEY; the older
|
|
99
|
+
* COHORT_API_TOKEN/COHORT_TOKEN are still accepted (Bearer) so an
|
|
100
|
+
* existing agent env keeps working.
|
|
101
|
+
* - trustedSignersPem: PINNED snapshot signer PEMs for the detective layer.
|
|
102
|
+
* Never throws; returns a fully-populated object with empty strings for missing
|
|
103
|
+
* fields so callers can null-check uniformly.
|
|
104
|
+
* @param {object} cfg
|
|
105
|
+
* @returns {{enabled:boolean, base:string, orgId:string, token:string, trustedSignersPem:string[]}}
|
|
106
|
+
*/
|
|
107
|
+
export function configFromAgent(cfg) {
|
|
108
|
+
const n = (cfg && cfg.org && cfg.org.cohort) || {};
|
|
109
|
+
const apiUrl = n.apiUrl ? String(n.apiUrl).replace(/\/+$/, "") : "";
|
|
110
|
+
const orgId = (n.orgId ? String(n.orgId) : "") || process.env.COHORT_ORG_ID || "";
|
|
111
|
+
let base = n.base ? String(n.base).replace(/\/+$/, "") : "";
|
|
112
|
+
if (!base && apiUrl) {
|
|
113
|
+
// Legacy back-compat: apiUrl[/orgId] is the server origin for the binding.
|
|
114
|
+
base = orgId ? `${apiUrl}/${encodeURIComponent(orgId)}` : apiUrl;
|
|
115
|
+
}
|
|
116
|
+
const token =
|
|
117
|
+
(n.token && String(n.token)) ||
|
|
118
|
+
process.env.COHORT_API_TOKEN ||
|
|
119
|
+
process.env.COHORT_TOKEN ||
|
|
120
|
+
process.env.COHORT_API_KEY ||
|
|
121
|
+
"";
|
|
122
|
+
const signers = Array.isArray(n.trustedSignersPem)
|
|
123
|
+
? n.trustedSignersPem.filter((s) => typeof s === "string" && s)
|
|
124
|
+
: [];
|
|
125
|
+
return { enabled: !!(n.enabled && base), base, orgId, token, trustedSignersPem: signers };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Low-level transport
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
function pickFetch(fetchImpl) {
|
|
133
|
+
if (typeof fetchImpl === "function") return fetchImpl;
|
|
134
|
+
if (typeof fetch === "function") return fetch;
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function baseHeaders(token, orgId) {
|
|
139
|
+
const h = {
|
|
140
|
+
"content-type": "application/json",
|
|
141
|
+
"x-org-protocol": String(PROTOCOL_VERSION),
|
|
142
|
+
};
|
|
143
|
+
if (token) h.authorization = `Bearer ${token}`;
|
|
144
|
+
// Defence-in-depth: pin the org when we know it. hq treats a mismatch as 401
|
|
145
|
+
// and a match as harmless, so this only ever fences a misrouted key.
|
|
146
|
+
if (orgId) h["x-org-id"] = String(orgId);
|
|
147
|
+
return h;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Perform a single HTTP request with timeout + JSON parse. Returns the raw
|
|
152
|
+
* outcome ({ok,status,body}); never throws.
|
|
153
|
+
*/
|
|
154
|
+
async function httpRequest(fetchImpl, url, init, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
155
|
+
const f = pickFetch(fetchImpl);
|
|
156
|
+
if (!f) return { ok: false, status: 0, body: null };
|
|
157
|
+
const ctrl = typeof AbortController === "function" ? new AbortController() : null;
|
|
158
|
+
const timer = ctrl ? setTimeout(() => ctrl.abort(), timeoutMs) : null;
|
|
159
|
+
try {
|
|
160
|
+
const res = await f(url, { ...init, signal: ctrl ? ctrl.signal : undefined });
|
|
161
|
+
let body = null;
|
|
162
|
+
try { body = await res.json(); } catch { body = null; }
|
|
163
|
+
return { ok: !!res.ok, status: typeof res.status === "number" ? res.status : 0, body };
|
|
164
|
+
} catch {
|
|
165
|
+
return { ok: false, status: 0, body: null };
|
|
166
|
+
} finally {
|
|
167
|
+
if (timer) clearTimeout(timer);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Join an org server ORIGIN + a /v1 RPC/read path.
|
|
173
|
+
*
|
|
174
|
+
* hq serves the whole agent binding at `/api/v1/<path>` (the catch-all route
|
|
175
|
+
* `src/app/api/v1/[...method]/route.ts` — there is NO bare `/v1` mount), and the
|
|
176
|
+
* org-data DTO reads (fetchSelfProfile/orgDataGet) already build `/api/v1/org/…`.
|
|
177
|
+
* So the ONE canonical convention is `${origin}/api/v1/${path}` — this keeps the
|
|
178
|
+
* RPC/read binding and the org-data reads pointed at the same base.
|
|
179
|
+
*
|
|
180
|
+
* Back-compat: a `base` that ALREADY carries the mount path baked in (ends in
|
|
181
|
+
* `/api/v1` or a legacy bare `/v1`) is respected as-is rather than double-
|
|
182
|
+
* prefixed, so a service configured the old way keeps working.
|
|
183
|
+
*/
|
|
184
|
+
function v1Url(base, path) {
|
|
185
|
+
const b = String(base).replace(/\/+$/, "");
|
|
186
|
+
if (/\/(?:api\/)?v1$/.test(b)) return `${b}/${path}`;
|
|
187
|
+
return `${b}/api/v1/${path}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Normalise a server body into a res frame ({ok,result} | {ok,error}). */
|
|
191
|
+
function asResFrame(r) {
|
|
192
|
+
const b = r.body;
|
|
193
|
+
if (b && typeof b === "object" && typeof b.ok === "boolean") return b; // already a frame
|
|
194
|
+
if (r.ok) return okFrame(b);
|
|
195
|
+
return errFrame(httpStatusToCode(r.status), `http ${r.status}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function httpStatusToCode(status) {
|
|
199
|
+
switch (status) {
|
|
200
|
+
case 400: return "BAD_REQUEST";
|
|
201
|
+
case 401: return "UNAUTHORIZED";
|
|
202
|
+
case 403: return "FORBIDDEN_SCOPE";
|
|
203
|
+
case 404: return "NOT_FOUND";
|
|
204
|
+
case 409: return "CONFLICT";
|
|
205
|
+
case 423: return "GOVERNANCE_NOT_READY";
|
|
206
|
+
case 429: return "RATE_LIMITED";
|
|
207
|
+
default: return "INTERNAL";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Pairing handshake (pre-auth)
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Pre-auth pairing handshake. POST /v1/pairing.request — no bearer token (the
|
|
217
|
+
* agent has none yet). The operator picks a `code` and shares it with an org
|
|
218
|
+
* admin, who resolves the handshake via pairing.approve; hq keys the
|
|
219
|
+
* PairingRequest row on `(orgId, code)` (server contract: `code` is required,
|
|
220
|
+
* `agentId` optional). We ALSO send the device `pubkey` + `displayName` so a
|
|
221
|
+
* server that pins the device key on approval has it (hq currently ignores the
|
|
222
|
+
* extra fields — zod strips unknown keys — so this stays forward-compatible).
|
|
223
|
+
* Returns the res frame; on transport failure returns a fail-open error frame.
|
|
224
|
+
* @param {object} o - { base, agentId, pubkey, code?, displayName?, fetchImpl? }
|
|
225
|
+
* @returns {Promise<object>} res frame {ok:true,result:{status,code}}|{ok:false,error}
|
|
226
|
+
*/
|
|
227
|
+
export async function pairRequest(o = {}) {
|
|
228
|
+
const { base, agentId, displayName, pubkey, code } = o;
|
|
229
|
+
if (!base) return errFrame("BAD_REQUEST", "missing base");
|
|
230
|
+
if (!agentId || !pubkey) return errFrame("BAD_REQUEST", "missing agentId or pubkey");
|
|
231
|
+
const body = { agentId, displayName: displayName || agentId, pubkey };
|
|
232
|
+
// `code` is the operator-chosen handshake token hq keys the row on. Include it
|
|
233
|
+
// whenever supplied so a real end-to-end pairing.request/approve flow works.
|
|
234
|
+
if (code != null && String(code).trim()) body.code = String(code).trim();
|
|
235
|
+
const r = await httpRequest(o.fetchImpl, v1Url(base, "pairing.request"), {
|
|
236
|
+
method: "POST",
|
|
237
|
+
headers: { "content-type": "application/json", "x-org-protocol": String(PROTOCOL_VERSION) },
|
|
238
|
+
body: JSON.stringify(body),
|
|
239
|
+
});
|
|
240
|
+
return asResFrame(r);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
// RPC (POST /v1/{method})
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Invoke an RPC method over the /v1 binding. Builds the correct headers
|
|
249
|
+
* (Bearer, x-org-protocol, and x-idempotency-key for side-effecting methods),
|
|
250
|
+
* posts the params object, and returns the parsed res frame. Fail-open: a
|
|
251
|
+
* transport error yields an INTERNAL error frame rather than throwing.
|
|
252
|
+
*
|
|
253
|
+
* @param {string} method full dotted method name (e.g. "board.claim")
|
|
254
|
+
* @param {object} params the params object (request body)
|
|
255
|
+
* @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
|
|
256
|
+
* @returns {Promise<object>} res frame
|
|
257
|
+
*/
|
|
258
|
+
export async function call(method, params, o = {}) {
|
|
259
|
+
const { base, token, orgId } = o;
|
|
260
|
+
if (!base) return errFrame("BAD_REQUEST", "missing base");
|
|
261
|
+
const def = methodDef(method);
|
|
262
|
+
if (!def) return errFrame("NOT_FOUND", `unknown method ${method}`);
|
|
263
|
+
|
|
264
|
+
const headers = baseHeaders(def.auth === false ? "" : token, def.auth === false ? "" : orgId);
|
|
265
|
+
if (def.sideEffecting) {
|
|
266
|
+
const key = o.idempotencyKey || (def.idempotent ? defaultIdempotencyKey(method, params) : "");
|
|
267
|
+
if (key) headers["x-idempotency-key"] = String(key);
|
|
268
|
+
}
|
|
269
|
+
const r = await httpRequest(o.fetchImpl, v1Url(base, method), {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers,
|
|
272
|
+
body: JSON.stringify(params || {}),
|
|
273
|
+
});
|
|
274
|
+
return asResFrame(r);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Derive a stable idempotency key for an idempotent method (best-effort). */
|
|
278
|
+
function defaultIdempotencyKey(method, params) {
|
|
279
|
+
const id = params && (params.id || params.entityId || params.agentId);
|
|
280
|
+
return id ? `${method}:${id}` : "";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Reads (GET)
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* GET a read endpoint. Reads return the BARE payload JSON on 200 (not wrapped in
|
|
289
|
+
* a frame); on failure the server returns an error frame. We normalise: success
|
|
290
|
+
* → { ok:true, payload }, failure → { ok:false, error, status }. Fail-open.
|
|
291
|
+
*
|
|
292
|
+
* @param {string} path the read path under /v1 (e.g. "snapshot", "events?cursor=0")
|
|
293
|
+
* @param {object} o - { base, token, orgId?, etag?, fetchImpl? }
|
|
294
|
+
* @returns {Promise<{ok:boolean, payload?:any, error?:object, status:number, etag?:string, notModified?:boolean}>}
|
|
295
|
+
*/
|
|
296
|
+
export async function read(path, o = {}) {
|
|
297
|
+
const { base, token, orgId } = o;
|
|
298
|
+
if (!base) return { ok: false, error: errFrame("BAD_REQUEST", "missing base").error, status: 0 };
|
|
299
|
+
const headers = baseHeaders(token, orgId);
|
|
300
|
+
delete headers["content-type"]; // GET has no body
|
|
301
|
+
if (o.etag) headers["if-none-match"] = o.etag;
|
|
302
|
+
|
|
303
|
+
const f = pickFetch(o.fetchImpl);
|
|
304
|
+
if (!f) return { ok: false, error: errFrame("INTERNAL", "no fetch").error, status: 0 };
|
|
305
|
+
const ctrl = typeof AbortController === "function" ? new AbortController() : null;
|
|
306
|
+
const readTimeoutMs = Number.isFinite(o.timeoutMs) && o.timeoutMs > 0 ? o.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
307
|
+
const timer = ctrl ? setTimeout(() => ctrl.abort(), readTimeoutMs) : null;
|
|
308
|
+
try {
|
|
309
|
+
const res = await f(v1Url(base, path), { method: "GET", headers, signal: ctrl ? ctrl.signal : undefined });
|
|
310
|
+
const status = typeof res.status === "number" ? res.status : 0;
|
|
311
|
+
const respEtag = readHeader(res, "etag");
|
|
312
|
+
if (status === 304) return { ok: true, notModified: true, status, etag: o.etag };
|
|
313
|
+
let body = null;
|
|
314
|
+
try { body = await res.json(); } catch { body = null; }
|
|
315
|
+
if (res.ok) return { ok: true, payload: body, status, etag: respEtag };
|
|
316
|
+
const error = body && body.error ? body.error : errFrame(httpStatusToCode(status), `http ${status}`).error;
|
|
317
|
+
return { ok: false, error, status };
|
|
318
|
+
} catch {
|
|
319
|
+
return { ok: false, error: errFrame("INTERNAL", "transport error").error, status: 0 };
|
|
320
|
+
} finally {
|
|
321
|
+
if (timer) clearTimeout(timer);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// Org-data API (GET /api/v1/org/...) — the directory/profile read surface
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
//
|
|
329
|
+
// DISTINCT from the /v1 RPC binding above: the org-data API is the human-facing
|
|
330
|
+
// directory surface (`/api/v1/org/whoami`, `/api/v1/org/members/<slug>`) that
|
|
331
|
+
// returns a MemberProfileDTO. `maestro setup`/`sync` PULL from here to
|
|
332
|
+
// auto-populate config/agent.json so an admin describes each agent ONCE in
|
|
333
|
+
// Cohort. Auth is the org API KEY (Bearer) + the org slug; the agent id (slug)
|
|
334
|
+
// selects a specific member, its absence selects whoami (sole-agent / email hint).
|
|
335
|
+
//
|
|
336
|
+
// Fail-open like everything else: a missing key / unreachable server / non-200
|
|
337
|
+
// yields an error frame, never a throw, so the wizard simply falls back to the
|
|
338
|
+
// manual prompts.
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Pull THIS agent's record from the Cohort org-data API as a MemberProfileDTO.
|
|
342
|
+
* Selects `GET /api/v1/org/members/<agentId>` when an agentId (slug) is supplied,
|
|
343
|
+
* else `GET /api/v1/org/whoami?orgId=<orgId>[&slug=<memberHint>][&email=<hint>]`
|
|
344
|
+
* (sole-agent / member-hinted resolution — orgId is the tenant PIN, not a member
|
|
345
|
+
* hint; `slug`/`email` are member hints). Returns the standard res frame
|
|
346
|
+
* ({ok:true,result:DTO}|{ok:false,error}); fail-open (never throws).
|
|
347
|
+
*
|
|
348
|
+
* @param {object} o
|
|
349
|
+
* @param {string} o.base Cohort server origin (e.g. https://cohort.internal)
|
|
350
|
+
* @param {string} o.apiKey org API key (Bearer)
|
|
351
|
+
* @param {string} o.orgId org slug — sent as the `?orgId=`/`x-org-id` tenant pin
|
|
352
|
+
* @param {string} [o.agentId] member slug — when set, hits /org/members/<slug>
|
|
353
|
+
* @param {string} [o.memberSlug] member-slug hint for whoami resolution (?slug=)
|
|
354
|
+
* @param {string} [o.emailHint] email hint for whoami resolution
|
|
355
|
+
* @param {Function} [o.fetchImpl] injectable fetch for tests
|
|
356
|
+
* @returns {Promise<object>} res frame
|
|
357
|
+
*/
|
|
358
|
+
export async function fetchSelfProfile(o = {}) {
|
|
359
|
+
const base = o.base ? String(o.base).replace(/\/+$/, "") : "";
|
|
360
|
+
const apiKey = o.apiKey ? String(o.apiKey) : "";
|
|
361
|
+
const orgId = o.orgId ? String(o.orgId) : "";
|
|
362
|
+
if (!base) return errFrame("BAD_REQUEST", "missing base");
|
|
363
|
+
if (!apiKey) return errFrame("UNAUTHORIZED", "missing api key");
|
|
364
|
+
if (!orgId) return errFrame("BAD_REQUEST", "missing orgId");
|
|
365
|
+
|
|
366
|
+
// The org is resolved server-side from the API KEY, not from a query param.
|
|
367
|
+
// Pin it defence-in-depth via `?orgId=` + the `x-org-id` header (hq treats a
|
|
368
|
+
// mismatch as 401, a match as harmless). whoami's `slug`/`email` params are
|
|
369
|
+
// reserved for genuine MEMBER hints — never the org slug — so we send a member
|
|
370
|
+
// slug hint (o.memberSlug) only when one is explicitly supplied.
|
|
371
|
+
const memberHint = o.memberSlug ? String(o.memberSlug) : "";
|
|
372
|
+
const path = o.agentId
|
|
373
|
+
? `org/members/${encodeURIComponent(String(o.agentId))}?orgId=${encodeURIComponent(orgId)}`
|
|
374
|
+
: `org/whoami?orgId=${encodeURIComponent(orgId)}${memberHint ? `&slug=${encodeURIComponent(memberHint)}` : ""}${o.emailHint ? `&email=${encodeURIComponent(String(o.emailHint))}` : ""}`;
|
|
375
|
+
const url = `${base}/api/v1/${path}`;
|
|
376
|
+
const headers = { authorization: `Bearer ${apiKey}`, accept: "application/json", "x-org-id": orgId };
|
|
377
|
+
|
|
378
|
+
const r = await httpRequest(o.fetchImpl, url, { method: "GET", headers });
|
|
379
|
+
if (!r.ok) {
|
|
380
|
+
const b = r.body;
|
|
381
|
+
if (b && typeof b === "object" && b.error) return errFrame(b.error.code || httpStatusToCode(r.status), b.error.message || `http ${r.status}`);
|
|
382
|
+
return errFrame(httpStatusToCode(r.status), `http ${r.status}`);
|
|
383
|
+
}
|
|
384
|
+
// The org-data API returns the BARE MemberProfileDTO on 200 (it may also wrap
|
|
385
|
+
// it under {member}/{profile}/{result}); normalise to a res frame.
|
|
386
|
+
const dto = unwrapProfile(r.body);
|
|
387
|
+
if (!dto) return errFrame("NOT_FOUND", "no profile in response");
|
|
388
|
+
return okFrame(dto);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Normalise the org-data API payload to the bare MemberProfileDTO (or null). */
|
|
392
|
+
function unwrapProfile(body) {
|
|
393
|
+
if (!body || typeof body !== "object") return null;
|
|
394
|
+
if (body.slug || body.displayName || body.agentEmail) return body;
|
|
395
|
+
for (const key of ["member", "profile", "result", "data"]) {
|
|
396
|
+
const v = body[key];
|
|
397
|
+
if (v && typeof v === "object" && (v.slug || v.displayName || v.agentEmail)) return v;
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Read a response header across fetch Response and plain-object fakes. */
|
|
403
|
+
function readHeader(res, name) {
|
|
404
|
+
try {
|
|
405
|
+
if (res && res.headers && typeof res.headers.get === "function") return res.headers.get(name) || undefined;
|
|
406
|
+
if (res && res.headers && typeof res.headers === "object") return res.headers[name] || res.headers[name.toLowerCase()];
|
|
407
|
+
} catch { /* ignore */ }
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ---------------------------------------------------------------------------
|
|
412
|
+
// Org-data API: structural reads (chart / relationships / strategy)
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
414
|
+
//
|
|
415
|
+
// The SAME human-facing directory surface as fetchSelfProfile (GET
|
|
416
|
+
// /api/v1/org/*, Bearer org API key + `?slug=<org>`), extended to the org's
|
|
417
|
+
// STRUCTURE: the chart (nodes + reporting/governance edges), a single member's
|
|
418
|
+
// relationships, and the org strategy doc. A provisioned agent consumes these as
|
|
419
|
+
// its source of truth so it knows who owns what, who it escalates to, and the
|
|
420
|
+
// org's north star — instead of inferring everything from its local archetype.
|
|
421
|
+
//
|
|
422
|
+
// hq returns these as `{ ok:true, ...DTO }` (chart/relationships) or
|
|
423
|
+
// `{ ok:true, strategy:DTO }`. We normalise to the same res frame fetchSelfProfile
|
|
424
|
+
// uses ({ok:true,result:DTO}|{ok:false,error}) so callers null-check uniformly.
|
|
425
|
+
// Fail-open like everything else: a missing key / unreachable server / non-200
|
|
426
|
+
// yields an error frame, never a throw.
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Shared org-data GET. Builds `GET {base}/api/v1/org/{path}?slug=<orgId>[&…]`
|
|
430
|
+
* with Bearer auth, runs it fail-open, and projects the 200 body through `pick`
|
|
431
|
+
* (body → meaningful DTO) into a res frame. Mirrors fetchSelfProfile's contract.
|
|
432
|
+
* @param {object} o - { base, apiKey, orgId, fetchImpl? }
|
|
433
|
+
* @param {string} path the sub-path under /api/v1/org/ (e.g. "chart")
|
|
434
|
+
* @param {object} extraParams extra query params (merged after slug)
|
|
435
|
+
* @param {(body:any)=>any} pick projects the 200 body to the DTO (null ⇒ NOT_FOUND)
|
|
436
|
+
* @returns {Promise<object>} res frame
|
|
437
|
+
*/
|
|
438
|
+
async function orgDataGet(o, path, extraParams, pick) {
|
|
439
|
+
const base = o && o.base ? String(o.base).replace(/\/+$/, "") : "";
|
|
440
|
+
const apiKey = o && o.apiKey ? String(o.apiKey) : "";
|
|
441
|
+
const orgId = o && o.orgId ? String(o.orgId) : "";
|
|
442
|
+
if (!base) return errFrame("BAD_REQUEST", "missing base");
|
|
443
|
+
if (!apiKey) return errFrame("UNAUTHORIZED", "missing api key");
|
|
444
|
+
if (!orgId) return errFrame("BAD_REQUEST", "missing orgId");
|
|
445
|
+
|
|
446
|
+
// Org is resolved from the API key server-side; `orgId` is the defence-in-depth
|
|
447
|
+
// pin (`?orgId=` + `x-org-id` header), NOT a `slug` (which hq reads as a member
|
|
448
|
+
// hint). `member=<slug>` and other genuine params ride in via extraParams.
|
|
449
|
+
const qs = new URLSearchParams({ orgId, ...(extraParams || {}) }).toString();
|
|
450
|
+
const url = `${base}/api/v1/org/${path}?${qs}`;
|
|
451
|
+
const headers = { authorization: `Bearer ${apiKey}`, accept: "application/json", "x-org-id": orgId };
|
|
452
|
+
|
|
453
|
+
const r = await httpRequest(o.fetchImpl, url, { method: "GET", headers });
|
|
454
|
+
if (!r.ok) {
|
|
455
|
+
const b = r.body;
|
|
456
|
+
if (b && typeof b === "object" && b.error) return errFrame(b.error.code || httpStatusToCode(r.status), b.error.message || `http ${r.status}`);
|
|
457
|
+
return errFrame(httpStatusToCode(r.status), `http ${r.status}`);
|
|
458
|
+
}
|
|
459
|
+
const result = pick(r.body);
|
|
460
|
+
if (result == null) return errFrame("NOT_FOUND", `no ${path} payload`);
|
|
461
|
+
return okFrame(result);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Project the chart body ({ok,nodes,edges} | {chart:{…}}) → { nodes, edges }. */
|
|
465
|
+
function pickChart(body) {
|
|
466
|
+
if (!body || typeof body !== "object") return null;
|
|
467
|
+
const src = Array.isArray(body.nodes) || Array.isArray(body.edges) ? body : (body.chart || body.result);
|
|
468
|
+
if (!src || typeof src !== "object") return null;
|
|
469
|
+
return { nodes: Array.isArray(src.nodes) ? src.nodes : [], edges: Array.isArray(src.edges) ? src.edges : [] };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** Project the relationships body → { member, reportingLine, reports, governance }. */
|
|
473
|
+
function pickRelationships(body) {
|
|
474
|
+
if (!body || typeof body !== "object") return null;
|
|
475
|
+
const has = body.member || body.reportingLine || Array.isArray(body.reports) || Array.isArray(body.governance);
|
|
476
|
+
const src = has ? body : (body.relationships || body.result);
|
|
477
|
+
if (!src || typeof src !== "object") return null;
|
|
478
|
+
return {
|
|
479
|
+
member: src.member || null,
|
|
480
|
+
reportingLine: src.reportingLine || null,
|
|
481
|
+
reports: Array.isArray(src.reports) ? src.reports : [],
|
|
482
|
+
governance: Array.isArray(src.governance) ? src.governance : [],
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** Project the strategy body ({ok,strategy:{…}} | bare StrategyDTO) → StrategyDTO. */
|
|
487
|
+
function pickStrategy(body) {
|
|
488
|
+
if (!body || typeof body !== "object") return null;
|
|
489
|
+
if (body.strategy && typeof body.strategy === "object") return body.strategy;
|
|
490
|
+
if (body.northStar !== undefined || Array.isArray(body.phases) || Array.isArray(body.streams)) return body;
|
|
491
|
+
if (body.result && typeof body.result === "object") return body.result;
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Fetch the org chart (GET /api/v1/org/chart): active-member nodes + reporting/
|
|
497
|
+
* governance edges (keyed by member slug). Org-scoped via the API key. Returns a
|
|
498
|
+
* res frame whose result is `{ nodes, edges }`; fail-open (never throws).
|
|
499
|
+
* @param {object} o - { base, apiKey, orgId, fetchImpl? }
|
|
500
|
+
* @returns {Promise<object>} res frame {ok:true,result:{nodes,edges}}|{ok:false,error}
|
|
501
|
+
*/
|
|
502
|
+
export async function fetchOrgChart(o = {}) {
|
|
503
|
+
return orgDataGet(o, "chart", undefined, pickChart);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Fetch ONE member's relationships (GET /api/v1/org/relationships?member=<slug>):
|
|
508
|
+
* reporting line, reports, and governance edges. The member slug is `o.member`
|
|
509
|
+
* (falls back to `o.agentId`). Returns a res frame whose result is
|
|
510
|
+
* `{ member, reportingLine, reports, governance }`; fail-open.
|
|
511
|
+
* @param {object} o - { base, apiKey, orgId, member|agentId, fetchImpl? }
|
|
512
|
+
* @returns {Promise<object>} res frame
|
|
513
|
+
*/
|
|
514
|
+
export async function fetchRelationships(o = {}) {
|
|
515
|
+
const member = (o && (o.member || o.agentId)) ? String(o.member || o.agentId) : "";
|
|
516
|
+
if (!member) return errFrame("BAD_REQUEST", "missing member slug");
|
|
517
|
+
return orgDataGet(o, "relationships", { member }, pickRelationships);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Fetch the org strategy doc (GET /api/v1/org/strategy): north star, levers,
|
|
522
|
+
* phases and streams with their RACI assignments. Org-scoped via the API key.
|
|
523
|
+
* Returns a res frame whose result is the StrategyDTO; fail-open. A tenant with
|
|
524
|
+
* no strategy doc yet yields a NOT_FOUND error frame (the agent runs without it).
|
|
525
|
+
* @param {object} o - { base, apiKey, orgId, fetchImpl? }
|
|
526
|
+
* @returns {Promise<object>} res frame
|
|
527
|
+
*/
|
|
528
|
+
export async function fetchStrategy(o = {}) {
|
|
529
|
+
return orgDataGet(o, "strategy", undefined, pickStrategy);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Fetch the org hierarchy over the /v1 read surface (GET /v1/hierarchy). Unlike
|
|
534
|
+
* the org-data DTO reads above this is the protocol read binding (Bearer token,
|
|
535
|
+
* BARE payload on 200 per the read contract). Returns the bare
|
|
536
|
+
* `{ members, reporting }` payload (normalised), or null on any failure
|
|
537
|
+
* (fail-open). The agent uses it as the authoritative reporting graph.
|
|
538
|
+
* @param {object} o - { base, token, orgId?, fetchImpl? }
|
|
539
|
+
* @returns {Promise<{members:object[], reporting:object[]}|null>}
|
|
540
|
+
*/
|
|
541
|
+
export async function fetchHierarchy(o = {}) {
|
|
542
|
+
const r = await read("hierarchy", o);
|
|
543
|
+
if (!r.ok || !r.payload) return null;
|
|
544
|
+
const p = r.payload;
|
|
545
|
+
const members = Array.isArray(p) ? p : (Array.isArray(p.members) ? p.members : []);
|
|
546
|
+
const reporting = Array.isArray(p.reporting) ? p.reporting : [];
|
|
547
|
+
return { members, reporting };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ---------------------------------------------------------------------------
|
|
551
|
+
// Typed RPC helpers
|
|
552
|
+
// ---------------------------------------------------------------------------
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* register self with the org (registry.register). Idempotent.
|
|
556
|
+
* @param {object} entry directory entry (id, displayName, role, focus, pubkey…)
|
|
557
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
558
|
+
*/
|
|
559
|
+
export function register(entry, o = {}) {
|
|
560
|
+
return call("registry.register", entry || {}, o);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** Heartbeat the registry liveness (registry.heartbeat). */
|
|
564
|
+
export function heartbeat(params, o = {}) {
|
|
565
|
+
return call("registry.heartbeat", params || {}, o);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Presence beat (presence.beat) — the result carries directives[] (subset of
|
|
570
|
+
* DIRECTIVES) incl. the "halt" kill switch when this agent was deactivated.
|
|
571
|
+
*/
|
|
572
|
+
export function presenceBeat(params, o = {}) {
|
|
573
|
+
return call("presence.beat", params || {}, o);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** Create a board work-item (board.create). Idempotent. */
|
|
577
|
+
export function createItem(item, o = {}) {
|
|
578
|
+
return call("board.create", item || {}, o);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Claim a board item (board.claim). The server resolves the race atomically; the
|
|
583
|
+
* loser gets a CONFLICT error frame. Caller MUST pass a stable idempotencyKey
|
|
584
|
+
* for safe retries.
|
|
585
|
+
*/
|
|
586
|
+
export function claimItem(params, o = {}) {
|
|
587
|
+
return call("board.claim", params || {}, o);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** Complete a board item (board.complete). */
|
|
591
|
+
export function completeItem(params, o = {}) {
|
|
592
|
+
return call("board.complete", params || {}, o);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* List the ready feed (GET /v1/board.ready). Returns the items array, or [] on
|
|
597
|
+
* any failure (fail-open).
|
|
598
|
+
* @param {object} o - { base, token, fetchImpl? }
|
|
599
|
+
* @returns {Promise<object[]>}
|
|
600
|
+
*/
|
|
601
|
+
export async function listReady(o = {}) {
|
|
602
|
+
const r = await read("board.ready", o);
|
|
603
|
+
if (!r.ok || !r.payload) return [];
|
|
604
|
+
if (Array.isArray(r.payload)) return r.payload;
|
|
605
|
+
if (Array.isArray(r.payload.items)) return r.payload.items;
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Fetch the event slice (GET /v1/events?cursor=N&limit=500). Returns the events
|
|
611
|
+
* array (or [] on failure). The caller pipes these to verify.verifyEvents.
|
|
612
|
+
* @param {number} cursor seq to read from (exclusive)
|
|
613
|
+
* @param {object} o - { base, token, limit?, fetchImpl? }
|
|
614
|
+
* @returns {Promise<object[]>}
|
|
615
|
+
*/
|
|
616
|
+
export async function fetchEvents(cursor = 0, o = {}) {
|
|
617
|
+
const limit = Number.isFinite(o.limit) ? o.limit : 500;
|
|
618
|
+
const path = `events?cursor=${encodeURIComponent(cursor)}&limit=${encodeURIComponent(limit)}`;
|
|
619
|
+
const r = await read(path, o);
|
|
620
|
+
if (!r.ok || !r.payload) return [];
|
|
621
|
+
if (Array.isArray(r.payload)) return r.payload;
|
|
622
|
+
if (Array.isArray(r.payload.events)) return r.payload.events;
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Fetch the signed snapshot (GET /v1/snapshot). Returns the raw snapshot payload
|
|
628
|
+
* (with its `sig` envelope) for the detective layer to verify, or null on
|
|
629
|
+
* failure. Verification is the CALLER's job (verify.verifySnapshot with PINNED
|
|
630
|
+
* signers) — this function does not trust the server.
|
|
631
|
+
* @param {object} o - { base, token, fetchImpl? }
|
|
632
|
+
* @returns {Promise<object|null>}
|
|
633
|
+
*/
|
|
634
|
+
export async function fetchSnapshot(o = {}) {
|
|
635
|
+
const r = await read("snapshot", o);
|
|
636
|
+
if (!r.ok || !r.payload) return null;
|
|
637
|
+
return r.payload;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// ---------------------------------------------------------------------------
|
|
641
|
+
// Phase-2 typed RPC helpers — leases, handoffs, board DAG, governance
|
|
642
|
+
// ---------------------------------------------------------------------------
|
|
643
|
+
//
|
|
644
|
+
// Every helper below is a thin wrapper over call() over the /v1 binding: Bearer
|
|
645
|
+
// auth + x-org-protocol on every request, x-idempotency-key on the side-effecting
|
|
646
|
+
// ones (lease/handoff/board.* are all sideEffecting in the registry, so the
|
|
647
|
+
// caller SHOULD pass a stable `o.idempotencyKey` for safe retries; lease.claim /
|
|
648
|
+
// board.* are non-idempotent in the registry so no key is auto-synthesised — the
|
|
649
|
+
// server resolves the race atomically and the loser gets LEASE_HELD/CONFLICT).
|
|
650
|
+
// All fail open: a disabled/unreachable server yields an error frame, never a
|
|
651
|
+
// throw, so the lease/handoff/board paths can never take down the agent.
|
|
652
|
+
|
|
653
|
+
// --- leases (one primitive, scope ∈ LEASE_SCOPES) --------------------------
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Claim a generic lease (lease.claim). Exactly one holder per (scope,resourceId)
|
|
657
|
+
* until expiry/release; the loser gets a CONFLICT/LEASE_HELD error frame. The
|
|
658
|
+
* winning frame carries `{ token, expiresAt, ttlMs }` (token = the bearer for
|
|
659
|
+
* heartbeat/release; redacted everywhere else).
|
|
660
|
+
* @param {object} params - { scope, resourceId, holder?, ttlMs? }
|
|
661
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
662
|
+
*/
|
|
663
|
+
export function leaseClaim(params, o = {}) {
|
|
664
|
+
return call("lease.claim", params || {}, o);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Extend a held lease (lease.heartbeat). Slides expiry by the original TTL.
|
|
669
|
+
* @param {object} params - { scope, resourceId, leaseToken }
|
|
670
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
671
|
+
*/
|
|
672
|
+
export function leaseHeartbeat(params, o = {}) {
|
|
673
|
+
return call("lease.heartbeat", params || {}, o);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Release a held lease (lease.release). Frees (scope,resourceId) immediately.
|
|
678
|
+
* @param {object} params - { scope, resourceId, leaseToken }
|
|
679
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
680
|
+
*/
|
|
681
|
+
export function leaseRelease(params, o = {}) {
|
|
682
|
+
return call("lease.release", params || {}, o);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// --- handoffs (signed delegation lifecycle — ALL governance-gated §0.4) -----
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Offer a handoff (handoff.offer). The delegator posts the signed envelope; the
|
|
689
|
+
* server records a handoff row in state `offered`. governanceGated — returns a
|
|
690
|
+
* GOVERNANCE_NOT_READY error frame until the server is governance_ready.
|
|
691
|
+
* @param {object} params - { handoffId, itemId?, to, envelope, envelopeSig, deadlineAt? }
|
|
692
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
693
|
+
*/
|
|
694
|
+
export function handoffOffer(params, o = {}) {
|
|
695
|
+
return call("handoff.offer", params || {}, o);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Accept an offered handoff (handoff.accept). The delegatee claims the work.
|
|
700
|
+
* governanceGated.
|
|
701
|
+
* @param {object} params - { handoffId }
|
|
702
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
703
|
+
*/
|
|
704
|
+
export function handoffAccept(params, o = {}) {
|
|
705
|
+
return call("handoff.accept", params || {}, o);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Decline an offered handoff (handoff.decline). governanceGated.
|
|
710
|
+
* @param {object} params - { handoffId, reason? }
|
|
711
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
712
|
+
*/
|
|
713
|
+
export function handoffDecline(params, o = {}) {
|
|
714
|
+
return call("handoff.decline", params || {}, o);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Cancel a handoff the caller offered (handoff.cancel). governanceGated.
|
|
719
|
+
* @param {object} params - { handoffId, reason? }
|
|
720
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
721
|
+
*/
|
|
722
|
+
export function handoffCancel(params, o = {}) {
|
|
723
|
+
return call("handoff.cancel", params || {}, o);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// --- board DAG (decompose/assign governance-gated; link not) ----------------
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Decompose a board item into children + blocking links (board.decompose).
|
|
730
|
+
* governanceGated. Server creates the child items and the typed edges in one
|
|
731
|
+
* transaction (DFS cycle check on insert).
|
|
732
|
+
* @param {object} params - { itemId, children:[{title,…}], linkType? }
|
|
733
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
734
|
+
*/
|
|
735
|
+
export function boardDecompose(params, o = {}) {
|
|
736
|
+
return call("board.decompose", params || {}, o);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Assign an owner to a board item (board.assign). governanceGated (lead-agent +
|
|
741
|
+
* decision_rights checked server-side).
|
|
742
|
+
* @param {object} params - { itemId, assignee }
|
|
743
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
744
|
+
*/
|
|
745
|
+
export function boardAssign(params, o = {}) {
|
|
746
|
+
return call("board.assign", params || {}, o);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Link two board items with a typed edge (board.link). NOT governance-gated.
|
|
751
|
+
* @param {object} params - { fromItem, toItem, linkType? } (linkType default "blocks"; server contract)
|
|
752
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
753
|
+
*/
|
|
754
|
+
export function boardLink(params, o = {}) {
|
|
755
|
+
return call("board.link", params || {}, o);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// --- governance ------------------------------------------------------------
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Read the governance readiness status (GET-style RPC governance.status).
|
|
762
|
+
* Non-side-effecting; returns the res frame whose result carries
|
|
763
|
+
* `{ governanceReady, decisionRightsCount, signingLive, verifiedCards }` (server
|
|
764
|
+
* shape). Used by the daemon/doctor to know whether handoffs/assignment will flow.
|
|
765
|
+
* @param {object} o - { base, token, fetchImpl? }
|
|
766
|
+
*/
|
|
767
|
+
export function governanceStatus(o = {}) {
|
|
768
|
+
return call("governance.status", {}, o);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// ---------------------------------------------------------------------------
|
|
772
|
+
// Phase-3 typed RPC helpers — approvals, decisions, cost, policy
|
|
773
|
+
// ---------------------------------------------------------------------------
|
|
774
|
+
//
|
|
775
|
+
// Same contract as the phase-1/2 helpers: a thin wrapper over call() / read()
|
|
776
|
+
// over the /v1 binding, Bearer + x-org-protocol on every request, and the
|
|
777
|
+
// idempotent side-effecting methods (approval.request / decision.propose /
|
|
778
|
+
// cost.report) get an x-idempotency-key (auto-derived from a stable id when the
|
|
779
|
+
// caller doesn't supply one). All fail open — a disabled/unreachable server
|
|
780
|
+
// yields an error frame, never a throw — so the coordination loop (G16: ZERO LLM
|
|
781
|
+
// calls in any of these paths) can never take down the agent.
|
|
782
|
+
|
|
783
|
+
// --- approvals (G5 requester != approver; sha256 payload binding) -----------
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Request approval for a classified action (approval.request). Idempotent — the
|
|
787
|
+
* caller MUST pass a stable `o.idempotencyKey` (or `params.id`) so a retried
|
|
788
|
+
* request reuses the same approval row rather than spawning a duplicate ask.
|
|
789
|
+
*
|
|
790
|
+
* The params carry the sha256 `payload_hash` ("the human approved exactly this")
|
|
791
|
+
* — the server binds the approval to it and rejects a resolve whose executing
|
|
792
|
+
* payload hash differs. lib/org/approvals.requestApproval computes the hash and
|
|
793
|
+
* fills this in; call this directly only when you already have the hash.
|
|
794
|
+
*
|
|
795
|
+
* @param {object} params - { id?, kind, subject, payload_hash, itemId?, expiresInMs? }
|
|
796
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
797
|
+
*/
|
|
798
|
+
export function approvalRequest(params, o = {}) {
|
|
799
|
+
return call("approval.request", params || {}, o);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Resolve (approve/deny) an approval (approval.resolve). Requires the
|
|
804
|
+
* `approval.decide` scope — approvers/humans only; an ordinary agent token is
|
|
805
|
+
* rejected with FORBIDDEN_SCOPE. The server ALSO enforces requester != approver
|
|
806
|
+
* (G5: DB CHECK + method-layer) and re-checks the bound `payload_hash`.
|
|
807
|
+
* NOT idempotent in the registry; pass a stable `o.idempotencyKey` for retries.
|
|
808
|
+
*
|
|
809
|
+
* @param {object} params - { id, decision:'approved'|'denied', reason?, payload_hash? }
|
|
810
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
811
|
+
*/
|
|
812
|
+
export function approvalResolve(params, o = {}) {
|
|
813
|
+
return call("approval.resolve", params || {}, o);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Fetch a single approval's current state (approval.get). Non-side-effecting.
|
|
818
|
+
* @param {object} params - { id }
|
|
819
|
+
* @param {object} o - { base, token, fetchImpl? }
|
|
820
|
+
*/
|
|
821
|
+
export function approvalGet(params, o = {}) {
|
|
822
|
+
return call("approval.get", params || {}, o);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* List approvals visible to the actor (approval.list). Non-side-effecting.
|
|
827
|
+
* @param {object} params - { status?, requestedBy?, limit? }
|
|
828
|
+
* @param {object} o - { base, token, fetchImpl? }
|
|
829
|
+
*/
|
|
830
|
+
export function approvalList(params, o = {}) {
|
|
831
|
+
return call("approval.list", params || {}, o);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Bounded long-poll for an approval outcome (GET /v1/approval.wait?id=&timeoutMs=).
|
|
836
|
+
* Resolves when the approval is decided OR the timeout elapses (returning the
|
|
837
|
+
* CURRENT state either way). `timeoutMs` is clamped to <= 55000 server-side, so
|
|
838
|
+
* we clamp here too and budget the client abort timeout to outlast it. The
|
|
839
|
+
* server polls storage on an interval and never holds a DB write lock.
|
|
840
|
+
*
|
|
841
|
+
* Returns the standard read shape ({ ok, payload, error, status }); the payload
|
|
842
|
+
* carries `{ id, status, resolved_by_agent?, resolved_at?, ... }`. Fail-open.
|
|
843
|
+
*
|
|
844
|
+
* @param {object} params - { id, timeoutMs? }
|
|
845
|
+
* @param {object} o - { base, token, fetchImpl? }
|
|
846
|
+
* @returns {Promise<{ok:boolean, payload?:any, error?:object, status:number}>}
|
|
847
|
+
*/
|
|
848
|
+
export function approvalWait(params, o = {}) {
|
|
849
|
+
const id = params && params.id;
|
|
850
|
+
if (!id) return Promise.resolve({ ok: false, error: errFrame("BAD_REQUEST", "missing id").error, status: 0 });
|
|
851
|
+
const timeoutMs = clampWaitMs(params && params.timeoutMs);
|
|
852
|
+
const path = `approval.wait?id=${encodeURIComponent(id)}&timeoutMs=${encodeURIComponent(timeoutMs)}`;
|
|
853
|
+
// The long-poll deliberately outlives the default RPC timeout: give the client
|
|
854
|
+
// abort a margin over the server's bound so the server gets to return the
|
|
855
|
+
// timed-out state rather than us aborting first.
|
|
856
|
+
return read(path, { ...o, timeoutMs: timeoutMs + 5000 });
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/** Clamp a long-poll timeout to the protocol-bounded [0, 55000] ms window. */
|
|
860
|
+
function clampWaitMs(ms) {
|
|
861
|
+
const n = Number(ms);
|
|
862
|
+
if (!Number.isFinite(n) || n <= 0) return 55000;
|
|
863
|
+
return Math.min(55000, Math.floor(n));
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// --- decisions registry (adopt/supersede governance-gated) ------------------
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Propose a decision into the registry (decision.propose). Idempotent — pass a
|
|
870
|
+
* stable `o.idempotencyKey` (or `params.id`) so a retry reuses the row.
|
|
871
|
+
* @param {object} params - { id?, title, rationale?, scope?, links? }
|
|
872
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
873
|
+
*/
|
|
874
|
+
export function decisionPropose(params, o = {}) {
|
|
875
|
+
return call("decision.propose", params || {}, o);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Adopt a proposed decision (decision.adopt). governanceGated — returns
|
|
880
|
+
* GOVERNANCE_NOT_READY until the server is governance_ready.
|
|
881
|
+
* @param {object} params - { id }
|
|
882
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
883
|
+
*/
|
|
884
|
+
export function decisionAdopt(params, o = {}) {
|
|
885
|
+
return call("decision.adopt", params || {}, o);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Supersede an adopted decision with a newer one (decision.supersede).
|
|
890
|
+
* governanceGated.
|
|
891
|
+
* @param {object} params - { id, supersededBy }
|
|
892
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
893
|
+
*/
|
|
894
|
+
export function decisionSupersede(params, o = {}) {
|
|
895
|
+
return call("decision.supersede", params || {}, o);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* List adopted decisions (GET /v1/decision.list). Returns the decisions array,
|
|
900
|
+
* or [] on any failure (fail-open).
|
|
901
|
+
* @param {object} o - { base, token, status?, fetchImpl? }
|
|
902
|
+
* @returns {Promise<object[]>}
|
|
903
|
+
*/
|
|
904
|
+
export async function decisionList(o = {}) {
|
|
905
|
+
const qs = o && o.status ? `?status=${encodeURIComponent(o.status)}` : "";
|
|
906
|
+
const r = await read(`decision.list${qs}`, o);
|
|
907
|
+
if (!r.ok || !r.payload) return [];
|
|
908
|
+
if (Array.isArray(r.payload)) return r.payload;
|
|
909
|
+
if (Array.isArray(r.payload.decisions)) return r.payload.decisions;
|
|
910
|
+
return [];
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// --- cost rollups (agents report usage; rollups read org-wide) --------------
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Report per-session usage into the org rollup (cost.report). Idempotent on the
|
|
917
|
+
* row id / decision_id — pass a stable `o.idempotencyKey` so a re-pushed batch
|
|
918
|
+
* never double-counts. The params shape mirrors the maestro router ledger row
|
|
919
|
+
* (agent, model, input_tokens, output_tokens, estimated_usd, decision_id,
|
|
920
|
+
* cadence/source) — see lib/org/cost-sync.mjs.
|
|
921
|
+
* @param {object} params - { rows:[…] } or a single row
|
|
922
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
923
|
+
*/
|
|
924
|
+
export function costReport(params, o = {}) {
|
|
925
|
+
return call("cost.report", params || {}, o);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Read the org-wide cost rollup (GET /v1/cost.rollup). Aggregated by
|
|
930
|
+
* agent/model/cadence/day server-side. Returns the bare payload, or null on
|
|
931
|
+
* failure (fail-open). The payload includes the G16 honesty line: any LLM spend
|
|
932
|
+
* attributed to a coordination cadence is surfaced here so a violation is visible.
|
|
933
|
+
* @param {object} o - { base, token, day?, agent?, fetchImpl? }
|
|
934
|
+
* @returns {Promise<object|null>}
|
|
935
|
+
*/
|
|
936
|
+
export async function costRollup(o = {}) {
|
|
937
|
+
const params = [];
|
|
938
|
+
if (o && o.day) params.push(`day=${encodeURIComponent(o.day)}`);
|
|
939
|
+
if (o && o.agent) params.push(`agent=${encodeURIComponent(o.agent)}`);
|
|
940
|
+
const qs = params.length ? `?${params.join("&")}` : "";
|
|
941
|
+
const r = await read(`cost.rollup${qs}`, o);
|
|
942
|
+
if (!r.ok || !r.payload) return null;
|
|
943
|
+
return r.payload;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// --- policy distribution (publish admin; fetch via the read surface) --------
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Publish a signed policy bundle (policy.publish). ADMIN — reserved namespace,
|
|
950
|
+
* always requires the `admin` scope; an ordinary agent token is rejected with
|
|
951
|
+
* FORBIDDEN_SCOPE. Agents FETCH via policyFetch; they never publish.
|
|
952
|
+
* @param {object} params - { bundleId, version, kind, content, content_sig, source_commit, reload_kind?, rollout? }
|
|
953
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
954
|
+
*/
|
|
955
|
+
export function policyPublish(params, o = {}) {
|
|
956
|
+
return call("policy.publish", params || {}, o);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Fetch the current signed policy bundle(s) (GET /v1/policy). Returns the raw
|
|
961
|
+
* bundle payload (with its `content_sig` envelope) for lib/org/policy.mjs to
|
|
962
|
+
* verify against the PINNED trust root — this function does NOT trust the server.
|
|
963
|
+
* Returns null on failure (fail-open).
|
|
964
|
+
* @param {object} o - { base, token, kind?, fetchImpl? }
|
|
965
|
+
* @returns {Promise<object|null>}
|
|
966
|
+
*/
|
|
967
|
+
export async function policyFetch(o = {}) {
|
|
968
|
+
const qs = o && o.kind ? `?kind=${encodeURIComponent(o.kind)}` : "";
|
|
969
|
+
const r = await read(`policy${qs}`, o);
|
|
970
|
+
if (!r.ok || !r.payload) return null;
|
|
971
|
+
return r.payload;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// ---------------------------------------------------------------------------
|
|
975
|
+
// Org credential broker — the single source of truth for org third-party creds
|
|
976
|
+
// ---------------------------------------------------------------------------
|
|
977
|
+
//
|
|
978
|
+
// The payoff of the org plane: an org runs 50-100 agents on separate machines
|
|
979
|
+
// and must NOT provision a DeepSeek/Kimi/OpenAI key onto every one (unmanageable
|
|
980
|
+
// + a breach surface). Instead an admin sets each provider key ONCE in Cohort
|
|
981
|
+
// (credential.put); an agent LEASES it short-lived, role-scoped, and audited
|
|
982
|
+
// (credential.lease) — the org key never persists on the agent machine.
|
|
983
|
+
//
|
|
984
|
+
// Same contract as every other helper: a thin wrapper over call() over the /v1
|
|
985
|
+
// binding, Bearer + x-org-protocol on every request, x-idempotency-key on the
|
|
986
|
+
// side-effecting methods. All fail open — a disabled/unreachable server yields an
|
|
987
|
+
// error frame, never a throw — so a missing org credential surfaces downstream as
|
|
988
|
+
// the router's existing `missing_credential`, never as a crash.
|
|
989
|
+
//
|
|
990
|
+
// SCOPES (frozen in protocol.mjs): credential.put / list / revoke are ADMIN
|
|
991
|
+
// (reserved namespace ⇒ always require `admin`; an ordinary agent token is
|
|
992
|
+
// rejected with FORBIDDEN_SCOPE). credential.lease requires `credential.use`,
|
|
993
|
+
// which IS in DEFAULT_AGENT_SCOPES — so a normal paired agent can lease but never
|
|
994
|
+
// put/list/revoke.
|
|
995
|
+
//
|
|
996
|
+
// SECURITY: credential.lease returns the plaintext ONLY in the response frame
|
|
997
|
+
// (over TLS in prod). credential.list NEVER returns the value (metadata only).
|
|
998
|
+
// The value is NEVER logged anywhere on either side. These helpers MUST NOT
|
|
999
|
+
// persist the leased value to disk; the in-memory cache lives in
|
|
1000
|
+
// lib/model-router/org-credentials.mjs, not here.
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Lease an org-level third-party credential (credential.lease). Scope
|
|
1004
|
+
* `credential.use` (in DEFAULT_AGENT_SCOPES). The server resolves the caller's
|
|
1005
|
+
* verified identity, checks the credential's allowedRoles/allowedAgents, and —
|
|
1006
|
+
* on success — OPENS the AES-256-GCM box transiently and returns the plaintext
|
|
1007
|
+
* in the result frame ONLY: `{ provider, value, ttlMs, expiresAt, auth_env? }`.
|
|
1008
|
+
* A role/agent the credential does not permit gets FORBIDDEN_SCOPE.
|
|
1009
|
+
*
|
|
1010
|
+
* NOT idempotent in the registry (each lease is a fresh short-lived grant +
|
|
1011
|
+
* audit row); only pass an `o.idempotencyKey` if you explicitly want to coalesce
|
|
1012
|
+
* identical retries. Fail-open: an unreachable server yields an error frame so
|
|
1013
|
+
* the router falls through to whatever local env already has.
|
|
1014
|
+
*
|
|
1015
|
+
* @param {object} params - { provider }
|
|
1016
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1017
|
+
* @returns {Promise<object>} res frame {ok:true,result:{provider,value,ttlMs,expiresAt}}|{ok:false,error}
|
|
1018
|
+
*/
|
|
1019
|
+
export function credentialLease(params, o = {}) {
|
|
1020
|
+
return call("credential.lease", params || {}, o);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Set (upsert) an org-level third-party credential (credential.put). ADMIN —
|
|
1025
|
+
* reserved namespace, always requires the `admin` scope; an ordinary agent token
|
|
1026
|
+
* is rejected with FORBIDDEN_SCOPE. The admin sets each provider key ONCE; the
|
|
1027
|
+
* server SEALS the value at rest (AES-256-GCM) and never logs it. Idempotent on
|
|
1028
|
+
* `provider` — pass a stable `o.idempotencyKey` (auto-derived from
|
|
1029
|
+
* `params.provider` when absent) so a re-pushed key collapses to one rotation.
|
|
1030
|
+
*
|
|
1031
|
+
* @param {object} params - { provider, value, auth_env?, allowedRoles?, allowedAgents?, ttlMs? }
|
|
1032
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1033
|
+
*/
|
|
1034
|
+
export function credentialPut(params, o = {}) {
|
|
1035
|
+
const opts = o;
|
|
1036
|
+
if (!opts.idempotencyKey && params && params.provider) {
|
|
1037
|
+
opts.idempotencyKey = `credential.put:${params.provider}`;
|
|
1038
|
+
}
|
|
1039
|
+
return call("credential.put", params || {}, opts);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* List org credentials' METADATA (credential.list). ADMIN. NEVER returns the
|
|
1044
|
+
* value — only provider, auth_env, allowedRoles/allowedAgents, ttl, timestamps.
|
|
1045
|
+
* Non-side-effecting. Returns the res frame; the caller reads `result.credentials`.
|
|
1046
|
+
* @param {object} params - { provider? }
|
|
1047
|
+
* @param {object} o - { base, token, fetchImpl? }
|
|
1048
|
+
*/
|
|
1049
|
+
export function credentialList(params, o = {}) {
|
|
1050
|
+
return call("credential.list", params || {}, o);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Revoke an org credential (credential.revoke). ADMIN. Future leases for the
|
|
1055
|
+
* provider fail; already-leased values expire at their TTL (the org key never
|
|
1056
|
+
* persisted on the agent, so revoke + TTL is the bound on exposure). NOT
|
|
1057
|
+
* idempotent in the registry; pass a stable `o.idempotencyKey` for retries.
|
|
1058
|
+
* @param {object} params - { provider, reason? }
|
|
1059
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1060
|
+
*/
|
|
1061
|
+
export function credentialRevoke(params, o = {}) {
|
|
1062
|
+
return call("credential.revoke", params || {}, o);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// ---------------------------------------------------------------------------
|
|
1066
|
+
// Phase-4 typed RPC helpers — knowledge plane, contacts, meetings
|
|
1067
|
+
// ---------------------------------------------------------------------------
|
|
1068
|
+
//
|
|
1069
|
+
// The agent half of the shared knowledge plane (SPEC-org-server §4.7). Same
|
|
1070
|
+
// contract as the phase-1/2/3 helpers: a thin wrapper over call() / read() over
|
|
1071
|
+
// the /v1 binding, Bearer + x-org-protocol on every request, x-idempotency-key
|
|
1072
|
+
// on the idempotent side-effecting methods (knowledge.append / contacts.upsert /
|
|
1073
|
+
// meetings.record — auto-derived from a stable id when the caller doesn't supply
|
|
1074
|
+
// one). All fail open — a disabled/unreachable server yields an error frame,
|
|
1075
|
+
// never a throw.
|
|
1076
|
+
//
|
|
1077
|
+
// CRITICAL — the SERVER is authoritative for ACL. These helpers NEVER decide
|
|
1078
|
+
// what a fact's group is, never filter a search result, and never inspect a
|
|
1079
|
+
// barrier cell. The non-bypassable RLS floor (group membership, barrier:<cell>
|
|
1080
|
+
// deny-override, participant-derived ACLs) lives in the server's query layer.
|
|
1081
|
+
// The client only renders what the server chose to return. knowledge.search is
|
|
1082
|
+
// SIDE-EFFECTING by contract: every retrieval is logged to org_events family
|
|
1083
|
+
// 'knowledge' (the regulator-facing read-audit trail) — so it carries the same
|
|
1084
|
+
// idempotency/Bearer treatment as a write, not a read.
|
|
1085
|
+
|
|
1086
|
+
// --- knowledge facts (mutable-artifact semantics) --------------------------
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Append a knowledge episode (knowledge.append). THE sole shared-write primitive;
|
|
1090
|
+
* commutes (concurrent appends both land) and is IDEMPOTENT — pass a stable
|
|
1091
|
+
* `o.idempotencyKey` (or `params.id`) so a retried append doesn't double-write.
|
|
1092
|
+
* The server assigns the fact's group from the caller's verified identity / the
|
|
1093
|
+
* supplied `group` hint subject to the RLS floor — the client never decides ACL.
|
|
1094
|
+
* @param {object} params - { id?, group?, text, kind?, participants?, provenance?, links?, refs? }
|
|
1095
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1096
|
+
*/
|
|
1097
|
+
export function knowledgeAppend(params, o = {}) {
|
|
1098
|
+
return call("knowledge.append", params || {}, o);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Replace a fact via compare-and-swap on its version (knowledge.replace). The
|
|
1103
|
+
* server rejects a stale CAS with CONFLICT. NOT idempotent in the registry; pass
|
|
1104
|
+
* a stable `o.idempotencyKey` for safe retries.
|
|
1105
|
+
* @param {object} params - { id, expectedVersion, text, group?, provenance? }
|
|
1106
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1107
|
+
*/
|
|
1108
|
+
export function knowledgeReplace(params, o = {}) {
|
|
1109
|
+
return call("knowledge.replace", params || {}, o);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Rewrite a fact in place (knowledge.rewrite) — DESIGNATED-OWNER-ONLY. The server
|
|
1114
|
+
* rejects a non-owner with FORBIDDEN_SCOPE / a method-layer owner check. NOT
|
|
1115
|
+
* idempotent; pass a stable `o.idempotencyKey` for retries.
|
|
1116
|
+
* @param {object} params - { id, text, group?, provenance? }
|
|
1117
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1118
|
+
*/
|
|
1119
|
+
export function knowledgeRewrite(params, o = {}) {
|
|
1120
|
+
return call("knowledge.rewrite", params || {}, o);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Invalidate a fact (knowledge.invalidate) — INVALIDATE, DON'T DELETE. Marks the
|
|
1125
|
+
* fact invalid (provenance + tombstone) and excludes it from future search; the
|
|
1126
|
+
* row PERSISTS for audit and provenance/blast-radius queries. NOT idempotent;
|
|
1127
|
+
* pass a stable `o.idempotencyKey` for retries.
|
|
1128
|
+
* @param {object} params - { id, reason?, supersededBy? }
|
|
1129
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1130
|
+
*/
|
|
1131
|
+
export function knowledgeInvalidate(params, o = {}) {
|
|
1132
|
+
return call("knowledge.invalidate", params || {}, o);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Search the ACL'd shared facts (knowledge.search). SIDE-EFFECTING by contract:
|
|
1137
|
+
* every retrieval appends a 'knowledge' org_events row capturing
|
|
1138
|
+
* {agent, query, returned_ids, groups_considered} — the read-audit trail. The
|
|
1139
|
+
* SERVER applies the RLS floor and returns ONLY the facts this agent's verified
|
|
1140
|
+
* groups may see (barrier:<cell> deny-override); the client does not filter. NOT
|
|
1141
|
+
* idempotent in the registry (each search is a distinct audited retrieval) — only
|
|
1142
|
+
* pass an `o.idempotencyKey` if you explicitly want to coalesce identical retries.
|
|
1143
|
+
* @param {object} params - { query, group?, limit?, includeInvalid?, kind? }
|
|
1144
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1145
|
+
* @returns {Promise<object>} res frame whose result carries { facts:[…], returned_ids:[…], groups_considered:[…] }
|
|
1146
|
+
*/
|
|
1147
|
+
export function knowledgeSearch(params, o = {}) {
|
|
1148
|
+
return call("knowledge.search", params || {}, o);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// --- contacts registry (ACL-filtered) --------------------------------------
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Upsert an org contact (contacts.upsert). Idempotent — pass a stable
|
|
1155
|
+
* `o.idempotencyKey` (or `params.id`) so a re-pushed contact collapses to one row.
|
|
1156
|
+
* @param {object} params - { id?, name?, emails?, phones?, handles?, org?, group?, fields? }
|
|
1157
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1158
|
+
*/
|
|
1159
|
+
export function contactsUpsert(params, o = {}) {
|
|
1160
|
+
return call("contacts.upsert", params || {}, o);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* List org contacts visible to the actor (GET /v1/contacts.list). The server
|
|
1165
|
+
* ACL-filters; returns the contacts array, or [] on any failure (fail-open).
|
|
1166
|
+
* @param {object} o - { base, token, q?, group?, limit?, fetchImpl? }
|
|
1167
|
+
* @returns {Promise<object[]>}
|
|
1168
|
+
*/
|
|
1169
|
+
export async function contactsList(o = {}) {
|
|
1170
|
+
const params = [];
|
|
1171
|
+
if (o && o.q) params.push(`q=${encodeURIComponent(o.q)}`);
|
|
1172
|
+
if (o && o.group) params.push(`group=${encodeURIComponent(o.group)}`);
|
|
1173
|
+
if (o && Number.isFinite(o.limit)) params.push(`limit=${encodeURIComponent(o.limit)}`);
|
|
1174
|
+
const qs = params.length ? `?${params.join("&")}` : "";
|
|
1175
|
+
const r = await read(`contacts.list${qs}`, o);
|
|
1176
|
+
if (!r.ok || !r.payload) return [];
|
|
1177
|
+
if (Array.isArray(r.payload)) return r.payload;
|
|
1178
|
+
if (Array.isArray(r.payload.contacts)) return r.payload.contacts;
|
|
1179
|
+
return [];
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// --- meetings registry (ACL-filtered) --------------------------------------
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Record a meeting (meetings.record). Idempotent — pass a stable
|
|
1186
|
+
* `o.idempotencyKey` (or `params.id`) so a re-pushed meeting collapses to one row.
|
|
1187
|
+
* @param {object} params - { id?, title?, at?, participants?, notes?, group?, refs?, fields? }
|
|
1188
|
+
* @param {object} o - { base, token, idempotencyKey?, fetchImpl? }
|
|
1189
|
+
*/
|
|
1190
|
+
export function meetingsRecord(params, o = {}) {
|
|
1191
|
+
return call("meetings.record", params || {}, o);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* List org meetings visible to the actor (GET /v1/meetings.list). The server
|
|
1196
|
+
* ACL-filters (participant-derived ACL); returns the meetings array, or [] on any
|
|
1197
|
+
* failure (fail-open).
|
|
1198
|
+
* @param {object} o - { base, token, q?, group?, since?, limit?, fetchImpl? }
|
|
1199
|
+
* @returns {Promise<object[]>}
|
|
1200
|
+
*/
|
|
1201
|
+
export async function meetingsList(o = {}) {
|
|
1202
|
+
const params = [];
|
|
1203
|
+
if (o && o.q) params.push(`q=${encodeURIComponent(o.q)}`);
|
|
1204
|
+
if (o && o.group) params.push(`group=${encodeURIComponent(o.group)}`);
|
|
1205
|
+
if (o && o.since) params.push(`since=${encodeURIComponent(o.since)}`);
|
|
1206
|
+
if (o && Number.isFinite(o.limit)) params.push(`limit=${encodeURIComponent(o.limit)}`);
|
|
1207
|
+
const qs = params.length ? `?${params.join("&")}` : "";
|
|
1208
|
+
const r = await read(`meetings.list${qs}`, o);
|
|
1209
|
+
if (!r.ok || !r.payload) return [];
|
|
1210
|
+
if (Array.isArray(r.payload)) return r.payload;
|
|
1211
|
+
if (Array.isArray(r.payload.meetings)) return r.payload.meetings;
|
|
1212
|
+
return [];
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// ---------------------------------------------------------------------------
|
|
1216
|
+
// Messaging + calling (SP3/SP10) — participate in the org's human messaging app
|
|
1217
|
+
// ---------------------------------------------------------------------------
|
|
1218
|
+
//
|
|
1219
|
+
// The agent half of the messaging.*/calling.* surface (protocol.mjs). Same
|
|
1220
|
+
// contract as every other helper: a thin wrapper over call() / read() over the
|
|
1221
|
+
// /v1 binding, Bearer + x-org-protocol on every request, x-idempotency-key on the
|
|
1222
|
+
// idempotent side-effecting methods (messaging.send is idempotent on the
|
|
1223
|
+
// client-supplied message id; calling.* are not, so pass a stable key only when
|
|
1224
|
+
// you want to coalesce retries). All fail open — a disabled/unreachable server
|
|
1225
|
+
// yields an error frame, never a throw.
|
|
1226
|
+
//
|
|
1227
|
+
// These are the RAW RPC wrappers. The POLICY + TELEMETRY layer (the send-gate
|
|
1228
|
+
// chokepoint, the afterSend hook, and the source="messaging" cost attribution)
|
|
1229
|
+
// lives in lib/org/messaging.mjs, which composes these. Outbound CONTENT sends
|
|
1230
|
+
// should go through lib/org/messaging.sendMessage, NOT messagingSend directly,
|
|
1231
|
+
// so the send-gate is never bypassed.
|
|
1232
|
+
|
|
1233
|
+
/** Post a message to an org channel (messaging.send). Idempotent on the message id. */
|
|
1234
|
+
export function messagingSend(params, o = {}) {
|
|
1235
|
+
return call("messaging.send", params || {}, o);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/** React to an org message (messaging.react). */
|
|
1239
|
+
export function messagingReact(params, o = {}) {
|
|
1240
|
+
return call("messaging.react", params || {}, o);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/** Edit an org message the agent sent (messaging.edit). */
|
|
1244
|
+
export function messagingEdit(params, o = {}) {
|
|
1245
|
+
return call("messaging.edit", params || {}, o);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/** Read channel history (messaging.history). Read scope, non-mutating. */
|
|
1249
|
+
export function messagingHistory(params, o = {}) {
|
|
1250
|
+
return call("messaging.history", params || {}, o);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/** List the org channels visible to this agent (messaging.channels). Read scope. */
|
|
1254
|
+
export function messagingChannels(params, o = {}) {
|
|
1255
|
+
return call("messaging.channels", params || {}, o);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/** Start a call (calling.start). */
|
|
1259
|
+
export function callingStart(params, o = {}) {
|
|
1260
|
+
return call("calling.start", params || {}, o);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/** Join a call (calling.join). */
|
|
1264
|
+
export function callingJoin(params, o = {}) {
|
|
1265
|
+
return call("calling.join", params || {}, o);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/** Invite participants to a call (calling.invite). */
|
|
1269
|
+
export function callingInvite(params, o = {}) {
|
|
1270
|
+
return call("calling.invite", params || {}, o);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/** End a call (calling.end). */
|
|
1274
|
+
export function callingEnd(params, o = {}) {
|
|
1275
|
+
return call("calling.end", params || {}, o);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* Append one finalized transcript line to a live call (calling.addTranscriptLine).
|
|
1280
|
+
* Agent-operability write: the server persists a CallTranscriptLine projection row
|
|
1281
|
+
* (speaker = the acting member) and appends ONE redacted `calling` event — the
|
|
1282
|
+
* chain records line metadata (callId, memberId, ts) but NEVER the text (spec §3).
|
|
1283
|
+
* The actor must be a participant in a still-live call. Side-effecting.
|
|
1284
|
+
* @param {{callId:string, text:string}} params
|
|
1285
|
+
* @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
|
|
1286
|
+
*/
|
|
1287
|
+
export function callingAddTranscriptLine(params, o = {}) {
|
|
1288
|
+
return call("calling.addTranscriptLine", params || {}, o);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Post one message to a live call's in-call chat panel (calling.chat). Same
|
|
1293
|
+
* contract as addTranscriptLine: one CallChatMessage projection row + one redacted
|
|
1294
|
+
* `calling` event (metadata only, never the body). Participant-gated. Side-effecting.
|
|
1295
|
+
* @param {{callId:string, text:string}} params
|
|
1296
|
+
* @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
|
|
1297
|
+
*/
|
|
1298
|
+
export function callingChat(params, o = {}) {
|
|
1299
|
+
return call("calling.chat", params || {}, o);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// ---------------------------------------------------------------------------
|
|
1303
|
+
// Calling working sessions + vision fusion (2026-08)
|
|
1304
|
+
// ---------------------------------------------------------------------------
|
|
1305
|
+
//
|
|
1306
|
+
// The app-share surface: an AI participant "presents" a Cohort surface
|
|
1307
|
+
// (doc/sheet/channel/board/charter) on the call stage, rendered locally by
|
|
1308
|
+
// every client and driven by step frames over the room data lane. The three
|
|
1309
|
+
// writes persist a CallShareSession projection and append REDACTED share.*
|
|
1310
|
+
// events (ids/counts only — never step text or doc content); the three reads
|
|
1311
|
+
// hydrate late joiners, the narrated visual stream, and recent non-speech call
|
|
1312
|
+
// events. Same thin fail-open call() wrappers as the rest of calling.*.
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Start presenting a Cohort surface on a live call (calling.appShareStart).
|
|
1316
|
+
* One active session per call: your own previous session is superseded; a
|
|
1317
|
+
* CONFLICT frame comes back while another member is presenting. Appends ONE
|
|
1318
|
+
* redacted `share.started` event and publishes the state frame to the room.
|
|
1319
|
+
* The actor must be a participant in a still-live call. Side-effecting.
|
|
1320
|
+
* @param {{callId:string, surface:{kind:string, ref:string, title?:string}}} params
|
|
1321
|
+
* @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
|
|
1322
|
+
*/
|
|
1323
|
+
export function callingAppShareStart(params, o = {}) {
|
|
1324
|
+
return call("calling.appShareStart", params || {}, o);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Drive the active share with a step batch (calling.appShareAct): cursor /
|
|
1329
|
+
* scroll / highlight / type / refresh, ≤20 steps per call. Steps animate the
|
|
1330
|
+
* tile on every client; the chain records ONE redacted `share.acted` event
|
|
1331
|
+
* ({sessionId, stepCount} — never the step text). Session must be active and
|
|
1332
|
+
* owned by the acting member. Side-effecting.
|
|
1333
|
+
* @param {{callId:string, sessionId:string, steps:Array<object>}} params
|
|
1334
|
+
* @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
|
|
1335
|
+
*/
|
|
1336
|
+
export function callingAppShareAct(params, o = {}) {
|
|
1337
|
+
return call("calling.appShareAct", params || {}, o);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* End the active share session (calling.appShareEnd). Sharer or call host.
|
|
1342
|
+
* Appends one redacted `share.ended` event and clears the stage tile.
|
|
1343
|
+
* Side-effecting.
|
|
1344
|
+
* @param {{callId:string, sessionId:string, reason?:('ended'|'superseded')}} params
|
|
1345
|
+
* @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
|
|
1346
|
+
*/
|
|
1347
|
+
export function callingAppShareEnd(params, o = {}) {
|
|
1348
|
+
return call("calling.appShareEnd", params || {}, o);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* The call's active share session, or null (calling.getAppShare) — what a late
|
|
1353
|
+
* joiner renders: {sessionId, by, surface, startedAt, state}. Read-style.
|
|
1354
|
+
* @param {{callId:string}} params
|
|
1355
|
+
* @param {object} o - { base, token, orgId?, fetchImpl? }
|
|
1356
|
+
*/
|
|
1357
|
+
export function callingGetAppShare(params, o = {}) {
|
|
1358
|
+
return call("calling.getAppShare", params || {}, o);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* The narrated visual stream for a call (calling.getVisualContext): the last N
|
|
1363
|
+
* CallVisualEvent rows ({kind, sourceMemberId, summary, at}, chronological,
|
|
1364
|
+
* limit ≤50 default 20). Narration TEXT only — frames are never persisted
|
|
1365
|
+
* server-side. Read-style.
|
|
1366
|
+
* @param {{callId:string, limit?:number}} params
|
|
1367
|
+
* @param {object} o - { base, token, orgId?, fetchImpl? }
|
|
1368
|
+
*/
|
|
1369
|
+
export function callingGetVisualContext(params, o = {}) {
|
|
1370
|
+
return call("calling.getVisualContext", params || {}, o);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* Recent non-speech call events (calling.getCallEvents): reactions, raised /
|
|
1375
|
+
* lowered hands, joins and share.* lifecycle, mapped {kind, actorId, at,
|
|
1376
|
+
* payload} (payloads already redacted), limit ≤100 default 30. Read-style.
|
|
1377
|
+
* @param {{callId:string, limit?:number}} params
|
|
1378
|
+
* @param {object} o - { base, token, orgId?, fetchImpl? }
|
|
1379
|
+
*/
|
|
1380
|
+
export function callingGetCallEvents(params, o = {}) {
|
|
1381
|
+
return call("calling.getCallEvents", params || {}, o);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// ---------------------------------------------------------------------------
|
|
1385
|
+
// Pairing admin approval (authed)
|
|
1386
|
+
// ---------------------------------------------------------------------------
|
|
1387
|
+
//
|
|
1388
|
+
// pairRequest() (above) is the pre-auth half of the handshake. pairApprove is
|
|
1389
|
+
// the ADMIN half: an already-paired admin agent resolves a pending request by
|
|
1390
|
+
// code. Reserved-admin namespace ⇒ the dispatcher enforces `admin` scope. Thin
|
|
1391
|
+
// authed call() wrapper (Bearer + x-org-protocol; the request is side-effecting).
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* Approve a pending pairing request by code (pairing.approve). Admin scope.
|
|
1395
|
+
* Flips the PairingRequest pending→approved server-side (CONFLICT if already
|
|
1396
|
+
* resolved) and appends one `pairing` event. Side-effecting.
|
|
1397
|
+
* @param {{code:string}} params
|
|
1398
|
+
* @param {object} o - { base, token, orgId?, idempotencyKey?, fetchImpl? }
|
|
1399
|
+
*/
|
|
1400
|
+
export function pairApprove(params, o = {}) {
|
|
1401
|
+
return call("pairing.approve", params || {}, o);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// ---------------------------------------------------------------------------
|
|
1405
|
+
// Branding (Brand Studio v2 write surface) — agent mirror of the human
|
|
1406
|
+
// branding-generation / brand-templates / brand-export server actions.
|
|
1407
|
+
// ---------------------------------------------------------------------------
|
|
1408
|
+
//
|
|
1409
|
+
// Thin call() wrappers over the branding.* RPCs (protocol.mjs). Writes require
|
|
1410
|
+
// ADMIN (the server mirrors requireWorkspaceRole("ADMIN")); the two reads
|
|
1411
|
+
// (listVersions/exportTemplate) use org.read. The branding surface owns its OWN
|
|
1412
|
+
// versioned mutation + audit (the BrandVersion spine + self-appended events), so
|
|
1413
|
+
// these methods are sideEffecting:false at the dispatcher layer — no
|
|
1414
|
+
// x-idempotency-key is derived. Fail-open like every other wrapper.
|
|
1415
|
+
|
|
1416
|
+
/** Regenerate the workspace brand system / registry copy (branding.regenerate). Admin. */
|
|
1417
|
+
export function brandingRegenerate(params, o = {}) {
|
|
1418
|
+
return call("branding.regenerate", params || {}, o);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/** Kick off the bespoke-template pipeline for the workspace (branding.regenerateTemplates). Admin. */
|
|
1422
|
+
export function brandingRegenerateTemplates(params, o = {}) {
|
|
1423
|
+
return call("branding.regenerateTemplates", params || {}, o);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
/** Regenerate ONE bespoke template by slug (branding.regenerateTemplate). Admin. @param {{slug:string}} params */
|
|
1427
|
+
export function brandingRegenerateTemplate(params, o = {}) {
|
|
1428
|
+
return call("branding.regenerateTemplate", params || {}, o);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/** Regenerate all canonical brand image slots (branding.regenerateImages). Admin. */
|
|
1432
|
+
export function brandingRegenerateImages(params, o = {}) {
|
|
1433
|
+
return call("branding.regenerateImages", params || {}, o);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/** Set a brand image slot from a data URL (branding.uploadImage). Admin. @param {{slot:string, dataUrl:string}} params */
|
|
1437
|
+
export function brandingUploadImage(params, o = {}) {
|
|
1438
|
+
return call("branding.uploadImage", params || {}, o);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/** AI-amend one registry-copy surface (branding.amendArtifact). Admin. @param {{key:string, instruction:string, elementRef?:string}} params */
|
|
1442
|
+
export function brandingAmendArtifact(params, o = {}) {
|
|
1443
|
+
return call("branding.amendArtifact", params || {}, o);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/** AI-amend a CLONE/GENERATED template's copy (branding.amendTemplate). Admin. @param {{slug:string, instruction:string, elementRef?:string}} params */
|
|
1447
|
+
export function brandingAmendTemplate(params, o = {}) {
|
|
1448
|
+
return call("branding.amendTemplate", params || {}, o);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/** Clone a registry/generated template into a new CLONE (branding.cloneTemplate). Admin. @param {{baseRef:string}} params */
|
|
1452
|
+
export function brandingCloneTemplate(params, o = {}) {
|
|
1453
|
+
return call("branding.cloneTemplate", params || {}, o);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/** Soft-delete a CLONE/GENERATED template by slug (branding.deleteTemplate). Admin. @param {{slug:string}} params */
|
|
1457
|
+
export function brandingDeleteTemplate(params, o = {}) {
|
|
1458
|
+
return call("branding.deleteTemplate", params || {}, o);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/** Restore a versioned brand target to a specific seq (branding.restoreVersion). Admin. @param {{targetKind:"ARTIFACT"|"TEMPLATE", ref:string, seq:number}} params */
|
|
1462
|
+
export function brandingRestoreVersion(params, o = {}) {
|
|
1463
|
+
return call("branding.restoreVersion", params || {}, o);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
/** Move a versioned brand target's head to the previous version (branding.undo). Admin. @param {{targetKind:"ARTIFACT"|"TEMPLATE", ref:string}} params */
|
|
1467
|
+
export function brandingUndo(params, o = {}) {
|
|
1468
|
+
return call("branding.undo", params || {}, o);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/** Move a versioned brand target's head to the next version (branding.redo). Admin. @param {{targetKind:"ARTIFACT"|"TEMPLATE", ref:string}} params */
|
|
1472
|
+
export function brandingRedo(params, o = {}) {
|
|
1473
|
+
return call("branding.redo", params || {}, o);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/** Read the version history of a versioned brand target (branding.listVersions). org.read. @param {{targetKind:"ARTIFACT"|"TEMPLATE", ref:string}} params */
|
|
1477
|
+
export function brandingListVersions(params, o = {}) {
|
|
1478
|
+
return call("branding.listVersions", params || {}, o);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/** Deterministic key-free server render of a READY template to a PNG data URL (branding.exportTemplate). org.read. @param {{templateId:string, sizeId?:"mobile"|"tablet"|"desktop", theme?:"light"|"dark"}} params */
|
|
1482
|
+
export function brandingExportTemplate(params, o = {}) {
|
|
1483
|
+
return call("branding.exportTemplate", params || {}, o);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// --- Design read/propose surface (2026-07) ---------------------------------
|
|
1487
|
+
//
|
|
1488
|
+
// The ten methods below are the half of the family an ORDINARY agent seat can
|
|
1489
|
+
// reach: `design.read` for the foundation/voice/template reads and the three
|
|
1490
|
+
// costed producers (renderTemplate / rewriteInVoice / exportKit), `design.write`
|
|
1491
|
+
// for generateImage + proposeChange. Foundation MUTATIONS stay human-gated
|
|
1492
|
+
// (DECISIONS D-7): proposeChange stages a reviewable diff and writes nothing,
|
|
1493
|
+
// while saveFoundation / restoreFoundation are `admin` and only an admin
|
|
1494
|
+
// credential reaches them at all.
|
|
1495
|
+
//
|
|
1496
|
+
// The costed methods (renderTemplate, exportKit, generateImage, rewriteInVoice)
|
|
1497
|
+
// return their spend — surface it. `generateImage` with `budgetCents: 0` is the
|
|
1498
|
+
// QUOTE call: it generates nothing, spends nothing, and still returns every
|
|
1499
|
+
// slot's price and staleness.
|
|
1500
|
+
|
|
1501
|
+
/** The workspace brand foundation + version-history head (branding.getFoundation). design.read. @param {{versionId?:string, historyLimit?:number}} params */
|
|
1502
|
+
export function brandingGetFoundation(params, o = {}) {
|
|
1503
|
+
return call("branding.getFoundation", params || {}, o);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
/** Voice statement, traits, SAY/AVOID lexicon, in-practice examples (branding.getVoice). design.read. */
|
|
1507
|
+
export function brandingGetVoice(params, o = {}) {
|
|
1508
|
+
return call("branding.getVoice", params || {}, o);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/** The template gallery with per-template status chips (branding.listTemplates). design.read. @param {{visibleOnly?:boolean, category?:"digital"|"print"|"email"|"social"|"og"}} params */
|
|
1512
|
+
export function brandingListTemplates(params, o = {}) {
|
|
1513
|
+
return call("branding.listTemplates", params || {}, o);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/** Render a template to HTML (any) or PNG (GENERATED only) (branding.renderTemplate). design.read, costed. @param {{templateId:string, theme?:string, size?:string, format?:"html"|"png", content?:object, budgetCents?:number}} params */
|
|
1517
|
+
export function brandingRenderTemplate(params, o = {}) {
|
|
1518
|
+
return call("branding.renderTemplate", params || {}, o);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/** Rewrite a sentence in the workspace brand voice (branding.rewriteInVoice). design.read, costed. Produces copy; sends nothing. @param {{text:string, subject?:string}} params */
|
|
1522
|
+
export function brandingRewriteInVoice(params, o = {}) {
|
|
1523
|
+
return call("branding.rewriteInVoice", params || {}, o);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/** Export the brand kit — tokens, voice, and (by default) the mark bytes (branding.exportKit). design.read, costed. @param {{unit?:"px"|"rem"|"em", includeMarks?:boolean, versionId?:string, budgetCents?:number}} params */
|
|
1527
|
+
export function brandingExportKit(params, o = {}) {
|
|
1528
|
+
return call("branding.exportKit", params || {}, o);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/** Regenerate canonical brand image slots; omit `slots` for every STALE slot, `budgetCents:0` to QUOTE only (branding.generateImage). design.write, costed. @param {{slots?:string[], force?:boolean, budgetCents?:number}} params */
|
|
1532
|
+
export function brandingGenerateImage(params, o = {}) {
|
|
1533
|
+
return call("branding.generateImage", params || {}, o);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/** Stage a REVIEWABLE foundation diff — writes nothing to the foundation (branding.proposeChange). design.write. Blocks are replaced wholesale: send each COMPLETE block. @param {{patch:object, note?:string, rationale?:string, expectedVersionId?:string|null}} params */
|
|
1537
|
+
export function brandingProposeChange(params, o = {}) {
|
|
1538
|
+
return call("branding.proposeChange", params || {}, o);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
/** Save the brand foundation (branding.saveFoundation). ADMIN — an ordinary agent seat proposes instead. @param {{foundation:object, expectedVersionId?:string|null, note?:string}} params */
|
|
1542
|
+
export function brandingSaveFoundation(params, o = {}) {
|
|
1543
|
+
return call("branding.saveFoundation", params || {}, o);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/** Restore a historical foundation version as a NEW version (branding.restoreFoundation). ADMIN. @param {{versionId:string, note?:string, expectedVersionId?:string|null}} params */
|
|
1547
|
+
export function brandingRestoreFoundation(params, o = {}) {
|
|
1548
|
+
return call("branding.restoreFoundation", params || {}, o);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// ---------------------------------------------------------------------------
|
|
1552
|
+
// Back-compat surface (drop-in for lib/org/cohort-client.mjs)
|
|
1553
|
+
// ---------------------------------------------------------------------------
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* Project a maestro directory entry (assembleSelfEntry shape — id, name,
|
|
1557
|
+
* fullName, archetype{object}, towers, focus, presence, …) onto the params that
|
|
1558
|
+
* hq's `registry.register` accepts. The server schema is `.strict()` and only
|
|
1559
|
+
* models { displayName, archetype:string, humanSponsor, card } — so we MUST trim
|
|
1560
|
+
* here or a rich entry 400s (BAD_REQUEST). Everything the server doesn't model is
|
|
1561
|
+
* preserved REDACTION-SAFE under `card` (the handler records only a `hasCard`
|
|
1562
|
+
* boolean on the chain, never the blob). Pure; never throws.
|
|
1563
|
+
* @param {object} entry
|
|
1564
|
+
* @returns {object} registerSchema-shaped params
|
|
1565
|
+
*/
|
|
1566
|
+
function entryToRegisterParams(entry) {
|
|
1567
|
+
const e = entry || {};
|
|
1568
|
+
const displayName = e.fullName || e.name || e.displayName || String(e.id || "");
|
|
1569
|
+
// archetype is an object in the maestro entry ({function,altitude,label}); the
|
|
1570
|
+
// server wants a single string. Prefer the human label, then the function.
|
|
1571
|
+
const arch = e.archetype;
|
|
1572
|
+
const archetype =
|
|
1573
|
+
typeof arch === "string"
|
|
1574
|
+
? arch
|
|
1575
|
+
: (arch && (arch.label || arch.function || arch.altitude)) || "";
|
|
1576
|
+
const humanSponsor =
|
|
1577
|
+
(e.principal && (e.principal.fullName || e.principal.title)) || e.humanSponsor || "";
|
|
1578
|
+
const params = { card: e };
|
|
1579
|
+
if (displayName) params.displayName = String(displayName).slice(0, 200);
|
|
1580
|
+
if (archetype) params.archetype = String(archetype).slice(0, 120);
|
|
1581
|
+
if (humanSponsor) params.humanSponsor = String(humanSponsor).slice(0, 200);
|
|
1582
|
+
return params;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/**
|
|
1586
|
+
* Publish (upsert) this agent's directory entry — now via the /v1 binding
|
|
1587
|
+
* (registry.register), preserving the legacy return shape `{ok,status}` /
|
|
1588
|
+
* `{ok:false,skipped:true}` so existing callers (org-sync) don't break. The rich
|
|
1589
|
+
* maestro entry is projected onto the server's strict registerSchema (extra
|
|
1590
|
+
* fields ride under `card`, chain-redacted to a `hasCard` flag). Carries the
|
|
1591
|
+
* derived idempotency key + optional x-org-id pin. Fail-open.
|
|
1592
|
+
* @param {object} o - { cfg, entry, fetchImpl? }
|
|
1593
|
+
* @returns {Promise<{ok:boolean, status?:number, skipped?:boolean, reason?:string}>}
|
|
1594
|
+
*/
|
|
1595
|
+
export async function publishEntry(o = {}) {
|
|
1596
|
+
const { cfg, entry } = o;
|
|
1597
|
+
if (!isEnabled(cfg)) return { ok: false, skipped: true };
|
|
1598
|
+
if (!entry || !entry.id) return { ok: false, skipped: false, reason: "no-entry" };
|
|
1599
|
+
const c = configFromAgent(cfg);
|
|
1600
|
+
const frame = await register(entryToRegisterParams(entry), {
|
|
1601
|
+
base: c.base,
|
|
1602
|
+
token: c.token,
|
|
1603
|
+
orgId: c.orgId,
|
|
1604
|
+
idempotencyKey: `registry.register:${entry.id}`,
|
|
1605
|
+
fetchImpl: o.fetchImpl,
|
|
1606
|
+
});
|
|
1607
|
+
return { ok: !!(frame && frame.ok), status: frame && frame.ok ? 200 : 0 };
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/**
|
|
1611
|
+
* Fetch the live org directory — now via GET /v1/directory, preserving the
|
|
1612
|
+
* legacy contract (returns [] when disabled or on any error). Parses the array,
|
|
1613
|
+
* the hq `{members:[…]}` shape (each member carries its latest presence.beat
|
|
1614
|
+
* status snapshot per-member), and the legacy `{agents:[…]}` shape.
|
|
1615
|
+
* @param {object} o - { cfg, fetchImpl? }
|
|
1616
|
+
* @returns {Promise<object[]>}
|
|
1617
|
+
*/
|
|
1618
|
+
export async function fetchDirectory(o = {}) {
|
|
1619
|
+
const { cfg } = o;
|
|
1620
|
+
if (!isEnabled(cfg)) return [];
|
|
1621
|
+
const c = configFromAgent(cfg);
|
|
1622
|
+
const r = await read("directory", { base: c.base, token: c.token, orgId: c.orgId, fetchImpl: o.fetchImpl });
|
|
1623
|
+
if (!r.ok || !r.payload) return [];
|
|
1624
|
+
if (Array.isArray(r.payload)) return r.payload;
|
|
1625
|
+
if (Array.isArray(r.payload.members)) return r.payload.members;
|
|
1626
|
+
if (Array.isArray(r.payload.agents)) return r.payload.agents;
|
|
1627
|
+
return [];
|
|
1628
|
+
}
|