@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,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/mcp/results.mjs — MCP results → engine tool results (pure).
|
|
3
|
+
*
|
|
4
|
+
* Each MCP content block becomes text:
|
|
5
|
+
*
|
|
6
|
+
* text → its text
|
|
7
|
+
* image → with `attachImages`, a one-line note and the image itself
|
|
8
|
+
* in `images` (base64 + media type) for the wire to send as
|
|
9
|
+
* an image block — within the caps below; otherwise, or
|
|
10
|
+
* over a cap, a note with the MIME type and size
|
|
11
|
+
* audio → a note (no gateway wire carries audio input)
|
|
12
|
+
* resource → the embedded resource's text, or a note for a blob
|
|
13
|
+
* (an image blob is attached like an image)
|
|
14
|
+
* resource_link → a note naming the URI
|
|
15
|
+
* anything else → a note naming the block type
|
|
16
|
+
*
|
|
17
|
+
* `isError: true` makes an error result carrying the same text. A result with
|
|
18
|
+
* no content but `structuredContent` returns that JSON.
|
|
19
|
+
*
|
|
20
|
+
* @module lib/engine/mcp/results
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Image types every image-capable wire accepts. */
|
|
24
|
+
export const IMAGE_MEDIA_TYPES = Object.freeze(new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]));
|
|
25
|
+
export const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
26
|
+
export const MAX_IMAGES_PER_RESULT = 8;
|
|
27
|
+
export const MAX_IMAGE_BYTES_PER_RESULT = 20 * 1024 * 1024;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {{mediaType:string, data:string}} ImageAttachment
|
|
31
|
+
* @typedef {{ok:true, content:string, images?:ImageAttachment[]} | {ok:false, error:string}} MappedResult
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Attach an image if it fits; the note to put in the text either way.
|
|
36
|
+
* @param {string|undefined} mimeType @param {unknown} data
|
|
37
|
+
* @param {{attachImages:boolean, images:ImageAttachment[], bytes:number}} state
|
|
38
|
+
*/
|
|
39
|
+
function imageNote(mimeType, data, state) {
|
|
40
|
+
const type = String(mimeType || "unknown type").toLowerCase();
|
|
41
|
+
const size = approxBytes(data);
|
|
42
|
+
if (!state.attachImages) return `[image content: ${type}, ${size} bytes — not shown]`;
|
|
43
|
+
let why = null;
|
|
44
|
+
if (typeof data !== "string" || data === "") why = "no data";
|
|
45
|
+
else if (!IMAGE_MEDIA_TYPES.has(type)) why = `${type} is not a supported image type`;
|
|
46
|
+
else if (size > MAX_IMAGE_BYTES) why = `larger than ${MAX_IMAGE_BYTES} bytes`;
|
|
47
|
+
else if (state.images.length >= MAX_IMAGES_PER_RESULT) why = `more than ${MAX_IMAGES_PER_RESULT} images in one result`;
|
|
48
|
+
else if (state.bytes + size > MAX_IMAGE_BYTES_PER_RESULT) why = `images in one result over ${MAX_IMAGE_BYTES_PER_RESULT} bytes`;
|
|
49
|
+
if (why) return `[image content: ${type}, ${size} bytes — not shown: ${why}]`;
|
|
50
|
+
state.images.push({ mediaType: type, data: /** @type string */ (data) });
|
|
51
|
+
state.bytes += size;
|
|
52
|
+
return `[image content: ${type}, ${size} bytes]`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {unknown} result the JSON-RPC `result` of tools/call
|
|
57
|
+
* @param {{attachImages?:boolean}} [o]
|
|
58
|
+
* @returns {MappedResult}
|
|
59
|
+
*/
|
|
60
|
+
export function mapCallToolResult(result, { attachImages = false } = {}) {
|
|
61
|
+
if (!result || typeof result !== "object") {
|
|
62
|
+
return { ok: false, error: "The MCP server returned a malformed tool result." };
|
|
63
|
+
}
|
|
64
|
+
const r = /** @type {any} */ (result);
|
|
65
|
+
const state = { attachImages, images: /** @type {ImageAttachment[]} */ ([]), bytes: 0 };
|
|
66
|
+
const parts = Array.isArray(r.content) ? r.content.map((b) => blockText(b, state)).filter((t) => t !== "") : [];
|
|
67
|
+
if (parts.length === 0 && r.structuredContent !== undefined) parts.push(safeJson(r.structuredContent));
|
|
68
|
+
const text = parts.join("\n\n");
|
|
69
|
+
if (r.isError === true) return { ok: false, error: text === "" ? "The MCP tool reported an error with no details." : text };
|
|
70
|
+
const out = /** @type {{ok:true, content:string, images?:ImageAttachment[]}} */ ({ ok: true, content: text === "" ? "(no output)" : text });
|
|
71
|
+
if (state.images.length > 0) out.images = state.images;
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `resources/read` result → tool result.
|
|
77
|
+
* @param {unknown} result @param {{attachImages?:boolean}} [o]
|
|
78
|
+
* @returns {MappedResult}
|
|
79
|
+
*/
|
|
80
|
+
export function mapReadResourceResult(result, { attachImages = false } = {}) {
|
|
81
|
+
const contents = result && typeof result === "object" && Array.isArray(/** @type any */ (result).contents) ? /** @type any */ (result).contents : null;
|
|
82
|
+
if (!contents) return { ok: false, error: "The MCP server returned a malformed resource." };
|
|
83
|
+
const state = { attachImages, images: /** @type {ImageAttachment[]} */ ([]), bytes: 0 };
|
|
84
|
+
const parts = contents.map((/** @type any */ c) => {
|
|
85
|
+
if (!c || typeof c !== "object") return "";
|
|
86
|
+
if (typeof c.text === "string") return `[resource ${c.uri ?? "(no uri)"}${c.mimeType ? `, ${c.mimeType}` : ""}]\n${c.text}`;
|
|
87
|
+
if (typeof c.mimeType === "string" && c.mimeType.toLowerCase().startsWith("image/")) return `[resource ${c.uri ?? "(no uri)"}] ${imageNote(c.mimeType, c.blob, state)}`;
|
|
88
|
+
return `[resource ${c.uri ?? "(no uri)"}: ${c.mimeType || "binary"}, ${approxBytes(c.blob)} bytes — not shown]`;
|
|
89
|
+
});
|
|
90
|
+
const text = parts.filter((t) => t !== "").join("\n\n");
|
|
91
|
+
const out = /** @type {{ok:true, content:string, images?:ImageAttachment[]}} */ ({ ok: true, content: text === "" ? "(empty resource)" : text });
|
|
92
|
+
if (state.images.length > 0) out.images = state.images;
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @param {any} b @param {{attachImages:boolean, images:ImageAttachment[], bytes:number}} state */
|
|
97
|
+
function blockText(b, state) {
|
|
98
|
+
if (!b || typeof b !== "object") return "";
|
|
99
|
+
switch (b.type) {
|
|
100
|
+
case "text":
|
|
101
|
+
return typeof b.text === "string" ? b.text : "";
|
|
102
|
+
case "image":
|
|
103
|
+
return imageNote(b.mimeType, b.data, state);
|
|
104
|
+
case "audio":
|
|
105
|
+
return `[audio content: ${b.mimeType || "unknown type"}, ${approxBytes(b.data)} bytes — not shown]`;
|
|
106
|
+
case "resource": {
|
|
107
|
+
const res = b.resource || {};
|
|
108
|
+
if (typeof res.text === "string") return res.uri ? `[resource ${res.uri}]\n${res.text}` : res.text;
|
|
109
|
+
if (state.attachImages && typeof res.mimeType === "string" && res.mimeType.toLowerCase().startsWith("image/")) return `[resource ${res.uri || "(no uri)"}] ${imageNote(res.mimeType, res.blob, state)}`;
|
|
110
|
+
return `[resource ${res.uri || "(no uri)"}: ${res.mimeType || "binary"}, ${approxBytes(res.blob)} bytes — not shown]`;
|
|
111
|
+
}
|
|
112
|
+
case "resource_link":
|
|
113
|
+
return `[resource link: ${b.uri || "(no uri)"}${b.name ? ` (${b.name})` : ""}]`;
|
|
114
|
+
default:
|
|
115
|
+
return `[unsupported MCP content block "${String(b.type)}"]`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** @param {unknown} base64 */
|
|
120
|
+
export function approxBytes(base64) {
|
|
121
|
+
if (typeof base64 !== "string") return 0;
|
|
122
|
+
const pad = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
|
|
123
|
+
return Math.max(0, Math.floor((base64.length * 3) / 4) - pad);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @param {unknown} v */
|
|
127
|
+
function safeJson(v) {
|
|
128
|
+
try {
|
|
129
|
+
return JSON.stringify(v, null, 2);
|
|
130
|
+
} catch {
|
|
131
|
+
return String(v);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/mcp/stdio.mjs — the MCP stdio transport.
|
|
3
|
+
*
|
|
4
|
+
* Per the MCP specification: the client launches the server as a subprocess;
|
|
5
|
+
* messages are JSON-RPC objects, one per line (UTF-8, newline-delimited, no
|
|
6
|
+
* embedded newlines) on the child's stdin/stdout; the child's stderr is log
|
|
7
|
+
* output and is never parsed. Shutdown closes the child's stdin, then sends
|
|
8
|
+
* SIGTERM, then SIGKILL, each after a grace period.
|
|
9
|
+
*
|
|
10
|
+
* The child's stderr is kept as a short tail so a crash can be reported with
|
|
11
|
+
* the server's own last words.
|
|
12
|
+
*
|
|
13
|
+
* @module lib/engine/mcp/stdio
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
export const STDERR_TAIL_CHARS = 2_000;
|
|
19
|
+
export const SHUTDOWN_GRACE_MS = 1_500;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} p
|
|
23
|
+
* @param {string} p.command
|
|
24
|
+
* @param {string[]} [p.args]
|
|
25
|
+
* @param {Record<string,string|undefined>} [p.env] the full child environment
|
|
26
|
+
* @param {string} [p.cwd]
|
|
27
|
+
* @param {typeof spawn} [p.spawnImpl]
|
|
28
|
+
*/
|
|
29
|
+
export function createStdioTransport({ command, args = [], env, cwd, spawnImpl = spawn }) {
|
|
30
|
+
/** @type {import('node:child_process').ChildProcessWithoutNullStreams|null} */
|
|
31
|
+
let child = null;
|
|
32
|
+
let stderrTail = "";
|
|
33
|
+
/** @type {string|null} */
|
|
34
|
+
let exitReason = null;
|
|
35
|
+
/** @type {Promise<void>|null} */
|
|
36
|
+
let exited = null;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
/**
|
|
40
|
+
* @param {(msg:unknown) => void} onMessage
|
|
41
|
+
* @param {(reason:string) => void} onClose
|
|
42
|
+
* @returns {{ok:true}|{ok:false, error:string}}
|
|
43
|
+
*/
|
|
44
|
+
start(onMessage, onClose) {
|
|
45
|
+
try {
|
|
46
|
+
child = /** @type any */ (spawnImpl(command, args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] }));
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return { ok: false, error: `could not start "${command}": ${e instanceof Error ? e.message : e}` };
|
|
49
|
+
}
|
|
50
|
+
const c = /** @type {import('node:child_process').ChildProcessWithoutNullStreams} */ (child);
|
|
51
|
+
let buffer = "";
|
|
52
|
+
c.stdout.setEncoding("utf8");
|
|
53
|
+
c.stdout.on("data", (chunk) => {
|
|
54
|
+
buffer += chunk;
|
|
55
|
+
let nl;
|
|
56
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
57
|
+
const line = buffer.slice(0, nl).trim();
|
|
58
|
+
buffer = buffer.slice(nl + 1);
|
|
59
|
+
if (line === "") continue;
|
|
60
|
+
let msg;
|
|
61
|
+
try {
|
|
62
|
+
msg = JSON.parse(line);
|
|
63
|
+
} catch {
|
|
64
|
+
continue; // a server that logs to stdout: not a frame, not fatal
|
|
65
|
+
}
|
|
66
|
+
onMessage(msg);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
c.stderr.setEncoding("utf8");
|
|
70
|
+
c.stderr.on("data", (chunk) => {
|
|
71
|
+
stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_CHARS);
|
|
72
|
+
});
|
|
73
|
+
c.stdin.on("error", () => {
|
|
74
|
+
/* EPIPE after the child died; the exit handler reports it */
|
|
75
|
+
});
|
|
76
|
+
exited = new Promise((resolve) => {
|
|
77
|
+
const done = (/** @type string */ reason) => {
|
|
78
|
+
if (exitReason !== null) return;
|
|
79
|
+
exitReason = reason;
|
|
80
|
+
onClose(reason);
|
|
81
|
+
resolve();
|
|
82
|
+
};
|
|
83
|
+
c.on("error", (e) => done(`could not start "${command}": ${e.message}`));
|
|
84
|
+
c.on("exit", (code, signal) => done(code !== null ? `exited with code ${code}` : `killed by ${signal}`));
|
|
85
|
+
});
|
|
86
|
+
return { ok: true };
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/** @param {object} msg */
|
|
90
|
+
send(msg) {
|
|
91
|
+
if (!child || exitReason !== null) return { ok: false, error: `server process is not running (${exitReason ?? "not started"})` };
|
|
92
|
+
try {
|
|
93
|
+
child.stdin.write(`${JSON.stringify(msg)}\n`);
|
|
94
|
+
return { ok: true };
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return { ok: false, error: `write to server failed: ${e instanceof Error ? e.message : e}` };
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
stderrTail: () => stderrTail.trim(),
|
|
101
|
+
|
|
102
|
+
/** Close stdin, then SIGTERM, then SIGKILL. Resolves once the child is gone. */
|
|
103
|
+
async close() {
|
|
104
|
+
if (!child || exitReason !== null || !exited) return;
|
|
105
|
+
const c = child;
|
|
106
|
+
const wait = (/** @type number */ ms) => {
|
|
107
|
+
/** @type {ReturnType<typeof setTimeout>|undefined} */
|
|
108
|
+
let timer;
|
|
109
|
+
const grace = new Promise((r) => {
|
|
110
|
+
timer = setTimeout(r, ms);
|
|
111
|
+
});
|
|
112
|
+
return Promise.race([exited, grace]).then(() => {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
return exitReason !== null;
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
c.stdin.end();
|
|
119
|
+
} catch {
|
|
120
|
+
/* already closed */
|
|
121
|
+
}
|
|
122
|
+
if (await wait(SHUTDOWN_GRACE_MS)) return;
|
|
123
|
+
try {
|
|
124
|
+
c.kill("SIGTERM");
|
|
125
|
+
} catch {
|
|
126
|
+
/* already gone */
|
|
127
|
+
}
|
|
128
|
+
if (await wait(SHUTDOWN_GRACE_MS)) return;
|
|
129
|
+
try {
|
|
130
|
+
c.kill("SIGKILL");
|
|
131
|
+
} catch {
|
|
132
|
+
/* already gone */
|
|
133
|
+
}
|
|
134
|
+
await wait(SHUTDOWN_GRACE_MS);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/mcp/supervisor.mjs — a crashed stdio server gets one restart per run.
|
|
3
|
+
*
|
|
4
|
+
* A stdio MCP server that dies (a crash, an out-of-memory kill) takes its
|
|
5
|
+
* tools with it. The supervisor wraps a connection with the same interface;
|
|
6
|
+
* on the NEXT call after the server stopped, it starts the server again —
|
|
7
|
+
* once per run, per server — and makes the call on the new connection. The
|
|
8
|
+
* result says the server was restarted. A second death is reported like any
|
|
9
|
+
* crash, with a note that the restart was already used. HTTP servers are not
|
|
10
|
+
* restarted (their process is not ours; each request already reconnects).
|
|
11
|
+
*
|
|
12
|
+
* If the restarted server lists different tools, `toolsChanged` is set so the
|
|
13
|
+
* run re-lists them at its next turn boundary.
|
|
14
|
+
*
|
|
15
|
+
* @module lib/engine/mcp/supervisor
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { connectMcpServer } from "./client.mjs";
|
|
19
|
+
|
|
20
|
+
export const MAX_RESTARTS_PER_RUN = 1;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {import('./config.mjs').McpServerConfig} config
|
|
24
|
+
* @param {Parameters<typeof connectMcpServer>[1]} deps
|
|
25
|
+
* @param {import('./client.mjs').McpConnection} first
|
|
26
|
+
* @param {{connect?:typeof connectMcpServer, maxRestarts?:number}} [o]
|
|
27
|
+
*/
|
|
28
|
+
export function superviseConnection(config, deps, first, { connect = connectMcpServer, maxRestarts = MAX_RESTARTS_PER_RUN } = {}) {
|
|
29
|
+
let current = first;
|
|
30
|
+
let restarts = 0;
|
|
31
|
+
let closed = false;
|
|
32
|
+
let pendingRefresh = false;
|
|
33
|
+
/** @type {Promise<{ok:true, restarted:boolean, exhausted:boolean}|{ok:false, error:string}>|null} */
|
|
34
|
+
let restarting = null;
|
|
35
|
+
const toolNames = (/** @type any */ c) => JSON.stringify(c.tools.map((/** @type any */ t) => t.name));
|
|
36
|
+
|
|
37
|
+
/** @returns {Promise<{ok:true, restarted:boolean, exhausted:boolean}|{ok:false, error:string}>} */
|
|
38
|
+
const ensure = async () => {
|
|
39
|
+
if (closed || current.isAlive() || config.transport !== "stdio") return { ok: true, restarted: false, exhausted: false };
|
|
40
|
+
if (restarts >= maxRestarts) return { ok: true, restarted: false, exhausted: true };
|
|
41
|
+
if (!restarting) {
|
|
42
|
+
restarting = (async () => {
|
|
43
|
+
restarts++;
|
|
44
|
+
const before = toolNames(current);
|
|
45
|
+
const r = await connect(config, deps);
|
|
46
|
+
if (!r.ok) return { ok: /** @type {false} */ (false), error: `MCP server "${config.name}" had stopped, and restarting it failed: ${r.error}` };
|
|
47
|
+
await current.close().catch(() => {});
|
|
48
|
+
current = r.connection;
|
|
49
|
+
if (toolNames(current) !== before) pendingRefresh = true;
|
|
50
|
+
return { ok: /** @type {true} */ (true), restarted: true, exhausted: false };
|
|
51
|
+
})().finally(() => {
|
|
52
|
+
restarting = null;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return restarting;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const note = `(The MCP server "${config.name}" had stopped; it was restarted for this call.)`;
|
|
59
|
+
const exhaustedNote = ` It was already restarted once in this run and is not restarted again.`;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @template {{ok:boolean, content?:string, error?:string}} R
|
|
63
|
+
* @param {() => Promise<R>} op
|
|
64
|
+
* @returns {Promise<R|{ok:false, error:string}>}
|
|
65
|
+
*/
|
|
66
|
+
const withRestart = async (op) => {
|
|
67
|
+
const e = await ensure();
|
|
68
|
+
if (!e.ok) return { ok: false, error: e.error };
|
|
69
|
+
const r = await op();
|
|
70
|
+
if (e.restarted) return r.ok ? { ...r, content: `${r.content}\n\n${note}` } : { ...r, error: `${r.error}\n\n${note}` };
|
|
71
|
+
if (e.exhausted && !r.ok) return { ...r, error: `${r.error}${exhaustedNote}` };
|
|
72
|
+
return r;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
get name() {
|
|
77
|
+
return current.name;
|
|
78
|
+
},
|
|
79
|
+
get protocolVersion() {
|
|
80
|
+
return current.protocolVersion;
|
|
81
|
+
},
|
|
82
|
+
get serverInfo() {
|
|
83
|
+
return current.serverInfo;
|
|
84
|
+
},
|
|
85
|
+
get instructions() {
|
|
86
|
+
return current.instructions;
|
|
87
|
+
},
|
|
88
|
+
get capabilities() {
|
|
89
|
+
return current.capabilities;
|
|
90
|
+
},
|
|
91
|
+
get tools() {
|
|
92
|
+
return current.tools;
|
|
93
|
+
},
|
|
94
|
+
get toolsChanged() {
|
|
95
|
+
return pendingRefresh || current.toolsChanged;
|
|
96
|
+
},
|
|
97
|
+
get restarts() {
|
|
98
|
+
return restarts;
|
|
99
|
+
},
|
|
100
|
+
isAlive: () => current.isAlive(),
|
|
101
|
+
async refreshTools() {
|
|
102
|
+
pendingRefresh = false;
|
|
103
|
+
return current.refreshTools();
|
|
104
|
+
},
|
|
105
|
+
/** @param {string} tool @param {object} args @param {{signal?:AbortSignal, timeoutMs?:number}} [o] */
|
|
106
|
+
callTool: (tool, args, o) => withRestart(() => current.callTool(tool, args, o)),
|
|
107
|
+
/** @param {{signal?:AbortSignal}} [o] */
|
|
108
|
+
listResources: (o) => withRestart(() => current.listResources(o)),
|
|
109
|
+
/** @param {string} uri @param {{signal?:AbortSignal}} [o] */
|
|
110
|
+
readResource: (uri, o) => withRestart(() => current.readResource(uri, o)),
|
|
111
|
+
async close() {
|
|
112
|
+
closed = true;
|
|
113
|
+
await current.close();
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/messages.mjs — the engine's canonical conversation shape (pure).
|
|
3
|
+
*
|
|
4
|
+
* The loop, the session store and both wires speak ONE message shape, so a
|
|
5
|
+
* transcript written while talking OpenAI Chat can be resumed on Anthropic
|
|
6
|
+
* Messages and vice versa. It is deliberately block-structured (the richer of
|
|
7
|
+
* the two wire formats); the OpenAI wire flattens it on the way out.
|
|
8
|
+
*
|
|
9
|
+
* { role: 'user', content: Array<TextBlock | ToolResultBlock> }
|
|
10
|
+
* { role: 'assistant', content: Array<TextBlock | ToolUseBlock | ThinkingBlock> }
|
|
11
|
+
*
|
|
12
|
+
* TextBlock { type:'text', text }
|
|
13
|
+
* ToolUseBlock { type:'tool_use', id, name, input, inputError?, rawInput? }
|
|
14
|
+
* ToolResultBlock { type:'tool_result', tool_use_id, content: string, is_error? }
|
|
15
|
+
* ThinkingBlock { type:'thinking', thinking, signature? }
|
|
16
|
+
*
|
|
17
|
+
* `inputError` / `rawInput` mark a tool call whose argument JSON did not parse;
|
|
18
|
+
* the loop attempts a repair and the wires never send those two fields.
|
|
19
|
+
*
|
|
20
|
+
* @module lib/engine/messages
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** @param {string} text */
|
|
24
|
+
export function userText(text) {
|
|
25
|
+
return { role: "user", content: [{ type: "text", text: String(text) }] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Concatenate the text blocks of a message.
|
|
30
|
+
* @param {{content: any[]}|null|undefined} message
|
|
31
|
+
*/
|
|
32
|
+
export function messageText(message) {
|
|
33
|
+
if (!message || !Array.isArray(message.content)) return "";
|
|
34
|
+
return message.content
|
|
35
|
+
.filter((b) => b && b.type === "text" && typeof b.text === "string")
|
|
36
|
+
.map((b) => b.text)
|
|
37
|
+
.join("");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @param {{content:any[]}} message */
|
|
41
|
+
export function toolUses(message) {
|
|
42
|
+
return Array.isArray(message?.content) ? message.content.filter((b) => b && b.type === "tool_use") : [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Make a history safe to send:
|
|
47
|
+
* · consecutive same-role messages are merged (a resumed session whose last
|
|
48
|
+
* record is a user turn, followed by the new prompt, is one user turn);
|
|
49
|
+
* · an assistant `tool_use` with no matching `tool_result` in the next user
|
|
50
|
+
* turn (a run killed mid-tool) gets a synthetic error result, because both
|
|
51
|
+
* wires reject an unanswered tool call;
|
|
52
|
+
* · empty messages are dropped.
|
|
53
|
+
* Returns a new array; the input is not mutated.
|
|
54
|
+
*
|
|
55
|
+
* @param {Array<{role:string, content:any[]}>} messages
|
|
56
|
+
*/
|
|
57
|
+
export function normaliseHistory(messages) {
|
|
58
|
+
/** @type {Array<{role:string, content:any[]}>} */
|
|
59
|
+
const merged = [];
|
|
60
|
+
for (const m of messages || []) {
|
|
61
|
+
if (!m || (m.role !== "user" && m.role !== "assistant") || !Array.isArray(m.content)) continue;
|
|
62
|
+
if (m.content.length === 0) continue;
|
|
63
|
+
const last = merged[merged.length - 1];
|
|
64
|
+
if (last && last.role === m.role) last.content = [...last.content, ...m.content];
|
|
65
|
+
else merged.push({ role: m.role, content: [...m.content] });
|
|
66
|
+
}
|
|
67
|
+
for (let i = 0; i < merged.length; i++) {
|
|
68
|
+
const m = merged[i];
|
|
69
|
+
if (m.role !== "assistant") continue;
|
|
70
|
+
const uses = toolUses(m);
|
|
71
|
+
if (uses.length === 0) continue;
|
|
72
|
+
let next = merged[i + 1];
|
|
73
|
+
if (!next || next.role !== "user") {
|
|
74
|
+
next = { role: "user", content: [] };
|
|
75
|
+
merged.splice(i + 1, 0, next);
|
|
76
|
+
}
|
|
77
|
+
const answered = new Set(next.content.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id));
|
|
78
|
+
const missing = uses
|
|
79
|
+
.filter((u) => !answered.has(u.id))
|
|
80
|
+
.map((u) => ({
|
|
81
|
+
type: "tool_result",
|
|
82
|
+
tool_use_id: u.id,
|
|
83
|
+
content: "The run was interrupted before this tool finished; no result was recorded.",
|
|
84
|
+
is_error: true,
|
|
85
|
+
}));
|
|
86
|
+
// Tool results lead the user turn: the OpenAI wire requires tool messages
|
|
87
|
+
// to follow the assistant message directly.
|
|
88
|
+
if (missing.length > 0) next.content = [...missing, ...next.content];
|
|
89
|
+
}
|
|
90
|
+
return merged;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Deterministic JSON (sorted keys) — used for loop-detection signatures, where
|
|
95
|
+
* `{a,b}` and `{b,a}` are the same call.
|
|
96
|
+
* @param {unknown} v
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
export function stableStringify(v) {
|
|
100
|
+
if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "null";
|
|
101
|
+
if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`;
|
|
102
|
+
const keys = Object.keys(v).sort();
|
|
103
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(/** @type any */ (v)[k])}`).join(",")}}`;
|
|
104
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/engine/output/json.mjs — the headless result object (pure).
|
|
3
|
+
*
|
|
4
|
+
* maestro's dispatcher, responder and cost ledger already parse the JSON the
|
|
5
|
+
* `claude --print --output-format json` lane emits. The engine's result is a
|
|
6
|
+
* SUPERSET of that shape, so those readers work unchanged:
|
|
7
|
+
*
|
|
8
|
+
* type:'result', subtype, is_error, result, session_id, num_turns,
|
|
9
|
+
* duration_ms, duration_api_ms, total_cost_usd,
|
|
10
|
+
* usage{ input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens },
|
|
11
|
+
* permission_denials
|
|
12
|
+
*
|
|
13
|
+
* plus a `cohort` block with what only the gateway knows: request ids, the
|
|
14
|
+
* tier that served, integer micro-USD cost, quota state, the normalised usage
|
|
15
|
+
* record (design §4.5), and why the run stopped.
|
|
16
|
+
*
|
|
17
|
+
* `total_cost_usd` comes from the gateway's `x-cohort-cost-micros` and nothing
|
|
18
|
+
* else. If ANY request in the run lacked the header the figure is `null` —
|
|
19
|
+
* a partial sum would under-report, and a local price guess is exactly the
|
|
20
|
+
* pricer bug the design removes (C7). `cohort.costMicros` keeps the exact
|
|
21
|
+
* integer as a string; the float exists only at this display boundary.
|
|
22
|
+
*
|
|
23
|
+
* @module lib/engine/output/json
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { toResultUsage, emptyUsage } from "../wire/usage.mjs";
|
|
27
|
+
import { refusalFromWireError } from "../wire/errors.mjs";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {import('../loop.mjs').LoopStop} stop
|
|
31
|
+
* @returns {{ subtype: 'success'|'error_max_turns'|'error_max_budget_usd'|'error_during_execution', isError: boolean, message: string|null }}
|
|
32
|
+
*/
|
|
33
|
+
export function classifyStop(stop) {
|
|
34
|
+
switch (stop.kind) {
|
|
35
|
+
case "completed":
|
|
36
|
+
return { subtype: "success", isError: false, message: null };
|
|
37
|
+
case "max_turns":
|
|
38
|
+
return { subtype: "error_max_turns", isError: true, message: `Reached the maximum number of turns (${stop.maxTurns}).` };
|
|
39
|
+
case "wall_clock":
|
|
40
|
+
return { subtype: "error_during_execution", isError: true, message: `Stopped after the ${stop.wallClockMs} ms time limit.` };
|
|
41
|
+
case "aborted":
|
|
42
|
+
return { subtype: "error_during_execution", isError: true, message: "The run was aborted." };
|
|
43
|
+
case "wire_error":
|
|
44
|
+
return {
|
|
45
|
+
subtype: "error_during_execution",
|
|
46
|
+
isError: true,
|
|
47
|
+
message: `Model request failed (${stop.error?.code ?? "unknown"}): ${stop.error?.message ?? ""}`.trim(),
|
|
48
|
+
};
|
|
49
|
+
case "loop_detected":
|
|
50
|
+
return {
|
|
51
|
+
subtype: "error_during_execution",
|
|
52
|
+
isError: true,
|
|
53
|
+
message: `Stopped: ${stop.tool} was called with identical input ${stop.repeats} turns in a row.`,
|
|
54
|
+
};
|
|
55
|
+
case "hook_stopped":
|
|
56
|
+
return { subtype: "error_during_execution", isError: true, message: `Stopped by a hook: ${stop.reason}` };
|
|
57
|
+
case "max_budget":
|
|
58
|
+
// W4-E1 (row 24): --max-budget-usd reached; no further model call was made.
|
|
59
|
+
return {
|
|
60
|
+
subtype: "error_max_budget_usd",
|
|
61
|
+
isError: true,
|
|
62
|
+
message: `Reached the maximum budget ($${stop.maxBudgetUsd}); spent $${Number(BigInt(stop.spentMicros)) / 1e6}.`,
|
|
63
|
+
};
|
|
64
|
+
default:
|
|
65
|
+
throw new Error(`classifyStop: unhandled stop kind ${JSON.stringify(/** @type any */ (stop).kind)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @param {string|null} micros */
|
|
70
|
+
export function microsToUsd(micros) {
|
|
71
|
+
if (micros == null) return null;
|
|
72
|
+
return Number(BigInt(micros)) / 1e6;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {object} p
|
|
77
|
+
* @param {Awaited<ReturnType<typeof import('../loop.mjs').runLoop>>} p.outcome
|
|
78
|
+
* @param {string} p.sessionId
|
|
79
|
+
* @param {number} p.durationMs
|
|
80
|
+
* @param {string} p.wire
|
|
81
|
+
* @param {{ path:string, writeFailures:number, skipped:number }|null} [p.session]
|
|
82
|
+
*/
|
|
83
|
+
export function buildResult({ outcome, sessionId, durationMs, wire, session = null }) {
|
|
84
|
+
const c = classifyStop(outcome.stop);
|
|
85
|
+
return {
|
|
86
|
+
type: "result",
|
|
87
|
+
subtype: c.subtype,
|
|
88
|
+
is_error: c.isError,
|
|
89
|
+
result: c.isError ? c.message : outcome.resultText,
|
|
90
|
+
session_id: sessionId,
|
|
91
|
+
num_turns: outcome.numTurns,
|
|
92
|
+
duration_ms: durationMs,
|
|
93
|
+
duration_api_ms: outcome.apiMs,
|
|
94
|
+
total_cost_usd: microsToUsd(outcome.costMicros),
|
|
95
|
+
usage: toResultUsage(outcome.usage),
|
|
96
|
+
permission_denials: [],
|
|
97
|
+
cohort: {
|
|
98
|
+
wire,
|
|
99
|
+
model: outcome.model,
|
|
100
|
+
modelTier: outcome.modelTier,
|
|
101
|
+
requestIds: outcome.requestIds,
|
|
102
|
+
costMicros: outcome.costMicros,
|
|
103
|
+
quota: outcome.quota,
|
|
104
|
+
usage: outcome.usage,
|
|
105
|
+
// CF-158: the gateway's own view of the same turns, off the terminal
|
|
106
|
+
// cohort frame — a DISTINCT key from `usage` above, which is the engine's
|
|
107
|
+
// accumulated count from the wire. The two are near-equal but not the
|
|
108
|
+
// same number, and naming one after the other is exactly the silent
|
|
109
|
+
// divergence CF-158 exists to stop. null when no frame reported usage.
|
|
110
|
+
//
|
|
111
|
+
// SCOPE, which differs from `usage` above: this is the PARENT loop's own
|
|
112
|
+
// requests. cli.mjs replaces `usage` with the parent+children aggregate
|
|
113
|
+
// when a run has subagents (aggregateUsage), and child frame usage is not
|
|
114
|
+
// threaded through that record — it is counted run-wide only by the
|
|
115
|
+
// shared budget meter, as `cohort.budget.observedTokens`. Two adjacent
|
|
116
|
+
// keys with different scopes is worth one comment; silently differing
|
|
117
|
+
// scopes is the same trap as silently differing numbers.
|
|
118
|
+
gatewayUsage: outcome.gatewayUsage ?? null,
|
|
119
|
+
stop: outcome.stop.kind === "wire_error" ? { kind: "wire_error" } : outcome.stop,
|
|
120
|
+
error: outcome.stop.kind === "wire_error" ? outcome.stop.error : null,
|
|
121
|
+
// A run that ended on a gateway refusal carries it in the §4.8 shape;
|
|
122
|
+
// maestro's rate-guard classifyCohortRefusal reads exactly this.
|
|
123
|
+
refusal: outcome.stop.kind === "wire_error" ? refusalFromWireError(outcome.stop.error) : null,
|
|
124
|
+
lastText: c.isError ? outcome.resultText : undefined,
|
|
125
|
+
session,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A result for a run that failed before the loop started (bad flags, no token,
|
|
132
|
+
* unknown session). Same shape, so a caller never special-cases it.
|
|
133
|
+
* @param {{ sessionId:string|null, code:string, message:string, durationMs:number, wire?:string|null }} p
|
|
134
|
+
*/
|
|
135
|
+
export function buildSetupErrorResult({ sessionId, code, message, durationMs, wire = null }) {
|
|
136
|
+
return {
|
|
137
|
+
type: "result",
|
|
138
|
+
subtype: "error_during_execution",
|
|
139
|
+
is_error: true,
|
|
140
|
+
result: message,
|
|
141
|
+
session_id: sessionId,
|
|
142
|
+
num_turns: 0,
|
|
143
|
+
duration_ms: durationMs,
|
|
144
|
+
duration_api_ms: 0,
|
|
145
|
+
total_cost_usd: null,
|
|
146
|
+
usage: toResultUsage(emptyUsage()),
|
|
147
|
+
permission_denials: [],
|
|
148
|
+
cohort: {
|
|
149
|
+
wire,
|
|
150
|
+
model: null,
|
|
151
|
+
modelTier: null,
|
|
152
|
+
requestIds: [],
|
|
153
|
+
costMicros: null,
|
|
154
|
+
quota: null,
|
|
155
|
+
usage: emptyUsage("estimated"),
|
|
156
|
+
// No request was made, so the gateway reported nothing (CF-158).
|
|
157
|
+
gatewayUsage: null,
|
|
158
|
+
stop: { kind: "setup_error" },
|
|
159
|
+
error: { kind: "setup", code, message },
|
|
160
|
+
refusal: null,
|
|
161
|
+
session: null,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|