@cohortapp/agent-sdk 2.17.0 → 2.18.4
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/settings.json +18 -0
- package/.env.example +18 -5
- package/README.md +1 -0
- package/bin/maestro.mjs +62 -0
- package/docs/guides/billing-console-keys.md +60 -0
- package/docs/guides/front-door-session.md +54 -9
- package/docs/guides/mac-mini.md +20 -25
- package/docs/guides/setup-wizard.md +1 -1
- package/docs/runbooks/fleet-rollout.md +156 -0
- package/docs/runbooks/mac-mini-bootstrap.md +12 -14
- package/lib/action-executor.js +19 -3
- package/lib/budget-guard.mjs +279 -3
- package/lib/channels/base-adapter.mjs +3 -1
- package/lib/channels/contract.mjs +2 -1
- package/lib/channels/inbox-item.mjs +8 -0
- package/lib/claude-bin.mjs +5 -6
- package/lib/cli/doctor-checks.mjs +141 -10
- package/lib/cli/global-setup-extras.mjs +5 -1
- package/lib/cli/inbox.mjs +100 -15
- package/lib/cli/seat-auth.mjs +463 -0
- package/lib/cli/session.mjs +80 -12
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- package/lib/diagnostics/alerts.mjs +49 -0
- package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
- package/lib/engine/agents/definitions.mjs +343 -0
- package/lib/engine/agents/persist.mjs +275 -0
- package/lib/engine/agents/runtime.mjs +748 -0
- package/lib/engine/agents/usage.mjs +95 -0
- package/lib/engine/auth-status.mjs +139 -0
- package/lib/engine/budget.mjs +194 -0
- package/lib/engine/cli.mjs +1204 -0
- package/lib/engine/commands/index.mjs +269 -0
- package/lib/engine/context/budget.mjs +219 -0
- package/lib/engine/context/cache.mjs +125 -0
- package/lib/engine/context/child-env.mjs +215 -0
- package/lib/engine/context/compaction.mjs +342 -0
- package/lib/engine/context/images.mjs +90 -0
- package/lib/engine/context/instructions.mjs +327 -0
- package/lib/engine/context/lazy-instructions.mjs +169 -0
- package/lib/engine/context/manager.mjs +182 -0
- package/lib/engine/context/real-path.mjs +91 -0
- package/lib/engine/context/secret-values.mjs +163 -0
- package/lib/engine/context/settings.mjs +274 -0
- package/lib/engine/context/stream-input.mjs +159 -0
- package/lib/engine/guard.mjs +152 -0
- package/lib/engine/hooks.mjs +713 -0
- package/lib/engine/loop.mjs +560 -0
- package/lib/engine/mcp/client.mjs +254 -0
- package/lib/engine/mcp/config.mjs +301 -0
- package/lib/engine/mcp/http.mjs +201 -0
- package/lib/engine/mcp/index.mjs +146 -0
- package/lib/engine/mcp/jsonrpc.mjs +147 -0
- package/lib/engine/mcp/naming.mjs +66 -0
- package/lib/engine/mcp/resources.mjs +89 -0
- package/lib/engine/mcp/results.mjs +133 -0
- package/lib/engine/mcp/stdio.mjs +137 -0
- package/lib/engine/mcp/supervisor.mjs +116 -0
- package/lib/engine/messages.mjs +104 -0
- package/lib/engine/output/json.mjs +164 -0
- package/lib/engine/output/stream-json.mjs +266 -0
- package/lib/engine/permissions.mjs +845 -0
- package/lib/engine/process-identity.mjs +164 -0
- package/lib/engine/process-tree.mjs +551 -0
- package/lib/engine/prompt.mjs +60 -0
- package/lib/engine/session/store.mjs +299 -0
- package/lib/engine/session-runtime/args.mjs +97 -0
- package/lib/engine/session-runtime/host.mjs +143 -0
- package/lib/engine/session-runtime/inbox.mjs +122 -0
- package/lib/engine/session-runtime/notifications.mjs +129 -0
- package/lib/engine/session-runtime/registry.mjs +328 -0
- package/lib/engine/session-runtime/runner.mjs +344 -0
- package/lib/engine/session-runtime/socket.mjs +212 -0
- package/lib/engine/session-runtime/wakeup.mjs +115 -0
- package/lib/engine/skills/index.mjs +321 -0
- package/lib/engine/tools/bash-background.mjs +533 -0
- package/lib/engine/tools/bash.mjs +216 -0
- package/lib/engine/tools/edit.mjs +97 -0
- package/lib/engine/tools/glob.mjs +81 -0
- package/lib/engine/tools/grep.mjs +224 -0
- package/lib/engine/tools/index.mjs +84 -0
- package/lib/engine/tools/list-agents.mjs +32 -0
- package/lib/engine/tools/ls.mjs +127 -0
- package/lib/engine/tools/monitor.mjs +82 -0
- package/lib/engine/tools/notebook-edit.mjs +218 -0
- package/lib/engine/tools/read.mjs +103 -0
- package/lib/engine/tools/schedule-wakeup.mjs +45 -0
- package/lib/engine/tools/schema.mjs +144 -0
- package/lib/engine/tools/send-message.mjs +77 -0
- package/lib/engine/tools/session.mjs +70 -0
- package/lib/engine/tools/todo.mjs +144 -0
- package/lib/engine/tools/toolsearch.mjs +217 -0
- package/lib/engine/tools/walk.mjs +193 -0
- package/lib/engine/tools/web-switch.mjs +31 -0
- package/lib/engine/tools/webfetch-html.mjs +387 -0
- package/lib/engine/tools/webfetch-net.mjs +340 -0
- package/lib/engine/tools/webfetch.mjs +198 -0
- package/lib/engine/tools/websearch.mjs +91 -0
- package/lib/engine/tools/workflow.mjs +95 -0
- package/lib/engine/tools/write.mjs +76 -0
- package/lib/engine/tui/line-editor.mjs +137 -0
- package/lib/engine/tui/render.mjs +86 -0
- package/lib/engine/tui/tui.mjs +274 -0
- package/lib/engine/wire/anthropic-messages.mjs +263 -0
- package/lib/engine/wire/effort.mjs +36 -0
- package/lib/engine/wire/errors.mjs +496 -0
- package/lib/engine/wire/http.mjs +441 -0
- package/lib/engine/wire/index.mjs +76 -0
- package/lib/engine/wire/openai-chat.mjs +332 -0
- package/lib/engine/wire/prompt-cache.mjs +79 -0
- package/lib/engine/wire/search.mjs +140 -0
- package/lib/engine/wire/sse.mjs +114 -0
- package/lib/engine/wire/stall.mjs +349 -0
- package/lib/engine/wire/token-provider.mjs +175 -0
- package/lib/engine/wire/usage.mjs +192 -0
- package/lib/engine/workflow/host.mjs +524 -0
- package/lib/engine/workflow/journal.mjs +188 -0
- package/lib/engine/workflow/json-schema.mjs +171 -0
- package/lib/engine/workflow/meta.mjs +329 -0
- package/lib/engine/workflow/notifications.mjs +52 -0
- package/lib/engine/workflow/runtime.mjs +447 -0
- package/lib/engine/workflow/sandbox.mjs +534 -0
- package/lib/engine/workflow/worker.mjs +141 -0
- package/lib/engine/workflow/worktree.mjs +74 -0
- package/lib/execution/disposition.mjs +1 -1
- package/lib/execution/intake.mjs +10 -0
- package/lib/execution/surface-policy.mjs +15 -0
- package/lib/learning/curator.mjs +8 -6
- package/lib/learning/reflect.mjs +8 -6
- package/lib/model-router/catalog/cohort.yaml +137 -0
- package/lib/model-router/catalog.mjs +118 -1
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +89 -3
- package/lib/model-router/spawn.mjs +46 -47
- package/lib/model-router/taxonomy.mjs +126 -4
- package/lib/org/cost-sync.mjs +141 -11
- package/lib/org/inbound/broadcast.mjs +289 -0
- package/lib/org/inbound/collective.mjs +375 -0
- package/lib/org/inbound/directedness.mjs +96 -8
- package/lib/org/inbound/facts.mjs +78 -2
- package/lib/org/inbound/project.mjs +22 -0
- package/lib/org/inbound/surfaces.mjs +14 -0
- package/lib/org/llm-token.mjs +879 -0
- package/lib/org/mesh.mjs +61 -0
- package/lib/org/messaging.mjs +3 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +15 -0
- package/lib/org/quota.mjs +520 -0
- package/lib/org/tool-surface.mjs +104 -16
- package/lib/org/ui-parity.mjs +16 -1
- package/lib/org/work-ledger.mjs +37 -6
- package/lib/rate-guard.mjs +114 -1
- package/lib/resource-governor.mjs +41 -6
- package/lib/runtime/adapter.mjs +823 -0
- package/lib/runtime/child-env.mjs +191 -0
- package/lib/runtime/legacy-shell-guard.mjs +97 -0
- package/lib/runtime/seat-engine.mjs +162 -0
- package/lib/session/ask-ledger.mjs +271 -0
- package/lib/session/current-work.mjs +676 -0
- package/lib/session/feed-core.mjs +40 -3
- package/lib/session/launch-args.mjs +56 -4
- package/lib/session/status-summary.mjs +26 -9
- package/lib/session/upgrade-notice.mjs +42 -0
- package/lib/setup/claude-probe.mjs +117 -13
- package/lib/setup/enrich.mjs +13 -10
- package/lib/setup/sections/model.mjs +39 -13
- package/lib/telemetry/collect.mjs +208 -9
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +13 -3
- package/plugins/maestro-skills/skills/board-work.md +5 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
- package/plugins/maestro-skills/skills/main-session.md +18 -7
- package/scripts/ci/check-tarball-fidelity.mjs +126 -2
- package/scripts/ci/run-tests.mjs +47 -19
- package/scripts/cohort-llm/api-key-helper.mjs +92 -0
- package/scripts/collective/hook-runner.mjs +29 -2
- package/scripts/continuous-monitor.sh +13 -0
- package/scripts/cost/track-claude-usage.mjs +15 -0
- package/scripts/daemon/agent-daemon.mjs +408 -20
- package/scripts/daemon/assurance.mjs +48 -12
- package/scripts/daemon/cadence-consumer.mjs +218 -68
- package/scripts/daemon/cadence-handlers.mjs +73 -4
- package/scripts/daemon/classifier.mjs +75 -26
- package/scripts/daemon/context-compiler.mjs +51 -37
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +595 -149
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +24 -0
- package/scripts/daemon/responder.mjs +246 -79
- package/scripts/daemon/sdk-version.mjs +98 -16
- package/scripts/eval/probe-gateway.mjs +635 -0
- package/scripts/eval/replay/extract.mjs +270 -0
- package/scripts/eval/replay/grade.mjs +260 -0
- package/scripts/eval/replay/lib/config.mjs +50 -0
- package/scripts/eval/replay/lib/effects.mjs +65 -0
- package/scripts/eval/replay/lib/fixture.mjs +188 -0
- package/scripts/eval/replay/lib/judge.mjs +72 -0
- package/scripts/eval/replay/lib/redact.mjs +136 -0
- package/scripts/eval/replay/lib/sandbox.mjs +170 -0
- package/scripts/eval/replay/lib/schema-check.mjs +63 -0
- package/scripts/eval/replay/lib/transcript.mjs +76 -0
- package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
- package/scripts/eval/replay/report.mjs +185 -0
- package/scripts/eval/replay/run.mjs +404 -0
- package/scripts/fleet/rollout.mjs +1094 -0
- package/scripts/hooks/pre-send-audit.sh +36 -245
- package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
- package/scripts/hooks/validate-state-yaml.sh +190 -0
- package/scripts/huddle/huddle-llm.mjs +361 -0
- package/scripts/huddle/huddle-server.mjs +46 -121
- package/scripts/local-triggers/autoupdate.sh +448 -78
- package/scripts/local-triggers/run-trigger.sh +13 -0
- package/scripts/maintenance/pin-integrity.mjs +364 -0
- package/scripts/poll-slack-events.sh +41 -9
- package/scripts/poller/slack-socket-mode.mjs +28 -3
- package/scripts/session/supervisor.mjs +80 -13
- package/scripts/spawn-session.sh +13 -0
- package/bin/maestro.test.mjs +0 -1574
- package/lib/action-executor.test.mjs +0 -871
- package/lib/archetype.test.mjs +0 -132
- package/lib/assurance/plan-note.test.mjs +0 -234
- package/lib/assurance/room-budget.test.mjs +0 -486
- package/lib/assurance/tier.test.mjs +0 -174
- package/lib/autonomy.test.mjs +0 -66
- package/lib/backlog.test.mjs +0 -302
- package/lib/backup/policy.test.mjs +0 -305
- package/lib/budget-escalate.test.mjs +0 -232
- package/lib/budget-guard.envelope.test.mjs +0 -476
- package/lib/budget-guard.test.mjs +0 -427
- package/lib/cadence-bus-requeue.test.mjs +0 -83
- package/lib/cadence-bus-schedule.test.mjs +0 -194
- package/lib/cadence-bus.test.mjs +0 -720
- package/lib/cadences.test.mjs +0 -230
- package/lib/capability/inventory.test.mjs +0 -232
- package/lib/capability.test.mjs +0 -78
- package/lib/channels/base-adapter.test.mjs +0 -590
- package/lib/channels/channels.test.mjs +0 -371
- package/lib/channels/contract.test.mjs +0 -162
- package/lib/channels/inbox-item.test.mjs +0 -368
- package/lib/channels/orgmail/adapter.test.mjs +0 -448
- package/lib/channels/pairing.test.mjs +0 -270
- package/lib/channels/repeat-suppressor.test.mjs +0 -134
- package/lib/channels/slack-adapter.test.mjs +0 -212
- package/lib/channels/telegram-adapter.test.mjs +0 -306
- package/lib/channels/voice/adapter.test.mjs +0 -278
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
- package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
- package/lib/charter.test.mjs +0 -89
- package/lib/claude-bin.test.mjs +0 -131
- package/lib/cli/board.test.mjs +0 -227
- package/lib/cli/design.test.mjs +0 -270
- package/lib/cli/doctor-checks.test.mjs +0 -336
- package/lib/cli/global-setup-extras.test.mjs +0 -462
- package/lib/cli/inbox.test.mjs +0 -230
- package/lib/cli/session-ack.test.mjs +0 -63
- package/lib/cli/session.test.mjs +0 -613
- package/lib/collective/capture.test.mjs +0 -121
- package/lib/collective/cards.test.mjs +0 -114
- package/lib/collective/config.test.mjs +0 -123
- package/lib/collective/global-config.test.mjs +0 -220
- package/lib/collective/global-skills.test.mjs +0 -126
- package/lib/collective/presence.test.mjs +0 -95
- package/lib/collective/recall.test.mjs +0 -116
- package/lib/collective/vendor-skills.test.mjs +0 -306
- package/lib/comms/send-gate.test.mjs +0 -770
- package/lib/comms.test.mjs +0 -41
- package/lib/context/budget.test.mjs +0 -252
- package/lib/context/history-scope.test.mjs +0 -79
- package/lib/cost/ledger-row.test.mjs +0 -183
- package/lib/design/design-md.test.mjs +0 -318
- package/lib/design/fixtures/DESIGN.golden.md +0 -238
- package/lib/design/fixtures/PRODUCT.golden.md +0 -67
- package/lib/design/fixtures/foundation.json +0 -133
- package/lib/design/refresh-gate.test.mjs +0 -144
- package/lib/design/write.test.mjs +0 -241
- package/lib/diagnostics/alerts.test.mjs +0 -318
- package/lib/diagnostics/backup-freshness.test.mjs +0 -185
- package/lib/diagnostics/counters.test.mjs +0 -206
- package/lib/diagnostics/events.test.mjs +0 -290
- package/lib/diagnostics/otel.test.mjs +0 -196
- package/lib/diagnostics/trace.test.mjs +0 -251
- package/lib/env-compat.test.mjs +0 -104
- package/lib/execution/disposition.test.mjs +0 -553
- package/lib/execution/drive.test.mjs +0 -270
- package/lib/execution/effects.test.mjs +0 -344
- package/lib/execution/intake.test.mjs +0 -389
- package/lib/execution/journal.test.mjs +0 -261
- package/lib/execution/match.test.mjs +0 -235
- package/lib/execution/pipeline.test.mjs +0 -392
- package/lib/execution/route.test.mjs +0 -186
- package/lib/execution/surface-policy.test.mjs +0 -162
- package/lib/fs-atomic.test.mjs +0 -72
- package/lib/fs-ownership.test.mjs +0 -158
- package/lib/goals/admission.test.mjs +0 -164
- package/lib/goals/classify.test.mjs +0 -167
- package/lib/goals/collaborate.test.mjs +0 -336
- package/lib/goals/gaps.test.mjs +0 -284
- package/lib/goals/loop.test.mjs +0 -845
- package/lib/hooks/bus.test.mjs +0 -387
- package/lib/identity/persona.test.mjs +0 -142
- package/lib/kpi-sensors.test.mjs +0 -278
- package/lib/kpi.test.mjs +0 -244
- package/lib/learning/config.test.mjs +0 -75
- package/lib/learning/counters.test.mjs +0 -69
- package/lib/learning/curator-consolidate.test.mjs +0 -238
- package/lib/learning/curator.test.mjs +0 -106
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.test.mjs +0 -125
- package/lib/learning/skill-writer.test.mjs +0 -210
- package/lib/mandate/audit.test.mjs +0 -195
- package/lib/mandate/contract.test.mjs +0 -185
- package/lib/mandate/derive.test.mjs +0 -274
- package/lib/mandate/model.test.mjs +0 -164
- package/lib/mandate/refresh.test.mjs +0 -389
- package/lib/mcp/server.test.mjs +0 -426
- package/lib/model-router/auth-profiles.test.mjs +0 -580
- package/lib/model-router/catalog.test.mjs +0 -385
- package/lib/model-router/economics.test.mjs +0 -438
- package/lib/model-router/failover.test.mjs +0 -439
- package/lib/model-router/health.test.mjs +0 -338
- package/lib/model-router/integration-coverage.test.mjs +0 -831
- package/lib/model-router/integration.test.mjs +0 -564
- package/lib/model-router/ledger.test.mjs +0 -415
- package/lib/model-router/llm-task.test.mjs +0 -392
- package/lib/model-router/org-credentials.test.mjs +0 -265
- package/lib/model-router/pricing-refresh.test.mjs +0 -286
- package/lib/model-router/reconcile.test.mjs +0 -316
- package/lib/model-router/repair.test.mjs +0 -180
- package/lib/model-router/spawn.test.mjs +0 -446
- package/lib/model-router/taxonomy.test.mjs +0 -410
- package/lib/model-router.test.mjs +0 -1207
- package/lib/org/activity.test.mjs +0 -134
- package/lib/org/approvals.test.mjs +0 -216
- package/lib/org/awareness.test.mjs +0 -159
- package/lib/org/board-mine-cache.test.mjs +0 -53
- package/lib/org/board.test.mjs +0 -187
- package/lib/org/bootstrap-context.test.mjs +0 -153
- package/lib/org/client.test.mjs +0 -1206
- package/lib/org/cohort-client.test.mjs +0 -126
- package/lib/org/cost-sync.test.mjs +0 -153
- package/lib/org/doctor.test.mjs +0 -346
- package/lib/org/engagement-ledger.test.mjs +0 -112
- package/lib/org/engagement.test.mjs +0 -739
- package/lib/org/handoff.test.mjs +0 -269
- package/lib/org/inbound/directedness.test.mjs +0 -668
- package/lib/org/inbound/facts.test.mjs +0 -471
- package/lib/org/inbound/hydrate.test.mjs +0 -908
- package/lib/org/inbound/index.test.mjs +0 -429
- package/lib/org/inbound/project.test.mjs +0 -287
- package/lib/org/integration-tools.test.mjs +0 -160
- package/lib/org/keys.test.mjs +0 -92
- package/lib/org/knowledge.test.mjs +0 -326
- package/lib/org/leases.test.mjs +0 -235
- package/lib/org/mesh-directives.test.mjs +0 -110
- package/lib/org/mesh-integration.test.mjs +0 -127
- package/lib/org/mesh.test.mjs +0 -400
- package/lib/org/messaging.test.mjs +0 -471
- package/lib/org/param-contract.test.mjs +0 -477
- package/lib/org/policy.test.mjs +0 -237
- package/lib/org/protocol.checksum.test.mjs +0 -90
- package/lib/org/protocol.test.mjs +0 -323
- package/lib/org/push.test.mjs +0 -792
- package/lib/org/registry.test.mjs +0 -100
- package/lib/org/resource-tools.test.mjs +0 -361
- package/lib/org/tool-access.test.mjs +0 -144
- package/lib/org/tool-surface-integration.test.mjs +0 -120
- package/lib/org/tool-surface.test.mjs +0 -1268
- package/lib/org/typing.test.mjs +0 -291
- package/lib/org/ui-parity.test.mjs +0 -560
- package/lib/org/verify.test.mjs +0 -194
- package/lib/org/work-ledger.test.mjs +0 -273
- package/lib/plan/adoption-e2e.test.mjs +0 -366
- package/lib/plan/budget-enforcement.test.mjs +0 -400
- package/lib/plan/compile.test.mjs +0 -382
- package/lib/plan/emit.test.mjs +0 -269
- package/lib/plan/explain.test.mjs +0 -188
- package/lib/prompts/parallelism.test.mjs +0 -177
- package/lib/rag/rag.test.mjs +0 -505
- package/lib/rate-guard.test.mjs +0 -272
- package/lib/reactive-gate.test.mjs +0 -57
- package/lib/render.test.mjs +0 -68
- package/lib/resource-governor.test.mjs +0 -488
- package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
- package/lib/scheduling/jitter.test.mjs +0 -140
- package/lib/secrets/broker.test.mjs +0 -280
- package/lib/secrets/providers.test.mjs +0 -274
- package/lib/security/audit-engine.test.mjs +0 -424
- package/lib/security/coerce-args.test.mjs +0 -281
- package/lib/security/dangerous-tools.test.mjs +0 -68
- package/lib/security/external-content.test.mjs +0 -84
- package/lib/security/redact.test.mjs +0 -441
- package/lib/security/secret-equal.test.mjs +0 -55
- package/lib/session/config.test.mjs +0 -92
- package/lib/session/feed-core.test.mjs +0 -198
- package/lib/session/first-run.test.mjs +0 -121
- package/lib/session/frontdoor.test.mjs +0 -205
- package/lib/session/handoffs.test.mjs +0 -183
- package/lib/session/identity.test.mjs +0 -180
- package/lib/session/inbox-claims.test.mjs +0 -286
- package/lib/session/launch-args.test.mjs +0 -157
- package/lib/session/liveness.test.mjs +0 -100
- package/lib/session/status-summary.test.mjs +0 -118
- package/lib/session-permissions.test.mjs +0 -120
- package/lib/setup/claude-probe.test.mjs +0 -187
- package/lib/setup/completeness.test.mjs +0 -110
- package/lib/setup/context-pack.test.mjs +0 -89
- package/lib/setup/enrich.test.mjs +0 -115
- package/lib/setup/enroll-from-cohort.test.mjs +0 -300
- package/lib/setup/integration.test.mjs +0 -162
- package/lib/setup/io.test.mjs +0 -77
- package/lib/setup/runner.test.mjs +0 -132
- package/lib/setup/sections/identity.test.mjs +0 -234
- package/lib/setup/sections/inventory.test.mjs +0 -198
- package/lib/setup/sections/learning.test.mjs +0 -81
- package/lib/setup/sections/mandate.test.mjs +0 -388
- package/lib/setup/sections/messaging.test.mjs +0 -127
- package/lib/setup/sections/model.test.mjs +0 -240
- package/lib/setup/sections/org.test.mjs +0 -346
- package/lib/setup/sections/orgmail.test.mjs +0 -118
- package/lib/setup/sections/recovery.test.mjs +0 -98
- package/lib/setup/sections/subagents.test.mjs +0 -429
- package/lib/setup/sections/verify.test.mjs +0 -175
- package/lib/setup/sot.test.mjs +0 -81
- package/lib/setup/state.test.mjs +0 -115
- package/lib/singleton.test.mjs +0 -151
- package/lib/subagents/cli.test.mjs +0 -389
- package/lib/subagents/client.test.mjs +0 -309
- package/lib/subagents/gap.test.mjs +0 -234
- package/lib/subagents/lock.test.mjs +0 -248
- package/lib/subagents/manifest.test.mjs +0 -175
- package/lib/subagents/refs.test.mjs +0 -204
- package/lib/subagents/resolve.test.mjs +0 -422
- package/lib/subagents/schema.test.mjs +0 -328
- package/lib/telemetry/alerts.test.mjs +0 -109
- package/lib/telemetry/collect.test.mjs +0 -1274
- package/lib/tool-definitions-integration.test.mjs +0 -83
- package/lib/tool-definitions.test.mjs +0 -437
- package/lib/upgrade/global-refresh.test.mjs +0 -65
- package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
- package/lib/upgrade/post-steps.test.mjs +0 -200
- package/lib/upgrade/verify.test.mjs +0 -164
- package/lib/util/fetch-timeout.test.mjs +0 -202
- package/lib/util/reconnect.test.mjs +0 -369
- package/lib/util/unhandled.test.mjs +0 -216
- package/lib/voice/outbound.test.mjs +0 -69
- package/lib/voice/session-rotation.test.mjs +0 -114
- package/lib/voice/stt.test.mjs +0 -226
- package/lib/voice/voice.test.mjs +0 -990
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
- package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
- package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
- package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
- package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
- package/scripts/ci/check-skill-packs.test.mjs +0 -495
- package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
- package/scripts/ci/check.test.mjs +0 -194
- package/scripts/ci/conformance-org-api.test.mjs +0 -425
- package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
- package/scripts/collective/hook-runner.test.mjs +0 -173
- package/scripts/cost/fleet-digest.test.mjs +0 -207
- package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
- package/scripts/cost/track-claude-usage.test.mjs +0 -148
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
- package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
- package/scripts/daemon/agent-daemon.test.mjs +0 -995
- package/scripts/daemon/assurance-e2e.test.mjs +0 -613
- package/scripts/daemon/assurance.test.mjs +0 -1791
- package/scripts/daemon/board-mirror.test.mjs +0 -165
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
- package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
- package/scripts/daemon/cadence-consumer.test.mjs +0 -776
- package/scripts/daemon/cadence-handlers.test.mjs +0 -837
- package/scripts/daemon/classifier-identity.test.mjs +0 -137
- package/scripts/daemon/classifier.test.mjs +0 -266
- package/scripts/daemon/classify-kind.test.mjs +0 -40
- package/scripts/daemon/context-compiler.test.mjs +0 -406
- package/scripts/daemon/deliver.test.mjs +0 -564
- package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
- package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
- package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
- package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
- package/scripts/daemon/execution-ladder.test.mjs +0 -470
- package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
- package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
- package/scripts/daemon/inbox-deferral.test.mjs +0 -336
- package/scripts/daemon/inbox-wake.test.mjs +0 -199
- package/scripts/daemon/integration.test.mjs +0 -149
- package/scripts/daemon/lib/self-echo.test.mjs +0 -153
- package/scripts/daemon/lib/session-router.test.mjs +0 -554
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -556
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -221
- package/scripts/daemon/sdk-version.test.mjs +0 -31
- package/scripts/daemon/session-lock.test.mjs +0 -252
- package/scripts/daemon/session-outcomes.test.mjs +0 -533
- package/scripts/daemon/typing-registry.test.mjs +0 -102
- package/scripts/hooks/pre-send-audit.test.mjs +0 -354
- package/scripts/huddle/huddle-prompt.test.mjs +0 -176
- package/scripts/local-triggers/autoupdate.test.mjs +0 -518
- package/scripts/local-triggers/generate-plists.test.mjs +0 -456
- package/scripts/media-generation/brand-clause.test.mjs +0 -135
- package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
- package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
- package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
- package/scripts/poller/slack-socket-mode.test.mjs +0 -805
- package/scripts/poller-launchd/install.test.mjs +0 -243
- package/scripts/restore-from-backup.test.mjs +0 -181
- package/scripts/session/feed.test.mjs +0 -196
- package/scripts/session/supervisor-sh.test.mjs +0 -218
- package/scripts/session/supervisor.test.mjs +0 -482
- package/scripts/setup/configure-macos.test.mjs +0 -306
- package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
- package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
- package/scripts/setup/generate-capability.test.mjs +0 -134
- package/scripts/setup/init-agent.test.mjs +0 -370
- package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
- package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
- package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/agents/usage.mjs — a run's usage and cost across its subagents (pure).
|
|
3
|
+
*
|
|
4
|
+
* Every child run leaves a usage record: the tier that served it, its
|
|
5
|
+
* normalised usage (design §4.5), its gateway cost in integer micro-USD and its
|
|
6
|
+
* request ids. The parent's result reports:
|
|
7
|
+
*
|
|
8
|
+
* usage / cohort.usage parent + every child (what the run consumed)
|
|
9
|
+
* total_cost_usd / costMicros parent + every child, exact or null: one
|
|
10
|
+
* request without a settled cost nulls the sum,
|
|
11
|
+
* exactly as a single run does
|
|
12
|
+
* cohort.requestIds parent's, then each child's, in finish order
|
|
13
|
+
* modelUsage per tier, in the claude result's field names
|
|
14
|
+
* cohort.modelUsage {byTier:{[tier]:{usage, costMicros, requests}}, total:{usage, costMicros}}
|
|
15
|
+
*
|
|
16
|
+
* @module lib/engine/agents/usage
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { addUsage, emptyUsage, toResultUsage } from "../wire/usage.mjs";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} UsageRecord
|
|
23
|
+
* @property {string} tier
|
|
24
|
+
* @property {import('../wire/usage.mjs').NormalisedUsage} usage
|
|
25
|
+
* @property {string|null} costMicros null = unknown (may have been billed with no figure)
|
|
26
|
+
* @property {string[]} requestIds
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A loop outcome as a usage record. A run with no completed turn and an exact
|
|
31
|
+
* (empty) cost was never billed: its cost is "0", not unknown.
|
|
32
|
+
* @param {any} outcome runLoop's result
|
|
33
|
+
* @param {string} fallbackTier
|
|
34
|
+
* @returns {UsageRecord}
|
|
35
|
+
*/
|
|
36
|
+
export function recordFromOutcome(outcome, fallbackTier) {
|
|
37
|
+
const exact = outcome.costComplete !== false;
|
|
38
|
+
return {
|
|
39
|
+
tier: outcome.modelTier || fallbackTier,
|
|
40
|
+
usage: outcome.usage ?? emptyUsage("estimated"),
|
|
41
|
+
costMicros: outcome.costMicros != null ? outcome.costMicros : exact ? "0" : null,
|
|
42
|
+
requestIds: [...(outcome.requestIds ?? [])],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @param {Array<string|null>} parts */
|
|
47
|
+
function sumMicros(parts) {
|
|
48
|
+
let total = 0n;
|
|
49
|
+
for (const p of parts) {
|
|
50
|
+
if (p == null) return null;
|
|
51
|
+
total += BigInt(p);
|
|
52
|
+
}
|
|
53
|
+
return total.toString();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Combine the parent's record with its children's.
|
|
58
|
+
* @param {UsageRecord} parent @param {UsageRecord[]} children
|
|
59
|
+
*/
|
|
60
|
+
export function aggregateUsage(parent, children) {
|
|
61
|
+
const all = [parent, ...children];
|
|
62
|
+
/** @type {Record<string, {usage:any, costParts:Array<string|null>, requests:number}>} */
|
|
63
|
+
const tiers = {};
|
|
64
|
+
for (const r of all) {
|
|
65
|
+
const t = (tiers[r.tier] ??= { usage: emptyUsage("provider"), costParts: [], requests: 0 });
|
|
66
|
+
t.usage = addUsage(t.usage, r.usage);
|
|
67
|
+
t.costParts.push(r.costMicros);
|
|
68
|
+
t.requests += r.requestIds.length;
|
|
69
|
+
}
|
|
70
|
+
const usage = all.reduce((u, r) => addUsage(u, r.usage), emptyUsage("provider"));
|
|
71
|
+
const costMicros = sumMicros(all.map((r) => r.costMicros));
|
|
72
|
+
/** @type {Record<string, any>} */
|
|
73
|
+
const byTier = {};
|
|
74
|
+
/** @type {Record<string, any>} */
|
|
75
|
+
const modelUsage = {};
|
|
76
|
+
for (const [tier, t] of Object.entries(tiers)) {
|
|
77
|
+
const micros = sumMicros(t.costParts);
|
|
78
|
+
byTier[tier] = { usage: t.usage, costMicros: micros, requests: t.requests };
|
|
79
|
+
const ru = toResultUsage(t.usage);
|
|
80
|
+
modelUsage[tier] = {
|
|
81
|
+
inputTokens: ru.input_tokens,
|
|
82
|
+
outputTokens: ru.output_tokens,
|
|
83
|
+
cacheReadInputTokens: ru.cache_read_input_tokens,
|
|
84
|
+
cacheCreationInputTokens: ru.cache_creation_input_tokens,
|
|
85
|
+
costUSD: micros == null ? null : Number(BigInt(micros)) / 1e6,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
usage,
|
|
90
|
+
costMicros,
|
|
91
|
+
requestIds: all.flatMap((r) => r.requestIds),
|
|
92
|
+
modelUsage,
|
|
93
|
+
cohortModelUsage: { byTier, total: { usage, costMicros } },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/auth-status.mjs — `cohort auth status [--json] [--base-url <url>]`:
|
|
3
|
+
* does this process's gateway credential work? (conformance rows 1 and 25)
|
|
4
|
+
*
|
|
5
|
+
* Resolves the credential the way a run does (wire/token-provider.mjs: the
|
|
6
|
+
* token helper, else COHORT_LLM_TOKEN) and makes the cheapest authenticated
|
|
7
|
+
* call the gateway has, `GET /cohort/v1/quota` — no model request, no spend.
|
|
8
|
+
* A 401 on a helper credential re-runs the helper once, as the wire does.
|
|
9
|
+
*
|
|
10
|
+
* stdout (--json) {"ok":true,"baseUrl":"…","principal":{…}|null}
|
|
11
|
+
* {"ok":false,"baseUrl":"…"|null,"principal":null,"error":{"code","message"}}
|
|
12
|
+
* exit 0 ok · 1 not authenticated or unreachable · 2 bad arguments
|
|
13
|
+
*
|
|
14
|
+
* The token itself is never printed.
|
|
15
|
+
*
|
|
16
|
+
* @module lib/engine/auth-status
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createTokenProvider } from "./wire/token-provider.mjs";
|
|
20
|
+
import { tokenFailureError } from "./wire/errors.mjs";
|
|
21
|
+
|
|
22
|
+
export const AUTH_STATUS_TIMEOUT_MS = 10_000;
|
|
23
|
+
|
|
24
|
+
const USAGE = `Usage: cohort auth status [--json] [--base-url <url>]
|
|
25
|
+
|
|
26
|
+
Checks the gateway credential (COHORT_LLM_TOKEN_HELPER or COHORT_LLM_TOKEN)
|
|
27
|
+
against GET /cohort/v1/quota. Exit 0 when it is accepted, 1 otherwise.
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string[]} argv the words after `auth`
|
|
32
|
+
* @returns {{ok:true, opts:{json:boolean, baseUrl:string|null, help:boolean}}|{ok:false, error:string}}
|
|
33
|
+
*/
|
|
34
|
+
export function parseAuthStatusArgs(argv) {
|
|
35
|
+
const opts = { json: false, baseUrl: /** @type {string|null} */ (null), help: false };
|
|
36
|
+
if (argv[0] === "-h" || argv[0] === "--help") return { ok: true, opts: { ...opts, help: true } };
|
|
37
|
+
if (argv[0] !== "status") return { ok: false, error: `unknown auth subcommand ${JSON.stringify(argv[0] ?? "")}: expected "auth status"` };
|
|
38
|
+
for (let i = 1; i < argv.length; i++) {
|
|
39
|
+
const a = argv[i];
|
|
40
|
+
if (a === "--json") opts.json = true;
|
|
41
|
+
else if (a === "-h" || a === "--help") opts.help = true;
|
|
42
|
+
else if (a === "--base-url") {
|
|
43
|
+
const v = argv[++i];
|
|
44
|
+
if (!v) return { ok: false, error: "--base-url needs a value" };
|
|
45
|
+
opts.baseUrl = v;
|
|
46
|
+
} else if (a.startsWith("--base-url=")) opts.baseUrl = a.slice("--base-url=".length);
|
|
47
|
+
else return { ok: false, error: `unknown flag for auth status: ${a}` };
|
|
48
|
+
}
|
|
49
|
+
return { ok: true, opts };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The status for a quota answer. Pure.
|
|
54
|
+
* @param {{baseUrl:string, status:number, body:any}} p
|
|
55
|
+
*/
|
|
56
|
+
export function authStatusFromQuota({ baseUrl, status, body }) {
|
|
57
|
+
if (status >= 200 && status < 300 && body && typeof body === "object") {
|
|
58
|
+
return { ok: true, baseUrl, principal: body.principal && typeof body.principal === "object" ? body.principal : null };
|
|
59
|
+
}
|
|
60
|
+
const e = body && typeof body === "object" && body.error && typeof body.error === "object" ? body.error : null;
|
|
61
|
+
const code = typeof e?.code === "string" && e.code ? e.code : status === 401 ? "cohort_invalid_token" : status === 403 ? "cohort_forbidden" : `http_${status}`;
|
|
62
|
+
const message = typeof e?.message === "string" && e.message ? e.message : `the gateway refused the credential (HTTP ${status})`;
|
|
63
|
+
return { ok: false, baseUrl, principal: null, error: { code, message } };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string[]} argv the words after `auth`
|
|
68
|
+
* @param {{env?:Record<string,string|undefined>, stdout?:{write:(s:string)=>unknown}, stderr?:{write:(s:string)=>unknown}, fetchImpl?:typeof fetch, now?:()=>number, runTokenHelper?:(command:string, o:{env:Record<string,string|undefined>})=>Promise<string>, timeoutMs?:number}} [deps]
|
|
69
|
+
* @returns {Promise<number>}
|
|
70
|
+
*/
|
|
71
|
+
export async function runAuthStatus(argv, deps = {}) {
|
|
72
|
+
const env = deps.env ?? process.env;
|
|
73
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
74
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
75
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
76
|
+
const parsed = parseAuthStatusArgs(argv);
|
|
77
|
+
const wantsJson = argv.includes("--json");
|
|
78
|
+
if (!parsed.ok) {
|
|
79
|
+
if (wantsJson) stdout.write(`${JSON.stringify({ ok: false, baseUrl: null, principal: null, error: { code: "invalid_arguments", message: parsed.error } })}\n`);
|
|
80
|
+
else stderr.write(`cohort auth: ${parsed.error}\n\n${USAGE}`);
|
|
81
|
+
return 2;
|
|
82
|
+
}
|
|
83
|
+
const o = parsed.opts;
|
|
84
|
+
if (o.help) {
|
|
85
|
+
stdout.write(USAGE);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @param {{ok:boolean, baseUrl:string|null, principal:any, error?:{code:string, message:string}}} r */
|
|
90
|
+
const emit = (r) => {
|
|
91
|
+
if (o.json) stdout.write(`${JSON.stringify(r)}\n`);
|
|
92
|
+
else if (r.ok) stdout.write(`Authenticated to ${r.baseUrl}${r.principal ? ` as ${JSON.stringify(r.principal)}` : ""}\n`);
|
|
93
|
+
else stdout.write(`Not authenticated${r.baseUrl ? ` to ${r.baseUrl}` : ""}: ${r.error?.code}: ${r.error?.message}\n`);
|
|
94
|
+
return r.ok ? 0 : 1;
|
|
95
|
+
};
|
|
96
|
+
const fail = (/** @type {string|null} */ baseUrl, /** @type string */ code, /** @type string */ message) => emit({ ok: false, baseUrl, principal: null, error: { code, message } });
|
|
97
|
+
|
|
98
|
+
const baseUrl = String(o.baseUrl ?? env.COHORT_LLM_BASE_URL ?? "").trim().replace(/\/+$/, "");
|
|
99
|
+
if (!baseUrl) return fail(null, "missing_base_url", "no gateway URL: pass --base-url or set COHORT_LLM_BASE_URL");
|
|
100
|
+
const provider = createTokenProvider({ env, ...(deps.now ? { now: deps.now } : {}), ...(deps.runTokenHelper ? { runHelper: deps.runTokenHelper } : {}) });
|
|
101
|
+
if (!provider.ok) return fail(baseUrl, provider.error.code, provider.error.message);
|
|
102
|
+
const refreshable = typeof provider.token === "function";
|
|
103
|
+
|
|
104
|
+
/** @type {string|undefined} */
|
|
105
|
+
let rejected;
|
|
106
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
107
|
+
let token;
|
|
108
|
+
try {
|
|
109
|
+
token = typeof provider.token === "function" ? await provider.token(attempt > 0 ? { refresh: true, rejected } : { refresh: false }) : provider.token;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// A down token endpoint is cohort_unavailable (an outage the probe reports
|
|
112
|
+
// as unknown), not token_unavailable (a credential answer) — CF-143.
|
|
113
|
+
const te = tokenFailureError(e);
|
|
114
|
+
return fail(baseUrl, te.code, te.message);
|
|
115
|
+
}
|
|
116
|
+
let res;
|
|
117
|
+
try {
|
|
118
|
+
res = await fetchImpl(`${baseUrl}/cohort/v1/quota`, {
|
|
119
|
+
method: "GET",
|
|
120
|
+
headers: { authorization: `Bearer ${token}`, accept: "application/json" },
|
|
121
|
+
signal: AbortSignal.timeout(deps.timeoutMs ?? AUTH_STATUS_TIMEOUT_MS),
|
|
122
|
+
});
|
|
123
|
+
} catch (e) {
|
|
124
|
+
return fail(baseUrl, "cohort_unavailable", `the gateway did not answer: ${e instanceof Error ? e.message : String(e)}`);
|
|
125
|
+
}
|
|
126
|
+
let body = null;
|
|
127
|
+
try {
|
|
128
|
+
body = await res.json();
|
|
129
|
+
} catch {
|
|
130
|
+
body = null;
|
|
131
|
+
}
|
|
132
|
+
if (res.status === 401 && refreshable && attempt === 0) {
|
|
133
|
+
rejected = token;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
return emit(authStatusFromQuota({ baseUrl, status: res.status, body }));
|
|
137
|
+
}
|
|
138
|
+
return fail(baseUrl, "cohort_invalid_token", "unreachable");
|
|
139
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/budget.mjs — the per-run spend cap behind `--max-budget-usd` (W4-E1, conformance row 24).
|
|
3
|
+
*
|
|
4
|
+
* One meter per `cohort run`, shared by the run's own loop and every subagent
|
|
5
|
+
* and workflow child it starts, so the cap is on what the whole run spends:
|
|
6
|
+
*
|
|
7
|
+
* record(res) folds one gateway request's cost in: the `x-cohort-cost-micros`
|
|
8
|
+
* header or the SSE cohort frame, which the wires normalise to
|
|
9
|
+
* `res.cohort.costMicros` (WebSearch reports the same shape).
|
|
10
|
+
* exhausted() spent >= cap. Every loop asks before each model call (and
|
|
11
|
+
* before a compaction summary); once true no further model call
|
|
12
|
+
* is made — the current batch of tool results is finished first.
|
|
13
|
+
*
|
|
14
|
+
* The figure is the gateway's, never a local estimate. A request that failed
|
|
15
|
+
* after the gateway accepted it may have been billed with no figure: the meter
|
|
16
|
+
* cannot count what it was not told, so `unknownCost` is set and the cap is
|
|
17
|
+
* enforced on the known spend (a lower bound).
|
|
18
|
+
*
|
|
19
|
+
* ## CF-157 — "nothing spent" and "the server will not tell me" are not the same
|
|
20
|
+
*
|
|
21
|
+
* While the gateway runs `COHORT_BILLING_ENFORCE=shadow` it reports
|
|
22
|
+
* `cost_micros: null` on every frame and header and `{}` from
|
|
23
|
+
* `/cohort/v1/quota`. Measured again on 2026-09-22
|
|
24
|
+
* (`req_bf94feaab6405214071532a5`): still null. The old meter folded that into
|
|
25
|
+
* one boolean, left `spent` at 0 and answered `exhausted() === false` for the
|
|
26
|
+
* life of the run — so `--max-budget-usd` LOOKED armed and could never fire.
|
|
27
|
+
* That is the worst of both: an operator believes there is a cap, and there
|
|
28
|
+
* is not one.
|
|
29
|
+
*
|
|
30
|
+
* So cost visibility is now a state, not a flag:
|
|
31
|
+
*
|
|
32
|
+
* exact every billed request carried a figure — the cap binds exactly.
|
|
33
|
+
* partial some did, some did not — the cap binds on a lower bound.
|
|
34
|
+
* blind requests were billed and NONE carried a figure — THE CAP CANNOT
|
|
35
|
+
* BIND. `bindable()` is false and `notice()` says so once per run.
|
|
36
|
+
*
|
|
37
|
+
* **The choice: warn, never refuse.** An unbindable budget does not stop a run
|
|
38
|
+
* and does not refuse to start, deliberately, for two reasons. Blindness is not
|
|
39
|
+
* knowable at startup — it is only observable after a request has been billed,
|
|
40
|
+
* so "refuse to start" cannot be implemented honestly. And seats are live: a
|
|
41
|
+
* gateway that stops pricing would otherwise take down every seat at once,
|
|
42
|
+
* turning a billing-mode change into an outage. The run proceeds, uncapped, and
|
|
43
|
+
* says so on stderr. A caller that needs a hard stop should read
|
|
44
|
+
* `snapshot().bindable` and decide for itself.
|
|
45
|
+
*
|
|
46
|
+
* **Tokens are not money.** The terminal frame's `usage` (CF-158) gives an
|
|
47
|
+
* EXACT token count, and the meter accumulates it as `observedTokens` so a
|
|
48
|
+
* blind run still reports a measured quantity. It is deliberately NOT converted
|
|
49
|
+
* to dollars: prices live on hq's side in `docs/deepinfra-catalogue.json` (per
|
|
50
|
+
* provider slug, keyed `input_tokens` / `cache_read_tokens`), which
|
|
51
|
+
* `packages/llm/src/capabilities.ts` points at but carries none of itself —
|
|
52
|
+
* and this repo has no price table at all, so any USD figure derived here would be a
|
|
53
|
+
* guess wearing a measurement's clothes — the exact failure CF-157 is about.
|
|
54
|
+
* `spent` therefore stays at the gateway's own figures, and only those.
|
|
55
|
+
*
|
|
56
|
+
* Pure apart from its own counters; no I/O.
|
|
57
|
+
*
|
|
58
|
+
* @module lib/engine/budget
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
import { addUsage, emptyUsage, readFrameUsage, totalTokens } from "./wire/usage.mjs";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `--max-budget-usd` as integer micro-USD, or an error. PURE.
|
|
65
|
+
* @param {unknown} value
|
|
66
|
+
* @returns {{ok:true, usd:number, micros:bigint}|{ok:false, error:string}}
|
|
67
|
+
*/
|
|
68
|
+
export function parseBudgetUsd(value) {
|
|
69
|
+
const s = String(value ?? "").trim();
|
|
70
|
+
const n = Number(s);
|
|
71
|
+
if (s === "" || !Number.isFinite(n) || n <= 0) return { ok: false, error: `--max-budget-usd must be a positive number of US dollars, got "${value}"` };
|
|
72
|
+
const micros = BigInt(Math.round(n * 1e6));
|
|
73
|
+
if (micros <= 0n) return { ok: false, error: `--max-budget-usd is below one micro-dollar: "${value}"` };
|
|
74
|
+
return { ok: true, usd: n, micros };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @param {bigint} micros */
|
|
78
|
+
export function microsToUsdNumber(micros) {
|
|
79
|
+
return Number(micros) / 1e6;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {{maxMicros:bigint}} o
|
|
84
|
+
*/
|
|
85
|
+
export function createBudgetMeter({ maxMicros }) {
|
|
86
|
+
let spent = 0n;
|
|
87
|
+
let unknownCost = false;
|
|
88
|
+
let requests = 0;
|
|
89
|
+
/** Billed requests that carried a cost figure. */
|
|
90
|
+
let priced = 0;
|
|
91
|
+
/** Billed requests that carried NO cost figure — the shadow-mode signature. */
|
|
92
|
+
let unpriced = 0;
|
|
93
|
+
/** Exact token counts off the terminal frame (CF-158). Never priced. */
|
|
94
|
+
let tokens = emptyUsage("provider");
|
|
95
|
+
let tokenRequests = 0;
|
|
96
|
+
let noticed = false;
|
|
97
|
+
|
|
98
|
+
/** @returns {'exact'|'partial'|'blind'} */
|
|
99
|
+
const visibility = () => (unpriced === 0 ? "exact" : priced === 0 ? "blind" : "partial");
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
maxMicros,
|
|
103
|
+
/** @param {any} res a wire result (ok or not) */
|
|
104
|
+
record(res) {
|
|
105
|
+
if (!res || typeof res !== "object") return;
|
|
106
|
+
requests++;
|
|
107
|
+
// CF-158: the frame's usage is exact for TOKENS. Kept separately from
|
|
108
|
+
// `spent`, which only ever holds the gateway's own money figures.
|
|
109
|
+
const u = readFrameUsage(res.cohort?.usage);
|
|
110
|
+
if (u) {
|
|
111
|
+
tokens = addUsage(tokens, u);
|
|
112
|
+
tokenRequests++;
|
|
113
|
+
}
|
|
114
|
+
const c = res.cohort?.costMicros;
|
|
115
|
+
if (c != null) {
|
|
116
|
+
try {
|
|
117
|
+
spent += BigInt(c);
|
|
118
|
+
priced++;
|
|
119
|
+
return;
|
|
120
|
+
} catch {
|
|
121
|
+
/* an unparseable figure is an unknown one */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// No figure: a refusal answered before acceptance (a status) was never billed.
|
|
125
|
+
if (res.ok || res.accepted === true || res.error?.status == null) {
|
|
126
|
+
unknownCost = true;
|
|
127
|
+
unpriced++;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
// A blind meter never reaches the cap: spent stays 0 because no cost was
|
|
131
|
+
// ever reported. That is reported by bindable()/notice(), never papered
|
|
132
|
+
// over by treating an unreported cost as zero spend.
|
|
133
|
+
exhausted: () => spent >= maxMicros,
|
|
134
|
+
spentMicros: () => spent,
|
|
135
|
+
/** 'exact' | 'partial' | 'blind' — see the module header. */
|
|
136
|
+
costVisibility: visibility,
|
|
137
|
+
/** False when the gateway priced nothing, i.e. the cap cannot fire at all. */
|
|
138
|
+
bindable: () => visibility() !== "blind",
|
|
139
|
+
/**
|
|
140
|
+
* A one-time, run-level warning when the cap cannot bind, else null.
|
|
141
|
+
* Self-clearing, so a caller may ask after every turn and the operator
|
|
142
|
+
* still reads it once.
|
|
143
|
+
* @returns {string|null}
|
|
144
|
+
*/
|
|
145
|
+
notice() {
|
|
146
|
+
if (noticed || visibility() !== "blind") return null;
|
|
147
|
+
noticed = true;
|
|
148
|
+
const observed =
|
|
149
|
+
tokenRequests > 0
|
|
150
|
+
? `${totalTokens(tokens)} tokens over ${tokenRequests} request(s)`
|
|
151
|
+
: "no token counts either";
|
|
152
|
+
return (
|
|
153
|
+
// State the observation; do NOT diagnose the cause. An absent
|
|
154
|
+
// cost_micros is all this meter can see, and --base-url is free-form:
|
|
155
|
+
// a non-Cohort Anthropic-compatible endpoint, a proxy that drops the
|
|
156
|
+
// header, or a gateway bug land here identically. Naming shadow
|
|
157
|
+
// billing as THE reason would be inferring a cause from an absence,
|
|
158
|
+
// which is the habit this lane exists to break.
|
|
159
|
+
`--max-budget-usd ($${microsToUsdNumber(maxMicros)}) is NOT being enforced: ` +
|
|
160
|
+
`the endpoint priced none of ${unpriced} billed request(s) (no cost_micros; shadow billing is the usual cause), ` +
|
|
161
|
+
`so the run reports $0 spent and the cap can never trigger. Measured instead: ${observed} ` +
|
|
162
|
+
`— tokens are exact, but this SDK has no per-tier price table and does not convert them to dollars. ` +
|
|
163
|
+
`See docs/engine/eval.md §3 (CF-157).`
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
/**
|
|
167
|
+
* The `cohort.budget` block of a result.
|
|
168
|
+
*
|
|
169
|
+
* **`spentUsd` / `spentMicros` / `exhausted` are only meaningful when
|
|
170
|
+
* `bindable` is true.** On a blind run they read `0` / `"0"` / `false`
|
|
171
|
+
* because the endpoint priced nothing — not because nothing was spent.
|
|
172
|
+
* Any consumer that shows or acts on the spend must read `bindable` (or
|
|
173
|
+
* `costVisibility`) in the same breath; `observedTokens` is the figure
|
|
174
|
+
* that is actually measured on such a run.
|
|
175
|
+
*/
|
|
176
|
+
snapshot: () => ({
|
|
177
|
+
maxUsd: microsToUsdNumber(maxMicros),
|
|
178
|
+
maxMicros: maxMicros.toString(),
|
|
179
|
+
spentUsd: microsToUsdNumber(spent),
|
|
180
|
+
spentMicros: spent.toString(),
|
|
181
|
+
exhausted: spent >= maxMicros,
|
|
182
|
+
unknownCost,
|
|
183
|
+
requests,
|
|
184
|
+
costVisibility: visibility(),
|
|
185
|
+
bindable: visibility() !== "blind",
|
|
186
|
+
pricedRequests: priced,
|
|
187
|
+
unpricedRequests: unpriced,
|
|
188
|
+
// Exact token counts, or null when no frame reported any. Never dollars.
|
|
189
|
+
observedTokens: tokenRequests > 0 ? { ...tokens, total: totalTokens(tokens) } : null,
|
|
190
|
+
}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** @typedef {ReturnType<typeof createBudgetMeter>} BudgetMeter */
|