akm-cli 0.9.0-beta.5 → 0.9.0-beta.51
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 +711 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +6 -2
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +38 -10
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +27 -11
- package/dist/commands/agent/agent-dispatch.js +2 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +17 -4
- package/dist/commands/config-cli.js +18 -2
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +33 -26
- package/dist/commands/env/secret-cli.js +36 -22
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/checks.js +49 -1
- package/dist/commands/health/html-report.js +422 -80
- package/dist/commands/health.js +386 -9
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +81 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +635 -660
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +159 -69
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +124 -2
- package/dist/commands/improve/extract-prompt.js +39 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +389 -40
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +138 -7
- package/dist/commands/improve/improve-cli.js +36 -61
- package/dist/commands/improve/improve-profiles.js +14 -0
- package/dist/commands/improve/improve-result-file.js +14 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2498
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1083 -0
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/preparation.js +1966 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +418 -0
- package/dist/commands/improve/recombine.js +850 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +183 -40
- package/dist/commands/improve/salience.js +438 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain-policies.js +5 -0
- package/dist/commands/proposal/drain.js +43 -50
- package/dist/commands/proposal/proposal-cli.js +21 -31
- package/dist/commands/proposal/proposal.js +5 -0
- package/dist/commands/proposal/propose.js +7 -2
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +189 -63
- package/dist/commands/read/curate.js +414 -94
- package/dist/commands/read/knowledge.js +6 -3
- package/dist/commands/read/search-cli.js +9 -4
- package/dist/commands/read/search.js +10 -6
- package/dist/commands/read/show.js +86 -7
- package/dist/commands/sources/init.js +49 -17
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +43 -45
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/stash-cli.js +28 -40
- package/dist/commands/sources/stash-skeleton.js +23 -8
- package/dist/commands/tasks/tasks-cli.js +19 -27
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +21 -35
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +18 -2
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +0 -5
- package/dist/core/config/config-migration.js +12 -11
- package/dist/core/config/config-schema.js +340 -56
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +28 -7
- package/dist/core/events.js +3 -7
- package/dist/core/improve-types.js +11 -8
- package/dist/core/logs-db.js +10 -66
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +3 -0
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +714 -0
- package/dist/core/state-db.js +525 -474
- package/dist/indexer/db/db.js +439 -247
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +218 -4
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +123 -221
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +19 -6
- package/dist/indexer/search/ranking-contributors.js +22 -0
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/indexer/search/search-source.js +17 -18
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/integrations/agent/config.js +6 -53
- package/dist/integrations/agent/index.js +2 -18
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +45 -15
- package/dist/llm/embedder.js +42 -3
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/feature-gate.js +8 -4
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +74 -2
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/text/helpers.js +16 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +22 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2617 -1961
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
- package/dist/setup/setup.js +29 -8
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/registry-cache.js +92 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +5 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +37 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +1 -117
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +3 -2
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +13 -11
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
|
@@ -4,6 +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 { openDatabase } from "../../../storage/database.js";
|
|
7
8
|
import { extractInlineRefMentions } from "../../session-logs/inline-refs.js";
|
|
8
9
|
function getOpenCodeBaseDir() {
|
|
9
10
|
if (process.platform === "darwin") {
|
|
@@ -12,20 +13,49 @@ function getOpenCodeBaseDir() {
|
|
|
12
13
|
return path.join(os.homedir(), ".local", "share", "opencode");
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
|
-
* Opencode storage
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Opencode storage layouts:
|
|
17
|
+
*
|
|
18
|
+
* SQLite (current, observed 2026-06): `<base>/opencode.db` — a Drizzle-managed
|
|
19
|
+
* database with `session` / `message` / `part` tables. Message text lives in
|
|
20
|
+
* `part` rows (`data` JSON, `type: "text"`); `message.data` holds role/timing.
|
|
21
|
+
* This is the layout current opencode builds write; it is preferred whenever
|
|
22
|
+
* `opencode.db` exists.
|
|
23
|
+
*
|
|
24
|
+
* JSON files (legacy, observed 2026-05): `<base>/storage/session/<projectId>/
|
|
25
|
+
* <sessionId>.json` (metadata) + `<base>/storage/message/<sessionId>/
|
|
26
|
+
* <messageId>.json` (one per message). Read only when `opencode.db` is absent.
|
|
18
27
|
*
|
|
19
28
|
* Older builds wrote logs directly into `<base>/log/` and `<base>/*.log`;
|
|
20
29
|
* those are still scanned by {@link OpenCodeProvider.readEvents} for
|
|
21
30
|
* backward compatibility with the existing failure-pattern aggregator.
|
|
22
31
|
*/
|
|
32
|
+
/** Filename of opencode's SQLite session store, relative to its base dir. */
|
|
33
|
+
const OPENCODE_DB_FILENAME = "opencode.db";
|
|
23
34
|
export class OpenCodeProvider {
|
|
24
35
|
name = "opencode";
|
|
25
36
|
#baseDir = getOpenCodeBaseDir();
|
|
26
37
|
isAvailable() {
|
|
27
38
|
return fs.existsSync(this.#baseDir);
|
|
28
39
|
}
|
|
40
|
+
/** Absolute path to opencode's SQLite store under `base`. */
|
|
41
|
+
#dbPath(base) {
|
|
42
|
+
return path.join(base, OPENCODE_DB_FILENAME);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Directories/files opencode writes session data under. Returns the base dir
|
|
46
|
+
* when the SQLite store (`opencode.db`) exists, the legacy JSON session root
|
|
47
|
+
* (`<base>/storage/session`) when present, or both during a migration overlap.
|
|
48
|
+
* Empty when neither exists. See {@link SessionLogHarness.watchRoots}.
|
|
49
|
+
*/
|
|
50
|
+
watchRoots() {
|
|
51
|
+
const roots = [];
|
|
52
|
+
if (fs.existsSync(this.#dbPath(this.#baseDir)))
|
|
53
|
+
roots.push(this.#baseDir);
|
|
54
|
+
const sessionRoot = path.join(this.#baseDir, "storage", "session");
|
|
55
|
+
if (fs.existsSync(sessionRoot))
|
|
56
|
+
roots.push(sessionRoot);
|
|
57
|
+
return roots;
|
|
58
|
+
}
|
|
29
59
|
*readEvents(input) {
|
|
30
60
|
// Legacy behavior: stream raw log lines from the top-level dir and `log/`
|
|
31
61
|
// subdirectory. Kept to keep `getExecutionLogCandidates` working without
|
|
@@ -82,6 +112,9 @@ export class OpenCodeProvider {
|
|
|
82
112
|
listSessions(input = {}) {
|
|
83
113
|
const base = input.location ?? this.#baseDir;
|
|
84
114
|
const sinceMs = input.sinceMs ?? 0;
|
|
115
|
+
const dbPath = this.#dbPath(base);
|
|
116
|
+
if (fs.existsSync(dbPath))
|
|
117
|
+
return this.#listSessionsFromDb(dbPath, sinceMs);
|
|
85
118
|
const sessionRoot = path.join(base, "storage", "session");
|
|
86
119
|
if (!fs.existsSync(sessionRoot))
|
|
87
120
|
return [];
|
|
@@ -142,6 +175,8 @@ export class OpenCodeProvider {
|
|
|
142
175
|
return summaries.sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0));
|
|
143
176
|
}
|
|
144
177
|
readSession(ref) {
|
|
178
|
+
if (path.basename(ref.filePath) === OPENCODE_DB_FILENAME)
|
|
179
|
+
return this.#readSessionFromDb(ref);
|
|
145
180
|
let meta = {};
|
|
146
181
|
try {
|
|
147
182
|
meta = JSON.parse(fs.readFileSync(ref.filePath, "utf8"));
|
|
@@ -199,6 +234,141 @@ export class OpenCodeProvider {
|
|
|
199
234
|
inlineRefs,
|
|
200
235
|
};
|
|
201
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* List sessions from the SQLite store. `filePath` on each summary is the
|
|
239
|
+
* `opencode.db` path so {@link readSession} can route back to the DB reader.
|
|
240
|
+
* Returns `[]` (never throws) when the DB is unreadable or lacks the expected
|
|
241
|
+
* schema — callers treat a missing harness as "no sessions".
|
|
242
|
+
*/
|
|
243
|
+
#listSessionsFromDb(dbPath, sinceMs) {
|
|
244
|
+
let db;
|
|
245
|
+
try {
|
|
246
|
+
db = openDatabase(dbPath, { readonly: true, create: false });
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const rows = db
|
|
253
|
+
.prepare("SELECT id, title, directory, time_created, time_updated FROM session WHERE time_updated >= ? ORDER BY time_updated DESC")
|
|
254
|
+
.all(sinceMs);
|
|
255
|
+
return rows.map((r) => {
|
|
256
|
+
const startedAt = typeof r.time_created === "number" ? r.time_created : undefined;
|
|
257
|
+
const endedAt = typeof r.time_updated === "number" ? r.time_updated : undefined;
|
|
258
|
+
const title = typeof r.title === "string" && r.title.length > 0 ? r.title : undefined;
|
|
259
|
+
const projectHint = typeof r.directory === "string" && r.directory.length > 0 ? r.directory : undefined;
|
|
260
|
+
return {
|
|
261
|
+
harness: this.name,
|
|
262
|
+
sessionId: r.id,
|
|
263
|
+
filePath: dbPath,
|
|
264
|
+
...(startedAt !== undefined ? { startedAt } : {}),
|
|
265
|
+
...(endedAt !== undefined ? { endedAt } : {}),
|
|
266
|
+
...(projectHint ? { projectHint } : {}),
|
|
267
|
+
...(title ? { title } : {}),
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Missing `session` table / unexpected schema — treat as no sessions.
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
db.close();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Read one session from the SQLite store. Message text lives in `part` rows
|
|
281
|
+
* (`type: "text"`); `message.data` carries role + timing. One event per
|
|
282
|
+
* message, text-parts concatenated in time order. Returns empty events
|
|
283
|
+
* (never throws) when the DB is unreadable.
|
|
284
|
+
*/
|
|
285
|
+
#readSessionFromDb(ref) {
|
|
286
|
+
const emptyRef = { harness: this.name, sessionId: ref.sessionId, filePath: ref.filePath };
|
|
287
|
+
let db;
|
|
288
|
+
try {
|
|
289
|
+
db = openDatabase(ref.filePath, { readonly: true, create: false });
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return { ref: emptyRef, events: [], inlineRefs: [] };
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const meta = db
|
|
296
|
+
.prepare("SELECT title, directory, time_created, time_updated FROM session WHERE id = ?")
|
|
297
|
+
.get(ref.sessionId);
|
|
298
|
+
const startedAt = typeof meta?.time_created === "number" ? meta.time_created : undefined;
|
|
299
|
+
const endedAt = typeof meta?.time_updated === "number" ? meta.time_updated : undefined;
|
|
300
|
+
const title = typeof meta?.title === "string" && meta.title.length > 0 ? meta.title : undefined;
|
|
301
|
+
const projectHint = typeof meta?.directory === "string" && meta.directory.length > 0 ? meta.directory : undefined;
|
|
302
|
+
const messages = db
|
|
303
|
+
.prepare("SELECT id, data, time_created FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC")
|
|
304
|
+
.all(ref.sessionId);
|
|
305
|
+
const parts = db
|
|
306
|
+
.prepare("SELECT message_id, data FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC")
|
|
307
|
+
.all(ref.sessionId);
|
|
308
|
+
// Group text-part bodies by their parent message.
|
|
309
|
+
const textByMessage = new Map();
|
|
310
|
+
for (const part of parts) {
|
|
311
|
+
let parsed;
|
|
312
|
+
try {
|
|
313
|
+
parsed = JSON.parse(part.data);
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (parsed?.type !== "text")
|
|
319
|
+
continue;
|
|
320
|
+
const text = parsed.text;
|
|
321
|
+
if (typeof text !== "string" || text.length < 1)
|
|
322
|
+
continue;
|
|
323
|
+
const bucket = textByMessage.get(part.message_id) ?? [];
|
|
324
|
+
bucket.push(text);
|
|
325
|
+
textByMessage.set(part.message_id, bucket);
|
|
326
|
+
}
|
|
327
|
+
const events = [];
|
|
328
|
+
const inlineRefs = [];
|
|
329
|
+
for (const message of messages) {
|
|
330
|
+
let mdata = {};
|
|
331
|
+
try {
|
|
332
|
+
mdata = JSON.parse(message.data);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// role/timing unavailable — fall through with defaults
|
|
336
|
+
}
|
|
337
|
+
const role = typeof mdata.role === "string" ? mdata.role : "unknown";
|
|
338
|
+
const mtime = mdata.time?.created;
|
|
339
|
+
const ts = typeof mtime === "number"
|
|
340
|
+
? mtime
|
|
341
|
+
: typeof message.time_created === "number"
|
|
342
|
+
? message.time_created
|
|
343
|
+
: undefined;
|
|
344
|
+
const text = (textByMessage.get(message.id) ?? []).join("\n").trim();
|
|
345
|
+
if (text.length < 1)
|
|
346
|
+
continue;
|
|
347
|
+
events.push({ harness: this.name, text, ts, sessionId: ref.sessionId, role, filePath: ref.filePath });
|
|
348
|
+
inlineRefs.push(...extractInlineRefMentions(text, ts));
|
|
349
|
+
}
|
|
350
|
+
events.sort((a, b) => (a.ts ?? 0) - (b.ts ?? 0));
|
|
351
|
+
return {
|
|
352
|
+
ref: {
|
|
353
|
+
harness: this.name,
|
|
354
|
+
sessionId: ref.sessionId,
|
|
355
|
+
filePath: ref.filePath,
|
|
356
|
+
...(startedAt !== undefined ? { startedAt } : {}),
|
|
357
|
+
...(endedAt !== undefined ? { endedAt } : {}),
|
|
358
|
+
...(projectHint ? { projectHint } : {}),
|
|
359
|
+
...(title ? { title } : {}),
|
|
360
|
+
},
|
|
361
|
+
events,
|
|
362
|
+
inlineRefs,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return { ref: emptyRef, events: [], inlineRefs: [] };
|
|
367
|
+
}
|
|
368
|
+
finally {
|
|
369
|
+
db.close();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
202
372
|
/**
|
|
203
373
|
* Derive opencode base dir from a session metadata file path so a caller
|
|
204
374
|
* passing a custom `--location` can still find the message dir.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* OpenCode SDK harness (#564).
|
|
6
6
|
*
|
|
7
7
|
* Per-harness barrel for the SDK-mode dispatch path:
|
|
8
|
-
* - agent runner → ./sdk-runner.ts (runOpencodeSdk
|
|
8
|
+
* - agent runner → ./sdk-runner.ts (runOpencodeSdk)
|
|
9
9
|
*
|
|
10
10
|
* It also defines {@link OpencodeSdkHarness}, the {@link AkmHarness} descriptor
|
|
11
11
|
* that `HARNESS_REGISTRY` registers.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* names. Canonical id is `'opencode-sdk'` with no alias.
|
|
18
18
|
*/
|
|
19
19
|
import { BaseHarness } from "../types.js";
|
|
20
|
-
export { closeServer,
|
|
20
|
+
export { closeServer, runOpencodeSdk } from "./sdk-runner.js";
|
|
21
21
|
function caps(c) {
|
|
22
22
|
return {
|
|
23
23
|
sessionLogs: false,
|
|
@@ -230,5 +230,3 @@ export async function runOpencodeSdk(profile, prompt, opts = {}, llmConfig) {
|
|
|
230
230
|
await client.session.delete({ path: { id: sessionId } }).catch(() => { });
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
|
-
/** @deprecated Use {@link runOpencodeSdk} instead. */
|
|
234
|
-
export const runAgentSdk = runOpencodeSdk;
|
|
@@ -28,6 +28,22 @@ const ERROR_PATTERNS = /error|failed|exception|cannot|undefined|null pointer|ENO
|
|
|
28
28
|
export function getAvailableHarnesses() {
|
|
29
29
|
return HARNESSES.filter((harness) => harness.isAvailable());
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Map each available harness to its `{ harnessName, roots }` watch target,
|
|
33
|
+
* skipping harnesses that expose no roots (absent `watchRoots()` or an empty
|
|
34
|
+
* result). This is the one stable entry point the watcher uses so it never
|
|
35
|
+
* reaches into providers directly.
|
|
36
|
+
*/
|
|
37
|
+
export function getWatchTargets() {
|
|
38
|
+
const targets = [];
|
|
39
|
+
for (const harness of getAvailableHarnesses()) {
|
|
40
|
+
const roots = harness.watchRoots?.() ?? [];
|
|
41
|
+
if (roots.length === 0)
|
|
42
|
+
continue;
|
|
43
|
+
targets.push({ harnessName: harness.name, roots });
|
|
44
|
+
}
|
|
45
|
+
return targets;
|
|
46
|
+
}
|
|
31
47
|
export function normalizeSessionTopic(text) {
|
|
32
48
|
const normalized = text.replace(/\s+/g, " ").trim().toLowerCase();
|
|
33
49
|
if (normalized.length < 10)
|
package/dist/llm/client.js
CHANGED
|
@@ -103,35 +103,65 @@ function retryBackoffMs() {
|
|
|
103
103
|
return RETRY_BACKOFF_MIN_MS + Math.random() * (RETRY_BACKOFF_MAX_MS - RETRY_BACKOFF_MIN_MS);
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
-
* Detect whether an error message indicates a context
|
|
107
|
-
*
|
|
108
|
-
*
|
|
106
|
+
* Detect whether an error message indicates a context size exceeded condition.
|
|
107
|
+
* Covers common patterns from OpenAI-compatible APIs (LM Studio, Ollama, etc).
|
|
108
|
+
*
|
|
109
|
+
* Requires BOTH a context keyword AND token-count/overflow evidence so that
|
|
110
|
+
* model prose merely mentioning "context size" / "context length" (e.g. gemma
|
|
111
|
+
* narrating about a document) does not get misclassified as a provider
|
|
112
|
+
* context-limit error (#496).
|
|
113
|
+
*
|
|
114
|
+
* Canonical home: `graph-extract.ts` re-exports this so the index-pass
|
|
115
|
+
* graph extractor and the retry classifier (`isRetryable`) share one
|
|
116
|
+
* definition — retrying a context overflow cannot shrink the input, so it
|
|
117
|
+
* must never be retried.
|
|
109
118
|
*/
|
|
110
|
-
function
|
|
119
|
+
export function isContextSizeError(message) {
|
|
111
120
|
const lower = message.toLowerCase();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
const contextKw = /context (size|length|window)|prompt too long|exceeds.*context/.test(lower);
|
|
122
|
+
if (!contextKw) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const evidence = /\b\d+\s*(token|tokens|tk)\b/.test(lower) ||
|
|
126
|
+
/max(imum)?\s+(context|token|input)/.test(lower) ||
|
|
127
|
+
/exceeded|over.*limit|too.*long/.test(lower);
|
|
128
|
+
return evidence;
|
|
118
129
|
}
|
|
119
130
|
/**
|
|
120
131
|
* Decide whether a first-attempt {@link LlmCallError} is eligible for a single
|
|
121
132
|
* retry. Retryable: HTTP 5xx (`provider_error` with statusCode >= 500) and
|
|
122
|
-
* `network_error` whose message looks like a transient connection
|
|
123
|
-
*
|
|
124
|
-
*
|
|
133
|
+
* `network_error` whose message looks like a transient connection drop.
|
|
134
|
+
* NOT retryable: 4xx, `rate_limited` (429), `timeout`, `parse_error`, and
|
|
135
|
+
* context-overflow-classified errors.
|
|
136
|
+
*
|
|
137
|
+
* The connection-drop heuristic covers the substrings emitted across runtimes
|
|
138
|
+
* for a mid-flight socket close:
|
|
139
|
+
* - `ECONNRESET` / `EPIPE` — Node/libuv socket reset codes
|
|
140
|
+
* - `fetch failed` — undici's generic wrapper message
|
|
141
|
+
* - `socket connection was closed` — Bun's message for a dropped connection
|
|
142
|
+
* (e.g. "The socket connection was closed unexpectedly.")
|
|
143
|
+
* - `terminated` / `other side closed` — undici's phrasings for the same
|
|
144
|
+
*
|
|
145
|
+
* These all describe a transient transport failure where a second attempt can
|
|
146
|
+
* legitimately succeed, which is exactly the case a single bounded retry is
|
|
147
|
+
* meant to absorb. Before this list was widened, Bun's "socket connection was
|
|
148
|
+
* closed unexpectedly" fell through unretried and surfaced as a recurring
|
|
149
|
+
* failure in the improve/reflect and capability-probe flows.
|
|
125
150
|
*/
|
|
126
151
|
function isRetryable(err) {
|
|
127
|
-
if (
|
|
152
|
+
if (isContextSizeError(err.message))
|
|
128
153
|
return false;
|
|
129
154
|
if (err.code === "provider_error") {
|
|
130
155
|
return typeof err.statusCode === "number" && err.statusCode >= 500;
|
|
131
156
|
}
|
|
132
157
|
if (err.code === "network_error") {
|
|
133
158
|
const lower = err.message.toLowerCase();
|
|
134
|
-
return lower.includes("econnreset") ||
|
|
159
|
+
return (lower.includes("econnreset") ||
|
|
160
|
+
lower.includes("epipe") ||
|
|
161
|
+
lower.includes("fetch failed") ||
|
|
162
|
+
lower.includes("socket connection was closed") ||
|
|
163
|
+
lower.includes("terminated") ||
|
|
164
|
+
lower.includes("other side closed"));
|
|
135
165
|
}
|
|
136
166
|
return false;
|
|
137
167
|
}
|
package/dist/llm/embedder.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
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
|
import { embedCacheKey, getCachedEmbedding, setCachedEmbedding } from "./embedders/cache.js";
|
|
5
|
-
import {
|
|
5
|
+
import { DETERMINISTIC_EMBED_MODEL_ID, deterministicEmbed, isDeterministicEmbedEnabled, } from "./embedders/deterministic.js";
|
|
6
|
+
import { DEFAULT_LOCAL_MODEL, isTransformersAvailable, LocalEmbedder } from "./embedders/local.js";
|
|
6
7
|
import { hasRemoteEndpoint, RemoteEmbedder } from "./embedders/remote.js";
|
|
7
8
|
// ── Re-exports (public API) ─────────────────────────────────────────────────
|
|
8
9
|
export { clearEmbeddingCache } from "./embedders/cache.js";
|
|
@@ -39,6 +40,10 @@ export function resetLocalEmbedder() {
|
|
|
39
40
|
* and embedding config. Repeated identical queries return the cached vector.
|
|
40
41
|
*/
|
|
41
42
|
export async function embed(text, embeddingConfig, signal) {
|
|
43
|
+
// Deterministic mode (env-gated, test/bench only): model-free, stable.
|
|
44
|
+
if (isDeterministicEmbedEnabled()) {
|
|
45
|
+
return deterministicEmbed(text);
|
|
46
|
+
}
|
|
42
47
|
const key = embedCacheKey(text, embeddingConfig);
|
|
43
48
|
const cached = getCachedEmbedding(key);
|
|
44
49
|
if (cached)
|
|
@@ -52,16 +57,26 @@ export async function embed(text, embeddingConfig, signal) {
|
|
|
52
57
|
/**
|
|
53
58
|
* Generate embeddings for multiple texts in batch.
|
|
54
59
|
* Uses the OpenAI-compatible batch API for remote endpoints (batches of 100).
|
|
55
|
-
*
|
|
60
|
+
* Uses the LocalEmbedder.embedBatch path for the local transformer pipeline,
|
|
61
|
+
* which processes texts in chunks of 32 for genuine batched inference.
|
|
56
62
|
*/
|
|
57
63
|
export async function embedBatch(texts, embeddingConfig, signal) {
|
|
58
64
|
if (texts.length === 0)
|
|
59
65
|
return [];
|
|
66
|
+
// Deterministic mode (env-gated, test/bench only): model-free, stable.
|
|
67
|
+
if (isDeterministicEmbedEnabled()) {
|
|
68
|
+
return texts.map((t) => deterministicEmbed(t));
|
|
69
|
+
}
|
|
60
70
|
if (embeddingConfig && hasRemoteEndpoint(embeddingConfig)) {
|
|
61
71
|
return new RemoteEmbedder(embeddingConfig).embedBatch(texts, signal);
|
|
62
72
|
}
|
|
63
|
-
// Local transformer:
|
|
73
|
+
// Local transformer: use the batched path (chunks of 32 via LocalEmbedder).
|
|
74
|
+
// When a localModel override is set we cannot share the singleton (which uses
|
|
75
|
+
// the default model), so fall back to per-text embedWithModel in that case.
|
|
64
76
|
const localModel = embeddingConfig?.localModel;
|
|
77
|
+
if (!localModel) {
|
|
78
|
+
return getLocalEmbedder().embedBatch(texts, signal);
|
|
79
|
+
}
|
|
65
80
|
const results = [];
|
|
66
81
|
for (const text of texts) {
|
|
67
82
|
if (signal?.aborted) {
|
|
@@ -77,11 +92,35 @@ export async function embedBatch(texts, embeddingConfig, signal) {
|
|
|
77
92
|
// facade and its `@huggingface/transformers` import chain. Re-export
|
|
78
93
|
// preserves the existing public API.
|
|
79
94
|
export { cosineSimilarity } from "./embedders/types.js";
|
|
95
|
+
// ── Model ID resolution ─────────────────────────────────────────────────────
|
|
96
|
+
/**
|
|
97
|
+
* Derive a stable string identifier for the embedding model in use.
|
|
98
|
+
* This is the `model_id` stored in `body_embeddings` (and used for the
|
|
99
|
+
* drop-all-on-mismatch purge when the model changes).
|
|
100
|
+
*
|
|
101
|
+
* Rules:
|
|
102
|
+
* - Remote endpoint: use `config.model` (the API-level model name).
|
|
103
|
+
* - Local transformers: use `config.localModel ?? DEFAULT_LOCAL_MODEL`.
|
|
104
|
+
* - No config: use `DEFAULT_LOCAL_MODEL` (the shared singleton model).
|
|
105
|
+
*/
|
|
106
|
+
export function resolveEmbeddingModelId(embeddingConfig) {
|
|
107
|
+
if (isDeterministicEmbedEnabled())
|
|
108
|
+
return DETERMINISTIC_EMBED_MODEL_ID;
|
|
109
|
+
if (!embeddingConfig)
|
|
110
|
+
return DEFAULT_LOCAL_MODEL;
|
|
111
|
+
if (hasRemoteEndpoint(embeddingConfig))
|
|
112
|
+
return embeddingConfig.model ?? "remote";
|
|
113
|
+
return embeddingConfig.localModel ?? DEFAULT_LOCAL_MODEL;
|
|
114
|
+
}
|
|
80
115
|
// ── Availability check ──────────────────────────────────────────────────────
|
|
81
116
|
/**
|
|
82
117
|
* Check whether embedding is available with a detailed reason on failure.
|
|
83
118
|
*/
|
|
84
119
|
export async function checkEmbeddingAvailability(embeddingConfig) {
|
|
120
|
+
// Deterministic mode (env-gated): always available — no model, no network.
|
|
121
|
+
if (isDeterministicEmbedEnabled()) {
|
|
122
|
+
return { available: true };
|
|
123
|
+
}
|
|
85
124
|
if (embeddingConfig && hasRemoteEndpoint(embeddingConfig)) {
|
|
86
125
|
try {
|
|
87
126
|
await new RemoteEmbedder(embeddingConfig).embed("test");
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
/** Env var that switches the whole embedding facade into deterministic mode. */
|
|
5
|
+
export const DETERMINISTIC_EMBED_ENV = "AKM_EMBED_DETERMINISTIC";
|
|
6
|
+
/**
|
|
7
|
+
* Vector width. Matches the default local model (`bge-small`, 384 dims) so the
|
|
8
|
+
* index DB's embedding column and sqlite-vec table dimensions line up without
|
|
9
|
+
* any extra config.
|
|
10
|
+
*/
|
|
11
|
+
export const DETERMINISTIC_EMBED_DIM = 384;
|
|
12
|
+
/**
|
|
13
|
+
* Stable model id reported for deterministic mode. Used as the embedding
|
|
14
|
+
* `model_id` and folded into the provider fingerprint so a deterministic index
|
|
15
|
+
* is never confused with a real-model index (and vice versa).
|
|
16
|
+
*/
|
|
17
|
+
export const DETERMINISTIC_EMBED_MODEL_ID = "akm-deterministic-hash-v1";
|
|
18
|
+
/** True when deterministic embedding is enabled via env. */
|
|
19
|
+
export function isDeterministicEmbedEnabled() {
|
|
20
|
+
return process.env[DETERMINISTIC_EMBED_ENV] === "1";
|
|
21
|
+
}
|
|
22
|
+
/** FNV-1a 32-bit hash. Platform- and version-stable. */
|
|
23
|
+
function fnv1a(str) {
|
|
24
|
+
let h = 0x811c9dc5;
|
|
25
|
+
for (let i = 0; i < str.length; i++) {
|
|
26
|
+
h ^= str.charCodeAt(i);
|
|
27
|
+
// 32-bit FNV prime multiply via shifts to stay in uint32.
|
|
28
|
+
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
|
|
29
|
+
}
|
|
30
|
+
return h >>> 0;
|
|
31
|
+
}
|
|
32
|
+
/** Lowercase, split on non-alphanumeric, drop empties. */
|
|
33
|
+
function tokenize(text) {
|
|
34
|
+
return text
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.split(/[^a-z0-9]+/)
|
|
37
|
+
.filter((t) => t.length > 0);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Deterministically embed `text` into a unit-length vector of width `dim`
|
|
41
|
+
* using feature hashing. Empty / token-less input returns a fixed unit
|
|
42
|
+
* vector so cosine similarity never sees a zero vector (NaN guard).
|
|
43
|
+
*/
|
|
44
|
+
export function deterministicEmbed(text, dim = DETERMINISTIC_EMBED_DIM) {
|
|
45
|
+
const vec = new Array(dim).fill(0);
|
|
46
|
+
const tokens = tokenize(text);
|
|
47
|
+
for (const tok of tokens) {
|
|
48
|
+
const h = fnv1a(tok);
|
|
49
|
+
const idx = h % dim;
|
|
50
|
+
// Use a higher bit for the sign so it is independent of the bucket index.
|
|
51
|
+
const sign = (h >>> 16) & 1 ? 1 : -1;
|
|
52
|
+
vec[idx] += sign;
|
|
53
|
+
}
|
|
54
|
+
let norm = 0;
|
|
55
|
+
for (const v of vec)
|
|
56
|
+
norm += v * v;
|
|
57
|
+
norm = Math.sqrt(norm);
|
|
58
|
+
if (norm === 0) {
|
|
59
|
+
// No usable tokens — return a fixed, stable unit vector.
|
|
60
|
+
vec[0] = 1;
|
|
61
|
+
return vec;
|
|
62
|
+
}
|
|
63
|
+
for (let i = 0; i < dim; i++)
|
|
64
|
+
vec[i] /= norm;
|
|
65
|
+
return vec;
|
|
66
|
+
}
|
|
@@ -19,8 +19,24 @@ import { getDirname, resolveModule } from "../../runtime.js";
|
|
|
19
19
|
* `all-MiniLM-L6-v2` at the same 384-dimension footprint.
|
|
20
20
|
*/
|
|
21
21
|
export const DEFAULT_LOCAL_MODEL = "Xenova/bge-small-en-v1.5";
|
|
22
|
+
/** Type-guard: true when the value looks like a batch Tensor (has .dims). */
|
|
23
|
+
function isBatchTensor(v) {
|
|
24
|
+
return (v !== null &&
|
|
25
|
+
typeof v === "object" &&
|
|
26
|
+
"data" in v &&
|
|
27
|
+
"dims" in v &&
|
|
28
|
+
Array.isArray(v.dims) &&
|
|
29
|
+
v.dims.length >= 2);
|
|
30
|
+
}
|
|
22
31
|
const LOCAL_EMBEDDER_DTYPE = "fp32";
|
|
23
32
|
const LOCAL_EMBEDDER_FALLBACK_DTYPE = "auto";
|
|
33
|
+
/**
|
|
34
|
+
* Maximum texts per batch for the local transformers pipeline. The pipeline
|
|
35
|
+
* can run genuine batched inference over a string array; 32 is a safe default
|
|
36
|
+
* that fits well inside most model context budgets while providing 10–50×
|
|
37
|
+
* throughput improvement over one-at-a-time calls on the cold minority.
|
|
38
|
+
*/
|
|
39
|
+
const LOCAL_BATCH_SIZE = 32;
|
|
24
40
|
/**
|
|
25
41
|
* Return the local model name that will be used for embedding.
|
|
26
42
|
* When `overrideModel` is provided it takes precedence; otherwise
|
|
@@ -77,15 +93,63 @@ export class LocalEmbedder {
|
|
|
77
93
|
}
|
|
78
94
|
return this.embedWithModel(text, this.defaultModel);
|
|
79
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Embed a batch of texts. Processes in chunks of `LOCAL_BATCH_SIZE` (32) so
|
|
98
|
+
* the transformers pipeline can run genuine batched inference rather than one
|
|
99
|
+
* call per text. Falls back to one-at-a-time if the pipeline does not support
|
|
100
|
+
* array input (older versions of @huggingface/transformers). Each chunk is
|
|
101
|
+
* checked against the AbortSignal between calls.
|
|
102
|
+
*/
|
|
80
103
|
async embedBatch(texts, signal) {
|
|
81
104
|
if (texts.length === 0)
|
|
82
105
|
return [];
|
|
106
|
+
if (signal?.aborted) {
|
|
107
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
108
|
+
}
|
|
109
|
+
const pipeline = await this.getPipeline(this.defaultModel);
|
|
83
110
|
const results = [];
|
|
84
|
-
for (
|
|
111
|
+
for (let i = 0; i < texts.length; i += LOCAL_BATCH_SIZE) {
|
|
85
112
|
if (signal?.aborted) {
|
|
86
113
|
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
87
114
|
}
|
|
88
|
-
|
|
115
|
+
const chunk = texts.slice(i, i + LOCAL_BATCH_SIZE);
|
|
116
|
+
try {
|
|
117
|
+
// @huggingface/transformers feature-extraction pipeline accepts a
|
|
118
|
+
// string[] and returns a batch Tensor (NOT an Array<{data}>).
|
|
119
|
+
// The Tensor has .data (flat Float32Array, length = batch * dim) and
|
|
120
|
+
// .dims = [batch, dim]. Slice .data into per-row vectors using .dims.
|
|
121
|
+
const batchResult = await pipeline(chunk, {
|
|
122
|
+
pooling: "mean",
|
|
123
|
+
normalize: true,
|
|
124
|
+
});
|
|
125
|
+
if (isBatchTensor(batchResult)) {
|
|
126
|
+
const dim = batchResult.dims[1];
|
|
127
|
+
for (let row = 0; row < chunk.length; row++) {
|
|
128
|
+
results.push(Array.from(batchResult.data.subarray(row * dim, (row + 1) * dim)));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (Array.isArray(batchResult)) {
|
|
132
|
+
// Older versions of @huggingface/transformers returned Array<{data}>.
|
|
133
|
+
for (const r of batchResult) {
|
|
134
|
+
results.push(Array.from(r.data));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Single-text result returned for a chunk — should not happen for
|
|
139
|
+
// string[] input, but handle defensively.
|
|
140
|
+
throw new Error("unexpected pipeline return shape for batch input");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Fallback: process one-at-a-time (older pipeline versions or mismatched
|
|
145
|
+
// return type). Fail-open per text: a single failure aborts the chunk.
|
|
146
|
+
for (const text of chunk) {
|
|
147
|
+
if (signal?.aborted) {
|
|
148
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
149
|
+
}
|
|
150
|
+
results.push(await this.embedWithModel(text, this.defaultModel));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
89
153
|
}
|
|
90
154
|
return results;
|
|
91
155
|
}
|
package/dist/llm/feature-gate.js
CHANGED
|
@@ -30,10 +30,14 @@ const FEATURE_LOCATION = {
|
|
|
30
30
|
proposal_quality_gate: (cfg) => cfg.profiles?.improve?.default?.processes?.reflect?.qualityGate?.enabled ?? false,
|
|
31
31
|
// Legacy default: false
|
|
32
32
|
memory_contradiction_detection: (cfg) => cfg.profiles?.improve?.default?.processes?.consolidate?.contradictionDetection?.enabled ?? false,
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
33
|
+
// Always on at the LLM-wrapper level. Enablement is decided ONCE at the
|
|
34
|
+
// extract entry point (`akmExtract`): the `extract.enabled` process toggle
|
|
35
|
+
// gates extract as a STAGE of `akm improve` (the active improve profile, per
|
|
36
|
+
// #593/#594), while an explicit `akm extract` command always runs. Gating the
|
|
37
|
+
// inner LLM calls on `default.processes.extract.enabled` here was a footgun —
|
|
38
|
+
// dropping extract from the daily improve profile silently disabled the
|
|
39
|
+
// standalone `akm extract` command. (cfg unused — kept for resolver signature.)
|
|
40
|
+
session_extraction: (_cfg) => true,
|
|
37
41
|
};
|
|
38
42
|
/**
|
|
39
43
|
* Pure predicate: is the named feature gate enabled in `config`?
|