@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
|
@@ -60,7 +60,9 @@ import { resultTextFromStdout } from "./session-outcomes.mjs";
|
|
|
60
60
|
// The ack generator funnels through `claude --print` like every other daemon
|
|
61
61
|
// model call (CEO directive 2026-04-27: no direct Anthropic API from the
|
|
62
62
|
// daemon), pinned to the cheapest model with a tight, env-tunable timeout.
|
|
63
|
-
import { resolveClaudeBin
|
|
63
|
+
import { resolveClaudeBin } from "../../lib/claude-bin.mjs";
|
|
64
|
+
import { buildSpawn } from "../../lib/runtime/adapter.mjs";
|
|
65
|
+
import { resolveSeatSpawn } from "../../lib/runtime/seat-engine.mjs";
|
|
64
66
|
import { sessionPermissionArgs } from "../../lib/session-permissions.mjs";
|
|
65
67
|
// Second self-echo guard: even if an echo somehow reaches the ack decision,
|
|
66
68
|
// this agent never acknowledges its own message back at itself.
|
|
@@ -76,6 +78,7 @@ import * as counters from "../../lib/diagnostics/counters.mjs";
|
|
|
76
78
|
// the only composer of interim copy left in the system, and carries the two
|
|
77
79
|
// copy rules (GENERIC_OPENER, FORWARD_PROMISE) every interim must satisfy.
|
|
78
80
|
import { replyTier } from "../../lib/assurance/tier.mjs";
|
|
81
|
+
import { isRollCallItem } from "../../lib/org/inbound/broadcast.mjs";
|
|
79
82
|
import {
|
|
80
83
|
claimRoomInterim,
|
|
81
84
|
claimRoomNotice,
|
|
@@ -602,6 +605,14 @@ export function shouldAcknowledge(o = {}) {
|
|
|
602
605
|
logDispatchGateRefusal(item, "peer-ack-shaped");
|
|
603
606
|
return { ack: false, reason: "peer-ack-shaped", emitterClass: true, tier };
|
|
604
607
|
}
|
|
608
|
+
// ── A ROLL-CALL NEVER GETS AN INTERIM ─────────────────────────────────────
|
|
609
|
+
// A human asked the whole room for a line each. Thirteen "checking now"
|
|
610
|
+
// lines followed by thirteen answers is twice the traffic and half the
|
|
611
|
+
// signal; the answer IS the acknowledgement. `ack:false` here makes
|
|
612
|
+
// `interimForbidden` durable on the debt, so no later sweep in any process
|
|
613
|
+
// posts one either. The caller still opens the debt — silence about timing
|
|
614
|
+
// is not silence about outcome.
|
|
615
|
+
if (isRollCallItem(item)) return { ack: false, reason: "roll-call", tier };
|
|
605
616
|
// ── THE TIER ──────────────────────────────────────────────────────────────
|
|
606
617
|
// The answer tier is silence by construction: the reply arrives in this turn,
|
|
607
618
|
// so an interim in front of it is the content-free traffic this whole change
|
|
@@ -650,21 +661,46 @@ const CLAUDE_BIN = resolveClaudeBin();
|
|
|
650
661
|
* same spawn-not-exec injection hygiene) rather than importing it, because the
|
|
651
662
|
* classifier's 30s budget is a different promise from this path's ~5s one.
|
|
652
663
|
*/
|
|
664
|
+
/**
|
|
665
|
+
* The ack lane's spawn from the runtime adapter (lane "ack"): --print
|
|
666
|
+
* <permissions> <MCP by source> --model <m> --append-system-prompt <sys>, prompt
|
|
667
|
+
* on stdin; stock env posture (stale API keys stripped, PATH that can find the
|
|
668
|
+
* binary's runtime under launchd).
|
|
669
|
+
*
|
|
670
|
+
* W4-E1 (CF-44): `seat` is the seat's engine resolved once for this spawn. A
|
|
671
|
+
* claude seat adds no fields (unchanged); an engine-cohort seat runs `cli.mjs run`.
|
|
672
|
+
* @param {{systemPrompt:string, userPrompt:string, env?:object, bin?:string, seat?:{fields:object}}} o
|
|
673
|
+
*/
|
|
674
|
+
export function ackSpawn({ systemPrompt, userPrompt, env = process.env, bin = CLAUDE_BIN, seat = { fields: {} } }) {
|
|
675
|
+
return buildSpawn({
|
|
676
|
+
lane: "ack",
|
|
677
|
+
bin,
|
|
678
|
+
model: ACK_MODEL,
|
|
679
|
+
systemPrompt,
|
|
680
|
+
prompt: userPrompt,
|
|
681
|
+
permissions: sessionPermissionArgs({ source: "ack" }),
|
|
682
|
+
mcp: { source: "ack" },
|
|
683
|
+
env,
|
|
684
|
+
...seat.fields,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
653
688
|
function runAckModel(systemPrompt, userPrompt, o = {}) {
|
|
689
|
+
// W4-E1: the seat's engine, once per spawn (a claude seat reads config/agent.json only).
|
|
690
|
+
return resolveSeatSpawn(AGENT_REPO_DIR).then((seat) => runAckModelWith(seat, systemPrompt, userPrompt, o));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function runAckModelWith(seat, systemPrompt, userPrompt, o = {}) {
|
|
654
694
|
const timeoutMs = Number.isFinite(o.timeoutMs) ? o.timeoutMs : ACK_GEN_TIMEOUT_MS;
|
|
655
695
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
];
|
|
663
|
-
const proc = spawn(CLAUDE_BIN, args, {
|
|
696
|
+
const spawnSpec = ackSpawn({ systemPrompt, userPrompt, seat });
|
|
697
|
+
if (!spawnSpec.ok) {
|
|
698
|
+
rejectPromise(new Error(`claude CLI unavailable: ${spawnSpec.error.message}`));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const proc = spawn(spawnSpec.bin, spawnSpec.argv, {
|
|
664
702
|
stdio: ["pipe", "pipe", "pipe"],
|
|
665
|
-
|
|
666
|
-
// actually find the binary's runtime under launchd.
|
|
667
|
-
env: { ...process.env, PATH: augmentedPath(), ANTHROPIC_API_KEY: "", ANTHROPIC_AUTH_TOKEN: "" },
|
|
703
|
+
env: spawnSpec.env,
|
|
668
704
|
});
|
|
669
705
|
let stdout = "";
|
|
670
706
|
let settled = false;
|
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
import { readFrontDoorState, shouldHandOffTick } from "../../lib/session/frontdoor.mjs";
|
|
95
95
|
import { writeHandoff, listHandoffs, expireHandoffs, pruneHandoffs, handoffPaths } from "../../lib/session/handoffs.mjs";
|
|
96
96
|
import { writeFileAtomic } from "../../lib/fs-atomic.mjs";
|
|
97
|
-
import { resolveClaudeBin as sharedResolveClaude
|
|
97
|
+
import { resolveClaudeBin as sharedResolveClaude } from "../../lib/claude-bin.mjs";
|
|
98
98
|
import { getCadenceDef } from "./cadence-handlers.mjs";
|
|
99
99
|
import { obligationAllowedUnderPosture } from "../../lib/plan/compile.mjs";
|
|
100
100
|
import { isHumanLaneCadence } from "../../lib/cadences.mjs";
|
|
@@ -112,7 +112,10 @@ import {
|
|
|
112
112
|
loadRoutingConfig,
|
|
113
113
|
resolveChain,
|
|
114
114
|
} from "../../lib/model-router.mjs";
|
|
115
|
-
import {
|
|
115
|
+
import { buildSpawn, admitSpawn, isCohortRouted, readSeatEngine } from "../../lib/runtime/adapter.mjs";
|
|
116
|
+
import { seatEngineFields, engineRunShape, seatTokenSync } from "../../lib/runtime/seat-engine.mjs";
|
|
117
|
+
import { seatCohortClients } from "../../lib/org/llm-token.mjs";
|
|
118
|
+
import { COHORT_LLM_BILLED_BY } from "../../lib/org/cost-sync.mjs";
|
|
116
119
|
import { budgetLadder, batchLaneFor, spawnKnobsFor } from "../../lib/model-router/economics.mjs";
|
|
117
120
|
import * as budgetGuardForBand from "../../lib/budget-guard.mjs";
|
|
118
121
|
// WS4 — the consumer is one of the spawn sources, so it honours the SAME
|
|
@@ -214,24 +217,38 @@ export function parseUsageFromStdout(stdoutPath) {
|
|
|
214
217
|
}
|
|
215
218
|
if (!obj || typeof obj !== "object") return { ok: false, reason: "no-json-object" };
|
|
216
219
|
|
|
220
|
+
// A Cohort Engine result names the gateway requests that served it.
|
|
221
|
+
const cohort = obj.cohort && typeof obj.cohort === "object" ? obj.cohort : null;
|
|
222
|
+
const cohortMeta = cohort
|
|
223
|
+
? { servedByCohort: true, requestIds: Array.isArray(cohort.requestIds) ? cohort.requestIds.filter((x) => typeof x === "string" && x) : [] }
|
|
224
|
+
: {};
|
|
225
|
+
|
|
217
226
|
const usage = obj.usage && typeof obj.usage === "object" ? obj.usage : null;
|
|
218
|
-
if (!usage) return { ok: false, reason: "no-usage-field" };
|
|
227
|
+
if (!usage) return { ok: false, reason: "no-usage-field", ...cohortMeta };
|
|
219
228
|
|
|
220
229
|
const inputTokens = Number(usage.input_tokens);
|
|
221
230
|
const outputTokens = Number(usage.output_tokens);
|
|
222
231
|
if (!Number.isFinite(inputTokens) || !Number.isFinite(outputTokens)) {
|
|
223
|
-
return { ok: false, reason: "non-numeric-tokens" };
|
|
232
|
+
return { ok: false, reason: "non-numeric-tokens", ...cohortMeta };
|
|
224
233
|
}
|
|
225
234
|
|
|
226
|
-
const out = { ok: true, inputTokens, outputTokens };
|
|
235
|
+
const out = { ok: true, inputTokens, outputTokens, ...cohortMeta };
|
|
227
236
|
const cacheRead = Number(usage.cache_read_input_tokens);
|
|
228
237
|
if (Number.isFinite(cacheRead)) out.cacheReadTokens = cacheRead;
|
|
229
238
|
// Cache CREATION tokens bill at 1.25x input; dropping them was a large slice
|
|
230
239
|
// of the gap between the local estimate and the CLI's authoritative cost.
|
|
231
240
|
const cacheWrite = Number(usage.cache_creation_input_tokens);
|
|
232
241
|
if (Number.isFinite(cacheWrite)) out.cacheWriteTokens = cacheWrite;
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
// A NUMBER, not anything Number() will coerce. The Cohort Engine writes
|
|
243
|
+
// `total_cost_usd: null` when not every request reported gateway cost
|
|
244
|
+
// (lib/engine/output/json.mjs), and `Number(null)` is 0 — finite, >= 0 —
|
|
245
|
+
// which recorded such a cadence as $0.00 authoritative: the free-looking
|
|
246
|
+
// row the parse-failure branch below exists never to write. This consumer
|
|
247
|
+
// spawns the engine on an engine=cohort seat (engineRunShape above), so the
|
|
248
|
+
// null is reachable here exactly as in dispatcher.mjs#parseUsageFromText
|
|
249
|
+
// and responder.mjs#responderCostArgs. (engine-envelope-parity.test.mjs)
|
|
250
|
+
const totalCost = obj.total_cost_usd;
|
|
251
|
+
if (typeof totalCost === "number" && Number.isFinite(totalCost) && totalCost >= 0) out.totalCostUsd = totalCost;
|
|
235
252
|
// The CLI may report the resolved model (e.g. "claude-sonnet-4-6"); map it to
|
|
236
253
|
// the tracker's coarse class so pricing lookup stays correct.
|
|
237
254
|
if (typeof obj.model === "string" && obj.model) {
|
|
@@ -363,7 +380,7 @@ export function renderCadencePromptBody(agentRoot, body) {
|
|
|
363
380
|
* so non-zero exits remain diagnosable after the fact. The last ~4 KB of
|
|
364
381
|
* stderr is also captured in-memory and surfaced on the failure event.
|
|
365
382
|
*/
|
|
366
|
-
function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
|
|
383
|
+
function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log, target: gatedTarget, engine: gatedEngine, cohortToken: gatedToken, cohortBaseUrl: gatedBaseUrl }) {
|
|
367
384
|
return new Promise((resolveOut) => {
|
|
368
385
|
const fullPrompt = join(agentRoot, promptPath);
|
|
369
386
|
if (!existsSync(fullPrompt)) {
|
|
@@ -392,64 +409,81 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
|
|
|
392
409
|
// Route the cadence sub-session's model through the v2 router (opt-in). When
|
|
393
410
|
// a backend is chosen we pass its --model flag + spawn knobs; otherwise the
|
|
394
411
|
// argv stays exactly as before (no --model → stock CLI default model).
|
|
395
|
-
|
|
396
|
-
|
|
412
|
+
// The consumer's cohort gate already resolved (and admitted) the target;
|
|
413
|
+
// re-resolving here could pick a different one. undefined = no gate ran.
|
|
414
|
+
const target = gatedTarget !== undefined ? gatedTarget : routeCadenceSpawn(agentRoot, cadence);
|
|
397
415
|
if (target) {
|
|
398
|
-
if (target.maxTurns != null && Number.isFinite(Number(target.maxTurns))) {
|
|
399
|
-
routedArgs.push("--max-turns", String(target.maxTurns));
|
|
400
|
-
}
|
|
401
|
-
if (target.effort) routedArgs.push("--effort", String(target.effort));
|
|
402
|
-
if (target.agentsJson) {
|
|
403
|
-
routedArgs.push("--agents", typeof target.agentsJson === "string" ? target.agentsJson : JSON.stringify(target.agentsJson));
|
|
404
|
-
}
|
|
405
|
-
if (target.modelFlag) routedArgs.push("--model", String(target.modelFlag));
|
|
406
416
|
log({ level: "info", stage: "cadence_routed", cadence, decision_id: target.decisionId, backend: target.backend, model: target.model, transport: target.transport, lane: target.lane, explain: target.explain });
|
|
407
417
|
}
|
|
408
418
|
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
// The
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
// set, OR via the ANTHROPIC_API_KEY env var when one is present.
|
|
441
|
-
// If the env key is present BUT looks like a placeholder / empty
|
|
442
|
-
// string, we strip it so claude can fall back to Keychain OAuth.
|
|
443
|
-
// Set MAESTRO_PREFER_SUBSCRIPTION_AUTH=1 in .env to always strip
|
|
444
|
-
// the API key (force subscription auth) — useful when the agent
|
|
445
|
-
// owns a Claude Code Pro/Max subscription and shouldn't burn API
|
|
446
|
-
// credits for routine ticks.
|
|
447
|
-
const preferSubscription = process.env.MAESTRO_PREFER_SUBSCRIPTION_AUTH === "1";
|
|
448
|
-
const apiKey = env.ANTHROPIC_API_KEY || "";
|
|
449
|
-
if (preferSubscription || !apiKey.trim() || /^(your-api-key|placeholder|xxx+|sk-ant-xxx)/i.test(apiKey)) {
|
|
450
|
-
delete env.ANTHROPIC_API_KEY;
|
|
419
|
+
// argv + env come from the runtime adapter (lib/runtime/adapter.mjs, lane
|
|
420
|
+
// "cadence"). What it builds, unchanged from the inline version:
|
|
421
|
+
// argv --print --output-format json <permissions> <daemon MCP args>
|
|
422
|
+
// <router knobs> [--model <flag>] <body>
|
|
423
|
+
// --output-format json makes stdout one JSON object carrying the real
|
|
424
|
+
// token usage, read back below only to record a TRUTHFUL cost row
|
|
425
|
+
// (recovery C1). The body stays the LAST positional arg; no router →
|
|
426
|
+
// no knobs and no --model (the CLI default model).
|
|
427
|
+
// env {...process.env, AGENT_ROOT, AGENT_DIR, PATH (augmented)}. Stock:
|
|
428
|
+
// ANTHROPIC_API_KEY is deleted when MAESTRO_PREFER_SUBSCRIPTION_AUTH=1
|
|
429
|
+
// or the key is empty/a placeholder, so claude rides the seat's
|
|
430
|
+
// subscription credential. A v2 retarget goes through the one runtime
|
|
431
|
+
// env builder (foreign credentials AND the OAuth token scrubbed,
|
|
432
|
+
// ANTHROPIC_API_KEY="", CLAUDE_CODE_ATTRIBUTION_HEADER=0) with the
|
|
433
|
+
// cadence vars re-applied on top.
|
|
434
|
+
// W4-E1 (CF-44): the engine the gate resolved for this tick (else read now,
|
|
435
|
+
// once). An engine-cohort seat runs `cli.mjs run` with the admitted seat
|
|
436
|
+
// token and no retarget; a claude seat's input is exactly as before.
|
|
437
|
+
const engine = gatedEngine === "cohort" || gatedEngine === "claude" ? gatedEngine : readSeatEngine(agentRoot);
|
|
438
|
+
const engineShape = engine === "cohort" ? engineRunShape({ target, laneModel: null }) : null;
|
|
439
|
+
let engineToken = null;
|
|
440
|
+
let engineBaseUrl = null;
|
|
441
|
+
if (engineShape) {
|
|
442
|
+
engineToken = gatedToken || null;
|
|
443
|
+
engineBaseUrl = gatedBaseUrl || null;
|
|
444
|
+
if (!engineToken || !engineBaseUrl) {
|
|
445
|
+
try {
|
|
446
|
+
const seatToken = seatCohortClients({ agentRoot }).token;
|
|
447
|
+
if (!engineToken) engineToken = seatTokenSync(seatToken);
|
|
448
|
+
if (!engineBaseUrl) engineBaseUrl = seatToken.baseUrl || null;
|
|
449
|
+
} catch { /* no seat key: the adapter refuses the spawn as a value */ }
|
|
451
450
|
}
|
|
452
451
|
}
|
|
452
|
+
const spawnInput = {
|
|
453
|
+
lane: "cadence",
|
|
454
|
+
bin,
|
|
455
|
+
prompt: body,
|
|
456
|
+
permissions: permissionArgs,
|
|
457
|
+
mcp: {},
|
|
458
|
+
knobs: engineShape ? engineShape.knobs : target,
|
|
459
|
+
model: engineShape ? engineShape.model : target && target.modelFlag ? target.modelFlag : null,
|
|
460
|
+
agentRoot,
|
|
461
|
+
env: process.env,
|
|
462
|
+
retarget: engineShape ? engineShape.retarget : (target && target.envForSpawn) || {},
|
|
463
|
+
...seatEngineFields({ engine, agentRoot, token: engineToken, baseUrl: engineBaseUrl }),
|
|
464
|
+
};
|
|
465
|
+
let spawnSpec = buildSpawn(spawnInput);
|
|
466
|
+
let refusedRetarget = null;
|
|
467
|
+
if (!spawnSpec.ok && /^retarget_/.test(spawnSpec.error.code)) {
|
|
468
|
+
// Refused retarget (no third-party token): run the tick exactly as an
|
|
469
|
+
// unrouted spawn instead of handing that backend the seat's credential.
|
|
470
|
+
log({ level: "warn", stage: "cadence_retarget_refused", cadence, decision_id: target && target.decisionId, code: spawnSpec.error.code, error: spawnSpec.error.message });
|
|
471
|
+
refusedRetarget = spawnSpec.error;
|
|
472
|
+
spawnSpec = buildSpawn({ ...spawnInput, knobs: null, model: null, retarget: {} });
|
|
473
|
+
}
|
|
474
|
+
// CF-11: after a refusal the run is the unrouted stock session — the cost
|
|
475
|
+
// row must not carry the refused backend's model.
|
|
476
|
+
const costTarget = refusedRetarget ? null : target;
|
|
477
|
+
// The breaker and billing flags follow the engine as well as routing (W4-E1).
|
|
478
|
+
const servedByCohort = engine === "cohort" || (!refusedRetarget && isCohortRouted({ target }));
|
|
479
|
+
if (!spawnSpec.ok) {
|
|
480
|
+
resolveOut({ ok: false, exit_code: -5, cohort_routed: servedByCohort, error: `runtime unavailable: ${spawnSpec.error.message}` });
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const args = spawnSpec.argv;
|
|
484
|
+
// The adapter's binary: the claude binary resolved above, or node for the engine.
|
|
485
|
+
const spawnBin = spawnSpec.bin;
|
|
486
|
+
const env = spawnSpec.env;
|
|
453
487
|
const started = Date.now();
|
|
454
488
|
|
|
455
489
|
// Per-run log file. Pattern is short enough to be tail-friendly.
|
|
@@ -462,7 +496,7 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
|
|
|
462
496
|
const stdoutFd = openSync(stdoutPath, "a");
|
|
463
497
|
const stderrFd = openSync(stderrPath, "a");
|
|
464
498
|
|
|
465
|
-
log({ level: "info", stage: "subsession_spawn", cadence, bin, stdout: stdoutPath, stderr: stderrPath });
|
|
499
|
+
log({ level: "info", stage: "subsession_spawn", cadence, bin: spawnBin, ...(engine === "cohort" ? { engine } : {}), stdout: stdoutPath, stderr: stderrPath });
|
|
466
500
|
|
|
467
501
|
let child;
|
|
468
502
|
try {
|
|
@@ -470,7 +504,7 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
|
|
|
470
504
|
// 0 ignore (claude --print reads prompt from argv, not stdin)
|
|
471
505
|
// 1 → file (capture stdout for later inspection)
|
|
472
506
|
// 2 → file (capture stderr — critical for diagnosing exit-1)
|
|
473
|
-
child = spawn(
|
|
507
|
+
child = spawn(spawnBin, args, { cwd: agentRoot, env, stdio: ["ignore", stdoutFd, stderrFd] });
|
|
474
508
|
} catch (err) {
|
|
475
509
|
try { closeSync(stdoutFd); closeSync(stderrFd); } catch { /* */ }
|
|
476
510
|
resolveOut({ ok: false, exit_code: -4, error: `spawn failed: ${err.message}` });
|
|
@@ -501,6 +535,10 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
|
|
|
501
535
|
stderrTail = so.slice(-4096);
|
|
502
536
|
}
|
|
503
537
|
} catch { /* file may not exist if spawn ENOENT before fd-redirect */ }
|
|
538
|
+
// stdout's own tail, for the structured cohort-refusal check: an engine
|
|
539
|
+
// run's JSON result lands on stdout even when stderr is not empty.
|
|
540
|
+
let stdoutTail = "";
|
|
541
|
+
try { stdoutTail = readFileSync(stdoutPath, "utf-8").slice(-65_536); } catch { /* */ }
|
|
504
542
|
|
|
505
543
|
// Record a TRUTHFUL cost-ledger row (recovery C1). We parse the run's
|
|
506
544
|
// `--output-format json` object from stdoutPath to read the REAL token
|
|
@@ -518,13 +556,16 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
|
|
|
518
556
|
"--source", "cadence-consumer",
|
|
519
557
|
// Prefer the CLI-reported resolved model class; else the routed
|
|
520
558
|
// model flag (v2); else the historical sonnet default.
|
|
521
|
-
"--model", usage.model || (
|
|
559
|
+
"--model", usage.model || (costTarget && costTarget.model) || "sonnet",
|
|
522
560
|
"--duration-ms", String(durationMs),
|
|
523
561
|
"--exit", String(exit_code),
|
|
524
562
|
];
|
|
525
563
|
// Carry the v2 RouteDecision id onto the ledger row (SPEC §4.7) so a
|
|
526
564
|
// cadence spend row joins its routing decision. Absent on stock-CLI.
|
|
527
565
|
if (target && target.decisionId) trackerArgs.push("--decision-id", String(target.decisionId));
|
|
566
|
+
// Metered by cohort-llm at the gateway: mark it so org cost-sync skips it.
|
|
567
|
+
if (servedByCohort || usage.servedByCohort) trackerArgs.push("--billed-by", COHORT_LLM_BILLED_BY);
|
|
568
|
+
if (Array.isArray(usage.requestIds) && usage.requestIds.length) trackerArgs.push("--request-ids", usage.requestIds.join(","));
|
|
528
569
|
if (usage.ok) {
|
|
529
570
|
trackerArgs.push("--input-tokens", String(usage.inputTokens));
|
|
530
571
|
trackerArgs.push("--output-tokens", String(usage.outputTokens));
|
|
@@ -555,9 +596,11 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
|
|
|
555
596
|
resolveOut({
|
|
556
597
|
ok: exit_code === 0,
|
|
557
598
|
exit_code,
|
|
599
|
+
cohort_routed: servedByCohort,
|
|
558
600
|
signal: signal || null,
|
|
559
601
|
duration_ms: durationMs,
|
|
560
602
|
stderr_tail: stderrTail || null,
|
|
603
|
+
stdout_tail: stdoutTail || null,
|
|
561
604
|
stdout_path: stdoutPath,
|
|
562
605
|
stderr_path: stderrPath,
|
|
563
606
|
});
|
|
@@ -604,6 +647,62 @@ export function startConsumer(opts = {}) {
|
|
|
604
647
|
const rateGuard = opts.rateGuard || rateGuardModule;
|
|
605
648
|
const budgetGuard = opts.budgetGuard || budgetGuardModule;
|
|
606
649
|
const handoffDeadlineMs = opts.handoffDeadlineMs;
|
|
650
|
+
// cohort-llm pre-spawn gate (design C6 / §8.3). `opts.cohort` = {quota, token}
|
|
651
|
+
// (null disables it); `opts.routeCadence` replaces routeCadenceSpawn.
|
|
652
|
+
const routeCadence = typeof opts.routeCadence === "function" ? opts.routeCadence : routeCadenceSpawn;
|
|
653
|
+
let cohortClientsMemo = opts.cohort;
|
|
654
|
+
function cohortClients() {
|
|
655
|
+
if (cohortClientsMemo === undefined) {
|
|
656
|
+
try { cohortClientsMemo = seatCohortClients({ agentRoot }); } catch { cohortClientsMemo = null; }
|
|
657
|
+
}
|
|
658
|
+
return cohortClientsMemo;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* May this cadence's sub-session start on cohort-llm? Not cohort-routed →
|
|
663
|
+
* yes. Cohort-routed → a cohort hold or the quota endpoint decides. Human-lane
|
|
664
|
+
* cadences are NOT exempt: a refused seat would 429 them too.
|
|
665
|
+
*
|
|
666
|
+
* The resolved target rides out on the result (`target`) and is handed to
|
|
667
|
+
* the spawn, so the session that starts is the one that was gated — never a
|
|
668
|
+
* second routing decision that could differ (a band change in between).
|
|
669
|
+
*
|
|
670
|
+
* Failure: if ROUTING throws, the tick runs unrouted (`target: null` — the
|
|
671
|
+
* stock session, which no cohort quota governs), loudly. If ADMISSION throws
|
|
672
|
+
* for a cohort-routed target, the tick is deferred for a short retry — it is
|
|
673
|
+
* never spawned into a gateway that may 429/402 it.
|
|
674
|
+
*/
|
|
675
|
+
const COHORT_GATE_ERROR_RETRY_MS = 30_000;
|
|
676
|
+
// The seat's engine (config/agent.json runtime.engine). A seat on engine
|
|
677
|
+
// cohort is cohort-metered whatever routing picks, so it is always gated.
|
|
678
|
+
const seatEngine = typeof opts.seatEngine === "function" ? opts.seatEngine : () => readSeatEngine(agentRoot);
|
|
679
|
+
async function cohortGate(cadence) {
|
|
680
|
+
// W4-E1: the engine is resolved once per tick and rides the result to the spawn.
|
|
681
|
+
const engine = seatEngine();
|
|
682
|
+
let target;
|
|
683
|
+
try {
|
|
684
|
+
target = routeCadence(agentRoot, cadence);
|
|
685
|
+
} catch (err) {
|
|
686
|
+
log({ level: "error", stage: "cohort_gate_route_failed_unrouted", cadence, engine, error: err && err.message });
|
|
687
|
+
// Unrouted on the claude engine is the stock session no cohort quota
|
|
688
|
+
// governs; on engine cohort the session is still cohort-metered.
|
|
689
|
+
if (engine !== "cohort") return { ok: true, cohort: false, target: null, engine };
|
|
690
|
+
target = null;
|
|
691
|
+
}
|
|
692
|
+
if (!isCohortRouted({ engine, target })) return { ok: true, cohort: false, target: target || null, engine };
|
|
693
|
+
try {
|
|
694
|
+
const c = cohortClients();
|
|
695
|
+
const adm = await admitSpawn({ engine, target }, { quota: c && c.quota, token: c && c.token, rateGuard, rateGuardDeps: { agentRoot }, now: nowMs });
|
|
696
|
+
return { ...adm, target, engine };
|
|
697
|
+
} catch (err) {
|
|
698
|
+
log({ level: "error", stage: "cohort_gate_failed_closed", cadence, error: err && err.message });
|
|
699
|
+
const until = nowMs() + COHORT_GATE_ERROR_RETRY_MS;
|
|
700
|
+
return {
|
|
701
|
+
ok: false, cohort: true, target, engine,
|
|
702
|
+
deferral: { kind: "retry", until, code: "cohort_gate_error", message: `cohort-llm admission failed: ${err && err.message}`, refusal: null },
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}
|
|
607
706
|
const frontDoorStateImpl = typeof opts.frontDoorState === "function"
|
|
608
707
|
? opts.frontDoorState
|
|
609
708
|
: () => readFrontDoorState(agentRoot, { now: nowMs() });
|
|
@@ -985,6 +1084,29 @@ export function startConsumer(opts = {}) {
|
|
|
985
1084
|
if (!gov.admit) return deferTick(event, gov.reason);
|
|
986
1085
|
}
|
|
987
1086
|
|
|
1087
|
+
// cohort-llm quota gate: a tick whose sub-session cohort-llm would refuse is
|
|
1088
|
+
// requeued, not spawned — and never failTick'd (a spent window is an
|
|
1089
|
+
// upstream gate, not a broken cadence). The refusal is recorded as a cohort
|
|
1090
|
+
// hold, so the requeued tick is gated again without another quota read.
|
|
1091
|
+
let gatedTarget;
|
|
1092
|
+
let gatedEngine;
|
|
1093
|
+
let gatedToken = null;
|
|
1094
|
+
let gatedBaseUrl = null;
|
|
1095
|
+
{
|
|
1096
|
+
const cg = await cohortGate(event.cadence);
|
|
1097
|
+
gatedTarget = cg.target;
|
|
1098
|
+
gatedEngine = cg.engine;
|
|
1099
|
+
gatedToken = cg.token || null;
|
|
1100
|
+
if (gatedEngine === "cohort") { try { gatedBaseUrl = cohortClients()?.token?.baseUrl || null; } catch { gatedBaseUrl = null; } }
|
|
1101
|
+
if (!cg.ok) {
|
|
1102
|
+
const d = cg.deferral || {};
|
|
1103
|
+
return deferTick(event, `cohort:${d.code || d.kind || "refused"}`, {
|
|
1104
|
+
cohort_kind: d.kind || null,
|
|
1105
|
+
until: Number.isFinite(d.until) ? new Date(d.until).toISOString() : null,
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
988
1110
|
const def = getCadenceDef(event.cadence);
|
|
989
1111
|
let promptPath = def?.prompt;
|
|
990
1112
|
if (!promptPath) {
|
|
@@ -1069,6 +1191,12 @@ export function startConsumer(opts = {}) {
|
|
|
1069
1191
|
promptPath,
|
|
1070
1192
|
timeoutMs: maxSpawnMs,
|
|
1071
1193
|
log,
|
|
1194
|
+
// The target the cohort gate admitted (null = run unrouted). Never re-resolved.
|
|
1195
|
+
target: gatedTarget,
|
|
1196
|
+
// W4-E1: the engine the gate resolved, and the seat token it admitted with.
|
|
1197
|
+
engine: gatedEngine,
|
|
1198
|
+
cohortToken: gatedToken,
|
|
1199
|
+
cohortBaseUrl: gatedBaseUrl,
|
|
1072
1200
|
});
|
|
1073
1201
|
} finally {
|
|
1074
1202
|
activeSubSessions -= 1;
|
|
@@ -1087,8 +1215,9 @@ export function startConsumer(opts = {}) {
|
|
|
1087
1215
|
stderr_path: result.stderr_path || null,
|
|
1088
1216
|
});
|
|
1089
1217
|
recordSubsessionSuccess(event.cadence);
|
|
1090
|
-
// WS4: a clean sub-session closes the shared 429 breaker
|
|
1091
|
-
|
|
1218
|
+
// WS4: a clean sub-session closes the shared 429 breaker (the cohort hold
|
|
1219
|
+
// for a cohort-served run — never the Anthropic breaker on its behalf).
|
|
1220
|
+
try { rateGuard.recordSuccess(result.cohort_routed || seatEngine() === "cohort" ? rateGuardModule.COHORT_BACKEND : RATE_PROVIDER, { agentRoot }); } catch { /* */ }
|
|
1092
1221
|
stats.escalated += 1;
|
|
1093
1222
|
stats.last_decision = "escalated";
|
|
1094
1223
|
return { ok: true, decision: "escalated", exit_code: result.exit_code };
|
|
@@ -1099,10 +1228,31 @@ export function startConsumer(opts = {}) {
|
|
|
1099
1228
|
// instead of failTick — a 429 is an upstream gate, not a per-event failure,
|
|
1100
1229
|
// so it must not burn this cadence's retry budget toward the DLQ.
|
|
1101
1230
|
const cadenceOut = result.stderr_tail || result.error || result.stdout_tail || "";
|
|
1102
|
-
|
|
1231
|
+
// A seat on engine cohort is cohort-served whatever routing picked (the gate's own test).
|
|
1232
|
+
const engineCohort = seatEngine() === "cohort";
|
|
1233
|
+
const cohortServed = Boolean(result.cohort_routed) || engineCohort;
|
|
1234
|
+
const breakerProvider = cohortServed ? rateGuardModule.COHORT_BACKEND : RATE_PROVIDER;
|
|
1235
|
+
// A cohort-llm refusal first: keyed by the "cohort" backend, held until its
|
|
1236
|
+
// resetsAt, never an Anthropic breaker, never a failed attempt. Both streams:
|
|
1237
|
+
// an engine's JSON result is on stdout even when stderr carries text.
|
|
1238
|
+
const refusalText = [result.stderr_tail, result.error, result.stdout_tail].filter(Boolean).join("\n");
|
|
1239
|
+
const cohortRefusal = cohortServed ? (rateGuard.classifyCohortRefusal?.(refusalText) ?? null) : null;
|
|
1240
|
+
if (cohortRefusal) {
|
|
1241
|
+
try {
|
|
1242
|
+
const rec = rateGuard.recordCohortRefusal(cohortRefusal, { agentRoot });
|
|
1243
|
+
log({ level: "warn", stage: "subsession_cohort_refused", id: event.id, cadence: event.cadence, code: cohortRefusal.code, disposition: rec.disposition && rec.disposition.kind, open_until: rec.openUntil });
|
|
1244
|
+
} catch { /* */ }
|
|
1245
|
+
try { const c = cohortClients(); if (c && c.quota && typeof c.quota.invalidate === "function") c.quota.invalidate(); } catch { /* */ }
|
|
1246
|
+
return deferTick(event, `cohort-refused:${cohortRefusal.code}`);
|
|
1247
|
+
}
|
|
1248
|
+
// An engine-cohort lane has no Claude subscription: its English limit text is
|
|
1249
|
+
// never a subscription usage limit (logged; the run fails or backs off below).
|
|
1250
|
+
const cadenceUlText = rateGuard.classifyUsageLimit?.(cadenceOut, { agentRoot }) ?? { isLimit: false, resetAt: null };
|
|
1251
|
+
const cadenceUl = engineCohort ? { isLimit: false, resetAt: null } : cadenceUlText;
|
|
1252
|
+
if (engineCohort && cadenceUlText.isLimit) log({ level: "warn", stage: "subsession_usage_limit_text_ignored", id: event.id, cadence: event.cadence, engine: "cohort" });
|
|
1103
1253
|
if (cadenceUl.isLimit) {
|
|
1104
1254
|
try {
|
|
1105
|
-
const rec = rateGuard.recordUsageLimit(
|
|
1255
|
+
const rec = rateGuard.recordUsageLimit(breakerProvider, cadenceUl.resetAt, { agentRoot });
|
|
1106
1256
|
log({ level: "warn", stage: "subsession_usage_limited", id: event.id, cadence: event.cadence, open_until: rec.openUntil, reset_at: rec.resetAt });
|
|
1107
1257
|
} catch { /* */ }
|
|
1108
1258
|
// Same handling as a 429: a window-usage limit is a shared, provider-side
|
|
@@ -1113,7 +1263,7 @@ export function startConsumer(opts = {}) {
|
|
|
1113
1263
|
}
|
|
1114
1264
|
if (rateGuard.classifyStderr(cadenceOut)) {
|
|
1115
1265
|
try {
|
|
1116
|
-
const rec = rateGuard.recordRateLimit(
|
|
1266
|
+
const rec = rateGuard.recordRateLimit(breakerProvider, { agentRoot });
|
|
1117
1267
|
log({ level: "warn", stage: "subsession_rate_limited", id: event.id, cadence: event.cadence, open_until: rec.openUntil });
|
|
1118
1268
|
} catch { /* */ }
|
|
1119
1269
|
// L2: a 429 is a SHARED, provider-side rate-limit signal — the shared
|
|
@@ -500,7 +500,65 @@ async function handleNightlyBackup({ event, agentRoot, log }, opts = {}) {
|
|
|
500
500
|
* (`flagged`) logs a warning. The result (drift %) goes in the inline result.
|
|
501
501
|
* The lib fn is injectable via `opts.reconcileImpl` for tests.
|
|
502
502
|
*/
|
|
503
|
+
/**
|
|
504
|
+
* Regenerate state/dashboards/cost-summary.yaml from the local cost ledger.
|
|
505
|
+
*
|
|
506
|
+
* WHY THIS IS SPAWNED AND NOT IMPORTED: scripts/cost/track-claude-usage.mjs has
|
|
507
|
+
* no import.meta.main guard — it dispatches on process.argv at module scope, so
|
|
508
|
+
* importing it from the daemon would run the CLI's usage branch and
|
|
509
|
+
* process.exit(2) the daemon itself. A child process is the only safe call.
|
|
510
|
+
*
|
|
511
|
+
* Fail-soft by construction: this is a dashboard refresh, not the reconcile, so
|
|
512
|
+
* it must never turn a good reconcile into ok:false. Errors are RETURNED as a
|
|
513
|
+
* named reason, never thrown and never swallowed into silence.
|
|
514
|
+
*
|
|
515
|
+
* Injectable via `opts.summariseImpl` for tests (no child process).
|
|
516
|
+
*/
|
|
517
|
+
async function runCostSummarise({ agentRoot, days = 7 }, opts = {}) {
|
|
518
|
+
try {
|
|
519
|
+
const runner =
|
|
520
|
+
opts.summariseImpl ||
|
|
521
|
+
(async () => {
|
|
522
|
+
const { execFile } = await import("node:child_process");
|
|
523
|
+
const script = join(agentRoot, "scripts", "cost", "track-claude-usage.mjs");
|
|
524
|
+
const fallback = join(__dirname, "..", "cost", "track-claude-usage.mjs");
|
|
525
|
+
const target = existsSync(script) ? script : fallback;
|
|
526
|
+
if (!existsSync(target)) return { code: 127, stderr: "tracker-not-found" };
|
|
527
|
+
return new Promise((resolvePromise) => {
|
|
528
|
+
execFile(
|
|
529
|
+
process.execPath,
|
|
530
|
+
[target, "summarise", "--days", String(days)],
|
|
531
|
+
{ timeout: 2 * 60 * 1000, maxBuffer: 8 * 1024 * 1024, env: { ...process.env, AGENT_ROOT: agentRoot } },
|
|
532
|
+
(err, stdout, stderr) => {
|
|
533
|
+
resolvePromise({
|
|
534
|
+
code: err ? (typeof err.code === "number" ? err.code : 1) : 0,
|
|
535
|
+
stdout: String(stdout || ""),
|
|
536
|
+
stderr: String(stderr || ""),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
const res = await runner({ agentRoot, days });
|
|
543
|
+
if (!res || res.code !== 0) {
|
|
544
|
+
return { ok: false, reason: (res && String(res.stderr || "").trim().slice(0, 200)) || `exit ${res && res.code}` };
|
|
545
|
+
}
|
|
546
|
+
return { ok: true, reason: null };
|
|
547
|
+
} catch (err) {
|
|
548
|
+
return { ok: false, reason: err && err.message ? err.message : String(err) };
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
503
552
|
async function handleNightlyCostReconcile({ event, agentRoot, log }, opts = {}) {
|
|
553
|
+
// Refresh the cost dashboard FIRST. cost-summary.yaml is written by nothing
|
|
554
|
+
// else in production — `summarise` had no caller at all, so the file sat at a
|
|
555
|
+
// zeroed scaffold while real ledger rows accumulated beside it. Running it
|
|
556
|
+
// ahead of the reconcile means a reconcile failure cannot also cost the
|
|
557
|
+
// refresh, and its own failure is reported, never thrown.
|
|
558
|
+
const summarised = await runCostSummarise({ agentRoot }, opts);
|
|
559
|
+
if (!summarised.ok && typeof log === "function") {
|
|
560
|
+
log("warn", `[nightly-cost-reconcile] cost-summary regeneration failed: ${summarised.reason}`);
|
|
561
|
+
}
|
|
504
562
|
try {
|
|
505
563
|
const reconcile =
|
|
506
564
|
opts.reconcileImpl ||
|
|
@@ -514,6 +572,8 @@ async function handleNightlyCostReconcile({ event, agentRoot, log }, opts = {})
|
|
|
514
572
|
ok: true,
|
|
515
573
|
decision: "inline",
|
|
516
574
|
cadence: event.cadence,
|
|
575
|
+
costSummaryRegenerated: summarised.ok,
|
|
576
|
+
costSummaryError: summarised.reason,
|
|
517
577
|
day: res?.day ?? null,
|
|
518
578
|
driftPct: res?.driftPct ?? null,
|
|
519
579
|
flagged: !!(res && res.flagged),
|
|
@@ -521,7 +581,7 @@ async function handleNightlyCostReconcile({ event, agentRoot, log }, opts = {})
|
|
|
521
581
|
providerUSD: res?.providerUSD ?? null,
|
|
522
582
|
};
|
|
523
583
|
} catch (err) {
|
|
524
|
-
return { ok: false, decision: "inline", cadence: event.cadence, error: err && err.message ? err.message : String(err) };
|
|
584
|
+
return { ok: false, decision: "inline", cadence: event.cadence, costSummaryRegenerated: summarised.ok, costSummaryError: summarised.reason, error: err && err.message ? err.message : String(err) };
|
|
525
585
|
}
|
|
526
586
|
}
|
|
527
587
|
|
|
@@ -588,7 +648,8 @@ async function handleOrgCostSync({ event, agentRoot }, opts = {}) {
|
|
|
588
648
|
|
|
589
649
|
/**
|
|
590
650
|
* monitoring-alerts (inline): gather the diagnostics counters, today's cost
|
|
591
|
-
* ledger, peer presence, daemon health +
|
|
651
|
+
* ledger, peer presence, daemon health + yesterday's cadence output artefacts,
|
|
652
|
+
* evaluate the
|
|
592
653
|
* behavioral alert rules, and emit any firing alerts to the configured webhook
|
|
593
654
|
* (config/alerts.yaml `alerts.webhook`) — else log them. Deterministic, no LLM.
|
|
594
655
|
* Fail-open + fully injectable for tests. This is what makes lib/diagnostics/
|
|
@@ -609,8 +670,16 @@ async function handleMonitoringAlerts({ event, agentRoot, log }, opts = {}) {
|
|
|
609
670
|
const health = opts.health !== undefined ? opts.health : recoverStaleClaims && readDaemonHealth(agentRoot);
|
|
610
671
|
const cfg = opts.cfg !== undefined ? opts.cfg : loadAgentCfg(agentRoot);
|
|
611
672
|
const now = opts.now || Date.now();
|
|
673
|
+
// Read yesterday's cadence OUTPUT artefacts off disk. This is the only input
|
|
674
|
+
// here that is not derived from the cadence bus, and it is deliberately so:
|
|
675
|
+
// the bus logged a timed-out evening wrap as stage="processed" on 29 Aug 2026,
|
|
676
|
+
// so no rule keyed on the bus's own decision can see that class of miss.
|
|
677
|
+
const outputs = opts.outputs !== undefined
|
|
678
|
+
? opts.outputs
|
|
679
|
+
: (await import("../../lib/diagnostics/cadence-output-freshness.mjs"))
|
|
680
|
+
.cadenceOutputFreshness({ agentRoot, now });
|
|
612
681
|
|
|
613
|
-
const alerts = A.evaluateAlerts({ snapshot, ledger, presence, health, thresholds: opts.thresholds, now }) || [];
|
|
682
|
+
const alerts = A.evaluateAlerts({ snapshot, ledger, presence, health, outputs, thresholds: opts.thresholds, now }) || [];
|
|
614
683
|
|
|
615
684
|
const sinkUrl = cfg && cfg.alerts && cfg.alerts.webhook;
|
|
616
685
|
const sink = opts.sink || (async (alert) => {
|
|
@@ -622,7 +691,7 @@ async function handleMonitoringAlerts({ event, agentRoot, log }, opts = {}) {
|
|
|
622
691
|
}
|
|
623
692
|
});
|
|
624
693
|
const res = alerts.length ? await A.emitAlerts(alerts, { sink }) : { attempted: 0, delivered: 0, failed: 0 };
|
|
625
|
-
return { ok: true, decision: "inline", cadence: event.cadence, alerts: alerts.length, delivered: res.delivered ?? 0, severities: alerts.map((a) => a.severity) };
|
|
694
|
+
return { ok: true, decision: "inline", cadence: event.cadence, alerts: alerts.length, delivered: res.delivered ?? 0, severities: alerts.map((a) => a.severity), outputsChecked: outputs?.checked ?? 0, outputsMissing: outputs?.missing?.length ?? 0 };
|
|
626
695
|
} catch (err) {
|
|
627
696
|
return { ok: false, decision: "inline", cadence: event.cadence, error: err && err.message ? err.message : String(err) };
|
|
628
697
|
}
|