@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
|
@@ -14,7 +14,16 @@ const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.ur
|
|
|
14
14
|
// --verify` reads `sdk_version` off the dashboard to tell the two apart. The
|
|
15
15
|
// order of trust (sdk-version.mjs): the manifest of the copied tree, then
|
|
16
16
|
// node_modules, then the package this file ships in.
|
|
17
|
-
|
|
17
|
+
//
|
|
18
|
+
// The dashboard carries the INSTALLED version and the verdict beside it,
|
|
19
|
+
// because the number on its own cannot be checked. A seat reported 2.18.0 to
|
|
20
|
+
// the org off its manifest while node_modules already held 2.18.1: both
|
|
21
|
+
// numbers true, the disagreement the only interesting fact, and nothing on the
|
|
22
|
+
// seat or in the org said it. `sdk_version_agrees` is a real tri-state —
|
|
23
|
+
// `unknown` when the running version was INFERRED from the installed one, so
|
|
24
|
+
// equality by fallback can never be read as a clean hop.
|
|
25
|
+
const SDK = resolveRunningSdkVersion({ agentDir: AGENT_REPO_DIR, ownPackagePath: join(new URL(".", import.meta.url).pathname, "../../package.json") });
|
|
26
|
+
const DAEMON_SDK_VERSION = SDK.version;
|
|
18
27
|
|
|
19
28
|
let stats = {
|
|
20
29
|
daemon_start: new Date().toISOString(),
|
|
@@ -58,6 +67,10 @@ export function writeHealthDashboard() {
|
|
|
58
67
|
uptime_s: Math.floor((Date.now() - new Date(stats.daemon_start).getTime()) / 1000),
|
|
59
68
|
pid: process.pid,
|
|
60
69
|
sdk_version: DAEMON_SDK_VERSION,
|
|
70
|
+
sdk_version_source: SDK.runningSource,
|
|
71
|
+
sdk_version_installed: SDK.installed,
|
|
72
|
+
sdk_version_agrees: SDK.agrees === null ? "unknown" : String(SDK.agrees),
|
|
73
|
+
sdk_version_drift: SDK.drift,
|
|
61
74
|
...stats,
|
|
62
75
|
dispatcher,
|
|
63
76
|
};
|
|
@@ -39,6 +39,17 @@ process.env.MAESTRO_DAEMON = "1";
|
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
40
40
|
// 0. Environment — BEFORE anything reads a credential
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
42
|
+
// But first: the BOOT MARKER. The wrapper appends this process's stdout to
|
|
43
|
+
// logs/daemon/daemon-<start day>.log, which carries no per-line timestamps and
|
|
44
|
+
// is SHARED by every incarnation that starts on the same day — a crash at
|
|
45
|
+
// 09:00 and the healthy respawn at 09:01 write to the same file. This line is
|
|
46
|
+
// the first thing this incarnation prints, tagged with its pid, so a reader
|
|
47
|
+
// can split the file into "before this process" and "since this process":
|
|
48
|
+
// scripts/local-triggers/autoupdate.sh's health gate scans for fatal lines
|
|
49
|
+
// only AFTER `[DAEMON] boot pid=<its pid>`; without it a one-off crash earlier
|
|
50
|
+
// the same day read as "fatal since restart" every hour until midnight. The
|
|
51
|
+
// format is pinned by autoupdate.test.mjs — change both or neither.
|
|
52
|
+
console.log(`[DAEMON] boot pid=${process.pid} at=${new Date().toISOString()}`);
|
|
42
53
|
// The core daemon (agent-daemon.mjs, step 4) loads .env itself, but the org
|
|
43
54
|
// mesh connects at step 3b' — EARLIER in this file. Under launchd the process
|
|
44
55
|
// environment is bare, so the mesh was reading COHORT_API_KEY before anything
|
|
@@ -14,6 +14,11 @@ import { recall as orgRecall } from "../../lib/org/knowledge.mjs";
|
|
|
14
14
|
import { outcomeSourceShareable } from "./session-outcomes.mjs";
|
|
15
15
|
import { withParallelism } from "../../lib/prompts/parallelism.mjs";
|
|
16
16
|
import { isPrivateConversation, historyDirNames } from "../../lib/context/history-scope.mjs";
|
|
17
|
+
// A human's roll-call to the whole room: the SAME reply-shaping block the
|
|
18
|
+
// quick-reply responder injects, so a roll-call that fell through to a session
|
|
19
|
+
// (quick reply failed transiently) is answered in the same shape.
|
|
20
|
+
import { isRollCallItem, collectiveInstructions } from "../../lib/org/inbound/broadcast.mjs";
|
|
21
|
+
import { itemCollectiveIntent } from "../../lib/org/inbound/collective.mjs";
|
|
17
22
|
|
|
18
23
|
const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.url).pathname, "../..");
|
|
19
24
|
|
|
@@ -1068,6 +1073,25 @@ export async function buildPrompt(item, classResult, options = {}) {
|
|
|
1068
1073
|
parts.push("");
|
|
1069
1074
|
}
|
|
1070
1075
|
|
|
1076
|
+
// 1d. COLLECTIVE shaping — a human addressed the whole room and every seat
|
|
1077
|
+
// answers for itself. Placed with the contact blocks above because it is the
|
|
1078
|
+
// same kind of instruction: what may and may not be SAID, before any work.
|
|
1079
|
+
//
|
|
1080
|
+
// The intent (roll-call / work / ambient) comes from `collective.mjs`, the
|
|
1081
|
+
// twin of hq's `work/collective.ts`, so the same message gets the same block
|
|
1082
|
+
// whether the seat is driven from here or from hq. A WORK broadcast is told
|
|
1083
|
+
// its board row already exists — otherwise the session's first instinct is to
|
|
1084
|
+
// open a second one.
|
|
1085
|
+
if (type === "inbox" && item && isRollCallItem(item)) {
|
|
1086
|
+
parts.push(
|
|
1087
|
+
collectiveInstructions({
|
|
1088
|
+
intent: itemCollectiveIntent(item, isRollCallItem),
|
|
1089
|
+
respondents: item.respondents,
|
|
1090
|
+
}),
|
|
1091
|
+
);
|
|
1092
|
+
parts.push("");
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1071
1095
|
// 2. Session context
|
|
1072
1096
|
parts.push(`Date: ${date}`);
|
|
1073
1097
|
parts.push(`Classification: priority=${classResult.priority}, action=${action}, category=${classResult.category || "unclassified"}`);
|
|
@@ -72,11 +72,21 @@ import { generateAck } from "./assurance.mjs";
|
|
|
72
72
|
// This path never renders the persona block, which is why the doctrine is a
|
|
73
73
|
// separately-exported constant rather than part of voiceRules().
|
|
74
74
|
import { MESSAGE_CRAFT } from "../../lib/identity/persona.mjs";
|
|
75
|
+
// A human's roll-call to the whole room. The reply-shaping block (≤ 5 lines,
|
|
76
|
+
// about myself only, no @mentions, no ack) lives with the vocabulary in
|
|
77
|
+
// broadcast.mjs so the session path (prompt-builder) says the same thing this
|
|
78
|
+
// path does. What the seat is DOING is not read from here: the one renderer of
|
|
79
|
+
// "what am I doing" is lib/session/current-work (loadCurrentWork below), and
|
|
80
|
+
// responder-single-source.test.mjs pins that no second one is imported again.
|
|
81
|
+
import { isRollCallItem, collectiveInstructions } from "../../lib/org/inbound/broadcast.mjs";
|
|
82
|
+
import { itemCollectiveIntent } from "../../lib/org/inbound/collective.mjs";
|
|
75
83
|
|
|
76
84
|
const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.url).pathname, "../..");
|
|
77
85
|
const SONNET_MODEL = "claude-sonnet-4-6";
|
|
78
86
|
// Resolve claude against the agent's PATH (not launchd's bare env).
|
|
79
|
-
import { resolveClaudeBin
|
|
87
|
+
import { resolveClaudeBin } from "../../lib/claude-bin.mjs";
|
|
88
|
+
import { buildSpawn } from "../../lib/runtime/adapter.mjs";
|
|
89
|
+
import { resolveSeatSpawn } from "../../lib/runtime/seat-engine.mjs";
|
|
80
90
|
import { isPrivateConversation, interactionWriteDirNames } from "../../lib/context/history-scope.mjs";
|
|
81
91
|
const CLAUDE_BIN = resolveClaudeBin();
|
|
82
92
|
const CLAUDE_CLI_TIMEOUT_MS = 60_000;
|
|
@@ -144,6 +154,82 @@ export function unmeasuredReasonFor({ parsed, timedOut, exitCode, parseError, ti
|
|
|
144
154
|
return "no-json-envelope";
|
|
145
155
|
}
|
|
146
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Build the value `runClaudeCLI` resolves with, from a clean-exit close.
|
|
159
|
+
*
|
|
160
|
+
* With a parsed JSON envelope the reply text is `result`. Without one the
|
|
161
|
+
* call FAILS CLOSED: `text` is "" and `error` names why. The raw stdout is
|
|
162
|
+
* never surfaced as text — it is a whole model turn, not a reply, and the
|
|
163
|
+
* caller's empty-text guard turns it into "nothing to send" rather than a
|
|
164
|
+
* message on the wire. `withheldChars` records how much was dropped so the
|
|
165
|
+
* operator can see the size of the turn that was kept off the wire.
|
|
166
|
+
*
|
|
167
|
+
* @param {{ parsed: object|null, rawStdout: string, exitCode: number|null }} a
|
|
168
|
+
* @returns {{ text: string, jsonResult: object|null, exitCode: number|null, error?: string, withheldChars?: number }}
|
|
169
|
+
*/
|
|
170
|
+
export function cliResultFor({ parsed, rawStdout, exitCode }) {
|
|
171
|
+
if (parsed) {
|
|
172
|
+
return { text: parsed.result ?? "", jsonResult: parsed, exitCode };
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
text: "",
|
|
176
|
+
jsonResult: null,
|
|
177
|
+
exitCode,
|
|
178
|
+
error: "claude CLI output was not JSON; raw stdout withheld from the wire",
|
|
179
|
+
withheldChars: (rawStdout || "").length,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The tracker argv for a finished responder spawn — PURE, so the ledger row a
|
|
185
|
+
* given envelope produces is a unit test (engine-envelope-parity.test.mjs).
|
|
186
|
+
* `args` is everything after the tracker's script path.
|
|
187
|
+
*
|
|
188
|
+
* @param {{json:object|null, model:string, durationMs:number, exitCode:number, unmeasuredReason:string|null}} o
|
|
189
|
+
* @returns {{args:string[], measured:boolean, unmeasured:string|null}}
|
|
190
|
+
*/
|
|
191
|
+
export function responderCostArgs({ json, model, durationMs, exitCode, unmeasuredReason }) {
|
|
192
|
+
const usage = json && typeof json.usage === "object" && json.usage ? json.usage : null;
|
|
193
|
+
const inputTokens = usage ? Number(usage.input_tokens) : NaN;
|
|
194
|
+
const outputTokens = usage ? Number(usage.output_tokens) : NaN;
|
|
195
|
+
const measured = Number.isFinite(inputTokens) && Number.isFinite(outputTokens);
|
|
196
|
+
|
|
197
|
+
// Map the CLI-resolved model id onto the tracker's coarse pricing class.
|
|
198
|
+
const resolved = json && typeof json.model === "string" ? json.model : model || "";
|
|
199
|
+
const modelClass = /opus/i.test(resolved) ? "opus" : /haiku/i.test(resolved) ? "haiku" : "sonnet";
|
|
200
|
+
|
|
201
|
+
const args = [
|
|
202
|
+
"record",
|
|
203
|
+
"--cadence", "responder",
|
|
204
|
+
"--source", "responder",
|
|
205
|
+
"--model", modelClass,
|
|
206
|
+
"--duration-ms", String(durationMs),
|
|
207
|
+
"--exit", String(exitCode),
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
let unmeasured = null;
|
|
211
|
+
if (measured) {
|
|
212
|
+
args.push("--input-tokens", String(inputTokens));
|
|
213
|
+
args.push("--output-tokens", String(outputTokens));
|
|
214
|
+
const cacheRead = Number(usage.cache_read_input_tokens);
|
|
215
|
+
if (Number.isFinite(cacheRead)) args.push("--cache-read-tokens", String(cacheRead));
|
|
216
|
+
const cacheWrite = Number(usage.cache_creation_input_tokens);
|
|
217
|
+
if (Number.isFinite(cacheWrite)) args.push("--cache-creation-tokens", String(cacheWrite));
|
|
218
|
+
// A NUMBER, not anything Number() will coerce: the Cohort Engine writes
|
|
219
|
+
// `total_cost_usd: null` when not every request reported gateway cost,
|
|
220
|
+
// and `Number(null)` is 0 — which passed the finite/non-negative check
|
|
221
|
+
// and priced the session as $0.00 authoritative. Absent is the honest
|
|
222
|
+
// flag: the tracker then prices from tokens or records the row unpriced.
|
|
223
|
+
// (dispatcher.mjs#parseUsageFromText carries the same fix.)
|
|
224
|
+
const totalCost = json.total_cost_usd;
|
|
225
|
+
if (typeof totalCost === "number" && Number.isFinite(totalCost) && totalCost >= 0) args.push("--total-cost-usd", String(totalCost));
|
|
226
|
+
} else {
|
|
227
|
+
unmeasured = unmeasuredReason || (json ? "no-usage-field" : "no-json-envelope");
|
|
228
|
+
args.push("--tokens-unknown", unmeasured);
|
|
229
|
+
}
|
|
230
|
+
return { args, measured, unmeasured };
|
|
231
|
+
}
|
|
232
|
+
|
|
147
233
|
/**
|
|
148
234
|
* Append a cost-ledger row for a responder session (source "responder").
|
|
149
235
|
*
|
|
@@ -176,40 +262,12 @@ function recordResponderCost({ json, model, durationMs, exitCode, unmeasuredReas
|
|
|
176
262
|
const trackerPath = join(AGENT_REPO_DIR, "scripts/cost/track-claude-usage.mjs");
|
|
177
263
|
if (!existsSync(trackerPath)) return;
|
|
178
264
|
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const measured = Number.isFinite(inputTokens) && Number.isFinite(outputTokens);
|
|
183
|
-
|
|
184
|
-
// Map the CLI-resolved model id onto the tracker's coarse pricing class.
|
|
185
|
-
const resolved = json && typeof json.model === "string" ? json.model : model || "";
|
|
186
|
-
const modelClass = /opus/i.test(resolved) ? "opus" : /haiku/i.test(resolved) ? "haiku" : "sonnet";
|
|
187
|
-
|
|
188
|
-
const args = [
|
|
189
|
-
trackerPath, "record",
|
|
190
|
-
"--cadence", "responder",
|
|
191
|
-
"--source", "responder",
|
|
192
|
-
"--model", modelClass,
|
|
193
|
-
"--duration-ms", String(durationMs),
|
|
194
|
-
"--exit", String(exitCode),
|
|
195
|
-
];
|
|
196
|
-
|
|
197
|
-
if (measured) {
|
|
198
|
-
args.push("--input-tokens", String(inputTokens));
|
|
199
|
-
args.push("--output-tokens", String(outputTokens));
|
|
200
|
-
const cacheRead = Number(usage.cache_read_input_tokens);
|
|
201
|
-
if (Number.isFinite(cacheRead)) args.push("--cache-read-tokens", String(cacheRead));
|
|
202
|
-
const cacheWrite = Number(usage.cache_creation_input_tokens);
|
|
203
|
-
if (Number.isFinite(cacheWrite)) args.push("--cache-creation-tokens", String(cacheWrite));
|
|
204
|
-
const totalCost = Number(json.total_cost_usd);
|
|
205
|
-
if (Number.isFinite(totalCost) && totalCost >= 0) args.push("--total-cost-usd", String(totalCost));
|
|
206
|
-
} else {
|
|
207
|
-
const reason = unmeasuredReason || (json ? "no-usage-field" : "no-json-envelope");
|
|
208
|
-
args.push("--tokens-unknown", reason);
|
|
209
|
-
console.warn(`[responder] session usage unmeasured (${reason}) — recorded as unknown, not zero`);
|
|
265
|
+
const { args, unmeasured } = responderCostArgs({ json, model, durationMs, exitCode, unmeasuredReason });
|
|
266
|
+
if (unmeasured) {
|
|
267
|
+
console.warn(`[responder] session usage unmeasured (${unmeasured}) — recorded as unknown, not zero`);
|
|
210
268
|
}
|
|
211
269
|
|
|
212
|
-
spawn(process.execPath, args, {
|
|
270
|
+
spawn(process.execPath, [trackerPath, ...args], {
|
|
213
271
|
stdio: "ignore",
|
|
214
272
|
env: { ...process.env, AGENT_ROOT: AGENT_REPO_DIR, AGENT_DIR: AGENT_REPO_DIR },
|
|
215
273
|
}).unref();
|
|
@@ -234,37 +292,56 @@ function recordResponderCost({ json, model, durationMs, exitCode, unmeasuredReas
|
|
|
234
292
|
// the same UUID across spawns is the resume mechanism).
|
|
235
293
|
//
|
|
236
294
|
// @returns {Promise<{ text: string, jsonResult: object|null, exitCode: number }>}
|
|
295
|
+
/**
|
|
296
|
+
* The responder lane's spawn — binary, argv, env — from the runtime adapter
|
|
297
|
+
* (lane "responder"): --print <permissions> <bare MCP> --model <m>
|
|
298
|
+
* --append-system-prompt <sys> --output-format json [--session-id <id>].
|
|
299
|
+
* --output-format json is requested unconditionally (it is only valid with
|
|
300
|
+
* --print, which is always passed): a reply without the usage envelope cannot
|
|
301
|
+
* be priced, and the parse keeps a raw-text fallback for older CLIs. Env is the
|
|
302
|
+
* stock posture — stale ANTHROPIC_API_KEY/AUTH_TOKEN stripped so the CLI rides
|
|
303
|
+
* the seat's subscription credential; PATH augmented.
|
|
304
|
+
*
|
|
305
|
+
* W4-E1 (CF-44): `seat` is the seat's engine resolved once for this spawn
|
|
306
|
+
* (lib/runtime/seat-engine.mjs). A claude seat adds no fields, so its spawn is
|
|
307
|
+
* unchanged; an engine-cohort seat runs `cli.mjs run`, continuing --session-id.
|
|
308
|
+
* @param {{systemPrompt:string, userPrompt:string, model:string, sessionId?:string|null, env?:object, bin?:string, seat?:{fields:object}}} o
|
|
309
|
+
*/
|
|
310
|
+
export function responderSpawn({ systemPrompt, userPrompt, model, sessionId = null, env = process.env, bin = CLAUDE_BIN, seat = { fields: {} } }) {
|
|
311
|
+
return buildSpawn({
|
|
312
|
+
lane: "responder",
|
|
313
|
+
bin,
|
|
314
|
+
model,
|
|
315
|
+
systemPrompt,
|
|
316
|
+
prompt: userPrompt,
|
|
317
|
+
sessionId,
|
|
318
|
+
permissions: sessionPermissionArgs({ source: "responder" }),
|
|
319
|
+
mcp: { source: "responder" },
|
|
320
|
+
env,
|
|
321
|
+
...seat.fields,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
237
325
|
function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
|
|
326
|
+
// W4-E1: the seat's engine, once per spawn (a claude seat reads config/agent.json only).
|
|
327
|
+
return resolveSeatSpawn(AGENT_REPO_DIR).then((seat) => runSeatCLI(seat, systemPrompt, userPrompt, model, opts));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function runSeatCLI(seat, systemPrompt, userPrompt, model, opts = {}) {
|
|
238
331
|
const { sessionId = null, router = null, routingKey = null } = opts;
|
|
239
332
|
const startedAt = Date.now();
|
|
240
333
|
|
|
241
334
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"--model", model,
|
|
247
|
-
"--append-system-prompt", systemPrompt,
|
|
248
|
-
// --output-format json is only valid in combination with --print (per b1
|
|
249
|
-
// report). We always pass --print above, so this is safe.
|
|
250
|
-
//
|
|
251
|
-
// This used to be requested ONLY when a sessionId was supplied, which
|
|
252
|
-
// meant every non-routed reply ran with no usage envelope and therefore
|
|
253
|
-
// no way to price it. Every reply this daemon sends costs real money;
|
|
254
|
-
// asking for the envelope unconditionally is what makes it measurable.
|
|
255
|
-
// The parse below already has a raw-text fallback for older CLIs.
|
|
256
|
-
"--output-format", "json",
|
|
257
|
-
];
|
|
258
|
-
if (sessionId) {
|
|
259
|
-
args.push("--session-id", sessionId);
|
|
335
|
+
const spawnSpec = responderSpawn({ systemPrompt, userPrompt, model, sessionId, seat });
|
|
336
|
+
if (!spawnSpec.ok) {
|
|
337
|
+
rejectPromise(new Error(`claude CLI unavailable: ${spawnSpec.error.message}`));
|
|
338
|
+
return;
|
|
260
339
|
}
|
|
340
|
+
const args = spawnSpec.argv;
|
|
261
341
|
|
|
262
|
-
const proc = spawn(
|
|
342
|
+
const proc = spawn(spawnSpec.bin, args, {
|
|
263
343
|
stdio: ["pipe", "pipe", "pipe"],
|
|
264
|
-
|
|
265
|
-
// stale ANTHROPIC_API_KEY/AUTH_TOKEN inherited from the daemon env.
|
|
266
|
-
// Augment PATH so the subsession finds homebrew/nvm tools.
|
|
267
|
-
env: { ...process.env, PATH: augmentedPath(), ANTHROPIC_API_KEY: "", ANTHROPIC_AUTH_TOKEN: "" },
|
|
344
|
+
env: spawnSpec.env,
|
|
268
345
|
});
|
|
269
346
|
|
|
270
347
|
let stdout = "";
|
|
@@ -327,11 +404,13 @@ function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
|
|
|
327
404
|
parsed = trimmed ? JSON.parse(trimmed) : null;
|
|
328
405
|
if (!parsed) parseError = new Error("no stdout");
|
|
329
406
|
} catch (err) {
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
407
|
+
// Fail closed: older CLIs or unexpected output shapes must not crash
|
|
408
|
+
// the daemon, but raw stdout of a failed parse is a whole model turn
|
|
409
|
+
// (tool chatter, thinking, the un-enveloped reply) and has reached the
|
|
410
|
+
// wire before. It is withheld; the caller sees empty text plus an
|
|
411
|
+
// error and treats it as nothing to send.
|
|
333
412
|
parseError = err;
|
|
334
|
-
console.
|
|
413
|
+
console.error(`[responder] claude CLI JSON parse failed (sessionId=${sessionId}): ${err.message} — raw stdout withheld from the wire (${trimmed.length} chars)`);
|
|
335
414
|
}
|
|
336
415
|
|
|
337
416
|
// Every reply this daemon sends is a paid Claude session — INCLUDING the
|
|
@@ -358,11 +437,7 @@ function runClaudeCLI(systemPrompt, userPrompt, model, opts = {}) {
|
|
|
358
437
|
return;
|
|
359
438
|
}
|
|
360
439
|
|
|
361
|
-
|
|
362
|
-
resolvePromise({ text: parsed.result ?? "", jsonResult: parsed, exitCode: code });
|
|
363
|
-
} else {
|
|
364
|
-
resolvePromise({ text: trimmed, jsonResult: null, exitCode: code });
|
|
365
|
-
}
|
|
440
|
+
resolvePromise(cliResultFor({ parsed, rawStdout: trimmed, exitCode: code }));
|
|
366
441
|
});
|
|
367
442
|
|
|
368
443
|
try {
|
|
@@ -659,6 +734,7 @@ export async function loadConversationHistory(item, deps = {}) {
|
|
|
659
734
|
// the real generator, so behaviour is unchanged. The public senders call
|
|
660
735
|
// generateResponse() through this slot.
|
|
661
736
|
let _generateResponse;
|
|
737
|
+
|
|
662
738
|
/**
|
|
663
739
|
* Test seam: replace the response generator. Pass a function
|
|
664
740
|
* `(item, classResult) => Promise<string>` to force reply text; pass nothing to
|
|
@@ -683,11 +759,99 @@ export function setGenerateResponseForTests(fn) {
|
|
|
683
759
|
* and the flag is deliberately not kept "just in case", because keeping it is
|
|
684
760
|
* how it comes back.
|
|
685
761
|
*/
|
|
686
|
-
|
|
762
|
+
/**
|
|
763
|
+
* The bounded "what am I doing right now" block for a quick reply.
|
|
764
|
+
*
|
|
765
|
+
* The quick path is the one that answers a DM or a channel question IN the
|
|
766
|
+
* turn, and it carried nothing about the agent's own sessions: a "what are you
|
|
767
|
+
* doing right now?" was answered from the conversation alone, i.e. from
|
|
768
|
+
* nothing. lib/session/current-work reads the front door, its peers, the
|
|
769
|
+
* daemon's live children (with the board item each is linked to), the board
|
|
770
|
+
* items in progress and — as existence only — any interactive session a
|
|
771
|
+
* person has open on the box. ≤ 8 lines; "" when there is nothing to say.
|
|
772
|
+
*
|
|
773
|
+
* SCOPED TO THE SENDER'S CONVERSATION. A live session's summary is a
|
|
774
|
+
* restatement of someone's message; it is disclosed here only when it came
|
|
775
|
+
* from this item's conversation, and what is disclosable passes the sender's
|
|
776
|
+
* information barrier — the context compiler's `forbidsTextFor`, the same
|
|
777
|
+
* predicate the spawn path applies to history. Fail-open on the block (no
|
|
778
|
+
* block, not a failed reply); fail-CLOSED on the barrier (no predicate → the
|
|
779
|
+
* audience carries none and only same-conversation work is shown, which is
|
|
780
|
+
* the narrower answer). Injectable for tests.
|
|
781
|
+
*
|
|
782
|
+
* ON A ROLL-CALL this is the whole account of the seat — the front door, its
|
|
783
|
+
* peers, the daemon's live children, the board rows in progress / blocked /
|
|
784
|
+
* queued, the open handoffs — read ONCE, through the same audience and
|
|
785
|
+
* barrier as any other reply (a room never hears a private ask restated). A
|
|
786
|
+
* roll-call asks for `explicitEmpty`: when nothing is known the block still
|
|
787
|
+
* carries the header and the one line that says so, because a roll-call
|
|
788
|
+
* composed from no block is composed from invention.
|
|
789
|
+
*
|
|
790
|
+
* THIS is the path where an absent front door means "the daemon is
|
|
791
|
+
* answering on its own" — so this caller, and only this one, supplies
|
|
792
|
+
* {@link MAIN_ABSENT_NOTE}. The renderer is shared with `maestro session
|
|
793
|
+
* status`, `session_status` and the spawned session's context, from which
|
|
794
|
+
* that sentence would be false, so it does not carry the copy itself.
|
|
795
|
+
*/
|
|
796
|
+
export const MAIN_ABSENT_NOTE = "this reply comes from the background responder";
|
|
797
|
+
|
|
798
|
+
export async function loadCurrentWork(item, deps = {}) {
|
|
799
|
+
const rollCall = isRollCallItem(item);
|
|
800
|
+
try {
|
|
801
|
+
const mod = await import("../../lib/session/current-work.mjs");
|
|
802
|
+
const impl = deps.currentWorkBlock || mod.currentWorkBlock;
|
|
803
|
+
let forbidsText;
|
|
804
|
+
try {
|
|
805
|
+
const ff = deps.forbidsTextFor || (await import("./context-compiler.mjs")).forbidsTextFor;
|
|
806
|
+
const slug = item && item.sender ? String(item.sender).replace(/\s+/g, "-").toLowerCase() : null;
|
|
807
|
+
forbidsText = ff(slug);
|
|
808
|
+
} catch {
|
|
809
|
+
forbidsText = undefined;
|
|
810
|
+
}
|
|
811
|
+
const audience = mod.audienceForItem(item, { isPrivate: isPrivateConversation(item), forbidsText });
|
|
812
|
+
return (await impl(AGENT_REPO_DIR, { now: Date.now(), audience, explicitEmpty: rollCall, mainAbsentNote: MAIN_ABSENT_NOTE })) || "";
|
|
813
|
+
} catch {
|
|
814
|
+
return "";
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* The quick reply's user content. PURE — exported so the shape is pinned.
|
|
820
|
+
*
|
|
821
|
+
* The current-work block goes in under its own heading with the one rule the
|
|
822
|
+
* persona needs: describe it as your own work, and never quote a session name
|
|
823
|
+
* or id (they are internal — `session_status` says the same).
|
|
824
|
+
*/
|
|
825
|
+
export function buildQuickReplyUserContent({ item = {}, classResult = {}, conversationHistory = null, currentWork = "", rollCall = false } = {}) {
|
|
826
|
+
return [
|
|
827
|
+
`From: ${item.sender} (${item.sender_privilege || "unknown"})`,
|
|
828
|
+
`Via: ${item.service} / ${item.channel}`,
|
|
829
|
+
item.subject ? `Subject: ${item.subject}` : null,
|
|
830
|
+
conversationHistory ? `\nRecent conversation history:\n${conversationHistory}` : null,
|
|
831
|
+
currentWork
|
|
832
|
+
? `\n${currentWork}\n(If asked what you are doing, answer from the lines above — as your own work, in your own words, board item ids included where a person could look one up. Never quote session names or ids.)`
|
|
833
|
+
: null,
|
|
834
|
+
`\nCurrent message:\n${item.content || "(empty)"}`,
|
|
835
|
+
// A roll-call's thread context is the room's other answers — not evidence
|
|
836
|
+
// about this seat, and exactly what the reply must not summarise.
|
|
837
|
+
!rollCall && item.thread_context ? `\nThread context:\n${item.thread_context}` : null,
|
|
838
|
+
`\nClassification: ${classResult.summary}`,
|
|
839
|
+
].filter(Boolean).join("\n");
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* The real, CLI-backed answer generator. `deps` are test seams only —
|
|
844
|
+
* `{runCLI, currentWorkBlock, forbidsTextFor, loadConversationHistory}` — so
|
|
845
|
+
* the user content that reaches the model can be pinned without a spawn.
|
|
846
|
+
* Exported for that pin; production calls it through the `_generateResponse`
|
|
847
|
+
* slot with no deps.
|
|
848
|
+
*/
|
|
849
|
+
export async function realGenerateResponse(item, classResult, deps = {}) {
|
|
687
850
|
const preamble = loadPreamble();
|
|
688
851
|
const profile = loadUserProfile(item.sender);
|
|
689
852
|
// The classifier's recommended model (Opus for CEO, Sonnet for routine).
|
|
690
853
|
const model = classResult.model === "opus" ? "claude-opus-4-6" : SONNET_MODEL;
|
|
854
|
+
const rollCall = isRollCallItem(item);
|
|
691
855
|
|
|
692
856
|
const systemPrompt = `${preamble}
|
|
693
857
|
|
|
@@ -698,20 +862,20 @@ If it's informational, acknowledge appropriately.
|
|
|
698
862
|
Keep responses focused — 1-4 sentences for simple items, up to a short paragraph for more nuanced ones.
|
|
699
863
|
Match the sender's tone and urgency level.
|
|
700
864
|
${profile ? `\nSender profile:\n${profile}` : ""}
|
|
701
|
-
|
|
865
|
+
${rollCall ? `\n${collectiveInstructions({ intent: itemCollectiveIntent(item, isRollCallItem), respondents: item.respondents })}\n` : ""}
|
|
702
866
|
${MESSAGE_CRAFT}`;
|
|
703
867
|
|
|
704
|
-
|
|
868
|
+
// A roll-call is answered from what this seat KNOWS about itself — its main
|
|
869
|
+
// session, its peers and their purposes, its board items, the sessions the
|
|
870
|
+
// daemon has running — not from the room's history, which is other people's
|
|
871
|
+
// chatter and exactly what the answer must not summarise. ONE view of self:
|
|
872
|
+
// the audience-scoped current-work block (lib/session/current-work; the
|
|
873
|
+
// session registry, barrier-aware, so a room never hears a private ask
|
|
874
|
+
// restated), read once by loadCurrentWork on every path.
|
|
875
|
+
const conversationHistory = rollCall ? null : await (deps.loadConversationHistory || loadConversationHistory)(item);
|
|
876
|
+
const currentWork = await loadCurrentWork(item, deps);
|
|
705
877
|
|
|
706
|
-
const userContent =
|
|
707
|
-
`From: ${item.sender} (${item.sender_privilege || "unknown"})`,
|
|
708
|
-
`Via: ${item.service} / ${item.channel}`,
|
|
709
|
-
item.subject ? `Subject: ${item.subject}` : null,
|
|
710
|
-
conversationHistory ? `\nRecent conversation history:\n${conversationHistory}` : null,
|
|
711
|
-
`\nCurrent message:\n${item.content || "(empty)"}`,
|
|
712
|
-
item.thread_context ? `\nThread context:\n${item.thread_context}` : null,
|
|
713
|
-
`\nClassification: ${classResult.summary}`,
|
|
714
|
-
].filter(Boolean).join("\n");
|
|
878
|
+
const userContent = buildQuickReplyUserContent({ item, classResult, conversationHistory, currentWork, rollCall });
|
|
715
879
|
|
|
716
880
|
// (b2) Session-router decision. Compute the routing key from a daemon→router
|
|
717
881
|
// adapter view of the item. If the item can't be keyed (unknown service,
|
|
@@ -756,7 +920,7 @@ ${MESSAGE_CRAFT}`;
|
|
|
756
920
|
// and released in a `finally`: a throw anywhere in between must not leave the
|
|
757
921
|
// room's key claimed forever, which would silently disable continuity for it.
|
|
758
922
|
try {
|
|
759
|
-
const cliResult = await runClaudeCLI(systemPrompt, userContent, model, {
|
|
923
|
+
const cliResult = await (deps.runCLI || runClaudeCLI)(systemPrompt, userContent, model, {
|
|
760
924
|
sessionId,
|
|
761
925
|
router: key ? router : null,
|
|
762
926
|
routingKey: key,
|
|
@@ -764,7 +928,10 @@ ${MESSAGE_CRAFT}`;
|
|
|
764
928
|
|
|
765
929
|
const text = (cliResult.text || "").trim();
|
|
766
930
|
if (!text) {
|
|
767
|
-
|
|
931
|
+
// Empty text is "nothing to send" — including the fail-closed non-JSON
|
|
932
|
+
// path, where the raw model turn was withheld. Throwing here is what
|
|
933
|
+
// keeps it off the wire; the reason rides along for the log.
|
|
934
|
+
throw new Error(`claude CLI returned empty result text in generateResponse${cliResult.error ? ` (${cliResult.error}; ${cliResult.withheldChars ?? 0} chars withheld)` : ""}`);
|
|
768
935
|
}
|
|
769
936
|
|
|
770
937
|
// (b3) On success, touch the registry with the CLI-resolved session_id so
|
|
@@ -1,22 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* scripts/daemon/sdk-version.mjs — which SDK version is the code THIS daemon
|
|
3
|
-
* process is running?
|
|
3
|
+
* process is running, which one is INSTALLED beside it, and do they agree?
|
|
4
4
|
*
|
|
5
|
-
* The daemon runs from the agent dir's COPIED scripts/daemon
|
|
6
|
-
* node_modules. autoupdate installs the next version into
|
|
7
|
-
* and merges the files a minute or two later; a daemon that
|
|
8
|
-
* that window
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* The daemon runs from the agent dir's COPIED scripts/daemon (and ../../lib),
|
|
6
|
+
* not from node_modules. `autoupdate.sh` installs the next version into
|
|
7
|
+
* node_modules FIRST and merges the files a minute or two later; a daemon that
|
|
8
|
+
* (re)starts inside that window executes the old copies while node_modules
|
|
9
|
+
* already holds the new package. So the RUNNING version is the version of the
|
|
10
|
+
* COPIED tree, and the order of trust for it is:
|
|
11
11
|
*
|
|
12
12
|
* 1. .maestro/shipped-manifest.json `sdkVersion` — written by the upgrade
|
|
13
13
|
* that copied the files (2.12+), i.e. the version of scripts/daemon/*;
|
|
14
14
|
* 2. node_modules/@cohortapp/agent-sdk/package.json — a pre-2.12 manifest
|
|
15
|
-
* has no sdkVersion
|
|
15
|
+
* has no sdkVersion, so this is a fallback and NOT an observation of the
|
|
16
|
+
* copied tree (see `runningIsInferred` below);
|
|
16
17
|
* 3. the package this file itself ships in (a framework checkout).
|
|
17
18
|
*
|
|
19
|
+
* ── WHY IT NO LONGER RETURNS ONE STRING ──────────────────────────────────────
|
|
20
|
+
* It used to answer with `{version, source}` and nothing else, and every caller
|
|
21
|
+
* took `.version`. That is a true answer to "what am I executing" and a silent
|
|
22
|
+
* one about everything around it: this seat reported 2.18.0 to the org — read
|
|
23
|
+
* off a manifest written by the last `maestro upgrade` — while node_modules had
|
|
24
|
+
* already been moved to 2.18.1. Neither number was wrong. What was missing was
|
|
25
|
+
* that they DISAGREED, which is the only fact an operator watching a rollout
|
|
26
|
+
* actually wants, and the one the shape could not express.
|
|
27
|
+
*
|
|
28
|
+
* It matters twice over now: `autoupdate.sh`'s health gate compares the version
|
|
29
|
+
* a beat REPORTED against the version it INSTALLED, and refuses to call the hop
|
|
30
|
+
* healthy when they differ. A resolver that collapses the two teaches that gate
|
|
31
|
+
* to compare a number with itself.
|
|
32
|
+
*
|
|
33
|
+
* So the answer carries both, plus the verdict:
|
|
34
|
+
*
|
|
35
|
+
* running what the copied tree is, i.e. what is executing
|
|
36
|
+
* runningSource "manifest" | "node_modules" | "package" | "none"
|
|
37
|
+
* runningIsInferred true when `running` did NOT come from the manifest — the
|
|
38
|
+
* copied tree's version was not observed, only assumed
|
|
39
|
+
* installed node_modules/@cohortapp/agent-sdk, independently read
|
|
40
|
+
* agrees true | false | null (null = one of them is unknown, or
|
|
41
|
+
* `running` was inferred FROM `installed`, in which case
|
|
42
|
+
* equality is an artefact of the fallback and proves
|
|
43
|
+
* nothing — it must never read as agreement)
|
|
44
|
+
* drift a one-line human reason when `agrees === false`, else ""
|
|
45
|
+
*
|
|
46
|
+
* `version` and `source` stay on the object, unchanged, so existing callers
|
|
47
|
+
* keep working; `version` is `running`.
|
|
48
|
+
*
|
|
18
49
|
* Read once at daemon start, never again (the dashboard must say what the
|
|
19
|
-
* process IS, not what node_modules became meanwhile).
|
|
50
|
+
* process IS, not what node_modules became meanwhile). `installed` is read in
|
|
51
|
+
* the same breath for the same reason: a later read would compare this
|
|
52
|
+
* process's code against a node_modules that has moved on since, which is a
|
|
53
|
+
* different question and not this module's.
|
|
20
54
|
*
|
|
21
55
|
* @module scripts/daemon/sdk-version
|
|
22
56
|
*/
|
|
@@ -31,21 +65,69 @@ export const SDK_PACKAGE = "@cohortapp/agent-sdk";
|
|
|
31
65
|
/**
|
|
32
66
|
* Pure over an injected reader. `readJson(path)` returns the parsed file or
|
|
33
67
|
* throws / returns null; every source is fail-open.
|
|
68
|
+
*
|
|
34
69
|
* @param {{agentDir:string, ownPackagePath?:string, readJson?:(p:string)=>any}} o
|
|
35
|
-
* @returns {{version:string, source:"manifest"|"node_modules"|"package"|"none"
|
|
70
|
+
* @returns {{version:string, source:"manifest"|"node_modules"|"package"|"none",
|
|
71
|
+
* running:string, runningSource:"manifest"|"node_modules"|"package"|"none",
|
|
72
|
+
* runningIsInferred:boolean, installed:string,
|
|
73
|
+
* agrees:boolean|null, drift:string}}
|
|
36
74
|
*/
|
|
37
75
|
export function resolveRunningSdkVersion(o = {}) {
|
|
38
76
|
const readJson = o.readJson || ((p) => JSON.parse(readFileSync(p, "utf8")));
|
|
39
77
|
const tryRead = (p) => { try { return readJson(p); } catch { return null; /* absent/corrupt → next source */ } };
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
|
|
79
|
+
// INSTALLED — node_modules, read on its own terms and never skipped, because
|
|
80
|
+
// it is half of the comparison even when the manifest answers first.
|
|
42
81
|
const dep = tryRead(join(o.agentDir, "node_modules", ...SDK_PACKAGE.split("/"), "package.json"));
|
|
43
|
-
|
|
44
|
-
|
|
82
|
+
const installed = dep && dep.name === SDK_PACKAGE && typeof dep.version === "string" ? dep.version : "";
|
|
83
|
+
|
|
84
|
+
// RUNNING — the copied tree, in the documented order of trust.
|
|
85
|
+
let running = "";
|
|
86
|
+
let runningSource = "none";
|
|
87
|
+
const manifest = tryRead(join(o.agentDir, ".maestro", "shipped-manifest.json"));
|
|
88
|
+
if (manifest && typeof manifest.sdkVersion === "string" && manifest.sdkVersion) {
|
|
89
|
+
running = manifest.sdkVersion;
|
|
90
|
+
runningSource = "manifest";
|
|
91
|
+
} else if (installed) {
|
|
92
|
+
running = installed;
|
|
93
|
+
runningSource = "node_modules";
|
|
94
|
+
} else if (o.ownPackagePath) {
|
|
45
95
|
const own = tryRead(o.ownPackagePath);
|
|
46
|
-
if (own && own.name === SDK_PACKAGE && typeof own.version === "string")
|
|
96
|
+
if (own && own.name === SDK_PACKAGE && typeof own.version === "string") {
|
|
97
|
+
running = own.version;
|
|
98
|
+
runningSource = "package";
|
|
99
|
+
}
|
|
47
100
|
}
|
|
48
|
-
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
version: running,
|
|
104
|
+
source: runningSource,
|
|
105
|
+
running,
|
|
106
|
+
runningSource,
|
|
107
|
+
runningIsInferred: runningSource !== "manifest",
|
|
108
|
+
installed,
|
|
109
|
+
...verdict(running, runningSource, installed),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The comparison, separated so it is testable on its own and so the three
|
|
115
|
+
* answers are exhaustive in one place.
|
|
116
|
+
*
|
|
117
|
+
* `null` is not a weaker `true`. A run whose `running` was INFERRED from
|
|
118
|
+
* `installed` has not compared anything — the two strings are equal because one
|
|
119
|
+
* was copied from the other — and reporting that as agreement is precisely the
|
|
120
|
+
* substitution ("it is running because it is installed") that let a seat stay
|
|
121
|
+
* six days behind an hourly "up to date".
|
|
122
|
+
*/
|
|
123
|
+
function verdict(running, runningSource, installed) {
|
|
124
|
+
if (!running || !installed) return { agrees: null, drift: "" };
|
|
125
|
+
if (runningSource !== "manifest") return { agrees: null, drift: "" };
|
|
126
|
+
if (running === installed) return { agrees: true, drift: "" };
|
|
127
|
+
return {
|
|
128
|
+
agrees: false,
|
|
129
|
+
drift: `running ${running} (copied tree) but ${installed} is installed — an upgrade installed the package and has not merged the files yet, or a merge failed`,
|
|
130
|
+
};
|
|
49
131
|
}
|
|
50
132
|
|
|
51
133
|
export default { resolveRunningSdkVersion, SDK_PACKAGE };
|