@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
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* responder.test.mjs — conversation history behind holding messages / quick replies.
|
|
3
|
-
* Run: node --test scripts/daemon/responder.test.mjs
|
|
4
|
-
*
|
|
5
|
-
* `loadConversationHistory` used to build its candidate directories as
|
|
6
|
-
* `memory/interactions/slack/<id>` unconditionally, and its writer
|
|
7
|
-
* (`logInteraction`) early-returns for any service that is not Slack. A Cohort
|
|
8
|
-
* DM therefore had nothing written and nothing to read, so every holding
|
|
9
|
-
* message for Cohort was generated from the current message alone. A bare "?"
|
|
10
|
-
* produced "What's on your mind?" — indistinguishable from amnesia, and the
|
|
11
|
-
* CEO read it as exactly that (2026-08-13, dm-h001-a016).
|
|
12
|
-
*
|
|
13
|
-
* Cohort has an authoritative `messaging.history`. These tests pin that the
|
|
14
|
-
* cohort branch reads it, orders it oldest-first, and NEVER throws — the
|
|
15
|
-
* holding path must degrade to "no history" rather than take the send down.
|
|
16
|
-
*/
|
|
17
|
-
"use strict";
|
|
18
|
-
|
|
19
|
-
import { test } from "node:test";
|
|
20
|
-
import assert from "node:assert/strict";
|
|
21
|
-
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
|
22
|
-
import { tmpdir } from "node:os";
|
|
23
|
-
import { join } from "node:path";
|
|
24
|
-
|
|
25
|
-
import { loadConversationHistory, deriveRouterItem } from "./responder.mjs";
|
|
26
|
-
import { routingKey } from "./lib/session-router.mjs";
|
|
27
|
-
|
|
28
|
-
const cohortItem = {
|
|
29
|
-
sender: "Dana Okafor",
|
|
30
|
-
service: "cohort",
|
|
31
|
-
channel: "dm-h001-a016",
|
|
32
|
-
channel_id: "cmql1802601iihhzpf774vwbk",
|
|
33
|
-
content: "?",
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Newest-first, the way messaging.history actually returns it.
|
|
37
|
-
const HISTORY = [
|
|
38
|
-
{ id: "m3", createdAt: "2026-08-13T05:47:51Z", authorName: "Dana Okafor", body: "with that in mind, fix these issues you just noted please" },
|
|
39
|
-
{ id: "m2", createdAt: "2026-08-12T13:52:53Z", authorName: "Isla Roselli", body: "Both are merged. #19 went in as 94f227c4." },
|
|
40
|
-
{ id: "m1", createdAt: "2026-08-12T13:47:30Z", authorName: "Dana Okafor", body: "why haven't you merged? please go ahead" },
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
test("cohort: reads messaging.history and renders it oldest-first", async () => {
|
|
44
|
-
const seen = [];
|
|
45
|
-
const text = await loadConversationHistory(cohortItem, {
|
|
46
|
-
fetchHistory: async (params, o) => {
|
|
47
|
-
seen.push({ params, hasCfg: Boolean(o && o.cfg) });
|
|
48
|
-
return HISTORY;
|
|
49
|
-
},
|
|
50
|
-
loadOrgConfig: () => ({ org: { cohort: { enabled: true, base: "https://x", token: "t" } } }),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
assert.ok(text, "expected a transcript, got null");
|
|
54
|
-
assert.match(text, /Dana Okafor: why haven't you merged/);
|
|
55
|
-
assert.match(text, /Isla Roselli: Both are merged/);
|
|
56
|
-
assert.match(text, /Dana Okafor: with that in mind/);
|
|
57
|
-
|
|
58
|
-
// Oldest-first: the model reads a conversation, not a reversed one.
|
|
59
|
-
assert.ok(
|
|
60
|
-
text.indexOf("why haven't you merged") < text.indexOf("with that in mind"),
|
|
61
|
-
"history must be rendered oldest-first",
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// The channel it asks for is the item's channel_id, and cfg is ALWAYS passed:
|
|
65
|
-
// fetchHistory() fails open to [] when cfg is omitted, which is silently
|
|
66
|
-
// indistinguishable from an empty channel. That is the bug this file exists for.
|
|
67
|
-
assert.equal(seen.length, 1);
|
|
68
|
-
assert.equal(seen[0].params.channel, "cmql1802601iihhzpf774vwbk");
|
|
69
|
-
assert.equal(seen[0].hasCfg, true, "cfg must be passed or fetchHistory returns [] silently");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("cohort: an empty channel is null, not an empty transcript", async () => {
|
|
73
|
-
const text = await loadConversationHistory(cohortItem, {
|
|
74
|
-
fetchHistory: async () => [],
|
|
75
|
-
loadOrgConfig: () => ({}),
|
|
76
|
-
});
|
|
77
|
-
assert.equal(text, null);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("cohort: a roomless surface never asks messaging.history for its LABEL", async () => {
|
|
81
|
-
// A board / doc / decision item has a blank `channel_id` by design and its
|
|
82
|
-
// `channel` is the human label (`task/<title>`). This used to fall back to
|
|
83
|
-
// that label, so the history read asked hq for a channel named after a task
|
|
84
|
-
// title and got nothing — the same label-for-an-id substitution that had the
|
|
85
|
-
// REPLY posted to a task title. The entity's own thread already rides on the
|
|
86
|
-
// item as `thread_context`, so there is nothing to fetch here.
|
|
87
|
-
const seen = [];
|
|
88
|
-
const text = await loadConversationHistory(
|
|
89
|
-
{ sender: "Dana Okafor", service: "cohort", kind: "task_comment", channel: "task/Roll out tier-3 guardrails", channel_id: "", content: "?" },
|
|
90
|
-
{ fetchHistory: async (params) => { seen.push(params); return []; }, loadOrgConfig: () => ({}) },
|
|
91
|
-
);
|
|
92
|
-
assert.equal(seen.length, 0, "no history call may be made with a label");
|
|
93
|
-
assert.equal(text, null);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("cohort: a failing history call degrades to null and never throws", async () => {
|
|
97
|
-
const text = await loadConversationHistory(cohortItem, {
|
|
98
|
-
fetchHistory: async () => { throw new Error("cohort unreachable"); },
|
|
99
|
-
loadOrgConfig: () => ({}),
|
|
100
|
-
});
|
|
101
|
-
assert.equal(text, null);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("cohort: the current message is not fed back as its own history", async () => {
|
|
105
|
-
const text = await loadConversationHistory(
|
|
106
|
-
{ ...cohortItem, message_id: "m3" },
|
|
107
|
-
{
|
|
108
|
-
fetchHistory: async () => HISTORY,
|
|
109
|
-
loadOrgConfig: () => ({}),
|
|
110
|
-
},
|
|
111
|
-
);
|
|
112
|
-
assert.ok(text);
|
|
113
|
-
assert.doesNotMatch(text, /with that in mind/, "the inbound message itself must be excluded");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("slack: still reads the local interaction mirror", async () => {
|
|
117
|
-
const root = mkdtempSync(join(tmpdir(), "maestro-responder-"));
|
|
118
|
-
const dir = join(root, "memory", "interactions", "slack", "D099N1JGKRQ");
|
|
119
|
-
mkdirSync(dir, { recursive: true });
|
|
120
|
-
writeFileSync(
|
|
121
|
-
join(dir, "2026-08-13.jsonl"),
|
|
122
|
-
[
|
|
123
|
-
JSON.stringify({ ts: "2026-08-13T05:00:00Z", from: "Dana Okafor", content: "ship it" }),
|
|
124
|
-
JSON.stringify({ ts: "2026-08-13T05:01:00Z", from: "agent", content: "shipped" }),
|
|
125
|
-
].join("\n"),
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const text = await loadConversationHistory(
|
|
129
|
-
{ sender: "Dana Okafor", service: "slack", channel: "D099N1JGKRQ", content: "?" },
|
|
130
|
-
{ agentRepoDir: root },
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
assert.ok(text, "expected the slack mirror to still be read");
|
|
134
|
-
assert.match(text, /Dana Okafor: ship it/);
|
|
135
|
-
assert.match(text, /agent: shipped/);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("cohort: falls back to the local mirror when the server is unreachable", async () => {
|
|
139
|
-
// Isla's original returned null outright for Cohort when messaging.history
|
|
140
|
-
// failed. Falling through to the mirror means a transient API blip degrades
|
|
141
|
-
// to cached context instead of to none — the mirror is a poor source of
|
|
142
|
-
// truth but a perfectly good backstop.
|
|
143
|
-
const root = mkdtempSync(join(tmpdir(), "maestro-responder-"));
|
|
144
|
-
const dir = join(root, "memory", "interactions", "cohort", "chan-1");
|
|
145
|
-
mkdirSync(dir, { recursive: true });
|
|
146
|
-
writeFileSync(
|
|
147
|
-
join(dir, "2026-08-13.jsonl"),
|
|
148
|
-
JSON.stringify({ ts: "2026-08-13T05:00:00Z", from: "Dana Okafor", content: "cached line" }),
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const text = await loadConversationHistory(
|
|
152
|
-
{ sender: "Dana Okafor", service: "cohort", channel: "chan-1", channel_id: "chan-1", content: "?" },
|
|
153
|
-
{
|
|
154
|
-
agentRepoDir: root,
|
|
155
|
-
loadOrgConfig: () => ({}),
|
|
156
|
-
fetchHistory: async () => { throw new Error("network down"); },
|
|
157
|
-
},
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
assert.ok(text, "a server failure must not erase locally cached context");
|
|
161
|
-
assert.match(text, /Dana Okafor: cached line/);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("cohort: server history wins over the local mirror", async () => {
|
|
165
|
-
const root = mkdtempSync(join(tmpdir(), "maestro-responder-"));
|
|
166
|
-
const dir = join(root, "memory", "interactions", "cohort", "chan-1");
|
|
167
|
-
mkdirSync(dir, { recursive: true });
|
|
168
|
-
writeFileSync(
|
|
169
|
-
join(dir, "2026-08-13.jsonl"),
|
|
170
|
-
JSON.stringify({ ts: "2026-08-13T05:00:00Z", from: "stale", content: "outdated mirror line" }),
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
const text = await loadConversationHistory(
|
|
174
|
-
{ sender: "Dana Okafor", service: "cohort", channel: "chan-1", channel_id: "chan-1", content: "?" },
|
|
175
|
-
{
|
|
176
|
-
agentRepoDir: root,
|
|
177
|
-
loadOrgConfig: () => ({}),
|
|
178
|
-
fetchHistory: async () => ([
|
|
179
|
-
{ id: "m2", authorName: "Dana Okafor", body: "authoritative line", createdAt: "2026-08-13T06:00:00Z" },
|
|
180
|
-
]),
|
|
181
|
-
},
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
assert.match(text, /authoritative line/, "the server is the source of truth");
|
|
185
|
-
assert.doesNotMatch(text, /outdated mirror line/, "the stale mirror must not be mixed in");
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// ---------------------------------------------------------------------------
|
|
189
|
-
// Session continuity: the responder's router adapter (design §3 R10)
|
|
190
|
-
//
|
|
191
|
-
// `deriveRouterItem` returned null for `cohort`, so every Cohort turn fell back
|
|
192
|
-
// to a fresh pre-minted UUID and the session router — which exists, and which
|
|
193
|
-
// Slack and Gmail have used all along — never saw the one surface the owner
|
|
194
|
-
// actually uses.
|
|
195
|
-
// ---------------------------------------------------------------------------
|
|
196
|
-
|
|
197
|
-
test("deriveRouterItem keys a Cohort item on its room, so a follow-up can resume", () => {
|
|
198
|
-
const out = deriveRouterItem(cohortItem);
|
|
199
|
-
assert.deepEqual(out, {
|
|
200
|
-
source: "cohort",
|
|
201
|
-
channel: "cmql1802601iihhzpf774vwbk",
|
|
202
|
-
thread_root_id: null,
|
|
203
|
-
});
|
|
204
|
-
assert.equal(routingKey(out), "cohort:cmql1802601iihhzpf774vwbk");
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("deriveRouterItem narrows to the thread when there is one", () => {
|
|
208
|
-
const out = deriveRouterItem({ ...cohortItem, thread_id: "root-42" });
|
|
209
|
-
assert.equal(routingKey(out), "cohort:cmql1802601iihhzpf774vwbk:root-42");
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("deriveRouterItem refuses the display label as a key", () => {
|
|
213
|
-
// `task/<title>` and `doc/<name>` are labels; two rooms can share one.
|
|
214
|
-
assert.equal(deriveRouterItem({ sender: "Dana", service: "cohort", channel: "task/Ship the thing" }), null);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test("deriveRouterItem still keys slack, gmail and calendar exactly as before", () => {
|
|
218
|
-
assert.equal(routingKey(deriveRouterItem({ service: "slack", channel: "D099", thread_id: null })), "slack:D099");
|
|
219
|
-
assert.equal(routingKey(deriveRouterItem({ service: "gmail", thread_id: "t-1" })), "gmail:t-1");
|
|
220
|
-
assert.equal(routingKey(deriveRouterItem({ service: "calendar", event_id: "e-1" })), "calendar:e-1");
|
|
221
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sdk-version.test.mjs — the daemon's own-version resolution prefers the
|
|
3
|
-
* version of the COPIED tree (the manifest) over node_modules.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { test } from "node:test";
|
|
7
|
-
import assert from "node:assert/strict";
|
|
8
|
-
import { resolveRunningSdkVersion, SDK_PACKAGE } from "./sdk-version.mjs";
|
|
9
|
-
|
|
10
|
-
function reader(files) {
|
|
11
|
-
return (p) => { if (p in files) return files[p]; throw new Error(`ENOENT ${p}`); };
|
|
12
|
-
}
|
|
13
|
-
const A = "/seat/ivy-ai";
|
|
14
|
-
const MANIFEST = `${A}/.maestro/shipped-manifest.json`;
|
|
15
|
-
const DEP = `${A}/node_modules/@cohortapp/agent-sdk/package.json`;
|
|
16
|
-
const OWN = "/sdk/package.json";
|
|
17
|
-
|
|
18
|
-
test("the manifest's sdkVersion wins: node_modules may already hold the NEXT version while the copied scripts are still the old one", () => {
|
|
19
|
-
const r = resolveRunningSdkVersion({ agentDir: A, ownPackagePath: OWN, readJson: reader({
|
|
20
|
-
[MANIFEST]: { version: 1, sdkVersion: "2.11.17" },
|
|
21
|
-
[DEP]: { name: SDK_PACKAGE, version: "2.12.0" },
|
|
22
|
-
}) });
|
|
23
|
-
assert.deepEqual(r, { version: "2.11.17", source: "manifest" });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("a pre-2.12 manifest (no sdkVersion) or none → node_modules; then the package this file ships in; then empty", () => {
|
|
27
|
-
assert.deepEqual(resolveRunningSdkVersion({ agentDir: A, readJson: reader({ [MANIFEST]: { version: 1, files: [] }, [DEP]: { name: SDK_PACKAGE, version: "2.11.15" } }) }), { version: "2.11.15", source: "node_modules" });
|
|
28
|
-
assert.deepEqual(resolveRunningSdkVersion({ agentDir: A, ownPackagePath: OWN, readJson: reader({ [OWN]: { name: SDK_PACKAGE, version: "2.12.0" } }) }), { version: "2.12.0", source: "package" });
|
|
29
|
-
assert.deepEqual(resolveRunningSdkVersion({ agentDir: A, ownPackagePath: OWN, readJson: reader({ [OWN]: { name: "ivy-ai", version: "1.0.0" } }) }), { version: "", source: "none" }, "an agent's own package.json is not the SDK");
|
|
30
|
-
assert.deepEqual(resolveRunningSdkVersion({ agentDir: A, readJson: () => { throw new Error("EACCES"); } }), { version: "", source: "none" }, "fail-open");
|
|
31
|
-
});
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* test-session-lock.mjs — Tests for file-based session locks and sent-message registry
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
7
|
-
import assert from "node:assert/strict";
|
|
8
|
-
import { mkdirSync, rmSync, existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
9
|
-
import { join } from "path";
|
|
10
|
-
|
|
11
|
-
// Tests use a temporary directory to avoid polluting real state
|
|
12
|
-
const TEST_DIR = join(import.meta.dirname, "../../.test-session-locks");
|
|
13
|
-
const LOCKS_DIR = join(TEST_DIR, "locks");
|
|
14
|
-
const REGISTRY_PATH = join(TEST_DIR, "sent-registry.jsonl");
|
|
15
|
-
|
|
16
|
-
// We'll set these env vars so the module uses our test paths
|
|
17
|
-
process.env.__TEST_SESSION_DIR = TEST_DIR;
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
sanitiseItemId,
|
|
21
|
-
acquireLock,
|
|
22
|
-
releaseLock,
|
|
23
|
-
updateLock,
|
|
24
|
-
readLock,
|
|
25
|
-
scanStaleLocks,
|
|
26
|
-
checkRecentlySent,
|
|
27
|
-
registerSent,
|
|
28
|
-
} = await import("./session-lock.mjs");
|
|
29
|
-
|
|
30
|
-
// ─── sanitiseItemId ─────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
describe("sanitiseItemId", () => {
|
|
33
|
-
it("replaces colons and dots with hyphens", () => {
|
|
34
|
-
assert.equal(
|
|
35
|
-
sanitiseItemId("slack:D099N1JGKRQ:1775331885.690669"),
|
|
36
|
-
"slack-D099N1JGKRQ-1775331885-690669"
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("handles simple alphanumeric IDs unchanged", () => {
|
|
41
|
-
assert.equal(sanitiseItemId("abc123"), "abc123");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("replaces spaces and special chars", () => {
|
|
45
|
-
assert.equal(sanitiseItemId("foo bar/baz@qux"), "foo-bar-baz-qux");
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// ─── acquireLock / releaseLock / readLock ────────────────────────
|
|
50
|
-
|
|
51
|
-
describe("acquireLock / releaseLock / readLock", () => {
|
|
52
|
-
beforeEach(() => {
|
|
53
|
-
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
afterEach(() => {
|
|
57
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("acquires a lock on first call", () => {
|
|
61
|
-
const result = acquireLock("test-item-1", { sender: "Alice", channel: "C001" });
|
|
62
|
-
assert.equal(result.acquired, true);
|
|
63
|
-
assert.equal(existsSync(join(LOCKS_DIR, "test-item-1.lock")), true);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("rejects second acquire for same item", () => {
|
|
67
|
-
acquireLock("test-item-2", { sender: "Alice", channel: "C001" });
|
|
68
|
-
const result = acquireLock("test-item-2", { sender: "Bob", channel: "C002" });
|
|
69
|
-
assert.equal(result.acquired, false);
|
|
70
|
-
assert.ok(result.existing);
|
|
71
|
-
assert.equal(result.existing.sender, "Alice");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("allows acquire after release", () => {
|
|
75
|
-
acquireLock("test-item-3", { sender: "Alice", channel: "C001" });
|
|
76
|
-
releaseLock("test-item-3");
|
|
77
|
-
const result = acquireLock("test-item-3", { sender: "Bob", channel: "C002" });
|
|
78
|
-
assert.equal(result.acquired, true);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("readLock returns lock data when locked", () => {
|
|
82
|
-
acquireLock("test-item-4", { sender: "Alice", channel: "C001" });
|
|
83
|
-
const data = readLock("test-item-4");
|
|
84
|
-
assert.equal(data.sender, "Alice");
|
|
85
|
-
assert.equal(data.channel, "C001");
|
|
86
|
-
assert.equal(data.holdingSent, false);
|
|
87
|
-
assert.equal(data.substantiveSent, false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("readLock returns null when no lock", () => {
|
|
91
|
-
const data = readLock("nonexistent-item");
|
|
92
|
-
assert.equal(data, null);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("overwrites stale lock (older than STALE_LOCK_MS)", () => {
|
|
96
|
-
// Write a lock older than STALE_LOCK_MS (2h — raised from 15min so it
|
|
97
|
-
// exceeds the longest inbox/Opus session and survives daemon restarts;
|
|
98
|
-
// see session-lock.mjs). A 3h-old lock is unambiguously stale.
|
|
99
|
-
const lockPath = join(LOCKS_DIR, "test-item-5.lock");
|
|
100
|
-
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
101
|
-
const staleLock = {
|
|
102
|
-
sessionId: "old-session",
|
|
103
|
-
itemId: "test-item-5",
|
|
104
|
-
sender: "Alice",
|
|
105
|
-
channel: "C001",
|
|
106
|
-
acquiredAt: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),
|
|
107
|
-
holdingSent: false,
|
|
108
|
-
substantiveSent: false,
|
|
109
|
-
};
|
|
110
|
-
writeFileSync(lockPath, JSON.stringify(staleLock));
|
|
111
|
-
|
|
112
|
-
const result = acquireLock("test-item-5", { sender: "Bob", channel: "C002" });
|
|
113
|
-
assert.equal(result.acquired, true);
|
|
114
|
-
const data = readLock("test-item-5");
|
|
115
|
-
assert.equal(data.sender, "Bob");
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("does NOT overwrite a fresh lock younger than STALE_LOCK_MS", () => {
|
|
119
|
-
// 20 min old: well within the 2h freshness window, so a re-acquire must be
|
|
120
|
-
// denied (this is the behaviour the old ">15 min" test wrongly assumed
|
|
121
|
-
// would expire). Guards against any future shortening of STALE_LOCK_MS that
|
|
122
|
-
// would re-introduce the mid-session triple-dispatch incident.
|
|
123
|
-
const lockPath = join(LOCKS_DIR, "test-item-5b.lock");
|
|
124
|
-
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
125
|
-
writeFileSync(lockPath, JSON.stringify({
|
|
126
|
-
sessionId: "live-session", itemId: "test-item-5b", sender: "Alice", channel: "C001",
|
|
127
|
-
acquiredAt: new Date(Date.now() - 20 * 60 * 1000).toISOString(),
|
|
128
|
-
holdingSent: false, substantiveSent: false,
|
|
129
|
-
}));
|
|
130
|
-
const result = acquireLock("test-item-5b", { sender: "Bob", channel: "C002" });
|
|
131
|
-
assert.equal(result.acquired, false, "a 20-min-old lock is still fresh under the 2h window");
|
|
132
|
-
assert.equal(readLock("test-item-5b").sender, "Alice", "the live lock is preserved");
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// ─── updateLock ─────────────────────────────────────────────────
|
|
137
|
-
|
|
138
|
-
describe("updateLock", () => {
|
|
139
|
-
beforeEach(() => {
|
|
140
|
-
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
afterEach(() => {
|
|
144
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("updates holdingSent flag on existing lock", () => {
|
|
148
|
-
acquireLock("test-item-6", { sender: "Alice", channel: "C001" });
|
|
149
|
-
updateLock("test-item-6", { holdingSent: true });
|
|
150
|
-
const data = readLock("test-item-6");
|
|
151
|
-
assert.equal(data.holdingSent, true);
|
|
152
|
-
assert.equal(data.substantiveSent, false);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("no-ops when lock does not exist", () => {
|
|
156
|
-
// Should not throw
|
|
157
|
-
updateLock("nonexistent", { holdingSent: true });
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// ─── scanStaleLocks ─────────────────────────────────────────────
|
|
162
|
-
|
|
163
|
-
describe("scanStaleLocks", () => {
|
|
164
|
-
beforeEach(() => {
|
|
165
|
-
mkdirSync(LOCKS_DIR, { recursive: true });
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
afterEach(() => {
|
|
169
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("clears stale locks and leaves fresh ones", () => {
|
|
173
|
-
// Fresh lock
|
|
174
|
-
acquireLock("fresh-item", { sender: "Alice", channel: "C001" });
|
|
175
|
-
|
|
176
|
-
// Stale lock — older than STALE_LOCK_MS (2h). 3h ago is unambiguously
|
|
177
|
-
// stale. (The old fixture used 20min, which the raised threshold no longer
|
|
178
|
-
// treats as stale.)
|
|
179
|
-
const stalePath = join(LOCKS_DIR, "stale-item.lock");
|
|
180
|
-
writeFileSync(stalePath, JSON.stringify({
|
|
181
|
-
sessionId: "old", itemId: "stale-item", sender: "Bob", channel: "C002",
|
|
182
|
-
acquiredAt: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),
|
|
183
|
-
holdingSent: false, substantiveSent: false,
|
|
184
|
-
}));
|
|
185
|
-
|
|
186
|
-
const cleared = scanStaleLocks();
|
|
187
|
-
assert.equal(cleared, 1);
|
|
188
|
-
assert.equal(existsSync(join(LOCKS_DIR, "fresh-item.lock")), true);
|
|
189
|
-
assert.equal(existsSync(stalePath), false);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// ─── sent-message registry ──────────────────────────────────────
|
|
194
|
-
|
|
195
|
-
describe("sent-message registry", () => {
|
|
196
|
-
beforeEach(() => {
|
|
197
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
198
|
-
// Ensure clean registry
|
|
199
|
-
try { unlinkSync(REGISTRY_PATH); } catch {}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
afterEach(() => {
|
|
203
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it("allows first send to a channel", () => {
|
|
207
|
-
const result = checkRecentlySent("C001", "1234.5678", "holding");
|
|
208
|
-
assert.equal(result.allowed, true);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it("blocks duplicate send to same channel+thread+type within window", () => {
|
|
212
|
-
registerSent("C001", "1234.5678", "holding", "s-1", "Got it");
|
|
213
|
-
const result = checkRecentlySent("C001", "1234.5678", "holding");
|
|
214
|
-
assert.equal(result.allowed, false);
|
|
215
|
-
assert.ok(result.reason.includes("duplicate"));
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("blocks a different type to the same channel+thread (thread cooldown — Rule 2)", () => {
|
|
219
|
-
// Rule 2 deliberately blocks ANY type within the same channel+thread window
|
|
220
|
-
// (not just same-type). This prevents cascading replies when one thread is
|
|
221
|
-
// re-ingested across poll cycles as different message types. (The original
|
|
222
|
-
// expectation — "different type is allowed" — predated Rule 2 and was a
|
|
223
|
-
// stale assertion; the product intentionally cools down the whole thread.)
|
|
224
|
-
registerSent("C001", "1234.5678", "holding", "s-1", "Got it");
|
|
225
|
-
const result = checkRecentlySent("C001", "1234.5678", "substantive");
|
|
226
|
-
assert.equal(result.allowed, false);
|
|
227
|
-
assert.match(result.reason, /thread cooldown/);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("allows a different type to the same NON-threaded channel (no thread cooldown)", () => {
|
|
231
|
-
// With threadTs=null there is no thread to cool down, so Rule 1 (same
|
|
232
|
-
// channel+thread+TYPE) is the only same-channel gate — a different type is
|
|
233
|
-
// allowed. (Uses a non-DM channel so the DM channel-level dedup, Rule 0b,
|
|
234
|
-
// doesn't apply.)
|
|
235
|
-
registerSent("C010", null, "holding", "s-1", "Got it");
|
|
236
|
-
const result = checkRecentlySent("C010", null, "substantive");
|
|
237
|
-
assert.equal(result.allowed, true);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it("allows same type to different channel", () => {
|
|
241
|
-
registerSent("C001", "1234.5678", "holding", "s-1", "Got it");
|
|
242
|
-
const result = checkRecentlySent("C002", "1234.5678", "holding");
|
|
243
|
-
assert.equal(result.allowed, true);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("blocks rapid substantive sends to same channel even with different threads", () => {
|
|
247
|
-
registerSent("C001", "1111.0000", "substantive", "s-1", "Here is the answer");
|
|
248
|
-
const result = checkRecentlySent("C001", "2222.0000", "substantive");
|
|
249
|
-
assert.equal(result.allowed, false);
|
|
250
|
-
assert.ok(result.reason.includes("rapid"));
|
|
251
|
-
});
|
|
252
|
-
});
|