@cohortapp/agent-sdk 2.16.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 +23 -7
- 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/poller-daemon-setup.md +4 -1
- 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/context/budget.mjs +327 -0
- package/lib/context/history-scope.mjs +138 -0
- 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/economics.mjs +9 -0
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +95 -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 +82 -4
- package/lib/org/inbound/hydrate.mjs +555 -51
- 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 +15 -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 +104 -59
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +804 -157
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/lib/session-router.mjs +310 -42
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +121 -12
- package/scripts/daemon/responder.mjs +315 -146
- 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/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 -453
- 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 -300
- 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/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 -295
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -344
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -185
- 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,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/context/budget.mjs — the declared token budget for an inbound prompt.
|
|
3
|
+
*
|
|
4
|
+
* Every bound in this repo up to now has been a row count or a `.slice(n)` char
|
|
5
|
+
* clamp (design §5.4, R8). Both are proxies: a row count cannot tell a one-word
|
|
6
|
+
* turn from a pasted stack trace, and a char clamp spends the allowance on
|
|
7
|
+
* whichever section happens to be assembled first. So the prompt that reaches
|
|
8
|
+
* the model is whatever the assembly order happened to produce, and the parts
|
|
9
|
+
* that fall off do so SILENTLY — the model reads a thread with a hole in it and
|
|
10
|
+
* cannot tell the hole from a conversation that never happened.
|
|
11
|
+
*
|
|
12
|
+
* This module states the budget instead of discovering it:
|
|
13
|
+
*
|
|
14
|
+
* 1. a budget PER TIER (§5.1) — an Answer gets 6k input tokens, Work 12k,
|
|
15
|
+
* Plan 20k. Context rot is measured: accuracy falls with input length well
|
|
16
|
+
* before the window limit, so a bigger prompt is not a better one.
|
|
17
|
+
* 2. sections filled in PRIORITY order ({@link SECTION_ORDER}) — the trigger
|
|
18
|
+
* is the thing being answered and is never dropped whole; the backlog is
|
|
19
|
+
* the first thing to go.
|
|
20
|
+
* 3. every drop VISIBLE — `… 14 earlier turns not shown`, in the prompt, where
|
|
21
|
+
* the model reads it. A section that did not fit at all still leaves a line
|
|
22
|
+
* saying so.
|
|
23
|
+
*
|
|
24
|
+
* PURE. No clock, no env, no fs, no network: tokens are estimated from
|
|
25
|
+
* character length at {@link CHARS_PER_TOKEN}, the same ratio
|
|
26
|
+
* `context-compiler.mjs` already uses, so the two agree. A caller that has a
|
|
27
|
+
* real tokeniser passes `estimate` and this module uses it instead.
|
|
28
|
+
*
|
|
29
|
+
* Char clamps stay where they are as a second belt — this is the first one.
|
|
30
|
+
*
|
|
31
|
+
* @module lib/context/budget
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
"use strict";
|
|
35
|
+
|
|
36
|
+
/** Characters per token. The estimate `scripts/daemon/context-compiler.mjs` uses. */
|
|
37
|
+
export const CHARS_PER_TOKEN = 4;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Input-token budget per reply tier (design §5.4). A tier is the ack/effort
|
|
41
|
+
* tier from `lib/assurance/tier.mjs` — answer | work | plan.
|
|
42
|
+
*/
|
|
43
|
+
export const TIER_BUDGETS = Object.freeze({
|
|
44
|
+
answer: 6000,
|
|
45
|
+
work: 12000,
|
|
46
|
+
plan: 20000,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* FILL priority. Earlier wins the budget; later is dropped first.
|
|
51
|
+
*
|
|
52
|
+
* trigger the message being answered — without it there is nothing to answer
|
|
53
|
+
* thread the conversation it sits in
|
|
54
|
+
* entity the anchored artifact: the task card, the doc body, the decision
|
|
55
|
+
* digest the distilled record of this thread
|
|
56
|
+
* memory recalled facts from elsewhere
|
|
57
|
+
* backlog what else is open — useful, never load-bearing
|
|
58
|
+
*/
|
|
59
|
+
export const FILL_PRIORITY = Object.freeze([
|
|
60
|
+
"trigger",
|
|
61
|
+
"thread",
|
|
62
|
+
"entity",
|
|
63
|
+
"digest",
|
|
64
|
+
"memory",
|
|
65
|
+
"backlog",
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* RENDER order — a DIFFERENT question, and conflating the two costs the cache.
|
|
70
|
+
*
|
|
71
|
+
* Fill priority asks "who wins the budget"; the trigger wins it, because a
|
|
72
|
+
* prompt without the message being answered is not a smaller prompt but a
|
|
73
|
+
* broken one. Render order asks "what byte comes first", and design §4 is
|
|
74
|
+
* explicit about the consequence: "any byte change before a breakpoint
|
|
75
|
+
* invalidates everything after it". Rendering the trigger first — the ONE
|
|
76
|
+
* section that is different on every single inbound — gives the block a
|
|
77
|
+
* zero-length stable prefix, so WP-4's `cache_control` breakpoint would sit
|
|
78
|
+
* after volatile content and every read would be a silent miss. Prompt caching
|
|
79
|
+
* is the highest-ROI latency lever in the research pass; this is what it needs.
|
|
80
|
+
*
|
|
81
|
+
* So: stable first, volatile last, trigger last of all. `thread` sits second to
|
|
82
|
+
* last because it is append-only and rendered oldest-first, which makes its own
|
|
83
|
+
* prefix stable between turns even as it grows.
|
|
84
|
+
*/
|
|
85
|
+
export const RENDER_ORDER = Object.freeze([
|
|
86
|
+
"entity",
|
|
87
|
+
"digest",
|
|
88
|
+
"backlog",
|
|
89
|
+
"memory",
|
|
90
|
+
"thread",
|
|
91
|
+
"trigger",
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @deprecated Read {@link FILL_PRIORITY} or {@link RENDER_ORDER} by name — the
|
|
96
|
+
* whole point of the split is that "the section order" is two different orders.
|
|
97
|
+
* Kept as an alias so an existing importer does not silently get the wrong one.
|
|
98
|
+
*/
|
|
99
|
+
export const SECTION_ORDER = FILL_PRIORITY;
|
|
100
|
+
|
|
101
|
+
/** Sections that must render SOMETHING rather than be dropped whole. */
|
|
102
|
+
const NEVER_DROPPED_WHOLE = new Set(["trigger"]);
|
|
103
|
+
|
|
104
|
+
/** A blank-safe string. */
|
|
105
|
+
function s(v) {
|
|
106
|
+
return v == null ? "" : String(v);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The declared budget for a tier; an unknown tier takes the middle one. */
|
|
110
|
+
export function budgetFor(tier) {
|
|
111
|
+
const key = s(tier).toLowerCase();
|
|
112
|
+
return Object.prototype.hasOwnProperty.call(TIER_BUDGETS, key) ? TIER_BUDGETS[key] : TIER_BUDGETS.work;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Estimated tokens for a string, rounded up so a partial token still costs one. */
|
|
116
|
+
export function estimateTokens(text, charsPerToken = CHARS_PER_TOKEN) {
|
|
117
|
+
const t = s(text);
|
|
118
|
+
if (!t) return 0;
|
|
119
|
+
const per = Number.isFinite(charsPerToken) && charsPerToken > 0 ? charsPerToken : CHARS_PER_TOKEN;
|
|
120
|
+
return Math.ceil(t.length / per);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Where a section sorts in a declared order; unknown names sort last, stably. */
|
|
124
|
+
function rankIn(order, name) {
|
|
125
|
+
const i = order.indexOf(name);
|
|
126
|
+
return i === -1 ? order.length : i;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** `1 earlier turn` / `14 earlier turns` — the count is the point, so it leads. */
|
|
130
|
+
function turnNote(n) {
|
|
131
|
+
return `… ${n} earlier turn${n === 1 ? "" : "s"} not shown`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** `… 1,204 characters not shown`. */
|
|
135
|
+
function charNote(n) {
|
|
136
|
+
return `… ${n.toLocaleString("en-US")} character${n === 1 ? "" : "s"} not shown`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @typedef {Object} SectionInput
|
|
141
|
+
* @property {string} name one of {@link SECTION_ORDER}, or any name (sorts last)
|
|
142
|
+
* @property {string} [title] heading rendered above the body
|
|
143
|
+
* @property {string} [text] a whole-text section
|
|
144
|
+
* @property {string[]} [turns] a turn-structured section, OLDEST FIRST
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @typedef {Object} FittedSection
|
|
149
|
+
* @property {string} name
|
|
150
|
+
* @property {boolean} included false == dropped whole (still rendered as a note)
|
|
151
|
+
* @property {string} text what was kept, drop note included
|
|
152
|
+
* @property {number} keptTurns
|
|
153
|
+
* @property {number} droppedTurns
|
|
154
|
+
* @property {number} droppedChars
|
|
155
|
+
* @property {number} tokens
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Fit the sections into the tier's budget.
|
|
160
|
+
*
|
|
161
|
+
* Turn-structured sections spend the budget from the NEWEST turn backwards —
|
|
162
|
+
* the turns nearest the message being answered are the ones that explain it
|
|
163
|
+
* (the same rule `lib/org/inbound/hydrate.mjs#renderThread` follows, and for the
|
|
164
|
+
* same reason: clipping the joined string keeps the oldest turns and drops the
|
|
165
|
+
* newest, which is the exact inverse of what context is for).
|
|
166
|
+
*
|
|
167
|
+
* NEVER THROWS. A malformed section is skipped; a missing budget takes the
|
|
168
|
+
* tier's. The prompt path must not be able to fail on its own budgeting.
|
|
169
|
+
*
|
|
170
|
+
* @param {Object} o
|
|
171
|
+
* @param {SectionInput[]} o.sections
|
|
172
|
+
* @param {string} [o.tier] answer | work | plan
|
|
173
|
+
* @param {number} [o.budgetTokens] override the tier's budget
|
|
174
|
+
* @param {number} [o.charsPerToken]
|
|
175
|
+
* @param {(text:string)=>number} [o.estimate] a real tokeniser, when the caller has one
|
|
176
|
+
* @returns {{tier:string, budgetTokens:number, usedTokens:number,
|
|
177
|
+
* sections:FittedSection[], drops:Array<object>, text:string}}
|
|
178
|
+
*/
|
|
179
|
+
export function fitSections(o = {}) {
|
|
180
|
+
const tier = s(o.tier).toLowerCase() || "work";
|
|
181
|
+
const budgetTokens = Number.isFinite(o.budgetTokens) && o.budgetTokens > 0
|
|
182
|
+
? Math.floor(o.budgetTokens)
|
|
183
|
+
: budgetFor(tier);
|
|
184
|
+
const charsPerToken = Number.isFinite(o.charsPerToken) && o.charsPerToken > 0 ? o.charsPerToken : CHARS_PER_TOKEN;
|
|
185
|
+
const tokensOf = typeof o.estimate === "function" ? o.estimate : (t) => estimateTokens(t, charsPerToken);
|
|
186
|
+
|
|
187
|
+
const input = (Array.isArray(o.sections) ? o.sections : [])
|
|
188
|
+
.map((sec, i) => ({ sec, i }))
|
|
189
|
+
.filter(({ sec }) => sec && typeof sec === "object" && s(sec.name))
|
|
190
|
+
.sort((a, b) => rankIn(FILL_PRIORITY, s(a.sec.name)) - rankIn(FILL_PRIORITY, s(b.sec.name)) || a.i - b.i);
|
|
191
|
+
|
|
192
|
+
const out = [];
|
|
193
|
+
const drops = [];
|
|
194
|
+
let used = 0;
|
|
195
|
+
|
|
196
|
+
for (const { sec } of input) {
|
|
197
|
+
const name = s(sec.name);
|
|
198
|
+
const title = s(sec.title);
|
|
199
|
+
const headCost = title ? tokensOf(`${title}\n`) : 0;
|
|
200
|
+
const room = budgetTokens - used - headCost;
|
|
201
|
+
|
|
202
|
+
const fitted = Array.isArray(sec.turns)
|
|
203
|
+
? fitTurns(sec.turns, room, tokensOf)
|
|
204
|
+
: fitText(s(sec.text), room, tokensOf, charsPerToken);
|
|
205
|
+
|
|
206
|
+
// A section with no content at all is simply absent — there is nothing to
|
|
207
|
+
// report the loss of, and a note about an empty section is noise.
|
|
208
|
+
if (fitted.empty) continue;
|
|
209
|
+
|
|
210
|
+
if (!fitted.body && !NEVER_DROPPED_WHOLE.has(name)) {
|
|
211
|
+
// Dropped whole. It still shows: an absence the reader cannot see is the
|
|
212
|
+
// failure this module exists to prevent.
|
|
213
|
+
const note = `… ${name} not shown`;
|
|
214
|
+
const cost = tokensOf(note);
|
|
215
|
+
if (used + cost <= budgetTokens) {
|
|
216
|
+
out.push({ name, included: false, text: note, keptTurns: 0, droppedTurns: fitted.droppedTurns, droppedChars: fitted.droppedChars, tokens: cost });
|
|
217
|
+
used += cost;
|
|
218
|
+
} else {
|
|
219
|
+
out.push({ name, included: false, text: "", keptTurns: 0, droppedTurns: fitted.droppedTurns, droppedChars: fitted.droppedChars, tokens: 0 });
|
|
220
|
+
}
|
|
221
|
+
drops.push({ section: name, turns: fitted.droppedTurns, chars: fitted.droppedChars, note });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// The trigger is the thing being answered: it always renders something,
|
|
226
|
+
// even when the budget cannot pay for it. A prompt with no trigger is not a
|
|
227
|
+
// smaller prompt, it is a broken one.
|
|
228
|
+
let body = fitted.body;
|
|
229
|
+
if (!body && NEVER_DROPPED_WHOLE.has(name)) {
|
|
230
|
+
const minChars = Math.max(1, Math.floor(Math.max(room, 1) * charsPerToken));
|
|
231
|
+
const whole = Array.isArray(sec.turns) ? sec.turns.filter(Boolean).map(s).join("\n") : s(sec.text);
|
|
232
|
+
body = whole.slice(0, minChars);
|
|
233
|
+
const lost = whole.length - body.length;
|
|
234
|
+
if (lost > 0) {
|
|
235
|
+
body += `\n${charNote(lost)}`;
|
|
236
|
+
drops.push({ section: name, turns: 0, chars: lost, note: charNote(lost) });
|
|
237
|
+
}
|
|
238
|
+
} else if (fitted.droppedTurns > 0) {
|
|
239
|
+
drops.push({ section: name, turns: fitted.droppedTurns, chars: 0, note: turnNote(fitted.droppedTurns) });
|
|
240
|
+
} else if (fitted.droppedChars > 0) {
|
|
241
|
+
drops.push({ section: name, turns: 0, chars: fitted.droppedChars, note: charNote(fitted.droppedChars) });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const text = title ? `${title}\n${body}` : body;
|
|
245
|
+
const cost = tokensOf(text);
|
|
246
|
+
out.push({
|
|
247
|
+
name,
|
|
248
|
+
included: true,
|
|
249
|
+
text,
|
|
250
|
+
keptTurns: fitted.keptTurns,
|
|
251
|
+
droppedTurns: fitted.droppedTurns,
|
|
252
|
+
droppedChars: fitted.droppedChars,
|
|
253
|
+
tokens: cost,
|
|
254
|
+
});
|
|
255
|
+
used += cost;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Filled in priority order, RENDERED in cache order. The two are separate
|
|
259
|
+
// arrays for the reason RENDER_ORDER states, and this is the only place the
|
|
260
|
+
// difference is applied.
|
|
261
|
+
const rendered = out
|
|
262
|
+
.map((sec, i) => ({ sec, i }))
|
|
263
|
+
.sort((a, b) => rankIn(RENDER_ORDER, a.sec.name) - rankIn(RENDER_ORDER, b.sec.name) || a.i - b.i)
|
|
264
|
+
.map(({ sec }) => sec);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
tier,
|
|
268
|
+
budgetTokens,
|
|
269
|
+
usedTokens: used,
|
|
270
|
+
sections: rendered,
|
|
271
|
+
drops,
|
|
272
|
+
text: rendered.map((sec) => sec.text).filter(Boolean).join("\n\n"),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Keep the newest turns that fit; report the rest as a visible note. */
|
|
277
|
+
function fitTurns(turns, room, tokensOf) {
|
|
278
|
+
const rows = (Array.isArray(turns) ? turns : []).filter((t) => s(t).trim()).map((t) => s(t));
|
|
279
|
+
if (rows.length === 0) return { empty: true, body: "", keptTurns: 0, droppedTurns: 0, droppedChars: 0 };
|
|
280
|
+
if (room <= 0) return { empty: false, body: "", keptTurns: 0, droppedTurns: rows.length, droppedChars: 0 };
|
|
281
|
+
|
|
282
|
+
const kept = [];
|
|
283
|
+
let used = 0;
|
|
284
|
+
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
285
|
+
const cost = tokensOf(rows[i]) + (kept.length ? 1 : 0);
|
|
286
|
+
// The note itself has to fit, or the drop would be the silent kind.
|
|
287
|
+
const reserve = i > 0 ? tokensOf(turnNote(i)) + 1 : 0;
|
|
288
|
+
if (used + cost + reserve > room) break;
|
|
289
|
+
kept.unshift(rows[i]);
|
|
290
|
+
used += cost;
|
|
291
|
+
}
|
|
292
|
+
const dropped = rows.length - kept.length;
|
|
293
|
+
if (kept.length === 0) return { empty: false, body: "", keptTurns: 0, droppedTurns: dropped, droppedChars: 0 };
|
|
294
|
+
const body = dropped > 0 ? [turnNote(dropped), ...kept].join("\n") : kept.join("\n");
|
|
295
|
+
return { empty: false, body, keptTurns: kept.length, droppedTurns: dropped, droppedChars: 0 };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Keep the head that fits; report the tail as a visible note. */
|
|
299
|
+
function fitText(text, room, tokensOf, charsPerToken) {
|
|
300
|
+
const t = s(text).trim();
|
|
301
|
+
if (!t) return { empty: true, body: "", keptTurns: 0, droppedTurns: 0, droppedChars: 0 };
|
|
302
|
+
if (room <= 0) return { empty: false, body: "", keptTurns: 0, droppedTurns: 0, droppedChars: t.length };
|
|
303
|
+
if (tokensOf(t) <= room) return { empty: false, body: t, keptTurns: 1, droppedTurns: 0, droppedChars: 0 };
|
|
304
|
+
|
|
305
|
+
// Reserve room for the note, then take the largest head that still fits.
|
|
306
|
+
let chars = Math.max(0, Math.floor(room * charsPerToken));
|
|
307
|
+
let head = t.slice(0, chars);
|
|
308
|
+
let note = charNote(t.length - head.length);
|
|
309
|
+
while (head.length > 0 && tokensOf(`${head}\n${note}`) > room) {
|
|
310
|
+
chars = Math.floor(chars * 0.8);
|
|
311
|
+
head = t.slice(0, chars);
|
|
312
|
+
note = charNote(t.length - head.length);
|
|
313
|
+
}
|
|
314
|
+
if (!head) return { empty: false, body: "", keptTurns: 0, droppedTurns: 0, droppedChars: t.length };
|
|
315
|
+
return { empty: false, body: `${head}\n${note}`, keptTurns: 1, droppedTurns: 0, droppedChars: t.length - head.length };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export default {
|
|
319
|
+
TIER_BUDGETS,
|
|
320
|
+
FILL_PRIORITY,
|
|
321
|
+
RENDER_ORDER,
|
|
322
|
+
SECTION_ORDER,
|
|
323
|
+
CHARS_PER_TOKEN,
|
|
324
|
+
budgetFor,
|
|
325
|
+
estimateTokens,
|
|
326
|
+
fitSections,
|
|
327
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/context/history-scope.mjs — which interaction logs may be read for THIS
|
|
3
|
+
* reply.
|
|
4
|
+
*
|
|
5
|
+
* THE DEFECT THIS EXISTS TO CLOSE
|
|
6
|
+
*
|
|
7
|
+
* `scripts/daemon/responder.mjs#logInteraction` files every exchange under
|
|
8
|
+
* `memory/interactions/<service>/…`, and it files each one TWICE: once under the
|
|
9
|
+
* room (`<channelId>`) and once under the person (`dm-<sender-slug>`). The two
|
|
10
|
+
* readers — `prompt-builder.mjs` and `context-compiler.mjs` — then merged every
|
|
11
|
+
* candidate directory into one timestamp-sorted block headed "Recent
|
|
12
|
+
* conversation history with this sender".
|
|
13
|
+
*
|
|
14
|
+
* While that read was pinned to `…/slack/…` the merge was inert for every other
|
|
15
|
+
* service. Reading the item's own service (design §3 R12) makes it live, and
|
|
16
|
+
* Cohort is the one service where a person's DMs and the public rooms they post
|
|
17
|
+
* in share a namespace. So: Dana DMs the agent something private, later
|
|
18
|
+
* @-mentions the agent in a public channel, and the public room's prompt
|
|
19
|
+
* carries the DM sentence verbatim. That is design §5.5's resource-scope leak —
|
|
20
|
+
* "per-person facts may inform a reply but are NEVER quoted into a shared room"
|
|
21
|
+
* — arriving through the back door of a directory list.
|
|
22
|
+
*
|
|
23
|
+
* THE RULE, stated once so both readers and the writer cannot drift:
|
|
24
|
+
*
|
|
25
|
+
* A per-person directory is readable only when the reply itself is going to
|
|
26
|
+
* that person privately. In a shared room only the room's own log is read.
|
|
27
|
+
* Unknown is not private: an item that does not positively say it is a DM is
|
|
28
|
+
* treated as a room.
|
|
29
|
+
*
|
|
30
|
+
* PURE. Everything arrives on the argument; nothing is read from disk, the
|
|
31
|
+
* clock or the environment.
|
|
32
|
+
*
|
|
33
|
+
* @module lib/context/history-scope
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
"use strict";
|
|
37
|
+
|
|
38
|
+
/** A blank-safe string. */
|
|
39
|
+
function s(v) {
|
|
40
|
+
return v == null ? "" : String(v);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Is this inbound item a private 1:1 (or small group DM) conversation?
|
|
45
|
+
*
|
|
46
|
+
* The signals, in the order they are trusted:
|
|
47
|
+
* 1. `is_dm` — set from Cohort's `channel_kind` (`lib/org/inbound/project.mjs`)
|
|
48
|
+
* and from Slack's `channel_type` (`scripts/poller/utils.mjs`). A literal
|
|
49
|
+
* `false` is a positive assertion of "room" and ends the question.
|
|
50
|
+
* 2. `channel_type` — Slack's own envelope field: `im` / `mpim`.
|
|
51
|
+
* 3. A Slack channel id beginning `D` — the convention the raw_ref parser in
|
|
52
|
+
* `prompt-builder.mjs` already relies on.
|
|
53
|
+
* 4. A `dm/<name>` channel label, which is what the Telegram and Slack
|
|
54
|
+
* adapters write for a private chat.
|
|
55
|
+
*
|
|
56
|
+
* Anything else is a room. That direction is deliberate and is the opposite of
|
|
57
|
+
* this module's neighbours: everywhere else in hydration an unknown degrades
|
|
58
|
+
* towards MORE context, because the cost is a thinner prompt. Here the cost of
|
|
59
|
+
* guessing wrong is one person's private sentence in a public channel, so
|
|
60
|
+
* unknown buys nothing.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} item inbound item (`{service, is_dm, channel_type, channel_id, channel}`)
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
export function isPrivateConversation(item) {
|
|
66
|
+
if (!item || typeof item !== "object") return false;
|
|
67
|
+
if (item.is_dm === true) return true;
|
|
68
|
+
if (item.is_dm === false) return false;
|
|
69
|
+
|
|
70
|
+
const type = s(item.channel_type).toLowerCase();
|
|
71
|
+
if (type === "im" || type === "mpim") return true;
|
|
72
|
+
if (type === "channel" || type === "group") return false;
|
|
73
|
+
|
|
74
|
+
// Slack's own id convention. `service` defaults to slack here for the same
|
|
75
|
+
// reason both readers write `String(item.service || "slack")` — an item that
|
|
76
|
+
// predates the per-service split carries no service field, and a `D…` id on
|
|
77
|
+
// such an item is a Slack DM.
|
|
78
|
+
const service = s(item.service) || "slack";
|
|
79
|
+
const channelId = s(item.channel_id || item.channelId);
|
|
80
|
+
if (service === "slack" && /^D[A-Z0-9]+$/.test(channelId)) return true;
|
|
81
|
+
|
|
82
|
+
if (/^dm\//i.test(s(item.channel))) return true;
|
|
83
|
+
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The interaction-log directory NAMES this reply may read, relative to
|
|
89
|
+
* `memory/interactions/<service>/`. Callers join them onto the service root.
|
|
90
|
+
*
|
|
91
|
+
* Ordered room-first so a merged read still prefers the room's own record.
|
|
92
|
+
* Slack keeps its legacy bare `<sender-slug>` directory alongside the `dm-`
|
|
93
|
+
* one — historical logs live there and must not go dark — and it is subject to
|
|
94
|
+
* exactly the same gate.
|
|
95
|
+
*
|
|
96
|
+
* @param {object} o
|
|
97
|
+
* @param {string|null} [o.channelId] the room/DM channel id, when known
|
|
98
|
+
* @param {string|null} [o.senderSlug] the sender, slugified
|
|
99
|
+
* @param {boolean} [o.isPrivate] is the reply going to that person privately
|
|
100
|
+
* @param {string} [o.service]
|
|
101
|
+
* @returns {string[]} directory names, possibly empty
|
|
102
|
+
*/
|
|
103
|
+
export function historyDirNames({ channelId, senderSlug, isPrivate, service } = {}) {
|
|
104
|
+
const dirs = [];
|
|
105
|
+
const cid = s(channelId);
|
|
106
|
+
const slug = s(senderSlug);
|
|
107
|
+
if (cid) dirs.push(cid);
|
|
108
|
+
if (isPrivate === true && slug) {
|
|
109
|
+
dirs.push(`dm-${slug}`);
|
|
110
|
+
if ((s(service) || "slack") === "slack") dirs.push(slug);
|
|
111
|
+
}
|
|
112
|
+
return dirs;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The directories `logInteraction` may WRITE this exchange to.
|
|
117
|
+
*
|
|
118
|
+
* The read gate above is the one that stops the leak, but leaving the writer
|
|
119
|
+
* unconditional keeps every per-person directory permanently contaminated with
|
|
120
|
+
* public-room content — so a future reader, or an operator grepping the tree,
|
|
121
|
+
* meets the same defect again. One rule, both ends.
|
|
122
|
+
*
|
|
123
|
+
* @param {object} o
|
|
124
|
+
* @param {string|null} [o.channelId]
|
|
125
|
+
* @param {string|null} [o.senderSlug]
|
|
126
|
+
* @param {boolean} [o.isPrivate]
|
|
127
|
+
* @returns {string[]}
|
|
128
|
+
*/
|
|
129
|
+
export function interactionWriteDirNames({ channelId, senderSlug, isPrivate } = {}) {
|
|
130
|
+
const dirs = [];
|
|
131
|
+
const cid = s(channelId);
|
|
132
|
+
const slug = s(senderSlug);
|
|
133
|
+
if (cid) dirs.push(cid);
|
|
134
|
+
if (isPrivate === true && slug) dirs.push(`dm-${slug}`);
|
|
135
|
+
return dirs;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export default { isPrivateConversation, historyDirNames, interactionWriteDirNames };
|
|
@@ -91,6 +91,16 @@ export const DEFAULT_THRESHOLDS = Object.freeze({
|
|
|
91
91
|
warnSec: 120, // 2 min (heartbeat refreshes ~15s)
|
|
92
92
|
critSec: 300, // 5 min
|
|
93
93
|
}),
|
|
94
|
+
cadenceOutputMissing: Object.freeze({
|
|
95
|
+
// A scheduled workflow finished without leaving its output artefacts behind.
|
|
96
|
+
// This is the ONLY rule here not derived from the cadence bus, and that is
|
|
97
|
+
// the point: the evening wrap missed 5 of 9 days in Aug/Sep 2026 and the bus
|
|
98
|
+
// logged one of those misses as stage="processed". A rule reading the bus's
|
|
99
|
+
// own decision is green during that outage. See
|
|
100
|
+
// lib/diagnostics/cadence-output-freshness.mjs.
|
|
101
|
+
warnCount: 1, // one missing artefact is already a real miss
|
|
102
|
+
critCount: 2, // both artefacts gone = the workflow did not run at all
|
|
103
|
+
}),
|
|
94
104
|
});
|
|
95
105
|
|
|
96
106
|
const SEVERITY_RANK = { critical: 0, warning: 1 };
|
|
@@ -243,6 +253,9 @@ function round2(n) {
|
|
|
243
253
|
* optionally with `perSessionUSD: number[]`
|
|
244
254
|
* @param {object} [args.presence] `{ heartbeatAgeSec?, daemonHealthAgeSec?,
|
|
245
255
|
* daemonDown? }`
|
|
256
|
+
* @param {object} [args.outputs] cadence output verdict from
|
|
257
|
+
* lib/diagnostics/cadence-output-freshness.mjs
|
|
258
|
+
* `{ ok, days, checked, missing[] }`
|
|
246
259
|
* @param {object} [args.thresholds] merged thresholds (default DEFAULT_THRESHOLDS)
|
|
247
260
|
* @param {number} [args.now] clock in epoch ms (reserved; rules are
|
|
248
261
|
* age-based via injected `*AgeSec`)
|
|
@@ -254,6 +267,7 @@ export function evaluateAlerts(args = {}) {
|
|
|
254
267
|
const snapshot = args.snapshot && typeof args.snapshot === "object" ? args.snapshot : {};
|
|
255
268
|
const ledger = args.ledger && typeof args.ledger === "object" ? args.ledger : {};
|
|
256
269
|
const presence = args.presence && typeof args.presence === "object" ? args.presence : {};
|
|
270
|
+
const outputs = args.outputs && typeof args.outputs === "object" ? args.outputs : {};
|
|
257
271
|
const t = mergeThresholds(DEFAULT_THRESHOLDS, args.thresholds || {});
|
|
258
272
|
|
|
259
273
|
push(alerts, refusalSpikeRule(snapshot, t.refusalSpike));
|
|
@@ -261,6 +275,7 @@ export function evaluateAlerts(args = {}) {
|
|
|
261
275
|
push(alerts, costPerSessionRule(ledger, t.costPerSessionP99));
|
|
262
276
|
push(alerts, absentAgentRule(presence, t.absentAgent));
|
|
263
277
|
push(alerts, daemonDownRule(presence, t.daemonDown));
|
|
278
|
+
push(alerts, cadenceOutputMissingRule(outputs, t.cadenceOutputMissing));
|
|
264
279
|
|
|
265
280
|
// Deterministic order: critical first, then by id, so identical inputs always
|
|
266
281
|
// produce identical output (a de-dupe upstream can key on id).
|
|
@@ -365,6 +380,40 @@ function daemonDownRule(presence, cfg = {}) {
|
|
|
365
380
|
};
|
|
366
381
|
}
|
|
367
382
|
|
|
383
|
+
/**
|
|
384
|
+
* A scheduled workflow completed without producing its output artefacts.
|
|
385
|
+
*
|
|
386
|
+
* Unlike every other rule here, this one is NOT derived from the lane it judges.
|
|
387
|
+
* It reads files on disk that the workflow's own steps write, so it stays true
|
|
388
|
+
* when the cadence bus is wrong about itself — which is the failure this exists
|
|
389
|
+
* to catch (a timed-out evening wrap recorded as stage="processed", 29 Aug 2026).
|
|
390
|
+
*
|
|
391
|
+
* `checked === 0` means nothing was in scope (e.g. every candidate day predates
|
|
392
|
+
* its cadence). That is silence, not health, so it does not fire.
|
|
393
|
+
*/
|
|
394
|
+
function cadenceOutputMissingRule(outputs, cfg = {}) {
|
|
395
|
+
const missing = Array.isArray(outputs.missing) ? outputs.missing : null;
|
|
396
|
+
if (!missing || !num(outputs.checked)) return null; // nothing in scope
|
|
397
|
+
const count = missing.length;
|
|
398
|
+
const sev = severityFor(count, num(cfg.warnCount, Infinity), num(cfg.critCount, Infinity));
|
|
399
|
+
if (!sev) return null;
|
|
400
|
+
|
|
401
|
+
// Name the days and the workflow — an alert saying only "2 artefacts missing"
|
|
402
|
+
// sends the reader back to the bus log, which is exactly where the answer is not.
|
|
403
|
+
const days = [...new Set(missing.map((m) => m.day))].sort();
|
|
404
|
+
const labels = [...new Set(missing.map((m) => m.label || m.cadence))].sort();
|
|
405
|
+
const detail = missing
|
|
406
|
+
.slice(0, 4)
|
|
407
|
+
.map((m) => `${m.path} (${m.reason})`)
|
|
408
|
+
.join("; ");
|
|
409
|
+
const more = count > 4 ? ` +${count - 4} more` : "";
|
|
410
|
+
return {
|
|
411
|
+
id: "cadence_output_missing",
|
|
412
|
+
severity: sev,
|
|
413
|
+
detail: `${labels.join(", ")} produced no usable output for ${days.join(", ")}: ${detail}${more}. Checked on disk, independently of the cadence bus — the bus may still report this run as processed.`,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
368
417
|
// ---------------------------------------------------------------------------
|
|
369
418
|
// emitAlerts
|
|
370
419
|
// ---------------------------------------------------------------------------
|