@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
|
@@ -22,7 +22,9 @@ This runbook covers the complete setup of a Mac mini as the perpetual operations
|
|
|
22
22
|
|
|
23
23
|
Before starting, ensure you have credentials for:
|
|
24
24
|
|
|
25
|
-
-
|
|
25
|
+
- Claude Code Max subscription — the seat runs on a long-lived OAuth token
|
|
26
|
+
from `claude setup-token` (see Step 6); an Anthropic API key is the
|
|
27
|
+
pay-per-token alternative, not the default
|
|
26
28
|
- GitHub (access to maestro repo)
|
|
27
29
|
- Google Workspace (the-agent@the-company)
|
|
28
30
|
- Slack (the company workspace)
|
|
@@ -112,8 +114,14 @@ cat > ~/.maestro-env << 'EOF'
|
|
|
112
114
|
# the agent operating system Environment Variables
|
|
113
115
|
# Source this file in your shell profile
|
|
114
116
|
|
|
115
|
-
# Required
|
|
116
|
-
|
|
117
|
+
# Required — Claude auth. THE FLEET DEFAULT is the subscription OAuth token:
|
|
118
|
+
# run `claude setup-token` on any machine where you are logged in, paste the
|
|
119
|
+
# result here. (An ANTHROPIC_API_KEY is the pay-per-token alternative; set one
|
|
120
|
+
# or the other, never both.) `maestro setup` writes the same two lines into the
|
|
121
|
+
# agent's .env; doctor reports what `claude auth status` says (it reports the
|
|
122
|
+
# token is present, it does not validate it — `claude -p "ping"` proves it).
|
|
123
|
+
export CLAUDE_CODE_OAUTH_TOKEN="paste-the-output-of-claude-setup-token"
|
|
124
|
+
export MAESTRO_PREFER_SUBSCRIPTION_AUTH=1
|
|
117
125
|
|
|
118
126
|
# Optional but recommended
|
|
119
127
|
export SLACK_TOKEN="your-slack-token-here"
|
|
@@ -137,8 +145,8 @@ source ~/.maestro-env
|
|
|
137
145
|
Verify:
|
|
138
146
|
|
|
139
147
|
```bash
|
|
140
|
-
echo $
|
|
141
|
-
# Should print first 10 characters of
|
|
148
|
+
echo $CLAUDE_CODE_OAUTH_TOKEN | head -c 10
|
|
149
|
+
# Should print the first 10 characters of the token
|
|
142
150
|
```
|
|
143
151
|
|
|
144
152
|
**Security note**: Set file permissions to owner-only:
|
|
@@ -172,67 +180,28 @@ cat .mcp.json 2>/dev/null || echo "MCP config not found -- create .mcp.json"
|
|
|
172
180
|
|
|
173
181
|
## Step 8: Configure launchd for Perpetual Operation
|
|
174
182
|
|
|
175
|
-
|
|
183
|
+
Do not hand-write plists. The SDK renders every launchd job for the seat —
|
|
184
|
+
the daemon (`ai.maestro.<first>-daemon`, exec'd through
|
|
185
|
+
`scripts/daemon/launchd-wrapper.sh`, which sets HOME/PATH/AGENT_ROOT and
|
|
186
|
+
sources `.env`), the cadence triggers, the hourly `-autoupdate` job and the
|
|
187
|
+
front-door **main session** job (`ai.maestro.<first>-session`,
|
|
188
|
+
`scripts/session/supervisor.sh`, KeepAlive) — from
|
|
189
|
+
`scripts/local-triggers/generate-plists.sh`, and `init-agent.sh` runs it for
|
|
190
|
+
you:
|
|
176
191
|
|
|
177
192
|
```bash
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<plist version="1.0">
|
|
183
|
-
<dict>
|
|
184
|
-
<key>Label</key>
|
|
185
|
-
<string>ai.maestro.maestro</string>
|
|
186
|
-
|
|
187
|
-
<key>ProgramArguments</key>
|
|
188
|
-
<array>
|
|
189
|
-
<string>/bin/bash</string>
|
|
190
|
-
<string>-c</string>
|
|
191
|
-
<string>source ~/.maestro-env && cd ~/maestro && ./scripts/orchestrator.sh</string>
|
|
192
|
-
</array>
|
|
193
|
-
|
|
194
|
-
<key>RunAtLoad</key>
|
|
195
|
-
<true/>
|
|
196
|
-
|
|
197
|
-
<key>KeepAlive</key>
|
|
198
|
-
<dict>
|
|
199
|
-
<key>SuccessfulExit</key>
|
|
200
|
-
<false/>
|
|
201
|
-
</dict>
|
|
202
|
-
|
|
203
|
-
<key>StandardOutPath</key>
|
|
204
|
-
<string>~/the-agent-repo/maestro/logs/orchestrator-stdout.log</string>
|
|
205
|
-
|
|
206
|
-
<key>StandardErrorPath</key>
|
|
207
|
-
<string>~/the-agent-repo/maestro/logs/orchestrator-stderr.log</string>
|
|
208
|
-
|
|
209
|
-
<key>EnvironmentVariables</key>
|
|
210
|
-
<dict>
|
|
211
|
-
<key>PATH</key>
|
|
212
|
-
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
213
|
-
</dict>
|
|
214
|
-
|
|
215
|
-
<key>ThrottleInterval</key>
|
|
216
|
-
<integer>30</integer>
|
|
217
|
-
|
|
218
|
-
<key>ProcessType</key>
|
|
219
|
-
<string>Background</string>
|
|
220
|
-
</dict>
|
|
221
|
-
</plist>
|
|
222
|
-
EOF
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Load the agent:
|
|
226
|
-
|
|
227
|
-
```bash
|
|
228
|
-
launchctl load ~/Library/LaunchAgents/ai.maestro.maestro.plist
|
|
193
|
+
cd ~/<agent-name>
|
|
194
|
+
scripts/setup/init-agent.sh # deps → state dirs → generate-plists.sh → launchctl bootstrap
|
|
195
|
+
maestro session install # the main-session job (KeepAlive, screen/tmux)
|
|
196
|
+
scripts/setup/configure-macos.sh --tailscale-ssh # remote operator access (Tailscale SSH + Remote Login)
|
|
229
197
|
```
|
|
230
198
|
|
|
231
199
|
Verify it is running:
|
|
232
200
|
|
|
233
201
|
```bash
|
|
234
|
-
launchctl list | grep maestro
|
|
235
|
-
#
|
|
202
|
+
launchctl list | grep ai.maestro
|
|
203
|
+
# daemon + session + cadence triggers, each with a PID and exit status 0
|
|
204
|
+
maestro session status # the main session: lock holder, mux, heartbeat age
|
|
236
205
|
```
|
|
237
206
|
|
|
238
207
|
## Step 9: Set Up Application Accounts
|
|
@@ -287,11 +256,14 @@ The the agent operating system uses desktop control (via macOS Accessibility and
|
|
|
287
256
|
|
|
288
257
|
### Verify Permissions
|
|
289
258
|
|
|
290
|
-
Run the
|
|
259
|
+
Run the doctor — it is the one verification entry point (auth mode, org
|
|
260
|
+
enrollment and the id-vs-slug trap, launchd jobs, the main session's
|
|
261
|
+
heartbeat, the global SDK install, Tailscale SSH) and it prints a remedy per
|
|
262
|
+
line:
|
|
291
263
|
|
|
292
264
|
```bash
|
|
293
|
-
cd
|
|
294
|
-
|
|
265
|
+
cd ~/<agent-name>
|
|
266
|
+
maestro doctor
|
|
295
267
|
```
|
|
296
268
|
|
|
297
269
|
Manual verification:
|
|
@@ -384,10 +356,13 @@ echo "=============================="
|
|
|
384
356
|
- [ ] Git, Node.js, Python, jq, yq installed
|
|
385
357
|
- [ ] Claude Code CLI installed and verified
|
|
386
358
|
- [ ] maestro repo cloned to ~/maestro
|
|
359
|
+
- [ ] Claude auth: CLAUDE_CODE_OAUTH_TOKEN (+ MAESTRO_PREFER_SUBSCRIPTION_AUTH=1) in the agent's .env
|
|
387
360
|
- [ ] Environment variables configured in ~/.maestro-env
|
|
388
361
|
- [ ] File permissions set (chmod 600 on secrets)
|
|
389
362
|
- [ ] MCP servers installed and configured
|
|
390
|
-
- [ ] launchd
|
|
363
|
+
- [ ] launchd jobs rendered by generate-plists.sh and loaded (daemon, session, cadence, autoupdate)
|
|
364
|
+
- [ ] `maestro session status` shows a live heartbeat
|
|
365
|
+
- [ ] Tailscale SSH enabled (`configure-macos.sh --tailscale-ssh`)
|
|
391
366
|
- [ ] Slack signed in and added to Login Items
|
|
392
367
|
- [ ] Gmail signed in via Safari
|
|
393
368
|
- [ ] WhatsApp linked and added to Login Items
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cadence-bus-requeue.test.mjs — deferred ticks are re-keyed to the TAIL
|
|
3
|
+
* (audit F8: head-of-line block).
|
|
4
|
+
*
|
|
5
|
+
* `requeueTick(root, event)` used to write the event back under its own id.
|
|
6
|
+
* Ids are time-sortable and `claimNextTick` takes the lowest, so a deferred
|
|
7
|
+
* tick was re-claimed FIRST on the very next scan — forever, ahead of every
|
|
8
|
+
* newer tick, until whatever deferred it cleared. With `{defer:true}` the
|
|
9
|
+
* event is written under a NEW id (minted now, so it sorts last), carrying
|
|
10
|
+
* `metadata.deferrals` and `metadata.originalId`; the plain call is unchanged.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { test } from "node:test";
|
|
14
|
+
import assert from "node:assert/strict";
|
|
15
|
+
import { promises as fsp } from "node:fs";
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
|
|
20
|
+
import { enqueueTick, claimNextTick, requeueTick, listInbox, getBusPaths, listClaimed } from "./cadence-bus.mjs";
|
|
21
|
+
|
|
22
|
+
async function root() {
|
|
23
|
+
const p = join(tmpdir(), `cadence-requeue-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
24
|
+
await fsp.mkdir(p, { recursive: true });
|
|
25
|
+
return p;
|
|
26
|
+
}
|
|
27
|
+
async function rm(p) { try { await fsp.rm(p, { recursive: true, force: true }); } catch { /* */ } }
|
|
28
|
+
|
|
29
|
+
test("requeueTick({defer:true}) re-keys the event to the tail and carries deferrals + originalId", async () => {
|
|
30
|
+
const dir = await root();
|
|
31
|
+
try {
|
|
32
|
+
const a = enqueueTick({ cadence: "goal-steward", source: "manual", agentRoot: dir });
|
|
33
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
34
|
+
const b = enqueueTick({ cadence: "inbox-processor", source: "manual", agentRoot: dir });
|
|
35
|
+
assert.deepEqual(listInbox(dir), [a.id, b.id]);
|
|
36
|
+
|
|
37
|
+
const claim = claimNextTick(dir);
|
|
38
|
+
assert.equal(claim.event.id, a.id);
|
|
39
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
40
|
+
const r = requeueTick(dir, claim.event, { defer: true });
|
|
41
|
+
assert.equal(r.ok, true);
|
|
42
|
+
assert.notEqual(r.id, a.id, "a new id");
|
|
43
|
+
// The deferred tick now sorts AFTER b: b is claimed next, not a again.
|
|
44
|
+
assert.deepEqual(listInbox(dir), [b.id, r.id]);
|
|
45
|
+
assert.equal(existsSync(join(getBusPaths(dir).claimed, `${a.id}.json`)), false, "old claim removed");
|
|
46
|
+
assert.equal(existsSync(join(getBusPaths(dir).inbox, `${a.id}.json`)), false, "old id not left in inbox");
|
|
47
|
+
const on = JSON.parse(readFileSync(join(getBusPaths(dir).inbox, `${r.id}.json`), "utf-8"));
|
|
48
|
+
assert.equal(on.cadence, "goal-steward");
|
|
49
|
+
assert.equal(on.metadata.deferrals, 1);
|
|
50
|
+
assert.equal(on.metadata.originalId, a.id);
|
|
51
|
+
assert.equal(on.attempts, 0, "a deferral is not a failed attempt");
|
|
52
|
+
|
|
53
|
+
// Deferring again increments the count and KEEPS the original id.
|
|
54
|
+
const claim2 = claimNextTick(dir); // b
|
|
55
|
+
assert.equal(claim2.event.id, b.id);
|
|
56
|
+
requeueTick(dir, claim2.event); // plain requeue: unchanged id
|
|
57
|
+
assert.deepEqual(listInbox(dir), [b.id, r.id]);
|
|
58
|
+
const claimB = claimNextTick(dir);
|
|
59
|
+
assert.equal(claimB.event.id, b.id);
|
|
60
|
+
const claimA = claimNextTick(dir);
|
|
61
|
+
assert.equal(claimA.event.id, r.id);
|
|
62
|
+
await new Promise((res) => setTimeout(res, 2));
|
|
63
|
+
const r2 = requeueTick(dir, claimA.event, { defer: true });
|
|
64
|
+
const on2 = JSON.parse(readFileSync(join(getBusPaths(dir).inbox, `${r2.id}.json`), "utf-8"));
|
|
65
|
+
assert.equal(on2.metadata.deferrals, 2);
|
|
66
|
+
assert.equal(on2.metadata.originalId, a.id);
|
|
67
|
+
// The deferred tick's old claim is gone; only b (claimed above) is still held.
|
|
68
|
+
assert.deepEqual(listClaimed(dir), [b.id]);
|
|
69
|
+
assert.equal(existsSync(join(getBusPaths(dir).claimed, `${r.id}.json`)), false, "the re-key unlinked its claim");
|
|
70
|
+
} finally { await rm(dir); }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("requeueTick without defer is byte-for-byte the old behaviour (same id, returns true)", async () => {
|
|
74
|
+
const dir = await root();
|
|
75
|
+
try {
|
|
76
|
+
const a = enqueueTick({ cadence: "goal-steward", source: "manual", agentRoot: dir });
|
|
77
|
+
const claim = claimNextTick(dir);
|
|
78
|
+
const ok = requeueTick(dir, claim.event);
|
|
79
|
+
assert.equal(ok, true);
|
|
80
|
+
assert.deepEqual(listInbox(dir), [a.id]);
|
|
81
|
+
assert.equal(JSON.parse(readFileSync(join(getBusPaths(dir).inbox, `${a.id}.json`), "utf-8")).metadata.deferrals, undefined);
|
|
82
|
+
} finally { await rm(dir); }
|
|
83
|
+
});
|
package/lib/cadence-bus.mjs
CHANGED
|
@@ -811,29 +811,65 @@ export function failTick(agentRoot, id, errorOrReason, opts = {}) {
|
|
|
811
811
|
* Best-effort: on a write failure the claim is left in place so stale-claim
|
|
812
812
|
* recovery can retry it. Returns true if the event landed in inbox/.
|
|
813
813
|
*
|
|
814
|
+
* `{defer:true}` (audit F8, head-of-line block): ids are time-sortable and
|
|
815
|
+
* `claimNextTick` takes the lowest, so an event requeued under its OWN id was
|
|
816
|
+
* re-claimed FIRST on the very next scan — ahead of every newer tick, forever,
|
|
817
|
+
* until whatever deferred it cleared. A deferred requeue instead re-keys the
|
|
818
|
+
* event to a NEW id minted now (so it sorts to the tail), carrying
|
|
819
|
+
* `metadata.deferrals` (count) and `metadata.originalId` (the first id, for
|
|
820
|
+
* the audit join). Returns `{ok, id}` in that mode. The plain call is unchanged.
|
|
821
|
+
*
|
|
814
822
|
* @param {string} agentRoot
|
|
815
823
|
* @param {object} event Must include `id`.
|
|
816
|
-
* @
|
|
824
|
+
* @param {{defer?:boolean, now?:Date}} [opts]
|
|
825
|
+
* @returns {boolean|{ok:boolean, id:string}}
|
|
817
826
|
*/
|
|
818
|
-
export function requeueTick(agentRoot, event) {
|
|
827
|
+
export function requeueTick(agentRoot, event, opts = {}) {
|
|
819
828
|
if (!event || typeof event !== "object" || !event.id) {
|
|
820
829
|
throw new TypeError("requeueTick: event with an id is required");
|
|
821
830
|
}
|
|
822
831
|
const paths = ensureBusDirs(agentRoot);
|
|
823
|
-
const
|
|
832
|
+
const defer = !!(opts && opts.defer);
|
|
833
|
+
const priorId = event.id;
|
|
834
|
+
let out = event;
|
|
835
|
+
if (defer) {
|
|
836
|
+
const meta = event.metadata && typeof event.metadata === "object" ? event.metadata : {};
|
|
837
|
+
out = {
|
|
838
|
+
...event,
|
|
839
|
+
id: nextEventId(opts.now instanceof Date ? opts.now : new Date()),
|
|
840
|
+
metadata: {
|
|
841
|
+
...meta,
|
|
842
|
+
deferrals: (Number.isFinite(meta.deferrals) ? meta.deferrals : 0) + 1,
|
|
843
|
+
originalId: meta.originalId || priorId,
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
const target = join(paths.inbox, `${out.id}.json`);
|
|
824
848
|
try {
|
|
825
|
-
writeJsonAtomic(target,
|
|
849
|
+
writeJsonAtomic(target, out);
|
|
826
850
|
} catch (err) {
|
|
827
851
|
logBusEvent(paths.agentRoot, {
|
|
828
852
|
level: "error",
|
|
829
853
|
stage: "requeue_failed",
|
|
830
|
-
id:
|
|
854
|
+
id: priorId,
|
|
831
855
|
cadence: event.cadence,
|
|
832
856
|
error: err.message,
|
|
833
857
|
});
|
|
834
|
-
return false;
|
|
858
|
+
return defer ? { ok: false, id: priorId } : false;
|
|
859
|
+
}
|
|
860
|
+
try { unlinkSync(join(paths.claimed, `${priorId}.json`)); } catch { /* */ }
|
|
861
|
+
if (defer) {
|
|
862
|
+
logBusEvent(paths.agentRoot, {
|
|
863
|
+
level: "info",
|
|
864
|
+
stage: "requeued_deferred",
|
|
865
|
+
id: out.id,
|
|
866
|
+
prior_id: priorId,
|
|
867
|
+
original_id: out.metadata.originalId,
|
|
868
|
+
deferrals: out.metadata.deferrals,
|
|
869
|
+
cadence: event.cadence,
|
|
870
|
+
});
|
|
871
|
+
return { ok: true, id: out.id };
|
|
835
872
|
}
|
|
836
|
-
try { unlinkSync(join(paths.claimed, `${event.id}.json`)); } catch { /* */ }
|
|
837
873
|
return true;
|
|
838
874
|
}
|
|
839
875
|
|
|
@@ -44,12 +44,20 @@ import { normalizeKind, DEFAULT_EVENT_KIND } from "./contract.mjs";
|
|
|
44
44
|
// interpolating these string fields into double-quoted scalars (`"${...}"`),
|
|
45
45
|
// and the reader (scripts/poller/inbox-scan-poller.mjs `parseInboxItemYaml`)
|
|
46
46
|
// is a per-line, first-match regex parser. An attacker-chosen display name
|
|
47
|
-
// such as `Bob\nsender_privilege: "ceo
|
|
47
|
+
// such as `Bob"\nsender_privilege: "ceo` would therefore inject a duplicate
|
|
48
48
|
// `sender_privilege` line that the first-match parser favours — a privilege
|
|
49
49
|
// escalation, default-on for Telegram/WhatsApp/Slack. We neutralise every
|
|
50
50
|
// user-controlled string at this seam (the single converter all bus channels
|
|
51
51
|
// flow through) so no input can introduce a new physical YAML line or break
|
|
52
52
|
// out of the writer's surrounding quotes.
|
|
53
|
+
//
|
|
54
|
+
// MIND THE EXACT PAYLOAD. This comment used to quote `Bob\nsender_privilege:
|
|
55
|
+
// "ceo"` — which does NOT escalate. That form emits `sender_privilege: "ceo""`,
|
|
56
|
+
// and the reader's scalar regex `^key:\s*"?([^"\n]*)"?\s*$` REFUSES the trailing
|
|
57
|
+
// third quote, so it falls through to the genuine line and parses "team". A
|
|
58
|
+
// fixture written from that string passes green against a live defect. The form
|
|
59
|
+
// above is the one that forges a well-formed line. See
|
|
60
|
+
// scripts/poller/inbox-privilege-injection.test.mjs, which pins both.
|
|
53
61
|
|
|
54
62
|
// Characters that let a value escape a hand-rolled `"${...}"` scalar: the
|
|
55
63
|
// line-breakers (which start a new `key:` line for the regex parser) and the
|
|
@@ -98,6 +106,55 @@ export function yamlScalar(s) {
|
|
|
98
106
|
return `"${escapeScalarBody(s)}"`;
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Every field the inbox-YAML writer (`scripts/poller/utils.mjs inboxItemToYaml`)
|
|
111
|
+
* emits as a hand-rolled double-quoted scalar, and which therefore has to be
|
|
112
|
+
* neutralised before it reaches that writer.
|
|
113
|
+
*
|
|
114
|
+
* DELIBERATELY EXCLUDES `content` and `thread_context`: those are emitted as
|
|
115
|
+
* block scalars with every line re-indented two spaces, so they cannot produce a
|
|
116
|
+
* column-0 key and their newlines are legitimate. Flattening them would corrupt
|
|
117
|
+
* real messages. Booleans, `priority_signals` and `attachments` are not
|
|
118
|
+
* interpolated as strings and are likewise left alone.
|
|
119
|
+
*/
|
|
120
|
+
const QUOTED_SCALAR_FIELDS = [
|
|
121
|
+
"id", "service", "channel", "channel_id", "sender", "sender_id",
|
|
122
|
+
"sender_privilege", "timestamp", "subject", "thread_id", "raw_ref", "kind",
|
|
123
|
+
"event_kind", "direct_reason", "scope_id", "channel_kind", "channel_type",
|
|
124
|
+
"event_type",
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Neutralise an ALREADY-BUILT legacy inbox item in place of routing it through
|
|
129
|
+
* `eventToInboxItem`.
|
|
130
|
+
*
|
|
131
|
+
* Producers that hand-build their item and call `writeInboxItem` directly —
|
|
132
|
+
* `scripts/poller/slack-poller.mjs` is the one that did — never passed through
|
|
133
|
+
* the converter above, so none of its escaping applied and an attacker-chosen
|
|
134
|
+
* Slack display name could forge a `sender_privilege: "ceo"` line. This is the
|
|
135
|
+
* same neutralisation, factored out so a direct caller gets it in one line
|
|
136
|
+
* without a lossy item → event → item round trip.
|
|
137
|
+
*
|
|
138
|
+
* PICK ONE. This is NOT idempotent with `eventToInboxItem` — that function
|
|
139
|
+
* already sanitises its output, and sanitising twice double-escapes any value
|
|
140
|
+
* containing a quote or backslash. A producer uses the converter or this, never
|
|
141
|
+
* both.
|
|
142
|
+
*
|
|
143
|
+
* Benign items are returned field-for-field unchanged, so on-disk YAML for real
|
|
144
|
+
* traffic is byte-identical.
|
|
145
|
+
*
|
|
146
|
+
* @param {Record<string, any>} item legacy inbox item
|
|
147
|
+
* @returns {Record<string, any>} a shallow copy with quoted scalars neutralised
|
|
148
|
+
*/
|
|
149
|
+
export function sanitizeInboxItem(item) {
|
|
150
|
+
if (!item || typeof item !== "object") throw new Error("sanitizeInboxItem: item object required");
|
|
151
|
+
const out = { ...item };
|
|
152
|
+
for (const field of QUOTED_SCALAR_FIELDS) {
|
|
153
|
+
if (typeof out[field] === "string") out[field] = sanitizeYamlField(out[field]);
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
|
|
101
158
|
/**
|
|
102
159
|
* Convert a MessageEvent to the legacy inbox-item shape the daemon consumes.
|
|
103
160
|
*
|
|
@@ -342,4 +399,4 @@ function normalizeAttachmentForWriter(a, i) {
|
|
|
342
399
|
};
|
|
343
400
|
}
|
|
344
401
|
|
|
345
|
-
export default { eventToInboxItem, inboxItemToEvent };
|
|
402
|
+
export default { eventToInboxItem, inboxItemToEvent, sanitizeInboxItem, yamlScalar };
|