@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,958 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: ap log <event> --agent <name> [--stdin]
|
|
3
|
+
*
|
|
4
|
+
* Called by Pre/PostToolUse and Stop hooks.
|
|
5
|
+
* Events: tool-start, tool-end, session-end.
|
|
6
|
+
* Writes to .agentplate/logs/{agent-name}/{session-timestamp}/.
|
|
7
|
+
*
|
|
8
|
+
* When --stdin is passed, reads one line of JSON from stdin containing the full
|
|
9
|
+
* hook payload (tool_name, tool_input, transcript_path, session_id, etc.)
|
|
10
|
+
* and writes structured events to the EventStore for observability.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { Command } from "commander";
|
|
15
|
+
import { isStopHookPersistentCapability } from "../agents/capabilities.ts";
|
|
16
|
+
import { updateIdentity } from "../agents/identity.ts";
|
|
17
|
+
import { loadConfig } from "../config.ts";
|
|
18
|
+
import { ValidationError } from "../errors.ts";
|
|
19
|
+
import { createEventStore } from "../events/store.ts";
|
|
20
|
+
import { filterToolArgs } from "../events/tool-filter.ts";
|
|
21
|
+
import { analyzeSessionInsights } from "../insights/analyzer.ts";
|
|
22
|
+
import { hasWorkToVerify, runQualityGates } from "../insights/quality-gates.ts";
|
|
23
|
+
import { createLoamClient, type LoamClient } from "../loam/client.ts";
|
|
24
|
+
import { createLogger } from "../logging/logger.ts";
|
|
25
|
+
import { createMailClient } from "../mail/client.ts";
|
|
26
|
+
import { createMailStore } from "../mail/store.ts";
|
|
27
|
+
import { estimateCost } from "../metrics/pricing.ts";
|
|
28
|
+
import { createMetricsStore } from "../metrics/store.ts";
|
|
29
|
+
import { parseTranscriptUsage } from "../metrics/transcript.ts";
|
|
30
|
+
import { openSessionStore } from "../sessions/compat.ts";
|
|
31
|
+
import { createRunStore } from "../sessions/store.ts";
|
|
32
|
+
import type { AgentSession } from "../types.ts";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get or create a session timestamp directory for the agent.
|
|
36
|
+
* Uses a file-based marker to track the current session directory.
|
|
37
|
+
*/
|
|
38
|
+
async function getSessionDir(logsBase: string, agentName: string): Promise<string> {
|
|
39
|
+
const agentLogsDir = join(logsBase, agentName);
|
|
40
|
+
const markerPath = join(agentLogsDir, ".current-session");
|
|
41
|
+
|
|
42
|
+
const markerFile = Bun.file(markerPath);
|
|
43
|
+
if (await markerFile.exists()) {
|
|
44
|
+
const sessionDir = (await markerFile.text()).trim();
|
|
45
|
+
if (sessionDir.length > 0) {
|
|
46
|
+
return sessionDir;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create a new session directory
|
|
51
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
52
|
+
const sessionDir = join(agentLogsDir, timestamp);
|
|
53
|
+
const { mkdir } = await import("node:fs/promises");
|
|
54
|
+
await mkdir(sessionDir, { recursive: true });
|
|
55
|
+
await Bun.write(markerPath, sessionDir);
|
|
56
|
+
return sessionDir;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Update the lastActivity timestamp for an agent in the SessionStore.
|
|
61
|
+
* Non-fatal: silently ignores errors to avoid breaking hook execution.
|
|
62
|
+
*/
|
|
63
|
+
function updateLastActivity(projectRoot: string, agentName: string): void {
|
|
64
|
+
try {
|
|
65
|
+
const agentplateDir = join(projectRoot, ".agentplate");
|
|
66
|
+
const { store } = openSessionStore(agentplateDir);
|
|
67
|
+
try {
|
|
68
|
+
const session = store.getByName(agentName);
|
|
69
|
+
if (session) {
|
|
70
|
+
store.updateLastActivity(agentName);
|
|
71
|
+
// Tool-use observed: try booting → working. Matrix-guarded so a
|
|
72
|
+
// zombie classification (set by watchdog) is NOT silently revived
|
|
73
|
+
// here — that revival was a contributor to the schizophrenic
|
|
74
|
+
// state=zombie + tool-use-active symptom in agentplate-a993.
|
|
75
|
+
if (session.state === "booting") {
|
|
76
|
+
store.tryTransitionState(agentName, "working");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} finally {
|
|
80
|
+
store.close();
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// Non-fatal: don't break logging if session update fails
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Maximum retry attempts for the session-end transition.
|
|
89
|
+
*
|
|
90
|
+
* The Stop hook is the only signal that turns sessions.db state from
|
|
91
|
+
* "working" to "completed" for headless legacy paths and tmux sessions.
|
|
92
|
+
* If it loses that signal due to a transient SQLite contention error
|
|
93
|
+
* (e.g. "database is locked" while the watchdog ticks against the same
|
|
94
|
+
* file), the row stays in "working" forever and the watchdog later
|
|
95
|
+
* promotes it to "zombie". Retrying with exponential backoff lets brief
|
|
96
|
+
* lock contention resolve before we give up. (agentplate-e74b)
|
|
97
|
+
*/
|
|
98
|
+
const TRANSITION_MAX_ATTEMPTS = 5;
|
|
99
|
+
const TRANSITION_BACKOFF_BASE_MS = 50;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* One attempt at the session-end state transition.
|
|
103
|
+
*
|
|
104
|
+
* Throws on transient failures (e.g. SQLite "database is locked") so the
|
|
105
|
+
* caller can retry. The body is the original logic from
|
|
106
|
+
* `transitionToCompleted`.
|
|
107
|
+
*/
|
|
108
|
+
function transitionToCompletedOnce(projectRoot: string, agentName: string): void {
|
|
109
|
+
const agentplateDir = join(projectRoot, ".agentplate");
|
|
110
|
+
const { store } = openSessionStore(agentplateDir);
|
|
111
|
+
try {
|
|
112
|
+
const session = store.getByName(agentName);
|
|
113
|
+
if (session && isStopHookPersistentCapability(session.capability)) {
|
|
114
|
+
// Check if a persistent top-level agent self-exited by verifying the run
|
|
115
|
+
// is already completed.
|
|
116
|
+
// If `ap run complete` was called before session-end, the run status is 'completed'
|
|
117
|
+
// and we should transition the persistent session to completed too.
|
|
118
|
+
if (
|
|
119
|
+
(session.capability === "coordinator" || session.capability === "orchestrator") &&
|
|
120
|
+
session.runId
|
|
121
|
+
) {
|
|
122
|
+
const runStore = createRunStore(join(agentplateDir, "sessions.db"));
|
|
123
|
+
try {
|
|
124
|
+
const run = runStore.getRun(session.runId);
|
|
125
|
+
if (run && run.status === "completed") {
|
|
126
|
+
// Self-exit: the persistent agent called ap run complete before session ended
|
|
127
|
+
store.updateState(agentName, "completed");
|
|
128
|
+
store.updateLastActivity(agentName);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
runStore.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Normal persistent agent: only update activity, don't mark completed
|
|
136
|
+
store.updateLastActivity(agentName);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
store.updateState(agentName, "completed");
|
|
140
|
+
store.updateLastActivity(agentName);
|
|
141
|
+
} finally {
|
|
142
|
+
store.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Best-effort: log a session-end hook failure to events.db so it surfaces in
|
|
148
|
+
* `ap errors` and trace timelines. Swallows secondary errors (events.db may
|
|
149
|
+
* also be locked when the primary write failed).
|
|
150
|
+
*/
|
|
151
|
+
async function logHookFailure(
|
|
152
|
+
projectRoot: string,
|
|
153
|
+
agentName: string,
|
|
154
|
+
hookName: string,
|
|
155
|
+
error: unknown,
|
|
156
|
+
attempts: number,
|
|
157
|
+
): Promise<void> {
|
|
158
|
+
try {
|
|
159
|
+
const eventsDbPath = join(projectRoot, ".agentplate", "events.db");
|
|
160
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
161
|
+
try {
|
|
162
|
+
eventStore.insert({
|
|
163
|
+
runId: null,
|
|
164
|
+
agentName,
|
|
165
|
+
sessionId: null,
|
|
166
|
+
eventType: "error",
|
|
167
|
+
toolName: null,
|
|
168
|
+
toolArgs: null,
|
|
169
|
+
toolDurationMs: null,
|
|
170
|
+
level: "error",
|
|
171
|
+
data: JSON.stringify({
|
|
172
|
+
hook: hookName,
|
|
173
|
+
attempts,
|
|
174
|
+
message: error instanceof Error ? error.message : String(error),
|
|
175
|
+
}),
|
|
176
|
+
});
|
|
177
|
+
} finally {
|
|
178
|
+
eventStore.close();
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// Non-fatal: events.db may also be unavailable when the primary write failed.
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Transition agent state to 'completed' in the SessionStore.
|
|
187
|
+
* Called when session-end event fires.
|
|
188
|
+
*
|
|
189
|
+
* Retries on transient SQLite contention with exponential backoff
|
|
190
|
+
* (50/100/200/400/800ms). On persistent failure, records an `error` event
|
|
191
|
+
* to events.db so the missed signal shows up in observability tooling and
|
|
192
|
+
* the watchdog's stale-but-tmux-dead fallback can recognize it.
|
|
193
|
+
* (agentplate-e74b)
|
|
194
|
+
*
|
|
195
|
+
* Skips the transition for capabilities in `STOP_HOOK_PERSISTENT_CAPABILITIES`
|
|
196
|
+
* (coordinator, orchestrator, monitor, lead) whose Stop hook fires every model
|
|
197
|
+
* turn rather than once at true session end. See
|
|
198
|
+
* `src/agents/capabilities.ts` for the full rationale and consumer list.
|
|
199
|
+
*
|
|
200
|
+
* Non-fatal: silently ignores errors to avoid breaking hook execution.
|
|
201
|
+
*/
|
|
202
|
+
async function transitionToCompleted(projectRoot: string, agentName: string): Promise<void> {
|
|
203
|
+
let lastError: unknown;
|
|
204
|
+
for (let attempt = 0; attempt < TRANSITION_MAX_ATTEMPTS; attempt++) {
|
|
205
|
+
try {
|
|
206
|
+
transitionToCompletedOnce(projectRoot, agentName);
|
|
207
|
+
return;
|
|
208
|
+
} catch (err) {
|
|
209
|
+
lastError = err;
|
|
210
|
+
if (attempt < TRANSITION_MAX_ATTEMPTS - 1) {
|
|
211
|
+
await Bun.sleep(TRANSITION_BACKOFF_BASE_MS * 2 ** attempt);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// All retries failed — surface the missed signal via events.db.
|
|
217
|
+
await logHookFailure(
|
|
218
|
+
projectRoot,
|
|
219
|
+
agentName,
|
|
220
|
+
"session-end:transitionToCompleted",
|
|
221
|
+
lastError,
|
|
222
|
+
TRANSITION_MAX_ATTEMPTS,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Look up an agent's session record.
|
|
228
|
+
* Returns null if not found.
|
|
229
|
+
*/
|
|
230
|
+
function getAgentSession(projectRoot: string, agentName: string): AgentSession | null {
|
|
231
|
+
try {
|
|
232
|
+
const agentplateDir = join(projectRoot, ".agentplate");
|
|
233
|
+
const { store } = openSessionStore(agentplateDir);
|
|
234
|
+
try {
|
|
235
|
+
return store.getByName(agentName);
|
|
236
|
+
} finally {
|
|
237
|
+
store.close();
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Read one line of JSON from stdin. Returns parsed object or null on failure.
|
|
246
|
+
* Used when --stdin flag is present to receive hook payload from Claude Code.
|
|
247
|
+
*
|
|
248
|
+
* Reads ALL chunks from stdin to handle large payloads that exceed a single buffer.
|
|
249
|
+
*/
|
|
250
|
+
async function readStdinJson(): Promise<Record<string, unknown> | null> {
|
|
251
|
+
try {
|
|
252
|
+
const reader = Bun.stdin.stream().getReader();
|
|
253
|
+
const chunks: Uint8Array[] = [];
|
|
254
|
+
while (true) {
|
|
255
|
+
const { value, done } = await reader.read();
|
|
256
|
+
if (done) break;
|
|
257
|
+
if (value) chunks.push(value);
|
|
258
|
+
}
|
|
259
|
+
reader.releaseLock();
|
|
260
|
+
if (chunks.length === 0) return null;
|
|
261
|
+
// Concatenate all chunks
|
|
262
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
263
|
+
const combined = new Uint8Array(totalLength);
|
|
264
|
+
let offset = 0;
|
|
265
|
+
for (const chunk of chunks) {
|
|
266
|
+
combined.set(chunk, offset);
|
|
267
|
+
offset += chunk.length;
|
|
268
|
+
}
|
|
269
|
+
const text = new TextDecoder().decode(combined).trim();
|
|
270
|
+
if (text.length === 0) return null;
|
|
271
|
+
return JSON.parse(text) as Record<string, unknown>;
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Resolve the path to a Claude Code transcript JSONL file.
|
|
279
|
+
* Tries direct construction first, then searches all project directories.
|
|
280
|
+
* Caches the found path for faster subsequent lookups.
|
|
281
|
+
*/
|
|
282
|
+
async function resolveTranscriptPath(
|
|
283
|
+
projectRoot: string,
|
|
284
|
+
sessionId: string,
|
|
285
|
+
logsBase: string,
|
|
286
|
+
agentName: string,
|
|
287
|
+
): Promise<string | null> {
|
|
288
|
+
// Check SessionStore for a runtime-provided transcript path
|
|
289
|
+
try {
|
|
290
|
+
const { store } = openSessionStore(join(projectRoot, ".agentplate"));
|
|
291
|
+
try {
|
|
292
|
+
const session = store.getByName(agentName);
|
|
293
|
+
if (session?.transcriptPath) {
|
|
294
|
+
if (await Bun.file(session.transcriptPath).exists()) {
|
|
295
|
+
return session.transcriptPath;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} finally {
|
|
299
|
+
store.close();
|
|
300
|
+
}
|
|
301
|
+
} catch {
|
|
302
|
+
// Non-fatal: fall through to legacy resolution
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Check cached path first
|
|
306
|
+
const cachePath = join(logsBase, agentName, ".transcript-path");
|
|
307
|
+
const cacheFile = Bun.file(cachePath);
|
|
308
|
+
if (await cacheFile.exists()) {
|
|
309
|
+
const cached = (await cacheFile.text()).trim();
|
|
310
|
+
if (cached.length > 0 && (await Bun.file(cached).exists())) {
|
|
311
|
+
return cached;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const homeDir = process.env.HOME ?? "";
|
|
316
|
+
const claudeProjectsDir = join(homeDir, ".claude", "projects");
|
|
317
|
+
|
|
318
|
+
// Try direct construction from project root
|
|
319
|
+
const projectKey = projectRoot.replace(/\//g, "-");
|
|
320
|
+
const directPath = join(claudeProjectsDir, projectKey, `${sessionId}.jsonl`);
|
|
321
|
+
if (await Bun.file(directPath).exists()) {
|
|
322
|
+
await Bun.write(cachePath, directPath);
|
|
323
|
+
// Save discovered path to SessionStore for future lookups
|
|
324
|
+
try {
|
|
325
|
+
const { store: writeStore } = openSessionStore(join(projectRoot, ".agentplate"));
|
|
326
|
+
try {
|
|
327
|
+
writeStore.updateTranscriptPath(agentName, directPath);
|
|
328
|
+
} finally {
|
|
329
|
+
writeStore.close();
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
// Non-fatal: cache write failure should not break transcript resolution
|
|
333
|
+
}
|
|
334
|
+
return directPath;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Search all project directories for the session file
|
|
338
|
+
const { readdir } = await import("node:fs/promises");
|
|
339
|
+
try {
|
|
340
|
+
const projects = await readdir(claudeProjectsDir);
|
|
341
|
+
for (const project of projects) {
|
|
342
|
+
const candidate = join(claudeProjectsDir, project, `${sessionId}.jsonl`);
|
|
343
|
+
if (await Bun.file(candidate).exists()) {
|
|
344
|
+
await Bun.write(cachePath, candidate);
|
|
345
|
+
// Save discovered path to SessionStore for future lookups
|
|
346
|
+
try {
|
|
347
|
+
const { store: writeStore } = openSessionStore(join(projectRoot, ".agentplate"));
|
|
348
|
+
try {
|
|
349
|
+
writeStore.updateTranscriptPath(agentName, candidate);
|
|
350
|
+
} finally {
|
|
351
|
+
writeStore.close();
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
// Non-fatal: cache write failure should not break transcript resolution
|
|
355
|
+
}
|
|
356
|
+
return candidate;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
// Claude projects dir may not exist
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Auto-record expertise from loam learn results.
|
|
368
|
+
* Called during session-end for non-persistent agents.
|
|
369
|
+
* Records a reference entry for each suggested domain at the canonical root,
|
|
370
|
+
* then sends a slim notification mail to the parent agent.
|
|
371
|
+
*
|
|
372
|
+
* @returns List of successfully recorded domains
|
|
373
|
+
*/
|
|
374
|
+
export async function autoRecordExpertise(params: {
|
|
375
|
+
loamClient: LoamClient;
|
|
376
|
+
agentName: string;
|
|
377
|
+
capability: string;
|
|
378
|
+
taskId: string | null;
|
|
379
|
+
mailDbPath: string;
|
|
380
|
+
parentAgent: string | null;
|
|
381
|
+
projectRoot: string;
|
|
382
|
+
sessionStartedAt: string;
|
|
383
|
+
outcomeStatus?: "success" | "partial" | "failure";
|
|
384
|
+
}): Promise<string[]> {
|
|
385
|
+
const learnResult = await params.loamClient.learn({ since: "HEAD~1" });
|
|
386
|
+
if (learnResult.suggestedDomains.length === 0) {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const recordedDomains: string[] = [];
|
|
391
|
+
const filesList = learnResult.changedFiles.join(", ");
|
|
392
|
+
|
|
393
|
+
for (const domain of learnResult.suggestedDomains) {
|
|
394
|
+
try {
|
|
395
|
+
await params.loamClient.record(domain, {
|
|
396
|
+
type: "reference",
|
|
397
|
+
description: `${params.capability} agent ${params.agentName} completed work in this domain. Files: ${filesList}`,
|
|
398
|
+
tags: ["auto-session-end", params.capability],
|
|
399
|
+
evidenceBead: params.taskId ?? undefined,
|
|
400
|
+
outcomeStatus: params.outcomeStatus,
|
|
401
|
+
outcomeAgent: params.agentName,
|
|
402
|
+
});
|
|
403
|
+
recordedDomains.push(domain);
|
|
404
|
+
} catch {
|
|
405
|
+
// Non-fatal per domain: skip failed records
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Analyze session events for deeper insights (tool usage, file edits, errors)
|
|
410
|
+
let insightSummary = "";
|
|
411
|
+
try {
|
|
412
|
+
const eventsDbPath = join(params.projectRoot, ".agentplate", "events.db");
|
|
413
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
414
|
+
|
|
415
|
+
const events = eventStore.getByAgent(params.agentName, {
|
|
416
|
+
since: params.sessionStartedAt,
|
|
417
|
+
});
|
|
418
|
+
const toolStats = eventStore.getToolStats({
|
|
419
|
+
agentName: params.agentName,
|
|
420
|
+
since: params.sessionStartedAt,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
eventStore.close();
|
|
424
|
+
|
|
425
|
+
const analysis = analyzeSessionInsights({
|
|
426
|
+
events,
|
|
427
|
+
toolStats,
|
|
428
|
+
agentName: params.agentName,
|
|
429
|
+
capability: params.capability,
|
|
430
|
+
domains: learnResult.suggestedDomains,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Record each insight to loam
|
|
434
|
+
for (const insight of analysis.insights) {
|
|
435
|
+
try {
|
|
436
|
+
await params.loamClient.record(insight.domain, {
|
|
437
|
+
type: insight.type,
|
|
438
|
+
description: insight.description,
|
|
439
|
+
tags: insight.tags,
|
|
440
|
+
evidenceBead: params.taskId ?? undefined,
|
|
441
|
+
outcomeStatus: params.outcomeStatus,
|
|
442
|
+
outcomeAgent: params.agentName,
|
|
443
|
+
});
|
|
444
|
+
if (!recordedDomains.includes(insight.domain)) {
|
|
445
|
+
recordedDomains.push(insight.domain);
|
|
446
|
+
}
|
|
447
|
+
} catch {
|
|
448
|
+
// Non-fatal per insight: skip failed records
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Build insight summary for mail
|
|
453
|
+
if (analysis.insights.length > 0) {
|
|
454
|
+
const insightTypes = new Map<string, number>();
|
|
455
|
+
for (const insight of analysis.insights) {
|
|
456
|
+
const count = insightTypes.get(insight.type) ?? 0;
|
|
457
|
+
insightTypes.set(insight.type, count + 1);
|
|
458
|
+
}
|
|
459
|
+
const typeCounts = Array.from(insightTypes.entries())
|
|
460
|
+
.map(([type, count]) => `${count} ${type}`)
|
|
461
|
+
.join(", ");
|
|
462
|
+
insightSummary = `\n\nAuto-insights: ${typeCounts} (${analysis.toolProfile.totalToolCalls} tool calls, ${analysis.fileProfile.totalEdits} edits)`;
|
|
463
|
+
}
|
|
464
|
+
} catch {
|
|
465
|
+
// Non-fatal: insight analysis should not break session-end handling
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (recordedDomains.length > 0) {
|
|
469
|
+
const mailStore = createMailStore(params.mailDbPath);
|
|
470
|
+
const mailClient = createMailClient(mailStore);
|
|
471
|
+
const recipient = params.parentAgent ?? "orchestrator";
|
|
472
|
+
const domainsList = recordedDomains.join(", ");
|
|
473
|
+
mailClient.send({
|
|
474
|
+
from: params.agentName,
|
|
475
|
+
to: recipient,
|
|
476
|
+
subject: `loam: auto-recorded insights in ${domainsList}`,
|
|
477
|
+
body: `Session completed. Auto-recorded expertise in: ${domainsList}.\n\nChanged files: ${filesList}${insightSummary}`,
|
|
478
|
+
type: "status",
|
|
479
|
+
priority: "low",
|
|
480
|
+
});
|
|
481
|
+
mailClient.close();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return recordedDomains;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
interface AppliedRecordsData {
|
|
488
|
+
taskId: string | null;
|
|
489
|
+
agentName: string;
|
|
490
|
+
capability: string;
|
|
491
|
+
records: Array<{ id: string; domain: string }>;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Append outcome entries to the loam records that were applied when this agent was spawned.
|
|
496
|
+
*
|
|
497
|
+
* At spawn time, sling.ts writes .agentplate/agents/{name}/applied-records.json listing
|
|
498
|
+
* the mx-* IDs from the prime output. At session-end, this function reads that file,
|
|
499
|
+
* appends a "success" outcome to each record, and deletes the file.
|
|
500
|
+
*
|
|
501
|
+
* @returns Number of records successfully updated.
|
|
502
|
+
*/
|
|
503
|
+
export async function appendOutcomeToAppliedRecords(params: {
|
|
504
|
+
loamClient: LoamClient;
|
|
505
|
+
agentName: string;
|
|
506
|
+
capability: string;
|
|
507
|
+
taskId: string | null;
|
|
508
|
+
projectRoot: string;
|
|
509
|
+
outcomeStatus?: "success" | "partial" | "failure";
|
|
510
|
+
}): Promise<number> {
|
|
511
|
+
const appliedRecordsPath = join(
|
|
512
|
+
params.projectRoot,
|
|
513
|
+
".agentplate",
|
|
514
|
+
"agents",
|
|
515
|
+
params.agentName,
|
|
516
|
+
"applied-records.json",
|
|
517
|
+
);
|
|
518
|
+
const appliedFile = Bun.file(appliedRecordsPath);
|
|
519
|
+
if (!(await appliedFile.exists())) return 0;
|
|
520
|
+
|
|
521
|
+
let data: AppliedRecordsData;
|
|
522
|
+
try {
|
|
523
|
+
data = JSON.parse(await appliedFile.text()) as AppliedRecordsData;
|
|
524
|
+
} catch {
|
|
525
|
+
return 0;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const { records } = data;
|
|
529
|
+
if (!records || records.length === 0) return 0;
|
|
530
|
+
|
|
531
|
+
const taskSuffix = params.taskId ? ` for task ${params.taskId}` : "";
|
|
532
|
+
const status: "success" | "partial" | "failure" = params.outcomeStatus ?? "success";
|
|
533
|
+
const gateNote = params.outcomeStatus ? ` Quality gates: ${params.outcomeStatus}.` : "";
|
|
534
|
+
const outcome = {
|
|
535
|
+
status,
|
|
536
|
+
agent: params.agentName,
|
|
537
|
+
notes: `Applied by ${params.capability} agent ${params.agentName}${taskSuffix}. Session completed.${gateNote}`,
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
let appended = 0;
|
|
541
|
+
for (const { id, domain } of records) {
|
|
542
|
+
try {
|
|
543
|
+
await params.loamClient.appendOutcome(domain, id, outcome);
|
|
544
|
+
appended++;
|
|
545
|
+
} catch {
|
|
546
|
+
// Non-fatal per record
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
const { unlink } = await import("node:fs/promises");
|
|
552
|
+
await unlink(appliedRecordsPath);
|
|
553
|
+
} catch {
|
|
554
|
+
// Non-fatal: file may already be gone
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return appended;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Core implementation for the log command.
|
|
562
|
+
*/
|
|
563
|
+
async function runLog(opts: {
|
|
564
|
+
event: string;
|
|
565
|
+
agent: string;
|
|
566
|
+
toolName: string;
|
|
567
|
+
transcript: string | undefined;
|
|
568
|
+
stdin: boolean;
|
|
569
|
+
}): Promise<void> {
|
|
570
|
+
const validEvents = ["tool-start", "tool-end", "session-end"];
|
|
571
|
+
if (!validEvents.includes(opts.event)) {
|
|
572
|
+
throw new ValidationError(`Invalid event "${opts.event}". Valid: ${validEvents.join(", ")}`, {
|
|
573
|
+
field: "event",
|
|
574
|
+
value: opts.event,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Read stdin payload if --stdin flag is set
|
|
579
|
+
let stdinPayload: Record<string, unknown> | null = null;
|
|
580
|
+
if (opts.stdin) {
|
|
581
|
+
stdinPayload = await readStdinJson();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Extract fields from stdin payload (preferred) or fall back to flags
|
|
585
|
+
const toolName =
|
|
586
|
+
typeof stdinPayload?.tool_name === "string" ? stdinPayload.tool_name : opts.toolName;
|
|
587
|
+
const toolInput =
|
|
588
|
+
stdinPayload?.tool_input !== undefined &&
|
|
589
|
+
stdinPayload?.tool_input !== null &&
|
|
590
|
+
typeof stdinPayload.tool_input === "object"
|
|
591
|
+
? (stdinPayload.tool_input as Record<string, unknown>)
|
|
592
|
+
: null;
|
|
593
|
+
const sessionId = typeof stdinPayload?.session_id === "string" ? stdinPayload.session_id : null;
|
|
594
|
+
const transcriptPath =
|
|
595
|
+
typeof stdinPayload?.transcript_path === "string"
|
|
596
|
+
? stdinPayload.transcript_path
|
|
597
|
+
: opts.transcript;
|
|
598
|
+
|
|
599
|
+
const cwd = process.cwd();
|
|
600
|
+
const config = await loadConfig(cwd);
|
|
601
|
+
const logsBase = join(config.project.root, ".agentplate", "logs");
|
|
602
|
+
const sessionDir = await getSessionDir(logsBase, opts.agent);
|
|
603
|
+
|
|
604
|
+
const logger = createLogger({
|
|
605
|
+
logDir: sessionDir,
|
|
606
|
+
agentName: opts.agent,
|
|
607
|
+
verbose: config.logging.verbose,
|
|
608
|
+
redactSecrets: config.logging.redactSecrets,
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
switch (opts.event) {
|
|
612
|
+
case "tool-start": {
|
|
613
|
+
// Backward compatibility: always write to per-agent log files
|
|
614
|
+
logger.toolStart(toolName, toolInput ?? {});
|
|
615
|
+
updateLastActivity(config.project.root, opts.agent);
|
|
616
|
+
|
|
617
|
+
// Always write to EventStore for structured observability
|
|
618
|
+
// (works for both Claude Code --stdin and Pi runtime --tool-name agents)
|
|
619
|
+
try {
|
|
620
|
+
const eventsDbPath = join(config.project.root, ".agentplate", "events.db");
|
|
621
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
622
|
+
const filtered = toolInput
|
|
623
|
+
? filterToolArgs(toolName, toolInput)
|
|
624
|
+
: { args: {}, summary: toolName };
|
|
625
|
+
eventStore.insert({
|
|
626
|
+
runId: null,
|
|
627
|
+
agentName: opts.agent,
|
|
628
|
+
sessionId,
|
|
629
|
+
eventType: "tool_start",
|
|
630
|
+
toolName,
|
|
631
|
+
toolArgs: JSON.stringify(filtered.args),
|
|
632
|
+
toolDurationMs: null,
|
|
633
|
+
level: "info",
|
|
634
|
+
data: JSON.stringify({ summary: filtered.summary }),
|
|
635
|
+
});
|
|
636
|
+
eventStore.close();
|
|
637
|
+
} catch {
|
|
638
|
+
// Non-fatal: EventStore write should not break hook execution
|
|
639
|
+
}
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
case "tool-end": {
|
|
643
|
+
// Backward compatibility: always write to per-agent log files
|
|
644
|
+
logger.toolEnd(toolName, 0);
|
|
645
|
+
updateLastActivity(config.project.root, opts.agent);
|
|
646
|
+
|
|
647
|
+
// Always write to EventStore for structured observability
|
|
648
|
+
// (works for both Claude Code --stdin and Pi runtime --tool-name agents)
|
|
649
|
+
try {
|
|
650
|
+
const eventsDbPath = join(config.project.root, ".agentplate", "events.db");
|
|
651
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
652
|
+
const filtered = toolInput
|
|
653
|
+
? filterToolArgs(toolName, toolInput)
|
|
654
|
+
: { args: {}, summary: toolName };
|
|
655
|
+
eventStore.insert({
|
|
656
|
+
runId: null,
|
|
657
|
+
agentName: opts.agent,
|
|
658
|
+
sessionId,
|
|
659
|
+
eventType: "tool_end",
|
|
660
|
+
toolName,
|
|
661
|
+
toolArgs: JSON.stringify(filtered.args),
|
|
662
|
+
toolDurationMs: null,
|
|
663
|
+
level: "info",
|
|
664
|
+
data: JSON.stringify({ summary: filtered.summary }),
|
|
665
|
+
});
|
|
666
|
+
const correlation = eventStore.correlateToolEnd(opts.agent, toolName);
|
|
667
|
+
if (correlation) {
|
|
668
|
+
logger.toolEnd(toolName, correlation.durationMs);
|
|
669
|
+
}
|
|
670
|
+
eventStore.close();
|
|
671
|
+
} catch {
|
|
672
|
+
// Non-fatal: EventStore write should not break hook execution
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Throttled token snapshot recording (requires sessionId from --stdin; skipped for Pi agents)
|
|
676
|
+
if (sessionId) {
|
|
677
|
+
try {
|
|
678
|
+
// Throttle check
|
|
679
|
+
const snapshotMarkerPath = join(logsBase, opts.agent, ".last-snapshot");
|
|
680
|
+
const SNAPSHOT_INTERVAL_MS = 30_000;
|
|
681
|
+
const snapshotMarkerFile = Bun.file(snapshotMarkerPath);
|
|
682
|
+
let shouldSnapshot = true;
|
|
683
|
+
|
|
684
|
+
if (await snapshotMarkerFile.exists()) {
|
|
685
|
+
const lastTs = Number.parseInt(await snapshotMarkerFile.text(), 10);
|
|
686
|
+
if (!Number.isNaN(lastTs) && Date.now() - lastTs < SNAPSHOT_INTERVAL_MS) {
|
|
687
|
+
shouldSnapshot = false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (shouldSnapshot) {
|
|
692
|
+
const resolvedTranscriptPath = await resolveTranscriptPath(
|
|
693
|
+
config.project.root,
|
|
694
|
+
sessionId,
|
|
695
|
+
logsBase,
|
|
696
|
+
opts.agent,
|
|
697
|
+
);
|
|
698
|
+
if (resolvedTranscriptPath) {
|
|
699
|
+
const usage = await parseTranscriptUsage(resolvedTranscriptPath);
|
|
700
|
+
const cost = estimateCost(usage);
|
|
701
|
+
const metricsDbPath = join(config.project.root, ".agentplate", "metrics.db");
|
|
702
|
+
const metricsStore = createMetricsStore(metricsDbPath);
|
|
703
|
+
const agentSession = getAgentSession(config.project.root, opts.agent);
|
|
704
|
+
metricsStore.recordSnapshot({
|
|
705
|
+
agentName: opts.agent,
|
|
706
|
+
inputTokens: usage.inputTokens,
|
|
707
|
+
outputTokens: usage.outputTokens,
|
|
708
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
709
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
710
|
+
estimatedCostUsd: cost,
|
|
711
|
+
modelUsed: usage.modelUsed,
|
|
712
|
+
runId: agentSession?.runId ?? null,
|
|
713
|
+
createdAt: new Date().toISOString(),
|
|
714
|
+
});
|
|
715
|
+
metricsStore.close();
|
|
716
|
+
await Bun.write(snapshotMarkerPath, String(Date.now()));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
} catch {
|
|
720
|
+
// Non-fatal: snapshot recording should not break tool-end handling
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
case "session-end":
|
|
726
|
+
logger.info("session.end", { agentName: opts.agent });
|
|
727
|
+
// Transition agent state to completed (with retry/backoff and
|
|
728
|
+
// events.db fallback on persistent failure — agentplate-e74b).
|
|
729
|
+
await transitionToCompleted(config.project.root, opts.agent);
|
|
730
|
+
// Look up agent session for identity update and metrics recording
|
|
731
|
+
{
|
|
732
|
+
const agentSession = getAgentSession(config.project.root, opts.agent);
|
|
733
|
+
const taskId = agentSession?.taskId ?? null;
|
|
734
|
+
|
|
735
|
+
// Update agent identity with completed session
|
|
736
|
+
const identityBaseDir = join(config.project.root, ".agentplate", "agents");
|
|
737
|
+
try {
|
|
738
|
+
await updateIdentity(identityBaseDir, opts.agent, {
|
|
739
|
+
sessionsCompleted: 1,
|
|
740
|
+
completedTask: taskId ? { taskId, summary: `Completed task ${taskId}` } : undefined,
|
|
741
|
+
});
|
|
742
|
+
} catch {
|
|
743
|
+
// Non-fatal: identity may not exist for this agent
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Record session metrics (with optional token data from transcript)
|
|
747
|
+
if (agentSession) {
|
|
748
|
+
// NOTE: We intentionally do NOT auto-complete the run here for coordinator agents.
|
|
749
|
+
// The coordinator's Stop hook fires on every turn boundary, not just at true session exit,
|
|
750
|
+
// so auto-completing the run here would kill the session after the first turn.
|
|
751
|
+
// Run completion is handled by: `ap coordinator stop`, `ap run complete` (self-exit),
|
|
752
|
+
// or the watchdog daemon detecting a dead coordinator process.
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
const metricsDbPath = join(config.project.root, ".agentplate", "metrics.db");
|
|
756
|
+
const metricsStore = createMetricsStore(metricsDbPath);
|
|
757
|
+
const now = new Date().toISOString();
|
|
758
|
+
const durationMs = new Date(now).getTime() - new Date(agentSession.startedAt).getTime();
|
|
759
|
+
|
|
760
|
+
// Parse token usage from transcript if path provided
|
|
761
|
+
let inputTokens = 0;
|
|
762
|
+
let outputTokens = 0;
|
|
763
|
+
let cacheReadTokens = 0;
|
|
764
|
+
let cacheCreationTokens = 0;
|
|
765
|
+
let estimatedCostUsd: number | null = null;
|
|
766
|
+
let modelUsed: string | null = null;
|
|
767
|
+
|
|
768
|
+
if (transcriptPath) {
|
|
769
|
+
try {
|
|
770
|
+
const usage = await parseTranscriptUsage(transcriptPath);
|
|
771
|
+
inputTokens = usage.inputTokens;
|
|
772
|
+
outputTokens = usage.outputTokens;
|
|
773
|
+
cacheReadTokens = usage.cacheReadTokens;
|
|
774
|
+
cacheCreationTokens = usage.cacheCreationTokens;
|
|
775
|
+
modelUsed = usage.modelUsed;
|
|
776
|
+
estimatedCostUsd = estimateCost(usage);
|
|
777
|
+
} catch {
|
|
778
|
+
// Non-fatal: transcript parsing should not break metrics
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
metricsStore.recordSession({
|
|
783
|
+
agentName: opts.agent,
|
|
784
|
+
taskId: agentSession.taskId,
|
|
785
|
+
capability: agentSession.capability,
|
|
786
|
+
startedAt: agentSession.startedAt,
|
|
787
|
+
completedAt: now,
|
|
788
|
+
durationMs,
|
|
789
|
+
exitCode: null,
|
|
790
|
+
mergeResult: null,
|
|
791
|
+
parentAgent: agentSession.parentAgent,
|
|
792
|
+
inputTokens,
|
|
793
|
+
outputTokens,
|
|
794
|
+
cacheReadTokens,
|
|
795
|
+
cacheCreationTokens,
|
|
796
|
+
estimatedCostUsd,
|
|
797
|
+
modelUsed,
|
|
798
|
+
runId: agentSession.runId,
|
|
799
|
+
});
|
|
800
|
+
metricsStore.close();
|
|
801
|
+
} catch {
|
|
802
|
+
// Non-fatal: metrics recording should not break session-end handling
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Resolve outcome status from quality-gate results, threaded into
|
|
806
|
+
// every session-end loam record write so confirmation scoring
|
|
807
|
+
// reflects whether tests/lint/typecheck actually passed.
|
|
808
|
+
let outcomeStatus: "success" | "partial" | "failure" | undefined;
|
|
809
|
+
if (!isStopHookPersistentCapability(agentSession.capability)) {
|
|
810
|
+
try {
|
|
811
|
+
let baseRef = "main";
|
|
812
|
+
const baseBranchPath = join(config.project.root, ".agentplate", "session-branch.txt");
|
|
813
|
+
const baseFile = Bun.file(baseBranchPath);
|
|
814
|
+
if (await baseFile.exists()) {
|
|
815
|
+
const txt = (await baseFile.text()).trim();
|
|
816
|
+
if (txt.length > 0) baseRef = txt;
|
|
817
|
+
}
|
|
818
|
+
const hasWork = await hasWorkToVerify(agentSession.worktreePath, baseRef);
|
|
819
|
+
if (hasWork) {
|
|
820
|
+
const gates = config.project.qualityGates ?? [];
|
|
821
|
+
const outcome = await runQualityGates(gates, agentSession.worktreePath);
|
|
822
|
+
if (outcome) outcomeStatus = outcome.status;
|
|
823
|
+
}
|
|
824
|
+
} catch {
|
|
825
|
+
// Non-fatal: outcome status is optional
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Auto-record expertise via loam learn + record (post-session).
|
|
830
|
+
// Skip persistent agents whose Stop hook fires every turn.
|
|
831
|
+
if (!isStopHookPersistentCapability(agentSession.capability)) {
|
|
832
|
+
try {
|
|
833
|
+
const loamClient = createLoamClient(config.project.root);
|
|
834
|
+
const mailDbPath = join(config.project.root, ".agentplate", "mail.db");
|
|
835
|
+
await autoRecordExpertise({
|
|
836
|
+
loamClient,
|
|
837
|
+
agentName: opts.agent,
|
|
838
|
+
capability: agentSession.capability,
|
|
839
|
+
taskId,
|
|
840
|
+
mailDbPath,
|
|
841
|
+
parentAgent: agentSession.parentAgent,
|
|
842
|
+
projectRoot: config.project.root,
|
|
843
|
+
sessionStartedAt: agentSession.startedAt,
|
|
844
|
+
outcomeStatus,
|
|
845
|
+
});
|
|
846
|
+
} catch {
|
|
847
|
+
// Non-fatal: loam learn/record should not break session-end handling
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Append outcomes to applied loam records (outcome feedback loop).
|
|
852
|
+
// Reads applied-records.json written by sling.ts at spawn time.
|
|
853
|
+
if (!isStopHookPersistentCapability(agentSession.capability)) {
|
|
854
|
+
try {
|
|
855
|
+
const loamClient = createLoamClient(config.project.root);
|
|
856
|
+
await appendOutcomeToAppliedRecords({
|
|
857
|
+
loamClient,
|
|
858
|
+
agentName: opts.agent,
|
|
859
|
+
capability: agentSession.capability,
|
|
860
|
+
taskId,
|
|
861
|
+
projectRoot: config.project.root,
|
|
862
|
+
outcomeStatus,
|
|
863
|
+
});
|
|
864
|
+
} catch {
|
|
865
|
+
// Non-fatal
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Always write session-end event to EventStore (not just when --stdin is used)
|
|
871
|
+
try {
|
|
872
|
+
const eventsDbPath = join(config.project.root, ".agentplate", "events.db");
|
|
873
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
874
|
+
eventStore.insert({
|
|
875
|
+
runId: null,
|
|
876
|
+
agentName: opts.agent,
|
|
877
|
+
sessionId,
|
|
878
|
+
eventType: "session_end",
|
|
879
|
+
toolName: null,
|
|
880
|
+
toolArgs: null,
|
|
881
|
+
toolDurationMs: null,
|
|
882
|
+
level: "info",
|
|
883
|
+
data: transcriptPath ? JSON.stringify({ transcriptPath }) : null,
|
|
884
|
+
});
|
|
885
|
+
eventStore.close();
|
|
886
|
+
} catch {
|
|
887
|
+
// Non-fatal: EventStore write should not break session-end
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
// Clear the current session marker
|
|
891
|
+
{
|
|
892
|
+
const markerPath = join(logsBase, opts.agent, ".current-session");
|
|
893
|
+
try {
|
|
894
|
+
const { unlink } = await import("node:fs/promises");
|
|
895
|
+
await unlink(markerPath);
|
|
896
|
+
} catch {
|
|
897
|
+
// Marker may not exist
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
logger.close();
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
export function createLogCommand(): Command {
|
|
907
|
+
return new Command("log")
|
|
908
|
+
.description("Log a hook event")
|
|
909
|
+
.argument("<event>", "Event type: tool-start, tool-end, session-end")
|
|
910
|
+
.option("--agent <name>", "Agent name (required)")
|
|
911
|
+
.option("--tool-name <name>", "Tool name (for tool-start/tool-end events, legacy)")
|
|
912
|
+
.option("--transcript <path>", "Path to Claude Code transcript JSONL (for session-end, legacy)")
|
|
913
|
+
.option("--stdin", "Read hook payload JSON from stdin (preferred)")
|
|
914
|
+
.option("--json", "Output as JSON")
|
|
915
|
+
.action(
|
|
916
|
+
async (
|
|
917
|
+
event: string,
|
|
918
|
+
opts: {
|
|
919
|
+
agent?: string;
|
|
920
|
+
toolName?: string;
|
|
921
|
+
transcript?: string;
|
|
922
|
+
stdin?: boolean;
|
|
923
|
+
json?: boolean;
|
|
924
|
+
},
|
|
925
|
+
) => {
|
|
926
|
+
if (!opts.agent) {
|
|
927
|
+
throw new ValidationError("--agent is required for log command", { field: "agent" });
|
|
928
|
+
}
|
|
929
|
+
await runLog({
|
|
930
|
+
event,
|
|
931
|
+
agent: opts.agent,
|
|
932
|
+
toolName: opts.toolName ?? "unknown",
|
|
933
|
+
transcript: opts.transcript,
|
|
934
|
+
stdin: opts.stdin ?? false,
|
|
935
|
+
});
|
|
936
|
+
},
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Entry point for `ap log <event> --agent <name>`.
|
|
942
|
+
*/
|
|
943
|
+
export async function logCommand(args: string[]): Promise<void> {
|
|
944
|
+
const cmd = createLogCommand();
|
|
945
|
+
cmd.exitOverride();
|
|
946
|
+
|
|
947
|
+
try {
|
|
948
|
+
await cmd.parseAsync(args, { from: "user" });
|
|
949
|
+
} catch (err: unknown) {
|
|
950
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
951
|
+
const code = (err as { code: string }).code;
|
|
952
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
throw err;
|
|
957
|
+
}
|
|
958
|
+
}
|