@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,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/setup/enroll-from-cohort.mjs — pull-and-map an agent's record from the
|
|
3
|
+
* Cohort org-data API into the config/agent.json field subset.
|
|
4
|
+
*
|
|
5
|
+
* The org single-SoT payoff for ONBOARDING: an admin describes the agent ONCE in
|
|
6
|
+
* Cohort (its MemberProfileDTO — slug/title/team/role/agentEmail/profileSections/
|
|
7
|
+
* supervisor/…); when `COHORT_API_KEY` + `COHORT_ORG_ID` are present (optionally
|
|
8
|
+
* `COHORT_AGENT_ID`), `maestro setup` PULLS that record and auto-populates
|
|
9
|
+
* config/agent.json instead of prompting the operator field-by-field. The wizard
|
|
10
|
+
* then CONFIRMS the pulled values; a re-pull (`maestro sync`) keeps the agent in
|
|
11
|
+
* step with the org over time.
|
|
12
|
+
*
|
|
13
|
+
* Two pure, side-effect-free helpers live here so they are trivially testable
|
|
14
|
+
* (the network call lives in lib/org/client.fetchSelfProfile, injected for tests):
|
|
15
|
+
*
|
|
16
|
+
* mapProfileToAgentConfig(profile) → the config/agent.json subset the
|
|
17
|
+
* profile AUTHORITATIVELY provides
|
|
18
|
+
* mergeProfilePatch(current, patch, {force}) → the patch with locally-customised
|
|
19
|
+
* leaves dropped (unless force)
|
|
20
|
+
*
|
|
21
|
+
* Mapping policy — map ONLY fields the profile authoritatively provides; never
|
|
22
|
+
* invent a value. Function/altitude are derived from the member's role+team via a
|
|
23
|
+
* bounded keyword map and ALWAYS fall back to a safe default the wizard can change
|
|
24
|
+
* (never fail the pull on an unmappable role). Local customisation is sacred: a
|
|
25
|
+
* value the operator already set in config/agent.json is NOT clobbered by a pull
|
|
26
|
+
* unless `--force` is passed (the `maestro sync --force` path).
|
|
27
|
+
*
|
|
28
|
+
* Fail-open by contract: every helper tolerates a null/partial profile and
|
|
29
|
+
* returns a (possibly empty) plain object — never throws — so a degraded Cohort
|
|
30
|
+
* record can never block setup; the wizard simply falls back to manual prompts.
|
|
31
|
+
*
|
|
32
|
+
* @module lib/setup/enroll-from-cohort
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
"use strict";
|
|
36
|
+
|
|
37
|
+
import { readAgentJson } from "./sot.mjs";
|
|
38
|
+
import { isPlaceholder } from "./completeness.mjs";
|
|
39
|
+
|
|
40
|
+
// Archetype axes the identity section also offers. Keep these in sync with
|
|
41
|
+
// lib/setup/sections/identity.mjs FUNCTIONS/ALTITUDES — a derived value MUST be
|
|
42
|
+
// one the wizard's select() can render and the archetype resolver accepts.
|
|
43
|
+
const FUNCTIONS = Object.freeze([
|
|
44
|
+
"executive-operator",
|
|
45
|
+
"technical-leader",
|
|
46
|
+
"commercial-leader",
|
|
47
|
+
"people-leader",
|
|
48
|
+
"finance-leader",
|
|
49
|
+
"compliance-leader",
|
|
50
|
+
]);
|
|
51
|
+
const ALTITUDES = Object.freeze(["c-suite", "svp", "vp", "senior-manager"]);
|
|
52
|
+
|
|
53
|
+
const DEFAULT_FUNCTION = "executive-operator";
|
|
54
|
+
const DEFAULT_ALTITUDE = "c-suite";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Are the Cohort pull env vars present? The PULL path is gated on an API key +
|
|
58
|
+
* an org id; the agent id is optional (its absence selects the whoami endpoint).
|
|
59
|
+
* Pure read of process.env (injectable for tests).
|
|
60
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
61
|
+
* @returns {{ enabled:boolean, apiKey:string, orgId:string, agentId:string, base:string }}
|
|
62
|
+
*/
|
|
63
|
+
export function cohortPullEnv(env = process.env) {
|
|
64
|
+
const apiKey = str(env.COHORT_API_KEY) || str(env.COHORT_API_TOKEN) || str(env.COHORT_TOKEN);
|
|
65
|
+
const orgId = str(env.COHORT_ORG_ID);
|
|
66
|
+
const agentId = str(env.COHORT_AGENT_ID);
|
|
67
|
+
const base = str(env.COHORT_BASE) || str(env.COHORT_API_URL);
|
|
68
|
+
return { enabled: !!(apiKey && orgId), apiKey, orgId, agentId, base };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function str(v) {
|
|
72
|
+
return v == null ? "" : String(v).trim();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Map a Cohort MemberProfileDTO to the config/agent.json field subset it
|
|
77
|
+
* authoritatively provides. Only sets a field when the profile carries a real
|
|
78
|
+
* value (an empty/placeholder profile yields an empty patch). Never throws.
|
|
79
|
+
*
|
|
80
|
+
* Authoritatively mapped:
|
|
81
|
+
* firstName/lastName/fullName ← firstName/lastName/displayName
|
|
82
|
+
* title ← title
|
|
83
|
+
* email ← agentEmail
|
|
84
|
+
* function/altitude ← derived from role+team (bounded keyword map)
|
|
85
|
+
* archetype ← `${function} ${altitude}`-derived (left to the
|
|
86
|
+
* identity generator; we set function/altitude)
|
|
87
|
+
* principal ← supervisor (fullName/title/email split)
|
|
88
|
+
* operatingPrinciples ← profileSections (operating-principles section)
|
|
89
|
+
* communication.defaultTone ← profileSections (communication/tone section)
|
|
90
|
+
* responsibilities ← profileSections (responsibilities section)
|
|
91
|
+
*
|
|
92
|
+
* @param {object|null} profile MemberProfileDTO
|
|
93
|
+
* @returns {object} the config/agent.json patch (deep-mergeable)
|
|
94
|
+
*/
|
|
95
|
+
export function mapProfileToAgentConfig(profile) {
|
|
96
|
+
const p = profile && typeof profile === "object" ? profile : {};
|
|
97
|
+
const patch = {};
|
|
98
|
+
|
|
99
|
+
const first = str(p.firstName);
|
|
100
|
+
const last = str(p.lastName);
|
|
101
|
+
const display = str(p.displayName);
|
|
102
|
+
if (first) patch.firstName = first;
|
|
103
|
+
if (last) patch.lastName = last;
|
|
104
|
+
const fullName = display || [first, last].filter(Boolean).join(" ").trim();
|
|
105
|
+
if (fullName) patch.fullName = fullName;
|
|
106
|
+
|
|
107
|
+
const title = str(p.title);
|
|
108
|
+
if (title) patch.title = title;
|
|
109
|
+
|
|
110
|
+
const email = str(p.agentEmail) || str(p.email);
|
|
111
|
+
if (email) patch.email = email;
|
|
112
|
+
|
|
113
|
+
// function/altitude — derive from role + team. Only emitted when the profile
|
|
114
|
+
// carries a role/team/title signal (a genuinely empty profile yields no axis,
|
|
115
|
+
// leaving the wizard to prompt). When signal IS present, derivation always
|
|
116
|
+
// resolves to a VALID axis (safe default when unmappable) so the wizard renders
|
|
117
|
+
// + confirms it rather than failing the pull.
|
|
118
|
+
const hasAxisSignal = !!(str(p.role) || str(p.team) || str(p.title));
|
|
119
|
+
if (hasAxisSignal) {
|
|
120
|
+
patch.function = deriveFunction(p);
|
|
121
|
+
patch.altitude = deriveAltitude(p);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// principal — the supervisor the agent reports to.
|
|
125
|
+
const principal = mapSupervisor(p.supervisor);
|
|
126
|
+
if (principal) patch.principal = principal;
|
|
127
|
+
|
|
128
|
+
// profileSections — authored prose the org maintains. Mapped conservatively:
|
|
129
|
+
// only when a recognisable section exists, and only into fields the profile is
|
|
130
|
+
// the SoT for.
|
|
131
|
+
const sections = Array.isArray(p.profileSections) ? p.profileSections : [];
|
|
132
|
+
const principles = sectionLines(sections, ["operating-principles", "operating principles", "principles"]);
|
|
133
|
+
if (principles.length) patch.operatingPrinciples = principles;
|
|
134
|
+
const responsibilities = sectionLines(sections, ["responsibilities", "responsibility", "mandate"]);
|
|
135
|
+
if (responsibilities.length) patch.responsibilities = responsibilities;
|
|
136
|
+
const tone = sectionText(sections, ["communication", "tone", "communication-style"]);
|
|
137
|
+
if (tone) patch.communication = { defaultTone: tone };
|
|
138
|
+
|
|
139
|
+
// charter — the Cohort-authored operating charter (CharterDTO:
|
|
140
|
+
// approvedByLine/gatesText/sections). When present this is the SOURCE OF TRUTH
|
|
141
|
+
// the charter generator renders FROM (scripts/setup/generate-charter.mjs),
|
|
142
|
+
// overriding the archetype skeleton. We persist it verbatim so the generator
|
|
143
|
+
// and the doctor read the same authored charter.
|
|
144
|
+
const charter = mapCharter(p.charter);
|
|
145
|
+
if (charter) patch.charter = charter;
|
|
146
|
+
|
|
147
|
+
// org-authoritative operating fields the enrollment mapper previously DROPPED.
|
|
148
|
+
// Persisted under `orgProfile` so they are available to autonomy generation
|
|
149
|
+
// (autonomyLevel/escalationPath) and to any caller that needs the org's view of
|
|
150
|
+
// this member's reports/governance/skills/operating channel.
|
|
151
|
+
const orgProfile = mapOrgProfile(p);
|
|
152
|
+
if (orgProfile) patch.orgProfile = orgProfile;
|
|
153
|
+
|
|
154
|
+
return patch;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Map a CharterDTO ({ approvedByLine, gatesText, sections:[{kind,order,title,body}] })
|
|
159
|
+
* into the persisted charter subset. Returns null when there is no real charter
|
|
160
|
+
* (no sections AND no approvedBy/gates text) so an empty patch stays empty.
|
|
161
|
+
* @param {object|null} charter
|
|
162
|
+
* @returns {object|null}
|
|
163
|
+
*/
|
|
164
|
+
function mapCharter(charter) {
|
|
165
|
+
if (!charter || typeof charter !== "object") return null;
|
|
166
|
+
const sectionsIn = Array.isArray(charter.sections) ? charter.sections : [];
|
|
167
|
+
const sections = sectionsIn
|
|
168
|
+
.filter((s) => s && typeof s === "object")
|
|
169
|
+
.map((s) => ({
|
|
170
|
+
kind: str(s.kind),
|
|
171
|
+
order: Number.isFinite(s.order) ? s.order : 0,
|
|
172
|
+
title: str(s.title) || null,
|
|
173
|
+
body: str(s.body),
|
|
174
|
+
}))
|
|
175
|
+
.filter((s) => s.body || s.title);
|
|
176
|
+
const approvedByLine = str(charter.approvedByLine);
|
|
177
|
+
const gatesText = str(charter.gatesText);
|
|
178
|
+
if (!sections.length && !approvedByLine && !gatesText) return null;
|
|
179
|
+
const out = { sections };
|
|
180
|
+
if (approvedByLine) out.approvedByLine = approvedByLine;
|
|
181
|
+
if (gatesText) out.gatesText = gatesText;
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Collect the org-authoritative operating fields the wizard should NOT drop:
|
|
187
|
+
* autonomyLevel, escalationPath, skills, operatingChannel, reports, governance.
|
|
188
|
+
* Only includes a field when the profile carries a real value. Returns null when
|
|
189
|
+
* the profile carries none of them (keeps an empty patch empty).
|
|
190
|
+
* @param {object} p MemberProfileDTO
|
|
191
|
+
* @returns {object|null}
|
|
192
|
+
*/
|
|
193
|
+
function mapOrgProfile(p) {
|
|
194
|
+
const out = {};
|
|
195
|
+
const autonomyLevel = str(p.autonomyLevel);
|
|
196
|
+
if (autonomyLevel) out.autonomyLevel = autonomyLevel;
|
|
197
|
+
const escalationPath = str(p.escalationPath);
|
|
198
|
+
if (escalationPath) out.escalationPath = escalationPath;
|
|
199
|
+
const operatingChannel = str(p.operatingChannel);
|
|
200
|
+
if (operatingChannel) out.operatingChannel = operatingChannel;
|
|
201
|
+
const skills = Array.isArray(p.skills) ? p.skills.map(str).filter(Boolean) : [];
|
|
202
|
+
if (skills.length) out.skills = skills;
|
|
203
|
+
const reports = Array.isArray(p.reports) ? p.reports.filter((r) => r && typeof r === "object") : [];
|
|
204
|
+
if (reports.length) out.reports = reports;
|
|
205
|
+
const governance = Array.isArray(p.governance) ? p.governance.filter((g) => g && typeof g === "object") : [];
|
|
206
|
+
if (governance.length) out.governance = governance;
|
|
207
|
+
return Object.keys(out).length ? out : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Build the principal sub-object from a supervisor descriptor (string or DTO). */
|
|
211
|
+
function mapSupervisor(sup) {
|
|
212
|
+
if (!sup) return null;
|
|
213
|
+
if (typeof sup === "string") {
|
|
214
|
+
const fullName = str(sup);
|
|
215
|
+
if (!fullName) return null;
|
|
216
|
+
const [pf, ...pr] = fullName.split(/\s+/);
|
|
217
|
+
return { firstName: pf || "", lastName: pr.join(" "), fullName, title: "", email: "" };
|
|
218
|
+
}
|
|
219
|
+
if (typeof sup === "object") {
|
|
220
|
+
const fullName = str(sup.displayName) || str(sup.fullName) || [str(sup.firstName), str(sup.lastName)].filter(Boolean).join(" ").trim();
|
|
221
|
+
if (!fullName && !str(sup.email) && !str(sup.title)) return null;
|
|
222
|
+
const [pf, ...pr] = fullName.split(/\s+/);
|
|
223
|
+
return {
|
|
224
|
+
firstName: str(sup.firstName) || pf || "",
|
|
225
|
+
lastName: str(sup.lastName) || pr.join(" "),
|
|
226
|
+
fullName,
|
|
227
|
+
title: str(sup.title),
|
|
228
|
+
email: str(sup.email) || str(sup.agentEmail),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Derive the archetype `function` axis from the member's role + team using a
|
|
236
|
+
* bounded keyword map. Returns DEFAULT_FUNCTION when nothing matches — the pull
|
|
237
|
+
* never fails on an unmappable role; the wizard confirms/changes it.
|
|
238
|
+
* @param {object} p
|
|
239
|
+
* @returns {string} one of FUNCTIONS
|
|
240
|
+
*/
|
|
241
|
+
export function deriveFunction(p) {
|
|
242
|
+
const hay = `${str(p.role)} ${str(p.team)} ${str(p.title)}`.toLowerCase();
|
|
243
|
+
const rules = [
|
|
244
|
+
[/\b(eng|engineer|platform|infra|technical|cto|devops|software|sre)\b/, "technical-leader"],
|
|
245
|
+
[/\b(sales|revenue|gtm|growth|commercial|marketing|bd|biz dev|partnerships)\b/, "commercial-leader"],
|
|
246
|
+
[/\b(people|hr|talent|recruit|org design|human resources)\b/, "people-leader"],
|
|
247
|
+
[/\b(finance|fp&a|accounting|treasury|capital|corp dev|cfo)\b/, "finance-leader"],
|
|
248
|
+
[/\b(compliance|legal|risk|regulatory|counsel|audit|dfsa)\b/, "compliance-leader"],
|
|
249
|
+
[/\b(ops|operations|chief of staff|coo|executive|operator)\b/, "executive-operator"],
|
|
250
|
+
];
|
|
251
|
+
for (const [re, fn] of rules) if (re.test(hay)) return fn;
|
|
252
|
+
return DEFAULT_FUNCTION;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Derive the `altitude` axis from role/title seniority cues. Returns
|
|
257
|
+
* DEFAULT_ALTITUDE when nothing matches.
|
|
258
|
+
* @param {object} p
|
|
259
|
+
* @returns {string} one of ALTITUDES
|
|
260
|
+
*/
|
|
261
|
+
export function deriveAltitude(p) {
|
|
262
|
+
const hay = `${str(p.role)} ${str(p.title)}`.toLowerCase();
|
|
263
|
+
// Order matters: most-senior cue wins (check c-suite/SVP before VP/manager).
|
|
264
|
+
if (/\b(c-?suite|chief|ceo|cto|cfo|coo|cmo|cpo|founder)\b/.test(hay)) return "c-suite";
|
|
265
|
+
if (/\bsvp\b|senior vice president/.test(hay)) return "svp";
|
|
266
|
+
if (/\bvp\b|vice president|head of|director/.test(hay)) return "vp";
|
|
267
|
+
if (/\b(senior manager|sr\.? manager|manager|lead)\b/.test(hay)) return "senior-manager";
|
|
268
|
+
return DEFAULT_ALTITUDE;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Find a profile section by case-insensitive id/title match. */
|
|
272
|
+
function findSection(sections, names) {
|
|
273
|
+
const wanted = names.map((n) => n.toLowerCase());
|
|
274
|
+
return (
|
|
275
|
+
sections.find((s) => {
|
|
276
|
+
if (!s || typeof s !== "object") return false;
|
|
277
|
+
const id = str(s.id).toLowerCase();
|
|
278
|
+
const title = str(s.title).toLowerCase();
|
|
279
|
+
const heading = str(s.heading).toLowerCase();
|
|
280
|
+
return wanted.includes(id) || wanted.includes(title) || wanted.includes(heading);
|
|
281
|
+
}) || null
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Pull a section's content as a list of trimmed non-empty lines. */
|
|
286
|
+
function sectionLines(sections, names) {
|
|
287
|
+
const s = findSection(sections, names);
|
|
288
|
+
if (!s) return [];
|
|
289
|
+
if (Array.isArray(s.items)) return s.items.map(str).filter(Boolean);
|
|
290
|
+
if (Array.isArray(s.lines)) return s.lines.map(str).filter(Boolean);
|
|
291
|
+
const body = str(s.content) || str(s.body) || str(s.text);
|
|
292
|
+
if (!body) return [];
|
|
293
|
+
return body
|
|
294
|
+
.split(/\r?\n/)
|
|
295
|
+
.map((l) => l.replace(/^\s*[-*•]\s*/, "").trim())
|
|
296
|
+
.filter(Boolean);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Pull a section's content as a single trimmed string (first non-empty leaf). */
|
|
300
|
+
function sectionText(sections, names) {
|
|
301
|
+
const s = findSection(sections, names);
|
|
302
|
+
if (!s) return "";
|
|
303
|
+
const body = str(s.content) || str(s.body) || str(s.text);
|
|
304
|
+
if (body) return body.split(/\r?\n/).map((l) => l.trim()).filter(Boolean)[0] || "";
|
|
305
|
+
if (Array.isArray(s.items) && s.items.length) return str(s.items[0]);
|
|
306
|
+
return "";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Reduce a pull patch so it never clobbers a locally-customised value. A leaf is
|
|
311
|
+
* dropped from the patch when the CURRENT config already has a real (non-empty,
|
|
312
|
+
* non-placeholder) value for it — UNLESS `force` is set. This is the seam that
|
|
313
|
+
* lets `maestro setup`/`sync` re-pull freely while protecting operator edits.
|
|
314
|
+
*
|
|
315
|
+
* Nested objects (principal, communication) are reduced leaf-by-leaf; an entirely
|
|
316
|
+
* dropped object is omitted. Arrays (responsibilities/operatingPrinciples) are
|
|
317
|
+
* treated as a single leaf (kept only if the current array is empty or force).
|
|
318
|
+
*
|
|
319
|
+
* @param {object|null} current the existing config/agent.json ({} when none)
|
|
320
|
+
* @param {object} patch the patch from mapProfileToAgentConfig
|
|
321
|
+
* @param {{force?:boolean}} [opts]
|
|
322
|
+
* @returns {object} the reduced patch (safe to deep-merge)
|
|
323
|
+
*/
|
|
324
|
+
export function mergeProfilePatch(current, patch, opts = {}) {
|
|
325
|
+
const force = !!opts.force;
|
|
326
|
+
const cur = current && typeof current === "object" ? current : {};
|
|
327
|
+
const out = {};
|
|
328
|
+
for (const [k, v] of Object.entries(patch || {})) {
|
|
329
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
330
|
+
const sub = reduceObject((cur[k] && typeof cur[k] === "object" && cur[k]) || {}, v, force);
|
|
331
|
+
if (sub && Object.keys(sub).length) out[k] = sub;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (force || isUnset(cur[k])) out[k] = v;
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function reduceObject(curObj, patchObj, force) {
|
|
340
|
+
const out = {};
|
|
341
|
+
for (const [k, v] of Object.entries(patchObj)) {
|
|
342
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
343
|
+
const sub = reduceObject((curObj[k] && typeof curObj[k] === "object" && curObj[k]) || {}, v, force);
|
|
344
|
+
if (sub && Object.keys(sub).length) out[k] = sub;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (force || isUnset(curObj[k])) out[k] = v;
|
|
348
|
+
}
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** A leaf is "unset" when missing, empty, an empty array, or a placeholder. */
|
|
353
|
+
function isUnset(v) {
|
|
354
|
+
if (v === undefined || v === null) return true;
|
|
355
|
+
if (typeof v === "string") return v.trim() === "" || isPlaceholder(v);
|
|
356
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Compute the leaf-level diff between two config subsets — the human-readable
|
|
362
|
+
* "what changed" report `maestro sync` prints. Returns an array of
|
|
363
|
+
* `{ path, from, to }`. Pure; only inspects the keys present in `patch`.
|
|
364
|
+
* @param {object} before
|
|
365
|
+
* @param {object} patch the reduced patch about to be merged
|
|
366
|
+
* @returns {Array<{path:string, from:*, to:*}>}
|
|
367
|
+
*/
|
|
368
|
+
export function diffPatch(before, patch) {
|
|
369
|
+
const b = before && typeof before === "object" ? before : {};
|
|
370
|
+
const changes = [];
|
|
371
|
+
walkDiff(b, patch || {}, "", changes);
|
|
372
|
+
return changes;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function walkDiff(before, patch, prefix, changes) {
|
|
376
|
+
for (const [k, v] of Object.entries(patch || {})) {
|
|
377
|
+
const path = prefix ? `${prefix}.${k}` : k;
|
|
378
|
+
const prev = before ? before[k] : undefined;
|
|
379
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
380
|
+
walkDiff(prev && typeof prev === "object" ? prev : {}, v, path, changes);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (!sameLeaf(prev, v)) changes.push({ path, from: prev, to: v });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function sameLeaf(a, b) {
|
|
388
|
+
if (Array.isArray(a) && Array.isArray(b)) return JSON.stringify(a) === JSON.stringify(b);
|
|
389
|
+
return a === b;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* High-level orchestration used by both the org/identity sections and
|
|
394
|
+
* `maestro sync`: pull the self profile, map it, reduce against the current
|
|
395
|
+
* config, and (optionally) return the diff. Network call is injected via
|
|
396
|
+
* `fetchSelfProfileImpl` (the lib/org/client export) so this is unit-testable.
|
|
397
|
+
*
|
|
398
|
+
* Fail-open: a missing key, an unreachable server, or a profile-less response
|
|
399
|
+
* yields `{ pulled:false, patch:{}, changes:[] }` — the caller falls back to the
|
|
400
|
+
* manual prompts exactly as today.
|
|
401
|
+
*
|
|
402
|
+
* @param {object} o
|
|
403
|
+
* @param {string} o.agentRoot
|
|
404
|
+
* @param {NodeJS.ProcessEnv} [o.env]
|
|
405
|
+
* @param {boolean} [o.force]
|
|
406
|
+
* @param {Function} o.fetchSelfProfileImpl client.fetchSelfProfile (injected)
|
|
407
|
+
* @returns {Promise<{ pulled:boolean, reason?:string, profile?:object, patch:object, changes:Array }>}
|
|
408
|
+
*/
|
|
409
|
+
export async function pullAndPlan(o = {}) {
|
|
410
|
+
const env = o.env || process.env;
|
|
411
|
+
const e = cohortPullEnv(env);
|
|
412
|
+
if (!e.enabled) return { pulled: false, reason: "cohort-pull env not set", patch: {}, changes: [] };
|
|
413
|
+
if (typeof o.fetchSelfProfileImpl !== "function") {
|
|
414
|
+
return { pulled: false, reason: "no fetch impl", patch: {}, changes: [] };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let frame;
|
|
418
|
+
try {
|
|
419
|
+
frame = await o.fetchSelfProfileImpl({
|
|
420
|
+
base: e.base,
|
|
421
|
+
apiKey: e.apiKey,
|
|
422
|
+
orgId: e.orgId,
|
|
423
|
+
agentId: e.agentId || undefined,
|
|
424
|
+
emailHint: str(env.COHORT_AGENT_EMAIL) || undefined,
|
|
425
|
+
});
|
|
426
|
+
} catch {
|
|
427
|
+
return { pulled: false, reason: "fetch threw", patch: {}, changes: [] };
|
|
428
|
+
}
|
|
429
|
+
if (!frame || !frame.ok || !frame.result) {
|
|
430
|
+
return { pulled: false, reason: (frame && frame.error && frame.error.message) || "no profile", patch: {}, changes: [] };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const profile = frame.result;
|
|
434
|
+
const mapped = mapProfileToAgentConfig(profile);
|
|
435
|
+
const current = readAgentJson(o.agentRoot) || {};
|
|
436
|
+
const patch = mergeProfilePatch(current, mapped, { force: !!o.force });
|
|
437
|
+
const changes = diffPatch(current, patch);
|
|
438
|
+
return { pulled: true, profile, patch, changes };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export { FUNCTIONS, ALTITUDES, DEFAULT_FUNCTION, DEFAULT_ALTITUDE };
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enroll-from-cohort.test.mjs — pull-and-map a Cohort MemberProfileDTO into
|
|
3
|
+
* the config/agent.json subset, with local-customisation protection + diff.
|
|
4
|
+
* Run: node --test lib/setup/enroll-from-cohort.test.mjs
|
|
5
|
+
*/
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
mapProfileToAgentConfig,
|
|
16
|
+
mergeProfilePatch,
|
|
17
|
+
diffPatch,
|
|
18
|
+
deriveFunction,
|
|
19
|
+
deriveAltitude,
|
|
20
|
+
cohortPullEnv,
|
|
21
|
+
pullAndPlan,
|
|
22
|
+
FUNCTIONS,
|
|
23
|
+
ALTITUDES,
|
|
24
|
+
} from "./enroll-from-cohort.mjs";
|
|
25
|
+
import { readAgentJson } from "./sot.mjs";
|
|
26
|
+
|
|
27
|
+
function tmpRoot() {
|
|
28
|
+
const root = mkdtempSync(join(tmpdir(), "maestro-enroll-"));
|
|
29
|
+
mkdirSync(join(root, "config"), { recursive: true });
|
|
30
|
+
return root;
|
|
31
|
+
}
|
|
32
|
+
function writeAgent(root, obj) {
|
|
33
|
+
writeFileSync(join(root, "config", "agent.json"), JSON.stringify(obj, null, 2) + "\n");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A representative MemberProfileDTO from the org-data API.
|
|
37
|
+
const PROFILE = {
|
|
38
|
+
slug: "robin",
|
|
39
|
+
kind: "agent",
|
|
40
|
+
displayName: "Robin Vega",
|
|
41
|
+
firstName: "Robin",
|
|
42
|
+
lastName: "Vega",
|
|
43
|
+
title: "VP of Engineering",
|
|
44
|
+
team: "Platform Engineering",
|
|
45
|
+
role: "engineering-lead",
|
|
46
|
+
agentEmail: "robin@example.com",
|
|
47
|
+
supervisor: { displayName: "Layla Al-Masri", title: "CTO", email: "layla@example.com" },
|
|
48
|
+
profileSections: [
|
|
49
|
+
{ id: "operating-principles", content: "- Ship small\n- Default to autonomy\n- Escalate blockers fast" },
|
|
50
|
+
{ id: "responsibilities", items: ["Own CI/CD", "Run platform on-call"] },
|
|
51
|
+
{ id: "communication", content: "Direct, technical, low-ceremony." },
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
test("mapProfileToAgentConfig maps the authoritative fields", () => {
|
|
56
|
+
const patch = mapProfileToAgentConfig(PROFILE);
|
|
57
|
+
assert.equal(patch.firstName, "Robin");
|
|
58
|
+
assert.equal(patch.lastName, "Vega");
|
|
59
|
+
assert.equal(patch.fullName, "Robin Vega");
|
|
60
|
+
assert.equal(patch.title, "VP of Engineering");
|
|
61
|
+
assert.equal(patch.email, "robin@example.com");
|
|
62
|
+
assert.equal(patch.function, "technical-leader");
|
|
63
|
+
assert.equal(patch.altitude, "vp");
|
|
64
|
+
assert.equal(patch.principal.fullName, "Layla Al-Masri");
|
|
65
|
+
assert.equal(patch.principal.title, "CTO");
|
|
66
|
+
assert.equal(patch.principal.email, "layla@example.com");
|
|
67
|
+
assert.deepEqual(patch.operatingPrinciples, ["Ship small", "Default to autonomy", "Escalate blockers fast"]);
|
|
68
|
+
assert.deepEqual(patch.responsibilities, ["Own CI/CD", "Run platform on-call"]);
|
|
69
|
+
assert.equal(patch.communication.defaultTone, "Direct, technical, low-ceremony.");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("mapProfileToAgentConfig is fail-open on a null/empty profile", () => {
|
|
73
|
+
assert.deepEqual(mapProfileToAgentConfig(null), {});
|
|
74
|
+
assert.deepEqual(mapProfileToAgentConfig({}), {});
|
|
75
|
+
// A bare role signal IS enough to derive an axis.
|
|
76
|
+
assert.equal(mapProfileToAgentConfig({ role: "engineer" }).function, "technical-leader");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// A MemberProfileDTO carrying the Cohort-authored charter + the authoritative
|
|
80
|
+
// operating fields the mapper previously DROPPED.
|
|
81
|
+
const PROFILE_FULL = {
|
|
82
|
+
...PROFILE,
|
|
83
|
+
autonomyLevel: "high",
|
|
84
|
+
escalationPath: "CTO, then CEO for irreversible decisions",
|
|
85
|
+
operatingChannel: "#platform",
|
|
86
|
+
skills: ["ci-cd", "incident-response"],
|
|
87
|
+
reports: [{ member: { slug: "kai", displayName: "Kai Tan" }, style: "SOLID" }],
|
|
88
|
+
governance: [{ type: "ADVISES", direction: "out", counterpart: { slug: "secops" }, theme: "security" }],
|
|
89
|
+
charter: {
|
|
90
|
+
approvedByLine: "Approved by Layla Al-Masri, CTO — 2026-06-01",
|
|
91
|
+
gatesText: "All irreversible spend > $10k escalates.",
|
|
92
|
+
sections: [
|
|
93
|
+
{ kind: "purpose", order: 1, title: "Purpose & Mandate", body: "Own platform reliability and developer velocity." },
|
|
94
|
+
{ kind: "decision_rights", order: 2, title: null, body: "Owns deploy cadence; recommends headcount." },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
test("mapProfileToAgentConfig captures the Cohort-authored charter (SoT)", () => {
|
|
100
|
+
const patch = mapProfileToAgentConfig(PROFILE_FULL);
|
|
101
|
+
assert.ok(patch.charter, "charter present in patch");
|
|
102
|
+
assert.equal(patch.charter.approvedByLine, "Approved by Layla Al-Masri, CTO — 2026-06-01");
|
|
103
|
+
assert.equal(patch.charter.gatesText, "All irreversible spend > $10k escalates.");
|
|
104
|
+
assert.equal(patch.charter.sections.length, 2);
|
|
105
|
+
assert.equal(patch.charter.sections[0].title, "Purpose & Mandate");
|
|
106
|
+
assert.equal(patch.charter.sections[1].title, null);
|
|
107
|
+
assert.match(patch.charter.sections[1].body, /Owns deploy cadence/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("mapProfileToAgentConfig persists the authoritative org operating fields", () => {
|
|
111
|
+
const patch = mapProfileToAgentConfig(PROFILE_FULL);
|
|
112
|
+
assert.ok(patch.orgProfile, "orgProfile present in patch");
|
|
113
|
+
assert.equal(patch.orgProfile.autonomyLevel, "high");
|
|
114
|
+
assert.match(patch.orgProfile.escalationPath, /CTO/);
|
|
115
|
+
assert.equal(patch.orgProfile.operatingChannel, "#platform");
|
|
116
|
+
assert.deepEqual(patch.orgProfile.skills, ["ci-cd", "incident-response"]);
|
|
117
|
+
assert.equal(patch.orgProfile.reports[0].member.slug, "kai");
|
|
118
|
+
assert.equal(patch.orgProfile.governance[0].type, "ADVISES");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("charter/orgProfile absent → no charter/orgProfile keys (empty patch stays empty)", () => {
|
|
122
|
+
const patch = mapProfileToAgentConfig(PROFILE); // base fixture has neither
|
|
123
|
+
assert.equal(patch.charter, undefined);
|
|
124
|
+
assert.equal(patch.orgProfile, undefined);
|
|
125
|
+
// A charter with no real content is also dropped.
|
|
126
|
+
const noContent = mapProfileToAgentConfig({ charter: { approvedByLine: "", gatesText: "", sections: [] } });
|
|
127
|
+
assert.equal(noContent.charter, undefined);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("deriveFunction + deriveAltitude resolve to valid axes (safe default)", () => {
|
|
131
|
+
assert.ok(FUNCTIONS.includes(deriveFunction({ role: "sales lead", team: "Revenue" })));
|
|
132
|
+
assert.equal(deriveFunction({ role: "sales lead", team: "Revenue" }), "commercial-leader");
|
|
133
|
+
assert.equal(deriveFunction({ role: "general counsel", team: "Legal" }), "compliance-leader");
|
|
134
|
+
assert.equal(deriveFunction({ role: "weird-unknown-role" }), "executive-operator");
|
|
135
|
+
assert.ok(ALTITUDES.includes(deriveAltitude({ title: "Chief Technology Officer" })));
|
|
136
|
+
assert.equal(deriveAltitude({ title: "Chief Technology Officer" }), "c-suite");
|
|
137
|
+
assert.equal(deriveAltitude({ title: "Senior Manager, Ops" }), "senior-manager");
|
|
138
|
+
assert.equal(deriveAltitude({ title: "Mystery" }), "c-suite");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("mergeProfilePatch protects a locally-customised value unless --force", () => {
|
|
142
|
+
const current = { firstName: "Robin", title: "Custom Title The Operator Set", email: "" };
|
|
143
|
+
const patch = mapProfileToAgentConfig(PROFILE);
|
|
144
|
+
const reduced = mergeProfilePatch(current, patch, { force: false });
|
|
145
|
+
// title is locally set → dropped; email is empty → kept; firstName equals → still set is fine
|
|
146
|
+
assert.equal(reduced.title, undefined, "local title is NOT clobbered");
|
|
147
|
+
assert.equal(reduced.email, "robin@example.com", "empty local value gets filled");
|
|
148
|
+
// With --force, the org value wins.
|
|
149
|
+
const forced = mergeProfilePatch(current, patch, { force: true });
|
|
150
|
+
assert.equal(forced.title, "VP of Engineering");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("mergeProfilePatch reduces nested objects leaf-by-leaf", () => {
|
|
154
|
+
const current = { principal: { fullName: "Existing Boss", title: "" } };
|
|
155
|
+
const patch = mapProfileToAgentConfig(PROFILE);
|
|
156
|
+
const reduced = mergeProfilePatch(current, patch, { force: false });
|
|
157
|
+
assert.equal(reduced.principal.fullName, undefined, "local principal.fullName kept");
|
|
158
|
+
assert.equal(reduced.principal.title, "CTO", "empty principal.title filled");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("mergeProfilePatch treats a placeholder string as unset", () => {
|
|
162
|
+
const current = { title: "UNCONFIGURED" };
|
|
163
|
+
const reduced = mergeProfilePatch(current, { title: "VP of Engineering" }, { force: false });
|
|
164
|
+
assert.equal(reduced.title, "VP of Engineering");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("diffPatch reports leaf-level changes including nested + array", () => {
|
|
168
|
+
const before = { title: "Old", principal: { title: "Old VP" } };
|
|
169
|
+
const patch = { title: "VP of Engineering", principal: { title: "CTO" }, responsibilities: ["a", "b"] };
|
|
170
|
+
const diff = diffPatch(before, patch);
|
|
171
|
+
const byPath = Object.fromEntries(diff.map((d) => [d.path, d]));
|
|
172
|
+
assert.equal(byPath["title"].from, "Old");
|
|
173
|
+
assert.equal(byPath["title"].to, "VP of Engineering");
|
|
174
|
+
assert.equal(byPath["principal.title"].to, "CTO");
|
|
175
|
+
assert.ok(byPath["responsibilities"], "array change is reported");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("cohortPullEnv gates on api key + org id; agent id optional", () => {
|
|
179
|
+
assert.equal(cohortPullEnv({}).enabled, false);
|
|
180
|
+
assert.equal(cohortPullEnv({ COHORT_API_KEY: "k" }).enabled, false);
|
|
181
|
+
const e = cohortPullEnv({ COHORT_API_KEY: "k", COHORT_ORG_ID: "acme", COHORT_AGENT_ID: "robin" });
|
|
182
|
+
assert.equal(e.enabled, true);
|
|
183
|
+
assert.equal(e.apiKey, "k");
|
|
184
|
+
assert.equal(e.orgId, "acme");
|
|
185
|
+
assert.equal(e.agentId, "robin");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("pullAndPlan: with env + mocked profile, plans a patch and a diff", async () => {
|
|
189
|
+
const root = tmpRoot();
|
|
190
|
+
writeAgent(root, { firstName: "UNCONFIGURED" });
|
|
191
|
+
const plan = await pullAndPlan({
|
|
192
|
+
agentRoot: root,
|
|
193
|
+
env: { COHORT_API_KEY: "k", COHORT_ORG_ID: "acme", COHORT_AGENT_ID: "robin" },
|
|
194
|
+
fetchSelfProfileImpl: async () => ({ ok: true, result: PROFILE }),
|
|
195
|
+
});
|
|
196
|
+
assert.equal(plan.pulled, true);
|
|
197
|
+
assert.equal(plan.patch.firstName, "Robin", "placeholder firstName overwritten");
|
|
198
|
+
assert.equal(plan.patch.email, "robin@example.com");
|
|
199
|
+
assert.ok(plan.changes.length > 0);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("pullAndPlan: absent env → not pulled (manual path stands)", async () => {
|
|
203
|
+
const root = tmpRoot();
|
|
204
|
+
const plan = await pullAndPlan({
|
|
205
|
+
agentRoot: root,
|
|
206
|
+
env: {},
|
|
207
|
+
fetchSelfProfileImpl: async () => ({ ok: true, result: PROFILE }),
|
|
208
|
+
});
|
|
209
|
+
assert.equal(plan.pulled, false);
|
|
210
|
+
assert.deepEqual(plan.patch, {});
|
|
211
|
+
assert.deepEqual(plan.changes, []);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("pullAndPlan: fetch failure → fail-open, not pulled", async () => {
|
|
215
|
+
const root = tmpRoot();
|
|
216
|
+
const plan = await pullAndPlan({
|
|
217
|
+
agentRoot: root,
|
|
218
|
+
env: { COHORT_API_KEY: "k", COHORT_ORG_ID: "acme" },
|
|
219
|
+
fetchSelfProfileImpl: async () => ({ ok: false, error: { code: "INTERNAL", message: "down" } }),
|
|
220
|
+
});
|
|
221
|
+
assert.equal(plan.pulled, false);
|
|
222
|
+
assert.deepEqual(plan.patch, {});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("pullAndPlan: a local override is NOT clobbered without --force", async () => {
|
|
226
|
+
const root = tmpRoot();
|
|
227
|
+
writeAgent(root, { title: "My Custom Title" });
|
|
228
|
+
const env = { COHORT_API_KEY: "k", COHORT_ORG_ID: "acme", COHORT_AGENT_ID: "robin" };
|
|
229
|
+
const noForce = await pullAndPlan({ agentRoot: root, env, fetchSelfProfileImpl: async () => ({ ok: true, result: PROFILE }) });
|
|
230
|
+
assert.equal(noForce.patch.title, undefined, "custom title protected");
|
|
231
|
+
const forced = await pullAndPlan({ agentRoot: root, env, force: true, fetchSelfProfileImpl: async () => ({ ok: true, result: PROFILE }) });
|
|
232
|
+
assert.equal(forced.patch.title, "VP of Engineering", "--force overwrites");
|
|
233
|
+
});
|