akm-cli 0.9.31 → 0.10.26101002-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +265 -0
- package/STABILITY.md +79 -53
- package/dist/api-node.mjs +20 -0
- package/dist/api.d.ts +23 -0
- package/dist/api.js +45 -0
- package/dist/assets/hints/cli-hints-full.md +13 -27
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/improve-strategies/default.json +3 -3
- package/dist/assets/prompts/consolidate-pair.md +9 -7
- package/dist/cli/shared.js +31 -48
- package/dist/cli/unknown-flags.js +6 -43
- package/dist/cli.js +8 -34
- package/dist/commands/agent/contribute-cli.js +1 -1
- package/dist/commands/completions.js +1 -2
- package/dist/commands/config-cli.js +5 -28
- package/dist/commands/env/env-cli.js +5 -5
- package/dist/commands/env/secret-cli.js +2 -2
- package/dist/commands/health/checks.js +41 -16
- package/dist/commands/health/plugin-staleness.js +52 -22
- package/dist/commands/improve/autonomy-gate.js +5 -3
- package/dist/commands/improve/consolidate/pair-pass.js +7 -1
- package/dist/commands/improve/consolidate.js +6 -35
- package/dist/commands/improve/distill/lesson-update.js +91 -0
- package/dist/commands/improve/distill-guards.js +1 -43
- package/dist/commands/improve/distill.js +140 -20
- package/dist/commands/improve/eligibility.js +14 -1
- package/dist/commands/improve/extract-cli.js +1 -1
- package/dist/commands/improve/improve-cli.js +87 -102
- package/dist/commands/improve/improve-strategies.js +8 -12
- package/dist/commands/improve/improve.js +2 -2
- package/dist/commands/improve/loop-stages.js +0 -1
- package/dist/commands/improve/planner.js +0 -1
- package/dist/commands/improve/preparation.js +40 -99
- package/dist/commands/improve/proactive-maintenance.js +2 -2
- package/dist/commands/improve/reflect-noise.js +3 -55
- package/dist/commands/improve/reflect.js +3 -8
- package/dist/commands/improve/retrieval-scope.js +2 -2
- package/dist/commands/improve/salience.js +2 -9
- package/dist/commands/improve/stage.js +14 -9
- package/dist/commands/lint/index.js +5 -20
- package/dist/commands/observability-cli.js +3 -9
- package/dist/commands/proposal/drain.js +39 -6
- package/dist/commands/proposal/proposal-cli.js +11 -31
- package/dist/commands/proposal/repository.js +3 -3
- package/dist/commands/read/knowledge.js +3 -3
- package/dist/commands/read/remember-cli.js +0 -13
- package/dist/commands/read/search-cli.js +29 -97
- package/dist/commands/read/search.js +0 -11
- package/dist/commands/read/show.js +0 -19
- package/dist/commands/sources/plugin-upgrade.js +164 -101
- package/dist/commands/sources/sources-cli.js +1 -15
- package/dist/commands/sources/stash-cli.js +4 -12
- package/dist/commands/tasks/tasks-cli.js +4 -115
- package/dist/commands/workflow/plan.js +11 -33
- package/dist/commands/workflow-cli.js +2 -7
- package/dist/core/adapter/adapter-ids.js +1 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
- package/dist/core/adapter/adapters/akm-adapter.js +9 -48
- package/dist/core/adapter/adapters/akm-lint.js +10 -26
- package/dist/core/adapter/adapters/akm-metadata.js +2 -72
- package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
- package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
- package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
- package/dist/core/adapter/recognize-match.js +3 -4
- package/dist/core/common.js +18 -2
- package/dist/core/config/config-schema.js +12 -15
- package/dist/core/config/config-types.js +6 -15
- package/dist/core/config/config-walker.js +6 -23
- package/dist/core/config/config.js +17 -80
- package/dist/core/config/engine-semantics.js +7 -3
- package/dist/core/config/schema/engines.js +7 -7
- package/dist/core/config/schema/improve-processes.js +8 -61
- package/dist/core/config/schema/improve.js +2 -21
- package/dist/core/config/schema/index-config.js +6 -39
- package/dist/core/env-secret-ref.js +2 -45
- package/dist/core/errors.js +1 -2
- package/dist/core/extra-params.js +2 -86
- package/dist/core/improve-result.js +11 -1
- package/dist/core/mutation-target.js +2 -2
- package/dist/core/recognition-util.js +4 -5
- package/dist/core/warn.js +21 -4
- package/dist/core/write-source.js +3 -3
- package/dist/execution/target-ref.js +1 -2
- package/dist/indexer/search/db-search.js +4 -19
- package/dist/indexer/search/fts-query.js +0 -19
- package/dist/integrations/agent/engine-resolution.js +5 -2
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +41 -98
- package/dist/integrations/agent/request-lowering.js +3 -3
- package/dist/integrations/harnesses/aider/index.js +5 -0
- package/dist/integrations/harnesses/amazonq/index.js +1 -0
- package/dist/integrations/harnesses/claude/config-import.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +5 -0
- package/dist/integrations/harnesses/codex/index.js +1 -0
- package/dist/integrations/harnesses/copilot/index.js +1 -0
- package/dist/integrations/harnesses/gemini/index.js +1 -0
- package/dist/integrations/harnesses/index.js +18 -39
- package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
- package/dist/integrations/harnesses/opencode/config-import.js +121 -52
- package/dist/integrations/harnesses/opencode/index.js +22 -0
- package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
- package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
- package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
- package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
- package/dist/integrations/harnesses/opencode/session-log.js +104 -115
- package/dist/integrations/harnesses/opencode/version.js +98 -0
- package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
- package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
- package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +228 -0
- package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
- package/dist/integrations/harnesses/openhands/index.js +1 -0
- package/dist/integrations/harnesses/pi/index.js +1 -0
- package/dist/integrations/harnesses/shared.js +2 -0
- package/dist/integrations/harnesses/types.js +2 -0
- package/dist/llm/client.js +41 -11
- package/dist/output/cli-hints.js +1 -1
- package/dist/output/context.js +10 -19
- package/dist/output/format-exempt.js +5 -8
- package/dist/output/shapes/helpers.js +1 -22
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/shapes.js +3 -31
- package/dist/output/stdout.js +5 -1
- package/dist/output/text/command-format.js +6 -1
- package/dist/output/text/workflow-format.js +4 -22
- package/dist/scripts/akm-migrate-node.js +13049 -14013
- package/dist/scripts/akm-migrate.js +16316 -17276
- package/dist/setup/detect.js +10 -4
- package/dist/setup/harness-config-import.js +5 -14
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +12 -13
- package/dist/storage/repositories/task-history-repository.js +0 -5
- package/dist/storage/repositories/workflow-runs-repository.js +5 -15
- package/dist/tasks/embedded.js +2 -1
- package/dist/tasks/run/task-history.js +0 -2
- package/dist/tasks/schedule-gates.js +39 -0
- package/dist/tasks/scheduler-binding.js +0 -16
- package/dist/tasks/scheduler-sync.js +3 -97
- package/dist/tasks/source/bounded-document.js +1 -1
- package/dist/tasks/source/task-source-v4.js +2 -12
- package/dist/tasks/task-run-reserved-flags.js +2 -31
- package/dist/workflows/authoring/authoring.js +6 -6
- package/dist/workflows/compile.js +8 -65
- package/dist/workflows/exec/child-workflow.js +22 -3
- package/dist/workflows/exec/native-executor.js +12 -147
- package/dist/workflows/exec/run-workflow.js +31 -103
- package/dist/workflows/exec/step-work.js +9 -178
- package/dist/workflows/exec/unit-dispatch.js +0 -10
- package/dist/workflows/freeze/environment.js +3 -44
- package/dist/workflows/freeze/freeze.js +8 -41
- package/dist/workflows/freeze/targets/child-workflow.js +27 -63
- package/dist/workflows/freeze/targets/command.js +2 -16
- package/dist/workflows/freeze/targets/shell.js +0 -2
- package/dist/workflows/freeze/task-bindings.js +2 -28
- package/dist/workflows/ir/params.js +12 -2
- package/dist/workflows/parser.js +82 -232
- package/dist/workflows/plan.js +1 -1
- package/dist/workflows/renderer.js +10 -19
- package/dist/workflows/runtime/run-plan.js +11 -53
- package/dist/workflows/runtime/runs.js +4 -27
- package/dist/workflows/source-files.js +12 -16
- package/dist/workflows/source-semantics.js +1 -93
- package/docs/README.md +1 -1
- package/docs/agents/README.md +3 -3
- package/docs/agents/agent-install.md +1 -1
- package/docs/architecture/adapters.md +17 -31
- package/docs/architecture/architecture.md +4 -5
- package/docs/architecture/improvement.md +52 -21
- package/docs/architecture/internals/improve-workflow.md +15 -8
- package/docs/architecture/internals/indexing.md +1 -1
- package/docs/architecture/internals/search.md +1 -2
- package/docs/architecture/internals/storage-locations.md +5 -5
- package/docs/architecture/persisted-data-compat.md +13 -4
- package/docs/architecture/specs/0.9.0-decisions.md +6 -2
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
- package/docs/architecture/specs/workflow-format-unification.md +1 -6
- package/docs/architecture/testing/manual-testing-checklist.md +20 -29
- package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
- package/docs/architecture/testing/testing-workflow.md +1 -1
- package/docs/architecture/workflow-engine.md +5 -56
- package/docs/guides/author-workflows.md +39 -69
- package/docs/guides/capture-knowledge.md +1 -1
- package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
- package/docs/guides/discover-and-load.md +4 -4
- package/docs/guides/getting-started.md +1 -1
- package/docs/guides/improve-the-library.md +7 -7
- package/docs/guides/recipes/headless-install.md +2 -2
- package/docs/guides/run-workflows.md +1 -1
- package/docs/guides/use-with-any-agent.md +49 -4
- package/docs/integration/bundling-akm.md +7 -6
- package/docs/maintainers/curate-workmap.md +1 -2
- package/docs/maintainers/release-checklist.md +30 -1
- package/docs/posts/akm-command-tour.md +3 -3
- package/docs/product-surface.md +1 -1
- package/docs/reference/README.md +1 -0
- package/docs/reference/api.md +65 -0
- package/docs/reference/asset-types.md +3 -5
- package/docs/reference/bundle-types.md +2 -4
- package/docs/reference/cli.md +129 -171
- package/docs/reference/configuration.md +84 -19
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/env-and-secrets.md +2 -2
- package/docs/reference/supported-formats.md +3 -4
- package/docs/reference/tasks.md +12 -19
- package/docs/reference/website-sources.md +1 -1
- package/docs/reference/workflow-schema.md +89 -317
- package/docs/reference/workflows.md +20 -48
- package/package.json +12 -2
- package/schemas/akm-config.json +2 -243
- package/schemas/akm-task.json +2 -3
- package/schemas/akm-workflow.json +47 -84
- package/dist/assets/improve-strategies/catchup.json +0 -41
- package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
- package/dist/assets/improve-strategies/quick.json +0 -17
- package/dist/assets/improve-strategies/reflect-distill.json +0 -29
- package/dist/assets/improve-strategies/thorough.json +0 -49
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
- package/dist/cli/retired-commands.js +0 -118
- package/dist/commands/tasks/validate.js +0 -176
- package/dist/core/config/legacy-source-shape-shim.js +0 -117
- package/dist/integrations/harnesses/ids.js +0 -28
- package/dist/tasks/source/task-source-v3-frozen.js +0 -424
- package/dist/tasks/source/task-to-v4.js +0 -863
- package/dist/workflows/exec/worktree.js +0 -372
- package/dist/workflows/freeze/child-output-references.js +0 -83
- package/dist/workflows/freeze/identity.js +0 -22
- package/dist/workflows/freeze/targets/script.js +0 -50
- package/dist/workflows/freeze/targets/task.js +0 -124
- package/dist/workflows/github-yaml.js +0 -506
- 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
|
-
*
|
|
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
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
+
catch (error) {
|
|
115
|
+
this.#warnUnavailable(ref.filePath, `cannot open it (${toErrorMessage(error)})`);
|
|
116
|
+
return empty;
|
|
102
117
|
}
|
|
103
118
|
try {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
|
5
|
+
* OpenCode SDK harness descriptor (#564).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
}
|