@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,1026 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, mkdtemp } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { AgentError } from "../errors.ts";
|
|
6
|
+
import { cleanupTempDir } from "../test-helpers.ts";
|
|
7
|
+
import type { AgentManifest, AgentplateConfig } from "../types.ts";
|
|
8
|
+
import {
|
|
9
|
+
createManifestLoader,
|
|
10
|
+
expandAliasFromEnv,
|
|
11
|
+
resolveModel,
|
|
12
|
+
resolveProviderEnv,
|
|
13
|
+
} from "./manifest.ts";
|
|
14
|
+
|
|
15
|
+
const VALID_MANIFEST = {
|
|
16
|
+
version: "1.0",
|
|
17
|
+
agents: {
|
|
18
|
+
scout: {
|
|
19
|
+
file: "scout.md",
|
|
20
|
+
model: "sonnet",
|
|
21
|
+
tools: ["Read", "Grep", "Glob"],
|
|
22
|
+
capabilities: ["explore", "review"],
|
|
23
|
+
canSpawn: false,
|
|
24
|
+
constraints: ["read-only"],
|
|
25
|
+
},
|
|
26
|
+
builder: {
|
|
27
|
+
file: "builder.md",
|
|
28
|
+
model: "opus",
|
|
29
|
+
tools: ["Read", "Write", "Edit", "Bash"],
|
|
30
|
+
capabilities: ["implement", "refactor"],
|
|
31
|
+
canSpawn: false,
|
|
32
|
+
constraints: [],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe("createManifestLoader", () => {
|
|
38
|
+
let tempDir: string;
|
|
39
|
+
let manifestPath: string;
|
|
40
|
+
let agentBaseDir: string;
|
|
41
|
+
|
|
42
|
+
beforeEach(async () => {
|
|
43
|
+
tempDir = await mkdtemp(join(tmpdir(), "agentplate-manifest-test-"));
|
|
44
|
+
manifestPath = join(tempDir, "agent-manifest.json");
|
|
45
|
+
agentBaseDir = join(tempDir, "agents");
|
|
46
|
+
await mkdir(agentBaseDir, { recursive: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(async () => {
|
|
50
|
+
await cleanupTempDir(tempDir);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** Write the manifest JSON and create matching .md files. */
|
|
54
|
+
async function writeManifest(
|
|
55
|
+
data: Record<string, unknown>,
|
|
56
|
+
options?: { skipMdFiles?: boolean; mdFilesToSkip?: string[] },
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
await Bun.write(manifestPath, JSON.stringify(data));
|
|
59
|
+
if (options?.skipMdFiles) return;
|
|
60
|
+
|
|
61
|
+
const agents = data.agents;
|
|
62
|
+
if (agents && typeof agents === "object" && !Array.isArray(agents)) {
|
|
63
|
+
for (const [, def] of Object.entries(agents as Record<string, Record<string, unknown>>)) {
|
|
64
|
+
const file = def.file;
|
|
65
|
+
if (
|
|
66
|
+
typeof file === "string" &&
|
|
67
|
+
file.length > 0 &&
|
|
68
|
+
!options?.mdFilesToSkip?.includes(file)
|
|
69
|
+
) {
|
|
70
|
+
await Bun.write(join(agentBaseDir, file), `# ${file}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("load", () => {
|
|
77
|
+
test("reads manifest, validates structure, verifies .md files, builds capability index", async () => {
|
|
78
|
+
await writeManifest(VALID_MANIFEST);
|
|
79
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
80
|
+
|
|
81
|
+
const manifest = await loader.load();
|
|
82
|
+
|
|
83
|
+
expect(manifest.version).toBe("1.0");
|
|
84
|
+
expect(Object.keys(manifest.agents)).toHaveLength(2);
|
|
85
|
+
expect(manifest.agents.scout).toBeDefined();
|
|
86
|
+
expect(manifest.agents.builder).toBeDefined();
|
|
87
|
+
expect(manifest.capabilityIndex).toBeDefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("builds capability index mapping capabilities to agent names", async () => {
|
|
91
|
+
await writeManifest(VALID_MANIFEST);
|
|
92
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
93
|
+
|
|
94
|
+
const manifest = await loader.load();
|
|
95
|
+
|
|
96
|
+
expect(manifest.capabilityIndex.explore).toEqual(["scout"]);
|
|
97
|
+
expect(manifest.capabilityIndex.review).toEqual(["scout"]);
|
|
98
|
+
expect(manifest.capabilityIndex.implement).toEqual(["builder"]);
|
|
99
|
+
expect(manifest.capabilityIndex.refactor).toEqual(["builder"]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("capability index includes multiple agents for shared capabilities", async () => {
|
|
103
|
+
const data = {
|
|
104
|
+
version: "1.0",
|
|
105
|
+
agents: {
|
|
106
|
+
scout: {
|
|
107
|
+
file: "scout.md",
|
|
108
|
+
model: "sonnet",
|
|
109
|
+
tools: ["Read"],
|
|
110
|
+
capabilities: ["review"],
|
|
111
|
+
canSpawn: false,
|
|
112
|
+
constraints: [],
|
|
113
|
+
},
|
|
114
|
+
reviewer: {
|
|
115
|
+
file: "reviewer.md",
|
|
116
|
+
model: "sonnet",
|
|
117
|
+
tools: ["Read"],
|
|
118
|
+
capabilities: ["review"],
|
|
119
|
+
canSpawn: false,
|
|
120
|
+
constraints: [],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
await writeManifest(data);
|
|
125
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
126
|
+
|
|
127
|
+
const manifest = await loader.load();
|
|
128
|
+
|
|
129
|
+
expect(manifest.capabilityIndex.review).toEqual(["scout", "reviewer"]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("getAgent", () => {
|
|
134
|
+
test("returns undefined before load is called", async () => {
|
|
135
|
+
await writeManifest(VALID_MANIFEST);
|
|
136
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
137
|
+
|
|
138
|
+
expect(loader.getAgent("scout")).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("returns agent definition after load", async () => {
|
|
142
|
+
await writeManifest(VALID_MANIFEST);
|
|
143
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
144
|
+
await loader.load();
|
|
145
|
+
|
|
146
|
+
const scout = loader.getAgent("scout");
|
|
147
|
+
expect(scout).toBeDefined();
|
|
148
|
+
expect(scout?.model).toBe("sonnet");
|
|
149
|
+
expect(scout?.tools).toEqual(["Read", "Grep", "Glob"]);
|
|
150
|
+
expect(scout?.capabilities).toEqual(["explore", "review"]);
|
|
151
|
+
expect(scout?.canSpawn).toBe(false);
|
|
152
|
+
expect(scout?.constraints).toEqual(["read-only"]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("returns undefined for non-existent agent after load", async () => {
|
|
156
|
+
await writeManifest(VALID_MANIFEST);
|
|
157
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
158
|
+
await loader.load();
|
|
159
|
+
|
|
160
|
+
expect(loader.getAgent("nonexistent")).toBeUndefined();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("findByCapability", () => {
|
|
165
|
+
test("returns empty array before load is called", async () => {
|
|
166
|
+
await writeManifest(VALID_MANIFEST);
|
|
167
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
168
|
+
|
|
169
|
+
expect(loader.findByCapability("explore")).toEqual([]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("returns matching agents after load", async () => {
|
|
173
|
+
await writeManifest(VALID_MANIFEST);
|
|
174
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
175
|
+
await loader.load();
|
|
176
|
+
|
|
177
|
+
const explorers = loader.findByCapability("explore");
|
|
178
|
+
expect(explorers).toHaveLength(1);
|
|
179
|
+
expect(explorers[0]?.file).toBe("scout.md");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("returns empty array for non-existent capability", async () => {
|
|
183
|
+
await writeManifest(VALID_MANIFEST);
|
|
184
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
185
|
+
await loader.load();
|
|
186
|
+
|
|
187
|
+
expect(loader.findByCapability("nonexistent")).toEqual([]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("returns multiple agents sharing a capability", async () => {
|
|
191
|
+
const data = {
|
|
192
|
+
version: "1.0",
|
|
193
|
+
agents: {
|
|
194
|
+
scout: {
|
|
195
|
+
file: "scout.md",
|
|
196
|
+
model: "sonnet",
|
|
197
|
+
tools: ["Read"],
|
|
198
|
+
capabilities: ["review"],
|
|
199
|
+
canSpawn: false,
|
|
200
|
+
constraints: [],
|
|
201
|
+
},
|
|
202
|
+
reviewer: {
|
|
203
|
+
file: "reviewer.md",
|
|
204
|
+
model: "sonnet",
|
|
205
|
+
tools: ["Read"],
|
|
206
|
+
capabilities: ["review"],
|
|
207
|
+
canSpawn: false,
|
|
208
|
+
constraints: [],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
await writeManifest(data);
|
|
213
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
214
|
+
await loader.load();
|
|
215
|
+
|
|
216
|
+
const reviewers = loader.findByCapability("review");
|
|
217
|
+
expect(reviewers).toHaveLength(2);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("validate", () => {
|
|
222
|
+
test("returns error message if not loaded", () => {
|
|
223
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
224
|
+
|
|
225
|
+
const errors = loader.validate();
|
|
226
|
+
expect(errors).toHaveLength(1);
|
|
227
|
+
expect(errors[0]).toContain("not loaded");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("returns empty array for valid manifest", async () => {
|
|
231
|
+
await writeManifest(VALID_MANIFEST);
|
|
232
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
233
|
+
await loader.load();
|
|
234
|
+
|
|
235
|
+
const errors = loader.validate();
|
|
236
|
+
expect(errors).toEqual([]);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("error handling", () => {
|
|
241
|
+
test("throws AgentError for missing manifest file", async () => {
|
|
242
|
+
const loader = createManifestLoader(join(tempDir, "nonexistent.json"), agentBaseDir);
|
|
243
|
+
|
|
244
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
245
|
+
await expect(loader.load()).rejects.toThrow("not found");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("throws AgentError for invalid JSON", async () => {
|
|
249
|
+
await Bun.write(manifestPath, "not valid json {{{");
|
|
250
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
251
|
+
|
|
252
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
253
|
+
await expect(loader.load()).rejects.toThrow("Failed to parse");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("throws AgentError for missing version field", async () => {
|
|
257
|
+
await Bun.write(manifestPath, JSON.stringify({ agents: {} }));
|
|
258
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
259
|
+
|
|
260
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
261
|
+
await expect(loader.load()).rejects.toThrow("version");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("throws AgentError for empty version string", async () => {
|
|
265
|
+
await Bun.write(manifestPath, JSON.stringify({ version: "", agents: {} }));
|
|
266
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
267
|
+
|
|
268
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
269
|
+
await expect(loader.load()).rejects.toThrow("version");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("throws AgentError when agents field is missing", async () => {
|
|
273
|
+
await Bun.write(manifestPath, JSON.stringify({ version: "1.0" }));
|
|
274
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
275
|
+
|
|
276
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
277
|
+
await expect(loader.load()).rejects.toThrow("agents");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("throws AgentError when agents field is an array", async () => {
|
|
281
|
+
await Bun.write(manifestPath, JSON.stringify({ version: "1.0", agents: [] }));
|
|
282
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
283
|
+
|
|
284
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
285
|
+
await expect(loader.load()).rejects.toThrow("agents");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("throws AgentError for empty model string", async () => {
|
|
289
|
+
const data = {
|
|
290
|
+
version: "1.0",
|
|
291
|
+
agents: {
|
|
292
|
+
bad: {
|
|
293
|
+
file: "bad.md",
|
|
294
|
+
model: "",
|
|
295
|
+
tools: ["Read"],
|
|
296
|
+
capabilities: ["test"],
|
|
297
|
+
canSpawn: false,
|
|
298
|
+
constraints: [],
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
await writeManifest(data);
|
|
303
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
304
|
+
|
|
305
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
306
|
+
await expect(loader.load()).rejects.toThrow("model");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("throws AgentError for missing tools array", async () => {
|
|
310
|
+
const data = {
|
|
311
|
+
version: "1.0",
|
|
312
|
+
agents: {
|
|
313
|
+
bad: {
|
|
314
|
+
file: "bad.md",
|
|
315
|
+
model: "sonnet",
|
|
316
|
+
capabilities: ["test"],
|
|
317
|
+
canSpawn: false,
|
|
318
|
+
constraints: [],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
await writeManifest(data);
|
|
323
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
324
|
+
|
|
325
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
326
|
+
await expect(loader.load()).rejects.toThrow("tools");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("throws AgentError for missing capabilities array", async () => {
|
|
330
|
+
const data = {
|
|
331
|
+
version: "1.0",
|
|
332
|
+
agents: {
|
|
333
|
+
bad: {
|
|
334
|
+
file: "bad.md",
|
|
335
|
+
model: "sonnet",
|
|
336
|
+
tools: ["Read"],
|
|
337
|
+
canSpawn: false,
|
|
338
|
+
constraints: [],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
await writeManifest(data);
|
|
343
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
344
|
+
|
|
345
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
346
|
+
await expect(loader.load()).rejects.toThrow("capabilities");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("throws AgentError when canSpawn is not boolean", async () => {
|
|
350
|
+
const data = {
|
|
351
|
+
version: "1.0",
|
|
352
|
+
agents: {
|
|
353
|
+
bad: {
|
|
354
|
+
file: "bad.md",
|
|
355
|
+
model: "sonnet",
|
|
356
|
+
tools: ["Read"],
|
|
357
|
+
capabilities: ["test"],
|
|
358
|
+
canSpawn: "yes",
|
|
359
|
+
constraints: [],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
await writeManifest(data);
|
|
364
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
365
|
+
|
|
366
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
367
|
+
await expect(loader.load()).rejects.toThrow("canSpawn");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("throws AgentError when constraints is not an array", async () => {
|
|
371
|
+
const data = {
|
|
372
|
+
version: "1.0",
|
|
373
|
+
agents: {
|
|
374
|
+
bad: {
|
|
375
|
+
file: "bad.md",
|
|
376
|
+
model: "sonnet",
|
|
377
|
+
tools: ["Read"],
|
|
378
|
+
capabilities: ["test"],
|
|
379
|
+
canSpawn: false,
|
|
380
|
+
constraints: "read-only",
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
await writeManifest(data);
|
|
385
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
386
|
+
|
|
387
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
388
|
+
await expect(loader.load()).rejects.toThrow("constraints");
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test("throws AgentError when agent definition is not an object", async () => {
|
|
392
|
+
const data = {
|
|
393
|
+
version: "1.0",
|
|
394
|
+
agents: {
|
|
395
|
+
bad: "not an object",
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
await Bun.write(manifestPath, JSON.stringify(data));
|
|
399
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
400
|
+
|
|
401
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
402
|
+
await expect(loader.load()).rejects.toThrow("definition must be an object");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("throws AgentError when referenced .md file does not exist", async () => {
|
|
406
|
+
await writeManifest(VALID_MANIFEST, { mdFilesToSkip: ["scout.md"] });
|
|
407
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
408
|
+
|
|
409
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
410
|
+
await expect(loader.load()).rejects.toThrow("does not exist");
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("throws AgentError when file field is empty string", async () => {
|
|
414
|
+
const data = {
|
|
415
|
+
version: "1.0",
|
|
416
|
+
agents: {
|
|
417
|
+
bad: {
|
|
418
|
+
file: "",
|
|
419
|
+
model: "sonnet",
|
|
420
|
+
tools: ["Read"],
|
|
421
|
+
capabilities: ["test"],
|
|
422
|
+
canSpawn: false,
|
|
423
|
+
constraints: [],
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
await writeManifest(data);
|
|
428
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
429
|
+
|
|
430
|
+
await expect(loader.load()).rejects.toThrow(AgentError);
|
|
431
|
+
await expect(loader.load()).rejects.toThrow("file");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("aggregates multiple validation errors", async () => {
|
|
435
|
+
const data = {
|
|
436
|
+
version: "1.0",
|
|
437
|
+
agents: {
|
|
438
|
+
bad: {
|
|
439
|
+
file: "",
|
|
440
|
+
model: "",
|
|
441
|
+
tools: "not-array",
|
|
442
|
+
capabilities: "not-array",
|
|
443
|
+
canSpawn: "not-bool",
|
|
444
|
+
constraints: "not-array",
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
await Bun.write(manifestPath, JSON.stringify(data));
|
|
449
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
await loader.load();
|
|
453
|
+
expect(true).toBe(false); // Should not reach here
|
|
454
|
+
} catch (err) {
|
|
455
|
+
expect(err).toBeInstanceOf(AgentError);
|
|
456
|
+
const message = (err as AgentError).message;
|
|
457
|
+
expect(message).toContain("file");
|
|
458
|
+
expect(message).toContain("model");
|
|
459
|
+
expect(message).toContain("tools");
|
|
460
|
+
expect(message).toContain("capabilities");
|
|
461
|
+
expect(message).toContain("canSpawn");
|
|
462
|
+
expect(message).toContain("constraints");
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe("agent with all valid models", () => {
|
|
468
|
+
for (const model of ["sonnet", "opus", "haiku"] as const) {
|
|
469
|
+
test(`accepts model "${model}"`, async () => {
|
|
470
|
+
const data = {
|
|
471
|
+
version: "1.0",
|
|
472
|
+
agents: {
|
|
473
|
+
agent: {
|
|
474
|
+
file: "agent.md",
|
|
475
|
+
model,
|
|
476
|
+
tools: ["Read"],
|
|
477
|
+
capabilities: ["test"],
|
|
478
|
+
canSpawn: false,
|
|
479
|
+
constraints: [],
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
await writeManifest(data);
|
|
484
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
485
|
+
|
|
486
|
+
const manifest = await loader.load();
|
|
487
|
+
expect(manifest.agents.agent).toBeDefined();
|
|
488
|
+
expect(manifest.agents.agent?.model).toBe(model);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
describe("resolveModel", () => {
|
|
495
|
+
const baseManifest: AgentManifest = {
|
|
496
|
+
version: "1.0",
|
|
497
|
+
agents: {
|
|
498
|
+
coordinator: {
|
|
499
|
+
file: "coordinator.md",
|
|
500
|
+
model: "opus",
|
|
501
|
+
tools: ["Read", "Bash"],
|
|
502
|
+
capabilities: ["coordinate"],
|
|
503
|
+
canSpawn: true,
|
|
504
|
+
constraints: [],
|
|
505
|
+
},
|
|
506
|
+
monitor: {
|
|
507
|
+
file: "monitor.md",
|
|
508
|
+
model: "sonnet",
|
|
509
|
+
tools: ["Read", "Bash"],
|
|
510
|
+
capabilities: ["monitor"],
|
|
511
|
+
canSpawn: false,
|
|
512
|
+
constraints: [],
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
capabilityIndex: { coordinate: ["coordinator"], monitor: ["monitor"] },
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
function makeConfig(
|
|
519
|
+
models: AgentplateConfig["models"] = {},
|
|
520
|
+
providers: AgentplateConfig["providers"] = { anthropic: { type: "native" } },
|
|
521
|
+
): AgentplateConfig {
|
|
522
|
+
return {
|
|
523
|
+
project: { name: "test", root: "/tmp/test", canonicalBranch: "main" },
|
|
524
|
+
agents: {
|
|
525
|
+
manifestPath: ".agentplate/agent-manifest.json",
|
|
526
|
+
baseDir: ".agentplate/agent-defs",
|
|
527
|
+
maxConcurrent: 5,
|
|
528
|
+
staggerDelayMs: 1000,
|
|
529
|
+
maxDepth: 2,
|
|
530
|
+
maxSessionsPerRun: 0,
|
|
531
|
+
maxAgentsPerLead: 5,
|
|
532
|
+
},
|
|
533
|
+
worktrees: { baseDir: ".agentplate/worktrees" },
|
|
534
|
+
taskTracker: { backend: "auto", enabled: false },
|
|
535
|
+
loam: { enabled: false, domains: [], primeFormat: "markdown" },
|
|
536
|
+
merge: { aiResolveEnabled: false, reimagineEnabled: false },
|
|
537
|
+
providers,
|
|
538
|
+
watchdog: {
|
|
539
|
+
tier0Enabled: false,
|
|
540
|
+
tier0IntervalMs: 30000,
|
|
541
|
+
tier1Enabled: false,
|
|
542
|
+
tier2Enabled: false,
|
|
543
|
+
staleThresholdMs: 300000,
|
|
544
|
+
zombieThresholdMs: 600000,
|
|
545
|
+
nudgeIntervalMs: 60000,
|
|
546
|
+
},
|
|
547
|
+
models,
|
|
548
|
+
logging: { verbose: false, redactSecrets: true },
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
test("returns manifest model when no config override", () => {
|
|
553
|
+
const config = makeConfig();
|
|
554
|
+
expect(resolveModel(config, baseManifest, "coordinator", "haiku")).toEqual({
|
|
555
|
+
model: "opus",
|
|
556
|
+
isExplicitOverride: false,
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("config override takes precedence over manifest", () => {
|
|
561
|
+
const config = makeConfig({ coordinator: "sonnet" });
|
|
562
|
+
expect(resolveModel(config, baseManifest, "coordinator", "haiku")).toEqual({
|
|
563
|
+
model: "sonnet",
|
|
564
|
+
isExplicitOverride: true,
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test("falls back to default when role is not in manifest or config", () => {
|
|
569
|
+
const config = makeConfig();
|
|
570
|
+
expect(resolveModel(config, baseManifest, "unknown-role", "haiku")).toEqual({
|
|
571
|
+
model: "haiku",
|
|
572
|
+
isExplicitOverride: false,
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test("config override works for roles not in manifest", () => {
|
|
577
|
+
const config = makeConfig({ supervisor: "opus" });
|
|
578
|
+
expect(resolveModel(config, baseManifest, "supervisor", "sonnet")).toEqual({
|
|
579
|
+
model: "opus",
|
|
580
|
+
isExplicitOverride: true,
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("returns gateway env for provider-prefixed model", () => {
|
|
585
|
+
const config = makeConfig(
|
|
586
|
+
{ coordinator: "openrouter/openai/gpt-5.3" },
|
|
587
|
+
{
|
|
588
|
+
openrouter: {
|
|
589
|
+
type: "gateway",
|
|
590
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
591
|
+
authTokenEnv: "OPENROUTER_API_KEY",
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
);
|
|
595
|
+
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
596
|
+
expect(result).toEqual({
|
|
597
|
+
model: "sonnet",
|
|
598
|
+
env: {
|
|
599
|
+
ANTHROPIC_BASE_URL: "https://openrouter.ai/api/v1",
|
|
600
|
+
ANTHROPIC_API_KEY: "",
|
|
601
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "openai/gpt-5.3",
|
|
602
|
+
},
|
|
603
|
+
isExplicitOverride: true,
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test("includes auth token in env when env var is set", () => {
|
|
608
|
+
const config = makeConfig(
|
|
609
|
+
{ coordinator: "openrouter/openai/gpt-5.3" },
|
|
610
|
+
{
|
|
611
|
+
openrouter: {
|
|
612
|
+
type: "gateway",
|
|
613
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
614
|
+
authTokenEnv: "OPENROUTER_API_KEY",
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
);
|
|
618
|
+
const savedEnv = process.env.OPENROUTER_API_KEY;
|
|
619
|
+
process.env.OPENROUTER_API_KEY = "test-token-123";
|
|
620
|
+
try {
|
|
621
|
+
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
622
|
+
expect(result).toEqual({
|
|
623
|
+
model: "sonnet",
|
|
624
|
+
env: {
|
|
625
|
+
ANTHROPIC_BASE_URL: "https://openrouter.ai/api/v1",
|
|
626
|
+
ANTHROPIC_API_KEY: "",
|
|
627
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "openai/gpt-5.3",
|
|
628
|
+
ANTHROPIC_AUTH_TOKEN: "test-token-123",
|
|
629
|
+
},
|
|
630
|
+
isExplicitOverride: true,
|
|
631
|
+
});
|
|
632
|
+
} finally {
|
|
633
|
+
if (savedEnv === undefined) {
|
|
634
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
635
|
+
} else {
|
|
636
|
+
process.env.OPENROUTER_API_KEY = savedEnv;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
test("unknown provider falls through to model as-is", () => {
|
|
642
|
+
const config = makeConfig({ coordinator: "unknown-provider/some-model" });
|
|
643
|
+
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
644
|
+
expect(result).toEqual({ model: "unknown-provider/some-model", isExplicitOverride: true });
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
test("native provider returns model string without env", () => {
|
|
648
|
+
const config = makeConfig(
|
|
649
|
+
{ coordinator: "native-gw/claude-3-5-sonnet" },
|
|
650
|
+
{ "native-gw": { type: "native" } },
|
|
651
|
+
);
|
|
652
|
+
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
653
|
+
expect(result).toEqual({ model: "native-gw/claude-3-5-sonnet", isExplicitOverride: true });
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test("handles deeply nested model ID (slashes in model name)", () => {
|
|
657
|
+
const config = makeConfig(
|
|
658
|
+
{ coordinator: "openrouter/openai/gpt-5.3" },
|
|
659
|
+
{
|
|
660
|
+
openrouter: {
|
|
661
|
+
type: "gateway",
|
|
662
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
663
|
+
authTokenEnv: "OPENROUTER_API_KEY",
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
);
|
|
667
|
+
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
668
|
+
// First "/" splits provider "openrouter" from model ID "openai/gpt-5.3"
|
|
669
|
+
expect(result.model).toBe("sonnet");
|
|
670
|
+
expect(result.env?.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe("openai/gpt-5.3");
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("handles model ID with multiple slashes after provider", () => {
|
|
674
|
+
const config = makeConfig(
|
|
675
|
+
{ coordinator: "mygateway/org/model/version" },
|
|
676
|
+
{
|
|
677
|
+
mygateway: {
|
|
678
|
+
type: "gateway",
|
|
679
|
+
baseUrl: "https://mygateway.example.com",
|
|
680
|
+
authTokenEnv: "MYGATEWAY_KEY",
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
);
|
|
684
|
+
const result = resolveModel(config, baseManifest, "coordinator", "opus");
|
|
685
|
+
// Provider is "mygateway", model ID is everything after the first "/"
|
|
686
|
+
expect(result.model).toBe("sonnet");
|
|
687
|
+
expect(result.env?.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe("org/model/version");
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test("resolveModel sets isExplicitOverride true when config.models has override", () => {
|
|
691
|
+
const config = makeConfig({ builder: "opus" });
|
|
692
|
+
const result = resolveModel(config, baseManifest, "builder", "haiku");
|
|
693
|
+
expect(result.isExplicitOverride).toBe(true);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
test("resolveModel sets isExplicitOverride false when using manifest default", () => {
|
|
697
|
+
const config = makeConfig();
|
|
698
|
+
const result = resolveModel(config, baseManifest, "coordinator", "haiku");
|
|
699
|
+
expect(result.isExplicitOverride).toBe(false);
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
describe("expandAliasFromEnv", () => {
|
|
704
|
+
test("returns expanded model ID when env var is set", () => {
|
|
705
|
+
expect(
|
|
706
|
+
expandAliasFromEnv("haiku", {
|
|
707
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "us.anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
708
|
+
}),
|
|
709
|
+
).toBe("us.anthropic.claude-3-5-haiku-20241022-v1:0");
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
test("returns alias unchanged when env var is unset", () => {
|
|
713
|
+
expect(expandAliasFromEnv("haiku", {})).toBe("haiku");
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
test("expands all three aliases via their env vars", () => {
|
|
717
|
+
const env = {
|
|
718
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "bedrock-haiku-id",
|
|
719
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "bedrock-sonnet-id",
|
|
720
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "bedrock-opus-id",
|
|
721
|
+
};
|
|
722
|
+
expect(expandAliasFromEnv("haiku", env)).toBe("bedrock-haiku-id");
|
|
723
|
+
expect(expandAliasFromEnv("sonnet", env)).toBe("bedrock-sonnet-id");
|
|
724
|
+
expect(expandAliasFromEnv("opus", env)).toBe("bedrock-opus-id");
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test("trims whitespace from env var value", () => {
|
|
728
|
+
expect(
|
|
729
|
+
expandAliasFromEnv("sonnet", {
|
|
730
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: " bedrock-sonnet-id ",
|
|
731
|
+
}),
|
|
732
|
+
).toBe("bedrock-sonnet-id");
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
test("returns alias when env var is empty string", () => {
|
|
736
|
+
expect(expandAliasFromEnv("sonnet", { ANTHROPIC_DEFAULT_SONNET_MODEL: "" })).toBe("sonnet");
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
test("returns alias when env var is whitespace only", () => {
|
|
740
|
+
expect(expandAliasFromEnv("sonnet", { ANTHROPIC_DEFAULT_SONNET_MODEL: " " })).toBe("sonnet");
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
test("returns unknown alias unchanged", () => {
|
|
744
|
+
expect(expandAliasFromEnv("gpt-4", {})).toBe("gpt-4");
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
describe("resolveModel env var expansion", () => {
|
|
749
|
+
const baseManifest: AgentManifest = {
|
|
750
|
+
version: "1.0",
|
|
751
|
+
agents: {
|
|
752
|
+
scout: {
|
|
753
|
+
file: "scout.md",
|
|
754
|
+
model: "haiku",
|
|
755
|
+
tools: ["Read"],
|
|
756
|
+
capabilities: ["explore"],
|
|
757
|
+
canSpawn: false,
|
|
758
|
+
constraints: [],
|
|
759
|
+
},
|
|
760
|
+
builder: {
|
|
761
|
+
file: "builder.md",
|
|
762
|
+
model: "sonnet",
|
|
763
|
+
tools: ["Read", "Write"],
|
|
764
|
+
capabilities: ["implement"],
|
|
765
|
+
canSpawn: false,
|
|
766
|
+
constraints: [],
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
capabilityIndex: { explore: ["scout"], implement: ["builder"] },
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
function makeConfig(models: AgentplateConfig["models"] = {}): AgentplateConfig {
|
|
773
|
+
return {
|
|
774
|
+
project: { name: "test", root: "/tmp/test", canonicalBranch: "main" },
|
|
775
|
+
agents: {
|
|
776
|
+
manifestPath: ".agentplate/agent-manifest.json",
|
|
777
|
+
baseDir: ".agentplate/agent-defs",
|
|
778
|
+
maxConcurrent: 5,
|
|
779
|
+
staggerDelayMs: 1000,
|
|
780
|
+
maxDepth: 2,
|
|
781
|
+
maxSessionsPerRun: 0,
|
|
782
|
+
maxAgentsPerLead: 5,
|
|
783
|
+
},
|
|
784
|
+
worktrees: { baseDir: ".agentplate/worktrees" },
|
|
785
|
+
taskTracker: { backend: "auto", enabled: false },
|
|
786
|
+
loam: { enabled: false, domains: [], primeFormat: "markdown" },
|
|
787
|
+
merge: { aiResolveEnabled: false, reimagineEnabled: false },
|
|
788
|
+
providers: { anthropic: { type: "native" } },
|
|
789
|
+
watchdog: {
|
|
790
|
+
tier0Enabled: false,
|
|
791
|
+
tier0IntervalMs: 30000,
|
|
792
|
+
tier1Enabled: false,
|
|
793
|
+
tier2Enabled: false,
|
|
794
|
+
staleThresholdMs: 300000,
|
|
795
|
+
zombieThresholdMs: 600000,
|
|
796
|
+
nudgeIntervalMs: 60000,
|
|
797
|
+
},
|
|
798
|
+
models,
|
|
799
|
+
logging: { verbose: false, redactSecrets: true },
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
test("expands alias when env var is set", () => {
|
|
804
|
+
const saved = process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
805
|
+
process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = "us.anthropic.claude-3-5-haiku-20241022-v1:0";
|
|
806
|
+
try {
|
|
807
|
+
const result = resolveModel(makeConfig(), baseManifest, "scout", "sonnet");
|
|
808
|
+
expect(result).toEqual({
|
|
809
|
+
model: "us.anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
810
|
+
isExplicitOverride: false,
|
|
811
|
+
});
|
|
812
|
+
} finally {
|
|
813
|
+
if (saved === undefined) {
|
|
814
|
+
delete process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
815
|
+
} else {
|
|
816
|
+
process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = saved;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test("passes alias through when env var is unset", () => {
|
|
822
|
+
const saved = process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
823
|
+
delete process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
824
|
+
try {
|
|
825
|
+
const result = resolveModel(makeConfig(), baseManifest, "scout", "sonnet");
|
|
826
|
+
expect(result).toEqual({ model: "haiku", isExplicitOverride: false });
|
|
827
|
+
} finally {
|
|
828
|
+
if (saved !== undefined) {
|
|
829
|
+
process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = saved;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
test("config override to full model ID is not affected by env vars", () => {
|
|
835
|
+
const saved = process.env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
836
|
+
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = "bedrock-sonnet";
|
|
837
|
+
try {
|
|
838
|
+
// Config overrides to a direct model string (not an alias)
|
|
839
|
+
const config = makeConfig({ builder: "claude-3-5-sonnet-20241022" });
|
|
840
|
+
const result = resolveModel(config, baseManifest, "builder", "haiku");
|
|
841
|
+
expect(result).toEqual({ model: "claude-3-5-sonnet-20241022", isExplicitOverride: true });
|
|
842
|
+
} finally {
|
|
843
|
+
if (saved === undefined) {
|
|
844
|
+
delete process.env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
845
|
+
} else {
|
|
846
|
+
process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = saved;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test("config override to alias also expands via env var", () => {
|
|
852
|
+
const saved = process.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
853
|
+
process.env.ANTHROPIC_DEFAULT_OPUS_MODEL = "bedrock-opus-id";
|
|
854
|
+
try {
|
|
855
|
+
const config = makeConfig({ scout: "opus" });
|
|
856
|
+
const result = resolveModel(config, baseManifest, "scout", "haiku");
|
|
857
|
+
expect(result).toEqual({ model: "bedrock-opus-id", isExplicitOverride: true });
|
|
858
|
+
} finally {
|
|
859
|
+
if (saved === undefined) {
|
|
860
|
+
delete process.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
861
|
+
} else {
|
|
862
|
+
process.env.ANTHROPIC_DEFAULT_OPUS_MODEL = saved;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
describe("resolveProviderEnv", () => {
|
|
869
|
+
test("returns null for unknown provider", () => {
|
|
870
|
+
const result = resolveProviderEnv("unknown", "some/model", {});
|
|
871
|
+
expect(result).toBeNull();
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
test("returns null for native provider type", () => {
|
|
875
|
+
const result = resolveProviderEnv("anthropic", "some/model", {
|
|
876
|
+
anthropic: { type: "native" },
|
|
877
|
+
});
|
|
878
|
+
expect(result).toBeNull();
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
test("returns null for gateway without baseUrl", () => {
|
|
882
|
+
const result = resolveProviderEnv("gw", "some/model", {
|
|
883
|
+
gw: { type: "gateway" },
|
|
884
|
+
});
|
|
885
|
+
expect(result).toBeNull();
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
test("returns env dict for gateway with baseUrl", () => {
|
|
889
|
+
const result = resolveProviderEnv("openrouter", "openai/gpt-5.3", {
|
|
890
|
+
openrouter: { type: "gateway", baseUrl: "https://openrouter.ai/api/v1" },
|
|
891
|
+
});
|
|
892
|
+
expect(result).toEqual({
|
|
893
|
+
ANTHROPIC_BASE_URL: "https://openrouter.ai/api/v1",
|
|
894
|
+
ANTHROPIC_API_KEY: "",
|
|
895
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "openai/gpt-5.3",
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
test("includes auth token when env var is present", () => {
|
|
900
|
+
const result = resolveProviderEnv(
|
|
901
|
+
"openrouter",
|
|
902
|
+
"openai/gpt-5.3",
|
|
903
|
+
{
|
|
904
|
+
openrouter: {
|
|
905
|
+
type: "gateway",
|
|
906
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
907
|
+
authTokenEnv: "MY_TOKEN",
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
{ MY_TOKEN: "secret-token" },
|
|
911
|
+
);
|
|
912
|
+
expect(result).toEqual({
|
|
913
|
+
ANTHROPIC_BASE_URL: "https://openrouter.ai/api/v1",
|
|
914
|
+
ANTHROPIC_API_KEY: "",
|
|
915
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "openai/gpt-5.3",
|
|
916
|
+
ANTHROPIC_AUTH_TOKEN: "secret-token",
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
test("omits auth token when env var is not set", () => {
|
|
921
|
+
const result = resolveProviderEnv(
|
|
922
|
+
"openrouter",
|
|
923
|
+
"openai/gpt-5.3",
|
|
924
|
+
{
|
|
925
|
+
openrouter: {
|
|
926
|
+
type: "gateway",
|
|
927
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
928
|
+
authTokenEnv: "MISSING_TOKEN",
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
{},
|
|
932
|
+
);
|
|
933
|
+
expect(result).not.toHaveProperty("ANTHROPIC_AUTH_TOKEN");
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
test("env always sets ANTHROPIC_API_KEY to empty string", () => {
|
|
937
|
+
const result = resolveProviderEnv(
|
|
938
|
+
"openrouter",
|
|
939
|
+
"openai/gpt-5.3",
|
|
940
|
+
{
|
|
941
|
+
openrouter: {
|
|
942
|
+
type: "gateway",
|
|
943
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
944
|
+
authTokenEnv: "OPENROUTER_API_KEY",
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
{ OPENROUTER_API_KEY: "my-token" },
|
|
948
|
+
);
|
|
949
|
+
expect(result).not.toBeNull();
|
|
950
|
+
expect(result?.ANTHROPIC_API_KEY).toBe("");
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
test("handles authTokenEnv pointing to undefined env var", () => {
|
|
954
|
+
const result = resolveProviderEnv(
|
|
955
|
+
"openrouter",
|
|
956
|
+
"openai/gpt-5.3",
|
|
957
|
+
{
|
|
958
|
+
openrouter: {
|
|
959
|
+
type: "gateway",
|
|
960
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
961
|
+
authTokenEnv: "MISSING_VAR",
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
{},
|
|
965
|
+
);
|
|
966
|
+
expect(result).not.toBeNull();
|
|
967
|
+
expect(result).not.toHaveProperty("ANTHROPIC_AUTH_TOKEN");
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
test("handles authTokenEnv field being undefined", () => {
|
|
971
|
+
const result = resolveProviderEnv(
|
|
972
|
+
"mygw",
|
|
973
|
+
"some-model",
|
|
974
|
+
{
|
|
975
|
+
mygw: {
|
|
976
|
+
type: "gateway",
|
|
977
|
+
baseUrl: "https://mygw.example.com",
|
|
978
|
+
},
|
|
979
|
+
},
|
|
980
|
+
{},
|
|
981
|
+
);
|
|
982
|
+
expect(result).not.toBeNull();
|
|
983
|
+
expect(result?.ANTHROPIC_BASE_URL).toBe("https://mygw.example.com");
|
|
984
|
+
expect(result?.ANTHROPIC_API_KEY).toBe("");
|
|
985
|
+
expect(result).not.toHaveProperty("ANTHROPIC_AUTH_TOKEN");
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
describe("manifest validation accepts arbitrary model strings", () => {
|
|
990
|
+
let tempDir: string;
|
|
991
|
+
let manifestPath: string;
|
|
992
|
+
let agentBaseDir: string;
|
|
993
|
+
|
|
994
|
+
beforeEach(async () => {
|
|
995
|
+
tempDir = await mkdtemp(join(tmpdir(), "agentplate-model-test-"));
|
|
996
|
+
manifestPath = join(tempDir, "agent-manifest.json");
|
|
997
|
+
agentBaseDir = join(tempDir, "agents");
|
|
998
|
+
await mkdir(agentBaseDir, { recursive: true });
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
afterEach(async () => {
|
|
1002
|
+
await cleanupTempDir(tempDir);
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
test("accepts provider-prefixed model string", async () => {
|
|
1006
|
+
const data = {
|
|
1007
|
+
version: "1.0",
|
|
1008
|
+
agents: {
|
|
1009
|
+
agent: {
|
|
1010
|
+
file: "agent.md",
|
|
1011
|
+
model: "openrouter/openai/gpt-5.3",
|
|
1012
|
+
tools: ["Read"],
|
|
1013
|
+
capabilities: ["test"],
|
|
1014
|
+
canSpawn: false,
|
|
1015
|
+
constraints: [],
|
|
1016
|
+
},
|
|
1017
|
+
},
|
|
1018
|
+
};
|
|
1019
|
+
await Bun.write(manifestPath, JSON.stringify(data));
|
|
1020
|
+
await Bun.write(join(agentBaseDir, "agent.md"), "# Agent\n");
|
|
1021
|
+
const loader = createManifestLoader(manifestPath, agentBaseDir);
|
|
1022
|
+
|
|
1023
|
+
const manifest = await loader.load();
|
|
1024
|
+
expect(manifest.agents.agent?.model).toBe("openrouter/openai/gpt-5.3");
|
|
1025
|
+
});
|
|
1026
|
+
});
|