@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,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for lib/hooks/bus.mjs — the typed lifecycle hook bus.
|
|
3
|
+
*
|
|
4
|
+
* Covers the contract the audit (gaps-product-quality P1-8) specifies:
|
|
5
|
+
* - ordered priority dispatch (higher priority first, FIFO within a band)
|
|
6
|
+
* - a beforeSend subscriber can cancel the chain with a reason
|
|
7
|
+
* - a throwing subscriber is isolated (others still run; chain completes)
|
|
8
|
+
* - async subscribers are awaited in order
|
|
9
|
+
* - an unknown event type is rejected at subscribe AND emit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it } from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
HookBus,
|
|
17
|
+
createHookBus,
|
|
18
|
+
getHookBus,
|
|
19
|
+
resetHookBus,
|
|
20
|
+
HOOK_EVENT_NAMES,
|
|
21
|
+
HOOK_EVENTS,
|
|
22
|
+
isHookEvent,
|
|
23
|
+
isCancellableEvent,
|
|
24
|
+
} from "./bus.mjs";
|
|
25
|
+
|
|
26
|
+
/** A bus with an in-memory logger so error reporting is observable + silent. */
|
|
27
|
+
function busWithLog() {
|
|
28
|
+
const logged = [];
|
|
29
|
+
const bus = createHookBus({ logger: (e) => logged.push(e) });
|
|
30
|
+
return { bus, logged };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("HookBus — closed event set", () => {
|
|
34
|
+
it("exposes exactly the eight specified lifecycle events", () => {
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
[...HOOK_EVENT_NAMES].sort(),
|
|
37
|
+
[
|
|
38
|
+
"afterSend",
|
|
39
|
+
"beforeSend",
|
|
40
|
+
"onClassify",
|
|
41
|
+
"onDispatch",
|
|
42
|
+
"onError",
|
|
43
|
+
"onGuardFail",
|
|
44
|
+
"onSessionClose",
|
|
45
|
+
"onSkillAuthored",
|
|
46
|
+
]
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("marks only beforeSend as cancellable", () => {
|
|
51
|
+
assert.equal(isCancellableEvent("beforeSend"), true);
|
|
52
|
+
for (const e of HOOK_EVENT_NAMES) {
|
|
53
|
+
if (e === "beforeSend") continue;
|
|
54
|
+
assert.equal(isCancellableEvent(e), false, `${e} must not be cancellable`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("isHookEvent recognises known names and rejects others", () => {
|
|
59
|
+
assert.equal(isHookEvent("beforeSend"), true);
|
|
60
|
+
assert.equal(isHookEvent("nope"), false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("HookBus — ordered priority dispatch", () => {
|
|
65
|
+
it("runs subscribers in descending priority order", async () => {
|
|
66
|
+
const { bus } = busWithLog();
|
|
67
|
+
const order = [];
|
|
68
|
+
bus.subscribe("onClassify", () => order.push("low"), { priority: 1 });
|
|
69
|
+
bus.subscribe("onClassify", () => order.push("high"), { priority: 10 });
|
|
70
|
+
bus.subscribe("onClassify", () => order.push("mid"), { priority: 5 });
|
|
71
|
+
await bus.emit("onClassify", {});
|
|
72
|
+
assert.deepEqual(order, ["high", "mid", "low"]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("breaks priority ties by registration order (FIFO)", async () => {
|
|
76
|
+
const { bus } = busWithLog();
|
|
77
|
+
const order = [];
|
|
78
|
+
bus.subscribe("onDispatch", () => order.push("a"), { priority: 5 });
|
|
79
|
+
bus.subscribe("onDispatch", () => order.push("b"), { priority: 5 });
|
|
80
|
+
bus.subscribe("onDispatch", () => order.push("c"), { priority: 5 });
|
|
81
|
+
await bus.emit("onDispatch", {});
|
|
82
|
+
assert.deepEqual(order, ["a", "b", "c"]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("passes the same ctx object to every subscriber", async () => {
|
|
86
|
+
const { bus } = busWithLog();
|
|
87
|
+
const ctx = { tool: "slack_send", text: "hi" };
|
|
88
|
+
const seen = [];
|
|
89
|
+
bus.subscribe("afterSend", (c) => seen.push(c));
|
|
90
|
+
bus.subscribe("afterSend", (c) => seen.push(c));
|
|
91
|
+
await bus.emit("afterSend", ctx);
|
|
92
|
+
assert.equal(seen.length, 2);
|
|
93
|
+
assert.equal(seen[0], ctx);
|
|
94
|
+
assert.equal(seen[1], ctx);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("returns a not-cancelled decision for observe-only events", async () => {
|
|
98
|
+
const { bus } = busWithLog();
|
|
99
|
+
bus.subscribe("onGuardFail", () => ({ cancel: true, reason: "ignored" }));
|
|
100
|
+
const res = await bus.emit("onGuardFail", {});
|
|
101
|
+
// cancel returns are ignored for non-cancellable events.
|
|
102
|
+
assert.deepEqual(res, { cancelled: false, reason: null, by: null, errors: 0 });
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("HookBus — cancellable beforeSend chain", () => {
|
|
107
|
+
it("a subscriber cancels the chain with a reason and short-circuits", async () => {
|
|
108
|
+
const { bus } = busWithLog();
|
|
109
|
+
const ran = [];
|
|
110
|
+
bus.subscribe("beforeSend", () => { ran.push("gate-a"); }, { priority: 10 });
|
|
111
|
+
bus.subscribe("beforeSend", () => {
|
|
112
|
+
ran.push("send-gate");
|
|
113
|
+
return { cancel: true, reason: "banned-phrase" };
|
|
114
|
+
}, { priority: 5 });
|
|
115
|
+
bus.subscribe("beforeSend", () => { ran.push("should-not-run"); }, { priority: 1 });
|
|
116
|
+
|
|
117
|
+
const res = await bus.emit("beforeSend", { text: "Absolutely!" });
|
|
118
|
+
assert.equal(res.cancelled, true);
|
|
119
|
+
assert.equal(res.reason, "banned-phrase");
|
|
120
|
+
assert.equal(res.by, 1); // index of the canceller in priority order
|
|
121
|
+
assert.deepEqual(ran, ["gate-a", "send-gate"]); // third never ran
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("an async subscriber can cancel the chain", async () => {
|
|
125
|
+
const { bus } = busWithLog();
|
|
126
|
+
const res = await (async () => {
|
|
127
|
+
bus.subscribe("beforeSend", async () => {
|
|
128
|
+
await Promise.resolve();
|
|
129
|
+
return { cancel: true, reason: "ownership-backstop" };
|
|
130
|
+
});
|
|
131
|
+
return bus.emit("beforeSend", {});
|
|
132
|
+
})();
|
|
133
|
+
assert.equal(res.cancelled, true);
|
|
134
|
+
assert.equal(res.reason, "ownership-backstop");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("defaults the reason when a canceller omits one", async () => {
|
|
138
|
+
const { bus } = busWithLog();
|
|
139
|
+
bus.subscribe("beforeSend", () => ({ cancel: true }));
|
|
140
|
+
const res = await bus.emit("beforeSend", {});
|
|
141
|
+
assert.equal(res.cancelled, true);
|
|
142
|
+
assert.equal(res.reason, "cancelled");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("does NOT cancel when no subscriber vetoes", async () => {
|
|
146
|
+
const { bus } = busWithLog();
|
|
147
|
+
bus.subscribe("beforeSend", () => undefined);
|
|
148
|
+
bus.subscribe("beforeSend", () => ({ cancel: false }));
|
|
149
|
+
bus.subscribe("beforeSend", () => "non-object-return");
|
|
150
|
+
const res = await bus.emit("beforeSend", {});
|
|
151
|
+
assert.equal(res.cancelled, false);
|
|
152
|
+
assert.equal(res.reason, null);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("HookBus — error isolation (fail-open)", () => {
|
|
157
|
+
it("isolates a throwing subscriber; later subscribers still run", async () => {
|
|
158
|
+
const { bus, logged } = busWithLog();
|
|
159
|
+
const ran = [];
|
|
160
|
+
bus.subscribe("onClassify", () => ran.push("first"), { priority: 10 });
|
|
161
|
+
bus.subscribe("onClassify", () => { throw new Error("boom"); }, { priority: 5 });
|
|
162
|
+
bus.subscribe("onClassify", () => ran.push("third"), { priority: 1 });
|
|
163
|
+
|
|
164
|
+
const res = await bus.emit("onClassify", {});
|
|
165
|
+
assert.deepEqual(ran, ["first", "third"]);
|
|
166
|
+
assert.equal(res.errors, 1);
|
|
167
|
+
assert.equal(res.cancelled, false);
|
|
168
|
+
// the error was reported to the logger
|
|
169
|
+
const errRows = logged.filter((e) => e.stage === "hook_subscriber_error");
|
|
170
|
+
assert.equal(errRows.length, 1);
|
|
171
|
+
assert.equal(errRows[0].event, "onClassify");
|
|
172
|
+
assert.match(errRows[0].error, /boom/);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("isolates a rejecting async subscriber", async () => {
|
|
176
|
+
const { bus, logged } = busWithLog();
|
|
177
|
+
const ran = [];
|
|
178
|
+
bus.subscribe("onDispatch", async () => { throw new Error("async-boom"); });
|
|
179
|
+
bus.subscribe("onDispatch", () => ran.push("after"));
|
|
180
|
+
const res = await bus.emit("onDispatch", {});
|
|
181
|
+
assert.deepEqual(ran, ["after"]);
|
|
182
|
+
assert.equal(res.errors, 1);
|
|
183
|
+
assert.equal(logged.filter((e) => e.stage === "hook_subscriber_error").length, 1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("a throwing subscriber in a cancellable chain does NOT cancel the send", async () => {
|
|
187
|
+
const { bus } = busWithLog();
|
|
188
|
+
const ran = [];
|
|
189
|
+
bus.subscribe("beforeSend", () => { throw new Error("gate crashed"); }, { priority: 10 });
|
|
190
|
+
bus.subscribe("beforeSend", () => ran.push("downstream"), { priority: 1 });
|
|
191
|
+
const res = await bus.emit("beforeSend", {});
|
|
192
|
+
// fail-open: a buggy gate must not silently drop traffic
|
|
193
|
+
assert.equal(res.cancelled, false);
|
|
194
|
+
assert.equal(res.errors, 1);
|
|
195
|
+
assert.deepEqual(ran, ["downstream"]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("a throwing subscriber re-emits onError without recursing forever", async () => {
|
|
199
|
+
const { bus, logged } = busWithLog();
|
|
200
|
+
const onErrorSeen = [];
|
|
201
|
+
bus.subscribe("onError", (ctx) => { onErrorSeen.push(ctx); });
|
|
202
|
+
bus.subscribe("onSessionClose", () => { throw new Error("close-boom"); });
|
|
203
|
+
await bus.emit("onSessionClose", {});
|
|
204
|
+
// let the fire-and-forget onError re-emit settle
|
|
205
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
206
|
+
assert.equal(onErrorSeen.length, 1);
|
|
207
|
+
assert.equal(onErrorSeen[0].sourceEvent, "onSessionClose");
|
|
208
|
+
assert.match(onErrorSeen[0].error, /close-boom/);
|
|
209
|
+
// exactly one subscriber-error row (the original) — onError did not recurse
|
|
210
|
+
assert.equal(logged.filter((e) => e.stage === "hook_subscriber_error").length, 1);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("a throwing onError subscriber is logged but does not loop", async () => {
|
|
214
|
+
const { bus, logged } = busWithLog();
|
|
215
|
+
bus.subscribe("onError", () => { throw new Error("handler-boom"); });
|
|
216
|
+
bus.subscribe("onClassify", () => { throw new Error("orig"); });
|
|
217
|
+
await bus.emit("onClassify", {});
|
|
218
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
219
|
+
// original error + the onError handler's own throw == 2 rows, then it stops.
|
|
220
|
+
const rows = logged.filter((e) => e.stage === "hook_subscriber_error");
|
|
221
|
+
assert.equal(rows.length, 2);
|
|
222
|
+
assert.deepEqual(rows.map((r) => r.event).sort(), ["onClassify", "onError"]);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("a broken logger never breaks the bus", async () => {
|
|
226
|
+
const bus = createHookBus({ logger: () => { throw new Error("logger down"); } });
|
|
227
|
+
const ran = [];
|
|
228
|
+
bus.subscribe("onClassify", () => { throw new Error("x"); });
|
|
229
|
+
bus.subscribe("onClassify", () => ran.push("survived"));
|
|
230
|
+
const res = await bus.emit("onClassify", {});
|
|
231
|
+
assert.deepEqual(ran, ["survived"]);
|
|
232
|
+
assert.equal(res.errors, 1);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("HookBus — async ordering", () => {
|
|
237
|
+
it("awaits async subscribers in priority order before the next runs", async () => {
|
|
238
|
+
const { bus } = busWithLog();
|
|
239
|
+
const order = [];
|
|
240
|
+
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
241
|
+
// higher priority deliberately SLOWER — must still complete before the
|
|
242
|
+
// lower-priority one starts, proving emit awaits in order.
|
|
243
|
+
bus.subscribe("onDispatch", async () => {
|
|
244
|
+
order.push("high-start");
|
|
245
|
+
await delay(20);
|
|
246
|
+
order.push("high-end");
|
|
247
|
+
}, { priority: 10 });
|
|
248
|
+
bus.subscribe("onDispatch", async () => {
|
|
249
|
+
order.push("low-start");
|
|
250
|
+
await delay(1);
|
|
251
|
+
order.push("low-end");
|
|
252
|
+
}, { priority: 1 });
|
|
253
|
+
await bus.emit("onDispatch", {});
|
|
254
|
+
assert.deepEqual(order, ["high-start", "high-end", "low-start", "low-end"]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("mixes sync and async subscribers, preserving order", async () => {
|
|
258
|
+
const { bus } = busWithLog();
|
|
259
|
+
const order = [];
|
|
260
|
+
bus.subscribe("afterSend", () => order.push("sync-1"), { priority: 3 });
|
|
261
|
+
bus.subscribe("afterSend", async () => {
|
|
262
|
+
await Promise.resolve();
|
|
263
|
+
order.push("async-2");
|
|
264
|
+
}, { priority: 2 });
|
|
265
|
+
bus.subscribe("afterSend", () => order.push("sync-3"), { priority: 1 });
|
|
266
|
+
await bus.emit("afterSend", {});
|
|
267
|
+
assert.deepEqual(order, ["sync-1", "async-2", "sync-3"]);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("a cancel in an async chain short-circuits subsequent subscribers", async () => {
|
|
271
|
+
const { bus } = busWithLog();
|
|
272
|
+
const ran = [];
|
|
273
|
+
bus.subscribe("beforeSend", async () => {
|
|
274
|
+
await Promise.resolve();
|
|
275
|
+
ran.push("first");
|
|
276
|
+
return { cancel: true, reason: "stop" };
|
|
277
|
+
}, { priority: 10 });
|
|
278
|
+
bus.subscribe("beforeSend", () => ran.push("second"), { priority: 1 });
|
|
279
|
+
const res = await bus.emit("beforeSend", {});
|
|
280
|
+
assert.equal(res.cancelled, true);
|
|
281
|
+
assert.deepEqual(ran, ["first"]);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("HookBus — unknown event rejection", () => {
|
|
286
|
+
it("rejects subscribe to an unknown event", () => {
|
|
287
|
+
const { bus } = busWithLog();
|
|
288
|
+
assert.throws(() => bus.subscribe("notAnEvent", () => {}), /unknown event "notAnEvent"/);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("rejects emit of an unknown event", async () => {
|
|
292
|
+
const { bus } = busWithLog();
|
|
293
|
+
await assert.rejects(() => bus.emit("notAnEvent", {}), /unknown event "notAnEvent"/);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("rejects unsubscribe/count/clear of an unknown event", () => {
|
|
297
|
+
const { bus } = busWithLog();
|
|
298
|
+
assert.throws(() => bus.unsubscribe("nope", () => {}), /unknown event/);
|
|
299
|
+
assert.throws(() => bus.count("nope"), /unknown event/);
|
|
300
|
+
assert.throws(() => bus.clear("nope"), /unknown event/);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("rejects a non-function handler", () => {
|
|
304
|
+
const { bus } = busWithLog();
|
|
305
|
+
assert.throws(() => bus.subscribe("beforeSend", 42), /must be a function/);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe("HookBus — unsubscribe", () => {
|
|
310
|
+
it("the handle returned by subscribe removes the subscriber", async () => {
|
|
311
|
+
const { bus } = busWithLog();
|
|
312
|
+
const ran = [];
|
|
313
|
+
const off = bus.subscribe("onClassify", () => ran.push("x"));
|
|
314
|
+
assert.equal(bus.count("onClassify"), 1);
|
|
315
|
+
off();
|
|
316
|
+
assert.equal(bus.count("onClassify"), 0);
|
|
317
|
+
await bus.emit("onClassify", {});
|
|
318
|
+
assert.deepEqual(ran, []);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("the unsubscribe handle is idempotent", () => {
|
|
322
|
+
const { bus } = busWithLog();
|
|
323
|
+
const fn = () => {};
|
|
324
|
+
const off = bus.subscribe("onClassify", fn);
|
|
325
|
+
// re-register the same fn so we can prove the handle removes only its own.
|
|
326
|
+
bus.subscribe("onClassify", fn);
|
|
327
|
+
off();
|
|
328
|
+
off(); // second call is a no-op
|
|
329
|
+
assert.equal(bus.count("onClassify"), 1);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("unsubscribe(event, fn) removes the first matching registration", () => {
|
|
333
|
+
const { bus } = busWithLog();
|
|
334
|
+
const fn = () => {};
|
|
335
|
+
bus.subscribe("onClassify", fn);
|
|
336
|
+
assert.equal(bus.unsubscribe("onClassify", fn), true);
|
|
337
|
+
assert.equal(bus.unsubscribe("onClassify", fn), false);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("unsubscribing during emit does not perturb the in-flight chain", async () => {
|
|
341
|
+
const { bus } = busWithLog();
|
|
342
|
+
const ran = [];
|
|
343
|
+
let off3;
|
|
344
|
+
bus.subscribe("onClassify", () => { ran.push("1"); off3(); }, { priority: 3 });
|
|
345
|
+
bus.subscribe("onClassify", () => ran.push("2"), { priority: 2 });
|
|
346
|
+
off3 = bus.subscribe("onClassify", () => ran.push("3"), { priority: 1 });
|
|
347
|
+
await bus.emit("onClassify", {});
|
|
348
|
+
// 3 was removed mid-emit but the snapshot still ran it this round.
|
|
349
|
+
assert.deepEqual(ran, ["1", "2", "3"]);
|
|
350
|
+
// next emit reflects the removal.
|
|
351
|
+
ran.length = 0;
|
|
352
|
+
await bus.emit("onClassify", {});
|
|
353
|
+
assert.deepEqual(ran, ["1", "2"]);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe("HookBus — factory + singleton", () => {
|
|
358
|
+
it("createHookBus returns isolated instances", async () => {
|
|
359
|
+
const a = createHookBus({ logger: () => {} });
|
|
360
|
+
const b = createHookBus({ logger: () => {} });
|
|
361
|
+
assert.notEqual(a, b);
|
|
362
|
+
const ranA = [];
|
|
363
|
+
a.subscribe("onClassify", () => ranA.push("a"));
|
|
364
|
+
await b.emit("onClassify", {});
|
|
365
|
+
assert.deepEqual(ranA, []); // b's emit never touched a's subscribers
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("getHookBus returns the same instance across calls", () => {
|
|
369
|
+
resetHookBus();
|
|
370
|
+
const first = getHookBus({ logger: () => {} });
|
|
371
|
+
const second = getHookBus();
|
|
372
|
+
assert.equal(first, second);
|
|
373
|
+
resetHookBus();
|
|
374
|
+
const third = getHookBus({ logger: () => {} });
|
|
375
|
+
assert.notEqual(first, third);
|
|
376
|
+
resetHookBus();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("is an instance of HookBus", () => {
|
|
380
|
+
assert.ok(createHookBus() instanceof HookBus);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("HOOK_EVENTS is frozen (closed set is immutable)", () => {
|
|
384
|
+
assert.ok(Object.isFrozen(HOOK_EVENTS));
|
|
385
|
+
assert.ok(Object.isFrozen(HOOK_EVENT_NAMES));
|
|
386
|
+
});
|
|
387
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cohortapp/agent-sdk — Main entry point
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all public modules for convenient consumption:
|
|
5
|
+
* import { getToolsForAccessLevel, executeAction } from "@cohortapp/agent-sdk";
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
getToolsForAccessLevel,
|
|
10
|
+
getToolNamesForAccessLevel,
|
|
11
|
+
getAllToolsByCategory,
|
|
12
|
+
} from "./tool-definitions.js";
|
|
13
|
+
|
|
14
|
+
export { executeAction } from "./action-executor.js";
|
|
15
|
+
|
|
16
|
+
export { acquireLock, releaseLock, checkLock } from "./singleton.js";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/learning/config.mjs — config loader for the self-learning loop (WS3).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors lib/collective/config.mjs: pure, dependency-light, js-yaml loaded
|
|
5
|
+
* lazily, FAIL-OPEN (these run inside global hooks on every session machine-
|
|
6
|
+
* wide, so a throw here would break unrelated sessions — we return defaults).
|
|
7
|
+
*
|
|
8
|
+
* config/learning.yaml only needs the keys you want to override; everything
|
|
9
|
+
* below is the built-in default. The repo denylist is INHERITED from the
|
|
10
|
+
* collective config (the same ring-fenced repos must not feed the learning loop
|
|
11
|
+
* either) and may be extended here.
|
|
12
|
+
*
|
|
13
|
+
* @module lib/learning/config
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
|
|
21
|
+
/** Built-in defaults. Mirrors scaffold/config/learning.yaml. */
|
|
22
|
+
export const DEFAULTS = Object.freeze({
|
|
23
|
+
enabled: true,
|
|
24
|
+
minTurns: 4, // Pass B gate: skip sessions shorter than this
|
|
25
|
+
reviewModel: "sonnet", // self-improvement reviewer model (never the main model)
|
|
26
|
+
nudgeToolUses: 40, // raise the skill prior past this many tool-uses
|
|
27
|
+
nudgeSessions: 8, // raise the skill prior past this many sessions
|
|
28
|
+
staleDays: 30, // active → stale after this many days idle
|
|
29
|
+
archiveDays: 90, // → archived after this many days idle
|
|
30
|
+
repoDenylist: [], // extends the inherited collective denylist
|
|
31
|
+
// Phase-2 curator: periodically merge narrowly-overlapping agent-authored
|
|
32
|
+
// skills into class-level umbrellas via one bounded LLM call, run as a
|
|
33
|
+
// DETACHED worker from the skill-curator cadence. Fail-open defaults.
|
|
34
|
+
consolidation: Object.freeze({
|
|
35
|
+
enabled: true, // turn the umbrella-consolidation pass on/off
|
|
36
|
+
minSkills: 5, // don't spawn the LLM below this many agent-authored active skills
|
|
37
|
+
intervalDays: 14, // minimum days between consolidation passes
|
|
38
|
+
model: "sonnet", // reviewer model for the clustering call
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const CONFIG_REL = "config/learning.yaml";
|
|
43
|
+
|
|
44
|
+
function clone(v) { return JSON.parse(JSON.stringify(v)); }
|
|
45
|
+
|
|
46
|
+
/** Last-wins shallow-ish merge (objects deep-merge, arrays/scalars replace). */
|
|
47
|
+
function merge(base, override) {
|
|
48
|
+
if (override == null) return clone(base);
|
|
49
|
+
if (Array.isArray(base) || Array.isArray(override)) return clone(override);
|
|
50
|
+
if (typeof base !== "object" || typeof override !== "object") return clone(override);
|
|
51
|
+
const out = clone(base);
|
|
52
|
+
for (const k of Object.keys(override)) {
|
|
53
|
+
const b = out[k], o = override[k];
|
|
54
|
+
if (b && o && typeof b === "object" && typeof o === "object" && !Array.isArray(b) && !Array.isArray(o)) out[k] = merge(b, o);
|
|
55
|
+
else if (o !== undefined) out[k] = clone(o);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let _yaml = null;
|
|
61
|
+
async function getYaml() {
|
|
62
|
+
if (_yaml) return _yaml;
|
|
63
|
+
const mod = await import("js-yaml");
|
|
64
|
+
_yaml = mod.default || mod;
|
|
65
|
+
return _yaml;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load the learning config for an agent root, merged over DEFAULTS. Optionally
|
|
70
|
+
* inherits `repoDenylist` from an already-loaded collective config. Returns a
|
|
71
|
+
* fresh DEFAULTS clone when the root is unknown or the file is missing/bad.
|
|
72
|
+
*
|
|
73
|
+
* @param {string|null} agentRoot
|
|
74
|
+
* @param {object} [collectiveCfg] already-loaded collective config (for denylist inheritance)
|
|
75
|
+
* @returns {Promise<object>}
|
|
76
|
+
*/
|
|
77
|
+
export async function loadLearningConfig(agentRoot, collectiveCfg) {
|
|
78
|
+
const base = clone(DEFAULTS);
|
|
79
|
+
// Inherit the collective denylist as the floor.
|
|
80
|
+
if (collectiveCfg && Array.isArray(collectiveCfg.repoDenylist)) {
|
|
81
|
+
base.repoDenylist = clone(collectiveCfg.repoDenylist);
|
|
82
|
+
}
|
|
83
|
+
if (!agentRoot) return base;
|
|
84
|
+
const p = join(agentRoot, CONFIG_REL);
|
|
85
|
+
if (!existsSync(p)) return base;
|
|
86
|
+
try {
|
|
87
|
+
const yaml = await getYaml();
|
|
88
|
+
const raw = yaml.load(readFileSync(p, "utf8"));
|
|
89
|
+
if (!raw || typeof raw !== "object") return base;
|
|
90
|
+
const merged = merge(base, raw);
|
|
91
|
+
// Union the inherited + file denylists so the file extends, never shrinks, the floor.
|
|
92
|
+
if (collectiveCfg && Array.isArray(collectiveCfg.repoDenylist)) {
|
|
93
|
+
merged.repoDenylist = Array.from(new Set([...(collectiveCfg.repoDenylist || []), ...(merged.repoDenylist || [])]));
|
|
94
|
+
}
|
|
95
|
+
return merged;
|
|
96
|
+
} catch {
|
|
97
|
+
return base;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Is the learning loop enabled for this config? */
|
|
102
|
+
export function isLearningEnabled(cfg) {
|
|
103
|
+
return !!cfg && cfg.enabled !== false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default { DEFAULTS, loadLearningConfig, isLearningEnabled };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config.test.mjs — learning config loader tests.
|
|
3
|
+
* Run: node --test lib/learning/config.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 } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { loadLearningConfig, isLearningEnabled, DEFAULTS } from "./config.mjs";
|
|
14
|
+
|
|
15
|
+
function tmpRoot() {
|
|
16
|
+
const root = mkdtempSync(join(tmpdir(), "maestro-learncfg-"));
|
|
17
|
+
mkdirSync(join(root, "config"), { recursive: true });
|
|
18
|
+
return root;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test("loadLearningConfig: defaults when no file / no root", async () => {
|
|
22
|
+
assert.deepEqual(await loadLearningConfig(null), DEFAULTS);
|
|
23
|
+
const root = tmpRoot();
|
|
24
|
+
const cfg = await loadLearningConfig(root);
|
|
25
|
+
assert.equal(cfg.minTurns, 4);
|
|
26
|
+
assert.equal(cfg.reviewModel, "sonnet");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("loadLearningConfig: file overrides merge over defaults", async () => {
|
|
30
|
+
const root = tmpRoot();
|
|
31
|
+
writeFileSync(join(root, "config", "learning.yaml"), "minTurns: 6\nreviewModel: sonnet\nenabled: false\n");
|
|
32
|
+
const cfg = await loadLearningConfig(root);
|
|
33
|
+
assert.equal(cfg.minTurns, 6);
|
|
34
|
+
assert.equal(cfg.enabled, false);
|
|
35
|
+
assert.equal(isLearningEnabled(cfg), false);
|
|
36
|
+
// Untouched keys keep their defaults.
|
|
37
|
+
assert.equal(cfg.archiveDays, 90);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("loadLearningConfig: inherits + unions the collective denylist", async () => {
|
|
41
|
+
const root = tmpRoot();
|
|
42
|
+
writeFileSync(join(root, "config", "learning.yaml"), "repoDenylist: [extra-repo]\n");
|
|
43
|
+
const cfg = await loadLearningConfig(root, { repoDenylist: ["client-work", "secrets"] });
|
|
44
|
+
assert.ok(cfg.repoDenylist.includes("client-work"));
|
|
45
|
+
assert.ok(cfg.repoDenylist.includes("secrets"));
|
|
46
|
+
assert.ok(cfg.repoDenylist.includes("extra-repo"), "file extends the inherited floor");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("loadLearningConfig: consolidation defaults present (enabled / 5 / 14 / sonnet)", async () => {
|
|
50
|
+
const root = tmpRoot();
|
|
51
|
+
const cfg = await loadLearningConfig(root);
|
|
52
|
+
assert.equal(cfg.consolidation.enabled, true);
|
|
53
|
+
assert.equal(cfg.consolidation.minSkills, 5);
|
|
54
|
+
assert.equal(cfg.consolidation.intervalDays, 14);
|
|
55
|
+
assert.equal(cfg.consolidation.model, "sonnet");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("loadLearningConfig: consolidation overrides deep-merge (keep untouched sub-keys)", async () => {
|
|
59
|
+
const root = tmpRoot();
|
|
60
|
+
writeFileSync(join(root, "config", "learning.yaml"), "consolidation:\n enabled: false\n minSkills: 3\n");
|
|
61
|
+
const cfg = await loadLearningConfig(root);
|
|
62
|
+
assert.equal(cfg.consolidation.enabled, false);
|
|
63
|
+
assert.equal(cfg.consolidation.minSkills, 3);
|
|
64
|
+
// intervalDays / model untouched → defaults.
|
|
65
|
+
assert.equal(cfg.consolidation.intervalDays, 14);
|
|
66
|
+
assert.equal(cfg.consolidation.model, "sonnet");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("loadLearningConfig: corrupt yaml fails open to defaults+inheritance", async () => {
|
|
70
|
+
const root = tmpRoot();
|
|
71
|
+
writeFileSync(join(root, "config", "learning.yaml"), "minTurns: [unterminated");
|
|
72
|
+
const cfg = await loadLearningConfig(root, { repoDenylist: ["ring-fenced"] });
|
|
73
|
+
assert.equal(cfg.minTurns, 4);
|
|
74
|
+
assert.deepEqual(cfg.repoDenylist, ["ring-fenced"]);
|
|
75
|
+
});
|