@ag-eco/agentplate-cli 0.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/LICENSE +21 -0
- package/README.md +462 -0
- package/agents/ap-co-creation.md +90 -0
- package/agents/builder.md +144 -0
- package/agents/coordinator.md +377 -0
- package/agents/lead.md +435 -0
- package/agents/merger.md +164 -0
- package/agents/monitor.md +214 -0
- package/agents/orchestrator.md +239 -0
- package/agents/reviewer.md +140 -0
- package/agents/scout.md +125 -0
- package/agents/supervisor.md +427 -0
- package/package.json +66 -0
- package/src/agents/capabilities.test.ts +85 -0
- package/src/agents/capabilities.ts +125 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +101 -0
- package/src/agents/copilot-hooks-deployer.test.ts +162 -0
- package/src/agents/copilot-hooks-deployer.ts +93 -0
- package/src/agents/guard-rules.test.ts +372 -0
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/headless-mail-injector.test.ts +709 -0
- package/src/agents/headless-mail-injector.ts +377 -0
- package/src/agents/headless-prompt.test.ts +102 -0
- package/src/agents/headless-prompt.ts +68 -0
- package/src/agents/hooks-deployer.test.ts +3119 -0
- package/src/agents/hooks-deployer.ts +804 -0
- package/src/agents/identity.test.ts +604 -0
- package/src/agents/identity.ts +384 -0
- package/src/agents/lifecycle.test.ts +196 -0
- package/src/agents/lifecycle.ts +183 -0
- package/src/agents/mail-poll-detect.test.ts +153 -0
- package/src/agents/mail-poll-detect.ts +73 -0
- package/src/agents/manifest.test.ts +1026 -0
- package/src/agents/manifest.ts +376 -0
- package/src/agents/overlay.test.ts +1058 -0
- package/src/agents/overlay.ts +490 -0
- package/src/agents/scope-detect.test.ts +190 -0
- package/src/agents/scope-detect.ts +146 -0
- package/src/agents/turn-lock.test.ts +181 -0
- package/src/agents/turn-lock.ts +235 -0
- package/src/agents/turn-runner-dispatch.test.ts +182 -0
- package/src/agents/turn-runner-dispatch.ts +105 -0
- package/src/agents/turn-runner.test.ts +2312 -0
- package/src/agents/turn-runner.ts +1383 -0
- package/src/beads/client.test.ts +217 -0
- package/src/beads/client.ts +230 -0
- package/src/beads/molecules.test.ts +338 -0
- package/src/beads/molecules.ts +198 -0
- package/src/commands/agents.test.ts +328 -0
- package/src/commands/agents.ts +299 -0
- package/src/commands/clean.test.ts +797 -0
- package/src/commands/clean.ts +791 -0
- package/src/commands/completions.test.ts +348 -0
- package/src/commands/completions.ts +981 -0
- package/src/commands/coordinator.test.ts +2975 -0
- package/src/commands/coordinator.ts +1841 -0
- package/src/commands/costs.test.ts +1183 -0
- package/src/commands/costs.ts +599 -0
- package/src/commands/dashboard.test.ts +954 -0
- package/src/commands/dashboard.ts +1212 -0
- package/src/commands/discover.test.ts +288 -0
- package/src/commands/discover.ts +202 -0
- package/src/commands/doctor.test.ts +303 -0
- package/src/commands/doctor.ts +311 -0
- package/src/commands/ecosystem.test.ts +226 -0
- package/src/commands/ecosystem.ts +248 -0
- package/src/commands/errors.test.ts +654 -0
- package/src/commands/errors.ts +197 -0
- package/src/commands/feed.test.ts +709 -0
- package/src/commands/feed.ts +260 -0
- package/src/commands/group.test.ts +475 -0
- package/src/commands/group.ts +546 -0
- package/src/commands/hooks.test.ts +458 -0
- package/src/commands/hooks.ts +263 -0
- package/src/commands/init.test.ts +1011 -0
- package/src/commands/init.ts +967 -0
- package/src/commands/inspect.test.ts +1239 -0
- package/src/commands/inspect.ts +648 -0
- package/src/commands/log.test.ts +1913 -0
- package/src/commands/log.ts +958 -0
- package/src/commands/logs.test.ts +801 -0
- package/src/commands/logs.ts +483 -0
- package/src/commands/mail.test.ts +1501 -0
- package/src/commands/mail.ts +848 -0
- package/src/commands/merge.test.ts +864 -0
- package/src/commands/merge.ts +381 -0
- package/src/commands/metrics.test.ts +458 -0
- package/src/commands/metrics.ts +129 -0
- package/src/commands/monitor.test.ts +191 -0
- package/src/commands/monitor.ts +409 -0
- package/src/commands/nudge.test.ts +579 -0
- package/src/commands/nudge.ts +646 -0
- package/src/commands/orchestrator.ts +42 -0
- package/src/commands/prime.test.ts +612 -0
- package/src/commands/prime.ts +359 -0
- package/src/commands/replay.test.ts +757 -0
- package/src/commands/replay.ts +231 -0
- package/src/commands/run.test.ts +469 -0
- package/src/commands/run.ts +353 -0
- package/src/commands/serve/agent-actions.test.ts +210 -0
- package/src/commands/serve/agent-actions.ts +192 -0
- package/src/commands/serve/build.test.ts +202 -0
- package/src/commands/serve/build.ts +206 -0
- package/src/commands/serve/coordinator-actions.test.ts +339 -0
- package/src/commands/serve/coordinator-actions.ts +410 -0
- package/src/commands/serve/dev.test.ts +168 -0
- package/src/commands/serve/dev.ts +117 -0
- package/src/commands/serve/mail-actions.test.ts +312 -0
- package/src/commands/serve/mail-actions.ts +167 -0
- package/src/commands/serve/rest.test.ts +1680 -0
- package/src/commands/serve/rest.ts +1130 -0
- package/src/commands/serve/static.ts +51 -0
- package/src/commands/serve/ws.test.ts +361 -0
- package/src/commands/serve/ws.ts +332 -0
- package/src/commands/serve.test.ts +459 -0
- package/src/commands/serve.ts +654 -0
- package/src/commands/sling.test.ts +1583 -0
- package/src/commands/sling.ts +1351 -0
- package/src/commands/spec.test.ts +179 -0
- package/src/commands/spec.ts +105 -0
- package/src/commands/status.test.ts +614 -0
- package/src/commands/status.ts +403 -0
- package/src/commands/stop.test.ts +964 -0
- package/src/commands/stop.ts +319 -0
- package/src/commands/supervisor.test.ts +185 -0
- package/src/commands/supervisor.ts +537 -0
- package/src/commands/trace.test.ts +762 -0
- package/src/commands/trace.ts +205 -0
- package/src/commands/update.test.ts +466 -0
- package/src/commands/update.ts +263 -0
- package/src/commands/upgrade.test.ts +48 -0
- package/src/commands/upgrade.ts +240 -0
- package/src/commands/watch.test.ts +257 -0
- package/src/commands/watch.ts +308 -0
- package/src/commands/worktree.test.ts +1297 -0
- package/src/commands/worktree.ts +451 -0
- package/src/config.test.ts +1535 -0
- package/src/config.ts +1064 -0
- package/src/doctor/agents.test.ts +523 -0
- package/src/doctor/agents.ts +399 -0
- package/src/doctor/config-check.test.ts +191 -0
- package/src/doctor/config-check.ts +183 -0
- package/src/doctor/consistency.test.ts +807 -0
- package/src/doctor/consistency.ts +347 -0
- package/src/doctor/databases.test.ts +350 -0
- package/src/doctor/databases.ts +243 -0
- package/src/doctor/dependencies.test.ts +296 -0
- package/src/doctor/dependencies.ts +272 -0
- package/src/doctor/ecosystem.test.ts +308 -0
- package/src/doctor/ecosystem.ts +156 -0
- package/src/doctor/logs.test.ts +253 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +315 -0
- package/src/doctor/merge-queue.ts +167 -0
- package/src/doctor/providers.test.ts +409 -0
- package/src/doctor/providers.ts +250 -0
- package/src/doctor/serve.test.ts +95 -0
- package/src/doctor/serve.ts +86 -0
- package/src/doctor/structure.test.ts +423 -0
- package/src/doctor/structure.ts +285 -0
- package/src/doctor/types.ts +43 -0
- package/src/doctor/version.test.ts +241 -0
- package/src/doctor/version.ts +132 -0
- package/src/doctor/watchdog.test.ts +167 -0
- package/src/doctor/watchdog.ts +214 -0
- package/src/e2e/init-sling-lifecycle.test.ts +283 -0
- package/src/errors.test.ts +350 -0
- package/src/errors.ts +217 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +369 -0
- package/src/events/tailer.test.ts +719 -0
- package/src/events/tailer.ts +332 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/index.ts +533 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/insights/quality-gates.test.ts +141 -0
- package/src/insights/quality-gates.ts +156 -0
- package/src/json.test.ts +72 -0
- package/src/json.ts +53 -0
- package/src/loam/client.test.ts +752 -0
- package/src/loam/client.ts +664 -0
- package/src/logging/color.test.ts +252 -0
- package/src/logging/color.ts +105 -0
- package/src/logging/format.test.ts +110 -0
- package/src/logging/format.ts +255 -0
- package/src/logging/logger.test.ts +814 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +259 -0
- package/src/logging/reporter.ts +110 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/logging/theme.ts +140 -0
- package/src/mail/broadcast.test.ts +204 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +774 -0
- package/src/mail/client.ts +236 -0
- package/src/mail/store.test.ts +898 -0
- package/src/mail/store.ts +425 -0
- package/src/merge/lock.test.ts +149 -0
- package/src/merge/lock.ts +140 -0
- package/src/merge/predict.test.ts +387 -0
- package/src/merge/predict.ts +249 -0
- package/src/merge/queue.test.ts +426 -0
- package/src/merge/queue.ts +246 -0
- package/src/merge/resolver.test.ts +1993 -0
- package/src/merge/resolver.ts +926 -0
- package/src/metrics/pricing.test.ts +258 -0
- package/src/metrics/pricing.ts +135 -0
- package/src/metrics/store.test.ts +978 -0
- package/src/metrics/store.ts +501 -0
- package/src/metrics/summary.test.ts +398 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +483 -0
- package/src/metrics/transcript.ts +114 -0
- package/src/runtimes/__fixtures__/claude-stream-fixture.ts +22 -0
- package/src/runtimes/aider.test.ts +124 -0
- package/src/runtimes/aider.ts +147 -0
- package/src/runtimes/amp.test.ts +164 -0
- package/src/runtimes/amp.ts +154 -0
- package/src/runtimes/claude.test.ts +1474 -0
- package/src/runtimes/claude.ts +579 -0
- package/src/runtimes/codex.test.ts +805 -0
- package/src/runtimes/codex.ts +273 -0
- package/src/runtimes/connections.test.ts +214 -0
- package/src/runtimes/connections.ts +103 -0
- package/src/runtimes/copilot.test.ts +707 -0
- package/src/runtimes/copilot.ts +316 -0
- package/src/runtimes/cursor.test.ts +497 -0
- package/src/runtimes/cursor.ts +205 -0
- package/src/runtimes/gemini.test.ts +537 -0
- package/src/runtimes/gemini.ts +243 -0
- package/src/runtimes/goose.test.ts +133 -0
- package/src/runtimes/goose.ts +157 -0
- package/src/runtimes/headless-connection.test.ts +264 -0
- package/src/runtimes/headless-connection.ts +158 -0
- package/src/runtimes/opencode.test.ts +325 -0
- package/src/runtimes/opencode.ts +188 -0
- package/src/runtimes/pi-guards.test.ts +486 -0
- package/src/runtimes/pi-guards.ts +367 -0
- package/src/runtimes/pi.test.ts +789 -0
- package/src/runtimes/pi.ts +305 -0
- package/src/runtimes/registry.test.ts +196 -0
- package/src/runtimes/registry.ts +99 -0
- package/src/runtimes/sapling.test.ts +1267 -0
- package/src/runtimes/sapling.ts +710 -0
- package/src/runtimes/types.ts +266 -0
- package/src/schema-consistency.test.ts +246 -0
- package/src/sessions/compat.test.ts +281 -0
- package/src/sessions/compat.ts +105 -0
- package/src/sessions/store.test.ts +1748 -0
- package/src/sessions/store.ts +858 -0
- package/src/test-helpers.test.ts +124 -0
- package/src/test-helpers.ts +145 -0
- package/src/test-setup.test.ts +31 -0
- package/src/test-setup.ts +28 -0
- package/src/tools/loam/api.ts +368 -0
- package/src/tools/loam/cli.ts +278 -0
- package/src/tools/loam/commands/add.ts +52 -0
- package/src/tools/loam/commands/archive.ts +214 -0
- package/src/tools/loam/commands/audit.ts +276 -0
- package/src/tools/loam/commands/compact.ts +1062 -0
- package/src/tools/loam/commands/completions.ts +79 -0
- package/src/tools/loam/commands/config.ts +381 -0
- package/src/tools/loam/commands/delete-domain.ts +121 -0
- package/src/tools/loam/commands/delete.ts +316 -0
- package/src/tools/loam/commands/diff.ts +200 -0
- package/src/tools/loam/commands/doctor.ts +1113 -0
- package/src/tools/loam/commands/edit.ts +226 -0
- package/src/tools/loam/commands/init.ts +31 -0
- package/src/tools/loam/commands/learn.ts +179 -0
- package/src/tools/loam/commands/move.ts +323 -0
- package/src/tools/loam/commands/onboard.ts +374 -0
- package/src/tools/loam/commands/outcome.ts +185 -0
- package/src/tools/loam/commands/prime.ts +688 -0
- package/src/tools/loam/commands/prune.ts +614 -0
- package/src/tools/loam/commands/query.ts +218 -0
- package/src/tools/loam/commands/rank.ts +180 -0
- package/src/tools/loam/commands/ready.ts +189 -0
- package/src/tools/loam/commands/record.ts +1210 -0
- package/src/tools/loam/commands/restore.ts +166 -0
- package/src/tools/loam/commands/search.ts +327 -0
- package/src/tools/loam/commands/setup.ts +887 -0
- package/src/tools/loam/commands/status.ts +103 -0
- package/src/tools/loam/commands/sync.ts +298 -0
- package/src/tools/loam/commands/update.ts +19 -0
- package/src/tools/loam/commands/upgrade.ts +93 -0
- package/src/tools/loam/commands/validate.ts +190 -0
- package/src/tools/loam/index.ts +62 -0
- package/src/tools/loam/log.ts +127 -0
- package/src/tools/loam/registry/builtins.ts +409 -0
- package/src/tools/loam/registry/custom.ts +431 -0
- package/src/tools/loam/registry/init.ts +55 -0
- package/src/tools/loam/registry/template.ts +40 -0
- package/src/tools/loam/registry/type-registry.ts +113 -0
- package/src/tools/loam/schemas/config-schema.ts +489 -0
- package/src/tools/loam/schemas/config.ts +245 -0
- package/src/tools/loam/schemas/index.ts +18 -0
- package/src/tools/loam/schemas/record-schema.ts +191 -0
- package/src/tools/loam/schemas/record.ts +115 -0
- package/src/tools/loam/utils/active-work.ts +205 -0
- package/src/tools/loam/utils/anchor-validity.ts +80 -0
- package/src/tools/loam/utils/archive.ts +146 -0
- package/src/tools/loam/utils/audit.ts +667 -0
- package/src/tools/loam/utils/bm25.ts +238 -0
- package/src/tools/loam/utils/budget.ts +142 -0
- package/src/tools/loam/utils/config.ts +344 -0
- package/src/tools/loam/utils/dir-anchors.ts +62 -0
- package/src/tools/loam/utils/domain-rules.ts +114 -0
- package/src/tools/loam/utils/expertise.ts +393 -0
- package/src/tools/loam/utils/format-helpers.ts +96 -0
- package/src/tools/loam/utils/format.ts +1234 -0
- package/src/tools/loam/utils/git-context.ts +50 -0
- package/src/tools/loam/utils/git.ts +183 -0
- package/src/tools/loam/utils/hooks.ts +299 -0
- package/src/tools/loam/utils/index.ts +52 -0
- package/src/tools/loam/utils/json-output.ts +13 -0
- package/src/tools/loam/utils/lock.ts +76 -0
- package/src/tools/loam/utils/markers.ts +48 -0
- package/src/tools/loam/utils/numeric-flags.ts +20 -0
- package/src/tools/loam/utils/palette.ts +44 -0
- package/src/tools/loam/utils/prime-ranking.ts +135 -0
- package/src/tools/loam/utils/recipe-discovery.ts +195 -0
- package/src/tools/loam/utils/runtime-flags.ts +28 -0
- package/src/tools/loam/utils/scoring.ts +94 -0
- package/src/tools/loam/utils/version.ts +116 -0
- package/src/tools/sprout/commands/block.ts +64 -0
- package/src/tools/sprout/commands/blocked.ts +86 -0
- package/src/tools/sprout/commands/close.ts +129 -0
- package/src/tools/sprout/commands/completions.ts +198 -0
- package/src/tools/sprout/commands/config.ts +238 -0
- package/src/tools/sprout/commands/create.ts +164 -0
- package/src/tools/sprout/commands/dep.ts +148 -0
- package/src/tools/sprout/commands/doctor.ts +979 -0
- package/src/tools/sprout/commands/init.ts +83 -0
- package/src/tools/sprout/commands/label.ts +178 -0
- package/src/tools/sprout/commands/list.ts +210 -0
- package/src/tools/sprout/commands/migrate.ts +133 -0
- package/src/tools/sprout/commands/onboard.ts +207 -0
- package/src/tools/sprout/commands/plan-show.ts +278 -0
- package/src/tools/sprout/commands/plan.ts +2526 -0
- package/src/tools/sprout/commands/prime.ts +399 -0
- package/src/tools/sprout/commands/ready.ts +245 -0
- package/src/tools/sprout/commands/search.ts +221 -0
- package/src/tools/sprout/commands/show.ts +277 -0
- package/src/tools/sprout/commands/stats.ts +146 -0
- package/src/tools/sprout/commands/sync.ts +134 -0
- package/src/tools/sprout/commands/tpl.ts +364 -0
- package/src/tools/sprout/commands/unblock.ts +115 -0
- package/src/tools/sprout/commands/update.ts +257 -0
- package/src/tools/sprout/commands/upgrade.ts +91 -0
- package/src/tools/sprout/config-schema.ts +152 -0
- package/src/tools/sprout/config.ts +355 -0
- package/src/tools/sprout/filter.ts +107 -0
- package/src/tools/sprout/format.ts +43 -0
- package/src/tools/sprout/id.ts +22 -0
- package/src/tools/sprout/index.ts +204 -0
- package/src/tools/sprout/log.ts +76 -0
- package/src/tools/sprout/markers.ts +22 -0
- package/src/tools/sprout/output.ts +121 -0
- package/src/tools/sprout/plan-backref.ts +93 -0
- package/src/tools/sprout/plan-context.ts +81 -0
- package/src/tools/sprout/plan-domain.ts +139 -0
- package/src/tools/sprout/plan-lifecycle.ts +65 -0
- package/src/tools/sprout/plan-loam.ts +207 -0
- package/src/tools/sprout/plan-schema.ts +209 -0
- package/src/tools/sprout/sort.ts +31 -0
- package/src/tools/sprout/store.ts +172 -0
- package/src/tools/sprout/types.ts +118 -0
- package/src/tools/sprout/validation.ts +119 -0
- package/src/tools/sprout/version.ts +1 -0
- package/src/tools/sprout/yaml.ts +387 -0
- package/src/tools/trellis/commands/archive.ts +87 -0
- package/src/tools/trellis/commands/completions.ts +610 -0
- package/src/tools/trellis/commands/config.ts +382 -0
- package/src/tools/trellis/commands/create.ts +252 -0
- package/src/tools/trellis/commands/diff.ts +150 -0
- package/src/tools/trellis/commands/doctor.ts +771 -0
- package/src/tools/trellis/commands/emit.ts +365 -0
- package/src/tools/trellis/commands/history.ts +83 -0
- package/src/tools/trellis/commands/import.ts +198 -0
- package/src/tools/trellis/commands/init.ts +81 -0
- package/src/tools/trellis/commands/list.ts +103 -0
- package/src/tools/trellis/commands/onboard.ts +156 -0
- package/src/tools/trellis/commands/pin.ts +172 -0
- package/src/tools/trellis/commands/prime.ts +193 -0
- package/src/tools/trellis/commands/render.ts +122 -0
- package/src/tools/trellis/commands/schema.ts +353 -0
- package/src/tools/trellis/commands/show.ts +115 -0
- package/src/tools/trellis/commands/stats.ts +65 -0
- package/src/tools/trellis/commands/sync.ts +112 -0
- package/src/tools/trellis/commands/tree.ts +123 -0
- package/src/tools/trellis/commands/update.ts +330 -0
- package/src/tools/trellis/commands/upgrade.ts +95 -0
- package/src/tools/trellis/commands/validate.ts +166 -0
- package/src/tools/trellis/config-schema.ts +81 -0
- package/src/tools/trellis/config.ts +108 -0
- package/src/tools/trellis/frontmatter.ts +348 -0
- package/src/tools/trellis/id.ts +24 -0
- package/src/tools/trellis/index.ts +209 -0
- package/src/tools/trellis/markers.ts +28 -0
- package/src/tools/trellis/output.ts +84 -0
- package/src/tools/trellis/render.ts +212 -0
- package/src/tools/trellis/store.ts +144 -0
- package/src/tools/trellis/types.ts +82 -0
- package/src/tools/trellis/validate.ts +199 -0
- package/src/tools/trellis/yaml.ts +309 -0
- package/src/tracker/beads.test.ts +454 -0
- package/src/tracker/beads.ts +56 -0
- package/src/tracker/factory.test.ts +90 -0
- package/src/tracker/factory.ts +65 -0
- package/src/tracker/sprout.test.ts +461 -0
- package/src/tracker/sprout.ts +182 -0
- package/src/tracker/types.ts +52 -0
- package/src/trellis/client.test.ts +107 -0
- package/src/trellis/client.ts +179 -0
- package/src/types.ts +970 -0
- package/src/utils/bin.test.ts +10 -0
- package/src/utils/bin.ts +37 -0
- package/src/utils/browser.test.ts +49 -0
- package/src/utils/browser.ts +48 -0
- package/src/utils/fs.test.ts +119 -0
- package/src/utils/fs.ts +62 -0
- package/src/utils/pid.test.ts +152 -0
- package/src/utils/pid.ts +130 -0
- package/src/utils/process-scan.test.ts +53 -0
- package/src/utils/process-scan.ts +76 -0
- package/src/utils/time.test.ts +43 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/version.test.ts +33 -0
- package/src/utils/version.ts +70 -0
- package/src/version.ts +5 -0
- package/src/watchdog/daemon.test.ts +3721 -0
- package/src/watchdog/daemon.ts +1257 -0
- package/src/watchdog/health.test.ts +830 -0
- package/src/watchdog/health.ts +434 -0
- package/src/watchdog/triage.test.ts +205 -0
- package/src/watchdog/triage.ts +205 -0
- package/src/worktree/manager.test.ts +720 -0
- package/src/worktree/manager.ts +405 -0
- package/src/worktree/process.test.ts +172 -0
- package/src/worktree/process.ts +131 -0
- package/src/worktree/tmux.test.ts +1616 -0
- package/src/worktree/tmux.ts +721 -0
- package/templates/CLAUDE.md.tmpl +100 -0
- package/templates/copilot-hooks.json.tmpl +13 -0
- package/templates/hooks.json.tmpl +109 -0
- package/templates/overlay.md.tmpl +88 -0
- package/ui/dist/apple-touch-icon-bdy6teep.png +0 -0
- package/ui/dist/chunk-8s31f05k.css +1 -0
- package/ui/dist/chunk-vm5rz679.js +300 -0
- package/ui/dist/favicon-nzb39vza.svg +4 -0
- package/ui/dist/index.html +17 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: agentplate nudge <agent-name> [message]
|
|
3
|
+
*
|
|
4
|
+
* Sends a text nudge to an agent's interactive Claude Code session via
|
|
5
|
+
* tmux send-keys. Used to notify agents of new mail or relay urgent
|
|
6
|
+
* instructions mid-conversation.
|
|
7
|
+
*
|
|
8
|
+
* Includes retry logic (3 attempts) and debounce (500ms) to prevent
|
|
9
|
+
* rapid-fire nudges to the same agent.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { Command } from "commander";
|
|
14
|
+
import { encodeUserTurn } from "../agents/headless-prompt.ts";
|
|
15
|
+
import { createManifestLoader } from "../agents/manifest.ts";
|
|
16
|
+
import { type RunTurnOpts, runTurn, type TurnResult } from "../agents/turn-runner.ts";
|
|
17
|
+
import { buildRunTurnOptsFactory, isSpawnPerTurnAgent } from "../agents/turn-runner-dispatch.ts";
|
|
18
|
+
import { loadConfig } from "../config.ts";
|
|
19
|
+
import { AgentError } from "../errors.ts";
|
|
20
|
+
import { createEventStore } from "../events/store.ts";
|
|
21
|
+
import { jsonOutput } from "../json.ts";
|
|
22
|
+
import { printSuccess } from "../logging/color.ts";
|
|
23
|
+
import { getConnection } from "../runtimes/connections.ts";
|
|
24
|
+
import { hasNudge } from "../runtimes/headless-connection.ts";
|
|
25
|
+
import { openSessionStore } from "../sessions/compat.ts";
|
|
26
|
+
import type { AgentSession, EventStore } from "../types.ts";
|
|
27
|
+
import { capturePaneContent, isSessionAlive, sendKeys } from "../worktree/tmux.ts";
|
|
28
|
+
|
|
29
|
+
const DEFAULT_MESSAGE = "Check your mail inbox for new messages.";
|
|
30
|
+
const MAX_RETRIES = 3;
|
|
31
|
+
const RETRY_DELAY_MS = 500;
|
|
32
|
+
const DEBOUNCE_MS = 500;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Maximum total time (ms) to wait for a busy pane to become idle before
|
|
36
|
+
* giving up and reporting the nudge as deferred. Sized to ride out short
|
|
37
|
+
* tool calls without blocking long-running thinks.
|
|
38
|
+
*/
|
|
39
|
+
const IDLE_WAIT_MS = 3000;
|
|
40
|
+
const IDLE_POLL_INTERVAL_MS = 250;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Heuristic: does the captured pane content indicate the agent is mid-think?
|
|
44
|
+
*
|
|
45
|
+
* Claude Code's TUI shows "esc to interrupt" alongside the streaming token
|
|
46
|
+
* counter while a turn is in flight. The phrase is absent in idle state, when
|
|
47
|
+
* tool output is rendered, and on the trust dialog — so its presence is a
|
|
48
|
+
* reliable busy signal. Returns true when the agent appears busy and a nudge
|
|
49
|
+
* sent via tmux send-keys would be queued into the in-flight prompt instead
|
|
50
|
+
* of starting a fresh user turn. (agentplate-8ff4)
|
|
51
|
+
*/
|
|
52
|
+
export function paneAppearsBusy(paneContent: string): boolean {
|
|
53
|
+
return paneContent.includes("esc to interrupt");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Wait briefly for a tmux pane to leave the mid-think state.
|
|
58
|
+
*
|
|
59
|
+
* Polls capture-pane until the busy heuristic clears or the deadline elapses.
|
|
60
|
+
* Returns true if the pane became idle, false if it remained busy or pane
|
|
61
|
+
* capture failed throughout. Capture failures count as "not idle" so the
|
|
62
|
+
* caller defers the nudge rather than blasting send-keys into an unknown
|
|
63
|
+
* state. (agentplate-8ff4)
|
|
64
|
+
*/
|
|
65
|
+
async function waitForPaneIdle(
|
|
66
|
+
tmuxSession: string,
|
|
67
|
+
maxWaitMs: number = IDLE_WAIT_MS,
|
|
68
|
+
pollIntervalMs: number = IDLE_POLL_INTERVAL_MS,
|
|
69
|
+
): Promise<boolean> {
|
|
70
|
+
const deadline = Date.now() + maxWaitMs;
|
|
71
|
+
while (true) {
|
|
72
|
+
const content = await capturePaneContent(tmuxSession, 20);
|
|
73
|
+
if (content !== null && !paneAppearsBusy(content)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (Date.now() >= deadline) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
await Bun.sleep(pollIntervalMs);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Load the orchestrator's registered tmux session name.
|
|
85
|
+
*
|
|
86
|
+
* Written by `agentplate prime` at SessionStart when the orchestrator
|
|
87
|
+
* is running inside tmux. Enables agents to nudge the orchestrator
|
|
88
|
+
* even though it's not tracked in the SessionStore.
|
|
89
|
+
*/
|
|
90
|
+
async function loadOrchestratorTmuxSession(projectRoot: string): Promise<string | null> {
|
|
91
|
+
const regPath = join(projectRoot, ".agentplate", "orchestrator-tmux.json");
|
|
92
|
+
const file = Bun.file(regPath);
|
|
93
|
+
if (!(await file.exists())) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const text = await file.text();
|
|
98
|
+
const reg = JSON.parse(text) as { tmuxSession?: string };
|
|
99
|
+
return reg.tmuxSession ?? null;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve the tmux session name for an agent.
|
|
107
|
+
*
|
|
108
|
+
* For regular agents, looks up the SessionStore.
|
|
109
|
+
* For "orchestrator", falls back to the orchestrator-tmux.json registration
|
|
110
|
+
* file written by `agentplate prime`.
|
|
111
|
+
*
|
|
112
|
+
* Returns the tmux session name on success, or a structured `null` result that
|
|
113
|
+
* captures the terminal-state diagnosis so callers can surface a helpful
|
|
114
|
+
* recovery hint instead of a generic "no active session" error (agentplate-629f).
|
|
115
|
+
*/
|
|
116
|
+
type ResolveTargetResult =
|
|
117
|
+
| { kind: "found"; tmuxSession: string }
|
|
118
|
+
| { kind: "missing" }
|
|
119
|
+
| { kind: "terminal"; state: "completed" | "zombie"; capability: string; taskId: string };
|
|
120
|
+
|
|
121
|
+
async function resolveTargetSession(
|
|
122
|
+
projectRoot: string,
|
|
123
|
+
agentName: string,
|
|
124
|
+
): Promise<ResolveTargetResult> {
|
|
125
|
+
const agentplateDir = join(projectRoot, ".agentplate");
|
|
126
|
+
const { store } = openSessionStore(agentplateDir);
|
|
127
|
+
let terminal: ResolveTargetResult | null = null;
|
|
128
|
+
try {
|
|
129
|
+
const session = store.getByName(agentName);
|
|
130
|
+
if (session) {
|
|
131
|
+
if (session.state !== "zombie" && session.state !== "completed") {
|
|
132
|
+
return { kind: "found", tmuxSession: session.tmuxSession };
|
|
133
|
+
}
|
|
134
|
+
terminal = {
|
|
135
|
+
kind: "terminal",
|
|
136
|
+
state: session.state,
|
|
137
|
+
capability: session.capability,
|
|
138
|
+
taskId: session.taskId,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
} finally {
|
|
142
|
+
store.close();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fallback for orchestrator: check orchestrator-tmux.json
|
|
146
|
+
if (agentName === "orchestrator") {
|
|
147
|
+
const orchestratorTmux = await loadOrchestratorTmuxSession(projectRoot);
|
|
148
|
+
if (orchestratorTmux !== null) {
|
|
149
|
+
return { kind: "found", tmuxSession: orchestratorTmux };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return terminal ?? { kind: "missing" };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Build the operator-facing failure reason when a nudge cannot find a live
|
|
158
|
+
* session. Terminal-state agents get a recovery hint pointing at
|
|
159
|
+
* `ap sling --recover`; missing agents keep the generic message. (agentplate-629f)
|
|
160
|
+
*/
|
|
161
|
+
export function buildMissingSessionReason(
|
|
162
|
+
agentName: string,
|
|
163
|
+
resolution: ResolveTargetResult,
|
|
164
|
+
): string {
|
|
165
|
+
if (resolution.kind === "terminal") {
|
|
166
|
+
return (
|
|
167
|
+
`No active session for agent "${agentName}" (state: ${resolution.state}). ` +
|
|
168
|
+
`The agent has exited; re-dispatch with ` +
|
|
169
|
+
`'ap sling ${resolution.taskId} --capability ${resolution.capability} --recover'.`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return `No active session for agent "${agentName}"`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check debounce state for an agent. Returns true if a nudge was sent
|
|
177
|
+
* within the debounce window and should be skipped.
|
|
178
|
+
*/
|
|
179
|
+
async function isDebounced(statePath: string, agentName: string): Promise<boolean> {
|
|
180
|
+
const file = Bun.file(statePath);
|
|
181
|
+
if (!(await file.exists())) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const text = await file.text();
|
|
186
|
+
const state = JSON.parse(text) as Record<string, number>;
|
|
187
|
+
const lastNudge = state[agentName];
|
|
188
|
+
if (lastNudge === undefined) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return Date.now() - lastNudge < DEBOUNCE_MS;
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Record a nudge timestamp for debounce tracking.
|
|
199
|
+
*/
|
|
200
|
+
async function recordNudge(statePath: string, agentName: string): Promise<void> {
|
|
201
|
+
let state: Record<string, number> = {};
|
|
202
|
+
const file = Bun.file(statePath);
|
|
203
|
+
if (await file.exists()) {
|
|
204
|
+
try {
|
|
205
|
+
const text = await file.text();
|
|
206
|
+
state = JSON.parse(text) as Record<string, number>;
|
|
207
|
+
} catch {
|
|
208
|
+
// Corrupt state file — start fresh
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
state[agentName] = Date.now();
|
|
212
|
+
await Bun.write(statePath, `${JSON.stringify(state, null, "\t")}\n`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Outcome of a tmux nudge attempt. */
|
|
216
|
+
type SendNudgeResult =
|
|
217
|
+
| { kind: "delivered" }
|
|
218
|
+
| { kind: "deferred"; reason: string }
|
|
219
|
+
| { kind: "failed" };
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Send a nudge to an agent's tmux session with retry logic.
|
|
223
|
+
*
|
|
224
|
+
* @param tmuxSession - The tmux session name
|
|
225
|
+
* @param message - The text to send
|
|
226
|
+
* @returns delivered on success, deferred when the agent stays mid-think
|
|
227
|
+
* beyond the idle window, failed when send-keys exhausts retries.
|
|
228
|
+
*/
|
|
229
|
+
async function sendNudgeWithRetry(tmuxSession: string, message: string): Promise<SendNudgeResult> {
|
|
230
|
+
// Guard: never send-keys into a mid-think pane. Without this check, the
|
|
231
|
+
// nudge text is queued as input and corrupts the in-flight prompt.
|
|
232
|
+
// (agentplate-8ff4)
|
|
233
|
+
const idle = await waitForPaneIdle(tmuxSession);
|
|
234
|
+
if (!idle) {
|
|
235
|
+
return {
|
|
236
|
+
kind: "deferred",
|
|
237
|
+
reason: "Agent is mid-think (esc-to-interrupt visible) — nudge deferred",
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
242
|
+
try {
|
|
243
|
+
await sendKeys(tmuxSession, message);
|
|
244
|
+
// Follow-up Enter after a short delay to ensure submission.
|
|
245
|
+
// Claude Code's TUI may consume the first Enter during re-render/focus
|
|
246
|
+
// events, leaving text visible but unsubmitted (agentplate-t62v).
|
|
247
|
+
// Same workaround as sling.ts and coordinator.ts.
|
|
248
|
+
await Bun.sleep(500);
|
|
249
|
+
await sendKeys(tmuxSession, "");
|
|
250
|
+
return { kind: "delivered" };
|
|
251
|
+
} catch {
|
|
252
|
+
if (attempt < MAX_RETRIES) {
|
|
253
|
+
await Bun.sleep(RETRY_DELAY_MS);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { kind: "failed" };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Read the current run ID from current-run.txt, or null if no active run.
|
|
262
|
+
*/
|
|
263
|
+
async function readCurrentRunId(agentplateDir: string): Promise<string | null> {
|
|
264
|
+
const path = join(agentplateDir, "current-run.txt");
|
|
265
|
+
const file = Bun.file(path);
|
|
266
|
+
if (!(await file.exists())) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const text = await file.text();
|
|
271
|
+
const trimmed = text.trim();
|
|
272
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Fire-and-forget: record a nudge event to EventStore. Never throws.
|
|
280
|
+
*/
|
|
281
|
+
function recordNudgeEvent(
|
|
282
|
+
eventStore: EventStore,
|
|
283
|
+
opts: {
|
|
284
|
+
runId: string | null;
|
|
285
|
+
agentName: string;
|
|
286
|
+
from: string;
|
|
287
|
+
message: string;
|
|
288
|
+
delivered: boolean;
|
|
289
|
+
},
|
|
290
|
+
): void {
|
|
291
|
+
try {
|
|
292
|
+
eventStore.insert({
|
|
293
|
+
runId: opts.runId,
|
|
294
|
+
agentName: opts.agentName,
|
|
295
|
+
sessionId: null,
|
|
296
|
+
eventType: "custom",
|
|
297
|
+
toolName: null,
|
|
298
|
+
toolArgs: null,
|
|
299
|
+
toolDurationMs: null,
|
|
300
|
+
level: "info",
|
|
301
|
+
data: JSON.stringify({
|
|
302
|
+
type: "nudge",
|
|
303
|
+
from: opts.from,
|
|
304
|
+
message: opts.message,
|
|
305
|
+
delivered: opts.delivered,
|
|
306
|
+
}),
|
|
307
|
+
});
|
|
308
|
+
} catch {
|
|
309
|
+
// Fire-and-forget: event recording must never break nudge delivery
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Test-only injection point for the spawn-per-turn dispatch path. */
|
|
314
|
+
export interface NudgeAgentDeps {
|
|
315
|
+
_runTurnFn?: (opts: RunTurnOpts) => Promise<TurnResult>;
|
|
316
|
+
_loadConfig?: typeof loadConfig;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Look up the agent's session row. Returns null when missing or terminal.
|
|
321
|
+
* Terminal sessions are filtered here so the spawn-per-turn dispatch path
|
|
322
|
+
* never re-spawns a completed builder.
|
|
323
|
+
*/
|
|
324
|
+
function loadActiveSessionForNudge(projectRoot: string, agentName: string): AgentSession | null {
|
|
325
|
+
const agentplateDir = join(projectRoot, ".agentplate");
|
|
326
|
+
try {
|
|
327
|
+
const { store } = openSessionStore(agentplateDir);
|
|
328
|
+
try {
|
|
329
|
+
const session = store.getByName(agentName);
|
|
330
|
+
if (!session) return null;
|
|
331
|
+
if (session.state === "completed" || session.state === "zombie") return null;
|
|
332
|
+
return session;
|
|
333
|
+
} finally {
|
|
334
|
+
store.close();
|
|
335
|
+
}
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Best-effort: insert a nudge event into events.db. Never throws. */
|
|
342
|
+
function recordNudgeEventBestEffort(
|
|
343
|
+
agentplateDir: string,
|
|
344
|
+
agentName: string,
|
|
345
|
+
message: string,
|
|
346
|
+
delivered: boolean,
|
|
347
|
+
): void {
|
|
348
|
+
try {
|
|
349
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
350
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
351
|
+
try {
|
|
352
|
+
void readCurrentRunId(agentplateDir).then((runId) => {
|
|
353
|
+
try {
|
|
354
|
+
recordNudgeEvent(eventStore, {
|
|
355
|
+
runId,
|
|
356
|
+
agentName,
|
|
357
|
+
from: "orchestrator",
|
|
358
|
+
message,
|
|
359
|
+
delivered,
|
|
360
|
+
});
|
|
361
|
+
} finally {
|
|
362
|
+
try {
|
|
363
|
+
eventStore.close();
|
|
364
|
+
} catch {
|
|
365
|
+
// already closed
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
} catch {
|
|
370
|
+
try {
|
|
371
|
+
eventStore.close();
|
|
372
|
+
} catch {
|
|
373
|
+
// already closed
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
} catch {
|
|
377
|
+
// non-fatal
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
interface TryNudgeViaTurnRunnerInput {
|
|
382
|
+
agentName: string;
|
|
383
|
+
message: string;
|
|
384
|
+
agentplateDir: string;
|
|
385
|
+
projectRoot: string;
|
|
386
|
+
statePath: string;
|
|
387
|
+
deps: NudgeAgentDeps;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* If the target agent is a Phase 2 spawn-per-turn builder, deliver `message`
|
|
392
|
+
* as a single user turn through `runTurn` and return the delivery result.
|
|
393
|
+
*
|
|
394
|
+
* Returns `null` when the agent is not eligible (flag off, non-builder,
|
|
395
|
+
* terminal state, missing session, runtime cannot direct-spawn). The caller
|
|
396
|
+
* falls back to the legacy FIFO/connection/tmux paths.
|
|
397
|
+
*
|
|
398
|
+
* The runTurn call is awaited synchronously: that lets the in-process
|
|
399
|
+
* turn-lock serialize against the mail dispatcher running in `ap serve`.
|
|
400
|
+
* Failures throw — the caller treats them as a delivery error.
|
|
401
|
+
*/
|
|
402
|
+
async function tryNudgeViaTurnRunner(
|
|
403
|
+
input: TryNudgeViaTurnRunnerInput,
|
|
404
|
+
): Promise<{ delivered: boolean; queued?: boolean; reason?: string } | null> {
|
|
405
|
+
const session = loadActiveSessionForNudge(input.projectRoot, input.agentName);
|
|
406
|
+
if (!session) return null;
|
|
407
|
+
|
|
408
|
+
const _load = input.deps._loadConfig ?? loadConfig;
|
|
409
|
+
let config: Awaited<ReturnType<typeof loadConfig>>;
|
|
410
|
+
try {
|
|
411
|
+
config = await _load(input.projectRoot);
|
|
412
|
+
} catch {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const manifestLoader = createManifestLoader(
|
|
417
|
+
join(config.project.root, config.agents.manifestPath),
|
|
418
|
+
join(config.project.root, config.agents.baseDir),
|
|
419
|
+
);
|
|
420
|
+
let manifest: Awaited<ReturnType<typeof manifestLoader.load>>;
|
|
421
|
+
try {
|
|
422
|
+
manifest = await manifestLoader.load();
|
|
423
|
+
} catch {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
let factory: ReturnType<typeof buildRunTurnOptsFactory>;
|
|
428
|
+
try {
|
|
429
|
+
factory = buildRunTurnOptsFactory({
|
|
430
|
+
session,
|
|
431
|
+
config,
|
|
432
|
+
manifest,
|
|
433
|
+
agentplateDir: input.agentplateDir,
|
|
434
|
+
});
|
|
435
|
+
} catch {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!isSpawnPerTurnAgent(session, config, factory.runtime)) return null;
|
|
440
|
+
|
|
441
|
+
const runTurnFn = input.deps._runTurnFn ?? runTurn;
|
|
442
|
+
const opts = factory.build(encodeUserTurn(input.message));
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
const result = await runTurnFn(opts);
|
|
446
|
+
await recordNudge(input.statePath, input.agentName);
|
|
447
|
+
// Mirror the FIFO branch's queued semantics: the message has been
|
|
448
|
+
// consumed by claude inside this turn, but follow-up turns may still
|
|
449
|
+
// observe it as "queued" if the agent didn't act on it immediately.
|
|
450
|
+
return {
|
|
451
|
+
delivered: true,
|
|
452
|
+
queued: result.cleanResult !== true,
|
|
453
|
+
};
|
|
454
|
+
} catch (err) {
|
|
455
|
+
return {
|
|
456
|
+
delivered: false,
|
|
457
|
+
reason: `Spawn-per-turn dispatch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Core nudge function. Exported for use by mail send auto-nudge.
|
|
464
|
+
*
|
|
465
|
+
* Routes through the registered RuntimeConnection when available (headless agents),
|
|
466
|
+
* or falls back to the tmux send-keys path (interactive agents).
|
|
467
|
+
*
|
|
468
|
+
* Headless nudges return queued=true because Claude Code does not reliably poll
|
|
469
|
+
* stdin while an API stream is in flight — the message sits in the pipe buffer
|
|
470
|
+
* until the current turn completes.
|
|
471
|
+
*
|
|
472
|
+
* For task-scoped headless Claude (Phase 3 spawn-per-turn), the nudge becomes
|
|
473
|
+
* a single user-turn delivered via `runTurn`. The call awaits the turn
|
|
474
|
+
* synchronously so the in-process turn-lock can serialize against concurrent
|
|
475
|
+
* mail dispatchers.
|
|
476
|
+
*
|
|
477
|
+
* @param projectRoot - Absolute path to the project root
|
|
478
|
+
* @param agentName - Name of the agent to nudge
|
|
479
|
+
* @param message - Text to send (defaults to mail check prompt)
|
|
480
|
+
* @param force - Skip debounce check
|
|
481
|
+
* @returns Object with delivery status; queued=true when headless and buffered
|
|
482
|
+
*/
|
|
483
|
+
export async function nudgeAgent(
|
|
484
|
+
projectRoot: string,
|
|
485
|
+
agentName: string,
|
|
486
|
+
message: string = DEFAULT_MESSAGE,
|
|
487
|
+
force = false,
|
|
488
|
+
deps: NudgeAgentDeps = {},
|
|
489
|
+
): Promise<{ delivered: boolean; queued?: boolean; reason?: string }> {
|
|
490
|
+
let result: { delivered: boolean; queued?: boolean; reason?: string } | undefined;
|
|
491
|
+
|
|
492
|
+
const statePath = join(projectRoot, ".agentplate", "nudge-state.json");
|
|
493
|
+
|
|
494
|
+
// Check debounce early — applies to both headless and tmux paths
|
|
495
|
+
if (!force) {
|
|
496
|
+
const debounced = await isDebounced(statePath, agentName);
|
|
497
|
+
if (debounced) {
|
|
498
|
+
return { delivered: false, reason: "Debounced: nudge sent too recently" };
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const agentplateDir = join(projectRoot, ".agentplate");
|
|
503
|
+
|
|
504
|
+
// Runtime-agnostic delivery preference (mx-17830a):
|
|
505
|
+
// 1. Live in-process RuntimeConnection (Sapling RPC) → conn.nudge()
|
|
506
|
+
// 2. Spawn-per-turn task-scoped Claude → runTurn() (no live connection)
|
|
507
|
+
// 3. Tmux interactive agent → tmux send-keys
|
|
508
|
+
const inProcConn = getConnection(agentName);
|
|
509
|
+
if (inProcConn !== undefined && hasNudge(inProcConn)) {
|
|
510
|
+
// In-process RPC path (Sapling and friends).
|
|
511
|
+
const nudgeResult = await inProcConn.nudge(message);
|
|
512
|
+
await recordNudge(statePath, agentName);
|
|
513
|
+
result = { delivered: true, queued: nudgeResult.status === "Queued" };
|
|
514
|
+
} else {
|
|
515
|
+
// Spawn-per-turn dispatch for task-scoped headless Claude. When the
|
|
516
|
+
// agent is eligible, deliver the nudge as a user turn through `runTurn`.
|
|
517
|
+
// Returns null when ineligible (terminal state, persistent capability,
|
|
518
|
+
// flag off, etc.) and we fall through to the tmux path.
|
|
519
|
+
const spawnPerTurnResult = await tryNudgeViaTurnRunner({
|
|
520
|
+
agentName,
|
|
521
|
+
message,
|
|
522
|
+
agentplateDir,
|
|
523
|
+
projectRoot,
|
|
524
|
+
statePath,
|
|
525
|
+
deps,
|
|
526
|
+
});
|
|
527
|
+
if (spawnPerTurnResult !== null) {
|
|
528
|
+
recordNudgeEventBestEffort(agentplateDir, agentName, message, spawnPerTurnResult.delivered);
|
|
529
|
+
return spawnPerTurnResult;
|
|
530
|
+
}
|
|
531
|
+
// No live connection AND no spawn-per-turn eligibility — try tmux.
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (result === undefined) {
|
|
535
|
+
// Tmux path: resolve session name from SessionStore / orchestrator-tmux.json
|
|
536
|
+
const resolution = await resolveTargetSession(projectRoot, agentName);
|
|
537
|
+
|
|
538
|
+
if (resolution.kind !== "found") {
|
|
539
|
+
result = {
|
|
540
|
+
delivered: false,
|
|
541
|
+
reason: buildMissingSessionReason(agentName, resolution),
|
|
542
|
+
};
|
|
543
|
+
} else {
|
|
544
|
+
const tmuxSessionName = resolution.tmuxSession;
|
|
545
|
+
// Verify tmux session is alive
|
|
546
|
+
const alive = await isSessionAlive(tmuxSessionName);
|
|
547
|
+
if (!alive) {
|
|
548
|
+
result = {
|
|
549
|
+
delivered: false,
|
|
550
|
+
reason: `Tmux session "${tmuxSessionName}" is not alive`,
|
|
551
|
+
};
|
|
552
|
+
} else {
|
|
553
|
+
// Send with retry — sendNudgeWithRetry waits for an idle pane
|
|
554
|
+
// before attempting send-keys (agentplate-8ff4). It distinguishes
|
|
555
|
+
// "deferred" (agent mid-think) from "failed" (transient errors).
|
|
556
|
+
const sendResult = await sendNudgeWithRetry(tmuxSessionName, message);
|
|
557
|
+
if (sendResult.kind === "delivered") {
|
|
558
|
+
await recordNudge(statePath, agentName);
|
|
559
|
+
result = { delivered: true };
|
|
560
|
+
} else if (sendResult.kind === "deferred") {
|
|
561
|
+
result = { delivered: false, reason: sendResult.reason };
|
|
562
|
+
} else {
|
|
563
|
+
result = {
|
|
564
|
+
delivered: false,
|
|
565
|
+
reason: `Failed to send after ${MAX_RETRIES} attempts`,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Record event to EventStore (fire-and-forget)
|
|
573
|
+
try {
|
|
574
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
575
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
576
|
+
try {
|
|
577
|
+
const runId = await readCurrentRunId(agentplateDir);
|
|
578
|
+
recordNudgeEvent(eventStore, {
|
|
579
|
+
runId,
|
|
580
|
+
agentName,
|
|
581
|
+
from: "orchestrator",
|
|
582
|
+
message,
|
|
583
|
+
delivered: result.delivered,
|
|
584
|
+
});
|
|
585
|
+
} finally {
|
|
586
|
+
eventStore.close();
|
|
587
|
+
}
|
|
588
|
+
} catch {
|
|
589
|
+
// Event recording failure is non-fatal
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Entry point for `agentplate nudge <agent-name> [message]`.
|
|
597
|
+
*/
|
|
598
|
+
export async function nudgeCommand(args: string[]): Promise<void> {
|
|
599
|
+
const program = new Command();
|
|
600
|
+
program
|
|
601
|
+
.name("ap nudge")
|
|
602
|
+
.description("Send a text nudge to an agent")
|
|
603
|
+
.argument("<agent-name>", "Name of the agent to nudge")
|
|
604
|
+
.argument("[message...]", "Text to send (default: check mail prompt)")
|
|
605
|
+
.option("--from <name>", "Sender name", "orchestrator")
|
|
606
|
+
.option("--force", "Skip debounce check")
|
|
607
|
+
.option("--json", "Output result as JSON")
|
|
608
|
+
.exitOverride()
|
|
609
|
+
.action(
|
|
610
|
+
async (
|
|
611
|
+
agentName: string,
|
|
612
|
+
messageParts: string[],
|
|
613
|
+
opts: { from: string; force?: boolean; json?: boolean },
|
|
614
|
+
) => {
|
|
615
|
+
// Build the nudge message: prefix with sender, use custom or default text
|
|
616
|
+
const customMessage = messageParts.join(" ");
|
|
617
|
+
const rawMessage = customMessage.length > 0 ? customMessage : DEFAULT_MESSAGE;
|
|
618
|
+
const message = `[NUDGE from ${opts.from}] ${rawMessage}`;
|
|
619
|
+
|
|
620
|
+
// Resolve project root
|
|
621
|
+
const { resolveProjectRoot } = await import("../config.ts");
|
|
622
|
+
const projectRoot = await resolveProjectRoot(process.cwd());
|
|
623
|
+
|
|
624
|
+
const result = await nudgeAgent(projectRoot, agentName, message, opts.force ?? false);
|
|
625
|
+
|
|
626
|
+
if (opts.json) {
|
|
627
|
+
jsonOutput("nudge", {
|
|
628
|
+
agentName,
|
|
629
|
+
delivered: result.delivered,
|
|
630
|
+
queued: result.queued,
|
|
631
|
+
reason: result.reason,
|
|
632
|
+
});
|
|
633
|
+
} else if (result.delivered) {
|
|
634
|
+
if (result.queued) {
|
|
635
|
+
printSuccess("Nudge queued (headless — will process after current turn)", agentName);
|
|
636
|
+
} else {
|
|
637
|
+
printSuccess("Nudge delivered", agentName);
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
throw new AgentError(`Nudge failed: ${result.reason}`, { agentName });
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
await program.parseAsync(["node", "agentplate-nudge", ...args]);
|
|
646
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import {
|
|
3
|
+
type CoordinatorDeps,
|
|
4
|
+
createPersistentAgentCommand,
|
|
5
|
+
type PersistentAgentSpec,
|
|
6
|
+
persistentAgentCommand,
|
|
7
|
+
} from "./coordinator.ts";
|
|
8
|
+
|
|
9
|
+
const ORCHESTRATOR_SPEC: PersistentAgentSpec = {
|
|
10
|
+
commandName: "orchestrator",
|
|
11
|
+
displayName: "Orchestrator",
|
|
12
|
+
agentName: "orchestrator",
|
|
13
|
+
capability: "orchestrator",
|
|
14
|
+
agentDefFile: "orchestrator.md",
|
|
15
|
+
beaconBuilder: buildOrchestratorBeacon,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build the startup beacon for the ecosystem-level orchestrator session.
|
|
20
|
+
*/
|
|
21
|
+
export function buildOrchestratorBeacon(cliName = "bd"): string {
|
|
22
|
+
const timestamp = new Date().toISOString();
|
|
23
|
+
const parts = [
|
|
24
|
+
`[AGENTPLATE] orchestrator (orchestrator) ${timestamp}`,
|
|
25
|
+
"Depth: 0 | Parent: none | Role: persistent ecosystem orchestrator",
|
|
26
|
+
"HIERARCHY: You start per-repo coordinators with ap coordinator start --project <path>. Do NOT use ap sling directly.",
|
|
27
|
+
"DELEGATION: Work flows through sub-repo coordinators. Dispatch objectives by mail, then monitor coordinator progress and escalate only when needed.",
|
|
28
|
+
`Startup: run loam prime, check mail (ap mail check --agent orchestrator), check ${cliName} ready, inspect ecosystem status, then begin coordination`,
|
|
29
|
+
];
|
|
30
|
+
return parts.join(" — ");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createOrchestratorCommand(deps: CoordinatorDeps = {}): Command {
|
|
34
|
+
return createPersistentAgentCommand(ORCHESTRATOR_SPEC, deps);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function orchestratorCommand(
|
|
38
|
+
args: string[],
|
|
39
|
+
deps: CoordinatorDeps = {},
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
await persistentAgentCommand(args, ORCHESTRATOR_SPEC, deps);
|
|
42
|
+
}
|