akm-cli 0.8.0-rc2 → 0.8.1

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.
Files changed (313) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +238 -3
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/assets/help/help-accept.md +12 -0
  5. package/dist/assets/help/help-improve.md +81 -0
  6. package/dist/{commands → assets}/help/help-proposals.md +7 -4
  7. package/dist/assets/help/help-reject.md +11 -0
  8. package/dist/{output → assets/hints}/cli-hints-full.md +60 -32
  9. package/dist/{output → assets/hints}/cli-hints-short.md +10 -7
  10. package/dist/assets/profiles/default.json +15 -0
  11. package/dist/assets/profiles/graph-refresh.json +13 -0
  12. package/dist/assets/profiles/memory-focus.json +12 -0
  13. package/dist/assets/profiles/quick.json +15 -0
  14. package/dist/assets/profiles/thorough.json +15 -0
  15. package/dist/assets/prompts/extract-session.md +80 -0
  16. package/dist/assets/prompts/graph-extract-user-prompt.md +35 -0
  17. package/dist/assets/tasks/graph-refresh-weekly.yml +10 -0
  18. package/dist/cli/config-migrate.js +144 -0
  19. package/dist/cli/config-validate.js +39 -0
  20. package/dist/cli/confirm.js +73 -0
  21. package/dist/cli/parse-args.js +93 -3
  22. package/dist/cli/shared.js +129 -0
  23. package/dist/cli.js +2141 -1268
  24. package/dist/commands/add-cli.js +279 -0
  25. package/dist/commands/agent-dispatch.js +20 -12
  26. package/dist/commands/agent-support.js +11 -5
  27. package/dist/commands/completions.js +3 -0
  28. package/dist/commands/config-cli.js +129 -517
  29. package/dist/commands/consolidate.js +1557 -147
  30. package/dist/commands/curate.js +44 -3
  31. package/dist/commands/db-cli.js +23 -0
  32. package/dist/commands/distill-promotion-policy.js +5 -3
  33. package/dist/commands/distill.js +906 -100
  34. package/dist/commands/env.js +213 -0
  35. package/dist/commands/eval-cases.js +3 -0
  36. package/dist/commands/events.js +3 -0
  37. package/dist/commands/extract-cli.js +127 -0
  38. package/dist/commands/extract-prompt.js +217 -0
  39. package/dist/commands/extract.js +477 -0
  40. package/dist/commands/feedback-cli.js +331 -0
  41. package/dist/commands/graph.js +260 -5
  42. package/dist/commands/health.js +1042 -55
  43. package/dist/commands/history.js +51 -16
  44. package/dist/commands/improve-auto-accept.js +97 -0
  45. package/dist/commands/improve-cli.js +236 -0
  46. package/dist/commands/improve-profiles.js +138 -0
  47. package/dist/commands/improve-result-file.js +167 -0
  48. package/dist/commands/improve.js +1736 -346
  49. package/dist/commands/info.js +26 -28
  50. package/dist/commands/init.js +49 -1
  51. package/dist/commands/installed-stashes.js +6 -23
  52. package/dist/commands/knowledge.js +3 -0
  53. package/dist/commands/lint/agent-linter.js +3 -0
  54. package/dist/commands/lint/base-linter.js +199 -5
  55. package/dist/commands/lint/command-linter.js +3 -0
  56. package/dist/commands/lint/default-linter.js +3 -0
  57. package/dist/commands/lint/env-key-rules.js +154 -0
  58. package/dist/commands/lint/index.js +92 -3
  59. package/dist/commands/lint/knowledge-linter.js +3 -0
  60. package/dist/commands/lint/markdown-insertion.js +343 -0
  61. package/dist/commands/lint/memory-linter.js +3 -0
  62. package/dist/commands/lint/registry.js +3 -0
  63. package/dist/commands/lint/skill-linter.js +3 -0
  64. package/dist/commands/lint/task-linter.js +15 -12
  65. package/dist/commands/lint/types.js +3 -0
  66. package/dist/commands/lint/workflow-linter.js +3 -0
  67. package/dist/commands/lint.js +3 -0
  68. package/dist/commands/migration-help.js +5 -2
  69. package/dist/commands/proposal-drain-policies.js +128 -0
  70. package/dist/commands/proposal-drain.js +477 -0
  71. package/dist/commands/proposal.js +60 -6
  72. package/dist/commands/propose.js +24 -19
  73. package/dist/commands/reflect.js +1004 -94
  74. package/dist/commands/registry-cli.js +150 -0
  75. package/dist/commands/registry-search.js +3 -0
  76. package/dist/commands/remember-cli.js +257 -0
  77. package/dist/commands/remember.js +15 -6
  78. package/dist/commands/schema-repair.js +88 -15
  79. package/dist/commands/search.js +99 -14
  80. package/dist/commands/secret.js +173 -0
  81. package/dist/commands/self-update.js +3 -0
  82. package/dist/commands/show.js +32 -13
  83. package/dist/commands/source-add.js +7 -35
  84. package/dist/commands/source-clone.js +3 -0
  85. package/dist/commands/source-manage.js +3 -0
  86. package/dist/commands/tasks.js +161 -95
  87. package/dist/commands/url-checker.js +3 -0
  88. package/dist/core/action-contributors.js +3 -0
  89. package/dist/core/asset-ref.js +13 -2
  90. package/dist/core/asset-registry.js +9 -2
  91. package/dist/core/asset-serialize.js +88 -0
  92. package/dist/core/asset-spec.js +61 -5
  93. package/dist/core/common.js +93 -5
  94. package/dist/core/concurrent.js +3 -0
  95. package/dist/core/config-io.js +347 -0
  96. package/dist/core/config-migration.js +622 -0
  97. package/dist/core/config-schema.js +558 -0
  98. package/dist/core/config-sources.js +108 -0
  99. package/dist/core/config-types.js +4 -0
  100. package/dist/core/config-walker.js +337 -0
  101. package/dist/core/config.js +366 -1077
  102. package/dist/core/errors.js +42 -20
  103. package/dist/core/events.js +31 -25
  104. package/dist/core/file-lock.js +104 -0
  105. package/dist/core/frontmatter.js +75 -10
  106. package/dist/core/lesson-lint.js +3 -0
  107. package/dist/core/markdown.js +3 -0
  108. package/dist/core/memory-belief.js +62 -0
  109. package/dist/core/memory-contradiction-detect.js +274 -0
  110. package/dist/core/memory-improve.js +142 -14
  111. package/dist/core/parse.js +3 -0
  112. package/dist/core/paths.js +218 -50
  113. package/dist/core/proposal-quality-validators.js +380 -0
  114. package/dist/core/proposal-validators.js +11 -3
  115. package/dist/core/proposals.js +464 -5
  116. package/dist/core/state-db.js +349 -56
  117. package/dist/core/text-truncation.js +107 -0
  118. package/dist/core/time.js +3 -0
  119. package/dist/core/tty.js +59 -0
  120. package/dist/core/warn.js +7 -2
  121. package/dist/core/write-source.js +12 -0
  122. package/dist/indexer/db-backup.js +391 -0
  123. package/dist/indexer/db-search.js +136 -28
  124. package/dist/indexer/db.js +661 -166
  125. package/dist/indexer/ensure-index.js +3 -0
  126. package/dist/indexer/file-context.js +3 -0
  127. package/dist/indexer/graph-boost.js +162 -40
  128. package/dist/indexer/graph-db.js +241 -51
  129. package/dist/indexer/graph-dedup.js +3 -7
  130. package/dist/indexer/graph-extraction.js +242 -149
  131. package/dist/indexer/index-context.js +3 -9
  132. package/dist/indexer/indexer.js +86 -16
  133. package/dist/indexer/llm-cache.js +24 -19
  134. package/dist/indexer/manifest.js +3 -0
  135. package/dist/indexer/matchers.js +184 -11
  136. package/dist/indexer/memory-inference.js +94 -50
  137. package/dist/indexer/metadata-contributors.js +3 -0
  138. package/dist/indexer/metadata.js +110 -50
  139. package/dist/indexer/path-resolver.js +3 -0
  140. package/dist/indexer/project-context.js +192 -0
  141. package/dist/indexer/ranking-contributors.js +134 -7
  142. package/dist/indexer/ranking.js +8 -1
  143. package/dist/indexer/search-fields.js +5 -9
  144. package/dist/indexer/search-hit-enrichers.js +91 -2
  145. package/dist/indexer/search-source.js +20 -1
  146. package/dist/indexer/semantic-status.js +4 -1
  147. package/dist/indexer/staleness-detect.js +447 -0
  148. package/dist/indexer/usage-events.js +12 -9
  149. package/dist/indexer/walker.js +3 -0
  150. package/dist/integrations/agent/builders.js +135 -0
  151. package/dist/integrations/agent/config.js +121 -401
  152. package/dist/integrations/agent/detect.js +3 -0
  153. package/dist/integrations/agent/index.js +6 -14
  154. package/dist/integrations/agent/model-aliases.js +55 -0
  155. package/dist/integrations/agent/profiles.js +3 -0
  156. package/dist/integrations/agent/prompts.js +137 -8
  157. package/dist/integrations/agent/runner.js +208 -0
  158. package/dist/integrations/agent/sdk-runner.js +8 -2
  159. package/dist/integrations/agent/spawn.js +54 -14
  160. package/dist/integrations/github.js +3 -0
  161. package/dist/integrations/lockfile.js +22 -51
  162. package/dist/integrations/session-logs/index.js +4 -0
  163. package/dist/integrations/session-logs/inline-refs.js +35 -0
  164. package/dist/integrations/session-logs/pre-filter.js +152 -0
  165. package/dist/integrations/session-logs/providers/claude-code.js +226 -0
  166. package/dist/integrations/session-logs/providers/opencode.js +231 -25
  167. package/dist/integrations/session-logs/types.js +3 -0
  168. package/dist/llm/call-ai.js +14 -26
  169. package/dist/llm/client.js +16 -2
  170. package/dist/llm/embedder.js +20 -29
  171. package/dist/llm/embedders/cache.js +3 -7
  172. package/dist/llm/embedders/local.js +42 -1
  173. package/dist/llm/embedders/remote.js +20 -8
  174. package/dist/llm/embedders/types.js +3 -7
  175. package/dist/llm/feature-gate.js +92 -56
  176. package/dist/llm/graph-extract.js +402 -31
  177. package/dist/llm/index-passes.js +44 -29
  178. package/dist/llm/memory-infer.js +30 -2
  179. package/dist/llm/metadata-enhance.js +3 -7
  180. package/dist/output/cli-hints.js +7 -4
  181. package/dist/output/context.js +60 -8
  182. package/dist/output/renderers.js +170 -194
  183. package/dist/output/shapes/curate.js +56 -0
  184. package/dist/output/shapes/distill.js +10 -0
  185. package/dist/output/shapes/env-list.js +19 -0
  186. package/dist/output/shapes/events.js +11 -0
  187. package/dist/output/shapes/helpers.js +424 -0
  188. package/dist/output/shapes/history.js +7 -0
  189. package/dist/output/shapes/passthrough.js +105 -0
  190. package/dist/output/shapes/proposal-accept.js +7 -0
  191. package/dist/output/shapes/proposal-diff.js +7 -0
  192. package/dist/output/shapes/proposal-list.js +7 -0
  193. package/dist/output/shapes/proposal-producer.js +11 -0
  194. package/dist/output/shapes/proposal-reject.js +7 -0
  195. package/dist/output/shapes/proposal-show.js +7 -0
  196. package/dist/output/shapes/registry-search.js +6 -0
  197. package/dist/output/shapes/registry.js +30 -0
  198. package/dist/output/shapes/search.js +6 -0
  199. package/dist/output/shapes/secret-list.js +19 -0
  200. package/dist/output/shapes/show.js +6 -0
  201. package/dist/output/shapes/vault-list.js +19 -0
  202. package/dist/output/shapes.js +51 -549
  203. package/dist/output/text/add.js +6 -0
  204. package/dist/output/text/clone.js +6 -0
  205. package/dist/output/text/config.js +6 -0
  206. package/dist/output/text/curate.js +6 -0
  207. package/dist/output/text/distill.js +7 -0
  208. package/dist/output/text/enable-disable.js +7 -0
  209. package/dist/output/text/events.js +10 -0
  210. package/dist/output/text/feedback.js +6 -0
  211. package/dist/output/text/helpers.js +1059 -0
  212. package/dist/output/text/history.js +7 -0
  213. package/dist/output/text/import.js +6 -0
  214. package/dist/output/text/index.js +6 -0
  215. package/dist/output/text/info.js +6 -0
  216. package/dist/output/text/init.js +6 -0
  217. package/dist/output/text/list.js +6 -0
  218. package/dist/output/text/proposal-producer.js +8 -0
  219. package/dist/output/text/proposal.js +12 -0
  220. package/dist/output/text/registry-commands.js +11 -0
  221. package/dist/output/text/registry.js +30 -0
  222. package/dist/output/text/remember.js +6 -0
  223. package/dist/output/text/remove.js +6 -0
  224. package/dist/output/text/save.js +6 -0
  225. package/dist/output/text/search.js +6 -0
  226. package/dist/output/text/show.js +6 -0
  227. package/dist/output/text/update.js +6 -0
  228. package/dist/output/text/upgrade.js +6 -0
  229. package/dist/output/text/vault.js +16 -0
  230. package/dist/output/text/wiki.js +15 -0
  231. package/dist/output/text/workflow.js +14 -0
  232. package/dist/output/text.js +44 -1329
  233. package/dist/registry/build-index.js +3 -0
  234. package/dist/registry/create-provider-registry.js +3 -0
  235. package/dist/registry/factory.js +4 -1
  236. package/dist/registry/origin-resolve.js +3 -0
  237. package/dist/registry/providers/index.js +3 -0
  238. package/dist/registry/providers/skills-sh.js +11 -2
  239. package/dist/registry/providers/static-index.js +10 -1
  240. package/dist/registry/providers/types.js +3 -24
  241. package/dist/registry/resolve.js +11 -16
  242. package/dist/registry/types.js +3 -0
  243. package/dist/scripts/migrate-storage.js +17767 -0
  244. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  245. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  246. package/dist/setup/detect.js +3 -0
  247. package/dist/setup/ripgrep-install.js +3 -0
  248. package/dist/setup/ripgrep-resolve.js +3 -0
  249. package/dist/setup/setup.js +306 -67
  250. package/dist/setup/steps.js +3 -15
  251. package/dist/sources/include.js +3 -0
  252. package/dist/sources/provider-factory.js +3 -11
  253. package/dist/sources/provider.js +3 -20
  254. package/dist/sources/providers/filesystem.js +19 -23
  255. package/dist/sources/providers/git.js +171 -21
  256. package/dist/sources/providers/index.js +3 -0
  257. package/dist/sources/providers/install-types.js +3 -13
  258. package/dist/sources/providers/npm.js +3 -4
  259. package/dist/sources/providers/provider-utils.js +3 -0
  260. package/dist/sources/providers/sync-from-ref.js +3 -11
  261. package/dist/sources/providers/tar-utils.js +3 -0
  262. package/dist/sources/providers/website.js +18 -22
  263. package/dist/sources/resolve.js +3 -0
  264. package/dist/sources/types.js +3 -0
  265. package/dist/sources/website-ingest.js +3 -0
  266. package/dist/tasks/backends/cron.js +3 -0
  267. package/dist/tasks/backends/exec-utils.js +3 -0
  268. package/dist/tasks/backends/index.js +3 -11
  269. package/dist/tasks/backends/launchd.js +4 -1
  270. package/dist/tasks/backends/schtasks.js +4 -1
  271. package/dist/tasks/parser.js +51 -38
  272. package/dist/tasks/resolveAkmBin.js +3 -0
  273. package/dist/tasks/runner.js +35 -9
  274. package/dist/tasks/schedule.js +20 -1
  275. package/dist/tasks/schema.js +5 -3
  276. package/dist/tasks/validator.js +6 -3
  277. package/dist/version.js +3 -0
  278. package/dist/wiki/wiki-templates.js +6 -3
  279. package/dist/wiki/wiki.js +4 -1
  280. package/dist/workflows/authoring.js +4 -1
  281. package/dist/workflows/cli.js +3 -0
  282. package/dist/workflows/db.js +140 -10
  283. package/dist/workflows/document-cache.js +3 -10
  284. package/dist/workflows/parser.js +3 -0
  285. package/dist/workflows/renderer.js +3 -0
  286. package/dist/workflows/runs.js +18 -1
  287. package/dist/workflows/schema.js +3 -0
  288. package/dist/workflows/scope-key.js +3 -0
  289. package/dist/workflows/validator.js +5 -9
  290. package/docs/README.md +7 -2
  291. package/docs/data-and-telemetry.md +225 -0
  292. package/docs/migration/release-notes/0.7.5.md +2 -2
  293. package/docs/migration/release-notes/0.8.0.md +57 -5
  294. package/docs/migration/v0.7-to-v0.8.md +1378 -0
  295. package/package.json +28 -11
  296. package/.github/LICENSE +0 -374
  297. package/dist/commands/help/help-accept.md +0 -9
  298. package/dist/commands/help/help-improve.md +0 -53
  299. package/dist/commands/help/help-reject.md +0 -8
  300. package/dist/commands/install-audit.js +0 -385
  301. package/dist/commands/vault.js +0 -310
  302. package/dist/indexer/match-contributors.js +0 -141
  303. package/dist/integrations/agent/pipeline.js +0 -39
  304. package/dist/integrations/agent/runners.js +0 -31
  305. package/dist/llm/prompts/graph-extract-user-prompt.md +0 -12
  306. /package/dist/{tasks → assets}/backends/launchd-template.xml +0 -0
  307. /package/dist/{tasks → assets}/backends/schtasks-template.xml +0 -0
  308. /package/dist/{commands → assets}/help/help-propose.md +0 -0
  309. /package/dist/{wiki → assets/wiki}/index-template.md +0 -0
  310. /package/dist/{wiki → assets/wiki}/ingest-workflow-template.md +0 -0
  311. /package/dist/{wiki → assets/wiki}/log-template.md +0 -0
  312. /package/dist/{wiki → assets/wiki}/schema-template.md +0 -0
  313. /package/dist/{workflows → assets/workflows}/workflow-template.md +0 -0
@@ -1,3 +1,6 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  const COMMON_PASSTHROUGH = ["HOME", "PATH", "USER", "LANG", "LC_ALL", "TERM", "TMPDIR"];
2
5
  /**
3
6
  * Built-in profiles for the five agent CLIs the v1 spec calls out
@@ -1,3 +1,6 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  /**
2
5
  * Shared prompt builders for proposal-producing agent commands (#226).
3
6
  *
@@ -38,7 +41,8 @@ const TYPE_HINTS = {
38
41
  memory: "memory assets are short factual notes the user wants persisted across sessions. Frontmatter usually includes `description`.",
39
42
  workflow: "workflow assets are markdown describing a multi-step process. Include `# <Title>` and ordered `## Step N` sections.",
40
43
  script: "script assets are executable text files. Include a shebang and minimal usage comment.",
41
- vault: "vault assets store environment variables (KEY=VALUE pairs). Comments use `#`. Never echo secret values back to the user.",
44
+ env: "env assets are `.env` files holding a group of related CONFIGURATION for an app/service (KEY=VALUE pairs, `#` comments) — URLs, flags, and any credentials it needs. Values may or may not be sensitive; all are protected (key names discoverable, values stay on disk). Inject with `akm env run env:<name> -- <cmd>` (the safe path — values never reach stdout/your context); do NOT run `akm env export` and read its output, as that prints values. For a single sensitive value used on its own for authentication (token, key, cert) use a `secret` instead. Never echo values back to the user.",
45
+ vault: "vault assets are DEPRECATED (use env). They store environment variables (KEY=VALUE pairs); comments use `#`. Never echo secret values back to the user.",
42
46
  wiki: "wiki assets are markdown reference pages with `# Title` and structured headings.",
43
47
  };
44
48
  function hintForType(type) {
@@ -55,9 +59,15 @@ function knownTypeList() {
55
59
  */
56
60
  const RESPONSE_CONTRACT_JSON = [
57
61
  "Respond ONLY with a single JSON object. No prose before or after.",
58
- 'Shape: {"ref": "<type>:<name>", "content": "<full file contents>", "frontmatter": {...}}',
62
+ 'Shape: {"ref": "<type>:<name>", "content": "<full file contents>", "frontmatter": {...}, "confidence": <number 0..1>}',
59
63
  "`content` is the full file body that will be written if accepted.",
60
64
  "`frontmatter` is optional — include it if `content` starts with `---` so reviewers can sanity-check the keys.",
65
+ "`confidence` is REQUIRED. Self-rate this proposal on [0, 1] by how certain you are it materially improves the source asset. Calibrate honestly:",
66
+ " • 0.90+ — high certainty: fixes a real defect or adds load-bearing missing content; a reviewer would clearly accept.",
67
+ " • 0.70–0.89 — clear improvement, but a reviewer might reasonably prefer different framing or scope.",
68
+ " • 0.50–0.69 — marginal / judgment call; might help, might not be worth the churn.",
69
+ " • Below 0.50 — you are not confident this improves on the source. Prefer returning the source body roughly unchanged with a low score over inventing changes.",
70
+ "Auto-accept gates on confidence ≥ 0.80 by default. Overclaiming ships low-quality changes; underclaiming leaves good ones stuck in queue. Be honest.",
61
71
  ].join("\n");
62
72
  /**
63
73
  * Response contract used when a draft file path is available. Instructs the
@@ -69,14 +79,42 @@ function fileWriteContract(draftFilePath) {
69
79
  `Write the complete improved asset content to: ${draftFilePath}`,
70
80
  "Use your file-editing tools to create or overwrite that file.",
71
81
  "Do NOT output JSON to stdout. Do NOT print the file contents. Just write the file.",
72
- "When you are done writing the file, output a single line: DRAFT_WRITTEN",
82
+ "When done, output a single line on stdout: DRAFT_WRITTEN confidence=<0.0-1.0>",
83
+ "`confidence` is REQUIRED and must be your honest self-rated [0, 1] score for this proposal:",
84
+ " • 0.90+ — fixes a real defect or adds load-bearing missing content; reviewer would clearly accept.",
85
+ " • 0.70–0.89 — clear improvement, but a reviewer might prefer different framing.",
86
+ " • 0.50–0.69 — marginal / judgment call.",
87
+ " • Below 0.50 — not confident; prefer not writing changes at all.",
88
+ "Auto-accept gates on confidence ≥ 0.80. Overclaim → low-quality changes land; underclaim → good changes stuck in queue.",
73
89
  ].join("\n");
74
90
  }
91
+ /**
92
+ * Extract a confidence score from a `DRAFT_WRITTEN confidence=<n>` line emitted
93
+ * by an agent following {@link fileWriteContract}. Tolerates trailing prose,
94
+ * surrounding log lines, and missing/invalid confidence (returns `undefined`
95
+ * so callers can keep the proposal but skip auto-accept).
96
+ *
97
+ * Matched forms (case-insensitive, anywhere in stdout):
98
+ * - `DRAFT_WRITTEN confidence=0.85`
99
+ * - `DRAFT_WRITTEN confidence=0.85 ...trailing`
100
+ * - `DRAFT_WRITTEN` (no confidence — returns `undefined`)
101
+ */
102
+ export function extractDraftConfidence(stdout) {
103
+ if (!stdout)
104
+ return undefined;
105
+ const match = stdout.match(/\bDRAFT_WRITTEN\b[^\S\r\n]+confidence=([0-9]*\.?[0-9]+)/i);
106
+ if (!match)
107
+ return undefined;
108
+ const value = Number.parseFloat(match[1] ?? "");
109
+ if (!Number.isFinite(value) || value < 0 || value > 1)
110
+ return undefined;
111
+ return value;
112
+ }
75
113
  /**
76
114
  * Build the prompt for `akm reflect [ref]`. Asks the agent to review an
77
115
  * existing asset (plus any negative feedback / lint findings) and propose
78
- * an improved version. Returns a single string the agent runtime will
79
- * forward it as the trailing positional arg.
116
+ * an improved version. Returns a {@link ReflectPromptResult} containing the
117
+ * prompt string and an optional character ceiling for max-tokens enforcement.
80
118
  */
81
119
  export function buildReflectPrompt(input) {
82
120
  const sections = [];
@@ -119,9 +157,17 @@ export function buildReflectPrompt(input) {
119
157
  sections.push("No usage feedback recorded. Limit your proposal to schema and structural improvements only: missing required frontmatter fields, unclear `when_to_use`, ambiguous description, or broken formatting. Do not speculate about runtime weaknesses you have not observed.");
120
158
  }
121
159
  if (input.assetContent?.trim()) {
122
- sections.push("Current asset content (verbatim):");
160
+ // Cap at 12 000 chars to stay well under OS ARG_MAX when the prompt is
161
+ // passed as a CLI argument to opencode/claude. Large assets (wiki snapshots,
162
+ // long runbooks) would otherwise trigger E2BIG on posix_spawn.
163
+ const REFLECT_CONTENT_CAP = 12_000;
164
+ const body = input.assetContent.trimEnd();
165
+ const truncated = body.length > REFLECT_CONTENT_CAP;
166
+ sections.push(truncated
167
+ ? `Current asset content (first ${REFLECT_CONTENT_CAP} chars — full asset is ${body.length} chars):`
168
+ : "Current asset content (verbatim):");
123
169
  sections.push("```");
124
- sections.push(input.assetContent.trimEnd());
170
+ sections.push(truncated ? `${body.slice(0, REFLECT_CONTENT_CAP)}\n... [truncated — focus on the visible portion]` : body);
125
171
  sections.push("```");
126
172
  }
127
173
  else if (input.ref) {
@@ -147,12 +193,86 @@ export function buildReflectPrompt(input) {
147
193
  sections.push("Promote only guidance that is durable, generally applicable, and supported by repeated evidence. Do not copy anecdotal details, one-off incidents, or duplicate wording verbatim.");
148
194
  sections.push("If the guidance belongs in the main skill instructions, update the skill proposal. If it belongs in a companion reference document, return a `knowledge:skills/<skill>/references/<topic>` proposal instead.");
149
195
  }
196
+ if (input.rejectedProposals && input.rejectedProposals.length > 0) {
197
+ const lines = ["## Previously Rejected Proposals"];
198
+ lines.push("The following proposals for this ref were already reviewed and rejected. " +
199
+ "Do NOT reproduce the same content or the same structural shape. " +
200
+ "Your new proposal must meaningfully differ from each of these in its approach, framing, or evidence used.");
201
+ for (const rp of input.rejectedProposals) {
202
+ lines.push(`\nRef: ${rp.ref}`);
203
+ lines.push(`Rejection reason: ${rp.reason}`);
204
+ if (rp.contentPreview) {
205
+ lines.push("Rejected content preview:");
206
+ lines.push("```");
207
+ lines.push(rp.contentPreview);
208
+ lines.push("```");
209
+ }
210
+ }
211
+ sections.push(lines.join("\n"));
212
+ }
150
213
  if (input.avoidPatterns && input.avoidPatterns.length > 0) {
151
214
  sections.push(`## Avoid These Patterns\nPrevious assets in this run produced these errors — do not repeat them:\n${input.avoidPatterns.map((e) => `- ${e}`).join("\n")}`);
152
215
  }
216
+ // R-1 / #372: Self-Refine (arXiv:2303.17651) — inject prior draft as critique target.
217
+ // On refinement iterations (iter > 0), the agent is shown its previous proposal
218
+ // and asked to self-critique and improve it rather than starting from scratch.
219
+ if (input.priorDraft?.trim()) {
220
+ sections.push("## Self-Refine: Critique and Improve\n" +
221
+ "The following is your previous draft proposal. " +
222
+ "Identify specific weaknesses: missing evidence, vague wording, incomplete frontmatter, " +
223
+ "or claims that duplicate existing content without adding new signal. " +
224
+ "Then produce an improved version that addresses those weaknesses. " +
225
+ "The revised proposal must be meaningfully better than the draft below — " +
226
+ "do not return the same content unchanged.\n\n" +
227
+ "Previous draft:\n```\n" +
228
+ input.priorDraft.trimEnd() +
229
+ "\n```");
230
+ }
153
231
  sections.push("Produce a single proposal that addresses the feedback and respects the asset-type contract. If the proposal's frontmatter is missing `when_to_use`, you MUST generate one — a one-line trigger sentence describing exactly when a user should reach for this asset.");
232
+ // Content-preservation safety rails (#reflect-pipeline-fixes).
233
+ // These rules counter the observed failure modes where reflect rewrites
234
+ // asset content into shorter prose, drops concrete structure, or strips
235
+ // load-bearing frontmatter. Loud and explicit so small models follow.
236
+ //
237
+ // maxOutputChars is hoisted so the return value can include it for callers
238
+ // on the LLM path that want to set a hard max_tokens cap on the request.
239
+ let maxOutputChars;
240
+ if (input.ref && input.assetContent?.trim()) {
241
+ // Strip frontmatter to get source body length — mirrors checkReflectSize which
242
+ // compares body-only lengths. Inline regex avoids importing parseFrontmatter.
243
+ const rawContent = input.assetContent.trimEnd();
244
+ const fmBodyMatch = rawContent.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
245
+ const sourceBodyLen = (fmBodyMatch ? fmBodyMatch[1] : rawContent).trim().length;
246
+ // Compute concrete char bounds matching checkReflectSize constants:
247
+ // REFLECT_SIZE_GUARD_MIN_BYTES=200, REFLECT_SHRINK_RATIO_MIN=0.5,
248
+ // REFLECT_ABSOLUTE_FLOOR_BYTES=150, REFLECT_EXPAND_RATIO_MAX=2.5,
249
+ // REFLECT_ABSOLUTE_CEILING_BYTES=2500, REFLECT_ABSOLUTE_MAX_BYTES=25000.
250
+ // Embed concrete counts only when the gate will actually fire (source >= 200 chars).
251
+ const showCharBounds = sourceBodyLen >= 200;
252
+ const minChars = Math.max(Math.round(0.5 * sourceBodyLen), 150);
253
+ const maxChars = Math.min(Math.max(Math.round(2.5 * sourceBodyLen), 2500), 25000);
254
+ if (showCharBounds)
255
+ maxOutputChars = maxChars;
256
+ sections.push([
257
+ "## Content preservation rules (MUST follow)",
258
+ "1. PRESERVE ALL concrete content: code blocks, fenced snippets, CLI commands, numbered/bulleted checklists, tables, YAML/JSON examples, file paths, configuration keys, environment variable names, and CSS/HTML selectors. These are load-bearing — do NOT replace them with prose summaries.",
259
+ "2. PRESERVE the source asset's frontmatter. The post-processor reassembles the final asset from the original frontmatter plus your body. Do NOT emit `---` frontmatter delimiters at the top of `content` — start `content` with the markdown body (e.g. `# Heading` or the first paragraph). If you include frontmatter anyway, identity fields (`name`, `ref`, `id`, `slug`, `type`) will be reset to the original values.",
260
+ showCharBounds
261
+ ? `3. DO NOT shrink the asset. Your body must be at least ${minChars} characters (source body is ${sourceBodyLen} chars; floor is 50%). If you genuinely need to remove a major section, explain why in a comment line at the top of the body (e.g. \`<!-- removed obsolete section X because ... -->\`).`
262
+ : "3. DO NOT shrink the asset dramatically. The improved body must be at least 50% of the source body length. If you genuinely need to remove a major section, explain why in a comment line at the top of the body (e.g. `<!-- removed obsolete section X because ... -->`).",
263
+ showCharBounds
264
+ ? `4. DO NOT pad the asset with speculative material. Your body must be at most ${maxChars} characters (source body is ${sourceBodyLen} chars; ceiling is 250%). Do not add invented sections, hypothetical examples, or padding prose.`
265
+ : "4. DO NOT pad the asset with speculative material. The improved body must be at most 250% of the source body length unless the feedback explicitly requests added sections.",
266
+ "5. Improve clarity of surrounding prose, fix structural issues, add missing required frontmatter fields. Do NOT rewrite a runbook into an essay.",
267
+ ].join("\n"));
268
+ }
269
+ if (!input.draftFilePath && input.ref) {
270
+ // Reinforce that the `ref` field is mandatory and must exactly match the target.
271
+ // Small models frequently omit `ref` from the response JSON, causing parse errors.
272
+ sections.push(`IMPORTANT: The JSON "ref" field is REQUIRED. It MUST be exactly: "${input.ref}"`);
273
+ }
154
274
  sections.push(input.draftFilePath ? fileWriteContract(input.draftFilePath) : RESPONSE_CONTRACT_JSON);
155
- return sections.join("\n\n");
275
+ return { prompt: sections.join("\n\n"), ...(maxOutputChars !== undefined ? { maxOutputChars } : {}) };
156
276
  }
157
277
  /**
158
278
  * Build the prompt for `akm propose <type> <name> --task ...`. Asks the
@@ -240,6 +360,15 @@ export function parseAgentProposalPayload(stdout) {
240
360
  if (parsed.frontmatter && typeof parsed.frontmatter === "object" && !Array.isArray(parsed.frontmatter)) {
241
361
  out.frontmatter = parsed.frontmatter;
242
362
  }
363
+ // Phase 6A: extract optional `confidence` (number in [0, 1]). Clamp gently
364
+ // rather than reject — a model that returns 1.0 or 0 with extra precision
365
+ // (e.g. 1.0000001) should still surface a usable score. Anything that isn't
366
+ // a finite number is dropped so downstream `createProposal` can rely on the
367
+ // shape invariant.
368
+ if (typeof parsed.confidence === "number" && Number.isFinite(parsed.confidence)) {
369
+ const clamped = Math.max(0, Math.min(1, parsed.confidence));
370
+ out.confidence = clamped;
371
+ }
243
372
  return out;
244
373
  }
245
374
  /**
@@ -0,0 +1,208 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { ConfigError } from "../../core/errors";
5
+ import { warn } from "../../core/warn";
6
+ function resolveEffectiveMode(entry, profileName, config) {
7
+ if (entry.mode)
8
+ return entry.mode;
9
+ // Infer from profile pool when profile is specified
10
+ if (profileName) {
11
+ if (config.profiles?.llm?.[profileName])
12
+ return "llm";
13
+ const agentProfile = config.profiles?.agent?.[profileName];
14
+ if (agentProfile) {
15
+ return agentProfile.platform === "opencode-sdk" ? "sdk" : "agent";
16
+ }
17
+ }
18
+ // Fall back to defaults
19
+ if (config.defaults?.llm)
20
+ return "llm";
21
+ if (config.defaults?.agent)
22
+ return "agent";
23
+ return "llm";
24
+ }
25
+ function resolveProfileName(entry, mode, config) {
26
+ if (entry.profile)
27
+ return entry.profile;
28
+ if (mode === "llm") {
29
+ const defaultName = config.defaults?.llm;
30
+ if (defaultName)
31
+ return defaultName;
32
+ throw new ConfigError(`No LLM profile configured. Set defaults.llm in config or specify profile in the process entry.`, "LLM_NOT_CONFIGURED", "Run `akm setup` or define a profile under `profiles.llm` and set `defaults.llm`.");
33
+ }
34
+ const defaultName = config.defaults?.agent;
35
+ if (defaultName)
36
+ return defaultName;
37
+ throw new ConfigError(`No agent profile configured. Set defaults.agent in config or specify profile in the process entry.`, "INVALID_CONFIG_FILE", "Run `akm setup` to configure an agent profile, or add one under `profiles.agent`.");
38
+ }
39
+ function buildLlmRunnerSpec(profileName, timeoutMs, config) {
40
+ const profile = config.profiles?.llm?.[profileName];
41
+ if (!profile) {
42
+ throw new ConfigError(`LLM profile "${profileName}" not found in profiles.llm.`, "LLM_NOT_CONFIGURED", `Available profiles: ${Object.keys(config.profiles?.llm ?? {}).join(", ") || "none"}. Run \`akm setup\` to configure.`);
43
+ }
44
+ return {
45
+ kind: "llm",
46
+ connection: profile,
47
+ ...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
48
+ };
49
+ }
50
+ function buildAgentRunnerSpec(kind, profileName, timeoutMs, config) {
51
+ const profileConfig = config.profiles?.agent?.[profileName];
52
+ if (!profileConfig) {
53
+ throw new ConfigError(`Agent profile "${profileName}" not found in profiles.agent.`, "INVALID_CONFIG_FILE", `Available profiles: ${Object.keys(config.profiles?.agent ?? {}).join(", ") || "none"}. Run \`akm setup\` to configure.`);
54
+ }
55
+ // Validate mode/platform consistency
56
+ if (kind === "sdk" && profileConfig.platform !== "opencode-sdk") {
57
+ throw new ConfigError(`Mode "sdk" requires platform "opencode-sdk", but profiles.agent["${profileName}"].platform is "${profileConfig.platform}".`, "INVALID_CONFIG_FILE");
58
+ }
59
+ if (kind === "agent" && profileConfig.platform === "opencode-sdk") {
60
+ throw new ConfigError(`Mode "agent" requires platform "opencode" or "claude", but profiles.agent["${profileName}"].platform is "opencode-sdk".`, "INVALID_CONFIG_FILE");
61
+ }
62
+ const agentProfile = {
63
+ name: profileName,
64
+ bin: profileConfig.bin ?? profileName,
65
+ args: profileConfig.args ?? [],
66
+ stdio: "captured",
67
+ envPassthrough: [],
68
+ parseOutput: "text",
69
+ ...(profileConfig.model ? { model: profileConfig.model } : {}),
70
+ ...(profileConfig.workspace ? { workspace: profileConfig.workspace } : {}),
71
+ };
72
+ return {
73
+ kind,
74
+ profile: agentProfile,
75
+ ...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
76
+ };
77
+ }
78
+ /**
79
+ * Resolve the runner used for "validation" passes on the `improve` section
80
+ * (Advantage D3 / Phase 4B — third model tier).
81
+ *
82
+ * Look-up order:
83
+ * 1. `profiles.improve.default.processes.validation` (preferred — lets users
84
+ * wire a lower-cost classifier model for staleness detection, confidence
85
+ * scoring, and lesson classification).
86
+ * 2. `defaults.llm` as a final fallback so callers always get a usable
87
+ * runner when any LLM is configured.
88
+ *
89
+ * Returns `null` when neither is configured (callers may then skip the
90
+ * validation pass rather than throwing).
91
+ */
92
+ /**
93
+ * Shared resolution path for the `validation` and `triage judgment` tiers,
94
+ * both of which take a `{ mode?, profile?, timeoutMs? }`-shaped block, attempt
95
+ * to resolve it via {@link resolveImproveProcessRunnerFromProfile}, and
96
+ * otherwise fall back to an `llm` runner built from `defaults.llm`.
97
+ *
98
+ * @param block the `{ mode?, profile?, timeoutMs? }` subset to resolve.
99
+ * @param config the active AKM config.
100
+ * @param opts.fallbackTimeoutMs timeout applied to the `defaults.llm` fallback
101
+ * runner (validation passes `undefined`; triage forwards `block.timeoutMs`).
102
+ * @param opts.suppressLlmFallbackForExplicitMode when `true` and the block
103
+ * explicitly sets `mode: "agent"` or `"sdk"`, do NOT silently fall back
104
+ * to `defaults.llm` if the profile resolver returns `null` or throws —
105
+ * return `null` instead and emit a `warn(...)` (see FIX 8). Validation
106
+ * passes `false` to preserve its always-fallback behavior.
107
+ */
108
+ function resolveProcessRunnerWithLlmFallback(block, config, opts) {
109
+ const explicitNonLlmMode = block?.mode === "agent" || block?.mode === "sdk";
110
+ if (block && (block.mode || block.profile)) {
111
+ try {
112
+ const spec = resolveImproveProcessRunnerFromProfile(block, config);
113
+ if (spec)
114
+ return spec;
115
+ }
116
+ catch {
117
+ // Fall through to defaults.llm below (unless suppressed for explicit modes).
118
+ }
119
+ // FIX 8: an EXPLICIT agent/sdk request must not be silently downgraded to
120
+ // llm. When the profile resolver could not produce a runner, surface the
121
+ // misconfiguration and let callers skip this tier rather than substituting
122
+ // an llm judge.
123
+ if (opts.suppressLlmFallbackForExplicitMode && explicitNonLlmMode) {
124
+ warn(`[akm] Could not resolve the "${block?.mode}" judgment profile; ` +
125
+ `skipping the judgment tier instead of falling back to an llm judge. ` +
126
+ `Check profiles.agent and the judgment profile/mode configuration.`);
127
+ return null;
128
+ }
129
+ }
130
+ const defaultLlm = config.defaults?.llm;
131
+ if (defaultLlm) {
132
+ try {
133
+ return buildLlmRunnerSpec(defaultLlm, opts.fallbackTimeoutMs, config);
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ export function resolveValidationRunner(config) {
142
+ const validation = config.profiles?.improve?.default?.processes?.validation;
143
+ const block = validation && validation.enabled !== false ? validation : undefined;
144
+ return resolveProcessRunnerWithLlmFallback(block, config, {
145
+ fallbackTimeoutMs: undefined,
146
+ suppressLlmFallbackForExplicitMode: false,
147
+ });
148
+ }
149
+ /**
150
+ * Resolve the runner for the triage judgment tier (Proposal-Queue Triage,
151
+ * Phase 3). The `judgment` block is a `{ mode?, profile?, timeoutMs? }` subset
152
+ * compatible with {@link ImproveProcessConfig}, so it resolves through the same
153
+ * {@link resolveImproveProcessRunnerFromProfile} path.
154
+ *
155
+ * Mirrors {@link resolveValidationRunner}'s `defaults.llm` fallback only for
156
+ * the unset/`llm` case: when the block sets neither `mode` nor `profile` (so
157
+ * the profile resolver returns `null`) or `mode: "llm"`, fall back to an `llm`
158
+ * runner built from `defaults.llm` so `judgment.mode: llm` defaults are honored
159
+ * (§14). However (FIX 8) when the caller EXPLICITLY sets `mode: "agent"` or
160
+ * `"sdk"` and the profile resolver returns `null` or throws, do NOT downgrade
161
+ * to an llm judge — return `null` (callers skip the judgment tier and emit
162
+ * `triage_deferred`) and emit a `warn(...)`. Returns `null` when nothing is
163
+ * configured.
164
+ */
165
+ export function resolveTriageJudgmentRunner(judgment, config) {
166
+ return resolveProcessRunnerWithLlmFallback(judgment, config, {
167
+ fallbackTimeoutMs: judgment?.timeoutMs,
168
+ suppressLlmFallbackForExplicitMode: true,
169
+ });
170
+ }
171
+ export function resolveRunner(mode, profileName, config) {
172
+ if (mode === "llm")
173
+ return buildLlmRunnerSpec(profileName, undefined, config);
174
+ return buildAgentRunnerSpec(mode, profileName, undefined, config);
175
+ }
176
+ /**
177
+ * Resolve a RunnerSpec from an improve-profile process entry. Returns `null`
178
+ * when the entry is absent or provides no overrides — callers should fall
179
+ * back to the default per-process runner resolution path.
180
+ */
181
+ export function resolveImproveProcessRunnerFromProfile(processConfig, config) {
182
+ if (!processConfig)
183
+ return null;
184
+ const { mode: explicitMode, profile, timeoutMs } = processConfig;
185
+ if (!explicitMode && !profile)
186
+ return null;
187
+ const mode = explicitMode ?? resolveEffectiveMode(processConfig, profile, config);
188
+ const profileName = profile ?? resolveProfileName(processConfig, mode, config);
189
+ if (mode === "llm") {
190
+ return buildLlmRunnerSpec(profileName, timeoutMs, config);
191
+ }
192
+ if (mode === "agent" || mode === "sdk") {
193
+ return buildAgentRunnerSpec(mode, profileName, timeoutMs, config);
194
+ }
195
+ return null;
196
+ }
197
+ /**
198
+ * Convenience accessor for callers that previously read
199
+ * `getProcessOptions("index", "staleness_detection", config).thresholdDays`.
200
+ * After the 0.8.0 migration, those values live on first-class config keys —
201
+ * see `config.index?.stalenessDetection?.thresholdDays` etc.
202
+ */
203
+ export function getStalenessDetectionThresholdDays(config) {
204
+ return config.index?.stalenessDetection?.thresholdDays;
205
+ }
206
+ // Re-export `isProcessEnabled` from feature-gate.ts so callers that previously
207
+ // imported it from runner.ts continue to work.
208
+ export { isProcessEnabled } from "../../llm/feature-gate";
@@ -1,8 +1,12 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  /**
2
5
  * OpenCode SDK agent runner — uses embedded @opencode-ai/sdk instead of
3
6
  * Bun.spawn. Requires no agent CLI binary to be installed. The user provides
4
7
  * an OpenAI-compatible endpoint (or inherits from config.llm) for the SDK.
5
8
  */
9
+ import { resolveSecret } from "../../core/config";
6
10
  // Singleton server — started once per process, reused across calls
7
11
  let _server = null;
8
12
  /**
@@ -26,7 +30,7 @@ async function getOrStartServer(profile, llmConfig) {
26
30
  });
27
31
  // Resolve endpoint and model: profile fields take precedence over config.llm
28
32
  const endpoint = profile.endpoint ?? llmConfig?.endpoint;
29
- const apiKey = profile.apiKey ?? llmConfig?.apiKey;
33
+ const apiKey = resolveSecret(profile.apiKey ?? llmConfig?.apiKey);
30
34
  const model = profile.model;
31
35
  const sdkConfig = {};
32
36
  if (model)
@@ -55,7 +59,7 @@ async function getOrStartServer(profile, llmConfig) {
55
59
  throw new Error("Failed to initialise OpenCode SDK server.");
56
60
  return _server;
57
61
  }
58
- export async function runAgentSdk(profile, prompt, _opts = {}, llmConfig) {
62
+ export async function runOpencodeSdk(profile, prompt, _opts = {}, llmConfig) {
59
63
  const start = Date.now();
60
64
  let client;
61
65
  try {
@@ -118,3 +122,5 @@ export async function runAgentSdk(profile, prompt, _opts = {}, llmConfig) {
118
122
  await client.session.delete({ path: { id: sessionId } }).catch(() => { });
119
123
  }
120
124
  }
125
+ /** @deprecated Use {@link runOpencodeSdk} instead. */
126
+ export const runAgentSdk = runOpencodeSdk;
@@ -1,3 +1,6 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  /**
2
5
  * Agent CLI spawn wrapper (v1 spec §12.2).
3
6
  *
@@ -14,6 +17,7 @@
14
17
  import fs from "node:fs";
15
18
  import os from "node:os";
16
19
  import path from "node:path";
20
+ import { getCommandBuilder } from "./builders";
17
21
  import { DEFAULT_AGENT_TIMEOUT_MS } from "./config";
18
22
  /**
19
23
  * Kill the process group of `proc` with `signal`, falling back to
@@ -24,7 +28,7 @@ import { DEFAULT_AGENT_TIMEOUT_MS } from "./config";
24
28
  * reaped alongside the node wrapper. The fallback keeps test fakes working
25
29
  * without modification.
26
30
  */
27
- export function killGroup(proc, signal) {
31
+ function killGroup(proc, signal) {
28
32
  if (typeof proc.pid === "number") {
29
33
  try {
30
34
  process.kill(-proc.pid, signal);
@@ -61,7 +65,33 @@ export function supplementPathForSchedulerContext(existingPath) {
61
65
  if (existingPath.split(path.delimiter).some((d) => d.startsWith(home))) {
62
66
  return existingPath;
63
67
  }
64
- const candidates = [
68
+ const candidates = pathCandidatesForCurrentPlatform(home);
69
+ const existing = new Set(existingPath.split(path.delimiter).filter(Boolean));
70
+ const toAdd = candidates.filter((d) => !existing.has(d) && fs.existsSync(d));
71
+ if (toAdd.length === 0)
72
+ return existingPath;
73
+ return [...toAdd, existingPath].filter(Boolean).join(path.delimiter);
74
+ }
75
+ function pathCandidatesForCurrentPlatform(home) {
76
+ if (process.platform === "win32") {
77
+ // Windows: Bun + Cargo + Scoop + Chocolatey + system tools. Order favors
78
+ // user-local installs over machine-global so the user's chosen toolchain
79
+ // wins. These paths are commonly stripped from Task Scheduler / service
80
+ // environments, mirroring the cron/launchd problem on POSIX.
81
+ const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
82
+ const userProfile = process.env.USERPROFILE ?? home;
83
+ const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
84
+ return [
85
+ path.join(userProfile, ".bun", "bin"),
86
+ path.join(localAppData, "Programs", "bun"),
87
+ path.join(userProfile, ".cargo", "bin"),
88
+ path.join(localAppData, "Programs", "Git", "cmd"),
89
+ path.join(userProfile, "scoop", "shims"),
90
+ path.join(programFiles, "Git", "cmd"),
91
+ "C:\\ProgramData\\chocolatey\\bin",
92
+ ];
93
+ }
94
+ return [
65
95
  path.join(home, ".bun", "bin"),
66
96
  path.join(home, ".cargo", "bin"),
67
97
  path.join(home, ".local", "bin"),
@@ -69,11 +99,6 @@ export function supplementPathForSchedulerContext(existingPath) {
69
99
  "/opt/homebrew/sbin",
70
100
  "/usr/local/bin",
71
101
  ];
72
- const existing = new Set(existingPath.split(path.delimiter).filter(Boolean));
73
- const toAdd = candidates.filter((d) => !existing.has(d) && fs.existsSync(d));
74
- if (toAdd.length === 0)
75
- return existingPath;
76
- return [...toAdd, existingPath].filter(Boolean).join(path.delimiter);
77
102
  }
78
103
  function resolveSpawnFn(options) {
79
104
  if (options.spawn)
@@ -159,15 +184,30 @@ export async function runAgent(profile, prompt, options = {}) {
159
184
  const parseOutput = options.parseOutput ?? profile.parseOutput;
160
185
  const setTimeoutImpl = options.setTimeoutFn ?? setTimeout;
161
186
  const clearTimeoutImpl = options.clearTimeoutFn ?? clearTimeout;
162
- const args = [...profile.args, ...(options.args ?? [])];
163
- if (prompt !== undefined)
164
- args.push(prompt);
165
- const env = buildChildEnv(profile, options);
187
+ // Build argv via the platform-specific builder when dispatch params are
188
+ // provided; fall back to the legacy positional-prompt form otherwise.
189
+ let builtArgv;
190
+ let builtEnv;
191
+ if (options.dispatch !== undefined) {
192
+ const builder = getCommandBuilder(profile.commandBuilder ?? profile.name, options.builderRegistry);
193
+ const built = builder.build(profile, options.dispatch);
194
+ builtArgv = built.argv;
195
+ builtEnv = built.env;
196
+ }
197
+ else {
198
+ const legacyArgs = [...profile.args, ...(options.args ?? [])];
199
+ if (prompt !== undefined)
200
+ legacyArgs.push(prompt);
201
+ builtArgv = [profile.bin, ...legacyArgs];
202
+ }
203
+ // Extra args (e.g. forwarded CLI positionals) are appended after the builder output.
204
+ const finalArgv = [...builtArgv, ...(options.dispatch ? (options.args ?? []) : [])];
205
+ const env = { ...buildChildEnv(profile, options), ...(builtEnv ?? {}) };
166
206
  const start = Date.now();
167
207
  let proc;
168
208
  try {
169
209
  const spawnFn = resolveSpawnFn(options);
170
- proc = spawnFn([profile.bin, ...args], {
210
+ proc = spawnFn(finalArgv, {
171
211
  stdin: stdioMode === "captured" ? (options.stdin !== undefined ? "pipe" : "ignore") : "inherit",
172
212
  stdout: stdioMode === "captured" ? "pipe" : "inherit",
173
213
  stderr: stdioMode === "captured" ? "pipe" : "inherit",
@@ -207,13 +247,13 @@ export async function runAgent(profile, prompt, options = {}) {
207
247
  let timer;
208
248
  if (timeoutMs !== null) {
209
249
  timer = setTimeoutImpl(() => {
210
- if (proc.exitCode !== null)
250
+ if (!proc || proc.exitCode !== null)
211
251
  return;
212
252
  timedOut = true;
213
253
  killGroup(proc, "SIGTERM");
214
254
  // Follow up with SIGKILL after 5 s in case the process ignores SIGTERM.
215
255
  setTimeoutImpl(() => {
216
- if (proc.exitCode !== null)
256
+ if (!proc || proc.exitCode !== null)
217
257
  return;
218
258
  killGroup(proc, "SIGKILL");
219
259
  }, 5000);
@@ -1,3 +1,6 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  import * as childProcess from "node:child_process";
2
5
  export const GITHUB_API_BASE = "https://api.github.com";
3
6
  const GITHUB_TOKEN_DOMAINS = new Set(["api.github.com", "github.com", "uploads.github.com"]);