@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,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/model-router/economics.mjs — the router ECONOMICS layer (SPEC §8 + §6.4/§6.5).
|
|
3
|
+
*
|
|
4
|
+
* Five pure, role-agnostic cost levers that bolt onto the resolveChain substrate
|
|
5
|
+
* (resolve.mjs + catalog.mjs + ledger.mjs) WITHOUT re-routing a live session —
|
|
6
|
+
* routing happens at dispatch/spawn time only, never mid-session (cache prefixes
|
|
7
|
+
* are model-scoped; a mid-session switch silently re-bills the entire prefix, G8).
|
|
8
|
+
*
|
|
9
|
+
* 1. budgetLadder(spentUSD, capUSD) -> { band:0|75|90|100, degrade }
|
|
10
|
+
* A LADDER, not a cliff (§6.4):
|
|
11
|
+
* <75% → policy as written
|
|
12
|
+
* ≥75% → prefer cheaper tiers (downgrade one rung)
|
|
13
|
+
* ≥90% → cheap-only + batch deferrable
|
|
14
|
+
* 100% → essential-only (still answers inbox DMs)
|
|
15
|
+
*
|
|
16
|
+
* 2. degradeChain(chain, band) — a resolveChain `opts` hook that TIGHTENS a
|
|
17
|
+
* candidate chain per band (drops/reorders refs). Subordinate to the gates
|
|
18
|
+
* that resolve.mjs already applies; this only narrows the candidate set.
|
|
19
|
+
*
|
|
20
|
+
* 3. batchLaneFor(request) -> bool — deferrable task_classes (learning.*,
|
|
21
|
+
* digests, indexing, curation) route to the batch API (-50%).
|
|
22
|
+
*
|
|
23
|
+
* 4. cacheAffinity — pin(sessionKey, ref, ttl) / lookupPin(sessionKey),
|
|
24
|
+
* keyed on the conversation-resolver `session_key`, TTL = lane cache_ttl.
|
|
25
|
+
* Subordinate to governance/breakers: a pin that a breaker or band-100 state
|
|
26
|
+
* forces off emits a `pin_overridden` audit event (§4.9 / graft #16).
|
|
27
|
+
*
|
|
28
|
+
* 5. spawnKnobsFor(task_class) -> { maxTurns, effort, agentsJson } — defaults;
|
|
29
|
+
* `agentsJson` is the cheap-subagent map (Haiku-Explore), the SANCTIONED
|
|
30
|
+
* intra-session cost lever (§6.5 rule 2 / graft #13).
|
|
31
|
+
*
|
|
32
|
+
* House style (lib/cadence-bus.mjs, lib/model-router/ledger.mjs, lib/rate-guard.mjs):
|
|
33
|
+
* ESM .mjs, Node 20, near-zero-dep. Everything injectable (`deps.now`,
|
|
34
|
+
* `deps.store`, `deps.affinityFile`, `deps.audit`). The pure levers NEVER throw;
|
|
35
|
+
* the affinity store's write path degrades silently rather than crashing a spawn.
|
|
36
|
+
* The band/degrade levers are pure functions of their inputs.
|
|
37
|
+
*
|
|
38
|
+
* @module lib/model-router/economics
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { createHash } from "node:crypto";
|
|
42
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
43
|
+
import { join, resolve as resolvePath } from "node:path";
|
|
44
|
+
|
|
45
|
+
import { writeJsonAtomic, appendJsonl } from "../fs-atomic.mjs";
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Budget ladder — a ladder, not a cliff (§6.4)
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The economics bands. Note these are the ladder's ECONOMIC bands (0/75/90/100),
|
|
53
|
+
* deliberately coarser than budget-guard's notification bands ([50,75,90,100]):
|
|
54
|
+
* 50% is a "you're halfway" notice, not a routing change — the first DEGRADATION
|
|
55
|
+
* happens at 75% (§4.2 budget_ladder). budgetLadder collapses 50% back to band 0
|
|
56
|
+
* so a caller cannot accidentally degrade routing on a mere halfway notice.
|
|
57
|
+
*/
|
|
58
|
+
export const LADDER_BANDS = Object.freeze([0, 75, 90, 100]);
|
|
59
|
+
|
|
60
|
+
/** Map a spend percentage onto an economics band. */
|
|
61
|
+
function ladderBandForPct(pct) {
|
|
62
|
+
if (pct >= 100) return 100;
|
|
63
|
+
if (pct >= 90) return 90;
|
|
64
|
+
if (pct >= 75) return 75;
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Compute the budget degradation for the day's spend. PURE. NEVER throws.
|
|
70
|
+
*
|
|
71
|
+
* `degrade` is the structured instruction the resolver / dispatcher consume:
|
|
72
|
+
* - band 0 → { preferCheaper:false, cheapOnly:false, batchDeferrable:false,
|
|
73
|
+
* essentialOnly:false, downgradeTiers:0 }
|
|
74
|
+
* - band 75 → preferCheaper (downgrade one tier)
|
|
75
|
+
* - band 90 → cheapOnly + batchDeferrable (defer batch-eligible work)
|
|
76
|
+
* - band 100 → essentialOnly (inbox DMs answered on the cheapest qualifying
|
|
77
|
+
* row; everything else DEFER)
|
|
78
|
+
*
|
|
79
|
+
* @param {number} spentUSD today's spend (>= 0)
|
|
80
|
+
* @param {number} capUSD the daily cap (> 0; <= 0 ⇒ no cap ⇒ band 0)
|
|
81
|
+
* @returns {{ band:0|75|90|100, pct:number, spentUSD:number, capUSD:number,
|
|
82
|
+
* degrade:{ preferCheaper:boolean, cheapOnly:boolean,
|
|
83
|
+
* batchDeferrable:boolean, essentialOnly:boolean,
|
|
84
|
+
* downgradeTiers:number } }}
|
|
85
|
+
*/
|
|
86
|
+
export function budgetLadder(spentUSD, capUSD) {
|
|
87
|
+
const spent = numOr0(spentUSD);
|
|
88
|
+
const cap = Number(capUSD);
|
|
89
|
+
const hasCap = Number.isFinite(cap) && cap > 0;
|
|
90
|
+
const pct = hasCap ? round2((spent / cap) * 100) : 0;
|
|
91
|
+
const band = hasCap ? ladderBandForPct(pct) : 0;
|
|
92
|
+
|
|
93
|
+
const degrade = {
|
|
94
|
+
preferCheaper: band >= 75,
|
|
95
|
+
cheapOnly: band >= 90,
|
|
96
|
+
batchDeferrable: band >= 90,
|
|
97
|
+
essentialOnly: band >= 100,
|
|
98
|
+
// band 75 downgrades one tier; band 90+ collapses to cheap/fast directly
|
|
99
|
+
// (degradeChain drops the frontier/default head), so the tier count is the
|
|
100
|
+
// explain-string figure rather than a second knob.
|
|
101
|
+
downgradeTiers: band >= 90 ? 99 : band >= 75 ? 1 : 0,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return { band, pct, spentUSD: round6(spent), capUSD: hasCap ? cap : 0, degrade };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// degradeChain — the resolveChain opts hook (tighten a chain per band)
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
// Tier order, cheapest last. A chain member is recognised as a tier by its alias
|
|
112
|
+
// (the resolveChain alias vocabulary) OR by a literal ref whose model id contains
|
|
113
|
+
// a tier hint. The hook is alias-first: resolveChain tags each chain member with
|
|
114
|
+
// the alias it came from.
|
|
115
|
+
const TIER_ORDER = ["frontier", "default", "fast", "cheap"];
|
|
116
|
+
const TIER_RANK = Object.freeze({ frontier: 0, default: 1, fast: 2, cheap: 3 });
|
|
117
|
+
|
|
118
|
+
// Heuristic tier inference from a literal ref when no alias tag is present. Keeps
|
|
119
|
+
// degradeChain useful for chains built from raw "provider/model" refs.
|
|
120
|
+
function inferTier(member) {
|
|
121
|
+
const alias = typeof member === "object" && member ? member.alias : null;
|
|
122
|
+
if (alias && TIER_RANK[alias] != null) return alias;
|
|
123
|
+
const ref = String((member && member.ref) || member || "").toLowerCase();
|
|
124
|
+
if (ref.includes("opus")) return "frontier";
|
|
125
|
+
if (ref.includes("sonnet")) return "default";
|
|
126
|
+
if (ref.includes("haiku") || ref.includes("flash") || ref.includes("mini")) return "fast";
|
|
127
|
+
if (ref.includes("cheap") || ref.includes("deepseek") || ref.includes("kimi") || ref.includes("glm") || ref.includes("qwen")) return "cheap";
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Tighten a resolved candidate chain for a budget band. PURE. NEVER throws.
|
|
133
|
+
*
|
|
134
|
+
* Contract (the `band` argument is the economics band from budgetLadder):
|
|
135
|
+
* - band < 75 → the chain is returned unchanged.
|
|
136
|
+
* - band ≥ 75 → each tier-tagged member is downgraded ONE rung toward cheap
|
|
137
|
+
* (frontier→default→fast→cheap); the rest pass through. The
|
|
138
|
+
* resulting chain is de-duplicated, preserving order.
|
|
139
|
+
* - band ≥ 90 → frontier+default head members are DROPPED entirely (cheap/fast
|
|
140
|
+
* only); a chain that would empty out keeps its cheapest member
|
|
141
|
+
* so the caller always has at least one candidate.
|
|
142
|
+
* - `exempt` (pin or critical) → unchanged at any band below 100.
|
|
143
|
+
*
|
|
144
|
+
* Members are `{ ref, alias?, ... }` (resolveChain's chain shape) or bare ref
|
|
145
|
+
* strings. The returned members keep their original shape, only `ref`/`alias`
|
|
146
|
+
* shift on a downgrade. A `rules`/sentinel member is always preserved as a tail.
|
|
147
|
+
*
|
|
148
|
+
* @param {Array<object|string>} chain resolved chain (alias-tagged members)
|
|
149
|
+
* @param {0|75|90|100} band
|
|
150
|
+
* @param {object} [opts]
|
|
151
|
+
* @param {boolean} [opts.exempt] pin / critical — bypass degradation
|
|
152
|
+
* @param {Record<string,string>} [opts.aliases] alias → ref, for retargeting a
|
|
153
|
+
* downgraded member to the cheaper alias's ref
|
|
154
|
+
* @returns {Array<object|string>} the tightened chain
|
|
155
|
+
*/
|
|
156
|
+
export function degradeChain(chain, band, opts = {}) {
|
|
157
|
+
const members = Array.isArray(chain) ? chain : [];
|
|
158
|
+
if (!members.length) return members;
|
|
159
|
+
const b = Number(band) || 0;
|
|
160
|
+
if (b < 75 || opts.exempt) return members.slice();
|
|
161
|
+
|
|
162
|
+
const aliases = (opts && opts.aliases && typeof opts.aliases === "object") ? opts.aliases : {};
|
|
163
|
+
|
|
164
|
+
const isSentinel = (m) =>
|
|
165
|
+
(typeof m === "object" && m && (m.isRules || m.ref === "rules")) ||
|
|
166
|
+
m === "rules";
|
|
167
|
+
|
|
168
|
+
if (b >= 90) {
|
|
169
|
+
// cheap/fast only — drop the frontier/default head.
|
|
170
|
+
const kept = members.filter((m) => {
|
|
171
|
+
if (isSentinel(m)) return true;
|
|
172
|
+
const tier = inferTier(m);
|
|
173
|
+
return tier == null || TIER_RANK[tier] >= TIER_RANK.fast;
|
|
174
|
+
});
|
|
175
|
+
if (kept.length) return dedupeByRef(kept);
|
|
176
|
+
// Everything was frontier/default: keep the single cheapest as a safety net.
|
|
177
|
+
const cheapest = members
|
|
178
|
+
.filter((m) => !isSentinel(m))
|
|
179
|
+
.sort((a, z) => (TIER_RANK[inferTier(z)] ?? -1) - (TIER_RANK[inferTier(a)] ?? -1))[0];
|
|
180
|
+
return cheapest ? [cheapest] : members.slice();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// band >= 75: downgrade each tier-tagged member one rung.
|
|
184
|
+
const next = members.map((m) => {
|
|
185
|
+
if (isSentinel(m)) return m;
|
|
186
|
+
const tier = inferTier(m);
|
|
187
|
+
if (tier == null) return m;
|
|
188
|
+
const i = TIER_RANK[tier];
|
|
189
|
+
const downAlias = TIER_ORDER[Math.min(TIER_ORDER.length - 1, i + 1)];
|
|
190
|
+
if (downAlias === tier) return m; // already cheapest
|
|
191
|
+
// Retarget to the cheaper alias's ref when the caller supplied the alias map.
|
|
192
|
+
if (typeof m === "object" && m) {
|
|
193
|
+
const ref = aliases[downAlias] || m.ref;
|
|
194
|
+
return { ...m, ref, alias: downAlias };
|
|
195
|
+
}
|
|
196
|
+
return aliases[downAlias] || m;
|
|
197
|
+
});
|
|
198
|
+
return dedupeByRef(next);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function dedupeByRef(members) {
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const m of members) {
|
|
205
|
+
const ref = typeof m === "object" && m ? m.ref : m;
|
|
206
|
+
const key = String(ref);
|
|
207
|
+
if (seen.has(key)) continue;
|
|
208
|
+
seen.add(key);
|
|
209
|
+
out.push(m);
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// batchLaneFor — deferrable task classes → batch API (-50%)
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
// Deferrable task-class prefixes: nothing a human is waiting on in real time.
|
|
219
|
+
// learning.* (reflect/curate), digests, indexing, curation, capture, enrich.
|
|
220
|
+
const DEFERRABLE_PREFIXES = Object.freeze([
|
|
221
|
+
"learning", // learning.reflect / learning.curate
|
|
222
|
+
"digest", // cost-rollup / fleet digests
|
|
223
|
+
"index", // session indexing
|
|
224
|
+
"curate", // skill / knowledge curation
|
|
225
|
+
"capture", // collective capture
|
|
226
|
+
"enrich", // setup enrichment
|
|
227
|
+
"rollup", // org rollups
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
// Explicitly NON-deferrable task classes even if a prefix matched: anything in
|
|
231
|
+
// the live conversational / inbox path.
|
|
232
|
+
const REALTIME_TASK_CLASSES = Object.freeze(new Set([
|
|
233
|
+
"session.responder",
|
|
234
|
+
"classify.inbox",
|
|
235
|
+
"huddle",
|
|
236
|
+
"voice.brief",
|
|
237
|
+
]));
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Decide whether a request is BATCH-deferrable (eligible for the -50% batch API).
|
|
241
|
+
* PURE. NEVER throws.
|
|
242
|
+
*
|
|
243
|
+
* Rules, in order:
|
|
244
|
+
* 1. an explicit `lane` wins — `lane:"batch"` ⇒ true, `lane:"realtime"` ⇒ false.
|
|
245
|
+
* 2. a realtime-class task (inbox, session, huddle, voice) ⇒ false regardless.
|
|
246
|
+
* 3. a deferrable prefix (learning/digest/index/curate/capture/enrich/rollup)
|
|
247
|
+
* AND not high priority ⇒ true.
|
|
248
|
+
* 4. otherwise ⇒ false (default to realtime; never silently defer user work).
|
|
249
|
+
*
|
|
250
|
+
* @param {object} request RouteRequest (task_class, lane, priority)
|
|
251
|
+
* @returns {boolean}
|
|
252
|
+
*/
|
|
253
|
+
export function batchLaneFor(request = {}) {
|
|
254
|
+
const lane = request.lane;
|
|
255
|
+
if (lane === "batch") return true;
|
|
256
|
+
if (lane === "realtime") return false;
|
|
257
|
+
|
|
258
|
+
const tc = typeof request.task_class === "string" ? request.task_class : "";
|
|
259
|
+
if (REALTIME_TASK_CLASSES.has(tc)) return false;
|
|
260
|
+
|
|
261
|
+
// critical/high priority never defers, even a learning task escalated by hand.
|
|
262
|
+
const prio = request.priority;
|
|
263
|
+
if (prio === "critical" || prio === "high") return false;
|
|
264
|
+
|
|
265
|
+
const head = tc.split(".")[0];
|
|
266
|
+
return DEFERRABLE_PREFIXES.includes(head);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// cacheAffinity — session_key → pinned ref, TTL = lane cache_ttl (§4.9, graft #16)
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Parse a lane cache_ttl string ("1h", "5m", "30s", "0") to milliseconds.
|
|
275
|
+
* Unknown / falsy → the 1h default; "0" / "none" → 0 (no pin). PURE.
|
|
276
|
+
*/
|
|
277
|
+
export function ttlMs(ttl) {
|
|
278
|
+
if (ttl == null) return 3600_000;
|
|
279
|
+
if (typeof ttl === "number") return ttl > 0 ? ttl : 0;
|
|
280
|
+
const s = String(ttl).trim().toLowerCase();
|
|
281
|
+
if (s === "0" || s === "none" || s === "") return 0;
|
|
282
|
+
const m = s.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/);
|
|
283
|
+
if (!m) return 3600_000;
|
|
284
|
+
const n = Number(m[1]);
|
|
285
|
+
const unit = m[2] || "ms";
|
|
286
|
+
const mult = { ms: 1, s: 1000, m: 60_000, h: 3600_000, d: 86_400_000 }[unit];
|
|
287
|
+
return Math.round(n * mult);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function affinityFile(deps = {}) {
|
|
291
|
+
if (deps.affinityFile) return resolvePath(deps.affinityFile);
|
|
292
|
+
const root =
|
|
293
|
+
deps.agentRoot || process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd();
|
|
294
|
+
return join(resolvePath(root), "state", "model-router", "affinity.json");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function auditFile(deps = {}) {
|
|
298
|
+
if (deps.auditFile) return resolvePath(deps.auditFile);
|
|
299
|
+
const root =
|
|
300
|
+
deps.agentRoot || process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd();
|
|
301
|
+
return join(resolvePath(root), "logs", "audit", "routing.jsonl");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Hash a session_key so the on-disk affinity map carries no raw conversation
|
|
306
|
+
* identifiers (no PII; §4.9 keys on `sha256(session_key)`). PURE.
|
|
307
|
+
*/
|
|
308
|
+
export function hashSessionKey(sessionKey) {
|
|
309
|
+
return createHash("sha256").update(String(sessionKey || "")).digest("hex");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function readStore(deps) {
|
|
313
|
+
// An injected in-memory store (deps.store: a plain object) bypasses the FS so
|
|
314
|
+
// tests are hermetic and the daemon can keep one map hot.
|
|
315
|
+
if (deps && deps.store && typeof deps.store === "object") return deps.store;
|
|
316
|
+
const file = affinityFile(deps);
|
|
317
|
+
if (!existsSync(file)) return {};
|
|
318
|
+
try {
|
|
319
|
+
const obj = JSON.parse(readFileSync(file, "utf-8"));
|
|
320
|
+
return obj && typeof obj === "object" ? obj : {};
|
|
321
|
+
} catch {
|
|
322
|
+
return {};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function writeStore(store, deps) {
|
|
327
|
+
if (deps && deps.store && typeof deps.store === "object") {
|
|
328
|
+
// Mutate the injected store in place (the caller owns persistence).
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
writeJsonAtomic(affinityFile(deps), store);
|
|
333
|
+
return true;
|
|
334
|
+
} catch {
|
|
335
|
+
return false; // best-effort: a pin we can't persist degrades to no-pin
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function nowMs(deps) {
|
|
340
|
+
return (deps && typeof deps.now === "function" ? deps.now : Date.now)();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Pin a session_key to a catalog ref for `ttl`. Subsequent threaded follow-ups
|
|
345
|
+
* resolve sticky to the same model so the conversation's cache survives. NEVER
|
|
346
|
+
* throws; a write failure returns `{ pinned:false }`.
|
|
347
|
+
*
|
|
348
|
+
* @param {string} sessionKey conversation-resolver session_key
|
|
349
|
+
* @param {string} ref "provider/model" the decision chose
|
|
350
|
+
* @param {string|number} ttl lane cache_ttl ("1h"/"5m") or ms
|
|
351
|
+
* @param {object} [deps] { now, store, affinityFile, agentRoot, decisionId }
|
|
352
|
+
* @returns {{ pinned:boolean, expires_at:number, key:string }}
|
|
353
|
+
*/
|
|
354
|
+
export function pin(sessionKey, ref, ttl, deps = {}) {
|
|
355
|
+
const key = hashSessionKey(sessionKey);
|
|
356
|
+
const span = ttlMs(ttl);
|
|
357
|
+
if (!sessionKey || !ref || span <= 0) {
|
|
358
|
+
return { pinned: false, expires_at: 0, key };
|
|
359
|
+
}
|
|
360
|
+
const now = nowMs(deps);
|
|
361
|
+
const store = readStore(deps);
|
|
362
|
+
store[key] = {
|
|
363
|
+
ref: String(ref),
|
|
364
|
+
decision_id: deps.decisionId || null,
|
|
365
|
+
expires_at: now + span,
|
|
366
|
+
};
|
|
367
|
+
const ok = writeStore(store, deps);
|
|
368
|
+
return { pinned: ok, expires_at: store[key].expires_at, key };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Look up a live (non-expired) pin for a session_key. NEVER throws. An expired
|
|
373
|
+
* entry is treated as absent (and pruned on the next write). Returns null when
|
|
374
|
+
* there is no live pin.
|
|
375
|
+
*
|
|
376
|
+
* @param {string} sessionKey
|
|
377
|
+
* @param {object} [deps] { now, store, affinityFile, agentRoot }
|
|
378
|
+
* @returns {{ ref:string, decision_id:string|null, expires_at:number }|null}
|
|
379
|
+
*/
|
|
380
|
+
export function lookupPin(sessionKey, deps = {}) {
|
|
381
|
+
if (!sessionKey) return null;
|
|
382
|
+
const key = hashSessionKey(sessionKey);
|
|
383
|
+
const store = readStore(deps);
|
|
384
|
+
const entry = store[key];
|
|
385
|
+
if (!entry || typeof entry !== "object") return null;
|
|
386
|
+
const now = nowMs(deps);
|
|
387
|
+
if (!(entry.expires_at > now)) return null; // expired or malformed
|
|
388
|
+
return { ref: entry.ref, decision_id: entry.decision_id ?? null, expires_at: entry.expires_at };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Resolve a pin against governance: a live pin is honored ONLY when it still
|
|
393
|
+
* passes the caller-supplied gate (breaker closed, data-class ok, band < 100).
|
|
394
|
+
* A pin a breaker / governance / band-100 state forces off is DROPPED and a
|
|
395
|
+
* `pin_overridden` audit event is emitted (§4.9 / graft #16). NEVER throws.
|
|
396
|
+
*
|
|
397
|
+
* The gate is injected so this stays decoupled from resolve.mjs's internals: the
|
|
398
|
+
* caller passes `gate(ref) -> { ok:boolean, reason?:string }` (e.g. wrapping the
|
|
399
|
+
* breaker probe + band check).
|
|
400
|
+
*
|
|
401
|
+
* @param {string} sessionKey
|
|
402
|
+
* @param {(ref:string)=>{ok:boolean, reason?:string}} gate
|
|
403
|
+
* @param {object} [deps] { now, store, affinityFile, agentRoot, auditFile,
|
|
404
|
+
* audit (injected sink fn), decisionId }
|
|
405
|
+
* @returns {{ ref:string|null, overridden:boolean, reason:string|null }}
|
|
406
|
+
*/
|
|
407
|
+
export function resolvePin(sessionKey, gate, deps = {}) {
|
|
408
|
+
const live = lookupPin(sessionKey, deps);
|
|
409
|
+
if (!live) return { ref: null, overridden: false, reason: null };
|
|
410
|
+
|
|
411
|
+
let verdict = { ok: true };
|
|
412
|
+
if (typeof gate === "function") {
|
|
413
|
+
try {
|
|
414
|
+
verdict = gate(live.ref) || { ok: true };
|
|
415
|
+
} catch {
|
|
416
|
+
verdict = { ok: false, reason: "gate_error" };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (verdict.ok) {
|
|
421
|
+
return { ref: live.ref, overridden: false, reason: null };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Pin forced off — emit the audit event and report the override.
|
|
425
|
+
emitPinOverridden(sessionKey, live, verdict.reason || "governance", deps);
|
|
426
|
+
return { ref: null, overridden: true, reason: verdict.reason || "governance" };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function emitPinOverridden(sessionKey, live, reason, deps) {
|
|
430
|
+
const row = {
|
|
431
|
+
kind: "pin_overridden",
|
|
432
|
+
ts: new Date(nowMs(deps)).toISOString(),
|
|
433
|
+
session_key_hash: hashSessionKey(sessionKey),
|
|
434
|
+
ref: live.ref,
|
|
435
|
+
decision_id: live.decision_id ?? deps.decisionId ?? null,
|
|
436
|
+
reason,
|
|
437
|
+
};
|
|
438
|
+
// An injected audit sink wins (the daemon owns logs/audit/routing.jsonl);
|
|
439
|
+
// otherwise append to the on-disk audit log, best-effort.
|
|
440
|
+
if (deps && typeof deps.audit === "function") {
|
|
441
|
+
try {
|
|
442
|
+
deps.audit(row);
|
|
443
|
+
} catch {
|
|
444
|
+
/* audit is best-effort */
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
appendJsonl(auditFile(deps), row);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
// spawnKnobsFor — per-task-class spawn defaults incl. the cheap-subagent map
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
// The cheap-subagent map (`--agents <json>`): the SANCTIONED intra-session cost
|
|
456
|
+
// lever (§6.5 rule 2 / graft #13). A heavy session keeps its own (cached) main
|
|
457
|
+
// loop on the resolved model but farms cheap fan-out work (file exploration,
|
|
458
|
+
// grep, summarisation) to a Haiku-Explore subagent so the main loop's cache
|
|
459
|
+
// survives. The shape mirrors Claude Code's `--agents` JSON (name → { model,
|
|
460
|
+
// description }).
|
|
461
|
+
const HAIKU_EXPLORE = Object.freeze({
|
|
462
|
+
"Haiku-Explore": {
|
|
463
|
+
model: "claude-haiku-4-5",
|
|
464
|
+
description:
|
|
465
|
+
"Cheap fan-out: file exploration, grep/search, bulk read + summarise. Returns findings, never takes actions.",
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Per-task-class spawn knob defaults. maxTurns mirrors resolve.mjs's ceilings;
|
|
470
|
+
// `effort` is null unless a task class genuinely wants deep thinking; `agentsJson`
|
|
471
|
+
// attaches the cheap-subagent map to the EXPENSIVE, tool-heavy session classes
|
|
472
|
+
// (the ones that benefit from offloading fan-out), and stays null for one-shot /
|
|
473
|
+
// cheap classes where a subagent would only add overhead.
|
|
474
|
+
const SPAWN_KNOBS = Object.freeze({
|
|
475
|
+
"session.responder": { maxTurns: 40, effort: null, agentsJson: HAIKU_EXPLORE },
|
|
476
|
+
"classify.inbox": { maxTurns: 1, effort: null, agentsJson: null },
|
|
477
|
+
lookup: { maxTurns: 2, effort: null, agentsJson: null },
|
|
478
|
+
enrich: { maxTurns: 3, effort: null, agentsJson: null },
|
|
479
|
+
"voice.brief": { maxTurns: 4, effort: null, agentsJson: null },
|
|
480
|
+
huddle: { maxTurns: 8, effort: null, agentsJson: HAIKU_EXPLORE },
|
|
481
|
+
"learning.reflect": { maxTurns: 6, effort: "medium", agentsJson: null },
|
|
482
|
+
"learning.curate": { maxTurns: 6, effort: null, agentsJson: null },
|
|
483
|
+
capture: { maxTurns: 2, effort: null, agentsJson: null },
|
|
484
|
+
"cadence": { maxTurns: 20, effort: null, agentsJson: HAIKU_EXPLORE },
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
const DEFAULT_KNOBS = Object.freeze({ maxTurns: 40, effort: null, agentsJson: null });
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Default spawn knobs for a task class. PURE. NEVER throws. Resolves an exact
|
|
491
|
+
* task_class match first, then the dotted prefix (e.g. "lookup.gmail" → "lookup",
|
|
492
|
+
* "cadence.daily-digest" → "cadence"), then a generic ceiling. The returned
|
|
493
|
+
* `agentsJson` is a fresh shallow clone so a caller can mutate it safely.
|
|
494
|
+
*
|
|
495
|
+
* @param {string} taskClass
|
|
496
|
+
* @returns {{ maxTurns:number, effort:string|null, agentsJson:object|null }}
|
|
497
|
+
*/
|
|
498
|
+
export function spawnKnobsFor(taskClass) {
|
|
499
|
+
const tc = typeof taskClass === "string" ? taskClass : "";
|
|
500
|
+
let knobs = SPAWN_KNOBS[tc];
|
|
501
|
+
if (!knobs) {
|
|
502
|
+
const head = tc.split(".")[0];
|
|
503
|
+
knobs = SPAWN_KNOBS[head];
|
|
504
|
+
}
|
|
505
|
+
knobs = knobs || DEFAULT_KNOBS;
|
|
506
|
+
return {
|
|
507
|
+
maxTurns: knobs.maxTurns,
|
|
508
|
+
effort: knobs.effort,
|
|
509
|
+
// Deep clone the cheap-subagent map so a caller can mutate it (e.g. swap the
|
|
510
|
+
// explore model) without corrupting the shared module constant.
|
|
511
|
+
agentsJson: knobs.agentsJson ? cloneAgents(knobs.agentsJson) : null,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function cloneAgents(map) {
|
|
516
|
+
const out = {};
|
|
517
|
+
for (const [name, def] of Object.entries(map)) {
|
|
518
|
+
out[name] = def && typeof def === "object" ? { ...def } : def;
|
|
519
|
+
}
|
|
520
|
+
return out;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// Small numeric helpers (mirror ledger.mjs)
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
function numOr0(v) {
|
|
528
|
+
const n = Number(v);
|
|
529
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
530
|
+
}
|
|
531
|
+
function round2(n) {
|
|
532
|
+
return Number(Number(n).toFixed(2));
|
|
533
|
+
}
|
|
534
|
+
function round6(n) {
|
|
535
|
+
return Number(Number(n).toFixed(6));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export const _internals = {
|
|
539
|
+
ladderBandForPct,
|
|
540
|
+
inferTier,
|
|
541
|
+
dedupeByRef,
|
|
542
|
+
TIER_ORDER,
|
|
543
|
+
TIER_RANK,
|
|
544
|
+
DEFERRABLE_PREFIXES,
|
|
545
|
+
REALTIME_TASK_CLASSES,
|
|
546
|
+
HAIKU_EXPLORE,
|
|
547
|
+
SPAWN_KNOBS,
|
|
548
|
+
affinityFile,
|
|
549
|
+
auditFile,
|
|
550
|
+
readStore,
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
export default {
|
|
554
|
+
budgetLadder,
|
|
555
|
+
degradeChain,
|
|
556
|
+
batchLaneFor,
|
|
557
|
+
pin,
|
|
558
|
+
lookupPin,
|
|
559
|
+
resolvePin,
|
|
560
|
+
spawnKnobsFor,
|
|
561
|
+
ttlMs,
|
|
562
|
+
hashSessionKey,
|
|
563
|
+
LADDER_BANDS,
|
|
564
|
+
};
|