@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,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for lib/org/protocol.mjs — the shared Cohort protocol contract.
|
|
3
|
+
* Locks the load-bearing invariants: scope resolution (default-deny + reserved
|
|
4
|
+
* namespaces), the hash chain (determinism + tamper detection), and frame
|
|
5
|
+
* validation. @module lib/org/protocol.test
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import {
|
|
11
|
+
PROTOCOL_VERSION, METHODS, FAMILIES, BOARD_STATUS,
|
|
12
|
+
scopeForMethod, actorCan, isReservedAdminNamespace,
|
|
13
|
+
computeRowHash, verifyChain, canonicalize,
|
|
14
|
+
validateReqFrame, buildReqFrame, okFrame, errFrame,
|
|
15
|
+
} from "./protocol.mjs";
|
|
16
|
+
|
|
17
|
+
test("protocol: every method's family is a known family", () => {
|
|
18
|
+
for (const [name, def] of Object.entries(METHODS)) {
|
|
19
|
+
assert.ok(FAMILIES.includes(def.family), `${name} family ${def.family} is known`);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("scopeForMethod: reserved namespaces always resolve to admin", () => {
|
|
24
|
+
assert.equal(scopeForMethod("admin.deactivate"), "admin");
|
|
25
|
+
assert.equal(scopeForMethod("pairing.request"), "admin");
|
|
26
|
+
assert.equal(scopeForMethod("policy.anything"), "admin");
|
|
27
|
+
assert.equal(scopeForMethod("governance.x"), "admin");
|
|
28
|
+
// unknown method → admin (deny)
|
|
29
|
+
assert.equal(scopeForMethod("totally.unknown"), "admin");
|
|
30
|
+
// ordinary method → its declared scope
|
|
31
|
+
assert.equal(scopeForMethod("board.claim"), "board.work");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("actorCan: default-deny; admin satisfies everything", () => {
|
|
35
|
+
assert.equal(actorCan(["board.work"], "board.claim"), true);
|
|
36
|
+
assert.equal(actorCan(["board.read"], "board.claim"), false, "wrong scope denied");
|
|
37
|
+
assert.equal(actorCan(["admin"], "board.claim"), true, "admin is a superset");
|
|
38
|
+
assert.equal(actorCan(["board.work"], "admin.deactivate"), false, "non-admin cannot kill-switch");
|
|
39
|
+
assert.equal(actorCan([], "board.claim"), false);
|
|
40
|
+
assert.equal(actorCan(null, "board.claim"), false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("isReservedAdminNamespace", () => {
|
|
44
|
+
assert.equal(isReservedAdminNamespace("admin.x"), true);
|
|
45
|
+
assert.equal(isReservedAdminNamespace("board.claim"), false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("canonicalize: key order does not change the bytes", () => {
|
|
49
|
+
assert.equal(canonicalize({ b: 1, a: 2 }), canonicalize({ a: 2, b: 1 }));
|
|
50
|
+
assert.equal(canonicalize({ a: { y: 1, x: 2 } }), '{"a":{"x":2,"y":1}}');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("computeRowHash: deterministic + sensitive to every field", () => {
|
|
54
|
+
const row = { seq: 1, family: "board", kind: "create", entity_id: "i1", actor: "a", at: "2026-06-11T00:00:00Z", payload: { title: "x" } };
|
|
55
|
+
const h1 = computeRowHash("", row);
|
|
56
|
+
const h2 = computeRowHash("", { ...row });
|
|
57
|
+
assert.equal(h1, h2, "same input → same hash");
|
|
58
|
+
assert.notEqual(h1, computeRowHash("prev", row), "prev hash matters");
|
|
59
|
+
assert.notEqual(h1, computeRowHash("", { ...row, payload: { title: "y" } }), "payload matters");
|
|
60
|
+
assert.notEqual(h1, computeRowHash("", { ...row, seq: 2 }), "seq matters");
|
|
61
|
+
assert.match(h1, /^[0-9a-f]{64}$/, "lowercase hex sha-256");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("verifyChain: accepts a valid chain, pinpoints tampering", () => {
|
|
65
|
+
const mk = (seq, prev, payload) => {
|
|
66
|
+
const base = { seq, family: "board", kind: "create", entity_id: `i${seq}`, actor: "a", at: `2026-06-11T00:00:0${seq}Z`, payload };
|
|
67
|
+
return { ...base, prev_hash: prev, row_hash: computeRowHash(prev, base) };
|
|
68
|
+
};
|
|
69
|
+
const e1 = mk(1, "", { n: 1 });
|
|
70
|
+
const e2 = mk(2, e1.row_hash, { n: 2 });
|
|
71
|
+
const e3 = mk(3, e2.row_hash, { n: 3 });
|
|
72
|
+
assert.equal(verifyChain([e1, e2, e3]), null, "clean chain verifies");
|
|
73
|
+
|
|
74
|
+
// Tamper with e2's payload but keep its stored row_hash → detected at index 1.
|
|
75
|
+
const tampered = { ...e2, payload: { n: 999 } };
|
|
76
|
+
const break1 = verifyChain([e1, tampered, e3]);
|
|
77
|
+
assert.ok(break1 && break1.index === 1, "tamper detected at the rewritten row");
|
|
78
|
+
|
|
79
|
+
// Splice/delete e2 → e3.prev_hash no longer matches → detected at index 1.
|
|
80
|
+
const break2 = verifyChain([e1, e3]);
|
|
81
|
+
assert.ok(break2 && break2.index === 1, "deletion detected");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("validateReqFrame: registry-checked", () => {
|
|
85
|
+
assert.equal(validateReqFrame(buildReqFrame("board.claim", { id: "x" })).ok, true);
|
|
86
|
+
assert.equal(validateReqFrame({ method: "nope.method" }).ok, false);
|
|
87
|
+
assert.equal(validateReqFrame({}).ok, false);
|
|
88
|
+
assert.equal(validateReqFrame({ method: "board.claim", params: 7 }).ok, false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("ok/err frames + version", () => {
|
|
92
|
+
assert.deepEqual(okFrame({ a: 1 }), { ok: true, result: { a: 1 } });
|
|
93
|
+
const e = errFrame("FORBIDDEN_SCOPE", "no");
|
|
94
|
+
assert.equal(e.ok, false);
|
|
95
|
+
assert.equal(e.error.code, "FORBIDDEN_SCOPE");
|
|
96
|
+
assert.equal(errFrame("MADE_UP", "x").error.code, "INTERNAL", "unknown code coerces to INTERNAL");
|
|
97
|
+
assert.equal(PROTOCOL_VERSION, 1);
|
|
98
|
+
assert.ok(BOARD_STATUS.includes("ready") && BOARD_STATUS.includes("done"));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("protocol phase 2: governance gating + new families/scopes", async () => {
|
|
102
|
+
const p = await import("./protocol.mjs");
|
|
103
|
+
// handoffs + assignment/decomposition are governance-gated; ordinary board ops are not.
|
|
104
|
+
for (const m of ["handoff.offer", "handoff.accept", "handoff.decline", "handoff.cancel", "board.assign", "board.decompose"]) {
|
|
105
|
+
assert.equal(p.isGovernanceGated(m), true, `${m} is governance-gated`);
|
|
106
|
+
}
|
|
107
|
+
for (const m of ["board.create", "board.claim", "lease.claim", "registry.heartbeat"]) {
|
|
108
|
+
assert.equal(p.isGovernanceGated(m), false, `${m} is NOT governance-gated`);
|
|
109
|
+
}
|
|
110
|
+
// new scopes resolve; lease/handoff are in the default agent set, governance is admin-only.
|
|
111
|
+
assert.equal(p.scopeForMethod("lease.claim"), "lease.write");
|
|
112
|
+
assert.equal(p.scopeForMethod("handoff.offer"), "handoff.send");
|
|
113
|
+
assert.equal(p.scopeForMethod("governance.set_decision_rights"), "admin");
|
|
114
|
+
assert.ok(p.DEFAULT_AGENT_SCOPES.includes("lease.write") && p.DEFAULT_AGENT_SCOPES.includes("handoff.send"));
|
|
115
|
+
assert.equal(p.actorCan(p.DEFAULT_AGENT_SCOPES, "governance.set_decision_rights"), false, "agents cannot set decision rights");
|
|
116
|
+
assert.ok(p.FAMILIES.includes("lease") && p.FAMILIES.includes("handoff") && p.FAMILIES.includes("governance"));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("protocol: frozen family + method counts (additive evolution guard)", () => {
|
|
120
|
+
// These counts move ONLY when the protocol intentionally grows; pinning them
|
|
121
|
+
// catches an accidental method drop/addition that the checksum alone wouldn't
|
|
122
|
+
// explain. Bump deliberately alongside the descriptors.
|
|
123
|
+
// 47 = the 19 mesh-protocol families + 20 agent UI-parity families (charter,
|
|
124
|
+
// member, team, channel, persona, profile, sop, escalation, annotation,
|
|
125
|
+
// contact, file, memory, compact, notification, invitation, user, settings,
|
|
126
|
+
// billing, org, integration) + 1 Brand Studio v2 family (branding) + 1
|
|
127
|
+
// workspace-email family (email; 2026-07-21 agent-email-server spec §8.1)
|
|
128
|
+
// + 1 generative-UI artifact family (artifact; GU program 2026-07)
|
|
129
|
+
// + 5 agent-desk families (2026-07): books (Books desk), calendar
|
|
130
|
+
// (workspace calendar), crm (CRM desk), files (workspace drive — distinct
|
|
131
|
+
// from the UI-parity "file" pins/comments family), and directory (external
|
|
132
|
+
// people + organisations; the Design half of that program rides the existing
|
|
133
|
+
// `branding` family — the section is renamed, the wire name is not).
|
|
134
|
+
assert.equal(FAMILIES.length, 47, "family count");
|
|
135
|
+
// 357 = the mesh-protocol + agent UI-parity + calling + branding + email
|
|
136
|
+
// methods, plus the Live Integrations surface: integration.toolsetVersion +
|
|
137
|
+
// integration.listAgentTools (SP1) and integration.invokeTool (SP5, execute a
|
|
138
|
+
// granted plugin tool as the seat), plus the six artifact methods
|
|
139
|
+
// (create/act/patch/get/list/catalog — GU program 2026-07), plus the 141
|
|
140
|
+
// agent-desk methods (2026-07, the integration manifest's definitive table):
|
|
141
|
+
// books 43, calendar 8, crm 43 (38 + the guest-call trio addCallGuest/
|
|
142
|
+
// mintGuestToken/linkEmail back-ported from Cohort 2026-07-25 + the
|
|
143
|
+
// articulation program's proposeTerms/revokeDealShare), the email Inbox-app
|
|
144
|
+
// surface 31, files 20, and meetings.recapFile. Then the 2026-07 Directory &
|
|
145
|
+
// Design program adds 44: directory 34 (11 reads + 23 writes, of which the
|
|
146
|
+
// merge/awareness/archive verbs are human-gated in the hq domain) and 10 new
|
|
147
|
+
// branding methods (getFoundation, getVoice, listTemplates, renderTemplate,
|
|
148
|
+
// rewriteInVoice, exportKit, generateImage, proposeChange, saveFoundation,
|
|
149
|
+
// restoreFoundation) taking branding 14 → 24. The 2026-08 mobile-program
|
|
150
|
+
// parity pass adds 16: email 5 (reportSpam/react/moveMailbox/summarise/ask),
|
|
151
|
+
// files 9 (sheetTabOp/docTabOp/docSuggestResolve/docSuggestions/commentAdd/
|
|
152
|
+
// commentResolve/comments/sheetFedRange/ask), calendar.ask and crm.ask.
|
|
153
|
+
// The 2026-08 host-moderation pass adds 2: calling.removeParticipant +
|
|
154
|
+
// calling.setLock (host-gated remove-from-room + room lock) → 424.
|
|
155
|
+
// The 2026-08 call working-sessions pass adds 6: calling.appShareStart/
|
|
156
|
+
// appShareAct/appShareEnd (agent app-share lifecycle) + getAppShare/
|
|
157
|
+
// getVisualContext/getCallEvents (participation-gated reads) → 430.
|
|
158
|
+
// Bumped deliberately alongside
|
|
159
|
+
// the descriptors; the checksum (read at runtime) is the primary drift guard.
|
|
160
|
+
assert.equal(Object.keys(METHODS).length, 430, "method count");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("protocol SP3: messaging + calling families/methods/scopes", async () => {
|
|
164
|
+
const p = await import("./protocol.mjs");
|
|
165
|
+
// new families exist.
|
|
166
|
+
assert.ok(p.FAMILIES.includes("messaging") && p.FAMILIES.includes("calling"));
|
|
167
|
+
// messaging methods + their declared scopes / side-effect posture.
|
|
168
|
+
const messagingMethods = {
|
|
169
|
+
"messaging.send": { scope: "messaging.write", sideEffecting: true, idempotent: true },
|
|
170
|
+
"messaging.react": { scope: "messaging.write", sideEffecting: true },
|
|
171
|
+
"messaging.edit": { scope: "messaging.write", sideEffecting: true },
|
|
172
|
+
"messaging.history": { scope: "messaging.read", sideEffecting: false },
|
|
173
|
+
"messaging.channels": { scope: "messaging.read", sideEffecting: false },
|
|
174
|
+
};
|
|
175
|
+
for (const [m, want] of Object.entries(messagingMethods)) {
|
|
176
|
+
const def = p.METHODS[m];
|
|
177
|
+
assert.ok(def, `${m} is registered`);
|
|
178
|
+
assert.equal(def.family, "messaging", `${m} family`);
|
|
179
|
+
assert.equal(def.scope, want.scope, `${m} scope`);
|
|
180
|
+
assert.equal(p.scopeForMethod(m), want.scope, `${m} scopeForMethod`);
|
|
181
|
+
assert.equal(def.sideEffecting, want.sideEffecting, `${m} sideEffecting`);
|
|
182
|
+
if (want.idempotent) assert.equal(def.idempotent, true, `${m} idempotent`);
|
|
183
|
+
}
|
|
184
|
+
// send is idempotent via the client-supplied message id.
|
|
185
|
+
assert.equal(p.METHODS["messaging.send"].idempotent, true);
|
|
186
|
+
// calling methods: all calling.write + side-effecting. removeParticipant +
|
|
187
|
+
// setLock are the 2026-08 host-moderation verbs (host-gated in the hq domain).
|
|
188
|
+
for (const m of ["calling.start", "calling.join", "calling.end", "calling.invite", "calling.removeParticipant", "calling.setLock"]) {
|
|
189
|
+
const def = p.METHODS[m];
|
|
190
|
+
assert.ok(def, `${m} is registered`);
|
|
191
|
+
assert.equal(def.family, "calling", `${m} family`);
|
|
192
|
+
assert.equal(def.scope, "calling.write", `${m} scope`);
|
|
193
|
+
assert.equal(p.scopeForMethod(m), "calling.write", `${m} scopeForMethod`);
|
|
194
|
+
assert.equal(def.sideEffecting, true, `${m} sideEffecting`);
|
|
195
|
+
}
|
|
196
|
+
// messaging/calling are NOT reserved-admin namespaces (scope is NOT forced to admin).
|
|
197
|
+
assert.equal(p.isReservedAdminNamespace("messaging.send"), false);
|
|
198
|
+
assert.equal(p.isReservedAdminNamespace("calling.start"), false);
|
|
199
|
+
// new scopes are first-class capabilities.
|
|
200
|
+
for (const s of ["messaging.read", "messaging.write", "calling.write"]) {
|
|
201
|
+
assert.ok(p.SCOPES.includes(s), `SCOPES includes ${s}`);
|
|
202
|
+
}
|
|
203
|
+
// agents participate in the human app: the new scopes are in the default set.
|
|
204
|
+
for (const s of ["messaging.read", "messaging.write", "calling.write"]) {
|
|
205
|
+
assert.ok(p.DEFAULT_AGENT_SCOPES.includes(s), `DEFAULT_AGENT_SCOPES includes ${s}`);
|
|
206
|
+
}
|
|
207
|
+
// a default-scoped agent can actually exercise the new surfaces.
|
|
208
|
+
assert.equal(p.actorCan(p.DEFAULT_AGENT_SCOPES, "messaging.send"), true);
|
|
209
|
+
assert.equal(p.actorCan(p.DEFAULT_AGENT_SCOPES, "messaging.history"), true);
|
|
210
|
+
assert.equal(p.actorCan(p.DEFAULT_AGENT_SCOPES, "calling.start"), true);
|
|
211
|
+
// default-deny still holds: a read-only token cannot send.
|
|
212
|
+
assert.equal(p.actorCan(["messaging.read"], "messaging.send"), false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("protocol agent desks: per-family scope pairs, counts, and default grants", async () => {
|
|
216
|
+
const p = await import("./protocol.mjs");
|
|
217
|
+
// Each desk family rides its OWN read/write scope pair (the integration
|
|
218
|
+
// manifest's declared contract) so a key can be minted for exactly one desk.
|
|
219
|
+
// 2026-08 mobile-parity delta: crm +ask (44), calendar +ask (9), files +9
|
|
220
|
+
// (tab/suggestion/comment/fed-range ops + ask → 29).
|
|
221
|
+
const desks = { books: 43, crm: 44, files: 29, calendar: 9 };
|
|
222
|
+
for (const [fam, want] of Object.entries(desks)) {
|
|
223
|
+
const rows = Object.entries(p.METHODS).filter(([, d]) => d.family === fam);
|
|
224
|
+
assert.equal(rows.length, want, `${fam} method count`);
|
|
225
|
+
for (const [name, d] of rows) {
|
|
226
|
+
assert.ok(
|
|
227
|
+
d.scope === `${fam}.read` || d.scope === `${fam}.write`,
|
|
228
|
+
`${name} rides the ${fam}.read/${fam}.write pair (got ${d.scope})`,
|
|
229
|
+
);
|
|
230
|
+
if (d.scope === `${fam}.read` && name !== "files.exportRequest") {
|
|
231
|
+
assert.equal(d.sideEffecting, false, `${name} read is sideEffecting:false`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
assert.ok(p.SCOPES.includes(`${fam}.read`) && p.SCOPES.includes(`${fam}.write`), `${fam} scope pair declared`);
|
|
235
|
+
assert.ok(
|
|
236
|
+
p.DEFAULT_AGENT_SCOPES.includes(`${fam}.read`) && p.DEFAULT_AGENT_SCOPES.includes(`${fam}.write`),
|
|
237
|
+
`${fam} scopes in the default agent set`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
// The email Inbox-app surface rides the existing email pair: 41 methods
|
|
241
|
+
// total (36 + the 2026-08 spam/react/moveMailbox/summarise/ask delta).
|
|
242
|
+
assert.equal(Object.entries(p.METHODS).filter(([, d]) => d.family === "email").length, 41, "email method count");
|
|
243
|
+
assert.equal(p.scopeForMethod("email.draftSend"), "email.write");
|
|
244
|
+
assert.equal(p.scopeForMethod("email.mailboxes"), "email.read");
|
|
245
|
+
// files.exportRequest is the AUDITED export: read scope, side-effecting.
|
|
246
|
+
assert.deepEqual(
|
|
247
|
+
{ scope: p.METHODS["files.exportRequest"].scope, se: p.METHODS["files.exportRequest"].sideEffecting },
|
|
248
|
+
{ scope: "files.read", se: true },
|
|
249
|
+
);
|
|
250
|
+
// meetings.recapFile is knowledge capture (meetings family, knowledge.write).
|
|
251
|
+
assert.equal(p.METHODS["meetings.recapFile"].scope, "knowledge.write");
|
|
252
|
+
assert.equal(p.METHODS["meetings.recapFile"].family, "meetings");
|
|
253
|
+
// Spot-check idempotency posture from the manifest: draftSend/ruleSave are
|
|
254
|
+
// NOT idempotent (each send/save is a new act); holdReplies/markAllRead are.
|
|
255
|
+
assert.ok(!p.METHODS["email.draftSend"].idempotent);
|
|
256
|
+
assert.ok(!p.METHODS["email.ruleSave"].idempotent);
|
|
257
|
+
assert.equal(p.METHODS["email.holdReplies"].idempotent, true);
|
|
258
|
+
assert.equal(p.METHODS["email.markAllRead"].idempotent, true);
|
|
259
|
+
// A default-scoped agent can work the desks; a single-desk token stays fenced.
|
|
260
|
+
assert.equal(p.actorCan(p.DEFAULT_AGENT_SCOPES, "books.invoiceDraft"), true);
|
|
261
|
+
assert.equal(p.actorCan(p.DEFAULT_AGENT_SCOPES, "crm.listDeals"), true);
|
|
262
|
+
assert.equal(p.actorCan(["books.read"], "books.invoiceDraft"), false, "read tier cannot write");
|
|
263
|
+
assert.equal(p.actorCan(["books.read", "books.write"], "crm.createDeal"), false, "books key cannot touch crm");
|
|
264
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/org/registry.mjs — the org-directory source of truth (file-backed).
|
|
3
|
+
*
|
|
4
|
+
* Assembles THIS agent's directory entry from data that already exists in the
|
|
5
|
+
* repo — identity (config/agent.json), company (config/company.json), resolved
|
|
6
|
+
* archetype (function × altitude → towers), current backlog focus (state/queues),
|
|
7
|
+
* and live presence — then publishes it to state/org/self.json. Peers come from
|
|
8
|
+
* config/known-agents.json (+ any state/org/peers/*.json synced from cohort).
|
|
9
|
+
*
|
|
10
|
+
* The entry shape is the contract the cohort client (lib/org/cohort-client.mjs)
|
|
11
|
+
* publishes and that humans read in the org-pulse digest. Pure assembly +
|
|
12
|
+
* fail-open I/O; never throws.
|
|
13
|
+
*
|
|
14
|
+
* @module lib/org/registry
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
"use strict";
|
|
18
|
+
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
|
|
22
|
+
const ORG_REL = "state/org";
|
|
23
|
+
|
|
24
|
+
function readJson(path) {
|
|
25
|
+
try { return JSON.parse(readFileSync(path, "utf8")); } catch { return null; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let _yaml = null;
|
|
29
|
+
async function getYaml() {
|
|
30
|
+
if (_yaml) return _yaml;
|
|
31
|
+
const m = await import("js-yaml");
|
|
32
|
+
_yaml = m.default || m;
|
|
33
|
+
return _yaml;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Parse config/agent.json (identity SOT). Tolerant. */
|
|
37
|
+
export function readIdentity(agentRoot) {
|
|
38
|
+
return readJson(join(agentRoot, "config", "agent.json")) || {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Parse config/company.json. Tolerant. */
|
|
42
|
+
export function readCompany(agentRoot) {
|
|
43
|
+
return readJson(join(agentRoot, "config", "company.json")) || {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Parse config/known-agents.json → peer list. Tolerant. */
|
|
47
|
+
export function readPeers(agentRoot) {
|
|
48
|
+
const obj = readJson(join(agentRoot, "config", "known-agents.json"));
|
|
49
|
+
return obj && Array.isArray(obj.agents) ? obj.agents : [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build a directory entry from already-loaded pieces. Pure.
|
|
54
|
+
*
|
|
55
|
+
* @param {object} o - { identity, company, profile?, focus?, presence?, nowMs? }
|
|
56
|
+
* @returns {object} entry
|
|
57
|
+
*/
|
|
58
|
+
export function buildSelfEntry(o = {}) {
|
|
59
|
+
const id = o.identity || {};
|
|
60
|
+
const company = o.company || {};
|
|
61
|
+
const profile = o.profile || null;
|
|
62
|
+
const nowMs = Number.isFinite(o.nowMs) ? o.nowMs : Date.now();
|
|
63
|
+
const towers = profile && Array.isArray(profile.towers)
|
|
64
|
+
? profile.towers.map((t) => ({ id: t.id, name: t.name })).slice(0, 12)
|
|
65
|
+
: [];
|
|
66
|
+
const repo = id.repoSlug || (id.firstName ? `${String(id.firstName).toLowerCase()}-ai` : "");
|
|
67
|
+
return {
|
|
68
|
+
id: id.repoSlug || repo || String(id.firstName || "agent").toLowerCase(),
|
|
69
|
+
name: id.firstName || "",
|
|
70
|
+
fullName: id.fullName || "",
|
|
71
|
+
title: id.title || "",
|
|
72
|
+
email: id.email || "",
|
|
73
|
+
phone: id.phone || "",
|
|
74
|
+
slackId: id.slackMemberId || id.slackId || "",
|
|
75
|
+
repo,
|
|
76
|
+
archetype: {
|
|
77
|
+
function: id.function || (profile && profile.function) || "",
|
|
78
|
+
altitude: id.altitude || (profile && profile.altitude) || "",
|
|
79
|
+
label: (profile && profile.label) || id.archetype || "",
|
|
80
|
+
},
|
|
81
|
+
towers,
|
|
82
|
+
focus: Array.isArray(o.focus) ? o.focus.slice(0, 8) : [],
|
|
83
|
+
company: { name: company.name || id.company || "", domain: company.domain || id.companyDomain || "" },
|
|
84
|
+
principal: id.principal
|
|
85
|
+
? { fullName: id.principal.fullName || "", title: id.principal.title || "" }
|
|
86
|
+
: { fullName: "", title: "" },
|
|
87
|
+
presence: o.presence || { active: 0 },
|
|
88
|
+
status: o.status || "active",
|
|
89
|
+
updatedAt: new Date(nowMs).toISOString(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Read the agent's current backlog focus from queue files. Returns the top open
|
|
95
|
+
* items as { title, queue, priority }. `items` may be injected for testing.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} agentRoot
|
|
98
|
+
* @param {object} [opts] - { limit=5, queues=["action-stack","backlog"], items? }
|
|
99
|
+
* @returns {Promise<object[]>}
|
|
100
|
+
*/
|
|
101
|
+
export async function currentFocus(agentRoot, opts = {}) {
|
|
102
|
+
const limit = Number.isFinite(opts.limit) ? opts.limit : 5;
|
|
103
|
+
if (Array.isArray(opts.items)) return opts.items.slice(0, limit);
|
|
104
|
+
const queues = opts.queues || ["action-stack", "backlog"];
|
|
105
|
+
const out = [];
|
|
106
|
+
try {
|
|
107
|
+
const yaml = await getYaml();
|
|
108
|
+
for (const q of queues) {
|
|
109
|
+
const p = join(agentRoot, "state", "queues", `${q}.yaml`);
|
|
110
|
+
if (!existsSync(p)) continue;
|
|
111
|
+
let doc;
|
|
112
|
+
try { doc = yaml.load(readFileSync(p, "utf8")); } catch { continue; }
|
|
113
|
+
const items = Array.isArray(doc) ? doc : (doc && Array.isArray(doc.items) ? doc.items : []);
|
|
114
|
+
for (const it of items) {
|
|
115
|
+
if (!it || typeof it !== "object") continue;
|
|
116
|
+
const status = String(it.status || "open").toLowerCase();
|
|
117
|
+
if (status === "done" || status === "closed" || status === "cancelled") continue;
|
|
118
|
+
out.push({ title: String(it.title || it.next_action || "").slice(0, 140), queue: q, priority: it.priority || "normal" });
|
|
119
|
+
if (out.length >= limit) return out;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch { /* fail-open */ }
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Assemble + return this agent's full self entry (does the I/O). Fail-open. */
|
|
127
|
+
export async function assembleSelfEntry(agentRoot, opts = {}) {
|
|
128
|
+
const identity = readIdentity(agentRoot);
|
|
129
|
+
const company = readCompany(agentRoot);
|
|
130
|
+
let profile = opts.profile || null;
|
|
131
|
+
if (!profile && identity.function && identity.altitude) {
|
|
132
|
+
try {
|
|
133
|
+
const mod = await import("../archetype.mjs");
|
|
134
|
+
profile = await mod.resolveArchetype({ function: identity.function, altitude: identity.altitude });
|
|
135
|
+
} catch { profile = null; }
|
|
136
|
+
}
|
|
137
|
+
let presence = opts.presence;
|
|
138
|
+
if (!presence) {
|
|
139
|
+
try {
|
|
140
|
+
const mod = await import("../collective/presence.mjs");
|
|
141
|
+
presence = { active: mod.list(agentRoot, {}).length };
|
|
142
|
+
} catch { presence = { active: 0 }; }
|
|
143
|
+
}
|
|
144
|
+
const focus = opts.focus || await currentFocus(agentRoot, opts);
|
|
145
|
+
return buildSelfEntry({ identity, company, profile, focus, presence, nowMs: opts.nowMs });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function orgDir(agentRoot) { return join(agentRoot, ORG_REL); }
|
|
149
|
+
|
|
150
|
+
/** Persist the self entry to state/org/self.json. Fail-open → false. */
|
|
151
|
+
export function writeSelfEntry(agentRoot, entry) {
|
|
152
|
+
try {
|
|
153
|
+
mkdirSync(orgDir(agentRoot), { recursive: true });
|
|
154
|
+
writeFileSync(join(orgDir(agentRoot), "self.json"), JSON.stringify(entry, null, 2));
|
|
155
|
+
return true;
|
|
156
|
+
} catch { return false; }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function readSelfEntry(agentRoot) {
|
|
160
|
+
return readJson(join(orgDir(agentRoot), "self.json"));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Read the merged org directory: peers from known-agents.json, enriched with any
|
|
165
|
+
* synced peer entries under state/org/peers/, plus this agent's self entry. Each
|
|
166
|
+
* entry is shape-normalized to at least { id, name, fullName, title, slackId,
|
|
167
|
+
* role/archetype }. Newest/self first.
|
|
168
|
+
*/
|
|
169
|
+
export function readDirectory(agentRoot) {
|
|
170
|
+
const byId = new Map();
|
|
171
|
+
// Peers from the static known-agents registry (name/role/slackId).
|
|
172
|
+
for (const p of readPeers(agentRoot)) {
|
|
173
|
+
const id = p.repo || p.name || p.slackId;
|
|
174
|
+
if (!id) continue;
|
|
175
|
+
byId.set(String(id).toLowerCase(), {
|
|
176
|
+
id: String(id).toLowerCase(), name: p.name || "", fullName: p.fullName || "",
|
|
177
|
+
title: p.role || "", slackId: p.slackId || "", repo: p.repo || "", source: "known-agents",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// Richer synced peer entries (e.g. from cohort) override the static ones.
|
|
181
|
+
const peersDir = join(orgDir(agentRoot), "peers");
|
|
182
|
+
if (existsSync(peersDir)) {
|
|
183
|
+
try {
|
|
184
|
+
for (const f of readdirSync(peersDir).filter((x) => x.endsWith(".json"))) {
|
|
185
|
+
const e = readJson(join(peersDir, f));
|
|
186
|
+
if (e && e.id) byId.set(String(e.id).toLowerCase(), { ...e, source: e.source || "synced" });
|
|
187
|
+
}
|
|
188
|
+
} catch { /* ignore */ }
|
|
189
|
+
}
|
|
190
|
+
// Self entry last so it overrides any peer copy of itself.
|
|
191
|
+
const self = readSelfEntry(agentRoot);
|
|
192
|
+
if (self && self.id) byId.set(String(self.id).toLowerCase(), { ...self, self: true });
|
|
193
|
+
return [...byId.values()];
|
|
194
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registry.test.mjs — tests for the file-backed org registry.
|
|
3
|
+
* Run: node --test lib/org/registry.test.mjs
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
import { test } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
buildSelfEntry, currentFocus, writeSelfEntry, readSelfEntry, readDirectory,
|
|
15
|
+
} from "./registry.mjs";
|
|
16
|
+
|
|
17
|
+
function tmp() { return mkdtempSync(join(tmpdir(), "org-registry-")); }
|
|
18
|
+
function repo() {
|
|
19
|
+
const root = tmp();
|
|
20
|
+
mkdirSync(join(root, "config"), { recursive: true });
|
|
21
|
+
return root;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test("buildSelfEntry: maps identity + company + profile + focus", () => {
|
|
25
|
+
const entry = buildSelfEntry({
|
|
26
|
+
identity: {
|
|
27
|
+
firstName: "Alex", fullName: "Alex Chen", title: "Chief of Staff",
|
|
28
|
+
email: "alex@example.com", slackMemberId: "U1", repoSlug: "alex-ai",
|
|
29
|
+
function: "executive-operator", altitude: "c-suite",
|
|
30
|
+
principal: { fullName: "Sam Smith", title: "CEO" },
|
|
31
|
+
},
|
|
32
|
+
company: { name: "Northwind", domain: "northwind.example.com" },
|
|
33
|
+
profile: { label: "C-Suite Executive Operator", towers: [{ id: "t1", name: "Strategic Initiatives", focus: "x" }] },
|
|
34
|
+
focus: [{ title: "Ship Q3 plan", queue: "action-stack", priority: "high" }],
|
|
35
|
+
nowMs: Date.parse("2026-06-03T12:00:00Z"),
|
|
36
|
+
});
|
|
37
|
+
assert.equal(entry.id, "alex-ai");
|
|
38
|
+
assert.equal(entry.fullName, "Alex Chen");
|
|
39
|
+
assert.equal(entry.archetype.function, "executive-operator");
|
|
40
|
+
assert.equal(entry.archetype.label, "C-Suite Executive Operator");
|
|
41
|
+
assert.equal(entry.towers.length, 1);
|
|
42
|
+
assert.equal(entry.towers[0].name, "Strategic Initiatives");
|
|
43
|
+
assert.equal(entry.focus[0].title, "Ship Q3 plan");
|
|
44
|
+
assert.equal(entry.principal.fullName, "Sam Smith");
|
|
45
|
+
assert.equal(entry.company.name, "Northwind");
|
|
46
|
+
assert.equal(entry.updatedAt, "2026-06-03T12:00:00.000Z");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("buildSelfEntry: derives repo + id from firstName when no repoSlug", () => {
|
|
50
|
+
const entry = buildSelfEntry({ identity: { firstName: "Robin" } });
|
|
51
|
+
assert.equal(entry.repo, "robin-ai");
|
|
52
|
+
assert.equal(entry.id, "robin-ai");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("currentFocus: uses injected items, capped", async () => {
|
|
56
|
+
const items = Array.from({ length: 9 }, (_, i) => ({ title: `t${i}`, queue: "q", priority: "normal" }));
|
|
57
|
+
const focus = await currentFocus("/x", { items, limit: 3 });
|
|
58
|
+
assert.equal(focus.length, 3);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("currentFocus: reads + filters queue YAML (open only)", async () => {
|
|
62
|
+
const root = repo();
|
|
63
|
+
try {
|
|
64
|
+
mkdirSync(join(root, "state", "queues"), { recursive: true });
|
|
65
|
+
writeFileSync(join(root, "state", "queues", "action-stack.yaml"),
|
|
66
|
+
"items:\n - title: Open one\n status: open\n priority: high\n - title: Done one\n status: done\n");
|
|
67
|
+
const focus = await currentFocus(root, { queues: ["action-stack"], limit: 5 });
|
|
68
|
+
assert.equal(focus.length, 1);
|
|
69
|
+
assert.equal(focus[0].title, "Open one");
|
|
70
|
+
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("writeSelfEntry/readSelfEntry: round-trip", () => {
|
|
74
|
+
const root = repo();
|
|
75
|
+
try {
|
|
76
|
+
const e = buildSelfEntry({ identity: { firstName: "Alex", repoSlug: "alex-ai" } });
|
|
77
|
+
assert.equal(writeSelfEntry(root, e), true);
|
|
78
|
+
assert.equal(readSelfEntry(root).id, "alex-ai");
|
|
79
|
+
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("readDirectory: merges known-agents peers + self; self + synced override", () => {
|
|
83
|
+
const root = repo();
|
|
84
|
+
try {
|
|
85
|
+
writeFileSync(join(root, "config", "known-agents.json"), JSON.stringify({
|
|
86
|
+
agents: [{ name: "Bob", fullName: "Bob Lee", slackId: "U2", role: "Head of Eng", repo: "bob-ai" }],
|
|
87
|
+
}));
|
|
88
|
+
// synced richer peer overrides the static known-agents entry
|
|
89
|
+
mkdirSync(join(root, "state", "org", "peers"), { recursive: true });
|
|
90
|
+
writeFileSync(join(root, "state", "org", "peers", "bob.json"), JSON.stringify({ id: "bob-ai", name: "Bob", title: "VP Engineering", source: "cohort" }));
|
|
91
|
+
writeSelfEntry(root, buildSelfEntry({ identity: { firstName: "Alex", repoSlug: "alex-ai", title: "CoS" } }));
|
|
92
|
+
const dir = readDirectory(root);
|
|
93
|
+
const bob = dir.find((d) => d.id === "bob-ai");
|
|
94
|
+
const alex = dir.find((d) => d.id === "alex-ai");
|
|
95
|
+
assert.equal(bob.title, "VP Engineering", "synced peer overrides known-agents role");
|
|
96
|
+
assert.equal(bob.source, "cohort");
|
|
97
|
+
assert.equal(alex.self, true, "self entry flagged");
|
|
98
|
+
assert.equal(dir.length, 2);
|
|
99
|
+
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
100
|
+
});
|