@cohortapp/agent-sdk 2.11.15 → 2.13.0
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/.env.example +37 -22
- package/README.md +2 -0
- package/bin/maestro.mjs +117 -39
- package/bin/maestro.test.mjs +175 -5
- package/docs/guides/front-door-session.md +313 -0
- package/docs/guides/mac-mini.md +100 -28
- package/docs/guides/org-onboarding.md +1 -1
- package/docs/guides/setup-wizard.md +9 -5
- package/docs/runbooks/cohort-cutover.md +11 -1
- package/docs/runbooks/mac-mini-bootstrap.md +38 -63
- package/lib/cadence-bus-requeue.test.mjs +83 -0
- package/lib/cadence-bus.mjs +43 -7
- package/lib/channels/inbox-item.mjs +59 -2
- package/lib/cli/board.mjs +285 -0
- package/lib/cli/board.test.mjs +227 -0
- package/lib/cli/design.mjs +185 -0
- package/lib/cli/design.test.mjs +270 -0
- package/lib/cli/doctor-checks.mjs +441 -0
- package/lib/cli/doctor-checks.test.mjs +336 -0
- package/lib/cli/global-setup-extras.mjs +454 -0
- package/lib/cli/global-setup-extras.test.mjs +462 -0
- package/lib/cli/inbox.mjs +304 -0
- package/lib/cli/inbox.test.mjs +230 -0
- package/lib/cli/session-ack.mjs +63 -0
- package/lib/cli/session-ack.test.mjs +63 -0
- package/lib/cli/session.mjs +760 -0
- package/lib/cli/session.test.mjs +613 -0
- package/lib/collective/global-config.mjs +209 -6
- package/lib/collective/global-config.test.mjs +145 -0
- package/lib/collective/global-skills.mjs +145 -0
- package/lib/collective/global-skills.test.mjs +126 -0
- package/lib/collective/presence.mjs +4 -3
- package/lib/collective/vendor-skills.mjs +305 -0
- package/lib/collective/vendor-skills.test.mjs +306 -0
- package/lib/comms/send-gate.mjs +115 -0
- package/lib/comms/send-gate.test.mjs +113 -0
- package/lib/design/design-md.mjs +793 -0
- package/lib/design/design-md.test.mjs +318 -0
- package/lib/design/fixtures/DESIGN.golden.md +238 -0
- package/lib/design/fixtures/PRODUCT.golden.md +67 -0
- package/lib/design/fixtures/foundation.json +133 -0
- package/lib/design/refresh-gate.mjs +154 -0
- package/lib/design/refresh-gate.test.mjs +144 -0
- package/lib/design/write.mjs +275 -0
- package/lib/design/write.test.mjs +241 -0
- package/lib/feature-init.mjs +2 -2
- package/lib/mcp/server.test.mjs +9 -4
- package/lib/model-router/spawn.test.mjs +21 -0
- package/lib/org/board-mine-cache.mjs +99 -0
- package/lib/org/board-mine-cache.test.mjs +53 -0
- package/lib/org/board.mjs +11 -0
- package/lib/org/board.test.mjs +11 -1
- package/lib/org/client.mjs +36 -0
- package/lib/org/client.test.mjs +46 -0
- package/lib/org/inbound/directedness.mjs +18 -2
- package/lib/org/inbound/directedness.test.mjs +58 -0
- package/lib/org/inbound/index.mjs +8 -1
- package/lib/org/inbound/index.test.mjs +22 -0
- package/lib/org/mesh-directives.test.mjs +110 -0
- package/lib/org/mesh.mjs +61 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +52 -0
- package/lib/org/protocol.test.mjs +12 -1
- package/lib/org/registry.mjs +3 -2
- package/lib/org/tool-surface.mjs +120 -0
- package/lib/org/tool-surface.test.mjs +118 -5
- package/lib/prompts/parallelism.mjs +79 -0
- package/lib/prompts/parallelism.test.mjs +177 -0
- package/lib/security/external-content.mjs +1 -1
- package/lib/security/external-content.test.mjs +17 -0
- package/lib/session/config.mjs +137 -0
- package/lib/session/config.test.mjs +92 -0
- package/lib/session/feed-core.mjs +229 -0
- package/lib/session/feed-core.test.mjs +198 -0
- package/lib/session/first-run.mjs +126 -0
- package/lib/session/first-run.test.mjs +121 -0
- package/lib/session/frontdoor.mjs +266 -0
- package/lib/session/frontdoor.test.mjs +205 -0
- package/lib/session/handoffs.mjs +295 -0
- package/lib/session/handoffs.test.mjs +183 -0
- package/lib/session/identity.mjs +220 -0
- package/lib/session/identity.test.mjs +180 -0
- package/lib/session/inbox-claims.mjs +434 -0
- package/lib/session/inbox-claims.test.mjs +286 -0
- package/lib/session/launch-args.mjs +161 -0
- package/lib/session/launch-args.test.mjs +157 -0
- package/lib/session/liveness.mjs +174 -0
- package/lib/session/liveness.test.mjs +100 -0
- package/lib/session/status-summary.mjs +172 -0
- package/lib/session/status-summary.test.mjs +118 -0
- package/lib/session-permissions.mjs +39 -3
- package/lib/session-permissions.test.mjs +20 -0
- package/lib/setup/claude-probe.mjs +161 -24
- package/lib/setup/claude-probe.test.mjs +187 -0
- package/lib/setup/sections/learning.mjs +2 -1
- package/lib/setup/sections/model.mjs +104 -24
- package/lib/setup/sections/model.test.mjs +240 -0
- package/lib/setup/sections/org.mjs +27 -2
- package/lib/setup/sections/org.test.mjs +35 -2
- package/lib/setup/sections/verify.mjs +5 -0
- package/lib/setup/state.mjs +30 -10
- package/lib/setup/state.test.mjs +24 -1
- package/lib/singleton.js +11 -3
- package/lib/singleton.test.mjs +16 -0
- package/lib/subagents/lock.mjs +1 -1
- package/lib/telemetry/collect.mjs +270 -6
- package/lib/telemetry/collect.test.mjs +196 -1
- package/lib/upgrade/global-refresh.mjs +108 -0
- package/lib/upgrade/global-refresh.test.mjs +65 -0
- package/lib/upgrade/launchd-reconcile.mjs +327 -0
- package/lib/upgrade/launchd-reconcile.test.mjs +272 -0
- package/lib/upgrade/post-steps.mjs +151 -0
- package/lib/upgrade/post-steps.test.mjs +200 -0
- package/lib/upgrade/verify.mjs +215 -0
- package/lib/upgrade/verify.test.mjs +164 -0
- package/lib/voice/outbound.mjs +3 -2
- package/lib/voice/post-call-brief.mjs +2 -1
- package/lib/voice/session-rotation.mjs +6 -1
- package/lib/voice/session-rotation.test.mjs +114 -0
- package/package.json +3 -3
- package/plugins/maestro-skills/plugin.json +25 -1
- package/plugins/maestro-skills/skills/board-work.md +63 -0
- package/plugins/maestro-skills/skills/cohort-design.md +153 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +80 -0
- package/plugins/maestro-skills/skills/main-session.md +102 -0
- package/plugins/maestro-skills/skills/peer-sessions.md +65 -0
- package/plugins/maestro-skills/skills/persona-discipline.md +75 -0
- package/plugins/maestro-skills/vendor/emilkowalski/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/emilkowalski/UPSTREAM.json +70 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/RECIPES.md +324 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/SKILL.md +199 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animation-vocabulary/SKILL.md +173 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/apple-design/SKILL.md +282 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/emil-design-eng/SKILL.md +674 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/find-animation-opportunities/SKILL.md +132 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/AUDIT.md +115 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/SKILL.md +101 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/PICKER.md +197 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/SKILL.md +90 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/SKILL.md +112 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/STANDARDS.md +187 -0
- package/plugins/maestro-skills/vendor/impeccable/LICENSE +191 -0
- package/plugins/maestro-skills/vendor/impeccable/NOTICE.md +11 -0
- package/plugins/maestro-skills/vendor/impeccable/SKILL.md +86 -0
- package/plugins/maestro-skills/vendor/impeccable/UPSTREAM.json +201 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-asset-producer.md +42 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-documenter.md +29 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-finish-reviewer.md +43 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-manual-edit-applier.md +97 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/adapt.md +312 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/adapt.native.md +58 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/android.md +46 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/animate.md +89 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/audit.md +136 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/audit.native.md +139 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/bolder.md +33 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/clarify.md +94 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/colorize.md +86 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/craft-floor.md +44 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/craft.md +5 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/critique.md +806 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/asset-producer.md +37 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/documenter.md +24 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/delight.md +70 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/distill.md +111 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/doctor.md +54 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/document.md +416 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/extract.md +69 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/harden.md +336 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/hooks.md +111 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/init.md +131 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/ios.md +51 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/layout.md +84 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/live-setup.md +104 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/live.md +325 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/new-work.md +147 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/onboard.md +234 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/operate.md +61 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/optimize.md +258 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/overdrive.md +127 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/polish.md +105 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/quieter.md +99 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/routing.md +24 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/shape.md +59 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/typeset.md +80 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/visualize.md +46 -0
- package/plugins/maestro-skills/vendor/taste-skill/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/taste-skill/UPSTREAM.json +37 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/minimalist-skill/SKILL.md +85 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/redesign-skill/SKILL.md +178 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/soft-skill/SKILL.md +98 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/taste-skill/SKILL.md +1206 -0
- package/plugins/maestro-skills/vendor/unlazy/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/unlazy/SECURITY.md +72 -0
- package/plugins/maestro-skills/vendor/unlazy/SKILL.md +104 -0
- package/plugins/maestro-skills/vendor/unlazy/UPSTREAM.json +94 -0
- package/plugins/maestro-skills/vendor/unlazy/references/dispatch.md +82 -0
- package/plugins/maestro-skills/vendor/unlazy/references/gates.md +149 -0
- package/plugins/maestro-skills/vendor/unlazy/references/method.md +49 -0
- package/plugins/maestro-skills/vendor/unlazy/references/orchestration.md +107 -0
- package/plugins/maestro-skills/vendor/unlazy/references/parallel.md +133 -0
- package/plugins/maestro-skills/vendor/unlazy/references/token-economy.md +48 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/dispatch-check.mjs +139 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/gate-check.mjs +960 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/gate-lint.mjs +245 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/check-supervisor.mjs +46 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/dispatch.mjs +293 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/gates.mjs +953 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/process-tree.mjs +161 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/regex-worker.mjs +9 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/PLAN.md +116 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/gates-leaf.md +51 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/gates-node.md +51 -0
- package/scaffold/CLAUDE.md +24 -0
- package/scripts/ci/check-durable-write-seam.mjs +147 -0
- package/scripts/ci/check-durable-write-seam.test.mjs +90 -0
- package/scripts/ci/check-skill-packs.mjs +388 -0
- package/scripts/ci/check-skill-packs.test.mjs +495 -0
- package/scripts/ci/check.mjs +6 -0
- package/scripts/collective/hook-runner.mjs +39 -4
- package/scripts/collective/hook-runner.test.mjs +85 -2
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +96 -0
- package/scripts/daemon/agent-daemon-design.test.mjs +238 -0
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +60 -0
- package/scripts/daemon/agent-daemon.mjs +249 -10
- package/scripts/daemon/agent-daemon.test.mjs +73 -0
- package/scripts/daemon/assurance-e2e.test.mjs +141 -6
- package/scripts/daemon/assurance.mjs +461 -37
- package/scripts/daemon/assurance.test.mjs +408 -43
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +393 -0
- package/scripts/daemon/cadence-consumer.mjs +289 -89
- package/scripts/daemon/cadence-handlers.mjs +53 -0
- package/scripts/daemon/classifier.mjs +1 -1
- package/scripts/daemon/dispatcher-resume.test.mjs +166 -0
- package/scripts/daemon/dispatcher.mjs +127 -19
- package/scripts/daemon/health.mjs +12 -1
- package/scripts/daemon/inbox-deferral-session.test.mjs +49 -0
- package/scripts/daemon/inbox-deferral.mjs +6 -0
- package/scripts/daemon/lib/self-echo.mjs +201 -0
- package/scripts/daemon/lib/self-echo.test.mjs +153 -0
- package/scripts/daemon/maestro-daemon.mjs +3 -0
- package/scripts/daemon/prompt-builder.mjs +19 -3
- package/scripts/daemon/responder.mjs +51 -40
- package/scripts/daemon/sdk-version.mjs +51 -0
- package/scripts/daemon/sdk-version.test.mjs +31 -0
- package/scripts/hooks/pre-send-audit.sh +97 -4
- package/scripts/hooks/pre-send-audit.test.mjs +140 -1
- package/scripts/local-triggers/autoupdate.sh +243 -19
- package/scripts/local-triggers/autoupdate.test.mjs +518 -0
- package/scripts/local-triggers/generate-plists.sh +24 -1
- package/scripts/local-triggers/generate-plists.test.mjs +49 -11
- package/scripts/org/send-orgmail.first-contact.test.mjs +102 -0
- package/scripts/org/send-orgmail.mjs +27 -3
- package/scripts/poller/inbox-privilege-injection.test.mjs +167 -0
- package/scripts/poller/slack-poller.mjs +13 -1
- package/scripts/poller/utils.mjs +46 -1
- package/scripts/poller-launchd/install.sh +19 -11
- package/scripts/poller-launchd/install.test.mjs +243 -0
- package/scripts/poller-launchd/launchd-poller-wrapper.sh +92 -0
- package/scripts/poller-launchd/migrate.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +4 -2
- package/scripts/session/feed.mjs +237 -0
- package/scripts/session/feed.test.mjs +196 -0
- package/scripts/session/supervisor-sh.test.mjs +218 -0
- package/scripts/session/supervisor.mjs +328 -0
- package/scripts/session/supervisor.sh +141 -0
- package/scripts/session/supervisor.test.mjs +482 -0
- package/scripts/setup/configure-macos.sh +250 -55
- package/scripts/setup/configure-macos.test.mjs +306 -0
- package/scripts/setup/init-agent.sh +112 -7
- package/scripts/setup/init-agent.test.mjs +220 -1
- package/scripts/vendor/skill-packs.mjs +354 -0
- package/scripts/vendor/sync-skill-packs.mjs +242 -0
- package/scripts/vendor/sync-skill-packs.test.mjs +103 -0
- package/scripts/watchdog/memory-watchdog.sh +37 -1
- package/scripts/watchdog/memory-watchdog.test.mjs +64 -0
- package/scripts/setup/boot-claude-session.sh +0 -94
package/.env.example
CHANGED
|
@@ -36,7 +36,11 @@ COHORT_BASE=https://os.cohortapp.com
|
|
|
36
36
|
# agent's workforce member (aliases: COHORT_API_KEY, COHORT_TOKEN).
|
|
37
37
|
COHORT_API_TOKEN=
|
|
38
38
|
|
|
39
|
-
#
|
|
39
|
+
# The org's ID — NOT its slug. It has the form org_default_<slug> (Adaptic:
|
|
40
|
+
# org_default_adaptic, slug adaptic) and is sent as the x-org-id pin header,
|
|
41
|
+
# which hq compares with strict equality against the key's Org.id: a slug here
|
|
42
|
+
# 401s every call. `maestro doctor` checks this value against the server and
|
|
43
|
+
# says "you set the slug" when that is what happened. Also used by pull-enrollment.
|
|
40
44
|
COHORT_ORG_ID=
|
|
41
45
|
|
|
42
46
|
# OPTIONAL — this agent's member id/slug. Used by pull-enrollment, org_whoami,
|
|
@@ -53,29 +57,40 @@ COHORT_AGENT_EMAIL=
|
|
|
53
57
|
# The agent's reasoning engines. At minimum you need Anthropic (Claude).
|
|
54
58
|
#
|
|
55
59
|
|
|
56
|
-
# REQUIRED — Primary reasoning engine.
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# Option
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
60
|
+
# REQUIRED — Primary reasoning engine. ONE auth story, three ways to satisfy it,
|
|
61
|
+
# in order of preference. `maestro setup` (model section, order 30) writes the
|
|
62
|
+
# chosen one here and chmods this file 600; `maestro doctor` reports which mode
|
|
63
|
+
# is in effect and what `claude auth status` reports (presence of a credential,
|
|
64
|
+
# not its validity — a bad token surfaces on the first real spawn).
|
|
65
|
+
#
|
|
66
|
+
# Option A — subscription OAuth token ← THE FLEET DEFAULT (seat machines)
|
|
67
|
+
# On ANY machine where you are logged in to Claude Code, run
|
|
68
|
+
# claude setup-token
|
|
69
|
+
# and paste the long-lived token below, together with
|
|
70
|
+
# MAESTRO_PREFER_SUBSCRIPTION_AUTH=1. Every spawn — the daemon's --print
|
|
71
|
+
# lane, the main session, cadence sub-sessions — carries it; the interactive
|
|
72
|
+
# keychain login is NOT relied on because it expires headlessly. The token is
|
|
73
|
+
# per seat; mint a fresh one with the same command when it is rejected.
|
|
74
|
+
# Headless setup reads it from the environment: CLAUDE_CODE_OAUTH_TOKEN=… maestro setup --headless
|
|
75
|
+
CLAUDE_CODE_OAUTH_TOKEN=
|
|
76
|
+
|
|
77
|
+
# Option B — keychain login on this machine (interactive / dev boxes only)
|
|
78
|
+
# `claude login` here, leave CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY
|
|
79
|
+
# empty, set MAESTRO_PREFER_SUBSCRIPTION_AUTH=1. Doctor warns: the login
|
|
80
|
+
# expires headlessly and a seat machine should hold the token instead.
|
|
81
|
+
#
|
|
82
|
+
# Set to 1 for Option A and Option B: every claude spawn strips
|
|
83
|
+
# ANTHROPIC_API_KEY from its env so claude rides the subscription (token or
|
|
84
|
+
# keychain) — routine cadence ticks then cost zero API credits.
|
|
77
85
|
MAESTRO_PREFER_SUBSCRIPTION_AUTH=
|
|
78
86
|
|
|
87
|
+
# Option C — API key (pay-per-token)
|
|
88
|
+
# Set ANTHROPIC_API_KEY to a valid sk-ant-api03-... key and leave the two
|
|
89
|
+
# above empty. Get one: https://console.anthropic.com/settings/keys
|
|
90
|
+
# Doctor validates it against api.anthropic.com on every run; an invalid
|
|
91
|
+
# key cascades 401s through every sub-session spawn.
|
|
92
|
+
ANTHROPIC_API_KEY=
|
|
93
|
+
|
|
79
94
|
# OPTIONAL — Supplemental model access (GPT-4, embeddings)
|
|
80
95
|
# Get your key: https://platform.openai.com/api-keys
|
|
81
96
|
# Subscription: OpenAI API plan (pay-per-token)
|
package/README.md
CHANGED
|
@@ -568,6 +568,8 @@ Autonomous work is designed to survive reboot and power loss, and the agent degr
|
|
|
568
568
|
|
|
569
569
|
- [Setup Wizard](docs/guides/setup-wizard.md) -- `maestro setup`: sections, resume/checkpoint, source of truth, enrichment, verify probe, all flags
|
|
570
570
|
- [Agent Persona Setup](docs/guides/agent-persona-setup.md) -- Identity, operating charter, and behavioural policies
|
|
571
|
+
- [Front-door Session](docs/guides/front-door-session.md) -- The always-on main session: what runs, how to attach, stop, restart, and read its status
|
|
572
|
+
- [Mac mini bring-up](docs/guides/mac-mini.md) -- Boxed mini to enrolled seat: token, create, setup, session install, Tailscale SSH, doctor
|
|
571
573
|
- [Email Setup](docs/guides/email-setup.md) -- Gmail IMAP polling, SMTP sending, thread dedup, signatures
|
|
572
574
|
- [Slack Setup](docs/guides/slack-setup.md) -- Slack app, tokens, sending, typing indicators, events, CDP
|
|
573
575
|
- [WhatsApp Setup](docs/guides/whatsapp-setup.md) -- Twilio WhatsApp sandbox/production, webhook handler
|
package/bin/maestro.mjs
CHANGED
|
@@ -816,12 +816,24 @@ function loadShippedManifest(cwd) {
|
|
|
816
816
|
function writeShippedManifest(cwd, files) {
|
|
817
817
|
const dst = join(cwd, SHIPPED_MANIFEST_REL);
|
|
818
818
|
mkdirSync(dirname(dst), { recursive: true });
|
|
819
|
+
// `sdkVersion` (2.12+) is what `upgrade --verify` compares against the
|
|
820
|
+
// running package; `version` is the manifest FORMAT and stays 1.
|
|
819
821
|
writeFileSync(
|
|
820
822
|
dst,
|
|
821
|
-
JSON.stringify({ version: 1, files: [...files].sort() }, null, 2) + "\n",
|
|
823
|
+
JSON.stringify({ version: 1, sdkVersion: readFrameworkVersion(), files: [...files].sort() }, null, 2) + "\n",
|
|
822
824
|
);
|
|
823
825
|
}
|
|
824
826
|
|
|
827
|
+
/** The SDK version the previous upgrade recorded here, or null (pre-2.12 manifest / none). */
|
|
828
|
+
function readShippedManifestVersion(cwd) {
|
|
829
|
+
try {
|
|
830
|
+
const raw = JSON.parse(readFileSync(join(cwd, SHIPPED_MANIFEST_REL), "utf-8"));
|
|
831
|
+
return typeof raw?.sdkVersion === "string" ? raw.sdkVersion : null;
|
|
832
|
+
} catch {
|
|
833
|
+
return null; // absent or unreadable — "from" is simply unknown
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
825
837
|
function sha256File(p) {
|
|
826
838
|
return createHash("sha256").update(readFileSync(p)).digest("hex");
|
|
827
839
|
}
|
|
@@ -1130,10 +1142,12 @@ function parseUpgradeFlags(args) {
|
|
|
1130
1142
|
noIncoming: false,
|
|
1131
1143
|
verbose: false,
|
|
1132
1144
|
noPrune: false,
|
|
1145
|
+
verify: false,
|
|
1133
1146
|
};
|
|
1134
1147
|
for (const a of args) {
|
|
1135
1148
|
if (a === "--dry-run" || a === "-n") flags.dryRun = true;
|
|
1136
1149
|
else if (a === "--force-overwrite" || a === "--force") flags.forceOverwrite = true;
|
|
1150
|
+
else if (a === "--verify") flags.verify = true;
|
|
1137
1151
|
else if (a === "--no-incoming") flags.noIncoming = true;
|
|
1138
1152
|
else if (a === "--no-prune") flags.noPrune = true;
|
|
1139
1153
|
else if (a === "--verbose" || a === "-v") flags.verbose = true;
|
|
@@ -1154,9 +1168,25 @@ Flags:
|
|
|
1154
1168
|
--force-overwrite Overwrite even locally-modified files (backs them up)
|
|
1155
1169
|
--no-incoming Don't write .maestro/incoming/ shadows for preserved files
|
|
1156
1170
|
--no-prune Keep framework files that upstream has deleted
|
|
1171
|
+
--verify Report only: is this seat wholly on this SDK version
|
|
1172
|
+
(global bin, local package, shipped manifest, running
|
|
1173
|
+
daemon, plists installed + loaded, global-setup)?
|
|
1174
|
+
Exit 1 on any mismatch. doctor prints the same rows.
|
|
1157
1175
|
--verbose, -v Print classification for every file
|
|
1158
1176
|
--help, -h Show this help
|
|
1159
1177
|
|
|
1178
|
+
After the file merge (every non-dry run, each step fail-open, reported here
|
|
1179
|
+
and in .maestro/upgrade-result.json {from,to,at,steps}):
|
|
1180
|
+
plists scripts/local-triggers/generate-plists.sh
|
|
1181
|
+
launchd install / replace / load every generated plist under
|
|
1182
|
+
~/Library/LaunchAgents (mode 600, launchctl bootstrap);
|
|
1183
|
+
-daemon and -session are rewritten, never restarted
|
|
1184
|
+
globalSetup maestro global-setup (identity block, cohort MCP, settings, skills,
|
|
1185
|
+
vendored design skill packs)
|
|
1186
|
+
globalInstall npm i -g @cohortapp/agent-sdk@<this version>
|
|
1187
|
+
(MAESTRO_SKIP_GLOBAL_INSTALL=1 skips)
|
|
1188
|
+
verify the --verify report
|
|
1189
|
+
|
|
1160
1190
|
Per-file behaviour:
|
|
1161
1191
|
added — new upstream file → copy
|
|
1162
1192
|
updated — existed, byte-equal to your committed copy → safe overwrite
|
|
@@ -1190,6 +1220,16 @@ Per-file behaviour:
|
|
|
1190
1220
|
process.exit(1);
|
|
1191
1221
|
}
|
|
1192
1222
|
|
|
1223
|
+
if (flags.verify) {
|
|
1224
|
+
// Report only — nothing is written. lib/upgrade/verify.mjs is the core;
|
|
1225
|
+
// the same rows appear in `maestro doctor`.
|
|
1226
|
+
const { runVerify, formatVerifyReport } = await import("../lib/upgrade/verify.mjs");
|
|
1227
|
+
const report = runVerify({ cwd, expected: readFrameworkVersion() });
|
|
1228
|
+
for (const line of formatVerifyReport(report)) console.log(line);
|
|
1229
|
+
process.exitCode = report.ok ? 0 : 1;
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1193
1233
|
const inGit = isGitRepo(cwd);
|
|
1194
1234
|
if (!inGit) {
|
|
1195
1235
|
warn("Not in a git repo — cannot detect local modifications.");
|
|
@@ -1330,6 +1370,8 @@ Per-file behaviour:
|
|
|
1330
1370
|
// Removals are backed up under .maestro/backup/ exactly like a forced
|
|
1331
1371
|
// overwrite, so a prune is always reversible.
|
|
1332
1372
|
const priorManifest = loadShippedManifest(cwd);
|
|
1373
|
+
// What this machine ran before this upgrade — the `from` of upgrade-result.json.
|
|
1374
|
+
const priorSdkVersion = readShippedManifestVersion(cwd);
|
|
1333
1375
|
|
|
1334
1376
|
if (flags.noPrune) {
|
|
1335
1377
|
log("Prune skipped (--no-prune): upstream-deleted files left in place.");
|
|
@@ -1614,6 +1656,31 @@ Per-file behaviour:
|
|
|
1614
1656
|
}
|
|
1615
1657
|
}
|
|
1616
1658
|
|
|
1659
|
+
// Post-steps (design 2026-09-08, WP-M6): plists → launchd reconcile →
|
|
1660
|
+
// global-setup → global npm install → verify. This is what puts an existing
|
|
1661
|
+
// seat wholly on the new architecture on the first hop from ANY old version,
|
|
1662
|
+
// because the old autoupdate.sh runs THIS upgrade and nothing else. Each
|
|
1663
|
+
// step is fail-open; the daemon is restarted by autoupdate, never here.
|
|
1664
|
+
if (!flags.dryRun) {
|
|
1665
|
+
console.log();
|
|
1666
|
+
console.log(`${C.bold}Seat reconcile${C.reset}`);
|
|
1667
|
+
try {
|
|
1668
|
+
const { runUpgradePostSteps } = await import("../lib/upgrade/post-steps.mjs");
|
|
1669
|
+
await runUpgradePostSteps({
|
|
1670
|
+
cwd,
|
|
1671
|
+
sdkVersion: readFrameworkVersion(),
|
|
1672
|
+
from: priorSdkVersion,
|
|
1673
|
+
say: { log: (m) => console.log(` ${m}`), ok, warn },
|
|
1674
|
+
// global-setup needs config/agent.json + the collective hook runner;
|
|
1675
|
+
// a repo without them is reported as a skipped step, not a crash.
|
|
1676
|
+
globalSetup: isAgentRepo(cwd) ? () => globalSetup([cwd]) : null,
|
|
1677
|
+
});
|
|
1678
|
+
log("Result: .maestro/upgrade-result.json · re-check any time: maestro upgrade --verify");
|
|
1679
|
+
} catch (e) {
|
|
1680
|
+
warn(`seat reconcile skipped: ${e && e.message ? e.message : e}`);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1617
1684
|
console.log();
|
|
1618
1685
|
log("Agent-specific paths (config/, CLAUDE.md, knowledge/, memory/, state/, outputs/, logs/, .env) were NOT touched.");
|
|
1619
1686
|
}
|
|
@@ -2301,6 +2368,35 @@ async function doctor() {
|
|
|
2301
2368
|
} catch { /* pmset unavailable (non-mac / sandbox) — skip */ }
|
|
2302
2369
|
}
|
|
2303
2370
|
|
|
2371
|
+
// Claude auth, the COHORT_ORG_ID id-vs-slug trap, the effective source of
|
|
2372
|
+
// every Cohort value, the global SDK install, Tailscale SSH and the main
|
|
2373
|
+
// session job — mode-aware, so a seat on the subscription token is no
|
|
2374
|
+
// longer told "ANTHROPIC_API_KEY not set". Pure checks + one injected edge
|
|
2375
|
+
// live in lib/cli/doctor-checks.mjs (design 2026-09-08 §3.9).
|
|
2376
|
+
try {
|
|
2377
|
+
const { runDoctorChecks } = await import("../lib/cli/doctor-checks.mjs");
|
|
2378
|
+
for (const r of await runDoctorChecks({ cwd })) {
|
|
2379
|
+
if (r.level === "ok") ok(r.msg);
|
|
2380
|
+
else if (r.level === "warn") warn(r.msg);
|
|
2381
|
+
else { fail(r.msg); issues++; }
|
|
2382
|
+
}
|
|
2383
|
+
} catch (err) {
|
|
2384
|
+
warn(`auth / front-door checks unavailable: ${err && err.message ? err.message : err}`);
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// `maestro upgrade --verify` rows: one SDK version end to end (global bin,
|
|
2388
|
+
// local package, shipped manifest, running daemon, plists, global-setup).
|
|
2389
|
+
try {
|
|
2390
|
+
const { runVerify } = await import("../lib/upgrade/verify.mjs");
|
|
2391
|
+
for (const r of runVerify({ cwd, expected: readFrameworkVersion() }).rows) {
|
|
2392
|
+
if (r.level === "ok") ok(r.msg);
|
|
2393
|
+
else if (r.level === "warn") warn(r.msg);
|
|
2394
|
+
else { fail(r.msg); issues++; }
|
|
2395
|
+
}
|
|
2396
|
+
} catch (err) {
|
|
2397
|
+
warn(`upgrade verify unavailable: ${err && err.message ? err.message : err}`);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2304
2400
|
// ── .env ────────────────────────────────────────────────────────────────
|
|
2305
2401
|
if (existsSync(join(cwd, ".env"))) {
|
|
2306
2402
|
// .env permission hygiene (setup-onboarding W4): every credential — Slack,
|
|
@@ -2327,46 +2423,9 @@ async function doctor() {
|
|
|
2327
2423
|
else if (required) { warn(`${key} not set`); issues++; }
|
|
2328
2424
|
else warn(`${key} not set (optional)`);
|
|
2329
2425
|
};
|
|
2330
|
-
check("ANTHROPIC_API_KEY", true);
|
|
2331
2426
|
check("SLACK_USER_TOKEN", false);
|
|
2332
2427
|
check("GMAIL_APP_PASSWORD", false);
|
|
2333
2428
|
|
|
2334
|
-
// Auth validity: if ANTHROPIC_API_KEY is set, ping the API to
|
|
2335
|
-
// verify it works. An invalid key in .env will silently be sent
|
|
2336
|
-
// to every `claude --print` sub-session and cause cascading 401s
|
|
2337
|
-
// (exactly the ravi-ai inbox-processor runaway). Better to catch
|
|
2338
|
-
// it here. Skips the check if the user opted out via
|
|
2339
|
-
// MAESTRO_PREFER_SUBSCRIPTION_AUTH=1 (subscription wins).
|
|
2340
|
-
const keyMatch = env.match(/^ANTHROPIC_API_KEY=(.+)$/m);
|
|
2341
|
-
const preferSubsMatch = env.match(/^MAESTRO_PREFER_SUBSCRIPTION_AUTH=(.+)$/m);
|
|
2342
|
-
const preferSubs = preferSubsMatch && /^1|true|yes$/i.test(preferSubsMatch[1].trim());
|
|
2343
|
-
if (keyMatch && !preferSubs) {
|
|
2344
|
-
const key = keyMatch[1].trim().replace(/^"|"$/g, "");
|
|
2345
|
-
try {
|
|
2346
|
-
const result = spawnSync("curl", [
|
|
2347
|
-
"-s", "-o", "/dev/null", "-w", "%{http_code}",
|
|
2348
|
-
"-X", "POST",
|
|
2349
|
-
"-H", `x-api-key: ${key}`,
|
|
2350
|
-
"-H", "anthropic-version: 2023-06-01",
|
|
2351
|
-
"-H", "content-type: application/json",
|
|
2352
|
-
"--max-time", "8",
|
|
2353
|
-
"https://api.anthropic.com/v1/messages",
|
|
2354
|
-
"-d", JSON.stringify({ model: "claude-haiku-4-5", max_tokens: 5, messages: [{ role: "user", content: "ping" }] }),
|
|
2355
|
-
], { encoding: "utf-8" });
|
|
2356
|
-
const code = (result.stdout || "").trim();
|
|
2357
|
-
if (code === "200") ok("ANTHROPIC_API_KEY validated against api.anthropic.com");
|
|
2358
|
-
else if (code === "401") {
|
|
2359
|
-
warn(`ANTHROPIC_API_KEY is INVALID (HTTP 401 from api.anthropic.com).`);
|
|
2360
|
-
warn(` This will cause every sub-session spawn to fail. Either:`);
|
|
2361
|
-
warn(` 1. Replace the key in .env with a valid one, OR`);
|
|
2362
|
-
warn(` 2. Set MAESTRO_PREFER_SUBSCRIPTION_AUTH=1 in .env to use Claude Code subscription auth.`);
|
|
2363
|
-
issues++;
|
|
2364
|
-
} else if (code) warn(`ANTHROPIC_API_KEY check returned HTTP ${code} (expected 200)`);
|
|
2365
|
-
else warn(`ANTHROPIC_API_KEY check skipped (no network / curl missing)`);
|
|
2366
|
-
} catch { warn("ANTHROPIC_API_KEY check failed (curl error)"); }
|
|
2367
|
-
} else if (preferSubs) {
|
|
2368
|
-
ok("MAESTRO_PREFER_SUBSCRIPTION_AUTH=1 — using Claude Code subscription (Keychain OAuth)");
|
|
2369
|
-
}
|
|
2370
2429
|
|
|
2371
2430
|
// ── Slack Socket Mode ────────────────────────────────────────────────
|
|
2372
2431
|
// When SLACK_APP_LEVEL_TOKEN is set, verify the launchd job is loaded
|
|
@@ -3297,6 +3356,14 @@ async function globalSetup(args = []) {
|
|
|
3297
3356
|
if (!dryRun) writeFileSync(pointerFile, JSON.stringify(gc.buildPointer(cwd, agentName), null, 2));
|
|
3298
3357
|
ok(`${dryRun ? "[dry-run] " : ""}machine pointer → ${pointerFile}`);
|
|
3299
3358
|
|
|
3359
|
+
// 4) Front-door session (2026-09 §3.7): identity block, cohort MCP server,
|
|
3360
|
+
// settings additions, global skills and the vendored design skill packs
|
|
3361
|
+
// (WP-M7) — lib/cli/global-setup-extras.mjs.
|
|
3362
|
+
try {
|
|
3363
|
+
const extras = await import("../lib/cli/global-setup-extras.mjs");
|
|
3364
|
+
await extras.applyFrontDoorSetup({ agentRoot: cwd, claudeDir, dryRun, log, ok, warn });
|
|
3365
|
+
} catch (err) { warn(`front-door setup skipped: ${err && err.message}`); }
|
|
3366
|
+
|
|
3300
3367
|
console.log();
|
|
3301
3368
|
if (dryRun) {
|
|
3302
3369
|
log("Dry run complete — no files written.");
|
|
@@ -3737,6 +3804,11 @@ switch (command) {
|
|
|
3737
3804
|
case "router": await routerCmd(args); break;
|
|
3738
3805
|
case "secrets": await secretsCmd(args); break;
|
|
3739
3806
|
case "who-owns": case "who": await whoOwnsCmd(args); break;
|
|
3807
|
+
case "inbox": process.exitCode = await (await import("../lib/cli/inbox.mjs")).runInbox(args); break;
|
|
3808
|
+
case "session-ack": process.exitCode = await (await import("../lib/cli/session-ack.mjs")).runSessionAck(args); break;
|
|
3809
|
+
case "session": { const r = await (await import("../lib/cli/session.mjs")).run(args); process.exitCode = r.code; break; }
|
|
3810
|
+
case "board": await (await import("../lib/cli/board.mjs")).boardCmd(args); break;
|
|
3811
|
+
case "design": await (await import("../lib/cli/design.mjs")).designCmd(args); break;
|
|
3740
3812
|
case "init":
|
|
3741
3813
|
case "update-init":
|
|
3742
3814
|
case "init-update":
|
|
@@ -3750,7 +3822,8 @@ Usage:
|
|
|
3750
3822
|
npx @cohortapp/agent-sdk setup [section] [flags] Configure this agent (deterministic, resumable wizard)
|
|
3751
3823
|
npx @cohortapp/agent-sdk pair <code> Open a pre-auth pairing handshake (org admin approves it)
|
|
3752
3824
|
npx @cohortapp/agent-sdk sync [--force] [--dry-run] Re-pull this agent's record from Cohort into config/agent.json
|
|
3753
|
-
npx @cohortapp/agent-sdk upgrade [--dry-run] Update framework files
|
|
3825
|
+
npx @cohortapp/agent-sdk upgrade [--dry-run] Update framework files (+ plists, launchd, global-setup, global install)
|
|
3826
|
+
npx @cohortapp/agent-sdk upgrade --verify Is this seat wholly on this SDK version? (exit 1 on mismatch)
|
|
3754
3827
|
npx @cohortapp/agent-sdk init [feature] [--apply] Run pending feature-init steps
|
|
3755
3828
|
npx @cohortapp/agent-sdk global-setup [--dry-run] Wire collective memory into ~/.claude (machine-wide)
|
|
3756
3829
|
npx @cohortapp/agent-sdk doctor Verify installation
|
|
@@ -3761,6 +3834,11 @@ Usage:
|
|
|
3761
3834
|
npx @cohortapp/agent-sdk secrets sync [--name X] Pull org secrets from the broker into the local store
|
|
3762
3835
|
npx @cohortapp/agent-sdk secrets rotate --name X Rotate a secret (new value read from stdin)
|
|
3763
3836
|
npx @cohortapp/agent-sdk who-owns <scope> Who owns a scope / escalate-to / reports (org context, zero-LLM)
|
|
3837
|
+
npx @cohortapp/agent-sdk inbox list|show|claim|reply|done|defer The main session's inbox (JSON; shares the daemon's markers)
|
|
3838
|
+
npx @cohortapp/agent-sdk session-ack <tickId> Ack a cadence tick handed to the main session
|
|
3839
|
+
npx @cohortapp/agent-sdk session <cmd> Front-door session: status|attach|start|stop|restart|spawn|peers|handoffs|ack
|
|
3840
|
+
npx @cohortapp/agent-sdk board mine|track|claim|complete Your work across every board (maestro board --help)
|
|
3841
|
+
npx @cohortapp/agent-sdk design sync [--out <dir>] Pull the brand foundation → DESIGN.md + PRODUCT.md
|
|
3764
3842
|
|
|
3765
3843
|
Upgrade flags:
|
|
3766
3844
|
--dry-run, -n Preview changes without writing
|
package/bin/maestro.test.mjs
CHANGED
|
@@ -50,6 +50,65 @@ async function rmRoot(path) {
|
|
|
50
50
|
try { await fsp.rm(path, { recursive: true, force: true }); } catch { /* */ }
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// The machine the CLI must NEVER touch from a test: the developer's real
|
|
55
|
+
// ~/Library/LaunchAgents, ~/.claude and global npm. `upgrade` now reconciles
|
|
56
|
+
// all three (WP-M6), so every CLI run gets a throwaway HOME, a `launchctl`
|
|
57
|
+
// stub that treats "installed" as "loaded" (in `list` AND in `print gui/<uid>`,
|
|
58
|
+
// which is what the reconcile reads), an `npm` stub, a `claude` stub (logs and
|
|
59
|
+
// exits 1 → global-setup's JSON fallback) and MAESTRO_SKIP_GLOBAL_INSTALL=1.
|
|
60
|
+
// The claude stub is reached through CLAUDE_BIN, not PATH: lib/claude-bin.mjs
|
|
61
|
+
// resolves ~/.local/bin, /opt/homebrew/bin, /usr/local/bin BEFORE PATH, so a
|
|
62
|
+
// PATH-only stub is silently bypassed by the developer's real `claude`.
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
const TEST_SHIM = await tmpRoot("maestro-shim-global");
|
|
66
|
+
{
|
|
67
|
+
const stub = async (name, body) => { writeFileSync(join(TEST_SHIM, name), `#!/bin/bash\n${body}\n`); await fsp.chmod(join(TEST_SHIM, name), 0o755); };
|
|
68
|
+
await stub("launchctl", `echo "launchctl $*" >> "$HOME/launchctl.log"
|
|
69
|
+
if [ "$1" = "list" ]; then
|
|
70
|
+
if [ -n "\${2:-}" ]; then [ -f "$HOME/Library/LaunchAgents/$2.plist" ] && exit 0 || exit 113; fi
|
|
71
|
+
printf 'PID\tStatus\tLabel\n'
|
|
72
|
+
for f in "$HOME"/Library/LaunchAgents/*.plist; do [ -f "$f" ] && printf -- '-\t0\t%s\n' "$(basename "$f" .plist)"; done
|
|
73
|
+
exit 0
|
|
74
|
+
fi
|
|
75
|
+
if [ "$1" = "print" ]; then
|
|
76
|
+
case "$2" in
|
|
77
|
+
gui/*/*) l="\${2##*/}"; if [ -f "$HOME/Library/LaunchAgents/$l.plist" ]; then echo "$l = {}"; exit 0; fi; echo "Could not find service \"$l\" in domain for user gui: 501" >&2; exit 113 ;;
|
|
78
|
+
gui/*) printf '%s = {\n\tservices = {\n' "$2"; for f in "$HOME"/Library/LaunchAgents/*.plist; do [ -f "$f" ] && printf '\t\t0\t0\t%s\n' "$(basename "$f" .plist)"; done; printf '\t}\n}\n'; exit 0 ;;
|
|
79
|
+
esac
|
|
80
|
+
fi
|
|
81
|
+
exit 0`);
|
|
82
|
+
await stub("npm", `echo "npm $*" >> "$HOME/npm.log"
|
|
83
|
+
case "$1" in
|
|
84
|
+
ls) echo '{}'; exit 1 ;;
|
|
85
|
+
view) exit 1 ;;
|
|
86
|
+
root) echo "$HOME/npm-global/lib/node_modules"; exit 0 ;;
|
|
87
|
+
esac
|
|
88
|
+
exit 0`);
|
|
89
|
+
await stub("claude", `echo "claude $*" >> "$HOME/claude.log"
|
|
90
|
+
exit 1`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A throwaway HOME for one CLI run (or a shared one when the test does not care). */
|
|
94
|
+
async function freshHome() {
|
|
95
|
+
const home = await tmpRoot("maestro-home");
|
|
96
|
+
mkdirSync(join(home, "Library", "LaunchAgents"), { recursive: true });
|
|
97
|
+
return home;
|
|
98
|
+
}
|
|
99
|
+
const TEST_HOME = await freshHome();
|
|
100
|
+
|
|
101
|
+
function baseEnv(extra = {}) {
|
|
102
|
+
return {
|
|
103
|
+
...process.env,
|
|
104
|
+
HOME: TEST_HOME,
|
|
105
|
+
PATH: `${TEST_SHIM}:${process.env.PATH || ""}`,
|
|
106
|
+
CLAUDE_BIN: join(TEST_SHIM, "claude"),
|
|
107
|
+
MAESTRO_SKIP_GLOBAL_INSTALL: "1",
|
|
108
|
+
...extra,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
53
112
|
/**
|
|
54
113
|
* Build a PATH that shadows `npm` (and optionally `git`) with no-op shims
|
|
55
114
|
* so `create` doesn't actually try to install dependencies. Returns the new
|
|
@@ -61,14 +120,14 @@ async function withShim() {
|
|
|
61
120
|
writeFileSync(npm, "#!/bin/bash\necho '[shim npm]' \"$@\" >&2\nexit 0\n");
|
|
62
121
|
await fsp.chmod(npm, 0o755);
|
|
63
122
|
// Keep real git (we want git init to actually init).
|
|
64
|
-
const env = {
|
|
123
|
+
const env = baseEnv({ PATH: `${dir}:${TEST_SHIM}:${process.env.PATH || ""}` });
|
|
65
124
|
return { env, dir };
|
|
66
125
|
}
|
|
67
126
|
|
|
68
127
|
function runCli(args, cwd, env) {
|
|
69
128
|
return spawnSync(process.execPath, [MAESTRO_CLI, ...args], {
|
|
70
129
|
cwd: cwd || process.cwd(),
|
|
71
|
-
env: env ||
|
|
130
|
+
env: env || baseEnv(),
|
|
72
131
|
encoding: "utf-8",
|
|
73
132
|
});
|
|
74
133
|
}
|
|
@@ -77,7 +136,7 @@ function runCli(args, cwd, env) {
|
|
|
77
136
|
function runCliStdin(args, cwd, input, env) {
|
|
78
137
|
return spawnSync(process.execPath, [MAESTRO_CLI, ...args], {
|
|
79
138
|
cwd: cwd || process.cwd(),
|
|
80
|
-
env: env ||
|
|
139
|
+
env: env || baseEnv(),
|
|
81
140
|
input,
|
|
82
141
|
encoding: "utf-8",
|
|
83
142
|
});
|
|
@@ -1325,10 +1384,17 @@ test("upgrade prune never removes machine-generated launchd plists", async () =>
|
|
|
1325
1384
|
try {
|
|
1326
1385
|
const r = runCli(["upgrade"], root);
|
|
1327
1386
|
assert.equal(r.status, 0, r.stderr);
|
|
1387
|
+
// Prune backs a file up under .maestro/backup/<path> before unlinking it
|
|
1388
|
+
// and lists it as "pruned"; neither may happen here. (The file itself is
|
|
1389
|
+
// gone after the run since WP-M6 — generate-plists.sh runs unconditionally
|
|
1390
|
+
// as the first post-step and, as it always has, clears scripts/local-
|
|
1391
|
+
// triggers/plists/ before rendering this machine's set. That is the
|
|
1392
|
+
// generator owning its directory, not prune eating machine config.)
|
|
1328
1393
|
assert.ok(
|
|
1329
|
-
existsSync(join(root, plist)),
|
|
1330
|
-
"generated plists are machine config; prune must leave them alone",
|
|
1394
|
+
!existsSync(join(root, ".maestro", "backup", plist)),
|
|
1395
|
+
"generated plists are machine config; prune must leave them alone (no prune backup)",
|
|
1331
1396
|
);
|
|
1397
|
+
assert.ok(!/pruned/.test(r.stdout), `prune must not report the plist:\n${r.stdout}`);
|
|
1332
1398
|
} finally {
|
|
1333
1399
|
await fsp.rm(root, { recursive: true, force: true });
|
|
1334
1400
|
}
|
|
@@ -1402,3 +1468,107 @@ test("upgrade --dry-run writes no shipped-manifest", async () => {
|
|
|
1402
1468
|
await fsp.rm(root, { recursive: true, force: true });
|
|
1403
1469
|
}
|
|
1404
1470
|
});
|
|
1471
|
+
|
|
1472
|
+
// ---------------------------------------------------------------------------
|
|
1473
|
+
// upgrade — seat reconcile post-steps (WP-M6): the first hop from an old
|
|
1474
|
+
// version must leave the machine wholly on the new architecture by itself.
|
|
1475
|
+
// ---------------------------------------------------------------------------
|
|
1476
|
+
|
|
1477
|
+
/** makeLegacyAgent + a NAMED config/agent.json, so plist labels and the identity block use "legacy". */
|
|
1478
|
+
async function makeNamedLegacyAgent() {
|
|
1479
|
+
const root = await makeLegacyAgent();
|
|
1480
|
+
writeFileSync(join(root, "config/agent.json"), JSON.stringify({ firstName: "Legacy", lastName: "Tester", fullName: "Legacy Tester" }, null, 2));
|
|
1481
|
+
execFileSync("git", ["add", "-A"], { cwd: root });
|
|
1482
|
+
execFileSync("git", ["-c", "user.email=test@test", "-c", "user.name=test", "commit", "-q", "-m", "name"], { cwd: root });
|
|
1483
|
+
return root;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
test("upgrade installs the -session plist into $HOME/Library/LaunchAgents (600, bootstrapped), never kickstarts daemon/session, records sdkVersion in the manifest and writes .maestro/upgrade-result.json", async () => {
|
|
1487
|
+
const root = await makeNamedLegacyAgent();
|
|
1488
|
+
const home = await freshHome();
|
|
1489
|
+
try {
|
|
1490
|
+
// An existing seat: the daemon job is installed (old content) and loaded.
|
|
1491
|
+
writeFileSync(join(home, "Library", "LaunchAgents", "ai.maestro.legacy-daemon.plist"), "<plist>old daemon</plist>");
|
|
1492
|
+
const r = runCli(["upgrade"], root, baseEnv({ HOME: home }));
|
|
1493
|
+
assert.equal(r.status, 0, r.stderr + r.stdout);
|
|
1494
|
+
|
|
1495
|
+
const la = join(home, "Library", "LaunchAgents");
|
|
1496
|
+
const sessionPlist = join(la, "ai.maestro.legacy-session.plist");
|
|
1497
|
+
assert.ok(existsSync(sessionPlist), `session plist must be installed; stdout:\n${r.stdout}`);
|
|
1498
|
+
assert.equal(statSync(sessionPlist).mode & 0o777, 0o600);
|
|
1499
|
+
assert.match(readFileSync(sessionPlist, "utf-8"), /scripts\/session\/supervisor\.sh/);
|
|
1500
|
+
assert.match(readFileSync(join(la, "ai.maestro.legacy-daemon.plist"), "utf-8"), /launchd-wrapper\.sh/, "daemon plist rewritten to the generated one");
|
|
1501
|
+
|
|
1502
|
+
const calls = readFileSync(join(home, "launchctl.log"), "utf-8");
|
|
1503
|
+
assert.match(calls, new RegExp(`^launchctl bootstrap gui/\\d+ ${sessionPlist.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m"), calls);
|
|
1504
|
+
assert.ok(!/kickstart/.test(calls), `nothing is kickstarted by upgrade (the -autoupdate job is the one it runs inside):\n${calls}`);
|
|
1505
|
+
assert.ok(!/bootout|unload/.test(calls), calls);
|
|
1506
|
+
assert.match(calls, /^launchctl print gui\/\d+$/m, "loaded-ness is read from the gui domain");
|
|
1507
|
+
|
|
1508
|
+
// The claude STUB handled `mcp add-json` — not the developer's real CLI.
|
|
1509
|
+
// lib/claude-bin.mjs would otherwise pick /usr/local/bin/claude over PATH;
|
|
1510
|
+
// CLAUDE_BIN in baseEnv is what makes this isolation real.
|
|
1511
|
+
assert.ok(existsSync(join(home, "claude.log")), "the claude stub must be the binary global-setup reached");
|
|
1512
|
+
assert.match(readFileSync(join(home, "claude.log"), "utf-8"), /^claude mcp add-json cohort .* --scope user$/m);
|
|
1513
|
+
const claudeJson = JSON.parse(readFileSync(join(home, ".claude.json"), "utf-8"));
|
|
1514
|
+
assert.equal(claudeJson.mcpServers.cohort.env.COHORT_AGENT_ROOT.replace(/^\/private/, ""), root.replace(/^\/private/, ""), "stub exit 1 → the JSON merge fallback registered the server");
|
|
1515
|
+
assert.ok(!("firstStartVersion" in claudeJson), "a real `claude` would have stamped its own keys into ~/.claude.json");
|
|
1516
|
+
assert.ok(!existsSync(join(home, "npm.log")) || !/npm install -g/.test(readFileSync(join(home, "npm.log"), "utf-8")), "no global install under MAESTRO_SKIP_GLOBAL_INSTALL=1");
|
|
1517
|
+
|
|
1518
|
+
const manifest = JSON.parse(readFileSync(join(root, ".maestro/shipped-manifest.json"), "utf-8"));
|
|
1519
|
+
const sdkVersion = JSON.parse(readFileSync(join(MAESTRO_ROOT, "package.json"), "utf-8")).version;
|
|
1520
|
+
assert.equal(manifest.sdkVersion, sdkVersion);
|
|
1521
|
+
assert.equal(manifest.version, 1, "the manifest FORMAT version is untouched");
|
|
1522
|
+
|
|
1523
|
+
const result = JSON.parse(readFileSync(join(root, ".maestro/upgrade-result.json"), "utf-8"));
|
|
1524
|
+
assert.equal(result.to, sdkVersion);
|
|
1525
|
+
assert.equal(result.from, null, "no prior manifest → from is unknown");
|
|
1526
|
+
assert.match(result.at, /^\d{4}-\d{2}-\d{2}T/);
|
|
1527
|
+
assert.deepEqual(Object.keys(result.steps), ["plists", "launchd", "globalSetup", "globalInstall", "verify"]);
|
|
1528
|
+
assert.equal(result.steps.plists.ok, true, result.steps.plists.detail);
|
|
1529
|
+
assert.equal(result.steps.launchd.ok, true, result.steps.launchd.detail);
|
|
1530
|
+
assert.equal(result.steps.globalSetup.ok, true, result.steps.globalSetup.detail);
|
|
1531
|
+
assert.match(result.steps.globalInstall.detail, /MAESTRO_SKIP_GLOBAL_INSTALL/);
|
|
1532
|
+
assert.match(r.stdout, /Seat reconcile/);
|
|
1533
|
+
|
|
1534
|
+
// global-setup ran for real against the throwaway HOME.
|
|
1535
|
+
assert.match(readFileSync(join(home, ".claude", "CLAUDE.md"), "utf-8"), /BEGIN maestro:identity/);
|
|
1536
|
+
assert.ok(existsSync(join(home, ".claude", "skills", "maestro-main-session", "SKILL.md")));
|
|
1537
|
+
|
|
1538
|
+
// Second run: from is the recorded version, the session job is unchanged.
|
|
1539
|
+
const again = runCli(["upgrade"], root, baseEnv({ HOME: home }));
|
|
1540
|
+
assert.equal(again.status, 0, again.stderr);
|
|
1541
|
+
const result2 = JSON.parse(readFileSync(join(root, ".maestro/upgrade-result.json"), "utf-8"));
|
|
1542
|
+
assert.equal(result2.from, sdkVersion);
|
|
1543
|
+
assert.match(result2.steps.launchd.detail, /0 installed, 0 replaced/);
|
|
1544
|
+
assert.match(result.steps.launchd.detail, /1 replaced/, "the pre-installed daemon plist is rewritten in place");
|
|
1545
|
+
} finally { await rmRoot(root); await rmRoot(home); }
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
test("upgrade --verify exits 0 on a reconciled seat and 1 once a generated plist is missing; doctor prints the same rows; --dry-run runs no post-steps", async () => {
|
|
1549
|
+
const root = await makeNamedLegacyAgent();
|
|
1550
|
+
const home = await freshHome();
|
|
1551
|
+
try {
|
|
1552
|
+
const dry = runCli(["upgrade", "--dry-run"], root, baseEnv({ HOME: home }));
|
|
1553
|
+
assert.equal(dry.status, 0, dry.stderr);
|
|
1554
|
+
assert.ok(!existsSync(join(root, ".maestro/upgrade-result.json")), "--dry-run writes no result");
|
|
1555
|
+
assert.ok(!existsSync(join(home, "Library", "LaunchAgents", "ai.maestro.legacy-session.plist")), "--dry-run installs nothing");
|
|
1556
|
+
|
|
1557
|
+
assert.equal(runCli(["upgrade"], root, baseEnv({ HOME: home })).status, 0);
|
|
1558
|
+
const v = runCli(["upgrade", "--verify"], root, baseEnv({ HOME: home }));
|
|
1559
|
+
assert.equal(v.status, 0, `verify should pass on a reconciled seat:\n${v.stdout}${v.stderr}`);
|
|
1560
|
+
assert.match(v.stdout, /generated plist\(s\) installed and loaded/);
|
|
1561
|
+
assert.match(v.stdout, /identity, MCP, settings, pointer and skills present/);
|
|
1562
|
+
assert.match(v.stdout, /verify: seat is on/);
|
|
1563
|
+
|
|
1564
|
+
execFileSync("rm", [join(home, "Library", "LaunchAgents", "ai.maestro.legacy-session.plist")]);
|
|
1565
|
+
const bad = runCli(["upgrade", "--verify"], root, baseEnv({ HOME: home }));
|
|
1566
|
+
assert.equal(bad.status, 1);
|
|
1567
|
+
assert.match(bad.stdout, /not installed: ai\.maestro\.legacy-session/);
|
|
1568
|
+
assert.match(bad.stdout, /verify: MISMATCH \(plists\)/);
|
|
1569
|
+
|
|
1570
|
+
const d = runCli(["doctor"], root, baseEnv({ HOME: home }));
|
|
1571
|
+
// doctor's fail() rows go to stderr; ok/warn to stdout.
|
|
1572
|
+
assert.match(d.stdout + d.stderr, /not installed: ai\.maestro\.legacy-session/, `doctor carries the verify rows:\n${d.stdout.slice(-1500)}\n${d.stderr.slice(-1500)}`);
|
|
1573
|
+
} finally { await rmRoot(root); await rmRoot(home); }
|
|
1574
|
+
});
|