@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
package/lib/cadences.test.mjs
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cadences.test.mjs — tests for the archetype cadence resolver.
|
|
3
|
-
* Run: `node --test lib/cadences.test.mjs`
|
|
4
|
-
* @module cadences.test
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
"use strict";
|
|
8
|
-
|
|
9
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { resolveCadences, scheduleFor, STANDARD_CADENCES, RECURRING, isQuarterStartMonth, isCadenceDue } from "./cadences.mjs";
|
|
12
|
-
|
|
13
|
-
function hasSchedule(c) {
|
|
14
|
-
return typeof c.interval === "number" || (c.calendar && typeof c.calendar === "object");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
test("resolveCadences = standard ⊕ function recurring workflows ⊕ altitude cadences", async () => {
|
|
18
|
-
const cads = await resolveCadences({ function: "technical-leader", altitude: "c-suite" });
|
|
19
|
-
const ids = cads.map((c) => c.id);
|
|
20
|
-
// standard present
|
|
21
|
-
assert.ok(ids.includes("inbox-processor") && ids.includes("daily-morning-brief"));
|
|
22
|
-
// a function recurring cadence (from the technical-leader capability workflows)
|
|
23
|
-
assert.ok(ids.includes("engineering-health"), `expected a function cadence; got ${ids.join(",")}`);
|
|
24
|
-
// altitude cadence (c-suite → strategic memo)
|
|
25
|
-
assert.ok(ids.includes("weekly-strategic-memo"));
|
|
26
|
-
// every entry has a concrete schedule + unique ids
|
|
27
|
-
assert.ok(cads.every(hasSchedule), "every cadence has interval or calendar");
|
|
28
|
-
assert.equal(ids.length, new Set(ids).size, "no duplicate cadence ids");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("event-driven workflows are excluded from cadences (pr-review is not scheduled)", async () => {
|
|
32
|
-
const cads = await resolveCadences({ function: "technical-leader", altitude: "vp" });
|
|
33
|
-
assert.ok(!cads.some((c) => c.id === "pr-review"), "event-driven pr-review must not be a scheduled cadence");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("altitude changes the cadence set (c-suite memo vs senior-manager execution review)", async () => {
|
|
37
|
-
const cSuite = (await resolveCadences({ function: "operations-leader", altitude: "c-suite" })).map((c) => c.id);
|
|
38
|
-
const srMgr = (await resolveCadences({ function: "operations-leader", altitude: "senior-manager" })).map((c) => c.id);
|
|
39
|
-
assert.ok(cSuite.includes("weekly-strategic-memo"));
|
|
40
|
-
assert.ok(srMgr.includes("weekly-execution"));
|
|
41
|
-
assert.notDeepEqual(cSuite, srMgr);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("scheduleFor is deterministic and matches the cadence keyword", () => {
|
|
45
|
-
assert.deepEqual(scheduleFor("weekly", "x"), scheduleFor("weekly", "x"));
|
|
46
|
-
assert.ok(scheduleFor("continuous", "x").interval);
|
|
47
|
-
assert.ok(scheduleFor("weekly", "engineering-health").calendar.weekday >= 1);
|
|
48
|
-
assert.ok(RECURRING.has("weekly") && !RECURRING.has("event-driven"));
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("resolveCadences requires { function, altitude }", async () => {
|
|
52
|
-
await assert.rejects(() => resolveCadences({}), /requires/);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("quarter guard: quarterly cadence is due in Jan/Apr/Jul/Oct, not other months", () => {
|
|
56
|
-
// The plist fires Day=1 of every month; the guard restricts it to the
|
|
57
|
-
// quarter-start months so the principal gets 4, not 12, reports/year.
|
|
58
|
-
const due = [1, 4, 7, 10];
|
|
59
|
-
for (let month = 1; month <= 12; month++) {
|
|
60
|
-
// Day 1 of each month, midday local to dodge any TZ/DST edge.
|
|
61
|
-
const d = new Date(2026, month - 1, 1, 12, 0, 0);
|
|
62
|
-
const expected = due.includes(month);
|
|
63
|
-
assert.equal(isQuarterStartMonth(d), expected, `month ${month}: isQuarterStartMonth`);
|
|
64
|
-
assert.equal(isCadenceDue("quarterly-self-assessment", d), expected, `month ${month}: quarterly due`);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("quarter guard does not constrain non-quarterly cadences", () => {
|
|
69
|
-
// A daily cadence is due every month — the guard is quarterly-only.
|
|
70
|
-
for (let month = 1; month <= 12; month++) {
|
|
71
|
-
const d = new Date(2026, month - 1, 1, 12, 0, 0);
|
|
72
|
-
assert.equal(isCadenceDue("daily-morning-brief", d), true, `month ${month}: daily always due`);
|
|
73
|
-
assert.equal(isCadenceDue("org-pulse", d), true, `month ${month}: org-pulse always due`);
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("STANDARD_CADENCES all carry a schedule; escalating ones carry a prompt", () => {
|
|
78
|
-
assert.ok(STANDARD_CADENCES.length >= 8);
|
|
79
|
-
// Every standard cadence is scheduled.
|
|
80
|
-
assert.ok(STANDARD_CADENCES.every((c) => hasSchedule(c)));
|
|
81
|
-
// Cadences that spawn a sub-session must carry a prompt; a guarded-inline
|
|
82
|
-
// cadence (e.g. skill-curator) legitimately has none — the daemon handles it
|
|
83
|
-
// in-process and never escalates.
|
|
84
|
-
assert.ok(STANDARD_CADENCES.filter((c) => c.mode === "escalate").every((c) => c.prompt));
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("economics/scheduling cadences are wired into STANDARD_CADENCES with the right mode + schedule", () => {
|
|
88
|
-
const byId = Object.fromEntries(STANDARD_CADENCES.map((c) => [c.id, c]));
|
|
89
|
-
// Deterministic economics cadences run inline (no LLM).
|
|
90
|
-
for (const id of ["nightly-cost-reconcile", "fleet-cost-digest", "org-cost-sync"]) {
|
|
91
|
-
assert.ok(byId[id], `${id} present in STANDARD_CADENCES`);
|
|
92
|
-
assert.equal(byId[id].mode, "inline", `${id} is inline (deterministic)`);
|
|
93
|
-
assert.ok(hasSchedule(byId[id]), `${id} has a schedule`);
|
|
94
|
-
}
|
|
95
|
-
// Calendar-pinned: nightly-cost-reconcile ~3:30am, fleet-cost-digest ~7am.
|
|
96
|
-
assert.deepEqual(byId["nightly-cost-reconcile"].calendar, { hour: 3, minute: 30 });
|
|
97
|
-
assert.deepEqual(byId["fleet-cost-digest"].calendar, { hour: 7, minute: 0 });
|
|
98
|
-
// org-cost-sync is an hourly interval.
|
|
99
|
-
assert.equal(byId["org-cost-sync"].interval, 3600);
|
|
100
|
-
// dynamic-jobs is the only GUARDED economics cadence (escalate-when-due) and a
|
|
101
|
-
// ~60s interval; because it can escalate it must carry a prompt.
|
|
102
|
-
assert.ok(byId["dynamic-jobs"], "dynamic-jobs present");
|
|
103
|
-
assert.equal(byId["dynamic-jobs"].mode, "guarded");
|
|
104
|
-
assert.equal(byId["dynamic-jobs"].interval, 60);
|
|
105
|
-
assert.ok(byId["dynamic-jobs"].prompt, "guarded dynamic-jobs carries an escalate prompt");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("economics/scheduling cadences resolve into every agent's cadence set", async () => {
|
|
109
|
-
const ids = (await resolveCadences({ function: "operations-leader", altitude: "vp" })).map((c) => c.id);
|
|
110
|
-
for (const id of ["nightly-cost-reconcile", "fleet-cost-digest", "org-cost-sync", "dynamic-jobs"]) {
|
|
111
|
-
assert.ok(ids.includes(id), `${id} resolves into the standard cadence set`);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("every STANDARD_CADENCES entry that declares a prompt has the file on disk", async () => {
|
|
116
|
-
// A guarded/escalate cadence whose prompt file is missing would DLQ at escalate
|
|
117
|
-
// time (regression guard for the dynamic-jobs.md gap).
|
|
118
|
-
const { existsSync } = await import("node:fs");
|
|
119
|
-
const { resolve, join } = await import("node:path");
|
|
120
|
-
const { fileURLToPath } = await import("node:url");
|
|
121
|
-
const root = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
122
|
-
for (const c of STANDARD_CADENCES.filter((x) => x.prompt)) {
|
|
123
|
-
assert.ok(existsSync(join(root, c.prompt)), `cadence ${c.id} prompt missing: ${c.prompt}`);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
// The PLAN leg (SPEC §4/§5.6): mandate-derived obligations graft onto the
|
|
129
|
-
// standard + archetype floor. This is what makes the schedule per-agent and
|
|
130
|
-
// justified rather than a static default list.
|
|
131
|
-
// ---------------------------------------------------------------------------
|
|
132
|
-
|
|
133
|
-
/** A compiled SCHEDULE obligation, shaped as lib/plan/compile.mjs emits it. */
|
|
134
|
-
function measureObligation(over = {}) {
|
|
135
|
-
return {
|
|
136
|
-
key: "schedule.measure.pipeline-coverage",
|
|
137
|
-
kind: "SCHEDULE",
|
|
138
|
-
cadence_id: "measure-pipeline-coverage",
|
|
139
|
-
scope: "mandate",
|
|
140
|
-
mode: "guarded",
|
|
141
|
-
prompt: "schedules/triggers/measure-pipeline-coverage.md",
|
|
142
|
-
schedule: { calendar: { weekday: 2, hour: 14, minute: 30 } },
|
|
143
|
-
cadence: "weekly",
|
|
144
|
-
objective_id: "obj_7Kx",
|
|
145
|
-
status: "active",
|
|
146
|
-
...over,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
test("plan leg: an adopted objective's measure obligation becomes a real cadence", async () => {
|
|
151
|
-
const cads = await resolveCadences({
|
|
152
|
-
function: "commercial-leader",
|
|
153
|
-
altitude: "vp",
|
|
154
|
-
obligations: [measureObligation()],
|
|
155
|
-
});
|
|
156
|
-
const c = cads.find((x) => x.id === "measure-pipeline-coverage");
|
|
157
|
-
assert.ok(c, "mandate-derived cadence resolves");
|
|
158
|
-
assert.equal(c.scope, "mandate");
|
|
159
|
-
assert.equal(c.mode, "guarded");
|
|
160
|
-
// The compiler solved this slot against the principal's calendar — the
|
|
161
|
-
// resolver's own scheduleFor() stagger must NOT overwrite it.
|
|
162
|
-
assert.deepEqual(c.calendar, { weekday: 2, hour: 14, minute: 30 });
|
|
163
|
-
// Provenance rides along so a tick can be walked back to its objective.
|
|
164
|
-
assert.equal(c.obligationKey, "schedule.measure.pipeline-coverage");
|
|
165
|
-
assert.equal(c.objectiveId, "obj_7Kx");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("plan leg: the standard + archetype floor is RETAINED, never replaced", async () => {
|
|
169
|
-
// An agent whose human has adopted nothing must keep running today's cadences.
|
|
170
|
-
const withPlan = await resolveCadences({ function: "commercial-leader", altitude: "vp", obligations: [measureObligation()] });
|
|
171
|
-
const withoutPlan = await resolveCadences({ function: "commercial-leader", altitude: "vp" });
|
|
172
|
-
const baseIds = withoutPlan.map((c) => c.id);
|
|
173
|
-
const planIds = withPlan.map((c) => c.id);
|
|
174
|
-
for (const id of baseIds) assert.ok(planIds.includes(id), `${id} survives the plan leg`);
|
|
175
|
-
assert.equal(planIds.length, baseIds.length + 1, "the plan leg only ADDS");
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("plan leg: an empty/absent obligation list is exactly today's behaviour", async () => {
|
|
179
|
-
const none = await resolveCadences({ function: "technical-leader", altitude: "c-suite" });
|
|
180
|
-
const empty = await resolveCadences({ function: "technical-leader", altitude: "c-suite", obligations: [] });
|
|
181
|
-
assert.deepEqual(empty, none);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("plan leg: a plan obligation cannot re-slot or override a standard cadence", async () => {
|
|
185
|
-
// First writer wins. A mandate obligation colliding with `inbox-processor`
|
|
186
|
-
// must not move it — that is what keeps existing launchd plists from churning.
|
|
187
|
-
const base = await resolveCadences({ function: "technical-leader", altitude: "c-suite" });
|
|
188
|
-
const original = base.find((c) => c.id === "inbox-processor");
|
|
189
|
-
const cads = await resolveCadences({
|
|
190
|
-
function: "technical-leader",
|
|
191
|
-
altitude: "c-suite",
|
|
192
|
-
obligations: [measureObligation({ cadence_id: "inbox-processor", schedule: { interval: 99999 }, key: "schedule.hijack" })],
|
|
193
|
-
});
|
|
194
|
-
const after = cads.find((c) => c.id === "inbox-processor");
|
|
195
|
-
assert.deepEqual(after, original, "the standard cadence is untouched");
|
|
196
|
-
assert.equal(cads.filter((c) => c.id === "inbox-processor").length, 1, "no duplicate entry");
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("plan leg: non-active / non-SCHEDULE obligations emit no cadence", async () => {
|
|
200
|
-
const cads = await resolveCadences({
|
|
201
|
-
function: "technical-leader",
|
|
202
|
-
altitude: "c-suite",
|
|
203
|
-
obligations: [
|
|
204
|
-
measureObligation({ cadence_id: "suspended-job", status: "suspended", key: "schedule.suspended" }),
|
|
205
|
-
{ key: "outcome.pipeline-coverage", kind: "OUTCOME", cadence_id: "outcome-job", status: "active" },
|
|
206
|
-
{ key: "react.email.received", kind: "REACT", status: "active", trigger: { topic: "email" } },
|
|
207
|
-
],
|
|
208
|
-
});
|
|
209
|
-
const ids = cads.map((c) => c.id);
|
|
210
|
-
assert.ok(!ids.includes("suspended-job"), "a suspended obligation is not scheduled");
|
|
211
|
-
assert.ok(!ids.includes("outcome-job"), "an OUTCOME is measured, not launchd-scheduled");
|
|
212
|
-
assert.ok(!ids.includes("react.email.received"), "a REACT is event-driven, not scheduled");
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("plan leg: an interval obligation carries its interval through", async () => {
|
|
216
|
-
const cads = await resolveCadences({
|
|
217
|
-
function: "technical-leader",
|
|
218
|
-
altitude: "c-suite",
|
|
219
|
-
obligations: [measureObligation({ cadence_id: "fast-sensor", schedule: { interval: 300 }, key: "schedule.fast" })],
|
|
220
|
-
});
|
|
221
|
-
const c = cads.find((x) => x.id === "fast-sensor");
|
|
222
|
-
assert.equal(c.interval, 300);
|
|
223
|
-
assert.equal(c.calendar, undefined);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
test("plan leg: founder altitude resolves its own cadences (was unreachable from the wizard)", async () => {
|
|
227
|
-
const ids = (await resolveCadences({ function: "finance-leader", altitude: "founder" })).map((c) => c.id);
|
|
228
|
-
assert.ok(ids.includes("weekly-runway-review"), "founder gets the runway review");
|
|
229
|
-
assert.ok(ids.includes("monthly-investor-update"), "founder gets the investor update");
|
|
230
|
-
});
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* inventory.test.mjs — the capability inventory (SPEC §5.1).
|
|
3
|
-
* Run: node --test lib/capability/inventory.test.mjs
|
|
4
|
-
*
|
|
5
|
-
* Everything external is injected: a fake HOME for ~/.claude/settings.json, a
|
|
6
|
-
* fake org tool table, a fake org probe, a fake capability surface, a fake
|
|
7
|
-
* clock. No network, no real settings file, no real MCP spawn.
|
|
8
|
-
*/
|
|
9
|
-
"use strict";
|
|
10
|
-
|
|
11
|
-
import { test } from "node:test";
|
|
12
|
-
import assert from "node:assert/strict";
|
|
13
|
-
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
14
|
-
import { tmpdir } from "node:os";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
buildInventory, writeManifest, readManifest, reachableIds, findCapability, byPlane,
|
|
19
|
-
checksumEntries, blastRadiusOfOrgTool, enumerateSkills, enumerateMcp, MANIFEST_REL,
|
|
20
|
-
} from "./inventory.mjs";
|
|
21
|
-
import { probeSkillPlugin, probeMcpServer, probeOrgPlane, probeGrantedPluginTools } from "./probe.mjs";
|
|
22
|
-
|
|
23
|
-
const AT = "2026-08-11T09:00:00.000Z";
|
|
24
|
-
|
|
25
|
-
/** A tmp agent repo + a tmp HOME, so nothing touches the real machine. */
|
|
26
|
-
function fixture({ withMarketplace = false, withEnabled = false, mcp = { cohort: { command: "cohort-mcp" } }, skills = ["morning-brief", "inbox-triage"] } = {}) {
|
|
27
|
-
const root = mkdtempSync(join(tmpdir(), "maestro-inv-"));
|
|
28
|
-
const home = mkdtempSync(join(tmpdir(), "maestro-home-"));
|
|
29
|
-
mkdirSync(join(root, "config"), { recursive: true });
|
|
30
|
-
|
|
31
|
-
if (skills) {
|
|
32
|
-
mkdirSync(join(root, "plugins", "agent-skills", "skills"), { recursive: true });
|
|
33
|
-
for (const s of skills) writeFileSync(join(root, "plugins", "agent-skills", "skills", `${s}.md`), `# ${s}\n`);
|
|
34
|
-
}
|
|
35
|
-
if (withMarketplace) {
|
|
36
|
-
mkdirSync(join(root, "plugins", "agent-skills", ".claude-plugin"), { recursive: true });
|
|
37
|
-
writeFileSync(join(root, "plugins", "agent-skills", ".claude-plugin", "marketplace.json"), JSON.stringify({ name: "agent-skills", plugins: [] }));
|
|
38
|
-
}
|
|
39
|
-
if (withEnabled) {
|
|
40
|
-
mkdirSync(join(home, ".claude"), { recursive: true });
|
|
41
|
-
writeFileSync(join(home, ".claude", "settings.json"), JSON.stringify({ enabledPlugins: { "agent-skills@agent-skills": true } }));
|
|
42
|
-
}
|
|
43
|
-
if (mcp) writeFileSync(join(root, ".mcp.json"), JSON.stringify({ mcpServers: mcp }));
|
|
44
|
-
return { root, home };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const FAKE_ORG_TOOLS = [
|
|
48
|
-
{ name: "board_ready", access: "read", binding: { kind: "rpc", method: "board.ready" } },
|
|
49
|
-
{ name: "task_create", access: "write", binding: { kind: "rpc", method: "task.create" } },
|
|
50
|
-
{ name: "messaging_send", access: "write", outbound: true, binding: { kind: "rpc", method: "messaging.send" } },
|
|
51
|
-
{ name: "books_invoice_send", access: "write", binding: { kind: "rpc", method: "books.invoiceSend" } },
|
|
52
|
-
{ name: "linear_create_issue", access: "write", integration: "linear", binding: { kind: "rpc" } },
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
function deps({ home, orgOk = true, mandateFamily = true, logs = [] }) {
|
|
56
|
-
return {
|
|
57
|
-
homeDir: home,
|
|
58
|
-
now: () => AT,
|
|
59
|
-
logImpl: (m) => logs.push(m),
|
|
60
|
-
orgToolsImpl: async () => FAKE_ORG_TOOLS,
|
|
61
|
-
orgProbeImpl: async () => (orgOk
|
|
62
|
-
? { ok: true, detail: "mandate.version round-trip ok", mandateFamily }
|
|
63
|
-
: { ok: false, detail: "org.cohort is not enabled", degraded: "org-not-enrolled", mandateFamily: false }),
|
|
64
|
-
capabilitySurface: async () => ({ workflows: [{ id: "weekly-operating-review", cadence: "weekly", purpose: "run the operating cadence" }] }),
|
|
65
|
-
wishlistImpl: async () => ([
|
|
66
|
-
{ id: "cohort", env: [], optional: false, purpose: "org spine" },
|
|
67
|
-
{ id: "notion", env: ["NOTION_API_KEY"], optional: true, purpose: "decision log" },
|
|
68
|
-
]),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
test("buildInventory: enumerates all four planes and marks unregistered skills UNREACHABLE", async () => {
|
|
73
|
-
const { root, home } = fixture();
|
|
74
|
-
const logs = [];
|
|
75
|
-
const m = await buildInventory({ agentRoot: root, archetype: { function: "executive-operator", altitude: "c-suite" } }, deps({ home, logs }));
|
|
76
|
-
|
|
77
|
-
assert.equal(m.schemaVersion, 1);
|
|
78
|
-
assert.equal(m.generatedAt, AT);
|
|
79
|
-
assert.ok(m.counts.builtin > 5, "builtins inventoried");
|
|
80
|
-
assert.ok(m.counts.skill >= 3, "2 skill files + 1 workflow");
|
|
81
|
-
assert.ok(m.counts.mcp >= 2, "cohort (declared) + notion (wished-for)");
|
|
82
|
-
assert.equal(m.counts.org, FAKE_ORG_TOOLS.length);
|
|
83
|
-
|
|
84
|
-
// The whole point: skills whose marketplace is unregistered are NOT reachable.
|
|
85
|
-
const skill = findCapability(m, "skill:agent-skills:morning-brief");
|
|
86
|
-
assert.ok(skill, "skill discovered");
|
|
87
|
-
assert.equal(skill.reachable, false);
|
|
88
|
-
assert.match(skill.probe.detail, /marketplace/);
|
|
89
|
-
|
|
90
|
-
// ...and it is LOGGED, not swallowed.
|
|
91
|
-
assert.ok(logs.some((l) => /DEGRADED skill plugin:agent-skills/.test(l)), `expected a logged degradation, got: ${logs.join(" | ")}`);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("buildInventory: registered + enabled marketplace makes skills reachable", async () => {
|
|
95
|
-
const { root, home } = fixture({ withMarketplace: true, withEnabled: true });
|
|
96
|
-
const m = await buildInventory({ agentRoot: root, archetype: { function: "executive-operator", altitude: "c-suite" } }, deps({ home }));
|
|
97
|
-
const skill = findCapability(m, "skill:agent-skills:inbox-triage");
|
|
98
|
-
assert.equal(skill.reachable, true);
|
|
99
|
-
assert.ok(reachableIds(m).has("skill:agent-skills:inbox-triage"));
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("repair:true registers the marketplace and enables the plugin (rung 1 stops being a lie)", async () => {
|
|
103
|
-
const { root, home } = fixture();
|
|
104
|
-
const logs = [];
|
|
105
|
-
const m = await buildInventory({ agentRoot: root, repair: true, archetype: { function: "executive-operator", altitude: "c-suite" } }, deps({ home, logs }));
|
|
106
|
-
assert.ok(existsSync(join(root, "plugins", "agent-skills", ".claude-plugin", "marketplace.json")), "marketplace written");
|
|
107
|
-
const settings = JSON.parse(readFileSync(join(home, ".claude", "settings.json"), "utf8"));
|
|
108
|
-
assert.ok(Object.keys(settings.enabledPlugins).some((k) => k.startsWith("agent-skills")), "plugin enabled");
|
|
109
|
-
assert.equal(findCapability(m, "skill:agent-skills:morning-brief").reachable, true);
|
|
110
|
-
assert.ok(m.repairs.length >= 2 && logs.some((l) => /repaired/.test(l)));
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("mcp plane: wished-for server absent from .mcp.json is unreachable with a stated reason", async () => {
|
|
114
|
-
const { root, home } = fixture();
|
|
115
|
-
const m = await buildInventory({ agentRoot: root, archetype: { function: "executive-operator", altitude: "c-suite" } }, deps({ home }));
|
|
116
|
-
const notion = findCapability(m, "mcp:notion");
|
|
117
|
-
assert.equal(notion.reachable, false);
|
|
118
|
-
assert.match(notion.probe.detail, /absent from \.mcp\.json|credential env/);
|
|
119
|
-
const cohort = findCapability(m, "mcp:cohort");
|
|
120
|
-
assert.equal(cohort.reachable, true);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("org plane: an un-enrolled agent inventories the tools but NONE are reachable", async () => {
|
|
124
|
-
const { root, home } = fixture();
|
|
125
|
-
const logs = [];
|
|
126
|
-
const m = await buildInventory({ agentRoot: root, archetype: { function: "executive-operator", altitude: "c-suite" } }, deps({ home, orgOk: false, logs }));
|
|
127
|
-
const org = byPlane(m, "org");
|
|
128
|
-
assert.equal(org.length, FAKE_ORG_TOOLS.length);
|
|
129
|
-
assert.equal(org.filter((e) => e.reachable).length, 0);
|
|
130
|
-
assert.equal(m.mandateFamily, false);
|
|
131
|
-
assert.ok(logs.some((l) => /org-not-enrolled/.test(l)));
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("blast radius: outbound → external, financial verbs → financial, reads → internal", () => {
|
|
135
|
-
assert.equal(blastRadiusOfOrgTool({ name: "messaging_send", access: "write", outbound: true }), "external");
|
|
136
|
-
assert.equal(blastRadiusOfOrgTool({ name: "books_invoice_send", access: "write" }), "financial");
|
|
137
|
-
assert.equal(blastRadiusOfOrgTool({ name: "board_ready", access: "read" }), "internal");
|
|
138
|
-
assert.equal(blastRadiusOfOrgTool({ name: "task_create", access: "write" }), "irreversible");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("determinism: checksum is stable across runs and ignores probedAt", async () => {
|
|
142
|
-
const { root, home } = fixture({ withMarketplace: true, withEnabled: true });
|
|
143
|
-
const a = await buildInventory({ agentRoot: root, archetype: { function: "executive-operator", altitude: "c-suite" } }, deps({ home }));
|
|
144
|
-
const b = await buildInventory({ agentRoot: root, archetype: { function: "executive-operator", altitude: "c-suite" } }, { ...deps({ home }), now: () => "2027-01-01T00:00:00.000Z" });
|
|
145
|
-
assert.equal(a.checksum, b.checksum, "same capabilities → same checksum despite a different clock");
|
|
146
|
-
assert.notEqual(a.generatedAt, b.generatedAt);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("checksum CHANGES when a capability's reachability flips", () => {
|
|
150
|
-
const base = [{ id: "x", plane: "org", kind: "method_tool", reachable: true }];
|
|
151
|
-
const flipped = [{ id: "x", plane: "org", kind: "method_tool", reachable: false }];
|
|
152
|
-
assert.notEqual(checksumEntries(base), checksumEntries(flipped));
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("write/read round-trip is atomic and queryable", async () => {
|
|
156
|
-
const { root, home } = fixture();
|
|
157
|
-
const m = await buildInventory({ agentRoot: root, archetype: { function: "executive-operator", altitude: "c-suite" } }, deps({ home }));
|
|
158
|
-
const p = writeManifest(root, m);
|
|
159
|
-
assert.equal(p, join(root, MANIFEST_REL));
|
|
160
|
-
const back = readManifest(root);
|
|
161
|
-
assert.equal(back.checksum, m.checksum);
|
|
162
|
-
assert.equal(byPlane(back, "builtin").length, byPlane(m, "builtin").length);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("degradation is recorded when no archetype is supplied (never silent)", async () => {
|
|
166
|
-
const { root, home } = fixture();
|
|
167
|
-
const logs = [];
|
|
168
|
-
const m = await buildInventory({ agentRoot: root }, deps({ home, logs }));
|
|
169
|
-
assert.ok(m.degradations.some((d) => /archetype/.test(d.id)));
|
|
170
|
-
assert.ok(logs.some((l) => /archetype:workflows/.test(l)));
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// ── probe-level units ────────────────────────────────────────────────────────
|
|
174
|
-
|
|
175
|
-
test("probeMcpServer: missing credential env fails closed with the key named", () => {
|
|
176
|
-
const r = probeMcpServer({ name: "notion", declared: true, requiredEnv: ["NOTION_API_KEY"], env: {} });
|
|
177
|
-
assert.equal(r.ok, false);
|
|
178
|
-
assert.equal(r.degraded, "mcp-credential-missing");
|
|
179
|
-
assert.match(r.detail, /NOTION_API_KEY/);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("probeMcpServer: an injected handshake decides the verdict", () => {
|
|
183
|
-
const ok = probeMcpServer({ name: "cohort", declared: true, env: {} }, { handshake: () => ({ ok: true, detail: "hello" }) });
|
|
184
|
-
assert.equal(ok.ok, true);
|
|
185
|
-
const bad = probeMcpServer({ name: "cohort", declared: true, env: {} }, { handshake: () => { throw new Error("spawn ENOENT"); } });
|
|
186
|
-
assert.equal(bad.ok, false);
|
|
187
|
-
assert.equal(bad.degraded, "mcp-handshake-threw");
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("probeOrgPlane: NOT_FOUND on mandate.version means transport LIVE, mandate family absent", async () => {
|
|
191
|
-
const r = await probeOrgPlane({ enabled: true, call: async () => ({ ok: false, error: { code: "NOT_FOUND" } }) });
|
|
192
|
-
assert.equal(r.ok, true, "the transport answered, so the plane is reachable");
|
|
193
|
-
assert.equal(r.mandateFamily, false);
|
|
194
|
-
assert.equal(r.degraded, "mandate-family-absent");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("probeOrgPlane: falls back to a snapshot read, and a dead server is unreachable", async () => {
|
|
198
|
-
const ok = await probeOrgPlane({ enabled: true, call: async () => ({ ok: false, error: { code: "BAD_REQUEST" } }), read: async () => ({ ok: true }) });
|
|
199
|
-
assert.equal(ok.ok, true);
|
|
200
|
-
const dead = await probeOrgPlane({ enabled: true, call: async () => ({ ok: false, error: { code: "BAD_REQUEST" } }), read: async () => ({ ok: false, status: 503 }) });
|
|
201
|
-
assert.equal(dead.ok, false);
|
|
202
|
-
assert.equal(dead.degraded, "org-unreachable");
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("probeGrantedPluginTools: an absent toolset cache is reported, not swallowed", () => {
|
|
206
|
-
const { root } = fixture();
|
|
207
|
-
const r = probeGrantedPluginTools(root);
|
|
208
|
-
assert.equal(r.ok, false);
|
|
209
|
-
assert.equal(r.degraded, "toolset-absent");
|
|
210
|
-
mkdirSync(join(root, "state", "org"), { recursive: true });
|
|
211
|
-
writeFileSync(join(root, "state", "org", "toolset.json"), JSON.stringify({ version: 7, tools: [{ name: "linear_create_issue" }] }));
|
|
212
|
-
const r2 = probeGrantedPluginTools(root);
|
|
213
|
-
assert.equal(r2.ok, true);
|
|
214
|
-
assert.equal(r2.toolsetVersion, 7);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test("probeSkillPlugin: a plugin dir with no marketplace fails closed and names the path", () => {
|
|
218
|
-
const { root, home } = fixture();
|
|
219
|
-
const r = probeSkillPlugin({ pluginDir: join(root, "plugins", "agent-skills"), pluginId: "agent-skills" }, { homeDir: home });
|
|
220
|
-
assert.equal(r.ok, false);
|
|
221
|
-
assert.equal(r.degraded, "marketplace-unregistered");
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("enumerateSkills/enumerateMcp are pure over injected fs state", () => {
|
|
225
|
-
const { root, home } = fixture({ withMarketplace: true, withEnabled: true, skills: ["a", "b", "c"] });
|
|
226
|
-
const s = enumerateSkills({ agentRoot: root, at: AT }, { homeDir: home });
|
|
227
|
-
assert.equal(s.entries.length, 3);
|
|
228
|
-
assert.ok(s.entries.every((e) => e.plane === "skill" && e.reachable));
|
|
229
|
-
|
|
230
|
-
const m = enumerateMcp({ agentRoot: root, at: AT, env: {}, wishlist: [] }, {});
|
|
231
|
-
assert.deepEqual(m.entries.map((e) => e.id), ["mcp:cohort"]);
|
|
232
|
-
});
|
package/lib/capability.test.mjs
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* capability.test.mjs — tests for the capability-surface resolver.
|
|
3
|
-
* Run: `node --test lib/capability.test.mjs`
|
|
4
|
-
* @module capability.test
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
"use strict";
|
|
8
|
-
|
|
9
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import {
|
|
12
|
-
resolveCapabilitySurface,
|
|
13
|
-
loadCapabilityPack,
|
|
14
|
-
filterTeamsByBreadth,
|
|
15
|
-
buildEventRouting,
|
|
16
|
-
listCapabilityFunctions,
|
|
17
|
-
STANDARD_DEFAULT_AGENTS,
|
|
18
|
-
} from "./capability.mjs";
|
|
19
|
-
|
|
20
|
-
const TEAMS = [
|
|
21
|
-
{ id: "a", core: true, agents: [{ id: "a1", tier: "lead" }, { id: "a2", tier: "core" }, { id: "a3", tier: "support" }] },
|
|
22
|
-
{ id: "b", core: false, agents: [{ id: "b1", tier: "lead" }, { id: "b2", tier: "support" }] },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
test("filterTeamsByBreadth: breadth scales the roster deterministically", () => {
|
|
26
|
-
const count = (b) => filterTeamsByBreadth(TEAMS, b).flatMap((t) => t.agents).length;
|
|
27
|
-
assert.equal(count("broad"), 5); // both teams, all tiers
|
|
28
|
-
assert.equal(count("moderate"), 3); // both teams, drop support (a: lead+core, b: lead)
|
|
29
|
-
assert.equal(count("narrow"), 2); // core team only (a), drop support
|
|
30
|
-
assert.equal(count("none"), 0);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("technical-leader capability pack loads with mcp/skills/workflows/teams", async () => {
|
|
34
|
-
assert.ok(listCapabilityFunctions().includes("technical-leader"));
|
|
35
|
-
const pack = await loadCapabilityPack("technical-leader");
|
|
36
|
-
assert.ok(pack.mcpServers.length >= 1);
|
|
37
|
-
assert.ok(pack.skills.length >= 1);
|
|
38
|
-
assert.ok(pack.workflows.length >= 1);
|
|
39
|
-
assert.ok(pack.agentTeams.length >= 3);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("resolveCapabilitySurface composes standard defaults + altitude-scaled role agents", async () => {
|
|
43
|
-
const s = await resolveCapabilitySurface({ function: "technical-leader", altitude: "c-suite" });
|
|
44
|
-
assert.equal(s.standardAgents.length, STANDARD_DEFAULT_AGENTS.length);
|
|
45
|
-
assert.ok(s.roleAgents.length > 0);
|
|
46
|
-
assert.equal(s.counts.total, s.counts.standard + s.counts.role);
|
|
47
|
-
assert.ok(s.mcpServers.length >= 1);
|
|
48
|
-
assert.ok(s.roleAgents.every((a) => a.id && a.role && a.team), "role agents carry id/role/team");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("altitude scales breadth: c-suite ≥ vp ≥ senior-manager role-agent count", async () => {
|
|
52
|
-
const c = await resolveCapabilitySurface({ function: "technical-leader", altitude: "c-suite" });
|
|
53
|
-
const v = await resolveCapabilitySurface({ function: "technical-leader", altitude: "vp" });
|
|
54
|
-
const m = await resolveCapabilitySurface({ function: "technical-leader", altitude: "senior-manager" });
|
|
55
|
-
assert.ok(c.counts.role >= v.counts.role, `c-suite(${c.counts.role}) ≥ vp(${v.counts.role})`);
|
|
56
|
-
assert.ok(v.counts.role >= m.counts.role, `vp(${v.counts.role}) ≥ senior-manager(${m.counts.role})`);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("buildEventRouting maps standard, workflow, and skill handlers", async () => {
|
|
60
|
-
const s = await resolveCapabilitySurface({ function: "technical-leader", altitude: "vp" });
|
|
61
|
-
const { routes } = buildEventRouting(s);
|
|
62
|
-
assert.ok(routes.some((r) => r.via === "standard-agent"));
|
|
63
|
-
assert.ok(routes.some((r) => r.via === "workflow"));
|
|
64
|
-
assert.ok(routes.some((r) => r.via === "skill"));
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("every function pack resolves to a 40–60 role roster at broad altitude, with unique ids + mcp/skills/workflows", async () => {
|
|
68
|
-
const fns = listCapabilityFunctions();
|
|
69
|
-
assert.ok(fns.length >= 6, `expected ≥6 capability packs, got ${fns.length}`);
|
|
70
|
-
for (const fn of fns) {
|
|
71
|
-
const pack = await loadCapabilityPack(fn);
|
|
72
|
-
const ids = pack.agentTeams.flatMap((t) => t.agents.map((a) => a.id));
|
|
73
|
-
assert.equal(ids.length, new Set(ids).size, `${fn}: duplicate agent ids`);
|
|
74
|
-
assert.ok(pack.mcpServers.length >= 1 && pack.skills.length >= 1 && pack.workflows.length >= 1, `${fn}: missing mcp/skills/workflows`);
|
|
75
|
-
const broad = await resolveCapabilitySurface({ function: fn, altitude: "founder" }); // founder ⇒ broad
|
|
76
|
-
assert.ok(broad.counts.role >= 40 && broad.counts.role <= 60, `${fn}: broad role count ${broad.counts.role} not in 40–60`);
|
|
77
|
-
}
|
|
78
|
-
});
|