@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,801 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { ValidationError } from "../errors.ts";
|
|
6
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
7
|
+
import type { LogEvent } from "../types.ts";
|
|
8
|
+
import {
|
|
9
|
+
buildLogDetail,
|
|
10
|
+
discoverLogFiles,
|
|
11
|
+
filterEvents,
|
|
12
|
+
logsCommand,
|
|
13
|
+
parseLogFile,
|
|
14
|
+
pollLogTick,
|
|
15
|
+
} from "./logs.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Test helper: capture stdout during command execution.
|
|
19
|
+
* Since logsCommand writes to process.stdout.write, we temporarily replace it.
|
|
20
|
+
*/
|
|
21
|
+
async function captureStdout(fn: () => Promise<void>): Promise<string> {
|
|
22
|
+
let output = "";
|
|
23
|
+
const originalWrite = process.stdout.write;
|
|
24
|
+
|
|
25
|
+
process.stdout.write = ((chunk: string) => {
|
|
26
|
+
output += chunk;
|
|
27
|
+
return true;
|
|
28
|
+
}) as typeof process.stdout.write;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await fn();
|
|
32
|
+
} finally {
|
|
33
|
+
process.stdout.write = originalWrite;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("logsCommand", () => {
|
|
40
|
+
let tmpDir: string;
|
|
41
|
+
let originalCwd: string;
|
|
42
|
+
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
// Create a temp directory for each test
|
|
45
|
+
tmpDir = join(
|
|
46
|
+
tmpdir(),
|
|
47
|
+
`agentplate-logs-test-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
48
|
+
);
|
|
49
|
+
await mkdir(tmpDir, { recursive: true });
|
|
50
|
+
|
|
51
|
+
// Save original cwd and change to tmpDir so loadConfig finds our test config
|
|
52
|
+
originalCwd = process.cwd();
|
|
53
|
+
process.chdir(tmpDir);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(async () => {
|
|
57
|
+
// Restore cwd
|
|
58
|
+
process.chdir(originalCwd);
|
|
59
|
+
|
|
60
|
+
// Clean up temp directory
|
|
61
|
+
try {
|
|
62
|
+
await cleanupTempDir(tmpDir);
|
|
63
|
+
} catch {
|
|
64
|
+
// Ignore cleanup errors
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Helper: create a minimal config.yaml in tmpDir.
|
|
70
|
+
*/
|
|
71
|
+
async function createConfig(): Promise<void> {
|
|
72
|
+
const agentplateDir = join(tmpDir, ".agentplate");
|
|
73
|
+
await mkdir(agentplateDir, { recursive: true });
|
|
74
|
+
|
|
75
|
+
const configContent = `project:
|
|
76
|
+
name: test-project
|
|
77
|
+
root: ${tmpDir}
|
|
78
|
+
canonicalBranch: main
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
await writeFile(join(agentplateDir, "config.yaml"), configContent, "utf-8");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Helper: create an events.ndjson file for a given agent and session.
|
|
86
|
+
*/
|
|
87
|
+
async function createLogFile(
|
|
88
|
+
agentName: string,
|
|
89
|
+
sessionTimestamp: string,
|
|
90
|
+
events: LogEvent[],
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
const logsDir = join(tmpDir, ".agentplate", "logs", agentName, sessionTimestamp);
|
|
93
|
+
await mkdir(logsDir, { recursive: true });
|
|
94
|
+
|
|
95
|
+
const ndjson = events.map((e) => JSON.stringify(e)).join("\n");
|
|
96
|
+
await writeFile(join(logsDir, "events.ndjson"), ndjson, "utf-8");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
test("shows help text", async () => {
|
|
100
|
+
await createConfig();
|
|
101
|
+
|
|
102
|
+
const output = await captureStdout(async () => {
|
|
103
|
+
await logsCommand(["--help"]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(output).toContain("logs");
|
|
107
|
+
expect(output).toContain("--agent");
|
|
108
|
+
expect(output).toContain("--level");
|
|
109
|
+
expect(output).toContain("--since");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("no logs directory returns gracefully", async () => {
|
|
113
|
+
await createConfig();
|
|
114
|
+
// Do NOT create logs directory
|
|
115
|
+
|
|
116
|
+
const output = await captureStdout(async () => {
|
|
117
|
+
await logsCommand([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(output).toContain("No log files found");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("lists all entries across agents", async () => {
|
|
124
|
+
await createConfig();
|
|
125
|
+
|
|
126
|
+
const eventsAgentA: LogEvent[] = [
|
|
127
|
+
{
|
|
128
|
+
timestamp: "2026-01-01T10:00:00.000Z",
|
|
129
|
+
level: "info",
|
|
130
|
+
event: "tool.start",
|
|
131
|
+
agentName: "agent-a",
|
|
132
|
+
data: { toolName: "Bash" },
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const eventsAgentB: LogEvent[] = [
|
|
137
|
+
{
|
|
138
|
+
timestamp: "2026-01-02T11:00:00.000Z",
|
|
139
|
+
level: "error",
|
|
140
|
+
event: "spawn.failed",
|
|
141
|
+
agentName: "agent-b",
|
|
142
|
+
data: { errorMessage: "worktree exists" },
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
await createLogFile("agent-a", "2026-01-01T00-00-00-000Z", eventsAgentA);
|
|
147
|
+
await createLogFile("agent-b", "2026-01-02T00-00-00-000Z", eventsAgentB);
|
|
148
|
+
|
|
149
|
+
const output = await captureStdout(async () => {
|
|
150
|
+
await logsCommand([]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(output).toContain("tool.start");
|
|
154
|
+
expect(output).toContain("agent-a");
|
|
155
|
+
expect(output).toContain("spawn.failed");
|
|
156
|
+
expect(output).toContain("agent-b");
|
|
157
|
+
expect(output).toContain("2 entries");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("filters by agent", async () => {
|
|
161
|
+
await createConfig();
|
|
162
|
+
|
|
163
|
+
const eventsAgentA: LogEvent[] = [
|
|
164
|
+
{
|
|
165
|
+
timestamp: "2026-01-01T10:00:00.000Z",
|
|
166
|
+
level: "info",
|
|
167
|
+
event: "tool.start",
|
|
168
|
+
agentName: "agent-a",
|
|
169
|
+
data: {},
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const eventsAgentB: LogEvent[] = [
|
|
174
|
+
{
|
|
175
|
+
timestamp: "2026-01-02T11:00:00.000Z",
|
|
176
|
+
level: "info",
|
|
177
|
+
event: "worker.done",
|
|
178
|
+
agentName: "agent-b",
|
|
179
|
+
data: {},
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
await createLogFile("agent-a", "2026-01-01T00-00-00-000Z", eventsAgentA);
|
|
184
|
+
await createLogFile("agent-b", "2026-01-02T00-00-00-000Z", eventsAgentB);
|
|
185
|
+
|
|
186
|
+
const output = await captureStdout(async () => {
|
|
187
|
+
await logsCommand(["--agent", "agent-a"]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(output).toContain("tool.start");
|
|
191
|
+
expect(output).toContain("agent-a");
|
|
192
|
+
expect(output).not.toContain("worker.done");
|
|
193
|
+
expect(output).not.toContain("agent-b");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("filters by level", async () => {
|
|
197
|
+
await createConfig();
|
|
198
|
+
|
|
199
|
+
const events: LogEvent[] = [
|
|
200
|
+
{
|
|
201
|
+
timestamp: "2026-01-01T10:00:00.000Z",
|
|
202
|
+
level: "info",
|
|
203
|
+
event: "info.event",
|
|
204
|
+
agentName: "agent-a",
|
|
205
|
+
data: {},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
timestamp: "2026-01-01T10:01:00.000Z",
|
|
209
|
+
level: "error",
|
|
210
|
+
event: "error.event",
|
|
211
|
+
agentName: "agent-a",
|
|
212
|
+
data: {},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
timestamp: "2026-01-01T10:02:00.000Z",
|
|
216
|
+
level: "warn",
|
|
217
|
+
event: "warn.event",
|
|
218
|
+
agentName: "agent-a",
|
|
219
|
+
data: {},
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
await createLogFile("agent-a", "2026-01-01T00-00-00-000Z", events);
|
|
224
|
+
|
|
225
|
+
const output = await captureStdout(async () => {
|
|
226
|
+
await logsCommand(["--level", "error"]);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(output).toContain("error.event");
|
|
230
|
+
expect(output).not.toContain("info.event");
|
|
231
|
+
expect(output).not.toContain("warn.event");
|
|
232
|
+
expect(output).toContain("1 entry");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("respects --limit", async () => {
|
|
236
|
+
await createConfig();
|
|
237
|
+
|
|
238
|
+
const events: LogEvent[] = [];
|
|
239
|
+
for (let i = 0; i < 10; i++) {
|
|
240
|
+
events.push({
|
|
241
|
+
timestamp: `2026-01-01T10:${i.toString().padStart(2, "0")}:00.000Z`,
|
|
242
|
+
level: "info",
|
|
243
|
+
event: `event-${i}`,
|
|
244
|
+
agentName: "agent-a",
|
|
245
|
+
data: {},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await createLogFile("agent-a", "2026-01-01T00-00-00-000Z", events);
|
|
250
|
+
|
|
251
|
+
const output = await captureStdout(async () => {
|
|
252
|
+
await logsCommand(["--limit", "3"]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Should show the 3 most recent entries (event-7, event-8, event-9)
|
|
256
|
+
expect(output).toContain("3 entries");
|
|
257
|
+
expect(output).toContain("event-7");
|
|
258
|
+
expect(output).toContain("event-8");
|
|
259
|
+
expect(output).toContain("event-9");
|
|
260
|
+
expect(output).not.toContain("event-0");
|
|
261
|
+
expect(output).not.toContain("event-6");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("JSON output", async () => {
|
|
265
|
+
await createConfig();
|
|
266
|
+
|
|
267
|
+
const events: LogEvent[] = [
|
|
268
|
+
{
|
|
269
|
+
timestamp: "2026-01-01T10:00:00.000Z",
|
|
270
|
+
level: "info",
|
|
271
|
+
event: "tool.start",
|
|
272
|
+
agentName: "agent-a",
|
|
273
|
+
data: { toolName: "Bash" },
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
timestamp: "2026-01-02T11:00:00.000Z",
|
|
277
|
+
level: "error",
|
|
278
|
+
event: "spawn.failed",
|
|
279
|
+
agentName: "agent-b",
|
|
280
|
+
data: { errorMessage: "worktree exists" },
|
|
281
|
+
},
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
await createLogFile("agent-a", "2026-01-01T00-00-00-000Z", [events[0] as LogEvent]);
|
|
285
|
+
await createLogFile("agent-b", "2026-01-02T00-00-00-000Z", [events[1] as LogEvent]);
|
|
286
|
+
|
|
287
|
+
const output = await captureStdout(async () => {
|
|
288
|
+
await logsCommand(["--json"]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Parse JSON output
|
|
292
|
+
const parsed = JSON.parse(output.trim()) as { entries: LogEvent[] };
|
|
293
|
+
expect(Array.isArray(parsed.entries)).toBe(true);
|
|
294
|
+
|
|
295
|
+
expect(parsed.entries).toHaveLength(2);
|
|
296
|
+
expect(parsed.entries[0]?.event).toBe("tool.start");
|
|
297
|
+
expect(parsed.entries[1]?.event).toBe("spawn.failed");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("filters by --since with ISO timestamp", async () => {
|
|
301
|
+
await createConfig();
|
|
302
|
+
|
|
303
|
+
const events: LogEvent[] = [
|
|
304
|
+
{
|
|
305
|
+
timestamp: "2026-01-01T10:00:00.000Z",
|
|
306
|
+
level: "info",
|
|
307
|
+
event: "event-10:00",
|
|
308
|
+
agentName: "agent-a",
|
|
309
|
+
data: {},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
timestamp: "2026-01-01T11:00:00.000Z",
|
|
313
|
+
level: "info",
|
|
314
|
+
event: "event-11:00",
|
|
315
|
+
agentName: "agent-a",
|
|
316
|
+
data: {},
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
timestamp: "2026-01-01T12:00:00.000Z",
|
|
320
|
+
level: "info",
|
|
321
|
+
event: "event-12:00",
|
|
322
|
+
agentName: "agent-a",
|
|
323
|
+
data: {},
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
await createLogFile("agent-a", "2026-01-01T00-00-00-000Z", events);
|
|
328
|
+
|
|
329
|
+
const output = await captureStdout(async () => {
|
|
330
|
+
await logsCommand(["--since", "2026-01-01T11:00:00.000Z"]);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
expect(output).toContain("event-11:00");
|
|
334
|
+
expect(output).toContain("event-12:00");
|
|
335
|
+
expect(output).not.toContain("event-10:00");
|
|
336
|
+
expect(output).toContain("2 entries");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("invalid level throws ValidationError", async () => {
|
|
340
|
+
await createConfig();
|
|
341
|
+
|
|
342
|
+
await expect(
|
|
343
|
+
captureStdout(async () => {
|
|
344
|
+
await logsCommand(["--level", "critical"]);
|
|
345
|
+
}),
|
|
346
|
+
).rejects.toThrow(ValidationError);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("invalid limit throws ValidationError", async () => {
|
|
350
|
+
await createConfig();
|
|
351
|
+
|
|
352
|
+
await expect(
|
|
353
|
+
captureStdout(async () => {
|
|
354
|
+
await logsCommand(["--limit", "abc"]);
|
|
355
|
+
}),
|
|
356
|
+
).rejects.toThrow(ValidationError);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("handles malformed NDJSON lines gracefully", async () => {
|
|
360
|
+
await createConfig();
|
|
361
|
+
|
|
362
|
+
const logsDir = join(tmpDir, ".agentplate", "logs", "agent-a", "2026-01-01T00-00-00-000Z");
|
|
363
|
+
await mkdir(logsDir, { recursive: true });
|
|
364
|
+
|
|
365
|
+
// Write mixed valid and invalid NDJSON lines
|
|
366
|
+
const mixedContent = `{"timestamp":"2026-01-01T10:00:00.000Z","level":"info","event":"valid-event-1","agentName":"agent-a","data":{}}
|
|
367
|
+
this is not json
|
|
368
|
+
{"timestamp":"2026-01-01T10:01:00.000Z","level":"info","event":"valid-event-2","agentName":"agent-a","data":{}}
|
|
369
|
+
{"incomplete": "object"
|
|
370
|
+
{"timestamp":"2026-01-01T10:02:00.000Z","level":"info","event":"valid-event-3","agentName":"agent-a","data":{}}
|
|
371
|
+
`;
|
|
372
|
+
|
|
373
|
+
await writeFile(join(logsDir, "events.ndjson"), mixedContent, "utf-8");
|
|
374
|
+
|
|
375
|
+
const output = await captureStdout(async () => {
|
|
376
|
+
await logsCommand([]);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Should show the 3 valid events, silently skip the malformed lines
|
|
380
|
+
expect(output).toContain("valid-event-1");
|
|
381
|
+
expect(output).toContain("valid-event-2");
|
|
382
|
+
expect(output).toContain("valid-event-3");
|
|
383
|
+
expect(output).toContain("3 entries");
|
|
384
|
+
expect(output).not.toContain("this is not json");
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// parseRelativeTime tests moved to src/utils/time.test.ts
|
|
389
|
+
|
|
390
|
+
describe("buildLogDetail", () => {
|
|
391
|
+
test("builds key=value pairs from data fields", () => {
|
|
392
|
+
const event: LogEvent = {
|
|
393
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
394
|
+
level: "info",
|
|
395
|
+
event: "test",
|
|
396
|
+
agentName: "a",
|
|
397
|
+
data: { toolName: "Bash", file: "index.ts" },
|
|
398
|
+
};
|
|
399
|
+
const result = buildLogDetail(event);
|
|
400
|
+
expect(result).toContain("toolName=Bash");
|
|
401
|
+
expect(result).toContain("file=index.ts");
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("truncates values longer than 80 characters", () => {
|
|
405
|
+
const longValue = "x".repeat(100);
|
|
406
|
+
const event: LogEvent = {
|
|
407
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
408
|
+
level: "info",
|
|
409
|
+
event: "test",
|
|
410
|
+
agentName: "a",
|
|
411
|
+
data: { message: longValue },
|
|
412
|
+
};
|
|
413
|
+
const result = buildLogDetail(event);
|
|
414
|
+
expect(result).not.toContain(longValue);
|
|
415
|
+
expect(result).toContain("...");
|
|
416
|
+
// 77 chars + "..." = 80
|
|
417
|
+
expect(result).toContain("x".repeat(77));
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("skips null and undefined values", () => {
|
|
421
|
+
const event: LogEvent = {
|
|
422
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
423
|
+
level: "info",
|
|
424
|
+
event: "test",
|
|
425
|
+
agentName: "a",
|
|
426
|
+
data: { present: "yes", missing: null, also: undefined },
|
|
427
|
+
};
|
|
428
|
+
const result = buildLogDetail(event);
|
|
429
|
+
expect(result).toContain("present=yes");
|
|
430
|
+
expect(result).not.toContain("missing");
|
|
431
|
+
expect(result).not.toContain("also");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("returns empty string for empty data", () => {
|
|
435
|
+
const event: LogEvent = {
|
|
436
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
437
|
+
level: "info",
|
|
438
|
+
event: "test",
|
|
439
|
+
agentName: "a",
|
|
440
|
+
data: {},
|
|
441
|
+
};
|
|
442
|
+
expect(buildLogDetail(event)).toBe("");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("stringifies non-string values as JSON", () => {
|
|
446
|
+
const event: LogEvent = {
|
|
447
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
448
|
+
level: "info",
|
|
449
|
+
event: "test",
|
|
450
|
+
agentName: "a",
|
|
451
|
+
data: { count: 42, active: true },
|
|
452
|
+
};
|
|
453
|
+
const result = buildLogDetail(event);
|
|
454
|
+
expect(result).toContain("count=42");
|
|
455
|
+
expect(result).toContain("active=true");
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe("discoverLogFiles", () => {
|
|
460
|
+
let tmpDir: string;
|
|
461
|
+
|
|
462
|
+
beforeEach(async () => {
|
|
463
|
+
tmpDir = join(
|
|
464
|
+
tmpdir(),
|
|
465
|
+
`agentplate-discover-test-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
466
|
+
);
|
|
467
|
+
await mkdir(tmpDir, { recursive: true });
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
afterEach(async () => {
|
|
471
|
+
await cleanupTempDir(tmpDir);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("discovers log files in proper agent/session structure", async () => {
|
|
475
|
+
// Create agent-a/session-1/events.ndjson
|
|
476
|
+
const dir1 = join(tmpDir, "agent-a", "2026-01-01T00-00-00");
|
|
477
|
+
await mkdir(dir1, { recursive: true });
|
|
478
|
+
await writeFile(join(dir1, "events.ndjson"), "{}");
|
|
479
|
+
|
|
480
|
+
// Create agent-b/session-2/events.ndjson
|
|
481
|
+
const dir2 = join(tmpDir, "agent-b", "2026-01-02T00-00-00");
|
|
482
|
+
await mkdir(dir2, { recursive: true });
|
|
483
|
+
await writeFile(join(dir2, "events.ndjson"), "{}");
|
|
484
|
+
|
|
485
|
+
const result = await discoverLogFiles(tmpDir);
|
|
486
|
+
expect(result).toHaveLength(2);
|
|
487
|
+
expect(result[0]?.agentName).toBe("agent-a");
|
|
488
|
+
expect(result[1]?.agentName).toBe("agent-b");
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("filters by agent name when provided", async () => {
|
|
492
|
+
const dir1 = join(tmpDir, "agent-a", "2026-01-01T00-00-00");
|
|
493
|
+
await mkdir(dir1, { recursive: true });
|
|
494
|
+
await writeFile(join(dir1, "events.ndjson"), "{}");
|
|
495
|
+
|
|
496
|
+
const dir2 = join(tmpDir, "agent-b", "2026-01-02T00-00-00");
|
|
497
|
+
await mkdir(dir2, { recursive: true });
|
|
498
|
+
await writeFile(join(dir2, "events.ndjson"), "{}");
|
|
499
|
+
|
|
500
|
+
const result = await discoverLogFiles(tmpDir, "agent-a");
|
|
501
|
+
expect(result).toHaveLength(1);
|
|
502
|
+
expect(result[0]?.agentName).toBe("agent-a");
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test("returns empty array for nonexistent directory", async () => {
|
|
506
|
+
const result = await discoverLogFiles(join(tmpDir, "nonexistent"));
|
|
507
|
+
expect(result).toEqual([]);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test("sorts by session timestamp", async () => {
|
|
511
|
+
const dir1 = join(tmpDir, "agent-a", "2026-01-02T00-00-00");
|
|
512
|
+
await mkdir(dir1, { recursive: true });
|
|
513
|
+
await writeFile(join(dir1, "events.ndjson"), "{}");
|
|
514
|
+
|
|
515
|
+
const dir2 = join(tmpDir, "agent-a", "2026-01-01T00-00-00");
|
|
516
|
+
await mkdir(dir2, { recursive: true });
|
|
517
|
+
await writeFile(join(dir2, "events.ndjson"), "{}");
|
|
518
|
+
|
|
519
|
+
const result = await discoverLogFiles(tmpDir);
|
|
520
|
+
expect(result[0]?.sessionTimestamp).toBe("2026-01-01T00-00-00");
|
|
521
|
+
expect(result[1]?.sessionTimestamp).toBe("2026-01-02T00-00-00");
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
describe("parseLogFile", () => {
|
|
526
|
+
let tmpDir: string;
|
|
527
|
+
|
|
528
|
+
beforeEach(async () => {
|
|
529
|
+
tmpDir = join(
|
|
530
|
+
tmpdir(),
|
|
531
|
+
`agentplate-parse-test-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
532
|
+
);
|
|
533
|
+
await mkdir(tmpDir, { recursive: true });
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
afterEach(async () => {
|
|
537
|
+
await cleanupTempDir(tmpDir);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("parses valid NDJSON lines", async () => {
|
|
541
|
+
const filePath = join(tmpDir, "events.ndjson");
|
|
542
|
+
const lines = [
|
|
543
|
+
JSON.stringify({
|
|
544
|
+
timestamp: "2026-01-01T10:00:00Z",
|
|
545
|
+
event: "tool.start",
|
|
546
|
+
level: "info",
|
|
547
|
+
agentName: "a",
|
|
548
|
+
data: {},
|
|
549
|
+
}),
|
|
550
|
+
JSON.stringify({
|
|
551
|
+
timestamp: "2026-01-01T10:01:00Z",
|
|
552
|
+
event: "tool.end",
|
|
553
|
+
level: "info",
|
|
554
|
+
agentName: "a",
|
|
555
|
+
data: {},
|
|
556
|
+
}),
|
|
557
|
+
];
|
|
558
|
+
await writeFile(filePath, lines.join("\n"));
|
|
559
|
+
|
|
560
|
+
const events = await parseLogFile(filePath);
|
|
561
|
+
expect(events).toHaveLength(2);
|
|
562
|
+
expect(events[0]?.event).toBe("tool.start");
|
|
563
|
+
expect(events[1]?.event).toBe("tool.end");
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test("skips malformed JSON lines silently", async () => {
|
|
567
|
+
const filePath = join(tmpDir, "events.ndjson");
|
|
568
|
+
const content = [
|
|
569
|
+
JSON.stringify({
|
|
570
|
+
timestamp: "2026-01-01T10:00:00Z",
|
|
571
|
+
event: "valid",
|
|
572
|
+
level: "info",
|
|
573
|
+
agentName: "a",
|
|
574
|
+
data: {},
|
|
575
|
+
}),
|
|
576
|
+
"not valid json",
|
|
577
|
+
'{"incomplete": true',
|
|
578
|
+
JSON.stringify({
|
|
579
|
+
timestamp: "2026-01-01T10:02:00Z",
|
|
580
|
+
event: "also-valid",
|
|
581
|
+
level: "info",
|
|
582
|
+
agentName: "a",
|
|
583
|
+
data: {},
|
|
584
|
+
}),
|
|
585
|
+
].join("\n");
|
|
586
|
+
await writeFile(filePath, content);
|
|
587
|
+
|
|
588
|
+
const events = await parseLogFile(filePath);
|
|
589
|
+
expect(events).toHaveLength(2);
|
|
590
|
+
expect(events[0]?.event).toBe("valid");
|
|
591
|
+
expect(events[1]?.event).toBe("also-valid");
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test("returns empty array for nonexistent file", async () => {
|
|
595
|
+
const events = await parseLogFile(join(tmpDir, "nonexistent.ndjson"));
|
|
596
|
+
expect(events).toEqual([]);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("skips objects missing required fields", async () => {
|
|
600
|
+
const filePath = join(tmpDir, "events.ndjson");
|
|
601
|
+
const content = [
|
|
602
|
+
JSON.stringify({ timestamp: "2026-01-01T00:00:00Z" }), // missing "event"
|
|
603
|
+
JSON.stringify({ event: "test" }), // missing "timestamp"
|
|
604
|
+
JSON.stringify({
|
|
605
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
606
|
+
event: "good",
|
|
607
|
+
level: "info",
|
|
608
|
+
agentName: "a",
|
|
609
|
+
data: {},
|
|
610
|
+
}),
|
|
611
|
+
].join("\n");
|
|
612
|
+
await writeFile(filePath, content);
|
|
613
|
+
|
|
614
|
+
const events = await parseLogFile(filePath);
|
|
615
|
+
expect(events).toHaveLength(1);
|
|
616
|
+
expect(events[0]?.event).toBe("good");
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
describe("filterEvents", () => {
|
|
621
|
+
const baseEvents: LogEvent[] = [
|
|
622
|
+
{
|
|
623
|
+
timestamp: "2026-01-01T10:00:00.000Z",
|
|
624
|
+
level: "info",
|
|
625
|
+
event: "e1",
|
|
626
|
+
agentName: "a",
|
|
627
|
+
data: {},
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
timestamp: "2026-01-01T11:00:00.000Z",
|
|
631
|
+
level: "error",
|
|
632
|
+
event: "e2",
|
|
633
|
+
agentName: "a",
|
|
634
|
+
data: {},
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
timestamp: "2026-01-01T12:00:00.000Z",
|
|
638
|
+
level: "warn",
|
|
639
|
+
event: "e3",
|
|
640
|
+
agentName: "a",
|
|
641
|
+
data: {},
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
timestamp: "2026-01-01T13:00:00.000Z",
|
|
645
|
+
level: "debug",
|
|
646
|
+
event: "e4",
|
|
647
|
+
agentName: "a",
|
|
648
|
+
data: {},
|
|
649
|
+
},
|
|
650
|
+
];
|
|
651
|
+
|
|
652
|
+
test("filters by level", () => {
|
|
653
|
+
const result = filterEvents(baseEvents, { level: "error" });
|
|
654
|
+
expect(result).toHaveLength(1);
|
|
655
|
+
expect(result[0]?.event).toBe("e2");
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test("filters by since", () => {
|
|
659
|
+
const since = new Date("2026-01-01T11:30:00.000Z");
|
|
660
|
+
const result = filterEvents(baseEvents, { since });
|
|
661
|
+
expect(result).toHaveLength(2);
|
|
662
|
+
expect(result[0]?.event).toBe("e3");
|
|
663
|
+
expect(result[1]?.event).toBe("e4");
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test("filters by until", () => {
|
|
667
|
+
const until = new Date("2026-01-01T11:30:00.000Z");
|
|
668
|
+
const result = filterEvents(baseEvents, { until });
|
|
669
|
+
expect(result).toHaveLength(2);
|
|
670
|
+
expect(result[0]?.event).toBe("e1");
|
|
671
|
+
expect(result[1]?.event).toBe("e2");
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test("combines level + since + until", () => {
|
|
675
|
+
const result = filterEvents(baseEvents, {
|
|
676
|
+
level: "info",
|
|
677
|
+
since: new Date("2026-01-01T09:00:00.000Z"),
|
|
678
|
+
until: new Date("2026-01-01T10:30:00.000Z"),
|
|
679
|
+
});
|
|
680
|
+
expect(result).toHaveLength(1);
|
|
681
|
+
expect(result[0]?.event).toBe("e1");
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test("returns all events with no filters", () => {
|
|
685
|
+
const result = filterEvents(baseEvents, {});
|
|
686
|
+
expect(result).toHaveLength(4);
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
describe("pollLogTick", () => {
|
|
691
|
+
let tmpDir: string;
|
|
692
|
+
|
|
693
|
+
beforeEach(async () => {
|
|
694
|
+
tmpDir = join(
|
|
695
|
+
tmpdir(),
|
|
696
|
+
`agentplate-poll-test-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
697
|
+
);
|
|
698
|
+
await mkdir(tmpDir, { recursive: true });
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
afterEach(async () => {
|
|
702
|
+
await cleanupTempDir(tmpDir);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
test("returns 0 for empty files", async () => {
|
|
706
|
+
const filePath = join(tmpDir, "events.ndjson");
|
|
707
|
+
await writeFile(filePath, "");
|
|
708
|
+
|
|
709
|
+
const lastKnownSizes = new Map<string, number>();
|
|
710
|
+
const count = await pollLogTick([{ path: filePath }], lastKnownSizes, {});
|
|
711
|
+
expect(count).toBe(0);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test("returns count of new events from file with new lines", async () => {
|
|
715
|
+
const filePath = join(tmpDir, "events.ndjson");
|
|
716
|
+
const line1 = JSON.stringify({
|
|
717
|
+
timestamp: "2026-01-01T10:00:00Z",
|
|
718
|
+
event: "e1",
|
|
719
|
+
level: "info",
|
|
720
|
+
agentName: "a",
|
|
721
|
+
data: {},
|
|
722
|
+
});
|
|
723
|
+
const line2 = JSON.stringify({
|
|
724
|
+
timestamp: "2026-01-01T10:01:00Z",
|
|
725
|
+
event: "e2",
|
|
726
|
+
level: "info",
|
|
727
|
+
agentName: "a",
|
|
728
|
+
data: {},
|
|
729
|
+
});
|
|
730
|
+
await writeFile(filePath, `${line1}\n${line2}\n`);
|
|
731
|
+
|
|
732
|
+
const lastKnownSizes = new Map<string, number>();
|
|
733
|
+
// Capture stdout to prevent test noise
|
|
734
|
+
const origWrite = process.stdout.write;
|
|
735
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
736
|
+
try {
|
|
737
|
+
const count = await pollLogTick([{ path: filePath }], lastKnownSizes, {});
|
|
738
|
+
expect(count).toBe(2);
|
|
739
|
+
// lastKnownSizes should be updated
|
|
740
|
+
expect(lastKnownSizes.get(filePath)).toBeGreaterThan(0);
|
|
741
|
+
} finally {
|
|
742
|
+
process.stdout.write = origWrite;
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
test("returns 0 when no new data since last position", async () => {
|
|
747
|
+
const filePath = join(tmpDir, "events.ndjson");
|
|
748
|
+
const line = JSON.stringify({
|
|
749
|
+
timestamp: "2026-01-01T10:00:00Z",
|
|
750
|
+
event: "e1",
|
|
751
|
+
level: "info",
|
|
752
|
+
agentName: "a",
|
|
753
|
+
data: {},
|
|
754
|
+
});
|
|
755
|
+
await writeFile(filePath, `${line}\n`);
|
|
756
|
+
|
|
757
|
+
const lastKnownSizes = new Map<string, number>();
|
|
758
|
+
const origWrite = process.stdout.write;
|
|
759
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
760
|
+
try {
|
|
761
|
+
// First tick reads everything
|
|
762
|
+
await pollLogTick([{ path: filePath }], lastKnownSizes, {});
|
|
763
|
+
// Second tick should find nothing new
|
|
764
|
+
const count = await pollLogTick([{ path: filePath }], lastKnownSizes, {});
|
|
765
|
+
expect(count).toBe(0);
|
|
766
|
+
} finally {
|
|
767
|
+
process.stdout.write = origWrite;
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("applies level filter", async () => {
|
|
772
|
+
const filePath = join(tmpDir, "events.ndjson");
|
|
773
|
+
const line1 = JSON.stringify({
|
|
774
|
+
timestamp: "2026-01-01T10:00:00Z",
|
|
775
|
+
event: "e1",
|
|
776
|
+
level: "info",
|
|
777
|
+
agentName: "a",
|
|
778
|
+
data: {},
|
|
779
|
+
});
|
|
780
|
+
const line2 = JSON.stringify({
|
|
781
|
+
timestamp: "2026-01-01T10:01:00Z",
|
|
782
|
+
event: "e2",
|
|
783
|
+
level: "error",
|
|
784
|
+
agentName: "a",
|
|
785
|
+
data: {},
|
|
786
|
+
});
|
|
787
|
+
await writeFile(filePath, `${line1}\n${line2}\n`);
|
|
788
|
+
|
|
789
|
+
const lastKnownSizes = new Map<string, number>();
|
|
790
|
+
const origWrite = process.stdout.write;
|
|
791
|
+
process.stdout.write = (() => true) as typeof process.stdout.write;
|
|
792
|
+
try {
|
|
793
|
+
const count = await pollLogTick([{ path: filePath }], lastKnownSizes, {
|
|
794
|
+
level: "error",
|
|
795
|
+
});
|
|
796
|
+
expect(count).toBe(1);
|
|
797
|
+
} finally {
|
|
798
|
+
process.stdout.write = origWrite;
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
});
|