akm-cli 0.9.31 → 0.10.261001-beta

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 (270) hide show
  1. package/CHANGELOG.md +409 -0
  2. package/STABILITY.md +99 -57
  3. package/dist/api-node.mjs +20 -0
  4. package/dist/api.d.ts +23 -0
  5. package/dist/api.js +45 -0
  6. package/dist/assets/hints/cli-hints-full.md +15 -29
  7. package/dist/assets/hints/cli-hints-short.md +2 -2
  8. package/dist/assets/improve-strategies/default.json +3 -3
  9. package/dist/assets/prompts/consolidate-pair.md +9 -7
  10. package/dist/assets/stash-skeleton/README.md +1 -1
  11. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
  12. package/dist/cli/shared.js +31 -48
  13. package/dist/cli/unknown-flags.js +6 -43
  14. package/dist/cli.js +8 -34
  15. package/dist/commands/agent/contribute-cli.js +1 -1
  16. package/dist/commands/completions.js +1 -2
  17. package/dist/commands/config-cli.js +5 -28
  18. package/dist/commands/env/env-cli.js +5 -5
  19. package/dist/commands/env/secret-cli.js +2 -2
  20. package/dist/commands/feedback-cli.js +75 -49
  21. package/dist/commands/health/checks.js +41 -16
  22. package/dist/commands/health/plugin-staleness.js +52 -22
  23. package/dist/commands/improve/autonomy-gate.js +5 -3
  24. package/dist/commands/improve/consolidate/chunking.js +3 -10
  25. package/dist/commands/improve/consolidate/continuity-check.js +4 -4
  26. package/dist/commands/improve/consolidate/pair-pass.js +29 -10
  27. package/dist/commands/improve/consolidate.js +10 -65
  28. package/dist/commands/improve/distill/lesson-update.js +91 -0
  29. package/dist/commands/improve/distill-guards.js +1 -43
  30. package/dist/commands/improve/distill.js +148 -21
  31. package/dist/commands/improve/eligibility.js +14 -1
  32. package/dist/commands/improve/extract-cli.js +1 -1
  33. package/dist/commands/improve/extract-prompt.js +17 -7
  34. package/dist/commands/improve/extract.js +5 -7
  35. package/dist/commands/improve/improve-cli.js +92 -106
  36. package/dist/commands/improve/improve-strategies.js +8 -12
  37. package/dist/commands/improve/improve.js +134 -52
  38. package/dist/commands/improve/loop-stages.js +18 -5
  39. package/dist/commands/improve/planner.js +0 -1
  40. package/dist/commands/improve/preparation.js +40 -99
  41. package/dist/commands/improve/proactive-maintenance.js +2 -2
  42. package/dist/commands/improve/reflect-noise.js +3 -55
  43. package/dist/commands/improve/reflect.js +29 -34
  44. package/dist/commands/improve/retrieval-queries.js +37 -0
  45. package/dist/commands/improve/retrieval-scope.js +2 -2
  46. package/dist/commands/improve/salience.js +2 -9
  47. package/dist/commands/improve/session-asset.js +4 -1
  48. package/dist/commands/improve/stage.js +23 -12
  49. package/dist/commands/lint/index.js +5 -20
  50. package/dist/commands/observability-cli.js +3 -9
  51. package/dist/commands/proposal/drain.js +43 -6
  52. package/dist/commands/proposal/proposal-cli.js +11 -31
  53. package/dist/commands/proposal/repository.js +20 -14
  54. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -3
  55. package/dist/commands/read/knowledge.js +3 -3
  56. package/dist/commands/read/remember-cli.js +0 -13
  57. package/dist/commands/read/search-cli.js +29 -97
  58. package/dist/commands/read/search.js +0 -11
  59. package/dist/commands/read/show.js +0 -19
  60. package/dist/commands/sources/plugin-upgrade.js +164 -101
  61. package/dist/commands/sources/sources-cli.js +1 -15
  62. package/dist/commands/sources/stash-cli.js +6 -14
  63. package/dist/commands/tasks/tasks-cli.js +4 -115
  64. package/dist/commands/workflow/plan.js +11 -33
  65. package/dist/commands/workflow-cli.js +2 -7
  66. package/dist/core/adapter/adapter-ids.js +1 -2
  67. package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
  68. package/dist/core/adapter/adapters/akm-adapter.js +9 -48
  69. package/dist/core/adapter/adapters/akm-lint.js +10 -26
  70. package/dist/core/adapter/adapters/akm-metadata.js +2 -72
  71. package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
  72. package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
  73. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
  74. package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
  75. package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
  76. package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
  77. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
  78. package/dist/core/adapter/recognize-match.js +3 -4
  79. package/dist/core/common.js +18 -2
  80. package/dist/core/config/config-schema.js +12 -15
  81. package/dist/core/config/config-types.js +6 -15
  82. package/dist/core/config/config-walker.js +6 -23
  83. package/dist/core/config/config.js +17 -80
  84. package/dist/core/config/engine-semantics.js +7 -3
  85. package/dist/core/config/schema/engines.js +7 -7
  86. package/dist/core/config/schema/improve-processes.js +10 -63
  87. package/dist/core/config/schema/improve.js +2 -21
  88. package/dist/core/config/schema/index-config.js +6 -39
  89. package/dist/core/env-secret-ref.js +2 -45
  90. package/dist/core/errors.js +1 -2
  91. package/dist/core/extra-params.js +2 -86
  92. package/dist/core/file-lock.js +2 -6
  93. package/dist/core/improve-result.js +13 -1
  94. package/dist/core/mutation-target.js +2 -2
  95. package/dist/core/paths.js +4 -0
  96. package/dist/core/recognition-util.js +4 -5
  97. package/dist/core/run-lock.js +1 -1
  98. package/dist/core/subprocess.js +49 -0
  99. package/dist/core/warn.js +21 -4
  100. package/dist/core/write-source.js +3 -3
  101. package/dist/execution/target-ref.js +1 -2
  102. package/dist/indexer/indexer.js +15 -6
  103. package/dist/indexer/passes/memory-inference.js +15 -6
  104. package/dist/indexer/search/db-search.js +49 -36
  105. package/dist/indexer/search/fts-query.js +0 -19
  106. package/dist/indexer/search/ranking.js +29 -1
  107. package/dist/indexer/usage/usage-events.js +21 -16
  108. package/dist/integrations/agent/engine-resolution.js +5 -2
  109. package/dist/integrations/agent/index.js +1 -1
  110. package/dist/integrations/agent/profiles.js +41 -98
  111. package/dist/integrations/agent/request-lowering.js +3 -3
  112. package/dist/integrations/harnesses/aider/index.js +5 -0
  113. package/dist/integrations/harnesses/amazonq/index.js +1 -0
  114. package/dist/integrations/harnesses/claude/config-import.js +2 -3
  115. package/dist/integrations/harnesses/claude/index.js +5 -0
  116. package/dist/integrations/harnesses/codex/index.js +1 -0
  117. package/dist/integrations/harnesses/copilot/index.js +1 -0
  118. package/dist/integrations/harnesses/gemini/index.js +1 -0
  119. package/dist/integrations/harnesses/index.js +18 -39
  120. package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
  121. package/dist/integrations/harnesses/opencode/config-import.js +121 -52
  122. package/dist/integrations/harnesses/opencode/index.js +22 -0
  123. package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
  124. package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
  125. package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
  126. package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
  127. package/dist/integrations/harnesses/opencode/session-log.js +104 -115
  128. package/dist/integrations/harnesses/opencode/version.js +98 -0
  129. package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
  130. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
  131. package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
  132. package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +254 -0
  133. package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
  134. package/dist/integrations/harnesses/openhands/index.js +1 -0
  135. package/dist/integrations/harnesses/pi/index.js +1 -0
  136. package/dist/integrations/harnesses/shared.js +2 -0
  137. package/dist/integrations/harnesses/types.js +2 -0
  138. package/dist/llm/client.js +41 -11
  139. package/dist/output/cli-hints.js +1 -1
  140. package/dist/output/context.js +10 -19
  141. package/dist/output/format-exempt.js +5 -8
  142. package/dist/output/shapes/helpers.js +1 -22
  143. package/dist/output/shapes/passthrough.js +0 -1
  144. package/dist/output/shapes.js +3 -31
  145. package/dist/output/stdout.js +5 -1
  146. package/dist/output/text/command-format.js +6 -1
  147. package/dist/output/text/workflow-format.js +4 -22
  148. package/dist/scripts/akm-migrate-node.js +15745 -16711
  149. package/dist/scripts/akm-migrate.js +16318 -17280
  150. package/dist/setup/detect.js +10 -4
  151. package/dist/setup/harness-config-import.js +5 -14
  152. package/dist/setup/setup.js +1 -1
  153. package/dist/setup/steps/tasks.js +12 -13
  154. package/dist/sources/providers/git-stash.js +94 -13
  155. package/dist/storage/repositories/index-utility-repository.js +2 -32
  156. package/dist/storage/repositories/index-vec-repository.js +4 -0
  157. package/dist/storage/repositories/task-history-repository.js +0 -5
  158. package/dist/storage/repositories/workflow-runs-repository.js +5 -15
  159. package/dist/tasks/embedded.js +2 -1
  160. package/dist/tasks/run/run-command-task.js +9 -1
  161. package/dist/tasks/run/run-workflow-task.js +4 -3
  162. package/dist/tasks/run/task-history.js +0 -2
  163. package/dist/tasks/run/task-log.js +38 -1
  164. package/dist/tasks/schedule-gates.js +39 -0
  165. package/dist/tasks/scheduler-binding.js +0 -16
  166. package/dist/tasks/scheduler-sync.js +3 -97
  167. package/dist/tasks/source/bounded-document.js +1 -1
  168. package/dist/tasks/source/task-source-v4.js +2 -12
  169. package/dist/tasks/task-run-reserved-flags.js +2 -31
  170. package/dist/workflows/authoring/authoring.js +6 -6
  171. package/dist/workflows/compile.js +8 -65
  172. package/dist/workflows/exec/child-workflow.js +22 -3
  173. package/dist/workflows/exec/native-executor.js +12 -147
  174. package/dist/workflows/exec/run-workflow.js +31 -103
  175. package/dist/workflows/exec/step-work.js +9 -178
  176. package/dist/workflows/exec/unit-dispatch.js +0 -10
  177. package/dist/workflows/freeze/environment.js +3 -44
  178. package/dist/workflows/freeze/freeze.js +8 -41
  179. package/dist/workflows/freeze/targets/child-workflow.js +27 -63
  180. package/dist/workflows/freeze/targets/command.js +2 -16
  181. package/dist/workflows/freeze/targets/shell.js +0 -2
  182. package/dist/workflows/freeze/task-bindings.js +2 -28
  183. package/dist/workflows/ir/params.js +12 -2
  184. package/dist/workflows/parser.js +82 -232
  185. package/dist/workflows/plan.js +1 -1
  186. package/dist/workflows/renderer.js +10 -19
  187. package/dist/workflows/runtime/run-plan.js +11 -53
  188. package/dist/workflows/runtime/runs.js +4 -27
  189. package/dist/workflows/source-files.js +12 -16
  190. package/dist/workflows/source-semantics.js +1 -93
  191. package/docs/README.md +1 -1
  192. package/docs/agents/README.md +3 -3
  193. package/docs/agents/agent-install.md +1 -1
  194. package/docs/architecture/0.10-process-rules.md +827 -0
  195. package/docs/architecture/adapters.md +17 -31
  196. package/docs/architecture/architecture.md +4 -5
  197. package/docs/architecture/improvement.md +115 -76
  198. package/docs/architecture/internals/improve-workflow.md +22 -16
  199. package/docs/architecture/internals/indexing.md +1 -1
  200. package/docs/architecture/internals/search.md +1 -2
  201. package/docs/architecture/internals/storage-locations.md +6 -6
  202. package/docs/architecture/persisted-data-compat.md +13 -4
  203. package/docs/architecture/specs/0.9.0-decisions.md +6 -2
  204. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
  205. package/docs/architecture/specs/workflow-format-unification.md +1 -6
  206. package/docs/architecture/testing/manual-testing-checklist.md +21 -30
  207. package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
  208. package/docs/architecture/testing/testing-workflow.md +1 -1
  209. package/docs/architecture/workflow-engine.md +5 -56
  210. package/docs/guides/author-workflows.md +39 -69
  211. package/docs/guides/bundles.md +3 -1
  212. package/docs/guides/capture-knowledge.md +1 -1
  213. package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
  214. package/docs/guides/discover-and-load.md +4 -4
  215. package/docs/guides/getting-started.md +1 -1
  216. package/docs/guides/improve-the-library.md +10 -9
  217. package/docs/guides/recipes/headless-install.md +2 -2
  218. package/docs/guides/run-workflows.md +1 -1
  219. package/docs/guides/scheduling.md +9 -1
  220. package/docs/guides/use-with-any-agent.md +49 -4
  221. package/docs/integration/bundling-akm.md +7 -6
  222. package/docs/maintainers/curate-workmap.md +1 -2
  223. package/docs/maintainers/release-checklist.md +33 -1
  224. package/docs/migration/README.md +2 -0
  225. package/docs/migration/release-notes/0.10.0.md +201 -0
  226. package/docs/migration/release-notes/README.md +1 -0
  227. package/docs/migration/v0.9-to-v0.10.md +507 -0
  228. package/docs/posts/akm-command-tour.md +3 -3
  229. package/docs/product-surface.md +1 -1
  230. package/docs/reference/README.md +1 -0
  231. package/docs/reference/api.md +65 -0
  232. package/docs/reference/asset-types.md +3 -5
  233. package/docs/reference/bundle-types.md +2 -4
  234. package/docs/reference/cli.md +160 -188
  235. package/docs/reference/configuration.md +101 -20
  236. package/docs/reference/data-and-telemetry.md +2 -2
  237. package/docs/reference/env-and-secrets.md +2 -2
  238. package/docs/reference/supported-formats.md +3 -4
  239. package/docs/reference/tasks.md +12 -19
  240. package/docs/reference/website-sources.md +1 -1
  241. package/docs/reference/workflow-schema.md +89 -317
  242. package/docs/reference/workflows.md +20 -48
  243. package/package.json +12 -2
  244. package/schemas/akm-config.json +2 -243
  245. package/schemas/akm-task.json +2 -3
  246. package/schemas/akm-workflow.json +47 -84
  247. package/dist/assets/improve-strategies/catchup.json +0 -41
  248. package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
  249. package/dist/assets/improve-strategies/quick.json +0 -17
  250. package/dist/assets/improve-strategies/reflect-distill.json +0 -29
  251. package/dist/assets/improve-strategies/thorough.json +0 -49
  252. package/dist/assets/prompts/retrieval-relevance-judge.md +0 -6
  253. package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
  254. package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
  255. package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
  256. package/dist/cli/retired-commands.js +0 -118
  257. package/dist/commands/improve/retrieval-gate.js +0 -132
  258. package/dist/commands/tasks/validate.js +0 -176
  259. package/dist/core/config/legacy-source-shape-shim.js +0 -117
  260. package/dist/indexer/feedback/utility-policy.js +0 -75
  261. package/dist/integrations/harnesses/ids.js +0 -28
  262. package/dist/tasks/source/task-source-v3-frozen.js +0 -424
  263. package/dist/tasks/source/task-to-v4.js +0 -863
  264. package/dist/workflows/exec/worktree.js +0 -372
  265. package/dist/workflows/freeze/child-output-references.js +0 -83
  266. package/dist/workflows/freeze/identity.js +0 -22
  267. package/dist/workflows/freeze/targets/script.js +0 -50
  268. package/dist/workflows/freeze/targets/task.js +0 -124
  269. package/dist/workflows/github-yaml.js +0 -506
  270. package/dist/workflows/runtime/run-outputs.js +0 -80
@@ -4,10 +4,13 @@
4
4
  import fs from "node:fs";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
+ import { toErrorMessage } from "../../../core/common.js";
8
+ import { warnOnce } from "../../../core/warn.js";
7
9
  import { openDatabase } from "../../../storage/database.js";
8
10
  import { openSqliteReadSnapshot } from "../../../storage/sqlite-read-snapshot.js";
9
- import { extractInlineRefMentions } from "../../session-logs/inline-refs.js";
10
11
  import { AbstractSessionLogProvider } from "../../session-logs/provider-base.js";
12
+ import { hasV1Session, listV1Sessions, readV1Session, V1_TABLES } from "./session-log-v1.js";
13
+ import { listV2Sessions, readV2Session, V2_TABLES, v2SessionState } from "./session-log-v2.js";
11
14
  function getOpenCodeBaseDir() {
12
15
  if (process.platform === "darwin") {
13
16
  return path.join(os.homedir(), "Library", "Application Support", "opencode");
@@ -15,15 +18,28 @@ function getOpenCodeBaseDir() {
15
18
  return path.join(os.homedir(), ".local", "share", "opencode");
16
19
  }
17
20
  /**
18
- * Opencode session storage:
21
+ * OpenCode session storage: one SQLite file, `<base>/opencode.db`, whose
22
+ * layout decides the reader, never the installed binary or its version:
19
23
  *
20
- * SQLite (current, observed 2026-06): `<base>/opencode.db` — a Drizzle-managed
21
- * database with `session` / `message` / `part` tables. Message text lives in
22
- * `part` rows (`data` JSON, `type: "text"`); `message.data` holds role/timing.
23
- * This is the sole supported layout.
24
+ * - V1: `session` table, with `message` / `part` for content (`session-log-v1.ts`).
25
+ * - V2: `session_v2` / `session_message` tables (`session-log-v2.ts`).
26
+ * - Both: a natively upgraded V1 file keeps its V1 tables and gains the V2
27
+ * ones. Sessions are merged by id, V2 winning, V1-only ones kept.
28
+ * - Neither: an unsupported layout, reported, never presented as empty.
29
+ *
30
+ * The file is opened read-only and never migrated or written.
24
31
  */
25
32
  /** Filename of opencode's SQLite session store, relative to its base dir. */
26
33
  const OPENCODE_DB_FILENAME = "opencode.db";
34
+ function tableNames(db) {
35
+ const rows = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all();
36
+ return new Set(rows.map((r) => r.name));
37
+ }
38
+ function classify(tables) {
39
+ const v1 = V1_TABLES.every((t) => tables.has(t));
40
+ const v2 = V2_TABLES.every((t) => tables.has(t));
41
+ return v1 || v2 ? { kind: "ok", v1, v2 } : { kind: "unsupported", tables: [...tables].sort() };
42
+ }
27
43
  export class OpenCodeProvider extends AbstractSessionLogProvider {
28
44
  name = "opencode";
29
45
  #baseDir = getOpenCodeBaseDir();
@@ -34,147 +50,120 @@ export class OpenCodeProvider extends AbstractSessionLogProvider {
34
50
  #dbPath(base) {
35
51
  return path.join(base, OPENCODE_DB_FILENAME);
36
52
  }
53
+ /** Classify the store under `location` (default: opencode's data dir) without listing anything. */
54
+ inspectStore(location) {
55
+ const dbPath = this.#dbPath(location ?? this.#baseDir);
56
+ if (!fs.existsSync(dbPath))
57
+ return { kind: "missing" };
58
+ let db;
59
+ try {
60
+ db = openDatabase(dbPath, { readonly: true, create: false });
61
+ }
62
+ catch (error) {
63
+ return { kind: "unreadable", reason: toErrorMessage(error) };
64
+ }
65
+ try {
66
+ return classify(tableNames(db));
67
+ }
68
+ catch (error) {
69
+ return { kind: "unreadable", reason: toErrorMessage(error) };
70
+ }
71
+ finally {
72
+ db.close();
73
+ }
74
+ }
37
75
  listSessions(input = {}) {
38
- const base = input.location ?? this.#baseDir;
39
- const sinceMs = input.sinceMs ?? 0;
40
- const dbPath = this.#dbPath(base);
76
+ const dbPath = this.#dbPath(input.location ?? this.#baseDir);
41
77
  if (!fs.existsSync(dbPath))
42
78
  return [];
43
- return this.#listSessionsFromDb(dbPath, sinceMs, input.isolatedSnapshot ?? false);
44
- }
45
- readSession(ref) {
46
- return this.#readSessionFromDb(ref);
47
- }
48
- /**
49
- * List sessions from the SQLite store. `filePath` on each summary is the
50
- * `opencode.db` path so {@link readSession} can route back to the DB reader.
51
- * Returns `[]` (never throws) when the DB is unreadable or lacks the expected
52
- * schema — callers treat a missing harness as "no sessions".
53
- */
54
- #listSessionsFromDb(dbPath, sinceMs, isolatedSnapshot) {
55
79
  let db;
56
80
  try {
57
- const opened = isolatedSnapshot
81
+ db = input.isolatedSnapshot
58
82
  ? openSqliteReadSnapshot(dbPath)
59
83
  : openDatabase(dbPath, { readonly: true, create: false });
60
- if (!opened)
61
- return [];
62
- db = opened;
63
84
  }
64
- catch {
85
+ catch (error) {
86
+ this.#warnUnavailable(dbPath, `cannot open it (${toErrorMessage(error)})`);
65
87
  return [];
66
88
  }
89
+ if (!db)
90
+ return [];
67
91
  try {
68
- const rows = db
69
- .prepare("SELECT id, title, directory, time_created, time_updated FROM session WHERE time_updated >= ? ORDER BY time_updated DESC")
70
- .all(sinceMs);
71
- return rows.map((r) => this.sessionRef({
72
- sessionId: r.id,
73
- filePath: dbPath,
74
- startedAt: typeof r.time_created === "number" ? r.time_created : undefined,
75
- endedAt: typeof r.time_updated === "number" ? r.time_updated : undefined,
76
- projectHint: typeof r.directory === "string" && r.directory.length > 0 ? r.directory : undefined,
77
- title: typeof r.title === "string" && r.title.length > 0 ? r.title : undefined,
78
- }));
92
+ const status = classify(tableNames(db));
93
+ if (status.kind !== "ok") {
94
+ this.#warnUnavailable(dbPath, `it has neither the OpenCode 1 (${V1_TABLES.join("/")}) nor the OpenCode 2 (${V2_TABLES.join("/")}) tables`);
95
+ return [];
96
+ }
97
+ return this.#listFromDb(db, dbPath, input.sinceMs ?? 0, status.v1, status.v2);
79
98
  }
80
- catch {
81
- // Missing `session` table / unexpected schema — treat as no sessions.
99
+ catch (error) {
100
+ this.#warnUnavailable(dbPath, `cannot query it (${toErrorMessage(error)})`);
82
101
  return [];
83
102
  }
84
103
  finally {
85
104
  db.close();
86
105
  }
87
106
  }
88
- /**
89
- * Read one session from the SQLite store. Message text lives in `part` rows
90
- * (`type: "text"`); `message.data` carries role + timing. One event per
91
- * message, text-parts concatenated in time order. Returns empty events
92
- * (never throws) when the DB is unreadable.
93
- */
94
- #readSessionFromDb(ref) {
107
+ readSession(ref) {
95
108
  const emptyRef = this.sessionRef({ sessionId: ref.sessionId, filePath: ref.filePath });
109
+ const empty = { ref: emptyRef, events: [], inlineRefs: [] };
96
110
  let db;
97
111
  try {
98
112
  db = openDatabase(ref.filePath, { readonly: true, create: false });
99
113
  }
100
- catch {
101
- return { ref: emptyRef, events: [], inlineRefs: [] };
114
+ catch (error) {
115
+ this.#warnUnavailable(ref.filePath, `cannot open it (${toErrorMessage(error)})`);
116
+ return empty;
102
117
  }
103
118
  try {
104
- const meta = db
105
- .prepare("SELECT title, directory, time_created, time_updated FROM session WHERE id = ?")
106
- .get(ref.sessionId);
107
- const startedAt = typeof meta?.time_created === "number" ? meta.time_created : undefined;
108
- const endedAt = typeof meta?.time_updated === "number" ? meta.time_updated : undefined;
109
- const title = typeof meta?.title === "string" && meta.title.length > 0 ? meta.title : undefined;
110
- const projectHint = typeof meta?.directory === "string" && meta.directory.length > 0 ? meta.directory : undefined;
111
- const messages = db
112
- .prepare("SELECT id, data, time_created FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC")
113
- .all(ref.sessionId);
114
- const parts = db
115
- .prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC")
116
- .all(ref.sessionId);
117
- // Group text-part bodies by their parent message.
118
- const textByMessage = new Map();
119
- for (const part of parts) {
120
- let parsed;
121
- try {
122
- parsed = JSON.parse(part.data);
123
- }
124
- catch {
125
- continue;
126
- }
127
- if (parsed?.type !== "text")
128
- continue;
129
- const text = parsed.text;
130
- if (typeof text !== "string" || text.length < 1)
131
- continue;
132
- const bucket = textByMessage.get(part.message_id) ?? [];
133
- bucket.push(text);
134
- textByMessage.set(part.message_id, bucket);
119
+ const status = classify(tableNames(db));
120
+ if (status.kind !== "ok") {
121
+ this.#warnUnavailable(ref.filePath, "it has no supported OpenCode 1 or OpenCode 2 session tables");
122
+ return empty;
135
123
  }
136
- const events = [];
137
- const inlineRefs = [];
138
- for (const message of messages) {
139
- let mdata = {};
140
- try {
141
- mdata = JSON.parse(message.data);
142
- }
143
- catch {
144
- // role/timing unavailable — fall through with defaults
145
- }
146
- const role = typeof mdata.role === "string" ? mdata.role : "unknown";
147
- const mtime = mdata.time?.created;
148
- const ts = typeof mtime === "number"
149
- ? mtime
150
- : typeof message.time_created === "number"
151
- ? message.time_created
152
- : undefined;
153
- const text = (textByMessage.get(message.id) ?? []).join("\n").trim();
154
- if (text.length < 1)
155
- continue;
156
- events.push({ harness: this.name, text, ts, sessionId: ref.sessionId, role, filePath: ref.filePath });
157
- inlineRefs.push(...extractInlineRefMentions(text, ts));
158
- }
159
- events.sort((a, b) => (a.ts ?? 0) - (b.ts ?? 0));
124
+ // V2 owns a session that has messages there; otherwise a retained V1 copy (a
125
+ // session the upgrade has not copied, or copied without messages) is the history.
126
+ const v2 = status.v2 ? v2SessionState(db, ref.sessionId) : { exists: false, messages: 0 };
127
+ const useV2 = v2.exists && (v2.messages > 0 || !status.v1 || !hasV1Session(db, ref.sessionId));
128
+ const read = useV2
129
+ ? readV2Session(db, this.name, ref.sessionId, ref.filePath)
130
+ : readV1Session(db, this.name, ref.sessionId, ref.filePath);
160
131
  return {
161
- ref: this.sessionRef({
162
- sessionId: ref.sessionId,
163
- filePath: ref.filePath,
164
- startedAt,
165
- endedAt,
166
- projectHint,
167
- title,
168
- }),
169
- events,
170
- inlineRefs,
132
+ ref: this.sessionRef({ ...read.meta, filePath: ref.filePath }),
133
+ events: read.events,
134
+ inlineRefs: read.inlineRefs,
171
135
  };
172
136
  }
173
- catch {
174
- return { ref: emptyRef, events: [], inlineRefs: [] };
137
+ catch (error) {
138
+ this.#warnUnavailable(ref.filePath, `cannot read session ${ref.sessionId} (${toErrorMessage(error)})`);
139
+ return empty;
175
140
  }
176
141
  finally {
177
142
  db.close();
178
143
  }
179
144
  }
145
+ /** List each present layout; one failing layout is reported and the other still listed. */
146
+ #listFromDb(db, dbPath, sinceMs, v1, v2) {
147
+ const merged = new Map();
148
+ const attempt = (label, list) => {
149
+ try {
150
+ for (const meta of list())
151
+ if (!merged.has(meta.sessionId))
152
+ merged.set(meta.sessionId, meta);
153
+ }
154
+ catch (error) {
155
+ this.#warnUnavailable(dbPath, `its ${label} session tables cannot be queried (${toErrorMessage(error)})`);
156
+ }
157
+ };
158
+ if (v2)
159
+ attempt("OpenCode 2", () => listV2Sessions(db, sinceMs));
160
+ if (v1)
161
+ attempt("OpenCode 1", () => listV1Sessions(db, sinceMs));
162
+ return [...merged.values()]
163
+ .sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0))
164
+ .map((meta) => this.sessionRef({ ...meta, filePath: dbPath }));
165
+ }
166
+ #warnUnavailable(dbPath, why) {
167
+ warnOnce(`opencode-history:${dbPath}:${why}`, `OpenCode session history at ${dbPath} was not read: ${why}. This is an unsupported or damaged store, not an empty history.`);
168
+ }
180
169
  }
@@ -0,0 +1,98 @@
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
+ * Which OpenCode major a binary is (#1049).
6
+ *
7
+ * akm drives two OpenCode majors through separate adapters. There is no config
8
+ * field for the choice: the binary says which major it is. {@link resolveOpencodeBin}
9
+ * picks the binary (the engine's own `bin`, else the newest OpenCode on PATH) and
10
+ * {@link detectOpencodeMajor} runs `<bin> --version` once per binary per process.
11
+ *
12
+ * - Major 1: OpenCode 1 adapters, plus a one-time warning recommending OpenCode 2.
13
+ * - Major 2: OpenCode 2 adapters.
14
+ * - Anything else, or a `--version` that fails or cannot be parsed: the newest
15
+ * adapters akm knows (OpenCode 2) and a one-time warning with the raw output.
16
+ * Detection never aborts; the dispatch reports whatever real error the binary
17
+ * produces.
18
+ *
19
+ * PATH lookups are memoized too: PATH does not change inside a process, and
20
+ * every dispatch asks for the same one or two binaries.
21
+ */
22
+ import childProcess from "node:child_process";
23
+ import { warnOnce } from "../../../core/warn.js";
24
+ import { defaultWhich } from "../../agent/detect.js";
25
+ import { OPENCODE_SDK_SERVER_BIN } from "../../agent/profiles.js";
26
+ /** OpenCode 2's own alias for its binary; it can sit next to an OpenCode 1 `opencode` on PATH. */
27
+ export const OPENCODE2_BIN = "opencode2";
28
+ const VERSION_TIMEOUT_MS = 30_000;
29
+ const defaultProbe = (bin) => {
30
+ const result = childProcess.spawnSync(bin, ["--version"], {
31
+ encoding: "utf8",
32
+ env: process.env,
33
+ stdio: "pipe",
34
+ timeout: VERSION_TIMEOUT_MS,
35
+ killSignal: "SIGKILL",
36
+ });
37
+ if (result.error || result.status !== 0)
38
+ return undefined;
39
+ return (result.stdout ?? "").trim();
40
+ };
41
+ let probeOverride;
42
+ const detected = new Map();
43
+ const onPath = new Map();
44
+ /** TEST-ONLY. Swap the `--version` probe (and forget every cached detection and PATH lookup); pass undefined to restore. */
45
+ export function _setOpencodeVersionProbeForTests(fake) {
46
+ probeOverride = fake;
47
+ detected.clear();
48
+ onPath.clear();
49
+ }
50
+ /** `which`, memoized for the process when it is the default lookup. */
51
+ function lookup(bin, which) {
52
+ if (which)
53
+ return which(bin) ?? undefined;
54
+ if (!onPath.has(bin))
55
+ onPath.set(bin, defaultWhich(bin));
56
+ return onPath.get(bin);
57
+ }
58
+ /** The major in `opencode --version` output (`1.18.34`, `opencode v2.0.26`), or undefined when there is none. */
59
+ export function parseOpencodeMajor(versionOutput) {
60
+ const match = versionOutput.match(/(\d+)\.\d+/);
61
+ return match ? Number(match[1]) : undefined;
62
+ }
63
+ /**
64
+ * The binary an OpenCode engine runs: its own `bin` when it sets one, else the
65
+ * most recent OpenCode on PATH (`opencode2` when present, else `opencode`).
66
+ */
67
+ export function resolveOpencodeBin(explicitBin, which) {
68
+ if (explicitBin)
69
+ return explicitBin;
70
+ return lookup(OPENCODE2_BIN, which) ? OPENCODE2_BIN : OPENCODE_SDK_SERVER_BIN;
71
+ }
72
+ /** Detect the OpenCode major of `bin`: one `--version` run per binary per process. */
73
+ export function detectOpencodeMajor(bin, options = {}) {
74
+ const path = lookup(bin, options.which) ?? bin;
75
+ const cached = detected.get(path);
76
+ if (cached)
77
+ return cached;
78
+ const raw = (options.probe ?? probeOverride ?? defaultProbe)(path);
79
+ const version = raw ?? "";
80
+ const reportedMajor = raw === undefined ? undefined : parseOpencodeMajor(raw);
81
+ const major = reportedMajor === 1 ? 1 : 2;
82
+ if (reportedMajor === 1) {
83
+ warnOnce(`opencode-version:${path}`, `${path} is OpenCode ${version}. OpenCode 1 support continues, but upgrading to OpenCode 2 is recommended.`);
84
+ }
85
+ else if (reportedMajor !== 2) {
86
+ warnOnce(`opencode-version:${path}`, raw === undefined
87
+ ? `Could not run \`${path} --version\`, so its OpenCode major is unknown; treating it as OpenCode 2.`
88
+ : `${path} reported an unrecognized OpenCode version (${JSON.stringify(raw)}); treating it as OpenCode 2.`);
89
+ }
90
+ const info = {
91
+ major,
92
+ ...(reportedMajor !== undefined ? { reportedMajor } : {}),
93
+ version,
94
+ runnable: raw !== undefined,
95
+ };
96
+ detected.set(path, info);
97
+ return info;
98
+ }
@@ -2,24 +2,10 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
- * OpenCode SDK harness DESCRIPTOR (#564).
5
+ * OpenCode SDK harness descriptor (#564).
6
6
  *
7
- * This module is a dependency-graph LEAF: it imports only the harness base
8
- * types (`../types`) and nothing from `core/config` or the SDK runner. Keeping
9
- * the descriptor separate from the runtime runner (`./sdk-runner`, which pulls
10
- * `core/config`) is load-bearing: `HARNESS_REGISTRY` in `../index.ts` imports
11
- * this class from HERE, not from the per-harness barrel (`./index.ts`). The
12
- * barrel additionally re-exports `runOpencodeSdk`/`closeServer` from
13
- * `./sdk-runner`; that re-export makes the barrel transitively depend on
14
- * `core/config`, and `core/config/config-types` derives `VALID_HARNESS_IDS`
15
- * back from `../index.ts`. If the registry imported the class through the
16
- * barrel, that cycle would evaluate `../index.ts` (and `new
17
- * OpencodeSdkHarness()`) while the barrel — and hence this class binding — was
18
- * still initializing, throwing a temporal-dead-zone "Cannot access
19
- * 'OpencodeSdkHarness' before initialization" whenever the barrel is the first
20
- * module loaded in a fresh graph (e.g. the workflow-exec subprocess entry).
21
- * Importing the descriptor from this leaf keeps the registry a config-leaf and
22
- * breaks the cycle.
7
+ * Descriptor only. The runner (`./sdk-runner`) imports `core/config`, so the
8
+ * registry must never load it; `agent/runner-dispatch.ts` imports it directly.
23
9
  */
24
10
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
25
11
  import { MODEL_WORK_AGENT_INFERENCE } from "../opencode/model-config.js";
@@ -52,5 +38,7 @@ export class OpencodeSdkHarness extends BaseHarness {
52
38
  capabilities = caps({
53
39
  agentDispatch: true,
54
40
  detection: true,
41
+ modelWork: true,
42
+ nativeAgent: true,
55
43
  });
56
44
  }