@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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/mcp/http.mjs — the MCP streamable HTTP transport (client side).
|
|
3
|
+
*
|
|
4
|
+
* Per the MCP specification (2025-03-26 and 2025-06-18):
|
|
5
|
+
* · every client message is an HTTP POST of one JSON-RPC message to the
|
|
6
|
+
* endpoint, with `Accept: application/json, text/event-stream`;
|
|
7
|
+
* · a notification or response is answered 202 Accepted with no body;
|
|
8
|
+
* · a request is answered either with `application/json` (the response
|
|
9
|
+
* object) or with `text/event-stream`, whose `data:` frames carry JSON-RPC
|
|
10
|
+
* messages — possibly server requests first — ending with the response;
|
|
11
|
+
* · the server may assign a session in the initialize response's
|
|
12
|
+
* `Mcp-Session-Id` header, which the client then sends on every request;
|
|
13
|
+
* a 404 for a request carrying it means the session has expired;
|
|
14
|
+
* · after initialization the client sends `MCP-Protocol-Version`;
|
|
15
|
+
* · the client ends a session with an HTTP DELETE carrying the session id.
|
|
16
|
+
*
|
|
17
|
+
* The optional GET stream for server-initiated messages (`openEventStream`)
|
|
18
|
+
* is opened once after initialization, with `Accept: text/event-stream` and
|
|
19
|
+
* the session headers. A server that does not offer it answers 405 (or any
|
|
20
|
+
* non-stream response), which is not an error. Messages on it — typically
|
|
21
|
+
* `notifications/tools/list_changed` — are delivered like any other. If the
|
|
22
|
+
* server ends the stream it is not reopened; it is aborted when the session
|
|
23
|
+
* closes.
|
|
24
|
+
*
|
|
25
|
+
* @module lib/engine/mcp/http
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readSse } from "../wire/sse.mjs";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {object} p
|
|
32
|
+
* @param {string} p.url
|
|
33
|
+
* @param {Record<string,string>} [p.headers]
|
|
34
|
+
* @param {typeof fetch} [p.fetchImpl]
|
|
35
|
+
*/
|
|
36
|
+
export function createHttpTransport({ url, headers = {}, fetchImpl = fetch }) {
|
|
37
|
+
/** @type {string|null} */
|
|
38
|
+
let sessionId = null;
|
|
39
|
+
/** @type {string|null} */
|
|
40
|
+
let protocolVersion = null;
|
|
41
|
+
/** @type {(msg:unknown)=>void} */
|
|
42
|
+
let deliver = () => {};
|
|
43
|
+
let closed = false;
|
|
44
|
+
const inflight = new Set();
|
|
45
|
+
/** @type {AbortController|null} */
|
|
46
|
+
let streamController = null;
|
|
47
|
+
|
|
48
|
+
const requestHeaders = () => ({
|
|
49
|
+
...headers,
|
|
50
|
+
"content-type": "application/json",
|
|
51
|
+
accept: "application/json, text/event-stream",
|
|
52
|
+
...(sessionId ? { "mcp-session-id": sessionId } : {}),
|
|
53
|
+
...(protocolVersion ? { "mcp-protocol-version": protocolVersion } : {}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
/** @param {(msg:unknown)=>void} onMessage @param {(reason:string)=>void} _onClose */
|
|
58
|
+
start(onMessage, _onClose) {
|
|
59
|
+
deliver = onMessage;
|
|
60
|
+
return { ok: true };
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/** @param {string} v */
|
|
64
|
+
setProtocolVersion(v) {
|
|
65
|
+
protocolVersion = v;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
get sessionId() {
|
|
69
|
+
return sessionId;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {any} msg
|
|
74
|
+
* @param {{signal?:AbortSignal}} [o]
|
|
75
|
+
* @returns {Promise<{ok:true, completed?:boolean}|{ok:false, error:string}>}
|
|
76
|
+
*/
|
|
77
|
+
async send(msg, o = {}) {
|
|
78
|
+
if (closed) return { ok: false, error: "the HTTP session is closed" };
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
const onAbort = () => controller.abort();
|
|
81
|
+
o.signal?.addEventListener("abort", onAbort, { once: true });
|
|
82
|
+
inflight.add(controller);
|
|
83
|
+
try {
|
|
84
|
+
const hadSession = sessionId !== null;
|
|
85
|
+
let res;
|
|
86
|
+
try {
|
|
87
|
+
res = await fetchImpl(url, { method: "POST", headers: requestHeaders(), body: JSON.stringify(msg), signal: controller.signal });
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return { ok: false, error: `HTTP request to ${url} failed: ${e instanceof Error ? e.message : e}` };
|
|
90
|
+
}
|
|
91
|
+
const assigned = res.headers.get("mcp-session-id");
|
|
92
|
+
if (assigned && msg.method === "initialize") sessionId = assigned;
|
|
93
|
+
if (res.status === 202) return { ok: true, completed: msg.id !== undefined };
|
|
94
|
+
if (res.status === 404 && hadSession) {
|
|
95
|
+
await res.body?.cancel().catch(() => {});
|
|
96
|
+
return { ok: false, error: "the MCP server's session has expired (HTTP 404)" };
|
|
97
|
+
}
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
const text = await res.text().catch(() => "");
|
|
100
|
+
return { ok: false, error: `HTTP ${res.status} from ${url}${text ? `: ${text.slice(0, 300)}` : ""}` };
|
|
101
|
+
}
|
|
102
|
+
const type = (res.headers.get("content-type") || "").toLowerCase();
|
|
103
|
+
if (type.includes("text/event-stream")) {
|
|
104
|
+
if (!res.body) return { ok: true, completed: true };
|
|
105
|
+
try {
|
|
106
|
+
for await (const frame of readSse(res.body)) {
|
|
107
|
+
if (!frame.data) continue;
|
|
108
|
+
try {
|
|
109
|
+
deliver(JSON.parse(frame.data));
|
|
110
|
+
} catch {
|
|
111
|
+
/* a non-JSON frame is not a message */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return { ok: false, error: `the response stream from ${url} broke: ${e instanceof Error ? e.message : e}` };
|
|
116
|
+
}
|
|
117
|
+
return { ok: true, completed: true };
|
|
118
|
+
}
|
|
119
|
+
const text = await res.text();
|
|
120
|
+
if (text.trim() === "") return { ok: true, completed: true };
|
|
121
|
+
try {
|
|
122
|
+
deliver(JSON.parse(text));
|
|
123
|
+
} catch {
|
|
124
|
+
return { ok: false, error: `the response from ${url} was not JSON` };
|
|
125
|
+
}
|
|
126
|
+
return { ok: true, completed: true };
|
|
127
|
+
} finally {
|
|
128
|
+
inflight.delete(controller);
|
|
129
|
+
o.signal?.removeEventListener("abort", onAbort);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Open the GET stream for server-initiated messages (once). Never rejects.
|
|
135
|
+
* @returns {Promise<{ok:true}|{ok:false, status:number|null}>}
|
|
136
|
+
*/
|
|
137
|
+
async openEventStream() {
|
|
138
|
+
if (closed || streamController) return { ok: false, status: null };
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
streamController = controller;
|
|
141
|
+
let res;
|
|
142
|
+
try {
|
|
143
|
+
res = await fetchImpl(url, {
|
|
144
|
+
method: "GET",
|
|
145
|
+
headers: {
|
|
146
|
+
...headers,
|
|
147
|
+
accept: "text/event-stream",
|
|
148
|
+
...(sessionId ? { "mcp-session-id": sessionId } : {}),
|
|
149
|
+
...(protocolVersion ? { "mcp-protocol-version": protocolVersion } : {}),
|
|
150
|
+
},
|
|
151
|
+
signal: controller.signal,
|
|
152
|
+
});
|
|
153
|
+
} catch {
|
|
154
|
+
return { ok: false, status: null };
|
|
155
|
+
}
|
|
156
|
+
const type = (res.headers.get("content-type") || "").toLowerCase();
|
|
157
|
+
if (!res.ok || !type.includes("text/event-stream") || !res.body) {
|
|
158
|
+
await res.body?.cancel().catch(() => {});
|
|
159
|
+
return { ok: false, status: res.status };
|
|
160
|
+
}
|
|
161
|
+
const body = res.body;
|
|
162
|
+
void (async () => {
|
|
163
|
+
try {
|
|
164
|
+
for await (const frame of readSse(body)) {
|
|
165
|
+
if (!frame.data) continue;
|
|
166
|
+
try {
|
|
167
|
+
deliver(JSON.parse(frame.data));
|
|
168
|
+
} catch {
|
|
169
|
+
/* a non-JSON frame is not a message */
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
/* the stream ended or was aborted; it is optional */
|
|
174
|
+
}
|
|
175
|
+
})();
|
|
176
|
+
return { ok: true };
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
stderrTail: () => "",
|
|
180
|
+
|
|
181
|
+
/** End the session (best effort) and abort anything still in flight. */
|
|
182
|
+
async close() {
|
|
183
|
+
if (closed) return;
|
|
184
|
+
closed = true;
|
|
185
|
+
streamController?.abort();
|
|
186
|
+
for (const c of inflight) c.abort();
|
|
187
|
+
if (!sessionId) return;
|
|
188
|
+
const controller = new AbortController();
|
|
189
|
+
const timer = setTimeout(() => controller.abort(), 2_000);
|
|
190
|
+
timer.unref?.();
|
|
191
|
+
try {
|
|
192
|
+
const res = await fetchImpl(url, { method: "DELETE", headers: { ...headers, "mcp-session-id": sessionId }, signal: controller.signal });
|
|
193
|
+
await res.body?.cancel().catch(() => {});
|
|
194
|
+
} catch {
|
|
195
|
+
/* the server may not allow DELETE (405) or may already be gone */
|
|
196
|
+
} finally {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/mcp/index.mjs — MCP servers → engine tools.
|
|
3
|
+
*
|
|
4
|
+
* `startMcpServers` connects every configured server in parallel and turns
|
|
5
|
+
* each tool it lists into an engine tool object named `mcp__<server>__<tool>`
|
|
6
|
+
* (see naming.mjs). A server that fails to start is reported in `statuses` and
|
|
7
|
+
* `errors`, and the run continues without it — the same posture as a tool
|
|
8
|
+
* that errors. A tool whose name is too long or collides is reported and not
|
|
9
|
+
* exposed.
|
|
10
|
+
*
|
|
11
|
+
* Each connection is supervised (supervisor.mjs): a stdio server that crashes
|
|
12
|
+
* is restarted once, on the next call. `refreshChanged` re-lists the tools of
|
|
13
|
+
* servers that sent `notifications/tools/list_changed` (or came back with a
|
|
14
|
+
* different list after a restart) and rebuilds the tool list; the run calls it
|
|
15
|
+
* at a turn boundary.
|
|
16
|
+
*
|
|
17
|
+
* MCP tools are marked `passthroughInput`: their input schemas are the
|
|
18
|
+
* server's, which the engine's small validator does not fully understand, so
|
|
19
|
+
* the loop checks only that the input is an object with the required keys and
|
|
20
|
+
* forwards it unchanged.
|
|
21
|
+
*
|
|
22
|
+
* @module lib/engine/mcp/index
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { connectMcpServer } from "./client.mjs";
|
|
26
|
+
import { mcpToolName } from "./naming.mjs";
|
|
27
|
+
import { superviseConnection } from "./supervisor.mjs";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {{name:string, source:string, transport:string, status:'connected'|'failed', tools:number, error?:string}} McpServerStatus
|
|
31
|
+
* @typedef {ReturnType<typeof superviseConnection>} SupervisedConnection
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {import('./config.mjs').McpServerConfig[]} servers
|
|
36
|
+
* @param {Parameters<typeof connectMcpServer>[1] & {connect?:typeof connectMcpServer}} [deps]
|
|
37
|
+
*/
|
|
38
|
+
export async function startMcpServers(servers, deps = {}) {
|
|
39
|
+
const connect = deps.connect ?? connectMcpServer;
|
|
40
|
+
const connected = await Promise.all(servers.map((s) => connect(s, deps).then((r) => ({ s, r }))));
|
|
41
|
+
/** @type {McpServerStatus[]} */
|
|
42
|
+
const statuses = [];
|
|
43
|
+
/** @type {string[]} */
|
|
44
|
+
const errors = [];
|
|
45
|
+
/** @type {Array<{s:import('./config.mjs').McpServerConfig, conn:SupervisedConnection, status:McpServerStatus}>} */
|
|
46
|
+
const live = [];
|
|
47
|
+
|
|
48
|
+
for (const { s, r } of connected) {
|
|
49
|
+
if (!r.ok) {
|
|
50
|
+
statuses.push({ name: s.name, source: s.source, transport: s.transport, status: "failed", tools: 0, error: r.error });
|
|
51
|
+
errors.push(r.error);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const status = { name: s.name, source: s.source, transport: s.transport, status: /** @type {'connected'} */ ("connected"), tools: 0 };
|
|
55
|
+
statuses.push(status);
|
|
56
|
+
live.push({ s, conn: superviseConnection(s, deps, r.connection, { connect }), status });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @param {string[]} sink */
|
|
60
|
+
const build = (sink) => {
|
|
61
|
+
/** @type {import('../tools/index.mjs').Tool[]} */
|
|
62
|
+
const tools = [];
|
|
63
|
+
const taken = new Set();
|
|
64
|
+
for (const { s, conn, status } of live) {
|
|
65
|
+
let count = 0;
|
|
66
|
+
for (const t of conn.tools) {
|
|
67
|
+
const named = mcpToolName(s.name, t.name);
|
|
68
|
+
if (!named.ok) {
|
|
69
|
+
sink.push(named.error);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (taken.has(named.name)) {
|
|
73
|
+
sink.push(`MCP tool name "${named.name}" is produced by more than one tool; only the first is exposed`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
taken.add(named.name);
|
|
77
|
+
tools.push(mcpTool(named.name, s, t, conn));
|
|
78
|
+
count++;
|
|
79
|
+
}
|
|
80
|
+
status.tools = count;
|
|
81
|
+
}
|
|
82
|
+
return tools;
|
|
83
|
+
};
|
|
84
|
+
let tools = build(errors);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
get tools() {
|
|
88
|
+
return tools;
|
|
89
|
+
},
|
|
90
|
+
statuses,
|
|
91
|
+
errors,
|
|
92
|
+
/** @type {SupervisedConnection[]} */
|
|
93
|
+
connections: live.map((l) => l.conn),
|
|
94
|
+
/**
|
|
95
|
+
* Re-list servers whose tools changed; rebuild the tool list if any did.
|
|
96
|
+
* @returns {Promise<{changed:boolean, tools:import('../tools/index.mjs').Tool[], errors:string[]}>}
|
|
97
|
+
*/
|
|
98
|
+
async refreshChanged() {
|
|
99
|
+
const problems = [];
|
|
100
|
+
let changed = false;
|
|
101
|
+
for (const { conn } of live) {
|
|
102
|
+
if (!conn.toolsChanged) continue;
|
|
103
|
+
const r = await conn.refreshTools();
|
|
104
|
+
if (r.ok) changed = true;
|
|
105
|
+
else problems.push(r.error);
|
|
106
|
+
}
|
|
107
|
+
if (changed) tools = build(problems);
|
|
108
|
+
return { changed, tools, errors: problems };
|
|
109
|
+
},
|
|
110
|
+
/** Shut every server down; never throws. */
|
|
111
|
+
async close() {
|
|
112
|
+
await Promise.all(live.map(({ conn }) => conn.close().catch(() => {})));
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {string} name
|
|
119
|
+
* @param {import('./config.mjs').McpServerConfig} server
|
|
120
|
+
* @param {{name:string, description?:string, inputSchema?:any, annotations?:{readOnlyHint?:boolean}}} t
|
|
121
|
+
* @param {{callTool:(tool:string, args:object, o?:{signal?:AbortSignal})=>Promise<any>}} conn
|
|
122
|
+
* @returns {import('../tools/index.mjs').Tool & {passthroughInput:true, mcp:object}}
|
|
123
|
+
*/
|
|
124
|
+
function mcpTool(name, server, t, conn) {
|
|
125
|
+
const schema = t.inputSchema && typeof t.inputSchema === "object" && !Array.isArray(t.inputSchema) ? { ...t.inputSchema } : {};
|
|
126
|
+
if (schema.type === undefined) schema.type = "object";
|
|
127
|
+
if (schema.properties === undefined) schema.properties = {};
|
|
128
|
+
return {
|
|
129
|
+
name,
|
|
130
|
+
description: typeof t.description === "string" && t.description.trim() !== "" ? t.description : `${t.name} (from the ${server.name} MCP server)`,
|
|
131
|
+
input_schema: schema,
|
|
132
|
+
// Read-only hints are the server's claim; they schedule parallel batches
|
|
133
|
+
// only. No permission is granted on their strength, and plan mode refuses
|
|
134
|
+
// every MCP tool whatever its hint.
|
|
135
|
+
readOnly: t.annotations?.readOnlyHint === true,
|
|
136
|
+
passthroughInput: true,
|
|
137
|
+
mcp: {
|
|
138
|
+
server: server.name,
|
|
139
|
+
tool: t.name,
|
|
140
|
+
transport: server.transport,
|
|
141
|
+
command: server.transport === "stdio" ? server.command : null,
|
|
142
|
+
args: server.transport === "stdio" ? server.args : [],
|
|
143
|
+
},
|
|
144
|
+
run: (input, ctx) => conn.callTool(t.name, input, { signal: ctx.signal }),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/mcp/jsonrpc.mjs — a JSON-RPC 2.0 peer, transport-agnostic.
|
|
3
|
+
*
|
|
4
|
+
* The MCP client is one of these over a transport. The peer owns request ids,
|
|
5
|
+
* the pending-request table, per-request timeouts and cancellation, and the
|
|
6
|
+
* replies to the few requests a server may send the client (`ping`). It never
|
|
7
|
+
* rejects: every request resolves to a result union, so a dead server is a
|
|
8
|
+
* value the caller reports, not an exception that unwinds a run.
|
|
9
|
+
*
|
|
10
|
+
* @module lib/engine/mcp/jsonrpc
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{kind:'rpc', code:number, message:string, data?:unknown}
|
|
15
|
+
* | {kind:'timeout', message:string}
|
|
16
|
+
* | {kind:'closed', message:string}
|
|
17
|
+
* | {kind:'aborted', message:string}
|
|
18
|
+
* | {kind:'transport', message:string}} RpcError
|
|
19
|
+
* @typedef {{ok:true, result:any} | {ok:false, error:RpcError}} RpcOutcome
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {object} p
|
|
24
|
+
* @param {(msg:object, o?:{signal?:AbortSignal}) => ({ok:true, completed?:boolean}|{ok:false, error:string}|Promise<{ok:true, completed?:boolean}|{ok:false, error:string}>)} p.send
|
|
25
|
+
* `completed:true` means the transport has delivered everything it will ever
|
|
26
|
+
* deliver for this message (an HTTP response body has ended).
|
|
27
|
+
* @param {(line:string) => void} [p.log]
|
|
28
|
+
* @param {(msg:{method:string, params?:any}) => void} [p.onNotification] server notifications
|
|
29
|
+
* (e.g. notifications/tools/list_changed); a throwing handler is contained
|
|
30
|
+
*/
|
|
31
|
+
export function createRpcPeer({ send, log = () => {}, onNotification = () => {} }) {
|
|
32
|
+
let nextId = 1;
|
|
33
|
+
/** @type {Map<number, (o:RpcOutcome) => void>} */
|
|
34
|
+
const pending = new Map();
|
|
35
|
+
/** @type {string|null} */
|
|
36
|
+
let closedReason = null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} method
|
|
40
|
+
* @param {object} [params]
|
|
41
|
+
* @param {{timeoutMs?:number, signal?:AbortSignal}} [o]
|
|
42
|
+
* @returns {Promise<RpcOutcome>}
|
|
43
|
+
*/
|
|
44
|
+
function request(method, params, o = {}) {
|
|
45
|
+
if (closedReason !== null) return Promise.resolve({ ok: false, error: { kind: "closed", message: closedReason } });
|
|
46
|
+
const id = nextId++;
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
/** @type {ReturnType<typeof setTimeout>|null} */
|
|
49
|
+
let timer = null;
|
|
50
|
+
const onAbort = () => {
|
|
51
|
+
finish({ ok: false, error: { kind: "aborted", message: "the request was cancelled" } });
|
|
52
|
+
notify("notifications/cancelled", { requestId: id, reason: "cancelled by the client" });
|
|
53
|
+
};
|
|
54
|
+
const finish = (/** @type RpcOutcome */ outcome) => {
|
|
55
|
+
if (!pending.has(id)) return;
|
|
56
|
+
pending.delete(id);
|
|
57
|
+
if (timer) clearTimeout(timer);
|
|
58
|
+
o.signal?.removeEventListener("abort", onAbort);
|
|
59
|
+
resolve(outcome);
|
|
60
|
+
};
|
|
61
|
+
pending.set(id, finish);
|
|
62
|
+
if (o.timeoutMs && o.timeoutMs > 0) {
|
|
63
|
+
timer = setTimeout(() => {
|
|
64
|
+
finish({ ok: false, error: { kind: "timeout", message: `no response to ${method} within ${o.timeoutMs} ms` } });
|
|
65
|
+
notify("notifications/cancelled", { requestId: id, reason: "timed out" });
|
|
66
|
+
}, o.timeoutMs);
|
|
67
|
+
// Not unref'd: a caller awaiting only this request must keep the
|
|
68
|
+
// process alive until it resolves. `finish` always clears it.
|
|
69
|
+
}
|
|
70
|
+
if (o.signal?.aborted) return onAbort();
|
|
71
|
+
o.signal?.addEventListener("abort", onAbort, { once: true });
|
|
72
|
+
|
|
73
|
+
const msg = { jsonrpc: "2.0", id, method, ...(params === undefined ? {} : { params }) };
|
|
74
|
+
Promise.resolve()
|
|
75
|
+
.then(() => send(msg, { signal: o.signal }))
|
|
76
|
+
.then(
|
|
77
|
+
(r) => {
|
|
78
|
+
if (!r.ok) finish({ ok: false, error: { kind: "transport", message: r.error } });
|
|
79
|
+
else if (r.completed) finish({ ok: false, error: { kind: "transport", message: `the server ended its response to ${method} without answering it` } });
|
|
80
|
+
},
|
|
81
|
+
(e) => finish({ ok: false, error: { kind: "transport", message: e instanceof Error ? e.message : String(e) } }),
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @param {string} method @param {object} [params] */
|
|
87
|
+
function notify(method, params) {
|
|
88
|
+
if (closedReason !== null) return;
|
|
89
|
+
const msg = { jsonrpc: "2.0", method, ...(params === undefined ? {} : { params }) };
|
|
90
|
+
Promise.resolve()
|
|
91
|
+
.then(() => send(msg))
|
|
92
|
+
.catch(() => {
|
|
93
|
+
/* a notification has no one to report a failure to */
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @param {unknown} msg a parsed frame from the transport (object or batch array) */
|
|
98
|
+
function receive(msg) {
|
|
99
|
+
if (Array.isArray(msg)) {
|
|
100
|
+
for (const m of msg) receive(m);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!msg || typeof msg !== "object") return;
|
|
104
|
+
const m = /** @type {any} */ (msg);
|
|
105
|
+
if (m.method !== undefined) {
|
|
106
|
+
// A request or notification from the server.
|
|
107
|
+
if (m.id === undefined || m.id === null) {
|
|
108
|
+
try {
|
|
109
|
+
onNotification(m);
|
|
110
|
+
} catch {
|
|
111
|
+
/* a notification handler defect must not break the connection */
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const reply =
|
|
116
|
+
m.method === "ping"
|
|
117
|
+
? { jsonrpc: "2.0", id: m.id, result: {} }
|
|
118
|
+
: { jsonrpc: "2.0", id: m.id, error: { code: -32601, message: `the client does not support ${m.method}` } };
|
|
119
|
+
Promise.resolve()
|
|
120
|
+
.then(() => send(reply))
|
|
121
|
+
.catch(() => {
|
|
122
|
+
/* the server will time out its own request */
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const finish = pending.get(Number(m.id));
|
|
127
|
+
if (!finish) {
|
|
128
|
+
log(`ignored a response for unknown request id ${JSON.stringify(m.id)}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (m.error) {
|
|
132
|
+
const e = m.error;
|
|
133
|
+
finish({ ok: false, error: { kind: "rpc", code: Number(e.code), message: String(e.message ?? "error"), data: e.data } });
|
|
134
|
+
} else {
|
|
135
|
+
finish({ ok: true, result: m.result });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Resolve every pending request as closed; later requests resolve closed at once. @param {string} reason */
|
|
140
|
+
function close(reason) {
|
|
141
|
+
if (closedReason !== null) return;
|
|
142
|
+
closedReason = reason;
|
|
143
|
+
for (const finish of [...pending.values()]) finish({ ok: false, error: { kind: "closed", message: reason } });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { request, notify, receive, close, get closed() { return closedReason !== null; } };
|
|
147
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/mcp/naming.mjs — how an MCP tool is named to the model (pure).
|
|
3
|
+
*
|
|
4
|
+
* mcp__<server>__<tool>
|
|
5
|
+
*
|
|
6
|
+
* This name is a compatibility contract, not a detail: maestro's settings
|
|
7
|
+
* (`permissions.allow: ["mcp__cohort__*"]`), its PreToolUse matchers
|
|
8
|
+
* (`mcp__cohort__(messaging_send|…)`), its prompts and skills all spell tools
|
|
9
|
+
* this way. `naming.test.mjs` pins it.
|
|
10
|
+
*
|
|
11
|
+
* Rules:
|
|
12
|
+
* · each part (server key, tool name) has every character outside
|
|
13
|
+
* [A-Za-z0-9_-] replaced by "_" — the set both gateway wires accept in a
|
|
14
|
+
* function name;
|
|
15
|
+
* · the whole name may be at most 64 characters (the wire limit). A longer
|
|
16
|
+
* name is a RETURNED ERROR. It is never truncated or hashed: a silently
|
|
17
|
+
* shortened name would no longer match the permission rules and hook
|
|
18
|
+
* matchers written against the real one, which is how a gated tool slips
|
|
19
|
+
* through a gate.
|
|
20
|
+
*
|
|
21
|
+
* @module lib/engine/mcp/naming
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const MCP_PREFIX = "mcp__";
|
|
25
|
+
export const MAX_TOOL_NAME_LENGTH = 64;
|
|
26
|
+
|
|
27
|
+
/** @param {string} part */
|
|
28
|
+
export function sanitiseNamePart(part) {
|
|
29
|
+
return String(part).replace(/[^A-Za-z0-9_-]/g, "_");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} server the server's config key
|
|
34
|
+
* @param {string} tool the tool name the server reported
|
|
35
|
+
* @returns {{ok:true, name:string} | {ok:false, error:string}}
|
|
36
|
+
*/
|
|
37
|
+
export function mcpToolName(server, tool) {
|
|
38
|
+
if (typeof server !== "string" || server === "") return { ok: false, error: "MCP server name is empty" };
|
|
39
|
+
if (typeof tool !== "string" || tool === "") return { ok: false, error: `MCP server "${server}" reported a tool with no name` };
|
|
40
|
+
const name = `${MCP_PREFIX}${sanitiseNamePart(server)}__${sanitiseNamePart(tool)}`;
|
|
41
|
+
if (name.length > MAX_TOOL_NAME_LENGTH) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
error: `MCP tool "${tool}" on server "${server}" would be named "${name}" (${name.length} characters); the limit is ${MAX_TOOL_NAME_LENGTH}. Rename the server key in the MCP config to something shorter.`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return { ok: true, name };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Is a server key usable at all? Checked when the config is read, so a key
|
|
52
|
+
* too long to leave room for any tool name fails before a process is spawned.
|
|
53
|
+
* @param {string} server
|
|
54
|
+
* @returns {{ok:true} | {ok:false, error:string}}
|
|
55
|
+
*/
|
|
56
|
+
export function validateServerName(server) {
|
|
57
|
+
if (typeof server !== "string" || server.trim() === "") return { ok: false, error: "an MCP server has an empty name" };
|
|
58
|
+
const prefix = `${MCP_PREFIX}${sanitiseNamePart(server)}__`;
|
|
59
|
+
if (prefix.length + 1 > MAX_TOOL_NAME_LENGTH) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
error: `MCP server name "${server}" is too long: its tools would be named "${prefix}…", which leaves no room under the ${MAX_TOOL_NAME_LENGTH}-character limit`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return { ok: true };
|
|
66
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/mcp/resources.mjs — ListMcpResources and ReadMcpResource.
|
|
3
|
+
*
|
|
4
|
+
* MCP servers can publish resources (files, records, documents) beside their
|
|
5
|
+
* tools. When at least one connected server declares the `resources`
|
|
6
|
+
* capability, two engine tools expose them:
|
|
7
|
+
*
|
|
8
|
+
* ListMcpResources {server?} every resource, or one server's
|
|
9
|
+
* ReadMcpResource {server, uri} one resource's contents; text passes
|
|
10
|
+
* through, images are attached for the
|
|
11
|
+
* wire (within the caps in results.mjs)
|
|
12
|
+
*
|
|
13
|
+
* Both are read-only built-ins for permissions. A server whose tools are denied
|
|
14
|
+
* outright (`mcp__<server>` or `mcp__<server>__*` deny) has its resources
|
|
15
|
+
* withheld too: it is neither listed nor readable, and when no other server
|
|
16
|
+
* publishes resources the two tools are not offered. Deny `ReadMcpResource`
|
|
17
|
+
* itself to withhold every server's resources.
|
|
18
|
+
*
|
|
19
|
+
* Authorship: tool descriptions written for Cohort Engine by the Cohort team,
|
|
20
|
+
* 2026-09-14 (original text).
|
|
21
|
+
*
|
|
22
|
+
* @module lib/engine/mcp/resources
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {{name:string, capabilities:Record<string,any>, listResources:(o?:{signal?:AbortSignal})=>Promise<any>, readResource:(uri:string, o?:{signal?:AbortSignal})=>Promise<any>}} ResourceServer
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** @param {ResourceServer} c */
|
|
30
|
+
const offersResources = (c) => Boolean(c.capabilities && typeof c.capabilities === "object" && c.capabilities.resources);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {() => ResourceServer[]} connections read at call time (servers can restart)
|
|
34
|
+
* @param {{isServerDenied?:(name:string)=>boolean}} [o] a server denied outright keeps its resources to itself
|
|
35
|
+
* @returns {import('../tools/index.mjs').Tool[]} empty when no permitted server offers resources
|
|
36
|
+
*/
|
|
37
|
+
export function createMcpResourceTools(connections, { isServerDenied = () => false } = {}) {
|
|
38
|
+
const servers = () => connections().filter((c) => offersResources(c) && !isServerDenied(c.name));
|
|
39
|
+
if (servers().length === 0) return [];
|
|
40
|
+
const names = () => servers().map((c) => c.name).join(", ");
|
|
41
|
+
|
|
42
|
+
/** @type {import('../tools/index.mjs').Tool} */
|
|
43
|
+
const list = {
|
|
44
|
+
name: "ListMcpResources",
|
|
45
|
+
readOnly: true,
|
|
46
|
+
description: "List the resources (documents, files, records) that connected MCP servers publish. Pass `server` to list one server's resources. Read one with ReadMcpResource.",
|
|
47
|
+
input_schema: { type: "object", properties: { server: { type: "string", description: "Only this MCP server." } } },
|
|
48
|
+
async run(input, ctx) {
|
|
49
|
+
const targets = input.server ? servers().filter((c) => c.name === input.server) : servers();
|
|
50
|
+
if (input.server && targets.length === 0) return { ok: false, error: `No connected MCP server named "${input.server}" publishes resources. Servers with resources: ${names() || "none"}.` };
|
|
51
|
+
const sections = [];
|
|
52
|
+
for (const c of targets) {
|
|
53
|
+
const r = await c.listResources({ signal: ctx.signal });
|
|
54
|
+
if (!r.ok) {
|
|
55
|
+
sections.push(`${c.name}: could not list resources (${r.error})`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (r.resources.length === 0) {
|
|
59
|
+
sections.push(`${c.name}: no resources`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const lines = r.resources.map((/** @type any */ x) => `- ${x.uri}${x.name ? ` — ${x.name}` : ""}${x.mimeType ? ` (${x.mimeType})` : ""}${x.description ? `: ${String(x.description).replace(/\s+/g, " ")}` : ""}`);
|
|
63
|
+
sections.push(`${c.name}:\n${lines.join("\n")}`);
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, content: sections.join("\n\n") };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** @type {import('../tools/index.mjs').Tool} */
|
|
70
|
+
const read = {
|
|
71
|
+
name: "ReadMcpResource",
|
|
72
|
+
readOnly: true,
|
|
73
|
+
description: "Read one resource from an MCP server by its URI (as listed by ListMcpResources).",
|
|
74
|
+
input_schema: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
server: { type: "string", description: "The MCP server that publishes the resource." },
|
|
78
|
+
uri: { type: "string", description: "The resource URI." },
|
|
79
|
+
},
|
|
80
|
+
required: ["server", "uri"],
|
|
81
|
+
},
|
|
82
|
+
async run(input, ctx) {
|
|
83
|
+
const c = servers().find((s) => s.name === input.server);
|
|
84
|
+
if (!c) return { ok: false, error: `No connected MCP server named "${input.server}" publishes resources. Servers with resources: ${names() || "none"}.` };
|
|
85
|
+
return c.readResource(input.uri, { signal: ctx.signal });
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
return [list, read];
|
|
89
|
+
}
|