@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,364 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pin-integrity.mjs — did the local fixes survive, and is the daemon running
|
|
4
|
+
* them, and how far behind the release has each pin drifted?
|
|
5
|
+
*
|
|
6
|
+
* A `.maestroignore` pin is FILE-granular: `maestro upgrade` never touches a
|
|
7
|
+
* pinned path, so the seat keeps its hand-patched copy — and silently misses
|
|
8
|
+
* every upstream line that path will ever receive. Nothing alarmed on that
|
|
9
|
+
* debt, so a pin taken for a two-line fix could sit hundreds of release lines
|
|
10
|
+
* behind with no signal. This script measures it and is meant to run as a
|
|
11
|
+
* doctor check: exit 0 is "no pin is behind past the threshold".
|
|
12
|
+
*
|
|
13
|
+
* For every path listed in .maestroignore (blank lines and `#` comments
|
|
14
|
+
* skipped), report one of:
|
|
15
|
+
* identical the local file is byte-identical to the vendored SDK copy —
|
|
16
|
+
* the local fix is GONE (an upgrade/rollback rewrote it, or it
|
|
17
|
+
* was never applied).
|
|
18
|
+
* not-loaded the local file differs but its mtime post-dates the daemon
|
|
19
|
+
* boot (state/cadence-bus/health.json), so the RUNNING daemon
|
|
20
|
+
* still executes the old code. Only meaningful for
|
|
21
|
+
* daemon-loaded modules (scripts/daemon, scripts/collective,
|
|
22
|
+
* lib; never *.test.mjs).
|
|
23
|
+
* ok differs from vendored and predates the daemon boot.
|
|
24
|
+
* missing-local the pin names a file that does not exist locally.
|
|
25
|
+
* no-vendored the SDK ships no copy under that path (nothing can clobber it).
|
|
26
|
+
*
|
|
27
|
+
* Every differing pin also carries `delta {release_only, local_only, behind}`
|
|
28
|
+
* measured by `diff`: release_only = lines the release has that we lack (the
|
|
29
|
+
* debt), local_only = the patch we keep. `behind` = release_only > 0.
|
|
30
|
+
*
|
|
31
|
+
* A third reference, upstream main, closes the gap the vendored release cannot
|
|
32
|
+
* see: a seat at exactly the current release with a pin byte-identical to that
|
|
33
|
+
* release reads `identical` (fix lost) — or `ok` — while the fix it was taken
|
|
34
|
+
* for has already merged upstream and will only arrive with the next release.
|
|
35
|
+
* When an SDK source checkout is known (`--repo DIR`, else `MAESTRO_SDK_REPO`)
|
|
36
|
+
* each pin is also compared with `git -C DIR show <ref>:<path>` (`--upstream`,
|
|
37
|
+
* default `origin/main`) and carries `upstream_state` + `delta_upstream
|
|
38
|
+
* {upstream_only, local_only}`:
|
|
39
|
+
* identical byte-identical to the file at <ref>.
|
|
40
|
+
* ok differs, but <ref> has no line we lack.
|
|
41
|
+
* behind-main upstream_only exceeds the threshold — main already carries
|
|
42
|
+
* lines this pin will never receive through an upgrade.
|
|
43
|
+
* no-upstream the path does not exist at <ref> (a notice, never a failure).
|
|
44
|
+
* No checkout, or a ref that does not resolve, skips the comparison with a
|
|
45
|
+
* one-line notice; the vendored report and its exit code are unaffected.
|
|
46
|
+
*
|
|
47
|
+
* The unpinned walk over scripts/daemon, scripts/collective, scripts/org,
|
|
48
|
+
* scripts/session and lib classifies every other .mjs/.js/.py/.sh file:
|
|
49
|
+
* unpinned differs from the vendored copy and is NOT pinned — an
|
|
50
|
+
* upgrade/rollback will overwrite it.
|
|
51
|
+
* unshipped a local file with no release twin (seat authorship). An
|
|
52
|
+
* upgrade cannot overwrite what it does not ship, so it needs
|
|
53
|
+
* no pin; listed so it is not mistaken for drift.
|
|
54
|
+
*
|
|
55
|
+
* Usage: node scripts/maintenance/pin-integrity.mjs [--json] [--root DIR] [--threshold N]
|
|
56
|
+
* [--repo DIR] [--upstream REF]
|
|
57
|
+
* MAESTRO_PIN_BEHIND_MAX=N allowed release-only (and upstream-only) lines per
|
|
58
|
+
* pin (default 0: any such line alarms). --threshold wins.
|
|
59
|
+
* MAESTRO_SDK_REPO=DIR SDK source checkout for the upstream comparison;
|
|
60
|
+
* --repo wins. Unset = comparison skipped.
|
|
61
|
+
* Exit 1 when any pin's release_only OR upstream_only exceeds the threshold;
|
|
62
|
+
* 0 otherwise.
|
|
63
|
+
* `diff` is required (macOS and Linux ship it); if it cannot be spawned the
|
|
64
|
+
* delta falls back to a line-multiset comparison so the check still runs.
|
|
65
|
+
*
|
|
66
|
+
* @module scripts/maintenance/pin-integrity
|
|
67
|
+
*/
|
|
68
|
+
import { readFileSync, writeFileSync, statSync, existsSync, readdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
69
|
+
import { spawnSync } from "node:child_process";
|
|
70
|
+
import { join, relative, resolve } from "node:path";
|
|
71
|
+
import { tmpdir } from "node:os";
|
|
72
|
+
import { createHash } from "node:crypto";
|
|
73
|
+
import { fileURLToPath } from "node:url";
|
|
74
|
+
|
|
75
|
+
/** Roots whose files the unpinned/unshipped walk classifies. */
|
|
76
|
+
export const WALK_ROOTS = Object.freeze(["scripts/daemon", "scripts/collective", "scripts/org", "scripts/session", "lib"]);
|
|
77
|
+
|
|
78
|
+
/** Paths whose code the daemon holds in memory — "not-loaded" applies only here. */
|
|
79
|
+
const DAEMON_LOADED_RE = /^(scripts\/daemon|scripts\/collective|lib)\//;
|
|
80
|
+
|
|
81
|
+
function sha(p) { return createHash("sha256").update(readFileSync(p)).digest("hex").slice(0, 16); }
|
|
82
|
+
|
|
83
|
+
function daemonBoot(root) {
|
|
84
|
+
try {
|
|
85
|
+
const h = JSON.parse(readFileSync(join(root, "state", "cadence-bus", "health.json"), "utf8"));
|
|
86
|
+
const startedAt = h.stats && h.stats.started_at ? new Date(h.stats.started_at) : null;
|
|
87
|
+
return { pid: h.pid ?? null, startedAt: startedAt && !Number.isNaN(startedAt.getTime()) ? startedAt : null };
|
|
88
|
+
} catch { return { pid: null, startedAt: null }; }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The pinned paths from `<root>/.maestroignore` (blank + `#` lines skipped). */
|
|
92
|
+
export function readPins(root) {
|
|
93
|
+
const f = join(root, ".maestroignore");
|
|
94
|
+
if (!existsSync(f)) return [];
|
|
95
|
+
return readFileSync(f, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function walk(dir, out = []) {
|
|
99
|
+
if (!existsSync(dir)) return out;
|
|
100
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
101
|
+
const p = join(dir, e.name);
|
|
102
|
+
if (e.isDirectory()) { if (e.name !== "node_modules") walk(p, out); }
|
|
103
|
+
else if (/\.(mjs|js|py|sh)$/.test(e.name)) out.push(p);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Pure-JS fallback: line multiset difference, used only when `diff` cannot spawn. */
|
|
109
|
+
function lineMultisetDelta(vendText, localText) {
|
|
110
|
+
const count = (t) => { const m = new Map(); for (const l of t.split("\n")) m.set(l, (m.get(l) || 0) + 1); return m; };
|
|
111
|
+
const a = count(vendText), b = count(localText);
|
|
112
|
+
let ro = 0, lo = 0;
|
|
113
|
+
for (const [l, n] of a) ro += Math.max(0, n - (b.get(l) || 0));
|
|
114
|
+
for (const [l, n] of b) lo += Math.max(0, n - (a.get(l) || 0));
|
|
115
|
+
return { release_only: ro, local_only: lo };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Lines only in `a` / only in `b`, by `diff` (multiset fallback when it cannot spawn). */
|
|
119
|
+
function countDiff(a, b) {
|
|
120
|
+
let ao = 0, bo = 0;
|
|
121
|
+
const d = spawnSync("diff", [a, b], { encoding: "utf8" });
|
|
122
|
+
if (!d.error && typeof d.stdout === "string") {
|
|
123
|
+
for (const ln of d.stdout.split("\n")) { if (ln.startsWith("< ")) ao++; else if (ln.startsWith("> ")) bo++; }
|
|
124
|
+
} else {
|
|
125
|
+
({ release_only: ao, local_only: bo } = lineMultisetDelta(readFileSync(a, "utf8"), readFileSync(b, "utf8")));
|
|
126
|
+
}
|
|
127
|
+
return { a_only: ao, b_only: bo };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** `{release_only, local_only, behind}` between the vendored and local copies. */
|
|
131
|
+
export function measureDelta(vend, local) {
|
|
132
|
+
const { a_only: ro, b_only: lo } = countDiff(vend, local);
|
|
133
|
+
return { release_only: ro, local_only: lo, behind: ro > 0 };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** `{upstream_only, local_only}` between the file at the upstream ref and the local copy. */
|
|
137
|
+
export function measureUpstreamDelta(upstreamCopy, local) {
|
|
138
|
+
const { a_only: uo, b_only: lo } = countDiff(upstreamCopy, local);
|
|
139
|
+
return { upstream_only: uo, local_only: lo };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Default upstream ref for the third reference. */
|
|
143
|
+
export const DEFAULT_UPSTREAM_REF = "origin/main";
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolve the SDK source checkout: explicit option → MAESTRO_SDK_REPO → null.
|
|
147
|
+
* @param {string|undefined} explicit
|
|
148
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
149
|
+
* @returns {string|null} absolute path, or null when nothing names a checkout
|
|
150
|
+
*/
|
|
151
|
+
export function resolveUpstreamRepo(explicit, env = process.env) {
|
|
152
|
+
const cand = explicit !== undefined && explicit !== null && explicit !== "" ? explicit : env.MAESTRO_SDK_REPO;
|
|
153
|
+
return cand && String(cand).trim() ? resolve(String(cand).trim()) : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function git(repo, args, encoding = "utf8") {
|
|
157
|
+
return spawnSync("git", ["-C", repo, ...args], { encoding, maxBuffer: 64 * 1024 * 1024 });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** First non-empty stderr line, for notices and per-file reasons. */
|
|
161
|
+
function firstLine(r) {
|
|
162
|
+
const t = r && r.error ? r.error.message : String((r && r.stderr) || "");
|
|
163
|
+
return (t.split("\n").map((l) => l.trim()).find(Boolean) || "").slice(0, 200);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Open the upstream reference: `{enabled, ref, repo, commit, notice}`. Never
|
|
168
|
+
* throws — a missing checkout, a non-repo or an unresolvable ref returns
|
|
169
|
+
* `enabled:false` with a one-line `notice`.
|
|
170
|
+
* @param {string|null} repo from resolveUpstreamRepo()
|
|
171
|
+
* @param {string} ref
|
|
172
|
+
*/
|
|
173
|
+
export function openUpstream(repo, ref = DEFAULT_UPSTREAM_REF) {
|
|
174
|
+
const base = { enabled: false, ref, repo, commit: null, notice: null };
|
|
175
|
+
if (!repo) return { ...base, notice: "upstream comparison skipped: no --repo and MAESTRO_SDK_REPO unset" };
|
|
176
|
+
if (!existsSync(repo)) return { ...base, notice: `upstream comparison skipped: ${repo} does not exist` };
|
|
177
|
+
let rp;
|
|
178
|
+
try { rp = git(repo, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]); } catch (e) { rp = { error: e }; }
|
|
179
|
+
if (rp.error) return { ...base, notice: `upstream comparison skipped: git could not run in ${repo} (${firstLine(rp)})` };
|
|
180
|
+
if (rp.status !== 0 || !String(rp.stdout).trim()) return { ...base, notice: `upstream comparison skipped: ${ref} does not resolve in ${repo}${firstLine(rp) ? " (" + firstLine(rp) + ")" : ""}` };
|
|
181
|
+
return { ...base, enabled: true, commit: String(rp.stdout).trim() };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* `git show <ref>:<path>` as a Buffer, or `{error}` when the path is absent at
|
|
186
|
+
* the ref (or git fails) — fail-open per file.
|
|
187
|
+
*/
|
|
188
|
+
function upstreamBlob(repo, ref, rel) {
|
|
189
|
+
let r;
|
|
190
|
+
try { r = git(repo, ["show", `${ref}:${rel}`], "buffer"); } catch (e) { r = { error: e }; }
|
|
191
|
+
if (r.error || r.status !== 0) return { error: firstLine({ ...r, stderr: r.stderr ? r.stderr.toString() : "" }) || `not at ${ref}` };
|
|
192
|
+
return { buf: r.stdout };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Resolve the behind threshold: explicit option → MAESTRO_PIN_BEHIND_MAX → 0.
|
|
197
|
+
* @param {number|string|undefined} explicit
|
|
198
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
199
|
+
*/
|
|
200
|
+
export function resolveThreshold(explicit, env = process.env) {
|
|
201
|
+
const cand = explicit !== undefined && explicit !== null && explicit !== "" ? explicit : env.MAESTRO_PIN_BEHIND_MAX;
|
|
202
|
+
const n = Number(cand);
|
|
203
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Build the report for an agent root.
|
|
208
|
+
* @param {string} root the agent repo
|
|
209
|
+
* @param {object} [o]
|
|
210
|
+
* @param {number} [o.threshold] allowed release-only / upstream-only lines per pin (default env/0)
|
|
211
|
+
* @param {string} [o.repo] SDK source checkout for the upstream reference (default env MAESTRO_SDK_REPO)
|
|
212
|
+
* @param {string} [o.upstream] git ref in that checkout (default origin/main)
|
|
213
|
+
* @param {Date} [o.now] report timestamp (test seam)
|
|
214
|
+
* @returns {object} report with `.summary`, `.pinned`, `.unpinned_divergences`,
|
|
215
|
+
* `.unshipped_local`, `.threshold`, `.over_threshold[]`, `.upstream`,
|
|
216
|
+
* `.over_threshold_main[]`, `.exitCode`
|
|
217
|
+
*/
|
|
218
|
+
export function pinIntegrity(root, o = {}) {
|
|
219
|
+
const threshold = resolveThreshold(o.threshold);
|
|
220
|
+
const vendored = join(root, "node_modules", "@cohortapp", "agent-sdk");
|
|
221
|
+
const boot = daemonBoot(root);
|
|
222
|
+
const pins = readPins(root);
|
|
223
|
+
const pinSet = new Set(pins);
|
|
224
|
+
const upstream = openUpstream(resolveUpstreamRepo(o.repo), o.upstream || DEFAULT_UPSTREAM_REF);
|
|
225
|
+
const scratch = upstream.enabled ? mkdtempSync(join(tmpdir(), "pin-integrity-upstream-")) : null;
|
|
226
|
+
const rows = [];
|
|
227
|
+
try {
|
|
228
|
+
for (const rel of pins) {
|
|
229
|
+
const local = join(root, rel), vend = join(vendored, rel);
|
|
230
|
+
if (!existsSync(local)) { rows.push({ path: rel, state: "missing-local" }); continue; }
|
|
231
|
+
const localSha = sha(local);
|
|
232
|
+
const row = { path: rel };
|
|
233
|
+
if (!existsSync(vend)) { Object.assign(row, { state: "no-vendored", local_sha: localSha }); }
|
|
234
|
+
else {
|
|
235
|
+
const vendSha = sha(vend);
|
|
236
|
+
const same = localSha === vendSha;
|
|
237
|
+
const mtime = statSync(local).mtime;
|
|
238
|
+
let state = same ? "identical" : "ok";
|
|
239
|
+
if (!same && boot.startedAt && mtime > boot.startedAt && DAEMON_LOADED_RE.test(rel) && !/\.test\.mjs$/.test(rel)) state = "not-loaded";
|
|
240
|
+
const delta = same ? null : measureDelta(vend, local);
|
|
241
|
+
Object.assign(row, { state, local_sha: localSha, vendored_sha: vendSha, mtime: mtime.toISOString(), delta });
|
|
242
|
+
}
|
|
243
|
+
if (upstream.enabled) Object.assign(row, upstreamRow(upstream, scratch, rel, local, localSha, threshold));
|
|
244
|
+
rows.push(row);
|
|
245
|
+
}
|
|
246
|
+
} finally { if (scratch) rmSync(scratch, { recursive: true, force: true }); }
|
|
247
|
+
|
|
248
|
+
const unpinned = [];
|
|
249
|
+
const unshipped = [];
|
|
250
|
+
for (const sub of WALK_ROOTS) {
|
|
251
|
+
for (const p of walk(join(root, sub))) {
|
|
252
|
+
const rel = relative(root, p);
|
|
253
|
+
if (pinSet.has(rel)) continue;
|
|
254
|
+
const vend = join(vendored, rel);
|
|
255
|
+
if (!existsSync(vend)) { unshipped.push({ path: rel }); continue; }
|
|
256
|
+
if (sha(p) !== sha(vend)) unpinned.push({ path: rel, mtime: statSync(p).mtime.toISOString() });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const over = rows.filter((r) => r.delta && r.delta.release_only > threshold).map((r) => ({ path: r.path, release_only: r.delta.release_only }));
|
|
261
|
+
const overMain = rows.filter((r) => r.upstream_state === "behind-main").map((r) => ({ path: r.path, upstream_only: r.delta_upstream.upstream_only }));
|
|
262
|
+
const summary = {
|
|
263
|
+
identical: rows.filter((r) => r.state === "identical").length,
|
|
264
|
+
not_loaded: rows.filter((r) => r.state === "not-loaded").length,
|
|
265
|
+
ok: rows.filter((r) => r.state === "ok").length,
|
|
266
|
+
unpinned: unpinned.length,
|
|
267
|
+
unshipped: unshipped.length,
|
|
268
|
+
behind: rows.filter((r) => r.delta && r.delta.behind).length,
|
|
269
|
+
release_only_lines: rows.reduce((n, r) => n + (r.delta ? r.delta.release_only : 0), 0),
|
|
270
|
+
};
|
|
271
|
+
if (upstream.enabled) {
|
|
272
|
+
summary.behind_main = overMain.length;
|
|
273
|
+
summary.upstream_only_lines = rows.reduce((n, r) => n + (r.delta_upstream ? r.delta_upstream.upstream_only : 0), 0);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
checked_at: (o.now || new Date()).toISOString(),
|
|
277
|
+
daemon: { pid: boot.pid, started_at: boot.startedAt ? boot.startedAt.toISOString() : null },
|
|
278
|
+
threshold,
|
|
279
|
+
pinned: rows,
|
|
280
|
+
unpinned_divergences: unpinned,
|
|
281
|
+
unshipped_local: unshipped,
|
|
282
|
+
over_threshold: over,
|
|
283
|
+
upstream,
|
|
284
|
+
over_threshold_main: overMain,
|
|
285
|
+
summary,
|
|
286
|
+
exitCode: over.length || overMain.length ? 1 : 0,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* The upstream columns for one pin: `upstream_state`, `upstream_sha`,
|
|
292
|
+
* `delta_upstream {upstream_only, local_only}` (null when identical), and
|
|
293
|
+
* `upstream_reason` when the path is not at the ref.
|
|
294
|
+
*/
|
|
295
|
+
function upstreamRow(upstream, scratch, rel, local, localSha, threshold) {
|
|
296
|
+
const blob = upstreamBlob(upstream.repo, upstream.ref, rel);
|
|
297
|
+
if (blob.error) return { upstream_state: "no-upstream", upstream_reason: blob.error, upstream_sha: null, delta_upstream: null };
|
|
298
|
+
const upSha = createHash("sha256").update(blob.buf).digest("hex").slice(0, 16);
|
|
299
|
+
if (upSha === localSha) return { upstream_state: "identical", upstream_sha: upSha, delta_upstream: null };
|
|
300
|
+
const tmp = join(scratch, upSha + "-" + rel.replace(/[^A-Za-z0-9._-]/g, "_"));
|
|
301
|
+
writeFileSync(tmp, blob.buf);
|
|
302
|
+
const delta_upstream = measureUpstreamDelta(tmp, local);
|
|
303
|
+
const upstream_state = delta_upstream.upstream_only > threshold ? "behind-main" : "ok";
|
|
304
|
+
return { upstream_state, upstream_sha: upSha, delta_upstream };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** The one-line summary, in the shape the wrap log and doctor grep for. */
|
|
308
|
+
export function summaryLine(report) {
|
|
309
|
+
const s = report.summary;
|
|
310
|
+
const base = `summary: identical(fix lost)=${s.identical} not-loaded=${s.not_loaded} ok=${s.ok} unpinned=${s.unpinned} unshipped=${s.unshipped} behind=${s.behind} (release-only lines ${s.release_only_lines})`;
|
|
311
|
+
if (report.upstream && report.upstream.enabled) return `${base} behind_main=${s.behind_main} (upstream-only lines ${s.upstream_only_lines})`;
|
|
312
|
+
return base;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** The upstream column for one pinned row, e.g. ` / behind-main (+37 upstream-only lines)`; empty when the comparison did not run. */
|
|
316
|
+
function formatUpstreamCol(r) {
|
|
317
|
+
if (!r.upstream_state) return "";
|
|
318
|
+
if (r.upstream_state === "no-upstream") return ` / no-upstream (${r.upstream_reason})`;
|
|
319
|
+
if (r.upstream_state === "identical") return " / identical (upstream)";
|
|
320
|
+
const d = r.delta_upstream;
|
|
321
|
+
return ` / ${r.upstream_state} (+${d.upstream_only} upstream-only lines, ${d.local_only} local-only)`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Human-readable report text (non-JSON mode). */
|
|
325
|
+
export function formatReport(report) {
|
|
326
|
+
const out = [];
|
|
327
|
+
const up = report.upstream;
|
|
328
|
+
out.push(`pin-integrity ${report.checked_at} daemon pid ${report.daemon.pid} up since ${report.daemon.started_at} threshold ${report.threshold}`);
|
|
329
|
+
if (up && up.enabled) out.push(` upstream reference: ${up.ref} @ ${up.commit.slice(0, 12)} (${up.repo}) — each pin reads "<vendored state> / <upstream state>"`);
|
|
330
|
+
else if (up && up.notice) out.push(` ${up.notice}`);
|
|
331
|
+
for (const r of report.pinned) {
|
|
332
|
+
out.push(` ${r.state.padEnd(13)} ${r.path}${r.mtime ? " (mtime " + r.mtime + ")" : ""}${r.delta ? ` [local-only ${r.delta.local_only} / release-only ${r.delta.release_only}${r.delta.behind ? " BEHIND" : ""}]` : ""}${formatUpstreamCol(r)}`);
|
|
333
|
+
}
|
|
334
|
+
if (report.unpinned_divergences.length) {
|
|
335
|
+
out.push(" UNPINNED local divergences (an upgrade/rollback will overwrite these):");
|
|
336
|
+
for (const u of report.unpinned_divergences) out.push(` ${u.path} (mtime ${u.mtime})`);
|
|
337
|
+
}
|
|
338
|
+
if (report.unshipped_local.length) {
|
|
339
|
+
out.push(" unshipped local files (no release twin; nothing to pin):");
|
|
340
|
+
for (const u of report.unshipped_local) out.push(` ${u.path}`);
|
|
341
|
+
}
|
|
342
|
+
out.push(` ${summaryLine(report)}`);
|
|
343
|
+
if (report.over_threshold.length) {
|
|
344
|
+
out.push(` BEHIND past threshold (${report.threshold} release-only line(s) allowed per pin):`);
|
|
345
|
+
for (const p of report.over_threshold) out.push(` ${p.path} release-only ${p.release_only}`);
|
|
346
|
+
}
|
|
347
|
+
if (report.over_threshold_main && report.over_threshold_main.length) {
|
|
348
|
+
out.push(` BEHIND-MAIN past threshold (${report.threshold} upstream-only line(s) allowed per pin; fixes already on ${up.ref} that no upgrade will deliver until released):`);
|
|
349
|
+
for (const p of report.over_threshold_main) out.push(` ${p.path} upstream-only ${p.upstream_only}`);
|
|
350
|
+
}
|
|
351
|
+
return out.join("\n");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function main(argv) {
|
|
355
|
+
const args = argv.slice(2);
|
|
356
|
+
const flagVal = (name) => (args.includes(name) ? args[args.indexOf(name) + 1] : undefined);
|
|
357
|
+
const root = flagVal("--root") ? resolve(flagVal("--root")) : resolve(fileURLToPath(new URL(".", import.meta.url)), "..", "..");
|
|
358
|
+
const report = pinIntegrity(root, { threshold: flagVal("--threshold"), repo: flagVal("--repo"), upstream: flagVal("--upstream") });
|
|
359
|
+
if (args.includes("--json")) console.log(JSON.stringify(report));
|
|
360
|
+
else console.log(formatReport(report));
|
|
361
|
+
process.exit(report.exitCode);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) main(process.argv);
|
|
@@ -1,24 +1,56 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# poll-slack-events.sh — Fetch buffered events from Railway
|
|
3
|
-
#
|
|
4
|
-
# Run
|
|
5
|
-
|
|
2
|
+
# poll-slack-events.sh — Fetch buffered events from the Railway-hosted webhook
|
|
3
|
+
# relay and write them to state/inbox/slack/ for the monitoring loop to process.
|
|
4
|
+
# Run by ai.maestro.<first>-poll-relay every 5 s (scripts/local-triggers/
|
|
5
|
+
# generate-plists.sh §2). See docs/guides/webhook-relay-setup.md.
|
|
6
|
+
#
|
|
7
|
+
# A seat with NO relay configured exits 0 and does nothing. This script used to
|
|
8
|
+
# default SLACK_EVENTS_RELAY_URL to a hostname that has since gone away, and to
|
|
9
|
+
# run under `set -e` with a JSON parse of whatever came back — so on every seat
|
|
10
|
+
# without Slack it fetched a 404 page, failed to parse it, and exited 1, every
|
|
11
|
+
# five seconds, for weeks (one seat: 102,568 runs, `launchctl list` showing a
|
|
12
|
+
# standing "1"). Nothing was wrong on those seats; the job simply had no relay
|
|
13
|
+
# to poll. Configuration is the gate: no URL, no work, clean exit.
|
|
14
|
+
#
|
|
15
|
+
# The relay URL comes from the environment or from <agent>/.env
|
|
16
|
+
# (SLACK_EVENTS_RELAY_URL). No hardcoded default: a URL that nobody set is not
|
|
17
|
+
# a URL to poll.
|
|
18
|
+
set -u
|
|
6
19
|
|
|
7
20
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
-
AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
9
|
-
EVENTS_URL="${SLACK_EVENTS_RELAY_URL:-https://slack-events-server-production.up.railway.app/events}"
|
|
21
|
+
AGENT_REPO_DIR="${AGENT_DIR:-${AGENT_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}}"
|
|
10
22
|
INBOX_DIR="$AGENT_REPO_DIR/state/inbox/slack"
|
|
23
|
+
|
|
24
|
+
# .env is the seat's configuration; launchd hands this script a bare env.
|
|
25
|
+
EVENTS_URL="${SLACK_EVENTS_RELAY_URL:-}"
|
|
26
|
+
if [ -z "$EVENTS_URL" ] && [ -r "$AGENT_REPO_DIR/.env" ]; then
|
|
27
|
+
EVENTS_URL="$(grep -E '^SLACK_EVENTS_RELAY_URL=' "$AGENT_REPO_DIR/.env" 2>/dev/null | tail -1 | cut -d= -f2- | tr -d "\"'[:space:]")"
|
|
28
|
+
fi
|
|
29
|
+
if [ -z "$EVENTS_URL" ]; then
|
|
30
|
+
# Not configured on this seat: nothing to poll, and nothing to say every 5 s.
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
11
34
|
mkdir -p "$INBOX_DIR"
|
|
12
35
|
|
|
13
|
-
|
|
14
|
-
|
|
36
|
+
# A relay that is down or answers with something other than JSON is a transient
|
|
37
|
+
# fact about the relay, not a failure of this seat: log one line, exit 0, and
|
|
38
|
+
# the next tick tries again. Under `set -e` a bad body used to be exit 1.
|
|
39
|
+
RESPONSE="$(curl -s --fail --max-time 5 "$EVENTS_URL" 2>/dev/null)" || {
|
|
40
|
+
echo "[poll-slack-events $(date -u +%H:%M:%SZ)] relay unreachable or non-2xx: $EVENTS_URL" >&2
|
|
41
|
+
exit 0
|
|
42
|
+
}
|
|
43
|
+
COUNT="$(printf '%s' "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null)" || {
|
|
44
|
+
echo "[poll-slack-events $(date -u +%H:%M:%SZ)] relay answered with a body that is not JSON: $EVENTS_URL" >&2
|
|
45
|
+
exit 0
|
|
46
|
+
}
|
|
15
47
|
|
|
16
48
|
if [ "$COUNT" = "0" ] || [ -z "$COUNT" ]; then
|
|
17
49
|
exit 0
|
|
18
50
|
fi
|
|
19
51
|
|
|
20
52
|
# Write each event to a separate file in the inbox
|
|
21
|
-
|
|
53
|
+
printf '%s' "$RESPONSE" | python3 -c "
|
|
22
54
|
import sys, json, os
|
|
23
55
|
|
|
24
56
|
data = json.load(sys.stdin)
|
|
@@ -423,6 +423,20 @@ export function eventToInboxItem(envelope, { ownSlackId, principalSlackId, agent
|
|
|
423
423
|
* Returns `{ url }` on success or `{ error }` on failure. The URL is
|
|
424
424
|
* single-use and expires quickly — call this every time we open a socket.
|
|
425
425
|
*/
|
|
426
|
+
/**
|
|
427
|
+
* What the seat's SLACK_APP_LEVEL_TOKEN says about whether Socket Mode is
|
|
428
|
+
* meant to run here. "absent" is a seat without Slack (a clean no-op, exit
|
|
429
|
+
* 0); "malformed" is a configured token that cannot work (EX_CONFIG, 78);
|
|
430
|
+
* "ok" is an xapp- token.
|
|
431
|
+
* @param {string|undefined} token
|
|
432
|
+
* @returns {"absent"|"malformed"|"ok"}
|
|
433
|
+
*/
|
|
434
|
+
export function classifyAppToken(token) {
|
|
435
|
+
const t = typeof token === "string" ? token.trim() : "";
|
|
436
|
+
if (!t) return "absent";
|
|
437
|
+
return t.startsWith("xapp-") ? "ok" : "malformed";
|
|
438
|
+
}
|
|
439
|
+
|
|
426
440
|
export async function openSocketConnection({ appToken, fetchFn = fetch }) {
|
|
427
441
|
if (!appToken) {
|
|
428
442
|
return { error: "missing-app-token" };
|
|
@@ -883,10 +897,21 @@ async function main() {
|
|
|
883
897
|
loadEnvFromFile(agentRoot);
|
|
884
898
|
|
|
885
899
|
const appToken = process.env.SLACK_APP_LEVEL_TOKEN || "";
|
|
886
|
-
|
|
887
|
-
|
|
900
|
+
const tokenState = classifyAppToken(appToken);
|
|
901
|
+
if (tokenState === "absent") {
|
|
902
|
+
// Not an error: a seat that does not use Slack has no app-level token and
|
|
903
|
+
// never will. Exiting non-zero here put every such seat into a KeepAlive
|
|
904
|
+
// crash loop — one seat logged 36,000 EX_CONFIG restarts over four weeks,
|
|
905
|
+
// one line each, and `launchctl list` showed a standing "78" beside a job
|
|
906
|
+
// that had nothing to do. A CLEAN exit is what the plist's KeepAlive
|
|
907
|
+
// {SuccessfulExit:false} does not restart: say so once and stop.
|
|
908
|
+
info(agentRoot, "Socket Mode not configured on this seat (no SLACK_APP_LEVEL_TOKEN in .env) — not starting; run scripts/setup/init-slack-socket-mode.mjs to enable it");
|
|
909
|
+
process.exit(0);
|
|
910
|
+
}
|
|
911
|
+
if (tokenState === "malformed") {
|
|
912
|
+
error(agentRoot, "SLACK_APP_LEVEL_TOKEN malformed (must start with xapp-). " +
|
|
888
913
|
"Run: node scripts/setup/init-slack-socket-mode.mjs");
|
|
889
|
-
process.exit(78); // EX_CONFIG
|
|
914
|
+
process.exit(78); // EX_CONFIG — a token IS configured and it is wrong: an operator error worth a loud, non-zero exit
|
|
890
915
|
}
|
|
891
916
|
|
|
892
917
|
const identity = loadAgentIdentity(agentRoot);
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
* @module scripts/session/supervisor
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
import { existsSync as fsExistsSync, readFileSync as fsReadFileSync, mkdirSync as fsMkdirSync, unlinkSync as fsUnlinkSync } from "node:fs";
|
|
45
|
+
import { existsSync as fsExistsSync, readFileSync as fsReadFileSync, mkdirSync as fsMkdirSync, unlinkSync as fsUnlinkSync, writeFileSync as fsWriteFileSync, chmodSync as fsChmodSync } from "node:fs";
|
|
46
|
+
import { randomUUID } from "node:crypto";
|
|
47
|
+
import { join } from "node:path";
|
|
46
48
|
import { spawn } from "node:child_process";
|
|
47
49
|
import { homedir } from "node:os";
|
|
48
50
|
import { pathToFileURL } from "node:url";
|
|
@@ -52,14 +54,23 @@ import { writeJsonAtomic as fsWriteJsonAtomic, writeFileAtomic as fsWriteFileAto
|
|
|
52
54
|
import { parseHeartbeat } from "../../lib/session/liveness.mjs";
|
|
53
55
|
import { ensureClaudeConfig, heartbeatSilence, attentionRecord, HEARTBEAT_GRACE_MS } from "../../lib/session/first-run.mjs";
|
|
54
56
|
import { acquireLock as singletonAcquireLock } from "../../lib/singleton.js";
|
|
55
|
-
import {
|
|
57
|
+
import { buildSpawn } from "../../lib/runtime/adapter.mjs";
|
|
58
|
+
import { resolveSeatSpawn } from "../../lib/runtime/seat-engine.mjs";
|
|
56
59
|
import { sessionPermissionArgs, MAIN_SESSION_SOURCE } from "../../lib/session-permissions.mjs";
|
|
57
60
|
import { loadSessionConfig, sessionPaths } from "../../lib/session/config.mjs";
|
|
61
|
+
import { noticeHonouredBy } from "../../lib/session/upgrade-notice.mjs";
|
|
62
|
+
import { SDK_PACKAGE } from "../daemon/sdk-version.mjs";
|
|
58
63
|
import {
|
|
59
64
|
loadMainSession, saveMainSession, newMainSession, agentFirstName, muxSessionName,
|
|
60
65
|
launchMode, recordLaunch, rotationDecision, rotateMainSession, BACKOFF_MS,
|
|
61
66
|
} from "../../lib/session/identity.mjs";
|
|
62
|
-
import {
|
|
67
|
+
import { detectMux, chooseMux, buildMuxCommands, parseScreenList, parseExitFile, renderEnvFile } from "../../lib/session/launch-args.mjs";
|
|
68
|
+
|
|
69
|
+
/** Write a private (0600) file that must not already exist. The engine env file carries the seat token. */
|
|
70
|
+
export function writePrivateFile(path, text) {
|
|
71
|
+
fsWriteFileSync(path, text, { mode: 0o600, flag: "wx" });
|
|
72
|
+
fsChmodSync(path, 0o600); // a umask cannot widen it, but be explicit
|
|
73
|
+
}
|
|
63
74
|
|
|
64
75
|
/** Another supervisor holds the lock (or the front door is not the session). Clean. */
|
|
65
76
|
export const EXIT_ANOTHER_HOLDS_LOCK = 0;
|
|
@@ -162,6 +173,10 @@ export async function runSupervisor(deps = {}) {
|
|
|
162
173
|
permissionArgs: deps.permissionArgs || null,
|
|
163
174
|
pollMs: deps.pollMs ?? DEFAULT_POLL_MS,
|
|
164
175
|
onSignal: deps.onSignal || installSignalHandlers,
|
|
176
|
+
// W4-E1: the seat's engine and its spawn fields (config/agent.json runtime.engine + seat token).
|
|
177
|
+
seatSpawn: deps.seatSpawn || ((root) => resolveSeatSpawn(root)),
|
|
178
|
+
writePrivateFile: deps.writePrivateFile || writePrivateFile,
|
|
179
|
+
envFileId: deps.envFileId || randomUUID,
|
|
165
180
|
};
|
|
166
181
|
const fsDeps = { readFileSync: d.readFileSync, writeJsonAtomic: d.writeJsonAtomic };
|
|
167
182
|
const agentRoot = d.agentRoot;
|
|
@@ -243,26 +258,73 @@ export async function runSupervisor(deps = {}) {
|
|
|
243
258
|
d.log("MAESTRO_SCOPED_PERMISSIONS=1 but config/session.yaml has no allowedTools — the interactive main session runs with bypass (an unattended session cannot answer permission prompts)");
|
|
244
259
|
}
|
|
245
260
|
}
|
|
246
|
-
|
|
247
|
-
|
|
261
|
+
// Binary + argv from the runtime adapter (lib/runtime/adapter.mjs, lane
|
|
262
|
+
// "main-session"), which delegates the argv contract to launch-args.
|
|
263
|
+
// W4-E1 (row 5): the seat's engine, resolved once for this launch. A claude
|
|
264
|
+
// seat adds no fields (unchanged); an engine-cohort seat launches
|
|
265
|
+
// `cli.mjs session` with the seat token. Read on every launch, so flipping
|
|
266
|
+
// runtime.engine back to "claude" restores claude at the next relaunch.
|
|
267
|
+
let seat = { engine: "claude", fields: {} };
|
|
268
|
+
try { seat = (await d.seatSpawn(agentRoot)) || seat; } catch { seat = { engine: "claude", fields: {} }; }
|
|
269
|
+
const engineCohort = seat.engine === "cohort";
|
|
270
|
+
const spec = buildSpawn({ lane: "main-session", bin: d.claudeBin || undefined, first, sessionId: record.sessionId, resumeMode: mode, permissions: permissionArgs, env: d.env, ...seat.fields });
|
|
271
|
+
if (!spec.ok && engineCohort) {
|
|
272
|
+
// An engine-cohort seat never falls back to claude (it would spend the seat's
|
|
273
|
+
// Anthropic credential). No seat token yet: back off, then let launchd relaunch.
|
|
274
|
+
d.log(`engine cohort main session not launched: ${spec.error.message} — backing off ${BACKOFF_MS / 60000} min`);
|
|
275
|
+
await d.sleep(BACKOFF_MS);
|
|
276
|
+
return EXIT_RELAUNCH;
|
|
277
|
+
}
|
|
278
|
+
// A claude main session cannot fail to build: a failure is a defect.
|
|
279
|
+
if (!spec.ok) throw new Error(`supervisor: runtime adapter: ${spec.error.message}`);
|
|
280
|
+
const claudeArgs = spec.argv;
|
|
281
|
+
// W4-E1: an engine-cohort session takes its env from a 0600 file the session
|
|
282
|
+
// sources and deletes (launch-args ENGINE_ENV_SCRIPT), never from the mux: a
|
|
283
|
+
// tmux server that is already running would hand the session its own global
|
|
284
|
+
// env (no seat token, possibly Claude credentials) instead of ours.
|
|
285
|
+
const envFile = engineCohort ? join(paths.stateDir, `engine-env-${d.envFileId()}.env`) : null;
|
|
286
|
+
const cmds = buildMuxCommands({ mux, muxName, cwd: agentRoot, claudeBin: spec.bin, claudeArgs, exitFile: paths.lastExitFile, envFile });
|
|
248
287
|
|
|
249
288
|
// 4b. First-run dialogs. Nobody is attached to press a key, so the two
|
|
250
289
|
// one-time acknowledgements are recorded up front. Fail-open: a seat
|
|
251
290
|
// still launches when this cannot be done — the watchdog below then
|
|
252
|
-
// reports the blocked session instead of nothing at all.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
291
|
+
// reports the blocked session instead of nothing at all. The engine has
|
|
292
|
+
// no first-run dialogs, so an engine-cohort seat's ~/.claude.json is left alone.
|
|
293
|
+
if (!engineCohort) {
|
|
294
|
+
const seeded = ensureClaudeConfig({
|
|
295
|
+
homeDir: d.homeDir, agentRoot, bypass: permissionArgs.includes("--dangerously-skip-permissions"),
|
|
296
|
+
readFileSync: d.readFileSync, writeFileAtomic: d.writeFileAtomic,
|
|
297
|
+
});
|
|
298
|
+
if (seeded.changed) d.log(`${seeded.path} seeded for an unattended launch: ${seeded.applied.join(", ")}`);
|
|
299
|
+
else if (!seeded.ok) d.log(`${seeded.path || "~/.claude.json"} not seeded (${seeded.error}) — a first-run dialog may block the session; the watchdog will report it`);
|
|
300
|
+
}
|
|
259
301
|
|
|
260
302
|
try { d.unlinkSync(paths.lastExitFile); } catch { /* none from a previous run */ }
|
|
261
303
|
try { d.unlinkSync(paths.attentionFile); } catch { /* none outstanding */ }
|
|
304
|
+
// 4c. An upgrade notice asking for a restart onto the version THIS launch
|
|
305
|
+
// runs is honoured by this launch: retire it, so `maestro session
|
|
306
|
+
// status` stops flagging a hop the session has completed (and a notice
|
|
307
|
+
// that is still there means a restart that has not happened). A
|
|
308
|
+
// health report ({healthy:false}) is never touched here.
|
|
309
|
+
{
|
|
310
|
+
let noticeText = null;
|
|
311
|
+
try { noticeText = d.readFileSync(paths.upgradeNoticeFile, "utf8"); } catch { noticeText = null; }
|
|
312
|
+
if (noticeText != null) {
|
|
313
|
+
let installed = "";
|
|
314
|
+
try { installed = String(JSON.parse(d.readFileSync(join(agentRoot, "node_modules", SDK_PACKAGE, "package.json"), "utf8")).version || ""); } catch { installed = ""; }
|
|
315
|
+
const h = noticeHonouredBy(noticeText, installed);
|
|
316
|
+
if (h.honoured) {
|
|
317
|
+
try { d.unlinkSync(paths.upgradeNoticeFile); d.log(`upgrade notice ${h.from || "?"} -> ${h.to} honoured: this launch runs ${h.to}; notice retired`); }
|
|
318
|
+
catch (e) { d.log(`upgrade notice ${h.from || "?"} -> ${h.to} honoured but not retired: ${e && e.message ? e.message : e}`); }
|
|
319
|
+
} else if (h.to) {
|
|
320
|
+
d.log(`upgrade notice ${h.from || "?"} -> ${h.to} left in place (${h.reason})`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
262
324
|
const launched = recordLaunch(record, { now: d.now });
|
|
263
325
|
const saved = saveMainSession(agentRoot, launched, fsDeps);
|
|
264
326
|
if (!saved.ok) d.log(`main-session.json not saved: ${saved.error}`);
|
|
265
|
-
d.log(`launching ${mux.kind} session ${muxName}: claude ${mode === "resume" ? "--resume" : "--session-id"} ${record.sessionId} (resumes=${launched.resumes})`);
|
|
327
|
+
d.log(`launching ${mux.kind} session ${muxName}: ${engineCohort ? "cohort session" : "claude"} ${mode === "resume" ? "--resume" : "--session-id"} ${record.sessionId} (resumes=${launched.resumes})`);
|
|
266
328
|
|
|
267
329
|
// 5. Run, in the foreground of this process. The watchdog fires once,
|
|
268
330
|
// after the grace period: a session that has not beaten by then is
|
|
@@ -279,7 +341,11 @@ export async function runSupervisor(deps = {}) {
|
|
|
279
341
|
try { d.writeJsonAtomic(paths.attentionFile, rec); } catch { /* the log line still says it */ }
|
|
280
342
|
d.log(`session ${muxName} has been up ${Math.round(silence.runMs / 1000)} s with no heartbeat — probably blocked on a first-run dialog or a permission prompt; attach with \`${rec.attach}\` and answer it`);
|
|
281
343
|
});
|
|
344
|
+
const dropEnvFile = () => { if (envFile) { try { d.unlinkSync(envFile); } catch { /* the session consumed it */ } } };
|
|
282
345
|
try {
|
|
346
|
+
// The engine's env (seat token, every Claude credential scrubbed) rides the
|
|
347
|
+
// env file; the mux client itself gets the supervisor's env, as for claude.
|
|
348
|
+
if (envFile) d.writePrivateFile(envFile, renderEnvFile(spec.env));
|
|
283
349
|
const r = await d.execFile(cmds.start.file, cmds.start.args, { cwd: agentRoot, env: d.env });
|
|
284
350
|
if (r && r.code !== 0) { launchFailed = true; d.log(`${mux.kind} start exited ${r.code}${r.stderr ? `: ${String(r.stderr).trim()}` : ""}`); }
|
|
285
351
|
else if (!cmds.blocking) await waitForProbe(cmds.waitProbe, d);
|
|
@@ -287,6 +353,7 @@ export async function runSupervisor(deps = {}) {
|
|
|
287
353
|
launchFailed = true;
|
|
288
354
|
d.log(`${mux.kind} start failed: ${err && err.message ? err.message : err}`);
|
|
289
355
|
}
|
|
356
|
+
dropEnvFile(); // normally already gone; left only when the session never started
|
|
290
357
|
const endedAt = Number(d.now());
|
|
291
358
|
try { cancelWatchdog(); } catch { /* already fired */ }
|
|
292
359
|
await stopIssued;
|
package/scripts/spawn-session.sh
CHANGED
|
@@ -13,6 +13,19 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
13
13
|
# alias of it), so honouring the environment first is the correct precedence.
|
|
14
14
|
AGENT_DIR="${AGENT_DIR:-$(dirname "$SCRIPT_DIR")}"
|
|
15
15
|
|
|
16
|
+
# Retired on engine-cohort seats (W4-E1): this lane runs claude outside the
|
|
17
|
+
# runtime adapter. The guard exits 3 with the reason when runtime.engine is cohort.
|
|
18
|
+
# Fail closed: when the guard cannot run (no node on PATH — launchd's
|
|
19
|
+
# /usr/bin:/bin — or the guard file is missing), config/agent.json is checked
|
|
20
|
+
# here and an engine-cohort seat is refused all the same.
|
|
21
|
+
LEGACY_GUARD="$(dirname "$SCRIPT_DIR")/lib/runtime/legacy-shell-guard.mjs"
|
|
22
|
+
if [ -f "$LEGACY_GUARD" ] && command -v node >/dev/null 2>&1; then
|
|
23
|
+
node "$LEGACY_GUARD" spawn-session.sh "$AGENT_DIR" || exit $?
|
|
24
|
+
elif [ -f "$AGENT_DIR/config/agent.json" ] && grep -Eq '"engine"[[:space:]]*:[[:space:]]*"cohort"' "$AGENT_DIR/config/agent.json"; then
|
|
25
|
+
echo "spawn-session.sh is retired on this seat: runtime.engine is \"cohort\", and this legacy lane runs the claude binary directly, outside the runtime adapter (the engine guard could not run: node or $LEGACY_GUARD is unavailable)." >&2
|
|
26
|
+
exit 3
|
|
27
|
+
fi
|
|
28
|
+
|
|
16
29
|
# Real identity for the sub-session prompt. config/agent.env is generated from
|
|
17
30
|
# config/agent.json by scripts/setup/generate-agent-env.mjs on every
|
|
18
31
|
# `maestro upgrade` — it exists precisely so shell scripts can read the SOT.
|