akm-cli 0.9.14 → 0.9.15-beta.2

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 (120) hide show
  1. package/CHANGELOG.md +559 -0
  2. package/STABILITY.md +6 -3
  3. package/dist/akm +54 -1
  4. package/dist/akm-migrate +34 -1
  5. package/dist/assets/prompts/reflect-feedback-framing.md +1 -0
  6. package/dist/assets/prompts/reflect-llm-framed-contract.md +2 -0
  7. package/dist/assets/prompts/reflect-llm-schema-contract.md +2 -0
  8. package/dist/assets/tasks/core/improve.yml +1 -1
  9. package/dist/assets/tasks/core/index-refresh.yml +1 -1
  10. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +1 -1
  11. package/dist/assets/tasks/improve/akm-improve-catchup.yml +1 -1
  12. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
  13. package/dist/assets/tasks/improve/akm-improve-frequent.yml +1 -1
  14. package/dist/assets/tasks/improve/akm-improve-nightly.yml +1 -1
  15. package/dist/cli/retired-commands.js +0 -1
  16. package/dist/cli/shared.js +9 -0
  17. package/dist/cli/unknown-flags.js +1 -0
  18. package/dist/cli.js +40 -3
  19. package/dist/commands/config-cli.js +85 -3
  20. package/dist/commands/env/env-cli.js +1 -42
  21. package/dist/commands/env/env.js +1 -1
  22. package/dist/commands/env/secret-cli.js +1 -2
  23. package/dist/commands/health/checks.js +357 -63
  24. package/dist/commands/health/engine-usage.js +45 -0
  25. package/dist/commands/health/improve-metrics.js +18 -0
  26. package/dist/commands/health/llm-usage.js +41 -1
  27. package/dist/commands/health/plugin-staleness.js +7 -3
  28. package/dist/commands/health/version-drift.js +93 -0
  29. package/dist/commands/health/windows.js +3 -1
  30. package/dist/commands/health.js +44 -9
  31. package/dist/commands/improve/consolidate/chunking.js +4 -2
  32. package/dist/commands/improve/improve-cli.js +99 -5
  33. package/dist/commands/improve/improve-report.js +154 -0
  34. package/dist/commands/improve/improve-result-file.js +45 -33
  35. package/dist/commands/improve/improve-strategies.js +133 -3
  36. package/dist/commands/improve/improve-usage-report.js +182 -0
  37. package/dist/commands/improve/improve.js +40 -3
  38. package/dist/commands/improve/locks.js +28 -78
  39. package/dist/commands/improve/planner.js +1 -0
  40. package/dist/commands/improve/preparation.js +9 -1
  41. package/dist/commands/improve/reflect.js +44 -4
  42. package/dist/commands/models-cli.js +50 -1
  43. package/dist/commands/proposal/repository.js +8 -3
  44. package/dist/commands/proposal/validators/proposal-quality-validators.js +41 -6
  45. package/dist/commands/proposal/validators/proposal-validators.js +24 -0
  46. package/dist/commands/read/search-cli.js +38 -2
  47. package/dist/commands/read/show.js +103 -4
  48. package/dist/commands/sources/info.js +5 -1
  49. package/dist/commands/sources/installed-stashes.js +58 -16
  50. package/dist/commands/sources/self-update.js +2 -2
  51. package/dist/commands/sources/stash-cli.js +48 -0
  52. package/dist/commands/tasks/tasks-cli.js +49 -2
  53. package/dist/commands/workflow-cli.js +86 -12
  54. package/dist/core/asset/markdown-fragments.js +35 -0
  55. package/dist/core/config/config-schema.js +14 -0
  56. package/dist/core/config/config.js +302 -24
  57. package/dist/core/config/schema/embedding.js +41 -0
  58. package/dist/core/env-secret-ref.js +58 -5
  59. package/dist/core/errors.js +30 -0
  60. package/dist/core/file-lock.js +49 -15
  61. package/dist/core/improve-result.js +51 -0
  62. package/dist/core/loopback.js +17 -0
  63. package/dist/core/parent-watchdog.js +64 -0
  64. package/dist/core/paths.js +11 -0
  65. package/dist/core/run-lock.js +107 -0
  66. package/dist/core/sensitive-marker-path.js +19 -0
  67. package/dist/core/state-db.js +74 -14
  68. package/dist/indexer/index-rebuild-lock.js +73 -0
  69. package/dist/indexer/index-writer-lock.js +40 -1
  70. package/dist/indexer/index-written-assets.js +29 -1
  71. package/dist/indexer/indexer.js +93 -29
  72. package/dist/indexer/materialize-embeddings.js +564 -48
  73. package/dist/indexer/search/db-search.js +49 -2
  74. package/dist/indexer/search/search-source.js +23 -1
  75. package/dist/integrations/agent/engine-resolution.js +96 -6
  76. package/dist/integrations/agent/execution-definitions.js +6 -15
  77. package/dist/integrations/agent/execution-lowering.js +6 -1
  78. package/dist/integrations/agent/execution-preparation.js +1 -1
  79. package/dist/integrations/agent/model-map.js +123 -20
  80. package/dist/integrations/agent/prompts.js +40 -8
  81. package/dist/integrations/agent/runner-dispatch.js +9 -3
  82. package/dist/integrations/agent/runner.js +2 -0
  83. package/dist/llm/client.js +8 -3
  84. package/dist/llm/embedder.js +20 -8
  85. package/dist/llm/embedders/local.js +10 -2
  86. package/dist/llm/embedders/remote.js +497 -32
  87. package/dist/output/shapes/helpers.js +38 -2
  88. package/dist/output/shapes/models-list.js +16 -0
  89. package/dist/output/shapes/passthrough.js +2 -0
  90. package/dist/output/shapes.js +4 -0
  91. package/dist/output/text/command-format.js +29 -0
  92. package/dist/output/text/helpers.js +1 -1
  93. package/dist/output/text/improve-report.js +27 -0
  94. package/dist/{commands/env/marker-path.js → output/text/models.js} +4 -3
  95. package/dist/output/text/show-format.js +4 -0
  96. package/dist/output/text.js +4 -0
  97. package/dist/scripts/akm-migrate-node.js +25146 -21759
  98. package/dist/scripts/akm-migrate.js +24271 -20885
  99. package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
  100. package/dist/storage/repositories/improve-runs-repository.js +34 -0
  101. package/dist/storage/repositories/index-fts-repository.js +49 -6
  102. package/dist/storage/repositories/index-schema.js +16 -0
  103. package/dist/storage/repositories/index-vec-repository.js +30 -0
  104. package/dist/storage/repositories/workflow-runs-repository.js +55 -18
  105. package/dist/tasks/backends/cron.js +14 -7
  106. package/dist/tasks/run/run-native-task.js +23 -1
  107. package/dist/tasks/run/run-workflow-task.js +16 -0
  108. package/dist/workflows/exec/child-workflow.js +2 -2
  109. package/dist/workflows/exec/dispatch-redaction.js +21 -9
  110. package/dist/workflows/exec/run-workflow.js +6 -5
  111. package/dist/workflows/runtime/runs.js +33 -5
  112. package/docs/migration/release-notes/0.9.15.md +133 -0
  113. package/docs/migration/release-notes/README.md +5 -0
  114. package/docs/reference/cli.md +271 -30
  115. package/docs/reference/configuration.md +234 -21
  116. package/docs/reference/data-and-telemetry.md +8 -0
  117. package/docs/reference/tasks.md +16 -1
  118. package/docs/reference/workflow-schema.md +5 -1
  119. package/package.json +1 -1
  120. package/schemas/akm-config.json +47 -0
@@ -25,6 +25,7 @@
25
25
  * `frontmatter` is optional — the proposal queue parses it from `content`
26
26
  * during validation. We carry it through if the agent supplies it.
27
27
  */
28
+ import reflectFeedbackFraming from "../../assets/prompts/reflect-feedback-framing.md" with { type: "text" };
28
29
  import reflectLlmFramedContract from "../../assets/prompts/reflect-llm-framed-contract.md" with { type: "text" };
29
30
  import reflectLlmSchemaContract from "../../assets/prompts/reflect-llm-schema-contract.md" with { type: "text" };
30
31
  import reflectOutputRepair from "../../assets/prompts/reflect-output-repair.md" with { type: "text" };
@@ -56,6 +57,25 @@ function hintForType(type) {
56
57
  function knownTypeList() {
57
58
  return [...placementTypes()].sort().join(", ");
58
59
  }
60
+ /**
61
+ * Default cap (in characters) on the asset content injected into
62
+ * {@link buildReflectPrompt}, and the floor a caller-supplied
63
+ * {@link ReflectPromptInput.contentBudgetChars} is never allowed to go below.
64
+ * Exists to stay well under OS ARG_MAX when the prompt is passed as a CLI
65
+ * argument to opencode/claude — large assets (wiki snapshots, long runbooks)
66
+ * would otherwise trigger E2BIG on posix_spawn. Agent/SDK runners always use
67
+ * this flat value; the direct-LLM path can raise it per #952 (see the reflect
68
+ * dispatch site in `src/commands/improve/reflect.ts`).
69
+ */
70
+ export const REFLECT_CONTENT_CAP = 12_000;
71
+ /**
72
+ * Marker appended to truncated asset content when it exceeds the active
73
+ * content budget (#952). Exported so `sanitizeReflectPayload` can detect a
74
+ * model that echoed this notice back into its rewrite instead of proposing
75
+ * real content, and so the output contracts can reference the exact string
76
+ * to forbid.
77
+ */
78
+ export const REFLECT_TRUNCATION_MARKER = "... [truncated — focus on the visible portion]";
59
79
  /**
60
80
  * Common envelope every prompt asks the agent to honour when NO draft file
61
81
  * path is available. The wrapper code uses `JSON.parse(stdout)` to extract
@@ -87,6 +107,7 @@ function fileWriteContract(draftFilePath) {
87
107
  `Write the complete improved asset content to: ${draftFilePath}`,
88
108
  "Use your file-editing tools to create or overwrite that file.",
89
109
  "Do NOT output JSON to stdout. Do NOT print the file contents. Just write the file.",
110
+ `Never include the text "${REFLECT_TRUNCATION_MARKER}" or any other content from outside the provided asset content in the file you write.`,
90
111
  "When done, output a single line on stdout: DRAFT_WRITTEN confidence=<0.0-1.0>",
91
112
  "`confidence` is REQUIRED and must be your honest self-rated [0, 1] score for this proposal:",
92
113
  " • 0.90+ — fixes a real defect or adds load-bearing missing content; reviewer would clearly accept.",
@@ -124,10 +145,14 @@ export function reflectLlmResponseContract(mode, targetScoped) {
124
145
  .replace("{{FIELD_RULE}}", targetScoped
125
146
  ? "The response has exactly the required fields `content`, `confidence`, and `frontmatterPatch`; do not echo `ref` or arbitrary `frontmatter`."
126
147
  : "The response has exactly the required fields `ref`, `content`, `confidence`, and `frontmatterPatch`; `ref` must identify the selected asset.")
148
+ .replaceAll("{{TRUNCATION_MARKER}}", REFLECT_TRUNCATION_MARKER)
127
149
  .trim();
128
150
  }
129
151
  const refLine = targetScoped ? "" : "AKM_REFLECT_REF: <selected asset ref>\n";
130
- return reflectLlmFramedContract.replace("{{REF_LINE}}", refLine).trim();
152
+ return reflectLlmFramedContract
153
+ .replace("{{REF_LINE}}", refLine)
154
+ .replaceAll("{{TRUNCATION_MARKER}}", REFLECT_TRUNCATION_MARKER)
155
+ .trim();
131
156
  }
132
157
  export function buildReflectOutputRepairPrompt(mode, targetScoped) {
133
158
  return reflectOutputRepair.replace("{{OUTPUT_CONTRACT}}", reflectLlmResponseContract(mode, targetScoped)).trim();
@@ -194,6 +219,10 @@ export function buildReflectPrompt(input) {
194
219
  }
195
220
  // Change 3 & 4 — feedback moved before asset content; missing else branch added
196
221
  if (input.feedback && input.feedback.length > 0) {
222
+ // #952 — feedback lines are unverified reports, not verified facts. Without
223
+ // this caveat, models fabricated whole new sections asserting whatever a
224
+ // feedback line claimed (invented incident dates, ports, disk layouts).
225
+ sections.push(reflectFeedbackFraming.trim());
197
226
  sections.push("Recent feedback / signals:");
198
227
  for (const line of input.feedback)
199
228
  sections.push(`- ${line}`);
@@ -238,17 +267,20 @@ export function buildReflectPrompt(input) {
238
267
  }
239
268
  }
240
269
  if (input.assetContent?.trim()) {
241
- // Cap at 12 000 chars to stay well under OS ARG_MAX when the prompt is
242
- // passed as a CLI argument to opencode/claude. Large assets (wiki snapshots,
243
- // long runbooks) would otherwise trigger E2BIG on posix_spawn.
244
- const REFLECT_CONTENT_CAP = 12_000;
270
+ // Cap defaults to REFLECT_CONTENT_CAP (12 000 chars) to stay well under OS
271
+ // ARG_MAX when the prompt is passed as a CLI argument to opencode/claude
272
+ // — large assets (wiki snapshots, long runbooks) would otherwise trigger
273
+ // E2BIG on posix_spawn. Direct-LLM callers never touch argv, so they may
274
+ // pass a larger, context-aware `contentBudgetChars` (#952); it is never
275
+ // allowed below the flat floor, which the caller enforces before calling in.
276
+ const contentCap = input.contentBudgetChars ?? REFLECT_CONTENT_CAP;
245
277
  const body = input.assetContent.trimEnd();
246
- const truncated = body.length > REFLECT_CONTENT_CAP;
278
+ const truncated = body.length > contentCap;
247
279
  sections.push(truncated
248
- ? `Current asset content (first ${REFLECT_CONTENT_CAP} chars — full asset is ${body.length} chars):`
280
+ ? `Current asset content (first ${contentCap} chars — full asset is ${body.length} chars):`
249
281
  : "Current asset content (verbatim):");
250
282
  sections.push("```");
251
- sections.push(truncated ? `${body.slice(0, REFLECT_CONTENT_CAP)}\n... [truncated — focus on the visible portion]` : body);
283
+ sections.push(truncated ? `${body.slice(0, contentCap)}\n${REFLECT_TRUNCATION_MARKER}` : body);
252
284
  sections.push("```");
253
285
  }
254
286
  else if (input.ref) {
@@ -20,7 +20,7 @@ import { assertNever } from "../../core/assert.js";
20
20
  import { collectSensitiveValues, isEnvPassthroughValueSafeToExpose, redactSensitiveText, redactSensitiveValue, } from "../../core/redaction.js";
21
21
  import { spawnEnvNamesFor } from "../../core/spawn-env.js";
22
22
  import { closeServer as disposeOpencodeSdkServers, opencodeSdkServerEnvironmentNames, runOpencodeSdk, } from "../harnesses/opencode-sdk/sdk-runner.js";
23
- import { lookupApiKeyFileValue, lookupCredentialFromEnv, materializeLlmConnection, materializeLlmConnectionWithCredential, resolveLlmCredentialValue, } from "./engine-resolution.js";
23
+ import { lookupApiKeyFileValue, lookupApiKeySecretRefValue, lookupCredentialFromEnv, materializeLlmConnection, materializeLlmConnectionWithCredential, resolveLlmCredentialValue, } from "./engine-resolution.js";
24
24
  import { materializeLlmRunnerConnection, materializeLlmRunnerConnectionWithCredential, } from "./runner.js";
25
25
  import { runAgent } from "./spawn.js";
26
26
  const liveRunnerDispatchLeases = new WeakMap();
@@ -134,10 +134,10 @@ export function acquireRunnerDispatchLease(spec, envSource = process.env) {
134
134
  },
135
135
  });
136
136
  const primaryCredential = spec.kind === "llm"
137
- ? resolveLlmCredentialValue(spec.engine, spec.credential, spec.apiKeyFile, credentialSource)
137
+ ? resolveLlmCredentialValue(spec.engine, spec.credential, spec.apiKeyFile, spec.apiKeySecretRef, credentialSource)
138
138
  : undefined;
139
139
  const fallbackCredential = spec.kind === "sdk"
140
- ? resolveLlmCredentialValue(spec.engine, spec.fallbackCredential, spec.fallbackApiKeyFile, credentialSource)
140
+ ? resolveLlmCredentialValue(spec.engine, spec.fallbackCredential, spec.fallbackApiKeyFile, spec.fallbackApiKeySecretRef, credentialSource)
141
141
  : undefined;
142
142
  const handle = Object.create(null);
143
143
  Object.defineProperty(handle, "toJSON", {
@@ -229,6 +229,12 @@ export function collectDispatchSensitiveValues(spec, opts, envSource = opts.envS
229
229
  add(lookupApiKeyFileValue(spec.apiKeyFile));
230
230
  if (spec.kind === "sdk" && spec.fallbackApiKeyFile)
231
231
  add(lookupApiKeyFileValue(spec.fallbackApiKeyFile));
232
+ // #953: a secret-store-backed credential is read at dispatch too — same
233
+ // best-effort scrub-set inclusion rationale.
234
+ if (spec.kind === "llm" && spec.apiKeySecretRef)
235
+ add(lookupApiKeySecretRefValue(spec.apiKeySecretRef));
236
+ if (spec.kind === "sdk" && spec.fallbackApiKeySecretRef)
237
+ add(lookupApiKeySecretRefValue(spec.fallbackApiKeySecretRef));
232
238
  if (spec.kind !== "llm") {
233
239
  for (const value of Object.values(spec.profile.env ?? {}))
234
240
  add(value);
@@ -9,6 +9,7 @@ export function materializeLlmRunnerConnection(runner) {
9
9
  connection: runner.connection,
10
10
  ...(runner.credential ? { credential: runner.credential } : {}),
11
11
  ...(runner.apiKeyFile ? { apiKeyFile: runner.apiKeyFile } : {}),
12
+ ...(runner.apiKeySecretRef ? { apiKeySecretRef: runner.apiKeySecretRef } : {}),
12
13
  timeoutMs: runner.timeoutMs ?? null,
13
14
  });
14
15
  }
@@ -19,6 +20,7 @@ export function materializeLlmRunnerConnectionWithCredential(runner, credentialV
19
20
  connection: runner.connection,
20
21
  ...(runner.credential ? { credential: runner.credential } : {}),
21
22
  ...(runner.apiKeyFile ? { apiKeyFile: runner.apiKeyFile } : {}),
23
+ ...(runner.apiKeySecretRef ? { apiKeySecretRef: runner.apiKeySecretRef } : {}),
22
24
  timeoutMs: runner.timeoutMs ?? null,
23
25
  }, credentialValue);
24
26
  }
@@ -290,12 +290,17 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
290
290
  },
291
291
  }
292
292
  : {};
293
+ // #949: which of these two wire forms a backend honors is a fact about the
294
+ // backend (and any gateway in front of it), not about akm's own `provider`
295
+ // label — a llama.cpp build honors chat_template_kwargs, a bare
296
+ // `enable_thinking` is honored by nothing observed, and a gateway
297
+ // (freellmapi, Bifrost) can drop either one depending on how it was built.
298
+ // Send both whenever thinking is explicitly resolved so the same engine
299
+ // block keeps working across a direct vhost or any gateway in front of it.
293
300
  const resolvedEnableThinking = options?.enableThinking ?? config.enableThinking;
294
301
  const thinkingParams = resolvedEnableThinking === undefined
295
302
  ? {}
296
- : config.provider === "vllm"
297
- ? { chat_template_kwargs: { enable_thinking: resolvedEnableThinking } }
298
- : { enable_thinking: resolvedEnableThinking };
303
+ : { chat_template_kwargs: { enable_thinking: resolvedEnableThinking }, enable_thinking: resolvedEnableThinking };
299
304
  const reasoningEffortParams = config.reasoningEffort === undefined ? {} : { reasoning_effort: config.reasoningEffort };
300
305
  const requestBody = JSON.stringify({
301
306
  model: config.model,
@@ -100,32 +100,44 @@ async function embedOnce(text, embeddingConfig, signal) {
100
100
  * skipped and why; the result array holds `undefined` at those indices.
101
101
  * Uses the LocalEmbedder.embedBatch path for the local transformer pipeline,
102
102
  * which processes texts in chunks of 32 for genuine batched inference.
103
+ *
104
+ * `onBatch`, when given, fires once per provider/local batch as it completes
105
+ * (#954) so a caller can commit each batch's rows durably as they land
106
+ * rather than buffering the whole call — see `EmbeddingBatchCommit`.
103
107
  */
104
- export async function embedBatch(texts, embeddingConfig, signal, onSkip) {
105
- if (embedderOverrides?.embedBatch)
106
- return embedderOverrides.embedBatch(texts, embeddingConfig, signal);
108
+ export async function embedBatch(texts, embeddingConfig, signal, onSkip, onBatch) {
109
+ if (embedderOverrides?.embedBatch) {
110
+ return embedderOverrides.embedBatch(texts, embeddingConfig, signal, onSkip, onBatch);
111
+ }
107
112
  if (texts.length === 0)
108
113
  return [];
109
114
  // Deterministic mode (env-gated, test/bench only): model-free, stable.
110
115
  if (isDeterministicEmbedEnabled()) {
111
- return texts.map((t) => deterministicEmbed(t));
116
+ const embeddings = texts.map((t) => deterministicEmbed(t));
117
+ // One onBatch commit for the whole call, like the local/remote batched
118
+ // paths — firing once per text opened one materializer transaction per
119
+ // entry in deterministic (test/bench) mode.
120
+ onBatch?.(embeddings.map((_embedding, i) => i), embeddings);
121
+ return embeddings;
112
122
  }
113
123
  if (embeddingConfig && hasRemoteEndpoint(embeddingConfig)) {
114
- return new RemoteEmbedder(embeddingConfig).embedBatch(texts, signal, onSkip);
124
+ return new RemoteEmbedder(embeddingConfig).embedBatch(texts, signal, onSkip, onBatch);
115
125
  }
116
126
  // Local transformer: use the batched path (chunks of 32 via LocalEmbedder).
117
127
  // When a localModel override is set we cannot share the singleton (which uses
118
128
  // the default model), so fall back to per-text embedWithModel in that case.
119
129
  const localModel = embeddingConfig?.localModel;
120
130
  if (!localModel) {
121
- return getLocalEmbedder().embedBatch(texts, signal);
131
+ return getLocalEmbedder().embedBatch(texts, signal, onBatch);
122
132
  }
123
133
  const results = [];
124
- for (const text of texts) {
134
+ for (const [i, text] of texts.entries()) {
125
135
  if (signal?.aborted) {
126
136
  throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
127
137
  }
128
- results.push(await getLocalEmbedder().embedWithModel(text, localModel));
138
+ const embedding = await getLocalEmbedder().embedWithModel(text, localModel);
139
+ results.push(embedding);
140
+ onBatch?.([i], [embedding]);
129
141
  }
130
142
  return results;
131
143
  }
@@ -81,8 +81,13 @@ export class LocalEmbedder {
81
81
  * Embed a batch of texts. Processes in chunks of `LOCAL_BATCH_SIZE` (32) so
82
82
  * the transformers pipeline can run genuine batched inference rather than one
83
83
  * call per text. Each chunk is checked against the AbortSignal between calls.
84
+ *
85
+ * `onBatch`, when given, fires once per chunk (indices into `texts` + the
86
+ * chunk's embeddings) as it completes (#954) — parity with
87
+ * `RemoteEmbedder.embedBatch`'s per-provider-batch commit callback, so a
88
+ * caller committing per batch does not need to special-case the local path.
84
89
  */
85
- async embedBatch(texts, signal) {
90
+ async embedBatch(texts, signal, onBatch) {
86
91
  if (texts.length === 0)
87
92
  return [];
88
93
  if (signal?.aborted) {
@@ -109,9 +114,12 @@ export class LocalEmbedder {
109
114
  if (batch !== chunk.length || batchResult.data.length !== batch * dim) {
110
115
  throw new Error("unexpected pipeline return shape for batch input");
111
116
  }
117
+ const chunkEmbeddings = [];
112
118
  for (let row = 0; row < chunk.length; row++) {
113
- results.push(Array.from(batchResult.data.subarray(row * dim, (row + 1) * dim)));
119
+ chunkEmbeddings.push(Array.from(batchResult.data.subarray(row * dim, (row + 1) * dim)));
114
120
  }
121
+ results.push(...chunkEmbeddings);
122
+ onBatch?.(Array.from({ length: chunk.length }, (_, row) => i + row), chunkEmbeddings);
115
123
  }
116
124
  return results;
117
125
  }