@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,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: ap replay [--run <id>] [--agent <name>...] [--json]
|
|
3
|
+
* [--since <ts>] [--until <ts>] [--limit <n>]
|
|
4
|
+
*
|
|
5
|
+
* Shows an interleaved chronological replay of events across multiple agents.
|
|
6
|
+
* Like reading a combined log — all agents' events merged by timestamp.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
import { loadConfig } from "../config.ts";
|
|
12
|
+
import { ValidationError } from "../errors.ts";
|
|
13
|
+
import { createEventStore } from "../events/store.ts";
|
|
14
|
+
import { jsonOutput } from "../json.ts";
|
|
15
|
+
import { color } from "../logging/color.ts";
|
|
16
|
+
import {
|
|
17
|
+
buildAgentColorMap,
|
|
18
|
+
buildEventDetail,
|
|
19
|
+
formatAbsoluteTime,
|
|
20
|
+
formatDate,
|
|
21
|
+
formatRelativeTime,
|
|
22
|
+
} from "../logging/format.ts";
|
|
23
|
+
import { eventLabel, renderHeader } from "../logging/theme.ts";
|
|
24
|
+
import type { StoredEvent } from "../types.ts";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Print events as an interleaved timeline with ANSI colors and agent labels.
|
|
28
|
+
*/
|
|
29
|
+
function printReplay(events: StoredEvent[], useAbsoluteTime: boolean): void {
|
|
30
|
+
const w = process.stdout.write.bind(process.stdout);
|
|
31
|
+
|
|
32
|
+
w(`${renderHeader("Replay")}\n`);
|
|
33
|
+
|
|
34
|
+
if (events.length === 0) {
|
|
35
|
+
w(`${color.dim("No events found.")}\n`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
w(`${color.dim(`${events.length} event${events.length === 1 ? "" : "s"}`)}\n\n`);
|
|
40
|
+
|
|
41
|
+
const colorMap = buildAgentColorMap(events);
|
|
42
|
+
let lastDate = "";
|
|
43
|
+
|
|
44
|
+
for (const event of events) {
|
|
45
|
+
// Print date separator when the date changes
|
|
46
|
+
const date = formatDate(event.createdAt);
|
|
47
|
+
if (date && date !== lastDate) {
|
|
48
|
+
if (lastDate !== "") {
|
|
49
|
+
w("\n");
|
|
50
|
+
}
|
|
51
|
+
w(`${color.dim(`--- ${date} ---`)}\n`);
|
|
52
|
+
lastDate = date;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const timeStr = useAbsoluteTime
|
|
56
|
+
? formatAbsoluteTime(event.createdAt)
|
|
57
|
+
: formatRelativeTime(event.createdAt);
|
|
58
|
+
|
|
59
|
+
const label = eventLabel(event.eventType);
|
|
60
|
+
|
|
61
|
+
const levelColorFn =
|
|
62
|
+
event.level === "error" ? color.red : event.level === "warn" ? color.yellow : null;
|
|
63
|
+
const applyLevel = (text: string) => (levelColorFn ? levelColorFn(text) : text);
|
|
64
|
+
|
|
65
|
+
const detail = buildEventDetail(event);
|
|
66
|
+
const detailSuffix = detail ? ` ${color.dim(detail)}` : "";
|
|
67
|
+
|
|
68
|
+
const agentColorFn = colorMap.get(event.agentName) ?? color.gray;
|
|
69
|
+
const agentLabel = ` ${agentColorFn(`[${event.agentName}]`)}`;
|
|
70
|
+
|
|
71
|
+
w(
|
|
72
|
+
`${color.dim(timeStr.padStart(10))} ` +
|
|
73
|
+
`${applyLevel(label.color(color.bold(label.full)))}` +
|
|
74
|
+
`${agentLabel}${detailSuffix}\n`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface ReplayOpts {
|
|
80
|
+
run?: string;
|
|
81
|
+
agent: string[]; // repeatable
|
|
82
|
+
since?: string;
|
|
83
|
+
until?: string;
|
|
84
|
+
limit?: string;
|
|
85
|
+
json?: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function executeReplay(opts: ReplayOpts): Promise<void> {
|
|
89
|
+
const json = opts.json ?? false;
|
|
90
|
+
const runId = opts.run;
|
|
91
|
+
const agentNames = opts.agent;
|
|
92
|
+
const sinceStr = opts.since;
|
|
93
|
+
const untilStr = opts.until;
|
|
94
|
+
const limitStr = opts.limit;
|
|
95
|
+
const limit = limitStr ? Number.parseInt(limitStr, 10) : 200;
|
|
96
|
+
|
|
97
|
+
if (Number.isNaN(limit) || limit < 1) {
|
|
98
|
+
throw new ValidationError("--limit must be a positive integer", {
|
|
99
|
+
field: "limit",
|
|
100
|
+
value: limitStr,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Validate timestamps if provided
|
|
105
|
+
if (sinceStr !== undefined && Number.isNaN(new Date(sinceStr).getTime())) {
|
|
106
|
+
throw new ValidationError("--since must be a valid ISO 8601 timestamp", {
|
|
107
|
+
field: "since",
|
|
108
|
+
value: sinceStr,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (untilStr !== undefined && Number.isNaN(new Date(untilStr).getTime())) {
|
|
112
|
+
throw new ValidationError("--until must be a valid ISO 8601 timestamp", {
|
|
113
|
+
field: "until",
|
|
114
|
+
value: untilStr,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const cwd = process.cwd();
|
|
119
|
+
const config = await loadConfig(cwd);
|
|
120
|
+
const agentplateDir = join(config.project.root, ".agentplate");
|
|
121
|
+
|
|
122
|
+
// Open event store
|
|
123
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
124
|
+
const eventsFile = Bun.file(eventsDbPath);
|
|
125
|
+
if (!(await eventsFile.exists())) {
|
|
126
|
+
if (json) {
|
|
127
|
+
jsonOutput("replay", { events: [] });
|
|
128
|
+
} else {
|
|
129
|
+
process.stdout.write("No events data yet.\n");
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
let events: StoredEvent[];
|
|
138
|
+
const queryOpts = { since: sinceStr, until: untilStr, limit };
|
|
139
|
+
|
|
140
|
+
if (runId) {
|
|
141
|
+
// Query by run ID
|
|
142
|
+
events = eventStore.getByRun(runId, queryOpts);
|
|
143
|
+
} else if (agentNames.length > 0) {
|
|
144
|
+
// Query each agent and merge
|
|
145
|
+
const allEvents: StoredEvent[] = [];
|
|
146
|
+
for (const name of agentNames) {
|
|
147
|
+
const agentEvents = eventStore.getByAgent(name, {
|
|
148
|
+
since: sinceStr,
|
|
149
|
+
until: untilStr,
|
|
150
|
+
});
|
|
151
|
+
allEvents.push(...agentEvents);
|
|
152
|
+
}
|
|
153
|
+
// Sort by createdAt chronologically
|
|
154
|
+
allEvents.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
155
|
+
// Apply limit after merge
|
|
156
|
+
events = allEvents.slice(0, limit);
|
|
157
|
+
} else {
|
|
158
|
+
// Default: try current-run.txt, then fall back to 24h timeline
|
|
159
|
+
const currentRunPath = join(agentplateDir, "current-run.txt");
|
|
160
|
+
const currentRunFile = Bun.file(currentRunPath);
|
|
161
|
+
if (await currentRunFile.exists()) {
|
|
162
|
+
const currentRunId = (await currentRunFile.text()).trim();
|
|
163
|
+
if (currentRunId) {
|
|
164
|
+
events = eventStore.getByRun(currentRunId, queryOpts);
|
|
165
|
+
} else {
|
|
166
|
+
// Empty file, fall back to timeline
|
|
167
|
+
const since24h = sinceStr ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
168
|
+
events = eventStore.getTimeline({
|
|
169
|
+
since: since24h,
|
|
170
|
+
until: untilStr,
|
|
171
|
+
limit,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
// No current run file, fall back to 24h timeline
|
|
176
|
+
const since24h = sinceStr ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
177
|
+
events = eventStore.getTimeline({
|
|
178
|
+
since: since24h,
|
|
179
|
+
until: untilStr,
|
|
180
|
+
limit,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (json) {
|
|
186
|
+
jsonOutput("replay", { events });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Use absolute time if --since is specified, relative otherwise
|
|
191
|
+
const useAbsoluteTime = sinceStr !== undefined;
|
|
192
|
+
printReplay(events, useAbsoluteTime);
|
|
193
|
+
} finally {
|
|
194
|
+
eventStore.close();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function createReplayCommand(): Command {
|
|
199
|
+
return new Command("replay")
|
|
200
|
+
.description("Interleaved chronological replay across agents")
|
|
201
|
+
.option("--run <id>", "Filter events by run ID")
|
|
202
|
+
.option(
|
|
203
|
+
"--agent <name>",
|
|
204
|
+
"Filter by agent name (can appear multiple times)",
|
|
205
|
+
(val: string, prev: string[]) => [...prev, val],
|
|
206
|
+
[] as string[],
|
|
207
|
+
)
|
|
208
|
+
.option("--since <timestamp>", "Start time filter (ISO 8601)")
|
|
209
|
+
.option("--until <timestamp>", "End time filter (ISO 8601)")
|
|
210
|
+
.option("--limit <n>", "Max events to show (default: 200)")
|
|
211
|
+
.option("--json", "Output as JSON array of StoredEvent objects")
|
|
212
|
+
.action(async (opts: ReplayOpts) => {
|
|
213
|
+
await executeReplay(opts);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function replayCommand(args: string[]): Promise<void> {
|
|
218
|
+
const cmd = createReplayCommand();
|
|
219
|
+
cmd.exitOverride();
|
|
220
|
+
try {
|
|
221
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
222
|
+
} catch (err: unknown) {
|
|
223
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
224
|
+
const code = (err as { code: string }).code;
|
|
225
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for agentplate run command.
|
|
3
|
+
*
|
|
4
|
+
* Uses real SQLite (temp files) and real file I/O for current-run.txt.
|
|
5
|
+
* No mocks -- tests exercise the actual RunStore and SessionStore.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import { mkdir, mkdtemp, rm, unlink } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import type { SessionStore } from "../sessions/store.ts";
|
|
13
|
+
import { createRunStore, createSessionStore } from "../sessions/store.ts";
|
|
14
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
15
|
+
import type { AgentSession, InsertRun, RunStore } from "../types.ts";
|
|
16
|
+
|
|
17
|
+
let tempDir: string;
|
|
18
|
+
let agentplateDir: string;
|
|
19
|
+
let dbPath: string;
|
|
20
|
+
let runStore: RunStore;
|
|
21
|
+
let sessionStore: SessionStore;
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
tempDir = await mkdtemp(join(tmpdir(), "agentplate-run-test-"));
|
|
25
|
+
agentplateDir = join(tempDir, ".agentplate");
|
|
26
|
+
await mkdir(agentplateDir, { recursive: true });
|
|
27
|
+
dbPath = join(agentplateDir, "sessions.db");
|
|
28
|
+
runStore = createRunStore(dbPath);
|
|
29
|
+
sessionStore = createSessionStore(dbPath);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
runStore.close();
|
|
34
|
+
sessionStore.close();
|
|
35
|
+
await cleanupTempDir(tempDir);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** Write a run ID to current-run.txt. */
|
|
39
|
+
async function writeCurrentRun(runId: string): Promise<void> {
|
|
40
|
+
await Bun.write(join(agentplateDir, "current-run.txt"), runId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Read current-run.txt contents, or null if missing/empty. */
|
|
44
|
+
async function readCurrentRunFile(): Promise<string | null> {
|
|
45
|
+
const file = Bun.file(join(agentplateDir, "current-run.txt"));
|
|
46
|
+
if (!(await file.exists())) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const trimmed = (await file.text()).trim();
|
|
50
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Helper to create an InsertRun with optional overrides. */
|
|
54
|
+
function makeRun(overrides: Partial<InsertRun> = {}): InsertRun {
|
|
55
|
+
return {
|
|
56
|
+
id: "run-2026-02-13T10:00:00.000Z",
|
|
57
|
+
startedAt: "2026-02-13T10:00:00.000Z",
|
|
58
|
+
coordinatorSessionId: "coord-session-001",
|
|
59
|
+
status: "active",
|
|
60
|
+
...overrides,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Helper to create an AgentSession with optional overrides. */
|
|
65
|
+
function makeSession(overrides: Partial<AgentSession> = {}): AgentSession {
|
|
66
|
+
return {
|
|
67
|
+
id: "session-001",
|
|
68
|
+
agentName: "test-agent",
|
|
69
|
+
capability: "builder",
|
|
70
|
+
worktreePath: "/tmp/worktrees/test-agent",
|
|
71
|
+
branchName: "agentplate/test-agent/task-1",
|
|
72
|
+
taskId: "task-1",
|
|
73
|
+
tmuxSession: "agentplate-test-agent",
|
|
74
|
+
state: "working",
|
|
75
|
+
pid: 12345,
|
|
76
|
+
parentAgent: null,
|
|
77
|
+
depth: 0,
|
|
78
|
+
runId: null,
|
|
79
|
+
startedAt: "2026-02-13T10:00:00.000Z",
|
|
80
|
+
lastActivity: "2026-02-13T10:30:00.000Z",
|
|
81
|
+
escalationLevel: 0,
|
|
82
|
+
stalledSince: null,
|
|
83
|
+
transcriptPath: null,
|
|
84
|
+
...overrides,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================
|
|
89
|
+
// Direct function tests (testing the store interactions)
|
|
90
|
+
// ============================================================
|
|
91
|
+
|
|
92
|
+
describe("show current run (default)", () => {
|
|
93
|
+
test("shows 'No active run' when current-run.txt does not exist", async () => {
|
|
94
|
+
// No current-run.txt written -- file does not exist
|
|
95
|
+
const file = Bun.file(join(agentplateDir, "current-run.txt"));
|
|
96
|
+
expect(await file.exists()).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("reads run ID from current-run.txt", async () => {
|
|
100
|
+
const runId = "run-2026-02-13T10:00:00.000Z";
|
|
101
|
+
await writeCurrentRun(runId);
|
|
102
|
+
const content = await readCurrentRunFile();
|
|
103
|
+
expect(content).toBe(runId);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("fetches run details from RunStore", () => {
|
|
107
|
+
const run = makeRun();
|
|
108
|
+
runStore.createRun(run);
|
|
109
|
+
|
|
110
|
+
const fetched = runStore.getRun("run-2026-02-13T10:00:00.000Z");
|
|
111
|
+
expect(fetched).not.toBeNull();
|
|
112
|
+
expect(fetched?.id).toBe("run-2026-02-13T10:00:00.000Z");
|
|
113
|
+
expect(fetched?.status).toBe("active");
|
|
114
|
+
expect(fetched?.agentCount).toBe(0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("returns null for missing run ID", () => {
|
|
118
|
+
const fetched = runStore.getRun("nonexistent");
|
|
119
|
+
expect(fetched).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("list runs", () => {
|
|
124
|
+
test("returns empty array when no runs exist", () => {
|
|
125
|
+
const runs = runStore.listRuns({ limit: 10 });
|
|
126
|
+
expect(runs).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("returns runs ordered by started_at descending", () => {
|
|
130
|
+
runStore.createRun(makeRun({ id: "run-1", startedAt: "2026-02-13T08:00:00.000Z" }));
|
|
131
|
+
runStore.createRun(makeRun({ id: "run-2", startedAt: "2026-02-13T12:00:00.000Z" }));
|
|
132
|
+
runStore.createRun(makeRun({ id: "run-3", startedAt: "2026-02-13T10:00:00.000Z" }));
|
|
133
|
+
|
|
134
|
+
const runs = runStore.listRuns({ limit: 10 });
|
|
135
|
+
expect(runs).toHaveLength(3);
|
|
136
|
+
expect(runs[0]?.id).toBe("run-2");
|
|
137
|
+
expect(runs[1]?.id).toBe("run-3");
|
|
138
|
+
expect(runs[2]?.id).toBe("run-1");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("respects --last limit", () => {
|
|
142
|
+
for (let i = 0; i < 5; i++) {
|
|
143
|
+
runStore.createRun(
|
|
144
|
+
makeRun({
|
|
145
|
+
id: `run-${i}`,
|
|
146
|
+
startedAt: `2026-02-13T${String(10 + i).padStart(2, "0")}:00:00.000Z`,
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const runs = runStore.listRuns({ limit: 3 });
|
|
152
|
+
expect(runs).toHaveLength(3);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("includes completed and active runs", () => {
|
|
156
|
+
runStore.createRun(makeRun({ id: "run-active", status: "active" }));
|
|
157
|
+
runStore.createRun(
|
|
158
|
+
makeRun({
|
|
159
|
+
id: "run-done",
|
|
160
|
+
startedAt: "2026-02-13T11:00:00.000Z",
|
|
161
|
+
status: "active",
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
runStore.completeRun("run-done", "completed");
|
|
165
|
+
|
|
166
|
+
const runs = runStore.listRuns({ limit: 10 });
|
|
167
|
+
expect(runs).toHaveLength(2);
|
|
168
|
+
const statuses = runs.map((r) => r.status);
|
|
169
|
+
expect(statuses).toContain("active");
|
|
170
|
+
expect(statuses).toContain("completed");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("complete run", () => {
|
|
175
|
+
test("marks active run as completed in RunStore", () => {
|
|
176
|
+
runStore.createRun(makeRun());
|
|
177
|
+
|
|
178
|
+
runStore.completeRun("run-2026-02-13T10:00:00.000Z", "completed");
|
|
179
|
+
|
|
180
|
+
const run = runStore.getRun("run-2026-02-13T10:00:00.000Z");
|
|
181
|
+
expect(run?.status).toBe("completed");
|
|
182
|
+
expect(run?.completedAt).not.toBeNull();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("current-run.txt can be deleted after completion", async () => {
|
|
186
|
+
await writeCurrentRun("run-2026-02-13T10:00:00.000Z");
|
|
187
|
+
expect(await readCurrentRunFile()).toBe("run-2026-02-13T10:00:00.000Z");
|
|
188
|
+
|
|
189
|
+
// Simulate what completeCurrentRun does
|
|
190
|
+
await unlink(join(agentplateDir, "current-run.txt"));
|
|
191
|
+
const file = Bun.file(join(agentplateDir, "current-run.txt"));
|
|
192
|
+
expect(await file.exists()).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("no active run returns null from readCurrentRunFile", async () => {
|
|
196
|
+
// No current-run.txt exists
|
|
197
|
+
const content = await readCurrentRunFile();
|
|
198
|
+
expect(content).toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("empty current-run.txt returns null", async () => {
|
|
202
|
+
await Bun.write(join(agentplateDir, "current-run.txt"), "");
|
|
203
|
+
const content = await readCurrentRunFile();
|
|
204
|
+
expect(content).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("whitespace-only current-run.txt returns null", async () => {
|
|
208
|
+
await Bun.write(join(agentplateDir, "current-run.txt"), " \n ");
|
|
209
|
+
const content = await readCurrentRunFile();
|
|
210
|
+
expect(content).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("show run details", () => {
|
|
215
|
+
test("fetches run and its agents from stores", () => {
|
|
216
|
+
const runId = "run-2026-02-13T10:00:00.000Z";
|
|
217
|
+
runStore.createRun(makeRun());
|
|
218
|
+
|
|
219
|
+
sessionStore.upsert(
|
|
220
|
+
makeSession({
|
|
221
|
+
agentName: "builder-1",
|
|
222
|
+
id: "s-1",
|
|
223
|
+
runId,
|
|
224
|
+
capability: "builder",
|
|
225
|
+
state: "working",
|
|
226
|
+
}),
|
|
227
|
+
);
|
|
228
|
+
sessionStore.upsert(
|
|
229
|
+
makeSession({
|
|
230
|
+
agentName: "scout-1",
|
|
231
|
+
id: "s-2",
|
|
232
|
+
runId,
|
|
233
|
+
capability: "scout",
|
|
234
|
+
state: "completed",
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const run = runStore.getRun(runId);
|
|
239
|
+
expect(run).not.toBeNull();
|
|
240
|
+
// agentCount is derived from sessions in the same run.
|
|
241
|
+
expect(run?.agentCount).toBe(2);
|
|
242
|
+
|
|
243
|
+
const agents = sessionStore.getByRun(runId);
|
|
244
|
+
expect(agents).toHaveLength(2);
|
|
245
|
+
expect(agents.map((a) => a.agentName).sort()).toEqual(["builder-1", "scout-1"]);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("agent count includes coordinator session in the same run (agentplate-8e69)", () => {
|
|
249
|
+
const runId = "run-2026-02-13T10:00:00.000Z";
|
|
250
|
+
runStore.createRun(makeRun());
|
|
251
|
+
|
|
252
|
+
// Coordinator + two children — all sharing the same run_id.
|
|
253
|
+
sessionStore.upsert(
|
|
254
|
+
makeSession({
|
|
255
|
+
agentName: "coordinator",
|
|
256
|
+
id: "s-coord",
|
|
257
|
+
runId,
|
|
258
|
+
capability: "coordinator",
|
|
259
|
+
state: "working",
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
sessionStore.upsert(makeSession({ agentName: "lead-1", id: "s-1", runId, capability: "lead" }));
|
|
263
|
+
sessionStore.upsert(
|
|
264
|
+
makeSession({ agentName: "builder-1", id: "s-2", runId, capability: "builder" }),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const run = runStore.getRun(runId);
|
|
268
|
+
expect(run?.agentCount).toBe(3);
|
|
269
|
+
expect(sessionStore.getByRun(runId)).toHaveLength(3);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("agent count drops when a session is removed", () => {
|
|
273
|
+
const runId = "run-2026-02-13T10:00:00.000Z";
|
|
274
|
+
runStore.createRun(makeRun());
|
|
275
|
+
sessionStore.upsert(makeSession({ agentName: "a", id: "s-1", runId }));
|
|
276
|
+
sessionStore.upsert(makeSession({ agentName: "b", id: "s-2", runId }));
|
|
277
|
+
expect(runStore.getRun(runId)?.agentCount).toBe(2);
|
|
278
|
+
|
|
279
|
+
sessionStore.remove("a");
|
|
280
|
+
expect(runStore.getRun(runId)?.agentCount).toBe(1);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("returns null for missing run", () => {
|
|
284
|
+
const run = runStore.getRun("nonexistent-run");
|
|
285
|
+
expect(run).toBeNull();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("returns empty agents for run with no sessions", () => {
|
|
289
|
+
runStore.createRun(makeRun());
|
|
290
|
+
const agents = sessionStore.getByRun("run-2026-02-13T10:00:00.000Z");
|
|
291
|
+
expect(agents).toEqual([]);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("agents include capability and state", () => {
|
|
295
|
+
const runId = "run-2026-02-13T10:00:00.000Z";
|
|
296
|
+
runStore.createRun(makeRun());
|
|
297
|
+
|
|
298
|
+
sessionStore.upsert(
|
|
299
|
+
makeSession({
|
|
300
|
+
agentName: "reviewer-1",
|
|
301
|
+
id: "s-1",
|
|
302
|
+
runId,
|
|
303
|
+
capability: "reviewer",
|
|
304
|
+
state: "stalled",
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const agents = sessionStore.getByRun(runId);
|
|
309
|
+
expect(agents).toHaveLength(1);
|
|
310
|
+
expect(agents[0]?.capability).toBe("reviewer");
|
|
311
|
+
expect(agents[0]?.state).toBe("stalled");
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("--json output mode", () => {
|
|
316
|
+
test("current run JSON includes run and duration", () => {
|
|
317
|
+
runStore.createRun(makeRun());
|
|
318
|
+
const run = runStore.getRun("run-2026-02-13T10:00:00.000Z");
|
|
319
|
+
expect(run).not.toBeNull();
|
|
320
|
+
|
|
321
|
+
// Simulate JSON output structure
|
|
322
|
+
const output = JSON.stringify({ run, duration: "some-duration" });
|
|
323
|
+
const parsed = JSON.parse(output) as { run: unknown; duration: string };
|
|
324
|
+
expect(parsed.run).not.toBeNull();
|
|
325
|
+
expect(parsed.duration).toBe("some-duration");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("list JSON includes runs array", () => {
|
|
329
|
+
runStore.createRun(makeRun({ id: "run-1" }));
|
|
330
|
+
runStore.createRun(makeRun({ id: "run-2", startedAt: "2026-02-13T11:00:00.000Z" }));
|
|
331
|
+
|
|
332
|
+
const runs = runStore.listRuns({ limit: 10 });
|
|
333
|
+
const output = JSON.stringify({ runs });
|
|
334
|
+
const parsed = JSON.parse(output) as { runs: unknown[] };
|
|
335
|
+
expect(parsed.runs).toHaveLength(2);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("show JSON includes run and agents", () => {
|
|
339
|
+
const runId = "run-2026-02-13T10:00:00.000Z";
|
|
340
|
+
runStore.createRun(makeRun());
|
|
341
|
+
sessionStore.upsert(makeSession({ agentName: "a1", id: "s-1", runId }));
|
|
342
|
+
|
|
343
|
+
const run = runStore.getRun(runId);
|
|
344
|
+
const agents = sessionStore.getByRun(runId);
|
|
345
|
+
|
|
346
|
+
const output = JSON.stringify({ run, agents, duration: "1m 30s" });
|
|
347
|
+
const parsed = JSON.parse(output) as {
|
|
348
|
+
run: unknown;
|
|
349
|
+
agents: unknown[];
|
|
350
|
+
duration: string;
|
|
351
|
+
};
|
|
352
|
+
expect(parsed.run).not.toBeNull();
|
|
353
|
+
expect(parsed.agents).toHaveLength(1);
|
|
354
|
+
expect(parsed.duration).toBe("1m 30s");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("no active run JSON returns null run", () => {
|
|
358
|
+
// No current-run.txt exists
|
|
359
|
+
const output = JSON.stringify({ run: null, message: "No active run" });
|
|
360
|
+
const parsed = JSON.parse(output) as { run: unknown; message: string };
|
|
361
|
+
expect(parsed.run).toBeNull();
|
|
362
|
+
expect(parsed.message).toBe("No active run");
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe("duration formatting", () => {
|
|
367
|
+
test("run with completedAt uses that for duration endpoint", () => {
|
|
368
|
+
runStore.createRun(makeRun());
|
|
369
|
+
runStore.completeRun("run-2026-02-13T10:00:00.000Z", "completed");
|
|
370
|
+
|
|
371
|
+
const run = runStore.getRun("run-2026-02-13T10:00:00.000Z");
|
|
372
|
+
expect(run?.completedAt).not.toBeNull();
|
|
373
|
+
|
|
374
|
+
// Verify the start and end are both set, so duration can be computed
|
|
375
|
+
const start = new Date(run?.startedAt ?? "").getTime();
|
|
376
|
+
const end = new Date(run?.completedAt ?? "").getTime();
|
|
377
|
+
expect(end).toBeGreaterThan(start);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("active run uses current time as duration endpoint", () => {
|
|
381
|
+
runStore.createRun(makeRun());
|
|
382
|
+
|
|
383
|
+
const run = runStore.getRun("run-2026-02-13T10:00:00.000Z");
|
|
384
|
+
expect(run?.completedAt).toBeNull();
|
|
385
|
+
expect(run?.status).toBe("active");
|
|
386
|
+
|
|
387
|
+
// Duration should be from startedAt to now
|
|
388
|
+
const start = new Date(run?.startedAt ?? "").getTime();
|
|
389
|
+
expect(Date.now() - start).toBeGreaterThan(0);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe("multiple runs lifecycle", () => {
|
|
394
|
+
test("create, use, complete multiple runs sequentially", async () => {
|
|
395
|
+
// Run 1: two sessions, then completed.
|
|
396
|
+
runStore.createRun(makeRun({ id: "run-1", startedAt: "2026-02-13T08:00:00.000Z" }));
|
|
397
|
+
await writeCurrentRun("run-1");
|
|
398
|
+
sessionStore.upsert(makeSession({ agentName: "r1-a", id: "s-r1-1", runId: "run-1" }));
|
|
399
|
+
sessionStore.upsert(makeSession({ agentName: "r1-b", id: "s-r1-2", runId: "run-1" }));
|
|
400
|
+
runStore.completeRun("run-1", "completed");
|
|
401
|
+
|
|
402
|
+
// Run 2: one session, still active.
|
|
403
|
+
runStore.createRun(makeRun({ id: "run-2", startedAt: "2026-02-13T12:00:00.000Z" }));
|
|
404
|
+
await writeCurrentRun("run-2");
|
|
405
|
+
sessionStore.upsert(makeSession({ agentName: "r2-a", id: "s-r2-1", runId: "run-2" }));
|
|
406
|
+
|
|
407
|
+
// Verify state
|
|
408
|
+
const currentRunId = await readCurrentRunFile();
|
|
409
|
+
expect(currentRunId).toBe("run-2");
|
|
410
|
+
|
|
411
|
+
const run1 = runStore.getRun("run-1");
|
|
412
|
+
expect(run1?.status).toBe("completed");
|
|
413
|
+
expect(run1?.agentCount).toBe(2);
|
|
414
|
+
|
|
415
|
+
const run2 = runStore.getRun("run-2");
|
|
416
|
+
expect(run2?.status).toBe("active");
|
|
417
|
+
expect(run2?.agentCount).toBe(1);
|
|
418
|
+
|
|
419
|
+
const allRuns = runStore.listRuns();
|
|
420
|
+
expect(allRuns).toHaveLength(2);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
describe("edge cases", () => {
|
|
425
|
+
test("run with zero agents", () => {
|
|
426
|
+
runStore.createRun(makeRun());
|
|
427
|
+
const run = runStore.getRun("run-2026-02-13T10:00:00.000Z");
|
|
428
|
+
expect(run?.agentCount).toBe(0);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("show run with agents from different capabilities", () => {
|
|
432
|
+
const runId = "run-2026-02-13T10:00:00.000Z";
|
|
433
|
+
runStore.createRun(makeRun());
|
|
434
|
+
|
|
435
|
+
const capabilities = ["builder", "scout", "reviewer"];
|
|
436
|
+
for (let i = 0; i < capabilities.length; i++) {
|
|
437
|
+
sessionStore.upsert(
|
|
438
|
+
makeSession({
|
|
439
|
+
agentName: `agent-${i}`,
|
|
440
|
+
id: `s-${i}`,
|
|
441
|
+
runId,
|
|
442
|
+
capability: capabilities[i] ?? "builder",
|
|
443
|
+
}),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const agents = sessionStore.getByRun(runId);
|
|
448
|
+
expect(agents).toHaveLength(3);
|
|
449
|
+
const caps = agents.map((a) => a.capability).sort();
|
|
450
|
+
expect(caps).toEqual(["builder", "reviewer", "scout"]);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test("sessions.db does not exist for list returns empty", async () => {
|
|
454
|
+
// Remove the db that was created in beforeEach
|
|
455
|
+
runStore.close();
|
|
456
|
+
sessionStore.close();
|
|
457
|
+
await rm(dbPath, { force: true });
|
|
458
|
+
// Also remove WAL/SHM files
|
|
459
|
+
await rm(`${dbPath}-wal`, { force: true });
|
|
460
|
+
await rm(`${dbPath}-shm`, { force: true });
|
|
461
|
+
|
|
462
|
+
const file = Bun.file(dbPath);
|
|
463
|
+
expect(await file.exists()).toBe(false);
|
|
464
|
+
|
|
465
|
+
// Re-create stores for afterEach cleanup
|
|
466
|
+
runStore = createRunStore(join(agentplateDir, "unused-run.db"));
|
|
467
|
+
sessionStore = createSessionStore(join(agentplateDir, "unused-session.db"));
|
|
468
|
+
});
|
|
469
|
+
});
|