akm-cli 0.8.6 → 0.8.14

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 (324) hide show
  1. package/CHANGELOG.md +442 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/tasks/core/backup.yml +4 -0
  9. package/dist/assets/tasks/core/extract.yml +4 -0
  10. package/dist/assets/tasks/core/improve.yml +4 -0
  11. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  12. package/dist/assets/tasks/core/sync.yml +4 -0
  13. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  14. package/dist/assets/tasks/core/version-check.yml +4 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +560 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/config-migrate.js +6 -6
  19. package/dist/cli/config-validate.js +4 -4
  20. package/dist/cli/confirm.js +3 -3
  21. package/dist/cli/parse-args.js +1 -1
  22. package/dist/cli/shared.js +72 -19
  23. package/dist/cli-node.mjs +26 -0
  24. package/dist/cli.js +206 -3866
  25. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  26. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  27. package/dist/commands/agent/contribute-cli.js +200 -0
  28. package/dist/commands/completions.js +1 -1
  29. package/dist/commands/config-cli.js +230 -3
  30. package/dist/commands/db-cli.js +2 -2
  31. package/dist/commands/env/env-cli.js +529 -0
  32. package/dist/commands/env/env.js +410 -0
  33. package/dist/commands/env/secret-cli.js +259 -0
  34. package/dist/commands/{secret.js → env/secret.js} +6 -47
  35. package/dist/commands/events.js +4 -4
  36. package/dist/commands/feedback-cli.js +18 -34
  37. package/dist/commands/graph/graph-cli.js +132 -0
  38. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  39. package/dist/commands/health/checks.js +279 -0
  40. package/dist/commands/health/html-report.js +448 -0
  41. package/dist/commands/health.js +189 -266
  42. package/dist/commands/{consolidate.js → improve/consolidate.js} +63 -38
  43. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  44. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  45. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  46. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  47. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  48. package/dist/commands/{extract.js → improve/extract.js} +221 -26
  49. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
  50. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  51. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  52. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  53. package/dist/commands/{improve.js → improve/improve.js} +672 -292
  54. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  55. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  56. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  57. package/dist/commands/improve/reflect-noise.js +0 -0
  58. package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
  59. package/dist/commands/improve/session-asset.js +248 -0
  60. package/dist/commands/lint/agent-linter.js +1 -1
  61. package/dist/commands/lint/base-linter.js +55 -37
  62. package/dist/commands/lint/command-linter.js +1 -1
  63. package/dist/commands/lint/default-linter.js +1 -1
  64. package/dist/commands/lint/env-key-rules.js +1 -1
  65. package/dist/commands/lint/index.js +19 -25
  66. package/dist/commands/lint/knowledge-linter.js +1 -1
  67. package/dist/commands/lint/memory-linter.js +1 -1
  68. package/dist/commands/lint/registry.js +8 -8
  69. package/dist/commands/lint/skill-linter.js +1 -1
  70. package/dist/commands/lint/task-linter.js +1 -1
  71. package/dist/commands/lint/workflow-linter.js +1 -1
  72. package/dist/commands/lint.js +1 -1
  73. package/dist/commands/observability-cli.js +244 -0
  74. package/dist/commands/proposal/drain-policies.js +3 -3
  75. package/dist/commands/proposal/drain.js +87 -15
  76. package/dist/commands/proposal/proposal-cli.js +490 -0
  77. package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
  78. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  79. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  80. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  81. package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
  82. package/dist/commands/{curate.js → read/curate.js} +7 -7
  83. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  84. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  85. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  86. package/dist/commands/read/search-cli.js +207 -0
  87. package/dist/commands/{search.js → read/search.js} +22 -27
  88. package/dist/commands/{show.js → read/show.js} +31 -45
  89. package/dist/commands/registry-cli.js +8 -8
  90. package/dist/commands/remember.js +14 -10
  91. package/dist/commands/sources/add-cli.js +293 -0
  92. package/dist/commands/{history.js → sources/history.js} +27 -25
  93. package/dist/commands/{info.js → sources/info.js} +6 -6
  94. package/dist/commands/{init.js → sources/init.js} +6 -6
  95. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  96. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  97. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  98. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  99. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  100. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  101. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  102. package/dist/commands/sources/sources-cli.js +305 -0
  103. package/dist/commands/sources/stash-cli.js +219 -0
  104. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  105. package/dist/commands/tasks/default-tasks.js +173 -0
  106. package/dist/commands/tasks/tasks-cli.js +210 -0
  107. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  108. package/dist/commands/wiki-cli.js +307 -0
  109. package/dist/commands/workflow-cli.js +329 -0
  110. package/dist/core/action-contributors.js +1 -1
  111. package/dist/core/assert.js +40 -0
  112. package/dist/core/asset/asset-create.js +54 -0
  113. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  114. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  115. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  116. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  117. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  118. package/dist/core/best-effort.js +64 -0
  119. package/dist/core/common.js +32 -18
  120. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  121. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  122. package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
  123. package/dist/core/config/config-types.js +16 -0
  124. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  125. package/dist/core/{config.js → config/config.js} +10 -8
  126. package/dist/core/env-secret-ref.js +90 -0
  127. package/dist/core/errors.js +13 -3
  128. package/dist/core/events.js +27 -4
  129. package/dist/core/file-lock.js +1 -1
  130. package/dist/core/improve-types.js +48 -0
  131. package/dist/core/lesson-lint.js +2 -2
  132. package/dist/core/logs-db.js +304 -0
  133. package/dist/core/paths.js +2 -2
  134. package/dist/core/ripgrep/install.js +2 -2
  135. package/dist/core/ripgrep/resolve.js +2 -2
  136. package/dist/core/state-db.js +195 -60
  137. package/dist/core/text-truncation.js +148 -0
  138. package/dist/core/time.js +1 -1
  139. package/dist/core/write-source.js +98 -85
  140. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  141. package/dist/indexer/{db.js → db/db.js} +128 -118
  142. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  143. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  144. package/dist/indexer/ensure-index.js +4 -4
  145. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  146. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  147. package/dist/indexer/indexer.js +37 -30
  148. package/dist/indexer/init.js +54 -0
  149. package/dist/indexer/manifest.js +10 -10
  150. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
  151. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  152. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  153. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  154. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  155. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  156. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  157. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  158. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  159. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  160. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  161. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  162. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  163. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  164. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  165. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  166. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  167. package/dist/integrations/agent/builder-shared.js +39 -0
  168. package/dist/integrations/agent/builders.js +14 -81
  169. package/dist/integrations/agent/config.js +6 -4
  170. package/dist/integrations/agent/detect.js +1 -1
  171. package/dist/integrations/agent/index.js +23 -8
  172. package/dist/integrations/agent/prompts.js +2 -3
  173. package/dist/integrations/agent/runner.js +22 -3
  174. package/dist/integrations/agent/spawn.js +9 -10
  175. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  176. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  177. package/dist/integrations/harnesses/claude/index.js +64 -0
  178. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
  179. package/dist/integrations/harnesses/index.js +144 -0
  180. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  181. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  182. package/dist/integrations/harnesses/opencode/index.js +59 -0
  183. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  184. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  185. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  186. package/dist/integrations/harnesses/types.js +43 -0
  187. package/dist/integrations/lockfile.js +7 -16
  188. package/dist/integrations/session-logs/index.js +82 -9
  189. package/dist/llm/call-ai.js +4 -4
  190. package/dist/llm/client.js +146 -6
  191. package/dist/llm/embedder.js +6 -6
  192. package/dist/llm/embedders/local.js +9 -22
  193. package/dist/llm/embedders/remote.js +2 -2
  194. package/dist/llm/embedders/types.js +1 -1
  195. package/dist/llm/graph-extract.js +31 -12
  196. package/dist/llm/index-passes.js +1 -1
  197. package/dist/llm/memory-infer.js +12 -5
  198. package/dist/llm/metadata-enhance.js +2 -2
  199. package/dist/llm/usage-persist.js +77 -0
  200. package/dist/llm/usage-telemetry.js +103 -0
  201. package/dist/output/context.js +9 -46
  202. package/dist/output/html-render.js +73 -0
  203. package/dist/output/renderers.js +88 -58
  204. package/dist/output/shapes/curate.js +7 -3
  205. package/dist/output/shapes/distill.js +7 -3
  206. package/dist/output/shapes/env-list.js +18 -16
  207. package/dist/output/shapes/events.js +5 -4
  208. package/dist/output/shapes/helpers.js +19 -5
  209. package/dist/output/shapes/history.js +7 -3
  210. package/dist/output/shapes/passthrough.js +8 -11
  211. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  212. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  213. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  214. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  215. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  216. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  217. package/dist/output/shapes/registry-search.js +7 -3
  218. package/dist/output/shapes/registry.js +12 -0
  219. package/dist/output/shapes/search.js +7 -3
  220. package/dist/output/shapes/secret-list.js +18 -16
  221. package/dist/output/shapes/show.js +7 -3
  222. package/dist/output/shapes.js +55 -30
  223. package/dist/output/text/add.js +2 -3
  224. package/dist/output/text/clone.js +2 -3
  225. package/dist/output/text/config.js +2 -3
  226. package/dist/output/text/curate.js +4 -3
  227. package/dist/output/text/distill.js +2 -3
  228. package/dist/output/text/enable-disable.js +5 -4
  229. package/dist/output/text/env.js +13 -0
  230. package/dist/output/text/events.js +5 -4
  231. package/dist/output/text/feedback.js +4 -3
  232. package/dist/output/text/helpers.js +123 -40
  233. package/dist/output/text/history.js +2 -3
  234. package/dist/output/text/import.js +2 -3
  235. package/dist/output/text/index.js +2 -3
  236. package/dist/output/text/info.js +2 -3
  237. package/dist/output/text/init.js +2 -3
  238. package/dist/output/text/list.js +2 -3
  239. package/dist/output/text/proposal/producer.js +9 -0
  240. package/dist/output/text/proposal/proposal.js +13 -0
  241. package/dist/output/text/registry-commands.js +8 -7
  242. package/dist/output/text/registry.js +12 -0
  243. package/dist/output/text/remember.js +4 -3
  244. package/dist/output/text/remove.js +2 -3
  245. package/dist/output/text/save.js +2 -3
  246. package/dist/output/text/search.js +4 -3
  247. package/dist/output/text/show.js +4 -3
  248. package/dist/output/text/update.js +2 -3
  249. package/dist/output/text/upgrade.js +2 -3
  250. package/dist/output/text/wiki.js +12 -11
  251. package/dist/output/text/workflow.js +12 -10
  252. package/dist/output/text.js +66 -32
  253. package/dist/registry/build-index.js +11 -10
  254. package/dist/registry/factory.js +1 -1
  255. package/dist/registry/origin-resolve.js +1 -1
  256. package/dist/registry/providers/index.js +2 -2
  257. package/dist/registry/providers/skills-sh.js +91 -72
  258. package/dist/registry/providers/static-index.js +75 -52
  259. package/dist/registry/resolve.js +3 -3
  260. package/dist/runtime.js +242 -0
  261. package/dist/scripts/migrate-storage.js +1654 -683
  262. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
  263. package/dist/setup/detect.js +311 -9
  264. package/dist/setup/harness-config-import.js +6 -120
  265. package/dist/setup/setup.js +454 -43
  266. package/dist/sources/include.js +1 -1
  267. package/dist/sources/provider-factory.js +2 -2
  268. package/dist/sources/providers/filesystem.js +3 -3
  269. package/dist/sources/providers/git.js +9 -9
  270. package/dist/sources/providers/index.js +4 -4
  271. package/dist/sources/providers/npm.js +6 -6
  272. package/dist/sources/providers/provider-utils.js +13 -20
  273. package/dist/sources/providers/sync-from-ref.js +5 -5
  274. package/dist/sources/providers/tar-utils.js +2 -2
  275. package/dist/sources/providers/website.js +2 -2
  276. package/dist/sources/resolve.js +5 -5
  277. package/dist/sources/website-ingest.js +5 -5
  278. package/dist/storage/database.js +102 -0
  279. package/dist/storage/engines/sqlite-migrations.js +42 -0
  280. package/dist/storage/locations.js +25 -0
  281. package/dist/storage/repositories/index-db.js +43 -0
  282. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  283. package/dist/tasks/backends/cron.js +4 -4
  284. package/dist/tasks/backends/exec-utils.js +32 -0
  285. package/dist/tasks/backends/index.js +3 -3
  286. package/dist/tasks/backends/launchd.js +7 -14
  287. package/dist/tasks/backends/schtasks.js +7 -16
  288. package/dist/tasks/embedded.js +71 -0
  289. package/dist/tasks/parser.js +2 -2
  290. package/dist/tasks/resolveAkmBin.js +1 -1
  291. package/dist/tasks/runner.js +127 -31
  292. package/dist/tasks/schedule.js +1 -1
  293. package/dist/tasks/validator.js +7 -7
  294. package/dist/text-import-hook.mjs +51 -0
  295. package/dist/version.js +2 -1
  296. package/dist/wiki/wiki.js +7 -7
  297. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  298. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  299. package/dist/workflows/cli.js +1 -1
  300. package/dist/workflows/db.js +54 -32
  301. package/dist/workflows/parser.js +4 -4
  302. package/dist/workflows/renderer.js +5 -5
  303. package/dist/workflows/runtime/agent-identity.js +56 -0
  304. package/dist/workflows/runtime/checkin.js +57 -0
  305. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  306. package/dist/workflows/validate-summary.js +82 -0
  307. package/docs/README.md +1 -1
  308. package/docs/data-and-telemetry.md +6 -6
  309. package/package.json +17 -8
  310. package/dist/commands/add-cli.js +0 -279
  311. package/dist/commands/env.js +0 -213
  312. package/dist/integrations/agent/sdk-runner.js +0 -126
  313. package/dist/output/shapes/vault-list.js +0 -19
  314. package/dist/output/text/proposal-producer.js +0 -8
  315. package/dist/output/text/proposal.js +0 -12
  316. package/dist/output/text/vault.js +0 -16
  317. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  318. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  319. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  320. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  321. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  322. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  323. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  324. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -0,0 +1,144 @@
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
+ /**
5
+ * Single registration point for every akm agent harness (#562).
6
+ *
7
+ * `HARNESS_REGISTRY` is the ONE source of truth that replaces the three
8
+ * previously-disconnected registries (session-logs index, agent profiles,
9
+ * config/setup platform strings). All derived exports below are computed from
10
+ * it, so adding a harness is a single entry here instead of ~16 scattered edits.
11
+ *
12
+ * This module is a dependency-graph LEAF: it imports nothing from
13
+ * `core/config`. `core/config/config-types.ts` derives `VALID_HARNESS_IDS`
14
+ * from here, which keeps the import direction acyclic (config ← harnesses).
15
+ *
16
+ * Implementations (session-log readers, agent profiles) are migrated under each
17
+ * harness in #563/#564; this step only owns ids + capability membership and
18
+ * wires the existing call sites to consult it (behaviour-preserving).
19
+ */
20
+ import { ClaudeHarness } from "./claude/index.js";
21
+ import { OpencodeHarness } from "./opencode/index.js";
22
+ import { OpencodeSdkHarness } from "./opencode-sdk/index.js";
23
+ // Each harness descriptor (ClaudeHarness, OpencodeHarness, OpencodeSdkHarness)
24
+ // lives in its own per-harness directory alongside that harness's session-log
25
+ // reader, agent builder, config importer, and/or SDK runner (#563/#564). They
26
+ // are imported above and registered in HARNESS_REGISTRY below.
27
+ /**
28
+ * The single registration point. Add a harness here (and nothing else) to make
29
+ * every derived registry pick it up.
30
+ *
31
+ * Typed `as const` (not `AkmHarness[]`) so each entry keeps its literal `id`,
32
+ * which lets `VALID_HARNESS_IDS` stay a literal tuple — `HarnessId`,
33
+ * `z.enum(...)`, and the `platform` union all need the literal types.
34
+ */
35
+ // Order is significant: VALID_HARNESS_IDS derives from this array and feeds the
36
+ // committed JSON-schema enum order. Kept as [opencode, claude, opencode-sdk] to
37
+ // match the pre-unification VALID_HARNESS_IDS so the generated schema does not
38
+ // drift (behaviour-preserving, #562).
39
+ export const HARNESS_REGISTRY = Object.freeze([
40
+ new OpencodeHarness(),
41
+ new ClaudeHarness(),
42
+ new OpencodeSdkHarness(),
43
+ ]);
44
+ /** Lookup by canonical id. */
45
+ export const HARNESS_BY_ID = new Map(HARNESS_REGISTRY.map((h) => [h.id, h]));
46
+ /**
47
+ * Lookup by canonical id OR any alias OR runtime id — the normalization bridge
48
+ * resolver. Both 'claude' and 'claude-code' map to the Claude harness.
49
+ */
50
+ const HARNESS_BY_ANY_ID = (() => {
51
+ const m = new Map();
52
+ for (const h of HARNESS_REGISTRY) {
53
+ m.set(h.id, h);
54
+ if (h.runtimeId)
55
+ m.set(h.runtimeId, h);
56
+ for (const a of h.aliases)
57
+ m.set(a, h);
58
+ }
59
+ return m;
60
+ })();
61
+ /**
62
+ * Canonical, ordered list of valid harness / platform ids. The Zod
63
+ * `AgentPlatformSchema` enum, the `AgentProfileConfigV2` platform union,
64
+ * `parseAgentProfilesMapV2`'s membership check, and setup's `DetectedHarness`
65
+ * union all derive from this so they cannot drift.
66
+ */
67
+ export const VALID_HARNESS_IDS = Object.freeze(HARNESS_REGISTRY.map((h) => h.id));
68
+ /** Harnesses that expose readable native session logs. */
69
+ export const SESSION_LOG_HARNESSES = HARNESS_REGISTRY.filter((h) => h.capabilities.sessionLogs);
70
+ /** Harnesses that can be dispatched as an agent CLI / SDK. */
71
+ export const AGENT_DISPATCH_HARNESSES = HARNESS_REGISTRY.filter((h) => h.capabilities.agentDispatch);
72
+ /** Harnesses that can import an existing harness config into akm. */
73
+ export const CONFIG_IMPORTER_HARNESSES = HARNESS_REGISTRY.filter((h) => h.capabilities.configImport);
74
+ /** Harnesses that participate in `akm setup` detection. */
75
+ export const DETECTION_HARNESSES = HARNESS_REGISTRY.filter((h) => h.capabilities.detection);
76
+ /**
77
+ * Resolve any id form (canonical id, alias, or runtime id) to the harness
78
+ * descriptor, or `undefined` if unknown.
79
+ */
80
+ export function getHarness(id) {
81
+ return HARNESS_BY_ANY_ID.get(id);
82
+ }
83
+ /**
84
+ * id normalization bridge — alias → canonical.
85
+ *
86
+ * Maps any known alias/runtime id to its canonical persisted id (e.g.
87
+ * 'claude-code' → 'claude'). Unknown ids pass through unchanged so callers can
88
+ * still validate them. This is what keeps existing persisted configs that say
89
+ * 'claude-code' working against the canonical 'claude' registry.
90
+ */
91
+ export function normalizeHarnessId(id) {
92
+ return HARNESS_BY_ANY_ID.get(id)?.id ?? id;
93
+ }
94
+ /**
95
+ * Resolve a legacy v1 agent-profile name to its v2 platform id (#566).
96
+ *
97
+ * v1 agent profiles never carried an explicit `platform`; it had to be inferred
98
+ * from the profile name. This is the SINGLE registry-backed resolver that
99
+ * replaces both the old standalone `guessAgentPlatform()` in config-migration
100
+ * and the `name.includes("claude") ? "claude" : "opencode"` heuristic in setup.
101
+ * Each harness owns its own `matchesV1ProfileName()`; an unknown name matches no
102
+ * harness and returns `undefined`, so the caller drops it instead of silently
103
+ * defaulting to `'opencode'`.
104
+ *
105
+ * Harnesses are consulted most-specific-id-first (longest id wins) so a
106
+ * decorated name like `"opencode-sdk-fast"` resolves to `'opencode-sdk'` rather
107
+ * than being over-matched by OpenCode's `"opencode"` prefix.
108
+ */
109
+ const V1_RESOLUTION_ORDER = [...HARNESS_REGISTRY].sort((a, b) => b.id.length - a.id.length);
110
+ export function v1ProfilePlatform(name) {
111
+ for (const h of V1_RESOLUTION_ORDER) {
112
+ if (h.matchesV1ProfileName(name))
113
+ return h.id;
114
+ }
115
+ return undefined;
116
+ }
117
+ /**
118
+ * Default agent-profile name for a detected harness id (#566).
119
+ *
120
+ * Used by `akm setup`'s headless/recommended-config path so a newly added
121
+ * dispatch-capable harness gets a usable default profile name (its canonical
122
+ * id) instead of falling through a hardcoded if-chain with no default. Returns
123
+ * `undefined` for `"none"` or any id that is unknown / not agent-dispatch
124
+ * capable.
125
+ */
126
+ export function defaultProfileName(detected) {
127
+ const h = HARNESS_BY_ANY_ID.get(detected);
128
+ if (!h?.capabilities.agentDispatch)
129
+ return undefined;
130
+ return h.id;
131
+ }
132
+ /**
133
+ * id normalization bridge — canonical → runtime identity.
134
+ *
135
+ * Maps a canonical id to the string a harness reports at runtime / in its
136
+ * session logs (e.g. 'claude' → 'claude-code'). Used by workflow run
137
+ * attribution and the session-logs provider name so the persisted runtime
138
+ * identity stays stable. Falls back to the canonical id when the harness has no
139
+ * distinct runtime identity.
140
+ */
141
+ export function denormalizeRuntimeIdentity(id) {
142
+ const h = HARNESS_BY_ANY_ID.get(id);
143
+ return h?.runtimeId ?? h?.id ?? id;
144
+ }
@@ -0,0 +1,43 @@
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
+ /**
5
+ * OpenCode agent command builder (migrated from `agent/builders.ts`, #564).
6
+ *
7
+ * Translates a platform-agnostic {@link AgentDispatchRequest} into the exact
8
+ * argv the `opencode` CLI expects. This is the OpenCode-specific slice of the
9
+ * builder strategy; the shared infrastructure (`AgentCommandBuilder`,
10
+ * `getCommandBuilder`, the default builder, flag/tool helpers) stays in
11
+ * `agent/builders.ts`, which imports this builder back into `BUILTIN_BUILDERS`.
12
+ *
13
+ * Behaviour-preserving relocation: the produced argv is byte-identical to the
14
+ * pre-migration `opencodeBuilder`. The builder's `platform` stays `'opencode'`
15
+ * (the canonical harness id).
16
+ */
17
+ import { assertNotFlag } from "../../agent/builder-shared.js";
18
+ import { resolveModel } from "../../agent/model-aliases.js";
19
+ /**
20
+ * OpenCode builder.
21
+ * Command shape: opencode run [--system-prompt "..."] [--model <m>] "<prompt>"
22
+ *
23
+ * Tool policy is omitted — opencode manages tool access through its own agent
24
+ * config files, not via CLI flags.
25
+ */
26
+ export const opencodeBuilder = {
27
+ platform: "opencode",
28
+ build(profile, req) {
29
+ assertNotFlag(req.systemPrompt, "systemPrompt");
30
+ assertNotFlag(req.model, "model");
31
+ const args = [...profile.args]; // starts with ["run"]
32
+ if (req.systemPrompt) {
33
+ args.push("--system-prompt", req.systemPrompt);
34
+ }
35
+ if (req.model) {
36
+ const resolved = resolveModel(req.model, "opencode", profile.modelAliases);
37
+ args.push("--model", resolved);
38
+ }
39
+ args.push("--");
40
+ args.push(req.prompt);
41
+ return { argv: [profile.bin, ...args] };
42
+ },
43
+ };
@@ -0,0 +1,82 @@
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
+ /**
5
+ * OpenCode LLM-config importer (migrated from `setup/harness-config-import.ts`,
6
+ * #564).
7
+ *
8
+ * Detects an OpenCode installation (filesystem only, no network) and reads its
9
+ * config to extract LLM connection details. API key VALUES are never stored —
10
+ * only the env var name that holds them. The pluggable registry
11
+ * (`HARNESS_CONFIG_IMPORTERS`) and the Claude importer stay in their respective
12
+ * modules; `setup/harness-config-import.ts` imports this importer back.
13
+ *
14
+ * Behaviour-preserving relocation.
15
+ */
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ function homeDir() {
19
+ return process.env.HOME ?? process.env.USERPROFILE ?? "";
20
+ }
21
+ /**
22
+ * Imports LLM config from an OpenCode installation.
23
+ *
24
+ * OpenCode stores config in `~/.config/opencode/config.json` or
25
+ * `~/.opencode/config.json`. Its schema has a `providers` array and a
26
+ * `model` field. API keys in providers appear as `$ENV_VAR_NAME` references.
27
+ */
28
+ export const openCodeImporter = {
29
+ harnessName: "OpenCode",
30
+ detect() {
31
+ const home = homeDir();
32
+ return fs.existsSync(path.join(home, ".config", "opencode")) || fs.existsSync(path.join(home, ".opencode"));
33
+ },
34
+ importConfig() {
35
+ const home = homeDir();
36
+ const candidates = [
37
+ path.join(home, ".config", "opencode", "config.json"),
38
+ path.join(home, ".opencode", "config.json"),
39
+ path.join(process.cwd(), ".opencode", "config.json"),
40
+ ];
41
+ for (const filePath of candidates) {
42
+ try {
43
+ if (!fs.existsSync(filePath))
44
+ continue;
45
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
46
+ // OpenCode config shape: { model?: string, providers?: Array<{id, apiKey, baseUrl, ...}> }
47
+ const model = typeof raw.model === "string" ? raw.model : undefined;
48
+ // Extract provider info from the first entry in the providers array
49
+ let provider;
50
+ let baseUrl;
51
+ let apiKeyEnvVar;
52
+ const providers = Array.isArray(raw.providers) ? raw.providers : [];
53
+ if (providers.length > 0) {
54
+ const first = providers[0];
55
+ provider = typeof first?.id === "string" ? first.id : undefined;
56
+ baseUrl =
57
+ typeof first?.baseUrl === "string"
58
+ ? first.baseUrl
59
+ : typeof first?.base_url === "string"
60
+ ? first.base_url
61
+ : undefined;
62
+ // apiKey is an env var reference like "$OPENAI_API_KEY" — extract the var name
63
+ const apiKeyVal = typeof first?.apiKey === "string" ? first.apiKey : "";
64
+ if (apiKeyVal.startsWith("$")) {
65
+ apiKeyEnvVar = apiKeyVal.slice(1);
66
+ }
67
+ }
68
+ return {
69
+ harnessName: "OpenCode",
70
+ provider,
71
+ model,
72
+ baseUrl,
73
+ apiKeyEnvVar,
74
+ };
75
+ }
76
+ catch {
77
+ // try next candidate
78
+ }
79
+ }
80
+ return null;
81
+ },
82
+ };
@@ -0,0 +1,59 @@
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
+ /**
5
+ * OpenCode harness (#564).
6
+ *
7
+ * Per-harness barrel that gathers the OpenCode integration surfaces previously
8
+ * scattered across the codebase:
9
+ * - session-log reader → ./session-log.ts (OpenCodeProvider)
10
+ * - agent command builder → ./agent-builder.ts (opencodeBuilder)
11
+ * - config importer → ./config-import.ts (openCodeImporter)
12
+ *
13
+ * It also defines {@link OpencodeHarness}, the {@link AkmHarness} descriptor
14
+ * that `HARNESS_REGISTRY` registers.
15
+ *
16
+ * id normalization: OpenCode's canonical id (`'opencode'`) is also its runtime
17
+ * identity and session-log provider name — there is no historical split (unlike
18
+ * Claude Code's 'claude' vs 'claude-code'), so no alias bridge is needed.
19
+ */
20
+ import { BaseHarness } from "../types.js";
21
+ export { opencodeBuilder } from "./agent-builder.js";
22
+ export { openCodeImporter } from "./config-import.js";
23
+ export { OpenCodeProvider } from "./session-log.js";
24
+ function caps(c) {
25
+ return {
26
+ sessionLogs: false,
27
+ agentDispatch: false,
28
+ detection: false,
29
+ configImport: false,
30
+ runtimeIdentity: false,
31
+ v1Migration: false,
32
+ ...c,
33
+ };
34
+ }
35
+ /**
36
+ * OpenCode.
37
+ *
38
+ * Canonical id is `'opencode'`; it has no distinct runtime identity or alias.
39
+ */
40
+ export class OpencodeHarness extends BaseHarness {
41
+ id = "opencode";
42
+ displayName = "OpenCode";
43
+ aliases = [];
44
+ // Home-relative config dir scanned by `akm setup` (#567). OpenCode has a
45
+ // session-log provider, so offering it as a stash source is functional.
46
+ setupDetectionDir = ".config/opencode";
47
+ // Decorated v1 profile names like "opencode-fast" still belong to OpenCode.
48
+ // `v1ProfilePlatform()` resolves most-specific-id-first, so "opencode-sdk-*"
49
+ // is claimed by OpencodeSdkHarness before this prefix can over-match it.
50
+ v1ProfilePrefixes = ["opencode"];
51
+ capabilities = caps({
52
+ sessionLogs: true,
53
+ agentDispatch: true,
54
+ detection: true,
55
+ configImport: true,
56
+ runtimeIdentity: true,
57
+ v1Migration: true,
58
+ });
59
+ }
@@ -4,7 +4,7 @@
4
4
  import fs from "node:fs";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
- import { extractInlineRefMentions } from "../inline-refs";
7
+ import { extractInlineRefMentions } from "../../session-logs/inline-refs.js";
8
8
  function getOpenCodeBaseDir() {
9
9
  if (process.platform === "darwin") {
10
10
  return path.join(os.homedir(), "Library", "Application Support", "opencode");
@@ -0,0 +1,49 @@
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
+ /**
5
+ * OpenCode SDK harness (#564).
6
+ *
7
+ * Per-harness barrel for the SDK-mode dispatch path:
8
+ * - agent runner → ./sdk-runner.ts (runOpencodeSdk / runAgentSdk)
9
+ *
10
+ * It also defines {@link OpencodeSdkHarness}, the {@link AkmHarness} descriptor
11
+ * that `HARNESS_REGISTRY` registers.
12
+ *
13
+ * Unlike the CLI harnesses, the SDK path has no native session logs of its own
14
+ * (`capabilities.sessionLogs = false`): it dispatches via the embedded
15
+ * `@opencode-ai/sdk` and surfaces output directly rather than writing platform
16
+ * session files. It is still detected at setup and migrated from v1 profile
17
+ * names. Canonical id is `'opencode-sdk'` with no alias.
18
+ */
19
+ import { BaseHarness } from "../types.js";
20
+ export { closeServer, runAgentSdk, runOpencodeSdk } from "./sdk-runner.js";
21
+ function caps(c) {
22
+ return {
23
+ sessionLogs: false,
24
+ agentDispatch: false,
25
+ detection: false,
26
+ configImport: false,
27
+ runtimeIdentity: false,
28
+ v1Migration: false,
29
+ ...c,
30
+ };
31
+ }
32
+ /**
33
+ * OpenCode SDK (embedded-SDK dispatch path).
34
+ *
35
+ * Dispatch-only: no native session logs, but detected at setup and migrated
36
+ * from v1 profile names.
37
+ */
38
+ export class OpencodeSdkHarness extends BaseHarness {
39
+ id = "opencode-sdk";
40
+ displayName = "OpenCode SDK";
41
+ aliases = [];
42
+ // Decorated v1 profile names like "opencode-sdk-fast" belong to the SDK path.
43
+ v1ProfilePrefixes = ["opencode-sdk"];
44
+ capabilities = caps({
45
+ agentDispatch: true,
46
+ detection: true,
47
+ v1Migration: true,
48
+ });
49
+ }
@@ -0,0 +1,234 @@
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
+ /**
5
+ * OpenCode SDK agent runner (migrated from `agent/sdk-runner.ts`, #564).
6
+ *
7
+ * Uses the embedded `@opencode-ai/sdk` instead of `Bun.spawn`. Requires no
8
+ * agent CLI binary to be installed. The user provides an OpenAI-compatible
9
+ * endpoint (or inherits from config.llm) for the SDK.
10
+ *
11
+ * This is the runtime surface of the {@link OpencodeSdkHarness} (`id =
12
+ * 'opencode-sdk'`). It is the dispatch path for `sdkMode` profiles; it exposes
13
+ * no native session logs of its own (`capabilities.sessionLogs = false`).
14
+ */
15
+ import { resolveSecret } from "../../../core/config/config.js";
16
+ import { DEFAULT_AGENT_TIMEOUT_MS } from "../../agent/config.js";
17
+ // Singleton server — started once per process, reused across calls
18
+ let _server = null;
19
+ /**
20
+ * Test-only seam: inject a fake {@link SdkServer} so `runOpencodeSdk` can be
21
+ * exercised without the real `@opencode-ai/sdk` (which would spin up a server).
22
+ * Pass `null` to clear. NOT part of the public runtime API — used only to
23
+ * assert the #564 bug fixes (systemPrompt/tools forwarding + timeout). The
24
+ * leading underscores mark it as internal.
25
+ */
26
+ export function __setTestServer(server) {
27
+ _server = server;
28
+ }
29
+ /**
30
+ * Close the singleton OpenCode SDK server and reset the handle.
31
+ * Primarily for use in tests to ensure clean teardown between test runs.
32
+ */
33
+ export function closeServer() {
34
+ try {
35
+ _server?.server.close();
36
+ }
37
+ catch {
38
+ /* ignore */
39
+ }
40
+ _server = null;
41
+ }
42
+ /**
43
+ * Convert an `AgentDispatchRequest.tools` policy into the SDK's tool-allowlist
44
+ * shape (`{ [toolName]: boolean }`).
45
+ *
46
+ * #564 bug fix (2): the tool list was previously dropped entirely. The CLI
47
+ * builder passes tools as a comma-separated `--allowedTools` flag; the SDK
48
+ * instead wants a per-tool boolean map. A list/comma-string of tool names is
49
+ * treated as an allowlist (each name → true). A structured policy object whose
50
+ * values are already booleans is forwarded as-is.
51
+ *
52
+ * Returns `undefined` when there is nothing to forward, so an absent policy
53
+ * leaves the SDK's own defaults untouched (behaviour-preserving for callers
54
+ * that pass no tools).
55
+ */
56
+ function toolsToSdkAllowlist(tools) {
57
+ if (tools === undefined || tools === null)
58
+ return undefined;
59
+ const names = [];
60
+ if (typeof tools === "string") {
61
+ names.push(...tools
62
+ .split(",")
63
+ .map((t) => t.trim())
64
+ .filter(Boolean));
65
+ }
66
+ else if (Array.isArray(tools)) {
67
+ for (const t of tools) {
68
+ if (typeof t === "string" && t.trim())
69
+ names.push(t.trim());
70
+ }
71
+ }
72
+ else if (typeof tools === "object") {
73
+ // Structured policy: forward boolean entries directly.
74
+ const out = {};
75
+ for (const [k, v] of Object.entries(tools)) {
76
+ if (typeof v === "boolean")
77
+ out[k] = v;
78
+ }
79
+ return Object.keys(out).length > 0 ? out : undefined;
80
+ }
81
+ if (names.length === 0)
82
+ return undefined;
83
+ const out = {};
84
+ for (const n of names)
85
+ out[n] = true;
86
+ return out;
87
+ }
88
+ async function getOrStartServer(profile, llmConfig) {
89
+ if (_server)
90
+ return _server;
91
+ const { createOpencode } = await import("@opencode-ai/sdk").catch(() => {
92
+ throw new Error("OpenCode SDK not available. Install @opencode-ai/sdk or configure a CLI agent instead.");
93
+ });
94
+ // Resolve endpoint and model: profile fields take precedence over config.llm
95
+ const endpoint = profile.endpoint ?? llmConfig?.endpoint;
96
+ const apiKey = resolveSecret(profile.apiKey ?? llmConfig?.apiKey);
97
+ const model = profile.model;
98
+ const sdkConfig = {};
99
+ if (model)
100
+ sdkConfig.model = model;
101
+ if (endpoint || apiKey) {
102
+ // Configure a custom OpenAI-compatible provider
103
+ sdkConfig.provider = {
104
+ "akm-custom": {
105
+ npm: "@ai-sdk/openai-compatible",
106
+ options: {
107
+ baseURL: endpoint?.replace(/\/chat\/completions$/, "").replace(/\/$/, ""),
108
+ ...(apiKey ? { apiKey } : {}),
109
+ },
110
+ },
111
+ };
112
+ // Use the custom provider's model if not already qualified
113
+ if (model && !model.includes("/")) {
114
+ sdkConfig.model = `akm-custom/${model}`;
115
+ }
116
+ }
117
+ _server = (await createOpencode(Object.keys(sdkConfig).length > 0 ? { config: sdkConfig } : {}));
118
+ process.once("exit", () => {
119
+ closeServer();
120
+ });
121
+ if (!_server)
122
+ throw new Error("Failed to initialise OpenCode SDK server.");
123
+ return _server;
124
+ }
125
+ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
126
+ const start = Date.now();
127
+ let client;
128
+ try {
129
+ ({ client } = await getOrStartServer(profile, llmConfig));
130
+ }
131
+ catch (e) {
132
+ return {
133
+ ok: false,
134
+ stdout: "",
135
+ stderr: String(e),
136
+ durationMs: Date.now() - start,
137
+ exitCode: 1,
138
+ reason: "spawn_failed",
139
+ error: String(e),
140
+ };
141
+ }
142
+ // One session per call — do NOT reuse (history accumulates, token costs grow)
143
+ const sessionRes = await client.session.create({ body: { title: "akm" } });
144
+ const sessionId = sessionRes.data?.id;
145
+ if (!sessionId) {
146
+ return {
147
+ ok: false,
148
+ stdout: "",
149
+ stderr: "Failed to create session",
150
+ durationMs: Date.now() - start,
151
+ exitCode: 1,
152
+ reason: "spawn_failed",
153
+ error: "Failed to create OpenCode session",
154
+ };
155
+ }
156
+ // #564 bug fixes (1) + (2): forward systemPrompt and tools from the abstract
157
+ // dispatch request. Both were previously accepted on AgentDispatchRequest but
158
+ // silently dropped on the SDK path, so SDK-mode dispatch ignored agent-asset
159
+ // system prompts and tool policies entirely (the CLI path honours both).
160
+ const dispatch = opts.dispatch;
161
+ const system = dispatch?.systemPrompt;
162
+ const tools = toolsToSdkAllowlist(dispatch?.tools);
163
+ const body = { parts: [{ type: "text", text: prompt }] };
164
+ if (system)
165
+ body.system = system;
166
+ if (tools)
167
+ body.tools = tools;
168
+ // #564 bug fix (3): enforce a hard timeout like the CLI path (runAgent).
169
+ // Previously runOpencodeSdk() awaited session.prompt() with no timeout, so a
170
+ // hung SDK call (e.g. a stalled local-model endpoint) blocked the caller
171
+ // indefinitely while the CLI path would have killed the process. We resolve
172
+ // the same budget runAgent uses (opts.timeoutMs override → profile.timeoutMs
173
+ // → DEFAULT_AGENT_TIMEOUT_MS) and race the prompt against it. null disables
174
+ // the timer (parity with runAgent's "no timeout" contract). There is no
175
+ // OS process to SIGTERM/SIGKILL here, so on timeout we best-effort delete the
176
+ // session (the SDK's equivalent of reaping the in-flight work) and return a
177
+ // structured `timeout` failure with the same reason vocabulary as the CLI.
178
+ const timeoutMs = opts.timeoutMs !== undefined ? opts.timeoutMs : (profile.timeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS);
179
+ const setTimeoutImpl = opts.setTimeoutFn ?? setTimeout;
180
+ const clearTimeoutImpl = opts.clearTimeoutFn ?? clearTimeout;
181
+ let timer;
182
+ const TIMED_OUT = Symbol("opencode-sdk-timeout");
183
+ try {
184
+ const promptPromise = client.session.prompt({ path: { id: sessionId }, body });
185
+ const result = timeoutMs === null
186
+ ? await promptPromise
187
+ : await Promise.race([
188
+ promptPromise,
189
+ new Promise((resolve) => {
190
+ timer = setTimeoutImpl(() => resolve(TIMED_OUT), timeoutMs);
191
+ }),
192
+ ]);
193
+ if (result === TIMED_OUT) {
194
+ return {
195
+ ok: false,
196
+ stdout: "",
197
+ stderr: "",
198
+ durationMs: Date.now() - start,
199
+ exitCode: null,
200
+ reason: "timeout",
201
+ error: `opencode-sdk agent "${profile.name}" timed out after ${timeoutMs}ms`,
202
+ };
203
+ }
204
+ const parts = result.data?.parts ?? [];
205
+ const textPart = parts.find((p) => p.type === "text");
206
+ const stdout = textPart?.text ?? "";
207
+ return {
208
+ ok: true,
209
+ stdout,
210
+ stderr: "",
211
+ durationMs: Date.now() - start,
212
+ exitCode: 0,
213
+ };
214
+ }
215
+ catch (e) {
216
+ return {
217
+ ok: false,
218
+ stdout: "",
219
+ stderr: String(e),
220
+ durationMs: Date.now() - start,
221
+ exitCode: 1,
222
+ reason: "non_zero_exit",
223
+ error: String(e),
224
+ };
225
+ }
226
+ finally {
227
+ if (timer !== undefined)
228
+ clearTimeoutImpl(timer);
229
+ // Clean up session to prevent disk accumulation in ~/.local/share/opencode/
230
+ await client.session.delete({ path: { id: sessionId } }).catch(() => { });
231
+ }
232
+ }
233
+ /** @deprecated Use {@link runOpencodeSdk} instead. */
234
+ export const runAgentSdk = runOpencodeSdk;
@@ -0,0 +1,43 @@
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
+ /**
5
+ * Shared base for harness descriptors (#566).
6
+ *
7
+ * Provides the default v1-profile-name matcher so the inference logic lives in
8
+ * ONE place instead of being duplicated across the old `guessAgentPlatform()`
9
+ * (config-migration) and the `name.includes("claude")` heuristic (setup). A
10
+ * concrete harness sets `id`/`aliases`/`capabilities` and, when its v1 profile
11
+ * names could be decorated (e.g. `"opencode-sdk-fast"`), `v1ProfilePrefixes`.
12
+ */
13
+ export class BaseHarness {
14
+ runtimeId;
15
+ setupDetectionDir;
16
+ /**
17
+ * Lowercase prefixes that a decorated v1 profile name may start with and
18
+ * still belong to this harness (e.g. `["opencode-sdk"]`). The canonical id
19
+ * and aliases are always matched in addition to these; an empty list means
20
+ * only exact id/alias matches.
21
+ */
22
+ v1ProfilePrefixes = [];
23
+ matchesV1ProfileName(name) {
24
+ // Only harnesses with a v1→v2 mapping participate; others never claim a
25
+ // legacy profile name (so unknown names are dropped, not misclassified).
26
+ if (!this.capabilities.v1Migration)
27
+ return false;
28
+ const lower = name.trim().toLowerCase();
29
+ if (!lower)
30
+ return false;
31
+ if (lower === this.id.toLowerCase())
32
+ return true;
33
+ for (const alias of this.aliases) {
34
+ if (lower === alias.toLowerCase())
35
+ return true;
36
+ }
37
+ for (const prefix of this.v1ProfilePrefixes) {
38
+ if (lower.startsWith(prefix.toLowerCase()))
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+ }