akm-cli 0.9.14 → 0.9.15-beta.1
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 +397 -0
- package/STABILITY.md +6 -3
- package/dist/assets/prompts/reflect-feedback-framing.md +1 -0
- package/dist/assets/prompts/reflect-llm-framed-contract.md +2 -0
- package/dist/assets/prompts/reflect-llm-schema-contract.md +2 -0
- package/dist/assets/tasks/core/improve.yml +1 -1
- package/dist/assets/tasks/core/index-refresh.yml +1 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +1 -1
- package/dist/cli/retired-commands.js +0 -1
- package/dist/cli/shared.js +9 -0
- package/dist/cli/unknown-flags.js +1 -0
- package/dist/cli.js +3 -2
- package/dist/commands/config-cli.js +85 -3
- package/dist/commands/env/env-cli.js +1 -42
- package/dist/commands/env/env.js +1 -1
- package/dist/commands/env/secret-cli.js +1 -2
- package/dist/commands/health/checks.js +357 -63
- package/dist/commands/health/engine-usage.js +45 -0
- package/dist/commands/health/improve-metrics.js +18 -0
- package/dist/commands/health/llm-usage.js +41 -1
- package/dist/commands/health/plugin-staleness.js +7 -3
- package/dist/commands/health/version-drift.js +93 -0
- package/dist/commands/health/windows.js +3 -1
- package/dist/commands/health.js +44 -9
- package/dist/commands/improve/consolidate/chunking.js +4 -2
- package/dist/commands/improve/improve-cli.js +99 -5
- package/dist/commands/improve/improve-report.js +154 -0
- package/dist/commands/improve/improve-result-file.js +45 -33
- package/dist/commands/improve/improve-strategies.js +133 -3
- package/dist/commands/improve/improve-usage-report.js +182 -0
- package/dist/commands/improve/improve.js +40 -3
- package/dist/commands/improve/locks.js +27 -78
- package/dist/commands/improve/planner.js +1 -0
- package/dist/commands/improve/preparation.js +9 -1
- package/dist/commands/improve/reflect.js +44 -4
- package/dist/commands/models-cli.js +50 -1
- package/dist/commands/proposal/repository.js +8 -3
- package/dist/commands/proposal/validators/proposal-quality-validators.js +41 -6
- package/dist/commands/proposal/validators/proposal-validators.js +24 -0
- package/dist/commands/read/search-cli.js +38 -2
- package/dist/commands/read/show.js +103 -4
- package/dist/commands/sources/info.js +5 -1
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/stash-cli.js +31 -0
- package/dist/commands/tasks/tasks-cli.js +49 -2
- package/dist/commands/workflow-cli.js +86 -12
- package/dist/core/asset/markdown-fragments.js +35 -0
- package/dist/core/config/config-schema.js +14 -0
- package/dist/core/config/config.js +302 -24
- package/dist/core/env-secret-ref.js +58 -5
- package/dist/core/errors.js +30 -0
- package/dist/core/improve-result.js +51 -0
- package/dist/core/loopback.js +17 -0
- package/dist/core/paths.js +11 -0
- package/dist/core/run-lock.js +96 -0
- package/dist/core/sensitive-marker-path.js +19 -0
- package/dist/core/state-db.js +74 -14
- package/dist/indexer/index-rebuild-lock.js +73 -0
- package/dist/indexer/index-writer-lock.js +40 -1
- package/dist/indexer/index-written-assets.js +21 -1
- package/dist/indexer/indexer.js +18 -17
- package/dist/indexer/materialize-embeddings.js +282 -32
- package/dist/indexer/search/db-search.js +49 -2
- package/dist/integrations/agent/engine-resolution.js +96 -6
- package/dist/integrations/agent/execution-definitions.js +6 -15
- package/dist/integrations/agent/execution-lowering.js +6 -1
- package/dist/integrations/agent/execution-preparation.js +1 -1
- package/dist/integrations/agent/model-map.js +123 -20
- package/dist/integrations/agent/prompts.js +40 -8
- package/dist/integrations/agent/runner-dispatch.js +9 -3
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/llm/client.js +8 -3
- package/dist/llm/embedder.js +20 -8
- package/dist/llm/embedders/local.js +10 -2
- package/dist/llm/embedders/remote.js +188 -21
- package/dist/output/shapes/helpers.js +38 -2
- package/dist/output/shapes/models-list.js +16 -0
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/shapes.js +4 -0
- package/dist/output/text/command-format.js +29 -0
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/improve-report.js +27 -0
- package/dist/{commands/env/marker-path.js → output/text/models.js} +4 -3
- package/dist/output/text/show-format.js +4 -0
- package/dist/output/text.js +4 -0
- package/dist/scripts/akm-migrate-node.js +24798 -21732
- package/dist/scripts/akm-migrate.js +23408 -20343
- package/dist/storage/repositories/improve-runs-repository.js +34 -0
- package/dist/storage/repositories/index-fts-repository.js +49 -6
- package/dist/storage/repositories/index-vec-repository.js +30 -0
- package/dist/storage/repositories/workflow-runs-repository.js +55 -18
- package/dist/tasks/backends/cron.js +14 -7
- package/dist/tasks/run/run-workflow-task.js +16 -0
- package/dist/workflows/exec/child-workflow.js +2 -2
- package/dist/workflows/exec/dispatch-redaction.js +21 -9
- package/dist/workflows/exec/run-workflow.js +6 -5
- package/dist/workflows/runtime/runs.js +33 -5
- package/docs/migration/release-notes/0.9.15.md +52 -0
- package/docs/migration/release-notes/README.md +4 -0
- package/docs/reference/cli.md +245 -29
- package/docs/reference/configuration.md +180 -19
- package/docs/reference/data-and-telemetry.md +8 -0
- package/docs/reference/tasks.md +16 -1
- package/docs/reference/workflow-schema.md +5 -1
- package/package.json +1 -1
- package/schemas/akm-config.json +8 -0
package/dist/llm/embedder.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* vectors so the scoring pipeline's L2-to-cosine conversion is correct.
|
|
9
9
|
*/
|
|
10
10
|
import { fetchWithTimeout, isHttpUrl, readBodyWithByteCap } from "../../core/common.js";
|
|
11
|
+
import { concurrentMap } from "../../core/concurrent.js";
|
|
11
12
|
import { resolveSecret } from "../../core/config/config.js";
|
|
13
|
+
import { defaultConcurrencyForEndpoint } from "../../core/loopback.js";
|
|
12
14
|
import { redactErrorBody, redactSensitiveText } from "../../core/redaction.js";
|
|
13
15
|
import { warnVerbose } from "../../core/warn.js";
|
|
14
16
|
import { resolveSecretFromStore } from "../../sources/snapshot-fetchers/secret-seam.js";
|
|
@@ -34,6 +36,47 @@ export const DEFAULT_TOKEN_BUDGET = 8000;
|
|
|
34
36
|
export function estimateTokenCount(text) {
|
|
35
37
|
return Math.round(text.length / 4);
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Distinguishes a batch rejected because it exceeded the endpoint's context
|
|
41
|
+
* window from every other failure mode (network error, 5xx, malformed
|
|
42
|
+
* response). Only this error class triggers the split-in-half retry in
|
|
43
|
+
* {@link RemoteEmbedder.embedBatch} (#954) — anything else keeps the
|
|
44
|
+
* original skip-the-whole-batch behavior pinned by
|
|
45
|
+
* tests/integration/embedding-batch-partial-failure.test.ts.
|
|
46
|
+
*/
|
|
47
|
+
export class ContextExceededError extends Error {
|
|
48
|
+
constructor(message) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = "ContextExceededError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Patterns providers use to report a request too large for the model's context window. */
|
|
54
|
+
const CONTEXT_EXCEEDED_PATTERN = /exceed_context_size_error|context size|context length|too many tokens/i;
|
|
55
|
+
/**
|
|
56
|
+
* True when an HTTP failure means "this request's input is too large for the
|
|
57
|
+
* endpoint's context window" rather than some other failure. HTTP 413
|
|
58
|
+
* (Payload Too Large) is always treated this way regardless of body; other
|
|
59
|
+
* status codes are checked against known provider error-body phrasing.
|
|
60
|
+
*/
|
|
61
|
+
export function isContextExceededResponse(status, body) {
|
|
62
|
+
if (status === 413)
|
|
63
|
+
return true;
|
|
64
|
+
return CONTEXT_EXCEEDED_PATTERN.test(body);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the effective in-flight request window for `RemoteEmbedder.embedBatch`.
|
|
68
|
+
* FIXED — no config override: 1 for a loopback endpoint, 2 for a remote one,
|
|
69
|
+
* via the shared `defaultConcurrencyForEndpoint` (`src/core/loopback.ts`),
|
|
70
|
+
* the same lowest-common-denominator rule `getDefaultLlmConcurrency`
|
|
71
|
+
* (`src/indexer/indexer.ts`) uses. The actual throughput knob is request
|
|
72
|
+
* SIZE, not request count: `embedding.batchSize` (document cap) and
|
|
73
|
+
* `embedding.maxTokens`/`contextLength` (token budget) reach a larger batch
|
|
74
|
+
* per request, which is where most of the win is — a 32-input batch takes
|
|
75
|
+
* about the same wall time as one input against a healthy endpoint.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveEmbeddingConcurrency(config) {
|
|
78
|
+
return defaultConcurrencyForEndpoint(config.endpoint);
|
|
79
|
+
}
|
|
37
80
|
/**
|
|
38
81
|
* Group `texts` into request-sized batches bounded by BOTH an estimated
|
|
39
82
|
* token budget and a document-count cap, so one large document does not
|
|
@@ -130,8 +173,34 @@ export class RemoteEmbedder {
|
|
|
130
173
|
* `onSkip` (index into `texts` + a named reason) rather than silently
|
|
131
174
|
* dropped; the returned array holds `undefined` at every skipped index.
|
|
132
175
|
* A caller abort (`signal.aborted`) still propagates as a rejection.
|
|
176
|
+
*
|
|
177
|
+
* Provider batches are dispatched through a bounded pool sized by
|
|
178
|
+
* `resolveEmbeddingConcurrency` (#954) instead of strictly sequentially, so
|
|
179
|
+
* request latency overlaps. `onBatch`, when given, fires once per provider
|
|
180
|
+
* batch (success or skip) as it completes, only after that batch's own
|
|
181
|
+
* outcome has already been classified — pass it to commit each batch's
|
|
182
|
+
* rows durably as they land rather than buffering the whole call.
|
|
183
|
+
* Completion order does not matter to `results` placement, which is always
|
|
184
|
+
* written by index regardless of dispatch order. A throw from `onBatch`
|
|
185
|
+
* itself (e.g. the caller's own commit failing) is never mistaken for a
|
|
186
|
+
* provider/network failure; it is captured and rethrown once every batch
|
|
187
|
+
* has been dispatched, alongside the `signal.aborted` check. The FIRST
|
|
188
|
+
* `onBatch` throw also stops the pool from dispatching any further
|
|
189
|
+
* provider request (#954 gap fix) — a persistence failure means every
|
|
190
|
+
* later batch's result can never be committed either, so there is no
|
|
191
|
+
* point paying for more HTTP requests; a batch already in flight when this
|
|
192
|
+
* happens is left to finish (never network-aborted) but its result is
|
|
193
|
+
* discarded rather than committed.
|
|
194
|
+
*
|
|
195
|
+
* A batch rejected specifically for exceeding the endpoint's context window
|
|
196
|
+
* (HTTP 413, or a recognised context-size error body — see
|
|
197
|
+
* {@link isContextExceededResponse}) is split in half and retried
|
|
198
|
+
* recursively rather than skipped outright, down to individual documents; a
|
|
199
|
+
* single document that still fails this way becomes a genuine
|
|
200
|
+
* `context-window-exceeded` skip. Every other failure (network error, 5xx,
|
|
201
|
+
* malformed response) keeps the original skip-the-whole-batch behavior.
|
|
133
202
|
*/
|
|
134
|
-
async embedBatch(texts, signal, onSkip) {
|
|
203
|
+
async embedBatch(texts, signal, onSkip, onBatch) {
|
|
135
204
|
if (texts.length === 0)
|
|
136
205
|
return [];
|
|
137
206
|
const results = new Array(texts.length).fill(undefined);
|
|
@@ -139,40 +208,134 @@ export class RemoteEmbedder {
|
|
|
139
208
|
const ollamaOpts = resolveOllamaOptions(this.config);
|
|
140
209
|
const tokenBudget = this.config.maxTokens ?? this.config.contextLength ?? DEFAULT_TOKEN_BUDGET;
|
|
141
210
|
const maxCount = this.config.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
211
|
+
const textBatches = buildTokenBoundedBatches(texts, tokenBudget, maxCount);
|
|
212
|
+
// Stops the pool from claiming any FURTHER provider batch once the
|
|
213
|
+
// caller's onBatch has failed once (the materializer's transaction
|
|
214
|
+
// failed, so a subsequent commit would just fail again) — dispatching
|
|
215
|
+
// real HTTP requests whose results can never be persisted is pure waste.
|
|
216
|
+
// Deliberately a SEPARATE controller from the caller's own `signal`,
|
|
217
|
+
// chained one-way to it: aborting this one must never cancel an
|
|
218
|
+
// in-flight request's own network call (it is left to finish and its
|
|
219
|
+
// result is discarded, not persisted), and a genuine caller abort must
|
|
220
|
+
// still surface below as the caller's own abort reason, not this
|
|
221
|
+
// internal one.
|
|
222
|
+
const dispatchAbort = new AbortController();
|
|
223
|
+
const stopDispatch = (reason) => {
|
|
224
|
+
if (!dispatchAbort.signal.aborted)
|
|
225
|
+
dispatchAbort.abort(reason);
|
|
226
|
+
};
|
|
227
|
+
let callerAbortListener;
|
|
228
|
+
if (signal) {
|
|
229
|
+
if (signal.aborted)
|
|
230
|
+
stopDispatch(signal.reason);
|
|
231
|
+
else {
|
|
232
|
+
callerAbortListener = () => stopDispatch(signal.reason);
|
|
233
|
+
signal.addEventListener("abort", callerAbortListener, { once: true });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// First error thrown BY the caller's onBatch callback (e.g. a real
|
|
237
|
+
// competing-process SQLITE_BUSY from the materializer's db.transaction())
|
|
238
|
+
// rather than by requestBatch itself. Captured here instead of being left
|
|
239
|
+
// to reach requestAndCommit's try/catch below, which exists solely to
|
|
240
|
+
// classify requestBatch's own provider/network failures — a persistence
|
|
241
|
+
// failure must never be caught by that block and misreported as a
|
|
242
|
+
// fabricated "batch-request-failed" skip (#954). Checked (and rethrown)
|
|
243
|
+
// once the pool drains, the same way `signal?.aborted` is today.
|
|
244
|
+
let firstOnBatchError;
|
|
245
|
+
const commitBatch = (indices, embeddings, model) => {
|
|
246
|
+
if (!onBatch)
|
|
247
|
+
return;
|
|
248
|
+
// Once persistence has failed once, an already in-flight batch that
|
|
249
|
+
// finishes afterward has nowhere safe to land — its result is
|
|
250
|
+
// discarded rather than retried into a transaction that will fail
|
|
251
|
+
// again (see the dispatch-stop comment above).
|
|
252
|
+
if (firstOnBatchError !== undefined)
|
|
253
|
+
return;
|
|
254
|
+
try {
|
|
255
|
+
onBatch(indices, embeddings, model);
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
firstOnBatchError = err;
|
|
259
|
+
stopDispatch(err);
|
|
153
260
|
}
|
|
154
|
-
|
|
261
|
+
};
|
|
262
|
+
// Requests a single provider batch (by index list), recursing on a
|
|
263
|
+
// context-size rejection. Never throws except to propagate a genuine
|
|
264
|
+
// caller abort — every other outcome (success or a non-abort failure)
|
|
265
|
+
// resolves normally after reporting via onSkip/onBatch. `onBatch` fires
|
|
266
|
+
// only once this try/catch has already settled success vs. failure, so a
|
|
267
|
+
// throw from it is never caught and reclassified by this block.
|
|
268
|
+
const requestAndCommit = async (indices) => {
|
|
269
|
+
const batch = indices.map((i) => texts[i]);
|
|
270
|
+
let batchEmbeddings;
|
|
271
|
+
let responseModel;
|
|
155
272
|
try {
|
|
156
|
-
const
|
|
157
|
-
for (let k = 0; k <
|
|
158
|
-
results[
|
|
273
|
+
const { vectors, model } = await this.requestBatch(batch, headers, ollamaOpts, signal);
|
|
274
|
+
for (let k = 0; k < indices.length; k++) {
|
|
275
|
+
results[indices[k]] = vectors[k];
|
|
159
276
|
}
|
|
277
|
+
batchEmbeddings = indices.map((i) => results[i]);
|
|
278
|
+
responseModel = model;
|
|
160
279
|
}
|
|
161
280
|
catch (err) {
|
|
162
281
|
// A caller abort must still propagate — it is not a "this batch
|
|
163
282
|
// failed" condition, it means stop entirely.
|
|
164
283
|
if (signal?.aborted)
|
|
165
284
|
throw err;
|
|
285
|
+
if (err instanceof ContextExceededError && indices.length > 1) {
|
|
286
|
+
const mid = Math.ceil(indices.length / 2);
|
|
287
|
+
await requestAndCommit(indices.slice(0, mid));
|
|
288
|
+
await requestAndCommit(indices.slice(mid));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
166
291
|
const message = err instanceof Error ? err.message : String(err);
|
|
292
|
+
const reason = err instanceof ContextExceededError ? "context-window-exceeded" : "batch-request-failed";
|
|
167
293
|
warnVerbose(`[embed] batch of ${batch.length} document(s) failed and was skipped: ${message}`);
|
|
168
|
-
for (const idx of
|
|
169
|
-
onSkip?.({ index: idx, reason
|
|
294
|
+
for (const idx of indices) {
|
|
295
|
+
onSkip?.({ index: idx, reason, message });
|
|
170
296
|
}
|
|
297
|
+
batchEmbeddings = indices.map(() => undefined);
|
|
298
|
+
}
|
|
299
|
+
commitBatch(indices, batchEmbeddings, responseModel);
|
|
300
|
+
};
|
|
301
|
+
const runProviderBatch = async (textBatch) => {
|
|
302
|
+
if (textBatch.oversized) {
|
|
303
|
+
const idx = textBatch.indices[0];
|
|
304
|
+
const estTokens = estimateTokenCount(texts[idx]);
|
|
305
|
+
onSkip?.({
|
|
306
|
+
index: idx,
|
|
307
|
+
reason: "context-window-exceeded",
|
|
308
|
+
message: `Document estimated at ${estTokens} tokens exceeds the ${tokenBudget}-token embedding budget; skipped.`,
|
|
309
|
+
});
|
|
310
|
+
commitBatch([idx], [undefined]);
|
|
311
|
+
return;
|
|
171
312
|
}
|
|
313
|
+
await requestAndCommit(textBatch.indices);
|
|
314
|
+
};
|
|
315
|
+
const concurrency = resolveEmbeddingConcurrency(this.config);
|
|
316
|
+
// concurrentMap swallows a thrown fn (per-item, results discarded here —
|
|
317
|
+
// requestAndCommit only throws to signal a caller abort), so abort must
|
|
318
|
+
// be re-checked once the pool has drained rather than relying on the
|
|
319
|
+
// throw itself to escape. Dispatch is gated on `dispatchAbort`, not the
|
|
320
|
+
// caller's `signal` directly — see the comment above.
|
|
321
|
+
await concurrentMap(textBatches, runProviderBatch, concurrency, { signal: dispatchAbort.signal });
|
|
322
|
+
if (callerAbortListener)
|
|
323
|
+
signal?.removeEventListener("abort", callerAbortListener);
|
|
324
|
+
if (signal?.aborted) {
|
|
325
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
326
|
+
}
|
|
327
|
+
if (firstOnBatchError !== undefined) {
|
|
328
|
+
throw firstOnBatchError instanceof Error ? firstOnBatchError : new Error(String(firstOnBatchError));
|
|
172
329
|
}
|
|
173
330
|
return results;
|
|
174
331
|
}
|
|
175
|
-
/**
|
|
332
|
+
/**
|
|
333
|
+
* Send one batch request and return its embeddings in input order, plus the
|
|
334
|
+
* server-reported `model` id when the response body carried one (#955) —
|
|
335
|
+
* used by the embedding-fingerprint canary to verify a config-string
|
|
336
|
+
* rename against what the endpoint actually served, not just re-assert the
|
|
337
|
+
* configured string. Throws on any failure.
|
|
338
|
+
*/
|
|
176
339
|
async requestBatch(batch, headers, ollamaOpts, signal) {
|
|
177
340
|
const body = {
|
|
178
341
|
input: batch,
|
|
@@ -197,7 +360,11 @@ export class RemoteEmbedder {
|
|
|
197
360
|
throw err;
|
|
198
361
|
return "";
|
|
199
362
|
});
|
|
200
|
-
|
|
363
|
+
const message = `Embedding batch request failed (${response.status}): ${this.safeErrorBody(respBody)}`;
|
|
364
|
+
if (isContextExceededResponse(response.status, respBody)) {
|
|
365
|
+
throw new ContextExceededError(message);
|
|
366
|
+
}
|
|
367
|
+
throw new Error(message);
|
|
201
368
|
}
|
|
202
369
|
const json = JSON.parse(await readBodyWithByteCap(response, undefined, { bodyTimeoutMs: 30_000, signal }));
|
|
203
370
|
if (!json.data || json.data.length !== batch.length) {
|
|
@@ -212,7 +379,7 @@ export class RemoteEmbedder {
|
|
|
212
379
|
}
|
|
213
380
|
results.push(l2Normalize(d.embedding));
|
|
214
381
|
}
|
|
215
|
-
return results;
|
|
382
|
+
return { vectors: results, model: typeof json.model === "string" && json.model ? json.model : undefined };
|
|
216
383
|
}
|
|
217
384
|
buildHeaders() {
|
|
218
385
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -2,6 +2,21 @@
|
|
|
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
|
export const NORMAL_DESCRIPTION_LIMIT = 250;
|
|
5
|
+
const FRAGMENT_PROVENANCE_FIELDS = [
|
|
6
|
+
"selectedRef",
|
|
7
|
+
"parentRef",
|
|
8
|
+
"fragmentOrdinal",
|
|
9
|
+
"fragmentCount",
|
|
10
|
+
"startLine",
|
|
11
|
+
"endLine",
|
|
12
|
+
"previousRef",
|
|
13
|
+
"nextRef",
|
|
14
|
+
"fragmentChars",
|
|
15
|
+
"fragmentEstimatedTokens",
|
|
16
|
+
"parentChars",
|
|
17
|
+
"parentEstimatedTokens",
|
|
18
|
+
];
|
|
19
|
+
const FRAGMENT_CONTEXT_FIELDS = ["contextMode", "contextMaxChars", "contextTruncated"];
|
|
5
20
|
export function shapeProposalProducerOutput(result, detail) {
|
|
6
21
|
if (result.ok === false) {
|
|
7
22
|
const base = {
|
|
@@ -268,8 +283,21 @@ export function shapeSearchHit(hit, detail) {
|
|
|
268
283
|
// Stash hit (local or remote)
|
|
269
284
|
// `ref` is included at `brief` so agents can run `akm show <ref>` without
|
|
270
285
|
// needing --detail full or --shape agent (REC-03).
|
|
271
|
-
if (detail === "brief")
|
|
272
|
-
return pickFields(hit, [
|
|
286
|
+
if (detail === "brief") {
|
|
287
|
+
return pickFields(hit, [
|
|
288
|
+
"type",
|
|
289
|
+
"name",
|
|
290
|
+
"ref",
|
|
291
|
+
"action",
|
|
292
|
+
"estimatedTokens",
|
|
293
|
+
"keys",
|
|
294
|
+
"selectedRef",
|
|
295
|
+
"parentRef",
|
|
296
|
+
"fragmentOrdinal",
|
|
297
|
+
"fragmentCount",
|
|
298
|
+
"parentEstimatedTokens",
|
|
299
|
+
]);
|
|
300
|
+
}
|
|
273
301
|
if (detail === "normal") {
|
|
274
302
|
// `warnings` is projected at `normal` so non-fatal hit-level issues are
|
|
275
303
|
// visible without forcing callers up to `--detail full`. Optional
|
|
@@ -288,6 +316,7 @@ export function shapeSearchHit(hit, detail) {
|
|
|
288
316
|
"warnings",
|
|
289
317
|
"quality",
|
|
290
318
|
"matchStage",
|
|
319
|
+
...FRAGMENT_PROVENANCE_FIELDS,
|
|
291
320
|
]), NORMAL_DESCRIPTION_LIMIT);
|
|
292
321
|
if (Array.isArray(hit.keys) && hit.keys.length > 0)
|
|
293
322
|
shaped.keys = hit.keys;
|
|
@@ -314,6 +343,7 @@ export function shapeSearchHitForAgent(hit) {
|
|
|
314
343
|
// hit (a strict-AND match is stronger signal than an OR-fallback
|
|
315
344
|
// recovery match) without going to `--detail full`.
|
|
316
345
|
"matchStage",
|
|
346
|
+
...FRAGMENT_PROVENANCE_FIELDS,
|
|
317
347
|
]);
|
|
318
348
|
if (picked.editable !== false)
|
|
319
349
|
delete picked.editHint;
|
|
@@ -360,6 +390,8 @@ export function shapeShowOutput(result, detail, shape = "human") {
|
|
|
360
390
|
"steps",
|
|
361
391
|
"keys",
|
|
362
392
|
"related",
|
|
393
|
+
...FRAGMENT_PROVENANCE_FIELDS,
|
|
394
|
+
...FRAGMENT_CONTEXT_FIELDS,
|
|
363
395
|
]);
|
|
364
396
|
if (shaped.editable !== false)
|
|
365
397
|
delete shaped.editHint;
|
|
@@ -382,6 +414,8 @@ export function shapeShowOutput(result, detail, shape = "human") {
|
|
|
382
414
|
"origin",
|
|
383
415
|
"keys",
|
|
384
416
|
"related",
|
|
417
|
+
...FRAGMENT_PROVENANCE_FIELDS,
|
|
418
|
+
...FRAGMENT_CONTEXT_FIELDS,
|
|
385
419
|
]);
|
|
386
420
|
}
|
|
387
421
|
const base = pickFields(result, [
|
|
@@ -409,6 +443,8 @@ export function shapeShowOutput(result, detail, shape = "human") {
|
|
|
409
443
|
"activeRun",
|
|
410
444
|
"keys",
|
|
411
445
|
"related",
|
|
446
|
+
...FRAGMENT_PROVENANCE_FIELDS,
|
|
447
|
+
...FRAGMENT_CONTEXT_FIELDS,
|
|
412
448
|
// ref, path, and editable are always projected — at every --detail level,
|
|
413
449
|
// not just --detail full — so JSON consumers can locate and edit the
|
|
414
450
|
// asset without needing --detail full (QA #7 / D-14).
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
export const modelsListShapes = [
|
|
5
|
+
{
|
|
6
|
+
command: "models-list",
|
|
7
|
+
handler: (result) => {
|
|
8
|
+
const r = result;
|
|
9
|
+
return {
|
|
10
|
+
...r,
|
|
11
|
+
shape: r.shape ?? "models-list",
|
|
12
|
+
schemaVersion: r.schemaVersion ?? 1,
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
];
|
|
@@ -43,6 +43,7 @@ const PASSTHROUGH_COMMANDS = [
|
|
|
43
43
|
"clone",
|
|
44
44
|
"command-dry-run",
|
|
45
45
|
"config",
|
|
46
|
+
"config-diff",
|
|
46
47
|
"env-create",
|
|
47
48
|
"env-export",
|
|
48
49
|
"env-remove",
|
|
@@ -50,6 +51,7 @@ const PASSTHROUGH_COMMANDS = [
|
|
|
50
51
|
"extract",
|
|
51
52
|
"health",
|
|
52
53
|
"improve",
|
|
54
|
+
"improve-report",
|
|
53
55
|
"import",
|
|
54
56
|
"index",
|
|
55
57
|
"info",
|
package/dist/output/shapes.js
CHANGED
|
@@ -22,6 +22,7 @@ import { curateShapes } from "./shapes/curate.js";
|
|
|
22
22
|
import { envListShapes } from "./shapes/env-list.js";
|
|
23
23
|
import { eventsShapes } from "./shapes/events.js";
|
|
24
24
|
import { migrateShapes } from "./shapes/migrate.js";
|
|
25
|
+
import { modelsListShapes } from "./shapes/models-list.js";
|
|
25
26
|
import { passthroughShapes } from "./shapes/passthrough.js";
|
|
26
27
|
import { proposalAcceptShapes } from "./shapes/proposal/accept.js";
|
|
27
28
|
import { proposalDiffShapes } from "./shapes/proposal/diff.js";
|
|
@@ -55,6 +56,7 @@ const BUILT_IN_OUTPUT_SHAPES = [
|
|
|
55
56
|
...envListShapes,
|
|
56
57
|
...secretListShapes,
|
|
57
58
|
...migrateShapes,
|
|
59
|
+
...modelsListShapes,
|
|
58
60
|
// Passthrough commands are registered last so an explicit dedicated handler
|
|
59
61
|
// above always wins over the identity-stamp fallback for the same name.
|
|
60
62
|
...passthroughShapes,
|
|
@@ -94,6 +96,8 @@ const LIST_RESULT_COLLECTION_KEYS = {
|
|
|
94
96
|
"workflow-list": "runs",
|
|
95
97
|
"task-history": "rows",
|
|
96
98
|
"log-list": "events", // `akm log list`
|
|
99
|
+
"config-diff": "rows", // `akm config diff`
|
|
100
|
+
"models-list": "rows", // `akm models list`
|
|
97
101
|
};
|
|
98
102
|
function withResultsAlias(command, shaped) {
|
|
99
103
|
const key = LIST_RESULT_COLLECTION_KEYS[command];
|
|
@@ -478,6 +478,35 @@ export function formatListPlain(r) {
|
|
|
478
478
|
lines.push("To search: akm search '<query>' | To view an asset: akm show <ref>");
|
|
479
479
|
return lines.join("\n");
|
|
480
480
|
}
|
|
481
|
+
/** Render `akm models list`'s `{ rows }` as a column-aligned table (#946). */
|
|
482
|
+
export function formatModelsListPlain(r) {
|
|
483
|
+
const rows = Array.isArray(r.rows) ? r.rows : [];
|
|
484
|
+
if (rows.length === 0)
|
|
485
|
+
return "No model aliases configured.";
|
|
486
|
+
const headers = ["ALIAS", "COLUMN", "MODEL", "SOURCE", "VIA", "ENGINE"];
|
|
487
|
+
const cells = rows.map((row) => [
|
|
488
|
+
String(row.alias ?? "?"),
|
|
489
|
+
String(row.column ?? "?"),
|
|
490
|
+
String(row.model ?? "?"),
|
|
491
|
+
String(row.source ?? "?"),
|
|
492
|
+
String(row.via ?? "?"),
|
|
493
|
+
row.engine !== undefined ? String(row.engine) : "-",
|
|
494
|
+
]);
|
|
495
|
+
const widths = headers.map((header, i) => {
|
|
496
|
+
let width = header.length;
|
|
497
|
+
for (const cols of cells) {
|
|
498
|
+
const cell = cols[i];
|
|
499
|
+
if (cell !== undefined && cell.length > width)
|
|
500
|
+
width = cell.length;
|
|
501
|
+
}
|
|
502
|
+
return width;
|
|
503
|
+
});
|
|
504
|
+
const renderRow = (cols) => cols
|
|
505
|
+
.map((cell, i) => cell.padEnd(widths[i] ?? cell.length))
|
|
506
|
+
.join(" ")
|
|
507
|
+
.trimEnd();
|
|
508
|
+
return [renderRow(headers), ...cells.map(renderRow)].join("\n");
|
|
509
|
+
}
|
|
481
510
|
/** Render a single `SourceEntry` — `akm bundle show <name>`'s detail view of one `list` row. */
|
|
482
511
|
export function formatBundleShowPlain(r) {
|
|
483
512
|
const name = typeof r.name === "string" ? r.name : "unknown";
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* No registry imports — no circular dependencies.
|
|
17
17
|
*/
|
|
18
|
-
export { formatAddPlain, formatBundleShowPlain, formatClonePlain, formatConfigPlain, formatCuratePlain, formatEnvCreatePlain, formatEnvExportPlain, formatEnvListPlain, formatEnvRemovePlain, formatEventLine, formatEventsPlain, formatFeedbackPlain, formatImportPlain, formatIndexPlain, formatInfoPlain, formatInitPlain, formatListPlain, formatRegistryAddPlain, formatRegistryListPlain, formatRegistryRemovePlain, formatRegistrySearchPlain, formatRememberPlain, formatRemovePlain, formatSearchPlain, formatSyncPlain, formatUpdatePlain, formatUpgradePlain, } from "./command-format.js";
|
|
18
|
+
export { formatAddPlain, formatBundleShowPlain, formatClonePlain, formatConfigPlain, formatCuratePlain, formatEnvCreatePlain, formatEnvExportPlain, formatEnvListPlain, formatEnvRemovePlain, formatEventLine, formatEventsPlain, formatFeedbackPlain, formatImportPlain, formatIndexPlain, formatInfoPlain, formatInitPlain, formatListPlain, formatModelsListPlain, formatRegistryAddPlain, formatRegistryListPlain, formatRegistryRemovePlain, formatRegistrySearchPlain, formatRememberPlain, formatRemovePlain, formatSearchPlain, formatSyncPlain, formatUpdatePlain, formatUpgradePlain, } from "./command-format.js";
|
|
19
19
|
export { formatHealthPlain } from "./health-format.js";
|
|
20
20
|
export { formatLintPlain } from "./lint-format.js";
|
|
21
21
|
export { formatGateDecisionSummary, formatProposalAcceptPlain, formatProposalDiffPlain, formatProposalDrainPlain, formatProposalListPlain, formatProposalProducerPlain, formatProposalRejectPlain, formatProposalShowPlain, } from "./proposal-format.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
* #944 — `akm improve report --format text`. Renders the SAME fixed-width
|
|
6
|
+
* table `formatUsageReportTable` produces for the end-of-run stderr summary
|
|
7
|
+
* (`improve-cli.ts`), prefixed with which run(s) it covers.
|
|
8
|
+
*/
|
|
9
|
+
import { formatUsageReportTable } from "../../commands/improve/improve-usage-report.js";
|
|
10
|
+
function formatImproveReportPlain(result) {
|
|
11
|
+
const usageReport = result.usageReport;
|
|
12
|
+
if (!usageReport)
|
|
13
|
+
return null;
|
|
14
|
+
const lines = [];
|
|
15
|
+
if (result.mode === "since") {
|
|
16
|
+
const runIds = Array.isArray(result.runIds) ? result.runIds : [];
|
|
17
|
+
lines.push(`[improve] report since ${String(result.since ?? "?")} (${runIds.length} run${runIds.length === 1 ? "" : "s"})`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
lines.push(`[improve] report for run ${String(result.runId ?? "?")}${result.strategy ? ` (strategy: ${result.strategy})` : ""}`);
|
|
21
|
+
}
|
|
22
|
+
lines.push(formatUsageReportTable(usageReport, result.notes));
|
|
23
|
+
return lines.join("\n");
|
|
24
|
+
}
|
|
25
|
+
export const improveReportFormatters = [
|
|
26
|
+
{ command: "improve-report", handler: (r) => formatImproveReportPlain(r) },
|
|
27
|
+
];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
4
|
+
import { formatModelsListPlain } from "./helpers.js";
|
|
5
|
+
export const modelsFormatters = [
|
|
6
|
+
{ command: "models-list", handler: (r) => formatModelsListPlain(r) },
|
|
7
|
+
];
|
|
@@ -14,6 +14,10 @@ export function formatShowPlain(r, detail) {
|
|
|
14
14
|
lines.push(`# origin: ${String(r.origin)}`);
|
|
15
15
|
if (r.action)
|
|
16
16
|
lines.push(`# ${String(r.action)}`);
|
|
17
|
+
if (Array.isArray(r.keys)) {
|
|
18
|
+
const keys = r.keys.map(String);
|
|
19
|
+
lines.push(keys.length > 0 ? `keys: ${keys.join(", ")}` : "(no keys)");
|
|
20
|
+
}
|
|
17
21
|
if (r.description)
|
|
18
22
|
lines.push(`description: ${String(r.description)}`);
|
|
19
23
|
if (r.workflowTitle)
|
package/dist/output/text.js
CHANGED
|
@@ -12,11 +12,13 @@ import { eventsFormatters } from "./text/events.js";
|
|
|
12
12
|
import { feedbackFormatters } from "./text/feedback.js";
|
|
13
13
|
import { healthFormatters } from "./text/health.js";
|
|
14
14
|
import { importFormatters } from "./text/import.js";
|
|
15
|
+
import { improveReportFormatters } from "./text/improve-report.js";
|
|
15
16
|
import { indexFormatters } from "./text/index.js";
|
|
16
17
|
import { infoFormatters } from "./text/info.js";
|
|
17
18
|
import { lintFormatters } from "./text/lint.js";
|
|
18
19
|
import { listFormatters } from "./text/list.js";
|
|
19
20
|
import { migrateFormatters } from "./text/migrate.js";
|
|
21
|
+
import { modelsFormatters } from "./text/models.js";
|
|
20
22
|
import { proposalProducerFormatters } from "./text/proposal/producer.js";
|
|
21
23
|
import { proposalFormatters } from "./text/proposal/proposal.js";
|
|
22
24
|
import { getTextFormatterHandler, registerTextFormatters } from "./text/registry.js";
|
|
@@ -54,6 +56,7 @@ const BUILT_IN_TEXT_FORMATTERS = [
|
|
|
54
56
|
...proposalProducerFormatters,
|
|
55
57
|
...infoFormatters,
|
|
56
58
|
...healthFormatters,
|
|
59
|
+
...improveReportFormatters,
|
|
57
60
|
...lintFormatters,
|
|
58
61
|
...configFormatters,
|
|
59
62
|
...feedbackFormatters,
|
|
@@ -63,6 +66,7 @@ const BUILT_IN_TEXT_FORMATTERS = [
|
|
|
63
66
|
...registryCommandFormatters,
|
|
64
67
|
...envFormatters,
|
|
65
68
|
...migrateFormatters,
|
|
69
|
+
...modelsFormatters,
|
|
66
70
|
];
|
|
67
71
|
registerTextFormatters(BUILT_IN_TEXT_FORMATTERS);
|
|
68
72
|
// ── JSONL output (unchanged — not part of the formatPlain dispatch) ───────────
|