akm-cli 0.9.26-alpha.2 → 0.9.27-alpha.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 +294 -0
- package/dist/assets/hints/cli-hints-full.md +16 -10
- package/dist/assets/hints/cli-hints-short.md +5 -5
- package/dist/assets/prompts/consolidate-system.md +2 -2
- package/dist/assets/prompts/extract-session.md +2 -2
- package/dist/assets/stash-skeleton/README.md +4 -3
- package/dist/commands/feedback-cli.js +244 -43
- package/dist/commands/improve/consolidate/pair-pass.js +1 -1
- package/dist/commands/improve/consolidate.js +14 -4
- package/dist/commands/improve/distill.js +70 -7
- package/dist/commands/improve/extract-prompt.js +61 -39
- package/dist/commands/improve/extract.js +2 -1
- package/dist/commands/improve/loop-stages.js +16 -1
- package/dist/commands/improve/memory/memory-belief.js +1 -1
- package/dist/commands/improve/preparation.js +46 -0
- package/dist/commands/improve/reflect.js +51 -8
- package/dist/commands/improve/retrieval-gate.js +1 -1
- package/dist/commands/improve/session-asset.js +3 -2
- package/dist/commands/improve/stage.js +1 -1
- package/dist/commands/proposal/repository.js +22 -6
- package/dist/core/asset/akm-markdown.js +40 -16
- package/dist/core/asset/frontmatter.js +67 -7
- package/dist/core/config/config-schema.js +1 -1
- package/dist/core/config/config.js +0 -4
- package/dist/core/config/schema/feedback.js +2 -19
- package/dist/indexer/indexer.js +35 -19
- package/dist/integrations/harnesses/codex/agent-builder.js +23 -15
- package/dist/llm/client.js +44 -14
- package/dist/llm/memory-infer.js +1 -1
- package/dist/scripts/akm-migrate-node.js +26 -22
- package/dist/scripts/akm-migrate.js +26 -22
- package/dist/storage/repositories/index-entry-schema.js +20 -4
- package/dist/storage/repositories/index-fts-repository.js +44 -3
- package/dist/storage/repositories/index-schema.js +14 -8
- package/docs/reference/cli.md +36 -17
- package/docs/reference/configuration.md +12 -6
- package/docs/reference/data-and-telemetry.md +3 -3
- package/package.json +1 -1
- package/schemas/akm-config.json +0 -14
|
@@ -196,17 +196,77 @@ function countLines(text) {
|
|
|
196
196
|
* frontmatter: round-tripping the mapping through the YAML serializer drops
|
|
197
197
|
* comments and normalizes formatting, which is unacceptable for a write that
|
|
198
198
|
* only needs to contribute one line. Shared by `ensureAkmMarkdownType`
|
|
199
|
-
* (
|
|
199
|
+
* (adding `type:` and `updated:` on write) and lint's `--fix` for
|
|
200
|
+
* `missing-updated`. The new line takes the block's own line ending.
|
|
200
201
|
*/
|
|
201
202
|
export function spliceFrontmatterLine(raw, line) {
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
// Alternating [line, eol, line, eol, …, line]: joining the parts back
|
|
204
|
+
// reproduces `raw` byte for byte, CRLF and all.
|
|
205
|
+
const parts = raw.split(/(\r?\n)/);
|
|
206
|
+
if (parts[0]?.trim() !== "---")
|
|
204
207
|
return null;
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
208
|
+
const closeAt = parts.findIndex((part, i) => i > 0 && part.trim() === "---");
|
|
209
|
+
if (closeAt === -1)
|
|
210
|
+
return null;
|
|
211
|
+
parts.splice(closeAt, 0, line, parts[1]);
|
|
212
|
+
return parts.join("");
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Replace the top-level `key:` line of an existing frontmatter block with
|
|
216
|
+
* `line`, leaving every other byte untouched — the counterpart to
|
|
217
|
+
* {@link spliceFrontmatterLine} for correcting a field's value in place.
|
|
218
|
+
*
|
|
219
|
+
* Only that one physical line is replaced, so a value that continues onto
|
|
220
|
+
* further lines (a block sequence, a folded scalar) would be left dangling:
|
|
221
|
+
* the caller must check that the result still parses to what it meant. Returns
|
|
222
|
+
* null when `raw` has no well-formed block or no such line.
|
|
223
|
+
*/
|
|
224
|
+
export function replaceFrontmatterLine(raw, key, line) {
|
|
225
|
+
const parts = raw.split(/(\r?\n)/);
|
|
226
|
+
if (parts[0]?.trim() !== "---")
|
|
227
|
+
return null;
|
|
228
|
+
const closeAt = parts.findIndex((part, i) => i > 0 && part.trim() === "---");
|
|
229
|
+
if (closeAt === -1)
|
|
207
230
|
return null;
|
|
208
|
-
|
|
209
|
-
|
|
231
|
+
const at = parts.findIndex((part, i) => i > 0 && i < closeAt && part.match(/^(\w[\w-]*):(?:[ \t]|$)/)?.[1] === key);
|
|
232
|
+
if (at === -1)
|
|
233
|
+
return null;
|
|
234
|
+
parts[at] = line;
|
|
235
|
+
return parts.join("");
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Replace the top-level blocks of `keys` in an existing frontmatter block with
|
|
239
|
+
* `lines`, leaving every other byte untouched — the counterpart to
|
|
240
|
+
* {@link spliceFrontmatterLine} for a write that owns whole keys, such as the
|
|
241
|
+
* provenance stamp's `generated`, `verified` and `provenance`.
|
|
242
|
+
*
|
|
243
|
+
* A key's block is its `key:` line and the lines directly under it that
|
|
244
|
+
* continue it: indented lines and `- ` items. Every such block is removed
|
|
245
|
+
* wherever it sits, and `lines` are added just before the closing `---`, in the
|
|
246
|
+
* block's own line ending. A block with a blank line or a comment inside it is
|
|
247
|
+
* not removed whole, so the caller must check that the result still parses to
|
|
248
|
+
* what it meant. Returns null when `raw` has no well-formed block.
|
|
249
|
+
*/
|
|
250
|
+
export function replaceFrontmatterBlocks(raw, keys, lines) {
|
|
251
|
+
const parts = raw.split(/(\r?\n)/);
|
|
252
|
+
if (parts[0]?.trim() !== "---")
|
|
253
|
+
return null;
|
|
254
|
+
const closeAt = parts.findIndex((part, i) => i > 0 && part.trim() === "---");
|
|
255
|
+
if (closeAt === -1)
|
|
256
|
+
return null;
|
|
257
|
+
const kept = [];
|
|
258
|
+
for (let i = 2; i < closeAt; i += 2) {
|
|
259
|
+
const key = parts[i].match(/^(\w[\w-]*):(?:[ \t]|$)/)?.[1];
|
|
260
|
+
if (key === undefined || !keys.includes(key)) {
|
|
261
|
+
kept.push(parts[i], parts[i + 1]);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
// One of the keys: drop its line and the lines that continue it.
|
|
265
|
+
while (i + 2 < closeAt && /^(?:[ \t]|-(?:[ \t]|$))/.test(parts[i + 2]))
|
|
266
|
+
i += 2;
|
|
267
|
+
}
|
|
268
|
+
const added = lines.flatMap((line) => [line, parts[1]]);
|
|
269
|
+
return [parts[0], parts[1], ...kept, ...added, ...parts.slice(closeAt)].join("");
|
|
210
270
|
}
|
|
211
271
|
/**
|
|
212
272
|
* Strip one layer of matching quotes — frontmatter list items are often quoted
|
|
@@ -59,7 +59,7 @@ import { WorkflowConfigSchema } from "./schema/workflow.js";
|
|
|
59
59
|
export { EmbeddingConnectionConfigSchema } from "./schema/embedding.js";
|
|
60
60
|
export { EngineConfigSchema, EnginesSchema, LlmConnectionConfigSchema, LlmProfileConfigSchema } from "./schema/engines.js";
|
|
61
61
|
export { ExperimentalConfigSchema } from "./schema/experimental.js";
|
|
62
|
-
export {
|
|
62
|
+
export { FeedbackConfigSchema } from "./schema/feedback.js";
|
|
63
63
|
export { ImproveConfigSchema } from "./schema/improve.js";
|
|
64
64
|
export { ConsolidateProcessConfigSchema, DistillProcessConfigSchema, ExtractProcessConfigSchema, ImproveProcessConfigSchema, ImproveProfileConfigSchema, MemoryInferenceProcessConfigSchema, ProactiveMaintenanceProcessConfigSchema, ReflectProcessConfigSchema, TriageProcessConfigSchema, ValidationProcessConfigSchema, } from "./schema/improve-processes.js";
|
|
65
65
|
export { IndexConfigSchema, IndexPassConfigSchema } from "./schema/index-config.js";
|
|
@@ -23,10 +23,6 @@ import { getConfigPath } from "../paths.js";
|
|
|
23
23
|
import { warn, warnOnce } from "../warn.js";
|
|
24
24
|
// Canonical harness-id source of truth (#565) — runtime value re-export.
|
|
25
25
|
export { VALID_HARNESS_IDS } from "./config-types.js";
|
|
26
|
-
// ── Feedback failure-mode constants (F-3 / #384) ────────────────────────────
|
|
27
|
-
// Canonical taxonomy lives in the schema/validator layer; re-exported here so
|
|
28
|
-
// existing `../core/config/config` import sites keep working.
|
|
29
|
-
export { FEEDBACK_FAILURE_MODES } from "./config-schema.js";
|
|
30
26
|
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
31
27
|
export const DEFAULT_CONFIG = {
|
|
32
28
|
configVersion: "0.9.0",
|
|
@@ -2,30 +2,13 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* `feedback` config section. The retired `allowedFailureModes` key (it went
|
|
6
|
+
* with `akm feedback --failure-mode`) is tolerated as an unknown key.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { nonEmptyString } from "./primitives.js";
|
|
10
|
-
// ── Feedback failure modes (F-3 / #384) ─────────────────────────────────────
|
|
11
|
-
/**
|
|
12
|
-
* Curated taxonomy of failure modes for negative feedback.
|
|
13
|
-
*
|
|
14
|
-
* Structured failure modes enable aggregation across feedback events so the
|
|
15
|
-
* distill pipeline can detect that "5 assets failed for the same reason" and
|
|
16
|
-
* act on it — free-text strings about the same issue are not aggregatable.
|
|
17
|
-
*/
|
|
18
|
-
export const FEEDBACK_FAILURE_MODES = [
|
|
19
|
-
"incorrect", // Factually wrong or logically flawed content
|
|
20
|
-
"outdated", // Correct at some point but now stale
|
|
21
|
-
"dangerous", // Could cause harm if followed (security, safety)
|
|
22
|
-
"incomplete", // Missing key steps, context, or caveats
|
|
23
|
-
"redundant", // Duplicates another asset without adding value
|
|
24
|
-
];
|
|
25
9
|
// ── Feedback ────────────────────────────────────────────────────────────────
|
|
26
10
|
export const FeedbackConfigSchema = z
|
|
27
11
|
.object({
|
|
28
12
|
requireReason: z.boolean().optional(),
|
|
29
|
-
allowedFailureModes: z.array(nonEmptyString).optional(),
|
|
30
13
|
})
|
|
31
14
|
.passthrough();
|
package/dist/indexer/indexer.js
CHANGED
|
@@ -5,7 +5,7 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { detectAdapterId } from "../core/adapter/detect-adapter.js";
|
|
7
7
|
import { adapterForId } from "../core/adapter/registry.js";
|
|
8
|
-
import { isHttpUrl } from "../core/common.js";
|
|
8
|
+
import { compareCodePoints, isHttpUrl } from "../core/common.js";
|
|
9
9
|
import { classifyPathAccess, describeInaccessiblePath } from "../core/path-access.js";
|
|
10
10
|
import { getDbPath } from "../core/paths.js";
|
|
11
11
|
import { SCRIPT_EXTENSIONS } from "../core/recognition-util.js";
|
|
@@ -14,6 +14,7 @@ import { isVerbose, warn, warnOnce, warnVerbose } from "../core/warn.js";
|
|
|
14
14
|
import { resolveSourcesForOrigin } from "../registry/origin-resolve.js";
|
|
15
15
|
import { closeDatabase, openExistingDatabase, openIndexDatabase, openReadonlyExistingDatabase, } from "../storage/repositories/index-connection.js";
|
|
16
16
|
import { deleteEntriesByBundle, deleteEntriesByDirAndBundle, deleteEntriesByDirExceptRefs, deleteEntriesByIds, deleteUsageEventsByEntryIds, findEntryIdByRef, getAllEntries, getEmbeddableEntryCount, getEntryCount, getIndexedBundleIdsByDir, getIndexedDirPathsByBundleId, relinkUsageEvents, upsertEntry, } from "../storage/repositories/index-entries-repository.js";
|
|
17
|
+
import { rebuildFtsIfTotalsStale } from "../storage/repositories/index-fts-repository.js";
|
|
17
18
|
import { clearStaleCacheEntries } from "../storage/repositories/index-llm-cache-repository.js";
|
|
18
19
|
import { deleteIndexDirState, deleteMeta, getIndexDirState, getMeta, setMeta, upsertIndexDirState, } from "../storage/repositories/index-meta-repository.js";
|
|
19
20
|
import { VACUUM_PENDING_META } from "../storage/repositories/index-schema.js";
|
|
@@ -125,7 +126,14 @@ export async function runEmbeddingPass(params) {
|
|
|
125
126
|
*/
|
|
126
127
|
function finalizeIndex(args) {
|
|
127
128
|
const { db, sources, sourceDirs, stashDir, signal, onProgress } = args;
|
|
128
|
-
|
|
129
|
+
// Rows replaced or removed since its BM25 totals were taken leave them off; recompute them (#1048).
|
|
130
|
+
const rebuilt = rebuildFtsIfTotalsStale(db);
|
|
131
|
+
onProgress({
|
|
132
|
+
phase: "fts",
|
|
133
|
+
message: rebuilt
|
|
134
|
+
? "Rebuilt the full-text search index to recompute its totals."
|
|
135
|
+
: "Full-text search index is current.",
|
|
136
|
+
});
|
|
129
137
|
const tFtsEnd = Date.now();
|
|
130
138
|
// Re-link state.db usage events to the regenerated index and recompute the
|
|
131
139
|
// derived utility cache. Stored refs already use the current item-ref grammar,
|
|
@@ -907,11 +915,6 @@ function requiresWorkflowSourcePreflight(ctxs) {
|
|
|
907
915
|
}
|
|
908
916
|
/** Phase 2 (sync): write all pre-generated scan records inside a single transaction. */
|
|
909
917
|
function persistDirRecords(db, dirRecords, warnings, bundleByRoot) {
|
|
910
|
-
// Per-source dedup: the same logical asset can appear more than once within
|
|
911
|
-
// one owning source, where source order still makes the first occurrence win.
|
|
912
|
-
// The owner is part of the key so identical concepts in different bundles
|
|
913
|
-
// remain distinct indexed rows.
|
|
914
|
-
const indexedAssetIdentities = new Set();
|
|
915
918
|
const deletedUsageEntryIds = new Set();
|
|
916
919
|
const findPersisted = db.prepare("SELECT id, content_hash, file_path, adapter_id FROM entries WHERE item_ref = ?");
|
|
917
920
|
const insertTransaction = db.transaction(() => {
|
|
@@ -961,7 +964,7 @@ function persistDirRecords(db, dirRecords, warnings, bundleByRoot) {
|
|
|
961
964
|
let persistedRows = 0;
|
|
962
965
|
let dedupedRows = 0;
|
|
963
966
|
if (stash) {
|
|
964
|
-
const
|
|
967
|
+
const sourceRoot = `${path.resolve(currentStashDir)}${path.sep}`;
|
|
965
968
|
for (const entry of stash.entries) {
|
|
966
969
|
const entryPath = entry.filename ? path.join(dirPath, entry.filename) : null;
|
|
967
970
|
if (!entryPath) {
|
|
@@ -973,23 +976,36 @@ function persistDirRecords(db, dirRecords, warnings, bundleByRoot) {
|
|
|
973
976
|
warn(`Skipping entry without adapter-owned concept identity: ${entryPath}`);
|
|
974
977
|
continue;
|
|
975
978
|
}
|
|
976
|
-
// Adapter-owned concept identity is path-based and cannot be replaced
|
|
977
|
-
// by presentation fields such as type/title.
|
|
978
|
-
const identityKey = `${ownerIdentity}\0${adapterConceptId}`;
|
|
979
|
-
if (indexedAssetIdentities.has(identityKey)) {
|
|
980
|
-
dedupedRows++;
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
|
-
indexedAssetIdentities.add(identityKey);
|
|
984
979
|
// content_hash = doc.hash from the drain, keyed by the recognized
|
|
985
980
|
// file's path. A missing hash preserves the existing value on upsert.
|
|
986
981
|
const contentHash = hashByFile?.get(entryPath);
|
|
982
|
+
// Adapter-owned concept identity is path-based and cannot be replaced
|
|
983
|
+
// by presentation fields such as type/title.
|
|
987
984
|
const provenance = deriveEntryProvenance(bundle, entry.type, entry.name, adapterConceptId);
|
|
985
|
+
// Two files of one source can claim one ref (a skill's references/a.md and
|
|
986
|
+
// knowledge/skills/x/references/a.md are both knowledge/skills/x/references/a). The file with
|
|
987
|
+
// the smaller path holds it, whichever directories this run drains and in whatever order the
|
|
988
|
+
// walk met them (#1050): an entry yields to a row held by a file with a smaller path and
|
|
989
|
+
// otherwise takes the ref over. The same concept in another bundle is a different row, and a
|
|
990
|
+
// holder whose file is gone is no claim.
|
|
991
|
+
const holder = findPersisted.get(provenance.itemRef) ?? undefined;
|
|
992
|
+
if (holder &&
|
|
993
|
+
holder.file_path !== entryPath &&
|
|
994
|
+
holder.file_path.startsWith(sourceRoot) &&
|
|
995
|
+
fs.existsSync(holder.file_path)) {
|
|
996
|
+
const yields = compareCodePoints(holder.file_path, entryPath) < 0;
|
|
997
|
+
const [indexed, skipped] = yields ? [holder.file_path, entryPath] : [entryPath, holder.file_path];
|
|
998
|
+
warnings.push(`Two files claim ${provenance.itemRef}: indexed ${indexed}, skipped ${skipped}.`);
|
|
999
|
+
if (yields) {
|
|
1000
|
+
dedupedRows++;
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
// The directory the ref comes from now has a row fewer than its files: drain it again.
|
|
1004
|
+
deleteIndexDirState(db, path.dirname(holder.file_path));
|
|
1005
|
+
}
|
|
988
1006
|
keptItemRefs.add(provenance.itemRef);
|
|
989
1007
|
persistedRows++;
|
|
990
|
-
const previous = sameVariant
|
|
991
|
-
? (findPersisted.get(provenance.itemRef) ?? undefined)
|
|
992
|
-
: undefined;
|
|
1008
|
+
const previous = sameVariant ? holder : undefined;
|
|
993
1009
|
const unchanged = previous !== undefined &&
|
|
994
1010
|
contentHash !== undefined &&
|
|
995
1011
|
previous.content_hash === contentHash &&
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
* `./result-extractor.ts`. Always emitted, mirroring how the Claude builder
|
|
20
20
|
* always emits `--print`: dispatch is the captured, non-interactive path.
|
|
21
21
|
* - Codex is the NATIVE-SCHEMA tier (plan §"Structured-output
|
|
22
|
-
* normalization"): `req.schema` is written to a
|
|
23
|
-
* `--output-schema <file>`. The file is
|
|
24
|
-
*
|
|
25
|
-
* post-run hook, and the
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* normalization"): `req.schema` is written to a file and passed via
|
|
23
|
+
* `--output-schema <file>`. The file is named by the hash of the schema
|
|
24
|
+
* and lives in akm's cache dir, so a schema is written once however many
|
|
25
|
+
* units dispatch with it: `BuiltCommand` has no post-run hook, and the
|
|
26
|
+
* spawned process reads the file after `build()` returns, so a file per
|
|
27
|
+
* dispatch could only leak (#1051). The engine still validates the output
|
|
28
|
+
* defensively (the constrained output is trusted but verified).
|
|
28
29
|
* - `codex exec` has no system-prompt flag; `req.systemPrompt` is folded
|
|
29
30
|
* into the prompt payload (system text first, blank line, then the task),
|
|
30
31
|
* after the `--` end-of-options separator so it can never be parsed as a
|
|
@@ -52,22 +53,29 @@
|
|
|
52
53
|
* that registry, so this builder is reachable under the `"codex"` platform
|
|
53
54
|
* name without any further wiring.
|
|
54
55
|
*/
|
|
55
|
-
import {
|
|
56
|
-
import { tmpdir } from "node:os";
|
|
56
|
+
import { mkdirSync } from "node:fs";
|
|
57
57
|
import { join } from "node:path";
|
|
58
|
+
import { writeFileAtomic } from "../../../core/common.js";
|
|
59
|
+
import { getCacheDir } from "../../../core/paths.js";
|
|
60
|
+
import { sha256Hex } from "../../../runtime.js";
|
|
58
61
|
import { resolveDispatchModel } from "../../agent/builder-shared.js";
|
|
59
62
|
import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
|
|
60
63
|
/**
|
|
61
|
-
* Write a node's JSON Schema to a
|
|
64
|
+
* Write a node's JSON Schema to a file for `--output-schema`, named by the hash
|
|
65
|
+
* of its text, in akm's cache dir. Returns the absolute file path (the value
|
|
66
|
+
* handed to the flag).
|
|
62
67
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
68
|
+
* The same schema is the same file, so concurrent fan-out units share it, and
|
|
69
|
+
* the atomic rename makes a write of identical bytes safe under a reader: no
|
|
70
|
+
* unit can see a half-written schema, and none removes it from another. Nothing
|
|
71
|
+
* is cleaned up, because nothing accumulates but one file per distinct schema.
|
|
66
72
|
*/
|
|
67
73
|
export function writeCodexOutputSchemaFile(schema) {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
74
|
+
const text = `${JSON.stringify(schema, null, 2)}\n`;
|
|
75
|
+
const dir = join(getCacheDir(), "codex-output-schemas");
|
|
76
|
+
mkdirSync(dir, { recursive: true });
|
|
77
|
+
const file = join(dir, `${sha256Hex(text)}.json`);
|
|
78
|
+
writeFileAtomic(file, text);
|
|
71
79
|
return file;
|
|
72
80
|
}
|
|
73
81
|
/**
|
package/dist/llm/client.js
CHANGED
|
@@ -222,12 +222,18 @@ async function chatCompletionReal(config, messages, options) {
|
|
|
222
222
|
* a config/endpoint change can never leave a stale verdict in place.
|
|
223
223
|
*/
|
|
224
224
|
const jsonSchemaUnsupportedConnections = new Set();
|
|
225
|
+
/**
|
|
226
|
+
* The same, for the two thinking-control fields (`chat_template_kwargs` and
|
|
227
|
+
* `enable_thinking`): a strict hosted API answers 400 `Unknown parameter`.
|
|
228
|
+
*/
|
|
229
|
+
const thinkingControlRejectedConnections = new Set();
|
|
225
230
|
function connectionKey(config) {
|
|
226
231
|
return `${config.endpoint}|${config.model}`;
|
|
227
232
|
}
|
|
228
|
-
/** TEST-ONLY. Clear the in-memory
|
|
229
|
-
export function
|
|
233
|
+
/** TEST-ONLY. Clear the in-memory endpoint-support trackers between tests. */
|
|
234
|
+
export function _resetEndpointSupportTrackersForTests() {
|
|
230
235
|
jsonSchemaUnsupportedConnections.clear();
|
|
236
|
+
thinkingControlRejectedConnections.clear();
|
|
231
237
|
}
|
|
232
238
|
/**
|
|
233
239
|
* Whether this connection has already been proven, this process, not to
|
|
@@ -239,6 +245,24 @@ export function _resetJsonSchemaSupportTrackerForTests() {
|
|
|
239
245
|
export function isJsonSchemaKnownUnsupported(config) {
|
|
240
246
|
return jsonSchemaUnsupportedConnections.has(connectionKey(config));
|
|
241
247
|
}
|
|
248
|
+
/** Whether a real call this process showed this connection rejects the thinking-control fields (#1045). */
|
|
249
|
+
export function isThinkingControlKnownRejected(config) {
|
|
250
|
+
return thinkingControlRejectedConnections.has(connectionKey(config));
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Whether `err` is a provider's 4xx (a rate limit is not one) that may be a
|
|
254
|
+
* verdict on the shape of this request rather than on the request's own content.
|
|
255
|
+
*/
|
|
256
|
+
function isRejectedRequest(err) {
|
|
257
|
+
return (err instanceof LlmCallError &&
|
|
258
|
+
err.code === "provider_error" &&
|
|
259
|
+
typeof err.statusCode === "number" &&
|
|
260
|
+
err.statusCode >= 400 &&
|
|
261
|
+
err.statusCode < 500 &&
|
|
262
|
+
err.statusCode !== 429);
|
|
263
|
+
}
|
|
264
|
+
/** A strict API names the field it does not know: `Unknown parameter: 'chat_template_kwargs'.` */
|
|
265
|
+
const THINKING_FIELD_NAMED = /chat_template_kwargs|enable_thinking/;
|
|
242
266
|
/**
|
|
243
267
|
* A single chat-completion attempt: one HTTP request/response cycle, with an
|
|
244
268
|
* inline fallback-once when a schema was requested and the provider 4xx's
|
|
@@ -248,29 +272,35 @@ export function isJsonSchemaKnownUnsupported(config) {
|
|
|
248
272
|
* `config.supportsJsonSchema === false` is the only thing that skips it,
|
|
249
273
|
* and that is a value a human (or workflow author) set explicitly, not one
|
|
250
274
|
* a probe wrote automatically.
|
|
275
|
+
*
|
|
276
|
+
* The thinking-control fields get the same treatment (#1045): a 4xx that
|
|
277
|
+
* names `chat_template_kwargs` or `enable_thinking` is answered by one retry
|
|
278
|
+
* without them, which may in turn fall back without the schema.
|
|
251
279
|
*/
|
|
252
|
-
async function chatCompletionAttempt(config, messages, options, timeoutMs)
|
|
280
|
+
async function chatCompletionAttempt(config, messages, options, timeoutMs, includeThinking = (options?.enableThinking ?? config.enableThinking) !== undefined &&
|
|
281
|
+
!isThinkingControlKnownRejected(config)) {
|
|
253
282
|
const wantsSchema = Boolean(options?.responseSchema) && config.supportsJsonSchema !== false && !isJsonSchemaKnownUnsupported(config);
|
|
254
283
|
try {
|
|
255
|
-
return await chatCompletionAttemptOnce(config, messages, options, timeoutMs, wantsSchema);
|
|
284
|
+
return await chatCompletionAttemptOnce(config, messages, options, timeoutMs, wantsSchema, includeThinking);
|
|
256
285
|
}
|
|
257
286
|
catch (err) {
|
|
258
|
-
if (!
|
|
259
|
-
!(err instanceof LlmCallError) ||
|
|
260
|
-
err.code !== "provider_error" ||
|
|
261
|
-
typeof err.statusCode !== "number" ||
|
|
262
|
-
err.statusCode < 400 ||
|
|
263
|
-
err.statusCode >= 500 ||
|
|
264
|
-
err.statusCode === 429) {
|
|
287
|
+
if (!isRejectedRequest(err))
|
|
265
288
|
throw err;
|
|
289
|
+
if (includeThinking && THINKING_FIELD_NAMED.test(err.message)) {
|
|
290
|
+
warnVerbose(`[akm] LLM rejected chat_template_kwargs/enable_thinking (${err.statusCode}); retrying once without them: ${err.message}`);
|
|
291
|
+
const fallback = await chatCompletionAttempt(config, messages, options, timeoutMs, false);
|
|
292
|
+
thinkingControlRejectedConnections.add(connectionKey(config));
|
|
293
|
+
return fallback;
|
|
266
294
|
}
|
|
295
|
+
if (!wantsSchema)
|
|
296
|
+
throw err;
|
|
267
297
|
warnVerbose(`[akm] LLM rejected response_format:json_schema (${err.statusCode}); retrying once without it: ${err.message}`);
|
|
268
|
-
const fallback = await chatCompletionAttemptOnce(config, messages, options, timeoutMs, false);
|
|
298
|
+
const fallback = await chatCompletionAttemptOnce(config, messages, options, timeoutMs, false, includeThinking);
|
|
269
299
|
jsonSchemaUnsupportedConnections.add(connectionKey(config));
|
|
270
300
|
return fallback;
|
|
271
301
|
}
|
|
272
302
|
}
|
|
273
|
-
async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, includeSchema) {
|
|
303
|
+
async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, includeSchema, includeThinking) {
|
|
274
304
|
if (config.extraParams !== undefined) {
|
|
275
305
|
const issue = validateExtraParams(config.extraParams)[0];
|
|
276
306
|
if (issue)
|
|
@@ -309,7 +339,7 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
|
|
|
309
339
|
// (freellmapi, Bifrost) can drop either one depending on how it was built.
|
|
310
340
|
// Send both whenever thinking is explicitly resolved so the same engine
|
|
311
341
|
// block keeps working across a direct vhost or any gateway in front of it.
|
|
312
|
-
const resolvedEnableThinking = options?.enableThinking ?? config.enableThinking;
|
|
342
|
+
const resolvedEnableThinking = includeThinking ? (options?.enableThinking ?? config.enableThinking) : undefined;
|
|
313
343
|
const thinkingParams = resolvedEnableThinking === undefined
|
|
314
344
|
? {}
|
|
315
345
|
: { chat_template_kwargs: { enable_thinking: resolvedEnableThinking }, enable_thinking: resolvedEnableThinking };
|
package/dist/llm/memory-infer.js
CHANGED
|
@@ -46,7 +46,7 @@ const PROMPT_PLACEHOLDERS = new Set([
|
|
|
46
46
|
* — no more "incomplete derived memory payload from LLM; skipping memory"
|
|
47
47
|
* for shape-only failures.
|
|
48
48
|
*/
|
|
49
|
-
const DERIVED_MEMORY_JSON_SCHEMA = {
|
|
49
|
+
export const DERIVED_MEMORY_JSON_SCHEMA = {
|
|
50
50
|
type: "object",
|
|
51
51
|
properties: {
|
|
52
52
|
title: { type: "string", minLength: 1 },
|
|
@@ -25639,6 +25639,7 @@ var $visitAsync = visit.visitAsync;
|
|
|
25639
25639
|
import path7 from "node:path";
|
|
25640
25640
|
// src/runtime.ts
|
|
25641
25641
|
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
|
|
25642
|
+
import { createHash } from "node:crypto";
|
|
25642
25643
|
import { createWriteStream, statfsSync } from "node:fs";
|
|
25643
25644
|
import { createRequire as createRequire2 } from "node:module";
|
|
25644
25645
|
import path6 from "node:path";
|
|
@@ -26803,6 +26804,9 @@ async function writeResponseToFileCapped(filePath, res, options) {
|
|
|
26803
26804
|
reader.releaseLock?.();
|
|
26804
26805
|
}
|
|
26805
26806
|
}
|
|
26807
|
+
function sha256Hex(data) {
|
|
26808
|
+
return createHash("sha256").update(data).digest("hex");
|
|
26809
|
+
}
|
|
26806
26810
|
function getDirname(importMetaUrl) {
|
|
26807
26811
|
return path6.dirname(fileURLToPath(importMetaUrl));
|
|
26808
26812
|
}
|
|
@@ -35845,8 +35849,7 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
35845
35849
|
|
|
35846
35850
|
// src/core/config/schema/feedback.ts
|
|
35847
35851
|
var FeedbackConfigSchema = exports_external.object({
|
|
35848
|
-
requireReason: exports_external.boolean().optional()
|
|
35849
|
-
allowedFailureModes: exports_external.array(nonEmptyString).optional()
|
|
35852
|
+
requireReason: exports_external.boolean().optional()
|
|
35850
35853
|
}).passthrough();
|
|
35851
35854
|
|
|
35852
35855
|
// src/core/config/schema/improve-processes.ts
|
|
@@ -36850,7 +36853,7 @@ function withConfigLock(fn) {
|
|
|
36850
36853
|
}
|
|
36851
36854
|
|
|
36852
36855
|
// src/core/config/config-sources.ts
|
|
36853
|
-
import { createHash } from "node:crypto";
|
|
36856
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
36854
36857
|
import fs9 from "node:fs";
|
|
36855
36858
|
import path13 from "node:path";
|
|
36856
36859
|
function bundleComponentConfig(bundle) {
|
|
@@ -36913,7 +36916,7 @@ function filesystemBundleSourceId(contentRoot) {
|
|
|
36913
36916
|
});
|
|
36914
36917
|
}
|
|
36915
36918
|
function hashBundleSourceIdentity(identity3) {
|
|
36916
|
-
return `sha256:${
|
|
36919
|
+
return `sha256:${createHash2("sha256").update("akm.bundle-source\x00v1\x00").update(JSON.stringify(identity3)).digest("hex")}`;
|
|
36917
36920
|
}
|
|
36918
36921
|
function bundleContentRoots(config) {
|
|
36919
36922
|
const bundles = config.bundles ?? {};
|
|
@@ -38375,7 +38378,7 @@ async function tryFetchJson(url, headers) {
|
|
|
38375
38378
|
}
|
|
38376
38379
|
|
|
38377
38380
|
// src/sources/providers/provider-utils.ts
|
|
38378
|
-
import { createHash as
|
|
38381
|
+
import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
|
|
38379
38382
|
import fs27 from "node:fs";
|
|
38380
38383
|
import path34 from "node:path";
|
|
38381
38384
|
|
|
@@ -38470,7 +38473,7 @@ function countLines(text) {
|
|
|
38470
38473
|
}
|
|
38471
38474
|
|
|
38472
38475
|
// src/core/adapter/adapters/shared.ts
|
|
38473
|
-
import { createHash as
|
|
38476
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
38474
38477
|
|
|
38475
38478
|
// src/core/asset/frontmatter-lint.ts
|
|
38476
38479
|
function checkUnquotedDescriptionColon(frontmatterText) {
|
|
@@ -38492,7 +38495,7 @@ function checkUnquotedDescriptionColon(frontmatterText) {
|
|
|
38492
38495
|
|
|
38493
38496
|
// src/core/adapter/adapters/shared.ts
|
|
38494
38497
|
function hashContent(content) {
|
|
38495
|
-
return
|
|
38498
|
+
return createHash3("sha256").update(content, "utf8").digest("hex");
|
|
38496
38499
|
}
|
|
38497
38500
|
function nonEmptyString2(value) {
|
|
38498
38501
|
if (typeof value !== "string")
|
|
@@ -39675,7 +39678,7 @@ function extractDirTagsFromName(name) {
|
|
|
39675
39678
|
}
|
|
39676
39679
|
|
|
39677
39680
|
// src/core/adapter/execution-source.ts
|
|
39678
|
-
import { createHash as
|
|
39681
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
39679
39682
|
|
|
39680
39683
|
// src/execution/source.ts
|
|
39681
39684
|
var EXECUTION_SOURCE_SCHEMA_VERSION = 1;
|
|
@@ -40013,7 +40016,7 @@ function renderMarkdownExecutionSource(input) {
|
|
|
40013
40016
|
bundle: identity3.bundle,
|
|
40014
40017
|
adapter: identity3.adapter,
|
|
40015
40018
|
file: identity3.file,
|
|
40016
|
-
hash:
|
|
40019
|
+
hash: createHash4("sha256").update(raw, "utf8").digest("hex")
|
|
40017
40020
|
},
|
|
40018
40021
|
...hasDefaults ? { defaults } : {},
|
|
40019
40022
|
...extensions === undefined ? {} : { extensions }
|
|
@@ -46568,7 +46571,7 @@ function uniqueSlug() {
|
|
|
46568
46571
|
}
|
|
46569
46572
|
function buildInstallCacheDir(cacheRootDir, source, id, version) {
|
|
46570
46573
|
const safeId = id.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
46571
|
-
const identitySuffix = version === "writable" ? `-${
|
|
46574
|
+
const identitySuffix = version === "writable" ? `-${createHash5("sha256").update(`${source}:${id}`).digest("hex").slice(0, 12)}` : "";
|
|
46572
46575
|
const slug = `${source}-${safeId}${identitySuffix}`;
|
|
46573
46576
|
const versionSlug = source === "local" ? uniqueSlug() : version?.replace(/[^a-zA-Z0-9_.-]+/g, "-") ?? uniqueSlug();
|
|
46574
46577
|
return path34.join(cacheRootDir, slug || source, versionSlug);
|
|
@@ -46589,7 +46592,7 @@ async function downloadArchive(url, destination) {
|
|
|
46589
46592
|
}
|
|
46590
46593
|
async function computeFileHash(filePath) {
|
|
46591
46594
|
const data = fs27.readFileSync(filePath);
|
|
46592
|
-
const hash =
|
|
46595
|
+
const hash = createHash5("sha256").update(data).digest("hex");
|
|
46593
46596
|
return `sha256:${hash}`;
|
|
46594
46597
|
}
|
|
46595
46598
|
function isDirectory(target) {
|
|
@@ -46764,7 +46767,7 @@ function redactUrlUserinfo(text) {
|
|
|
46764
46767
|
return text.replace(/\b([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^\s/@]+)@/g, "$1[REDACTED]@");
|
|
46765
46768
|
}
|
|
46766
46769
|
// src/sources/providers/git-provider.ts
|
|
46767
|
-
import { createHash as
|
|
46770
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
46768
46771
|
import fs30 from "node:fs";
|
|
46769
46772
|
import path36 from "node:path";
|
|
46770
46773
|
|
|
@@ -46825,7 +46828,7 @@ function resolveGitContentDir(config) {
|
|
|
46825
46828
|
throw new ConfigError("git source entry must have either `path` or `url`");
|
|
46826
46829
|
}
|
|
46827
46830
|
function getCachePaths(repoUrl, cacheRootOverride) {
|
|
46828
|
-
const key =
|
|
46831
|
+
const key = createHash6("sha256").update(repoUrl).digest("hex").slice(0, 16);
|
|
46829
46832
|
const cacheRoot = cacheRootOverride ?? getRegistryIndexCacheDir();
|
|
46830
46833
|
const rootDir = path36.join(cacheRoot, `git-${key}`);
|
|
46831
46834
|
return {
|
|
@@ -47046,7 +47049,7 @@ import fs33 from "node:fs";
|
|
|
47046
47049
|
import path39 from "node:path";
|
|
47047
47050
|
|
|
47048
47051
|
// src/sources/providers/tar-utils.ts
|
|
47049
|
-
import { createHash as
|
|
47052
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
47050
47053
|
import fs32 from "node:fs";
|
|
47051
47054
|
import path38 from "node:path";
|
|
47052
47055
|
function verifyArchiveIntegrity(archivePath, expected, source) {
|
|
@@ -47059,7 +47062,7 @@ function verifyArchiveIntegrity(archivePath, expected, source) {
|
|
|
47059
47062
|
const dashIndex = expected.indexOf("-");
|
|
47060
47063
|
const algorithm = expected.slice(0, dashIndex);
|
|
47061
47064
|
const expectedBase64 = expected.slice(dashIndex + 1);
|
|
47062
|
-
const actualBase64 =
|
|
47065
|
+
const actualBase64 = createHash7(algorithm).update(fileBuffer).digest("base64");
|
|
47063
47066
|
if (actualBase64 !== expectedBase64) {
|
|
47064
47067
|
fs32.unlinkSync(archivePath);
|
|
47065
47068
|
throw new Error(`Integrity check failed for ${archivePath}: expected ${algorithm} digest ${expectedBase64}, got ${actualBase64}`);
|
|
@@ -47067,7 +47070,7 @@ function verifyArchiveIntegrity(archivePath, expected, source) {
|
|
|
47067
47070
|
return;
|
|
47068
47071
|
}
|
|
47069
47072
|
if (/^[0-9a-f]{40}$/i.test(expected)) {
|
|
47070
|
-
const actualHex =
|
|
47073
|
+
const actualHex = createHash7("sha1").update(fileBuffer).digest("hex");
|
|
47071
47074
|
if (actualHex.toLowerCase() !== expected.toLowerCase()) {
|
|
47072
47075
|
fs32.unlinkSync(archivePath);
|
|
47073
47076
|
throw new Error(`Integrity check failed for ${archivePath}: expected sha1 ${expected}, got ${actualHex}`);
|
|
@@ -51670,14 +51673,15 @@ class ClaudeHarness extends BaseHarness {
|
|
|
51670
51673
|
}
|
|
51671
51674
|
|
|
51672
51675
|
// src/integrations/harnesses/codex/agent-builder.ts
|
|
51673
|
-
import {
|
|
51674
|
-
import { tmpdir } from "node:os";
|
|
51676
|
+
import { mkdirSync } from "node:fs";
|
|
51675
51677
|
import { join } from "node:path";
|
|
51676
51678
|
function writeCodexOutputSchemaFile(schema) {
|
|
51677
|
-
const
|
|
51678
|
-
|
|
51679
|
-
|
|
51680
|
-
|
|
51679
|
+
const text = `${JSON.stringify(schema, null, 2)}
|
|
51680
|
+
`;
|
|
51681
|
+
const dir = join(getCacheDir(), "codex-output-schemas");
|
|
51682
|
+
mkdirSync(dir, { recursive: true });
|
|
51683
|
+
const file = join(dir, `${sha256Hex(text)}.json`);
|
|
51684
|
+
writeFileAtomic(file, text);
|
|
51681
51685
|
return file;
|
|
51682
51686
|
}
|
|
51683
51687
|
function ensureSandboxFlags(base) {
|