@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,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mesh-integration.test.mjs — SP5 hot-refresh from the mesh beat + the
|
|
3
|
-
* `toolset_changed` directive.
|
|
4
|
-
*
|
|
5
|
-
* Covers: beatOnce polls + refetches the granted integration tools and writes
|
|
6
|
-
* state/org/toolset.json on a version change; a matching version writes nothing;
|
|
7
|
-
* a `toolset_changed` directive forces an immediate refresh ahead of the poll;
|
|
8
|
-
* the directive is a KNOWN directive. Hermetic: client is a stub, disk is a
|
|
9
|
-
* mkdtemp root. Run: node --test lib/org/mesh-integration.test.mjs
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
"use strict";
|
|
13
|
-
|
|
14
|
-
import { test, beforeEach, after } from "node:test";
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import { mkdtempSync, rmSync, existsSync, readFileSync } from "node:fs";
|
|
17
|
-
import { tmpdir } from "node:os";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
|
|
20
|
-
import { _internals } from "./mesh.mjs";
|
|
21
|
-
import {
|
|
22
|
-
currentToolsetVersion,
|
|
23
|
-
getIntegrationTools,
|
|
24
|
-
integrationToolsetPath,
|
|
25
|
-
_resetForTest,
|
|
26
|
-
} from "./integration-tools.mjs";
|
|
27
|
-
|
|
28
|
-
const { beatOnce, handleDirective, KNOWN_DIRECTIVES } = _internals;
|
|
29
|
-
|
|
30
|
-
const TOOLS = [
|
|
31
|
-
{ slug: "open-meteo", name: "forecast", description: "d", inputSchema: {} },
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const roots = [];
|
|
35
|
-
function freshRoot() {
|
|
36
|
-
const r = mkdtempSync(join(tmpdir(), "mesh-int-"));
|
|
37
|
-
roots.push(r);
|
|
38
|
-
return r;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** A mesh loop ctx with a stub client answering presence + the two SP1 reads. */
|
|
42
|
-
function makeCtx(root, { version, tools = TOOLS, directives = [] }) {
|
|
43
|
-
const calls = [];
|
|
44
|
-
const client = {
|
|
45
|
-
presenceBeat: async () => ({ ok: true, result: { directives } }),
|
|
46
|
-
call: async (method, params) => {
|
|
47
|
-
calls.push({ method, params });
|
|
48
|
-
if (method === "integration.toolsetVersion") return { ok: true, result: { version } };
|
|
49
|
-
if (method === "integration.listAgentTools") return { ok: true, result: { version, tools } };
|
|
50
|
-
return { ok: false, error: { code: "NOT_FOUND", message: "?" } };
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
const ctx = {
|
|
54
|
-
agentRoot: root,
|
|
55
|
-
client,
|
|
56
|
-
base: "https://org.example",
|
|
57
|
-
token: "nlk",
|
|
58
|
-
orgId: "acme",
|
|
59
|
-
agentMemberId: "wm_self",
|
|
60
|
-
fetchImpl: undefined,
|
|
61
|
-
now: () => 1000,
|
|
62
|
-
beatFocus: null,
|
|
63
|
-
collectStatus: async () => undefined,
|
|
64
|
-
stopped: false,
|
|
65
|
-
requestStop() {},
|
|
66
|
-
stop() { this.stopped = true; },
|
|
67
|
-
_calls: calls,
|
|
68
|
-
};
|
|
69
|
-
return ctx;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
beforeEach(() => _resetForTest());
|
|
73
|
-
after(() => {
|
|
74
|
-
for (const r of roots) {
|
|
75
|
-
try { rmSync(r, { recursive: true, force: true }); } catch { /* best-effort */ }
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("toolset_changed is a recognised directive", () => {
|
|
80
|
-
assert.ok(KNOWN_DIRECTIVES.has("toolset_changed"));
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("beatOnce refreshes + writes state/org/toolset.json on a version change", async () => {
|
|
84
|
-
const root = freshRoot();
|
|
85
|
-
const ctx = makeCtx(root, { version: 4 });
|
|
86
|
-
const directives = await beatOnce(ctx);
|
|
87
|
-
assert.deepEqual(directives, []);
|
|
88
|
-
// Cache + disk both reflect v4 with the mapped tool.
|
|
89
|
-
assert.equal(currentToolsetVersion(), 4);
|
|
90
|
-
assert.equal(getIntegrationTools()[0].name, "plugin__open_meteo__forecast");
|
|
91
|
-
const p = integrationToolsetPath(root);
|
|
92
|
-
assert.ok(existsSync(p), "toolset.json written");
|
|
93
|
-
const doc = JSON.parse(readFileSync(p, "utf8"));
|
|
94
|
-
assert.equal(doc.version, 4);
|
|
95
|
-
assert.equal(doc.tools[0].name, "plugin__open_meteo__forecast");
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("beatOnce with an unchanged version does not refetch the manifest", async () => {
|
|
99
|
-
const root = freshRoot();
|
|
100
|
-
await beatOnce(makeCtx(root, { version: 4 })); // seed v4
|
|
101
|
-
const ctx = makeCtx(root, { version: 4 }); // same version, fresh spy
|
|
102
|
-
await beatOnce(ctx);
|
|
103
|
-
const methods = ctx._calls.map((c) => c.method);
|
|
104
|
-
assert.ok(methods.includes("integration.toolsetVersion"));
|
|
105
|
-
assert.ok(
|
|
106
|
-
!methods.includes("integration.listAgentTools"),
|
|
107
|
-
"no manifest refetch when the version is unchanged",
|
|
108
|
-
);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("a toolset_changed directive forces an immediate refresh + disk write", async () => {
|
|
112
|
-
const root = freshRoot();
|
|
113
|
-
const ctx = makeCtx(root, { version: 7 });
|
|
114
|
-
await handleDirective("toolset_changed", ctx);
|
|
115
|
-
assert.equal(currentToolsetVersion(), 7);
|
|
116
|
-
const doc = JSON.parse(readFileSync(integrationToolsetPath(root), "utf8"));
|
|
117
|
-
assert.equal(doc.version, 7);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("a refresh failure never throws out of the beat (fail-open)", async () => {
|
|
121
|
-
const root = freshRoot();
|
|
122
|
-
const ctx = makeCtx(root, { version: 1 });
|
|
123
|
-
ctx.client.call = async () => { throw new Error("network down"); };
|
|
124
|
-
const directives = await beatOnce(ctx); // must resolve, not reject
|
|
125
|
-
assert.deepEqual(directives, []);
|
|
126
|
-
assert.equal(existsSync(integrationToolsetPath(root)), false);
|
|
127
|
-
});
|
package/lib/org/mesh.test.mjs
DELETED
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mesh.test.mjs — the agent's runtime membership of the org mesh (connectOrgMesh).
|
|
3
|
-
*
|
|
4
|
-
* Drives connectOrgMesh over an injected org client + injected interval + a tmp
|
|
5
|
-
* agentRoot, asserting: registers on boot when enabled; no-op when disabled; a
|
|
6
|
-
* "halt" beat directive trips the kill switch (.emergency-stop + requestStop);
|
|
7
|
-
* "policy_stale" triggers a policy refetch; "resync" refetches the directory into
|
|
8
|
-
* the local peers cache; an unreachable org never throws. No network.
|
|
9
|
-
*
|
|
10
|
-
* Run: node --test lib/org/mesh.test.mjs
|
|
11
|
-
*/
|
|
12
|
-
"use strict";
|
|
13
|
-
|
|
14
|
-
import { test } from "node:test";
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
17
|
-
import { tmpdir } from "node:os";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
|
|
20
|
-
import { connectOrgMesh, DEFAULT_BEAT_MS, _internals } from "./mesh.mjs";
|
|
21
|
-
|
|
22
|
-
const BASE = "https://org.cohortapp.com";
|
|
23
|
-
|
|
24
|
-
function tmpRepo() {
|
|
25
|
-
const root = mkdtempSync(join(tmpdir(), "org-mesh-"));
|
|
26
|
-
mkdirSync(join(root, "config"), { recursive: true });
|
|
27
|
-
writeFileSync(
|
|
28
|
-
join(root, "config", "agent.json"),
|
|
29
|
-
JSON.stringify({ firstName: "Alex", fullName: "Alex Chen", repoSlug: "alex-ai", title: "Chief of Staff" }),
|
|
30
|
-
);
|
|
31
|
-
return root;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function cfgEnabled() {
|
|
35
|
-
return { org: { cohort: { enabled: true, base: BASE, token: "tok" } } };
|
|
36
|
-
}
|
|
37
|
-
function cfgDisabled() {
|
|
38
|
-
return { org: { cohort: { enabled: false, base: BASE, token: "tok" } } };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* A fake org client. `beatFrames` is a queue of presence.beat frames to return
|
|
43
|
-
* (one per beat); when drained it returns a benign empty-directives frame.
|
|
44
|
-
* Records every register / presenceBeat / fetchDirectory call.
|
|
45
|
-
*/
|
|
46
|
-
function fakeClient(opts = {}) {
|
|
47
|
-
const c = {
|
|
48
|
-
isEnabled: opts.isEnabled || ((cfg) => !!(cfg && cfg.org && cfg.org.cohort && cfg.org.cohort.enabled)),
|
|
49
|
-
configFromAgent: (cfg) => ({ base: BASE, token: "tok", orgId: "", enabled: true, trustedSignersPem: [] }),
|
|
50
|
-
registerCalls: [],
|
|
51
|
-
beatCalls: [],
|
|
52
|
-
directoryCalls: [],
|
|
53
|
-
_beatFrames: opts.beatFrames ? opts.beatFrames.slice() : [],
|
|
54
|
-
register(entry, o) {
|
|
55
|
-
c.registerCalls.push({ entry, o });
|
|
56
|
-
return Promise.resolve(opts.registerFrame || { ok: true, result: { status: 200 } });
|
|
57
|
-
},
|
|
58
|
-
presenceBeat(params, o) {
|
|
59
|
-
c.beatCalls.push({ params, o });
|
|
60
|
-
const frame = c._beatFrames.length ? c._beatFrames.shift() : { ok: true, result: { directives: [] } };
|
|
61
|
-
return Promise.resolve(frame);
|
|
62
|
-
},
|
|
63
|
-
fetchDirectory(o) {
|
|
64
|
-
c.directoryCalls.push({ o });
|
|
65
|
-
return Promise.resolve(opts.directory || []);
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
return c;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** An injectable interval that never actually schedules; we beat manually. */
|
|
72
|
-
function noopInterval() {
|
|
73
|
-
const fn = () => ({ unref() {} });
|
|
74
|
-
fn.cleared = [];
|
|
75
|
-
return { setInterval: fn, clearInterval: (t) => fn.cleared.push(t) };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
|
|
80
|
-
test("disabled org → no-op handle, never registers", async () => {
|
|
81
|
-
const root = tmpRepo();
|
|
82
|
-
const client = fakeClient();
|
|
83
|
-
const handle = await connectOrgMesh({ agentRoot: root, cfg: cfgDisabled(), client });
|
|
84
|
-
assert.equal(handle.isEnabled, false);
|
|
85
|
-
assert.equal(client.registerCalls.length, 0);
|
|
86
|
-
assert.doesNotThrow(() => handle.stop()); // inert stop is safe
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("enabled org → registers self on boot (idempotent key) + starts the beat", async () => {
|
|
90
|
-
const root = tmpRepo();
|
|
91
|
-
const client = fakeClient();
|
|
92
|
-
const iv = noopInterval();
|
|
93
|
-
let intervalArgs = null;
|
|
94
|
-
const setIntervalSpy = (cb, ms) => { intervalArgs = { cb, ms }; return { unref() {} }; };
|
|
95
|
-
|
|
96
|
-
const handle = await connectOrgMesh({
|
|
97
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
98
|
-
setInterval: setIntervalSpy, clearInterval: iv.clearInterval,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
assert.equal(handle.isEnabled, true);
|
|
102
|
-
assert.equal(client.registerCalls.length, 1, "registered exactly once on boot");
|
|
103
|
-
const reg = client.registerCalls[0];
|
|
104
|
-
assert.equal(reg.entry.id, "alex-ai");
|
|
105
|
-
assert.equal(reg.o.idempotencyKey, "registry.register:alex-ai");
|
|
106
|
-
assert.equal(reg.o.base, BASE);
|
|
107
|
-
assert.ok(intervalArgs, "beat interval was scheduled");
|
|
108
|
-
assert.equal(intervalArgs.ms, DEFAULT_BEAT_MS);
|
|
109
|
-
handle.stop();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('presence-beat "halt" directive trips the kill switch (.emergency-stop + requestStop)', async () => {
|
|
113
|
-
const root = tmpRepo();
|
|
114
|
-
const client = fakeClient({ beatFrames: [{ ok: true, result: { directives: ["halt"] } }] });
|
|
115
|
-
let stopRequested = 0;
|
|
116
|
-
const handle = await connectOrgMesh({
|
|
117
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
118
|
-
setInterval: () => ({ unref() {} }),
|
|
119
|
-
requestStop: () => { stopRequested += 1; },
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Drive one beat explicitly via the loop context.
|
|
123
|
-
const directives = await _internals.beatOnce(handle._ctx);
|
|
124
|
-
assert.deepEqual(directives, ["halt"]);
|
|
125
|
-
assert.ok(existsSync(join(root, ".emergency-stop")), ".emergency-stop flag was written");
|
|
126
|
-
const flag = readFileSync(join(root, ".emergency-stop"), "utf8");
|
|
127
|
-
assert.match(flag, /\d{4}-\d{2}-\d{2}T/, "flag carries a timestamp");
|
|
128
|
-
assert.equal(stopRequested, 1, "requestStop() was invoked");
|
|
129
|
-
assert.equal(handle._ctx.stopped, true, "the beat loop stopped itself on halt");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('"halt" delivered via an error frame (deactivated token) also trips the kill switch', async () => {
|
|
133
|
-
const root = tmpRepo();
|
|
134
|
-
const client = fakeClient({ beatFrames: [{ ok: false, error: { code: "UNAUTHORIZED", directive: "halt" } }] });
|
|
135
|
-
let stopRequested = 0;
|
|
136
|
-
const handle = await connectOrgMesh({
|
|
137
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
138
|
-
setInterval: () => ({ unref() {} }),
|
|
139
|
-
requestStop: () => { stopRequested += 1; },
|
|
140
|
-
});
|
|
141
|
-
const directives = await _internals.beatOnce(handle._ctx);
|
|
142
|
-
assert.deepEqual(directives, ["halt"]);
|
|
143
|
-
assert.ok(existsSync(join(root, ".emergency-stop")));
|
|
144
|
-
assert.equal(stopRequested, 1);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('"policy_stale" directive triggers a policy refetch (handlePolicyStale)', async () => {
|
|
148
|
-
const root = tmpRepo();
|
|
149
|
-
const client = fakeClient({ beatFrames: [{ ok: true, result: { directives: ["policy_stale"] } }] });
|
|
150
|
-
|
|
151
|
-
// Capture the policy.handlePolicyStale call by injecting a fetchImpl: the real
|
|
152
|
-
// policy module will call client.policyFetch through fetchImpl. We assert the
|
|
153
|
-
// refresh path runs end-to-end without throwing and that a fetch happened.
|
|
154
|
-
let policyFetchHits = 0;
|
|
155
|
-
const fetchImpl = async (url) => {
|
|
156
|
-
if (String(url).includes("/v1/policy")) policyFetchHits += 1;
|
|
157
|
-
// Return a bundle with no valid signature → policy quarantines it (fail-open).
|
|
158
|
-
return { ok: true, status: 200, headers: { get: () => undefined }, json: async () => ({ kind: "model-allowlist", version: 2 }) };
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// Seed a cached kind so handlePolicyStale has a kind to refresh.
|
|
162
|
-
mkdirSync(join(root, "config", "cache", "policy"), { recursive: true });
|
|
163
|
-
writeFileSync(
|
|
164
|
-
join(root, "config", "cache", "policy", "model-allowlist.json"),
|
|
165
|
-
JSON.stringify({ kind: "model-allowlist", bundle: {}, verifiedAtMs: 1 }),
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
const handle = await connectOrgMesh({
|
|
169
|
-
agentRoot: root, cfg: cfgEnabled(), client, fetchImpl,
|
|
170
|
-
setInterval: () => ({ unref() {} }),
|
|
171
|
-
});
|
|
172
|
-
const directives = await _internals.beatOnce(handle._ctx);
|
|
173
|
-
assert.deepEqual(directives, ["policy_stale"]);
|
|
174
|
-
assert.ok(policyFetchHits >= 1, "policy bundle was re-fetched on the policy_stale directive");
|
|
175
|
-
handle.stop();
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test('"resync" directive refetches the directory into the local peers cache', async () => {
|
|
179
|
-
const root = tmpRepo();
|
|
180
|
-
const directory = [
|
|
181
|
-
{ id: "robin-ai", name: "Robin", title: "Engineer" },
|
|
182
|
-
{ id: "Sam-AI", name: "Sam", title: "CFO" },
|
|
183
|
-
{ id: "", name: "ignore-me" }, // no id → skipped
|
|
184
|
-
];
|
|
185
|
-
const client = fakeClient({ beatFrames: [{ ok: true, result: { directives: ["resync"] } }], directory });
|
|
186
|
-
|
|
187
|
-
const handle = await connectOrgMesh({
|
|
188
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
189
|
-
setInterval: () => ({ unref() {} }),
|
|
190
|
-
});
|
|
191
|
-
const directives = await _internals.beatOnce(handle._ctx);
|
|
192
|
-
assert.deepEqual(directives, ["resync"]);
|
|
193
|
-
assert.equal(client.directoryCalls.length, 1, "fetchDirectory was called once");
|
|
194
|
-
|
|
195
|
-
const peersDir = join(root, "state", "org", "peers");
|
|
196
|
-
const files = readdirSync(peersDir).sort();
|
|
197
|
-
assert.deepEqual(files, ["robin-ai.json", "sam-ai.json"], "two valid peers written, id sanitized/lowercased");
|
|
198
|
-
const robin = JSON.parse(readFileSync(join(peersDir, "robin-ai.json"), "utf8"));
|
|
199
|
-
assert.equal(robin.name, "Robin");
|
|
200
|
-
assert.equal(robin.source, "synced");
|
|
201
|
-
handle.stop();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("org unreachable: presence beat that rejects never throws (fail-open)", async () => {
|
|
205
|
-
const root = tmpRepo();
|
|
206
|
-
const client = fakeClient();
|
|
207
|
-
client.presenceBeat = () => Promise.reject(new Error("ECONNREFUSED"));
|
|
208
|
-
|
|
209
|
-
const handle = await connectOrgMesh({
|
|
210
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
211
|
-
setInterval: () => ({ unref() {} }),
|
|
212
|
-
});
|
|
213
|
-
let directives;
|
|
214
|
-
await assert.doesNotReject(async () => { directives = await _internals.beatOnce(handle._ctx); });
|
|
215
|
-
assert.deepEqual(directives, [], "no directives acted on when the org is unreachable");
|
|
216
|
-
handle.stop();
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("a REJECTED beat (error frame, not a throw) is loudly warned, not silently ignored", async () => {
|
|
220
|
-
const root = tmpRepo();
|
|
221
|
-
// A refused beat resolves with an error FRAME — it does not throw — so the
|
|
222
|
-
// fail-open catch above never sees it. Before this was handled, an agent with
|
|
223
|
-
// a bad/empty token 401'd on every beat for the life of the process while
|
|
224
|
-
// logging nothing: it was not in the mesh, could not receive `halt`, and
|
|
225
|
-
// looked perfectly healthy.
|
|
226
|
-
const client = fakeClient({
|
|
227
|
-
beatFrames: [{ ok: false, error: { code: "UNAUTHORIZED", message: "bad key" } }],
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const warnings = [];
|
|
231
|
-
const realWarn = console.warn;
|
|
232
|
-
console.warn = (...a) => { warnings.push(a.join(" ")); };
|
|
233
|
-
let directives;
|
|
234
|
-
try {
|
|
235
|
-
const handle = await connectOrgMesh({
|
|
236
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
237
|
-
setInterval: () => ({ unref() {} }),
|
|
238
|
-
});
|
|
239
|
-
directives = await _internals.beatOnce(handle._ctx);
|
|
240
|
-
handle.stop();
|
|
241
|
-
} finally {
|
|
242
|
-
console.warn = realWarn;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
assert.deepEqual(directives, [], "a rejected beat yields no directives");
|
|
246
|
-
const warned = warnings.join("\n");
|
|
247
|
-
assert.match(warned, /REJECTED/, "the rejection must be logged, not swallowed");
|
|
248
|
-
assert.match(warned, /UNAUTHORIZED/, "the error code must reach the operator");
|
|
249
|
-
assert.match(warned, /COHORT_API_KEY/, "an auth failure must name the likely cause");
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("an accepted beat logs no rejection warning", async () => {
|
|
253
|
-
const root = tmpRepo();
|
|
254
|
-
const client = fakeClient({ beatFrames: [{ ok: true, result: { directives: [] } }] });
|
|
255
|
-
const warnings = [];
|
|
256
|
-
const realWarn = console.warn;
|
|
257
|
-
console.warn = (...a) => { warnings.push(a.join(" ")); };
|
|
258
|
-
try {
|
|
259
|
-
const handle = await connectOrgMesh({
|
|
260
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
261
|
-
setInterval: () => ({ unref() {} }),
|
|
262
|
-
});
|
|
263
|
-
await _internals.beatOnce(handle._ctx);
|
|
264
|
-
handle.stop();
|
|
265
|
-
} finally {
|
|
266
|
-
console.warn = realWarn;
|
|
267
|
-
}
|
|
268
|
-
assert.ok(
|
|
269
|
-
!warnings.join("\n").includes("REJECTED"),
|
|
270
|
-
"a healthy beat must not cry wolf",
|
|
271
|
-
);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test("registration failure at boot is swallowed; the mesh still comes up", async () => {
|
|
275
|
-
const root = tmpRepo();
|
|
276
|
-
const client = fakeClient();
|
|
277
|
-
client.register = () => Promise.reject(new Error("boom"));
|
|
278
|
-
const handle = await connectOrgMesh({
|
|
279
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
280
|
-
setInterval: () => ({ unref() {} }),
|
|
281
|
-
});
|
|
282
|
-
assert.equal(handle.isEnabled, true, "mesh handle is live despite a failed registration");
|
|
283
|
-
handle.stop();
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("stop() clears the interval and is idempotent", async () => {
|
|
287
|
-
const root = tmpRepo();
|
|
288
|
-
const client = fakeClient();
|
|
289
|
-
const cleared = [];
|
|
290
|
-
const handle = await connectOrgMesh({
|
|
291
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
292
|
-
setInterval: () => ({ unref() {}, _id: 42 }),
|
|
293
|
-
clearInterval: (t) => cleared.push(t),
|
|
294
|
-
});
|
|
295
|
-
handle.stop();
|
|
296
|
-
handle.stop(); // idempotent — no second clear
|
|
297
|
-
assert.equal(cleared.length, 1, "clearInterval called exactly once");
|
|
298
|
-
assert.equal(cleared[0]._id, 42);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test("extractDirectives: filters to the known set + de-dupes", () => {
|
|
302
|
-
assert.deepEqual(
|
|
303
|
-
_internals.extractDirectives({ ok: true, result: { directives: ["resync", "resync", "bogus", "halt"] } }).sort(),
|
|
304
|
-
["halt", "resync"],
|
|
305
|
-
);
|
|
306
|
-
assert.deepEqual(_internals.extractDirectives({ ok: false, error: { directive: "rotate_token" } }), ["rotate_token"]);
|
|
307
|
-
assert.deepEqual(_internals.extractDirectives({ ok: false, error: { directive: "nope" } }), []);
|
|
308
|
-
assert.deepEqual(_internals.extractDirectives(null), []);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test('"rotate_token" directive drops a re-pair flag', async () => {
|
|
312
|
-
const root = tmpRepo();
|
|
313
|
-
const client = fakeClient({ beatFrames: [{ ok: true, result: { directives: ["rotate_token"] } }] });
|
|
314
|
-
const handle = await connectOrgMesh({
|
|
315
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
316
|
-
setInterval: () => ({ unref() {} }),
|
|
317
|
-
});
|
|
318
|
-
await _internals.beatOnce(handle._ctx);
|
|
319
|
-
const flagPath = join(root, "state", "org", "repair-needed.json");
|
|
320
|
-
assert.ok(existsSync(flagPath), "repair-needed flag written");
|
|
321
|
-
const flag = JSON.parse(readFileSync(flagPath, "utf8"));
|
|
322
|
-
assert.equal(flag.reason, "rotate_token");
|
|
323
|
-
handle.stop();
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("presence beat attaches params.status from the injected collector", async () => {
|
|
327
|
-
const root = tmpRepo();
|
|
328
|
-
const client = fakeClient();
|
|
329
|
-
const fakeStatus = {
|
|
330
|
-
state: "busy", activity: "session: inbox", subAgentsRunning: 1, session: { id: "s1", taskClass: "inbox" },
|
|
331
|
-
machine: { loadavg: [1, 1, 1], memUsedPct: 40 }, claudeAuth: "ok", alerts: [], ts: "2026-06-12T12:00:00Z",
|
|
332
|
-
};
|
|
333
|
-
const handle = await connectOrgMesh({
|
|
334
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
335
|
-
setInterval: () => ({ unref() {} }),
|
|
336
|
-
collectStatus: async () => fakeStatus,
|
|
337
|
-
});
|
|
338
|
-
await _internals.beatOnce(handle._ctx);
|
|
339
|
-
assert.equal(client.beatCalls.length, 1);
|
|
340
|
-
assert.deepEqual(client.beatCalls[0].params.status, fakeStatus, "snapshot rides params.status");
|
|
341
|
-
handle.stop();
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
test("presence beat omits params.status when the collector throws (fail-open)", async () => {
|
|
345
|
-
const root = tmpRepo();
|
|
346
|
-
const client = fakeClient();
|
|
347
|
-
const handle = await connectOrgMesh({
|
|
348
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
349
|
-
setInterval: () => ({ unref() {} }),
|
|
350
|
-
collectStatus: async () => { throw new Error("collector boom"); },
|
|
351
|
-
});
|
|
352
|
-
let directives;
|
|
353
|
-
await assert.doesNotReject(async () => { directives = await _internals.beatOnce(handle._ctx); });
|
|
354
|
-
assert.deepEqual(directives, []);
|
|
355
|
-
assert.equal(client.beatCalls.length, 1, "beat still went out");
|
|
356
|
-
assert.equal("status" in client.beatCalls[0].params, false, "no status key when collection failed");
|
|
357
|
-
handle.stop();
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
test("a critical alert in the snapshot escalates locally (alert-critical flag)", async () => {
|
|
361
|
-
const root = tmpRepo();
|
|
362
|
-
const client = fakeClient();
|
|
363
|
-
const crit = {
|
|
364
|
-
state: "error", activity: "idle", subAgentsRunning: 0, session: null,
|
|
365
|
-
machine: { loadavg: [0, 0, 0], memUsedPct: 10 }, claudeAuth: "relogin_required",
|
|
366
|
-
alerts: [{ id: "claude_relogin_required", severity: "critical", kind: "auth", detail: "claude re-login required" }],
|
|
367
|
-
ts: "2026-06-12T12:00:00Z",
|
|
368
|
-
};
|
|
369
|
-
const handle = await connectOrgMesh({
|
|
370
|
-
agentRoot: root, cfg: cfgEnabled(), client,
|
|
371
|
-
setInterval: () => ({ unref() {} }),
|
|
372
|
-
collectStatus: async () => crit,
|
|
373
|
-
});
|
|
374
|
-
await _internals.beatOnce(handle._ctx);
|
|
375
|
-
const flagPath = join(root, "state", "org", "alert-critical.json");
|
|
376
|
-
assert.ok(existsSync(flagPath), "critical alert flag was written");
|
|
377
|
-
const flag = JSON.parse(readFileSync(flagPath, "utf8"));
|
|
378
|
-
assert.equal(flag.alerts[0].id, "claude_relogin_required");
|
|
379
|
-
// status still rides the beat too
|
|
380
|
-
assert.deepEqual(client.beatCalls[0].params.status, crit);
|
|
381
|
-
handle.stop();
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
test("loadOrgConfig reads config/org.yaml (the file the daemon + enrollment share) — config-source regression", async () => {
|
|
385
|
-
const { loadOrgConfig, isEnabled } = await import("./client.mjs");
|
|
386
|
-
const root = mkdtempSync(join(tmpdir(), "mesh-cfg-"));
|
|
387
|
-
try {
|
|
388
|
-
// No file → disabled (standalone), never throws.
|
|
389
|
-
assert.deepEqual(loadOrgConfig(root), {});
|
|
390
|
-
assert.equal(isEnabled(loadOrgConfig(root)), false);
|
|
391
|
-
// Enrolled config/org.yaml (exactly what sections/org.mjs writes) → enabled,
|
|
392
|
-
// and the daemon must resolve org config from HERE, not config/collective.yaml.
|
|
393
|
-
mkdirSync(join(root, "config"), { recursive: true });
|
|
394
|
-
writeFileSync(join(root, "config", "org.yaml"),
|
|
395
|
-
"org:\n cohort:\n enabled: true\n base: https://cohort.example\n orgId: o1\n token: t1\n");
|
|
396
|
-
const cfg = loadOrgConfig(root);
|
|
397
|
-
assert.equal(isEnabled(cfg), true, "an enrolled agent is enabled via config/org.yaml");
|
|
398
|
-
assert.equal(cfg.org.cohort.base, "https://cohort.example");
|
|
399
|
-
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
400
|
-
});
|