akm-cli 0.9.5 → 0.9.6
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 +105 -0
- package/dist/commands/env/env-cli.js +1 -2
- package/dist/commands/env/secret-cli.js +1 -5
- package/dist/commands/feedback-cli.js +0 -4
- package/dist/commands/health/checks.js +0 -32
- package/dist/commands/health/surfaces.js +2 -2
- package/dist/commands/health.js +6 -15
- package/dist/commands/improve/autonomy-gate.js +1 -1
- package/dist/commands/improve/consolidate.js +25 -26
- package/dist/commands/improve/distill.js +2 -2
- package/dist/commands/improve/extract.js +8 -1
- package/dist/commands/improve/improve.js +3 -5
- package/dist/commands/improve/locks.js +13 -5
- package/dist/commands/improve/loop-stages.js +8 -9
- package/dist/commands/improve/memory/memory-contradiction-detect.js +1 -19
- package/dist/commands/improve/outcome-loop.js +0 -6
- package/dist/commands/improve/reflect.js +20 -9
- package/dist/commands/lint/index.js +4 -6
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -7
- package/dist/commands/sources/bundle-config-ops.js +9 -4
- package/dist/commands/sources/info.js +14 -10
- package/dist/commands/tasks/tasks.js +12 -16
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/common.js +61 -24
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-sources.js +32 -2
- package/dist/core/config/config.js +1 -1
- package/dist/core/config/schema/engines.js +9 -7
- package/dist/core/config/schema/primitives.js +0 -5
- package/dist/core/config/schema/search.js +2 -1
- package/dist/core/file-lock.js +2 -1
- package/dist/core/maintenance-barrier.js +2 -14
- package/dist/core/paths.js +0 -3
- package/dist/core/redaction.js +2 -2
- package/dist/core/spawn-env.js +8 -12
- package/dist/core/state/migrations.js +1 -12
- package/dist/core/state-db.js +9 -27
- package/dist/core/write-source.js +12 -19
- package/dist/execution/directory-identity.js +36 -10
- package/dist/indexer/graph/graph-boost.js +0 -4
- package/dist/indexer/index-writer-lock.js +43 -24
- package/dist/indexer/index-written-assets.js +5 -6
- package/dist/indexer/indexer.js +2 -39
- package/dist/indexer/materialize-embeddings.js +85 -41
- package/dist/indexer/search/db-search.js +15 -48
- package/dist/indexer/search/ranking-contributors.js +0 -25
- package/dist/indexer/search/ranking.js +3 -13
- package/dist/integrations/agent/builder-shared.js +0 -25
- package/dist/integrations/agent/model-map.js +2 -60
- package/dist/integrations/harnesses/aider/agent-builder.js +1 -3
- package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -7
- package/dist/integrations/harnesses/claude/agent-builder.js +1 -4
- package/dist/integrations/harnesses/codex/agent-builder.js +1 -4
- package/dist/integrations/harnesses/copilot/agent-builder.js +2 -6
- package/dist/integrations/harnesses/gemini/agent-builder.js +2 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -4
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +2 -16
- package/dist/integrations/harnesses/openhands/agent-builder.js +1 -3
- package/dist/integrations/harnesses/pi/agent-builder.js +2 -4
- package/dist/integrations/session-logs/index.js +0 -9
- package/dist/llm/client.js +75 -42
- package/dist/llm/embedder.js +7 -3
- package/dist/llm/embedders/remote.js +141 -42
- package/dist/registry/network.js +5 -37
- package/dist/runtime.js +2 -10
- package/dist/scripts/akm-migrate-node.js +60 -126
- package/dist/scripts/akm-migrate.js +60 -126
- package/dist/setup/engine-config.js +2 -5
- package/dist/setup/registry-stash-loader.js +0 -8
- package/dist/setup/setup.js +9 -46
- package/dist/setup/steps/connection-shared.js +10 -13
- package/dist/sources/providers/git-install.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +20 -1
- package/dist/storage/repositories/index-entries-repository.js +0 -15
- package/dist/tasks/backends/launchd.js +15 -20
- package/dist/tasks/backends/schtasks.js +18 -8
- package/dist/tasks/run/run-native-task.js +8 -6
- package/dist/tasks/source/bounded-document.js +2 -4
- package/dist/tasks/source/task-source-v3-frozen.js +5 -7
- package/dist/tasks/source/task-source-v4.js +5 -10
- package/dist/tasks/source/task-to-v3.js +5 -10
- package/dist/tasks/source/task-to-v4.js +1 -4
- package/dist/tasks/source-v3.js +6 -6
- package/dist/workflows/exec/native-executor.js +21 -31
- package/dist/workflows/exec/run-workflow.js +5 -6
- package/dist/workflows/exec/scheduler.js +3 -19
- package/dist/workflows/exec/step-work.js +1 -4
- package/dist/workflows/exec/unit-dispatch.js +2 -2
- package/dist/workflows/exec/worktree.js +1 -13
- package/dist/workflows/freeze/targets/child-workflow.js +2 -10
- package/dist/workflows/ir/plan-hash.js +4 -6
- package/dist/workflows/ir/schema-v4.js +0 -12
- package/dist/workflows/ir/schema.js +20 -31
- package/dist/workflows/parser.js +11 -52
- package/dist/workflows/renderer.js +2 -3
- package/dist/workflows/resource-limits.js +11 -41
- package/dist/workflows/runtime/runs.js +3 -4
- package/dist/workflows/source-ir/schema.js +14 -30
- package/dist/workflows/validator.js +1 -7
- package/docs/reference/configuration.md +1 -1
- package/package.json +1 -1
- package/schemas/akm-config.json +4 -23
- package/schemas/akm-task.json +1 -2
- package/schemas/akm-workflow.json +1 -13
- package/dist/indexer/search/semantic-status.js +0 -142
|
@@ -44,7 +44,7 @@ import { prepareInlineExecution, prepareInlineExecutionWithRunner } from "../../
|
|
|
44
44
|
import { buildReflectOutputRepairPrompt, buildReflectPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "../../integrations/agent/prompts.js";
|
|
45
45
|
import { runnerIsLlm, runnerSupportsFileWrite } from "../../integrations/agent/runner.js";
|
|
46
46
|
import { collectDispatchSensitiveValues } from "../../integrations/agent/runner-dispatch.js";
|
|
47
|
-
import { LlmCallError } from "../../llm/client.js";
|
|
47
|
+
import { isJsonSchemaKnownUnsupported, LlmCallError } from "../../llm/client.js";
|
|
48
48
|
import { callStructured } from "../../llm/structured-call.js";
|
|
49
49
|
import { baseFailureFields, enoentHintMessage, isEnoentFailure } from "../agent/agent-support.js";
|
|
50
50
|
import { isProposalSkipped, listProposalsReadOnly, proposalContent, } from "../proposal/repository.js";
|
|
@@ -578,8 +578,9 @@ export function sanitizeReflectPayload(payload, sourceContent, targetRef) {
|
|
|
578
578
|
}
|
|
579
579
|
/**
|
|
580
580
|
* JSON Schema for structured reflect output. Passed to `chatCompletion` when
|
|
581
|
-
*
|
|
582
|
-
* JSON object containing only the target-scoped
|
|
581
|
+
* {@link wantsJsonSchemaOutput} selects `outputMode: "json_schema"`, so the
|
|
582
|
+
* model returns a strict JSON object containing only the target-scoped
|
|
583
|
+
* fields AKM cannot derive.
|
|
583
584
|
*/
|
|
584
585
|
const REFLECT_FRONTMATTER_PATCH_JSON_SCHEMA = {
|
|
585
586
|
type: "object",
|
|
@@ -626,6 +627,17 @@ const REFLECT_UNSCOPED_JSON_SCHEMA = {
|
|
|
626
627
|
frontmatterPatch: REFLECT_FRONTMATTER_PATCH_JSON_SCHEMA,
|
|
627
628
|
},
|
|
628
629
|
};
|
|
630
|
+
/**
|
|
631
|
+
* Whether to frame the reflect prompt for structured JSON output on this
|
|
632
|
+
* connection. Optimistic by default — `chatCompletion` attempts
|
|
633
|
+
* `response_format: json_schema` fresh on every call and falls back once on
|
|
634
|
+
* a 4xx, so there is no persisted verdict to consult here. `false` only when
|
|
635
|
+
* a human/workflow explicitly disabled it, or a real call already proved
|
|
636
|
+
* this connection rejects it earlier in the same process.
|
|
637
|
+
*/
|
|
638
|
+
function wantsJsonSchemaOutput(connection) {
|
|
639
|
+
return connection.supportsJsonSchema !== false && !isJsonSchemaKnownUnsupported(connection);
|
|
640
|
+
}
|
|
629
641
|
/** Critique prompt injected between prior draft and refinement request (Self-Refine loop). */
|
|
630
642
|
const REFLECT_CRITIQUE_PROMPT = "Your previous proposal is shown above. Review it critically and provide an improved version that is more specific, actionable, and avoids any issues with the previous attempt. Return only the improved response using the output contract from the original prompt.";
|
|
631
643
|
/**
|
|
@@ -1354,21 +1366,20 @@ async function resolveReflectSource(options, stash, emitReflectFailed) {
|
|
|
1354
1366
|
}
|
|
1355
1367
|
/**
|
|
1356
1368
|
* Run the agent with the optional Self-Refine loop (R-1 / #372): up to
|
|
1357
|
-
*
|
|
1369
|
+
* `maxRefineIters` invocations, each injecting the prior draft as self-critique
|
|
1358
1370
|
* context and exiting early on a no-op refinement. Synthesizes per-iteration
|
|
1359
1371
|
* draft paths into `draftPathsToCleanup` (mutated) and returns the final agent
|
|
1360
1372
|
* result + last draft path. Extracted verbatim from `akmReflect`.
|
|
1361
1373
|
*/
|
|
1362
1374
|
async function runReflectRefineIterations(args) {
|
|
1363
1375
|
const { options, parsedRef, assetContent, feedback, schemaHints, relatedLessons, rejectedProposals, standardsContext, runnerSpec, lease, agentEnv, draftPathsToCleanup, onNotices, } = args;
|
|
1364
|
-
const
|
|
1365
|
-
const maxRefineIters = Math.min(Math.max(1, options.maxRefineIters ?? 1), MAX_REFINE_ITERS);
|
|
1376
|
+
const maxRefineIters = Math.max(1, options.maxRefineIters ?? 1);
|
|
1366
1377
|
// Determine whether this dispatch can honour the file-write contract.
|
|
1367
1378
|
// Agent CLI + OpenCode SDK runners both have filesystem access; the direct
|
|
1368
1379
|
// LLM HTTP runner does NOT.
|
|
1369
1380
|
const canRunnerWriteFile = runnerSupportsFileWrite(runnerSpec);
|
|
1370
1381
|
const outputMode = runnerIsLlm(runnerSpec)
|
|
1371
|
-
? runnerSpec.connection
|
|
1382
|
+
? wantsJsonSchemaOutput(runnerSpec.connection)
|
|
1372
1383
|
? "json_schema"
|
|
1373
1384
|
: "framed_markdown"
|
|
1374
1385
|
: undefined;
|
|
@@ -1421,10 +1432,10 @@ async function runReflectRefineIterations(args) {
|
|
|
1421
1432
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1422
1433
|
priorDraft,
|
|
1423
1434
|
iteration: iter,
|
|
1424
|
-
...(
|
|
1435
|
+
...(outputMode === "json_schema"
|
|
1425
1436
|
? { responseSchema: options.ref ? REFLECT_JSON_SCHEMA : REFLECT_UNSCOPED_JSON_SCHEMA }
|
|
1426
1437
|
: {}),
|
|
1427
|
-
outputMode:
|
|
1438
|
+
outputMode: outputMode ?? "framed_markdown",
|
|
1428
1439
|
...(options.ref ? { targetRef: options.ref } : {}),
|
|
1429
1440
|
allowRepair: repairAttempts === 0,
|
|
1430
1441
|
...(options.chat ? { chat: options.chat } : {}),
|
|
@@ -92,7 +92,7 @@ function collectWorkflowFiles(dir) {
|
|
|
92
92
|
continue;
|
|
93
93
|
const full = path.join(dir, entry.name);
|
|
94
94
|
if (entry.isDirectory()) {
|
|
95
|
-
if (
|
|
95
|
+
if (isAkmRegistryCachePath(full))
|
|
96
96
|
continue;
|
|
97
97
|
results.push(...collectWorkflowFiles(full));
|
|
98
98
|
continue;
|
|
@@ -185,11 +185,9 @@ function collectAdapterFiles(root, extensions) {
|
|
|
185
185
|
walk(full);
|
|
186
186
|
}
|
|
187
187
|
else if (entry.isFile() && matchesAdapterExtension(entry.name, extensions)) {
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
const segments = path.relative(root, full).split(/[\\/]/);
|
|
192
|
-
if (segments.includes(".cache") || segments.includes("registry"))
|
|
188
|
+
// Skip akm's OWN resolved registry-cache copies, not any user
|
|
189
|
+
// directory that happens to be named ".cache" or "registry".
|
|
190
|
+
if (isAkmRegistryCachePath(full))
|
|
193
191
|
continue;
|
|
194
192
|
results.push(full);
|
|
195
193
|
}
|
|
@@ -196,13 +196,6 @@ export function hasHotCaptureMode(frontmatter) {
|
|
|
196
196
|
* with 80-90% overlap legitimately compress to well under 50% of the larger.
|
|
197
197
|
*/
|
|
198
198
|
export const MERGE_SHRINK_RATIO_MIN = 0.3;
|
|
199
|
-
/**
|
|
200
|
-
* Absolute floor (chars) for merged body. When sources are short (<~333 chars),
|
|
201
|
-
* `MERGE_SHRINK_RATIO_MIN × largerBodyLen` falls below this and the absolute
|
|
202
|
-
* floor dominates — prevents false positives on very terse memory pairs.
|
|
203
|
-
* Matches the existing `promote_source_too_small` floor of 100 chars.
|
|
204
|
-
*/
|
|
205
|
-
export const MERGE_ABSOLUTE_FLOOR_CHARS = 100;
|
|
206
199
|
// ── Reflect size gate ────────────────────────────────────────────────────────
|
|
207
200
|
/** Ratio lower-bound: proposed body must be at least this fraction of source. */
|
|
208
201
|
export const REFLECT_SHRINK_RATIO_MIN = 0.5;
|
|
@@ -13,20 +13,25 @@
|
|
|
13
13
|
* keying rules across call sites.
|
|
14
14
|
*/
|
|
15
15
|
import path from "node:path";
|
|
16
|
-
import { primaryBundlePath } from "../../core/config/config.js";
|
|
16
|
+
import { bundleKeyForContentRoot, primaryBundlePath } from "../../core/config/config.js";
|
|
17
17
|
import { deriveBundleId } from "../../indexer/installations.js";
|
|
18
18
|
export { primaryBundlePath };
|
|
19
19
|
/**
|
|
20
20
|
* Upsert the primary filesystem bundle (`{ path, writable: true }`) and point
|
|
21
21
|
* `defaultBundle` at it. Reuses the current default key when it already names a
|
|
22
|
-
* filesystem bundle (so re-pointing the primary keeps a stable id)
|
|
23
|
-
*
|
|
22
|
+
* filesystem bundle (so re-pointing the primary keeps a stable id). Otherwise,
|
|
23
|
+
* reuses whichever configured bundle ALREADY resolves to `stashDir`'s content
|
|
24
|
+
* root (issue #870 — `AKM_BUNDLE_DIR`/`--dir` pointed at a directory already
|
|
25
|
+
* configured under a different id must not mint a second bundle for it); only
|
|
26
|
+
* when no bundle owns that root yet is a fresh slug-legal key derived.
|
|
24
27
|
*/
|
|
25
28
|
export function withPrimaryBundle(config, stashDir) {
|
|
26
29
|
const bundles = { ...(config.bundles ?? {}) };
|
|
27
30
|
let key = config.defaultBundle;
|
|
28
31
|
if (!key || !(key in bundles) || typeof bundles[key]?.path !== "string") {
|
|
29
|
-
key =
|
|
32
|
+
key =
|
|
33
|
+
bundleKeyForContentRoot(config, path.resolve(stashDir)) ??
|
|
34
|
+
deriveBundleId(undefined, stashDir, new Set(Object.keys(bundles)));
|
|
30
35
|
}
|
|
31
36
|
bundles[key] = {
|
|
32
37
|
...bundles[key],
|
|
@@ -8,7 +8,6 @@ import { classifyPathAccess, describeInaccessiblePath } from "../../core/path-ac
|
|
|
8
8
|
import { getDbPath } from "../../core/paths.js";
|
|
9
9
|
import { formatRegistryUrl } from "../../core/registry-url.js";
|
|
10
10
|
import { error } from "../../core/warn.js";
|
|
11
|
-
import { getEffectiveSemanticStatus, readSemanticStatus } from "../../indexer/search/semantic-status.js";
|
|
12
11
|
import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
|
|
13
12
|
import { getEntryCount, getEntryCountByType } from "../../storage/repositories/index-entries-repository.js";
|
|
14
13
|
import { getMeta } from "../../storage/repositories/index-meta-repository.js";
|
|
@@ -29,13 +28,6 @@ export function assembleInfo(options) {
|
|
|
29
28
|
const defaultBundle = config.defaultBundle ?? null;
|
|
30
29
|
// Asset types (copy into a mutable array — `placementTypes()` returns readonly)
|
|
31
30
|
const assetTypes = [...placementTypes()];
|
|
32
|
-
const semanticRuntime = readSemanticStatus();
|
|
33
|
-
const semanticStatus = getEffectiveSemanticStatus(config, semanticRuntime);
|
|
34
|
-
// Search modes
|
|
35
|
-
const searchModes = ["fts"];
|
|
36
|
-
if (semanticStatus === "ready-js" || semanticStatus === "ready-vec") {
|
|
37
|
-
searchModes.push("semantic", "hybrid");
|
|
38
|
-
}
|
|
39
31
|
// Registries (strip sensitive fields like apiKey from options)
|
|
40
32
|
const registries = (config.registries ?? []).map((r) => ({
|
|
41
33
|
url: formatRegistryUrl(r.url),
|
|
@@ -58,6 +50,20 @@ export function assembleInfo(options) {
|
|
|
58
50
|
// health and search use, so info reads the same database they do.
|
|
59
51
|
const resolvedDbPath = options?.dbPath ?? getDbPath();
|
|
60
52
|
const indexStats = readIndexStats(resolvedDbPath);
|
|
53
|
+
// Semantic status is read live from the index's own state, not a cached
|
|
54
|
+
// verdict — a failed embed attempt at search time falls back to FTS and
|
|
55
|
+
// reports that in the search response, it never disables the mode here.
|
|
56
|
+
const semanticStatus = config.semanticSearchMode === "off"
|
|
57
|
+
? "disabled"
|
|
58
|
+
: !indexStats.hasEmbeddings
|
|
59
|
+
? "pending"
|
|
60
|
+
: indexStats.vecAvailable
|
|
61
|
+
? "ready-vec"
|
|
62
|
+
: "ready-js";
|
|
63
|
+
const searchModes = ["fts"];
|
|
64
|
+
if (semanticStatus === "ready-js" || semanticStatus === "ready-vec") {
|
|
65
|
+
searchModes.push("semantic", "hybrid");
|
|
66
|
+
}
|
|
61
67
|
return {
|
|
62
68
|
schemaVersion: 1,
|
|
63
69
|
version: pkgVersion,
|
|
@@ -68,8 +74,6 @@ export function assembleInfo(options) {
|
|
|
68
74
|
semanticSearch: {
|
|
69
75
|
mode: config.semanticSearchMode,
|
|
70
76
|
status: semanticStatus,
|
|
71
|
-
...(semanticRuntime?.reason ? { reason: semanticRuntime.reason } : {}),
|
|
72
|
-
...(semanticRuntime?.message ? { message: semanticRuntime.message } : {}),
|
|
73
77
|
},
|
|
74
78
|
registries,
|
|
75
79
|
sourceProviders,
|
|
@@ -39,7 +39,6 @@ import { assertSchedulerNativeArtifactOwnership, assertSchedulerSourceSnapshot,
|
|
|
39
39
|
import { renderSchedulerPlanPreview, renderSchedulerSyncPlanPreview, } from "../../tasks/scheduler-sync-preview.js";
|
|
40
40
|
import { parseTaskSource } from "../../tasks/source/parse-task-source.js";
|
|
41
41
|
import { projectTaskSourceV4 } from "../../tasks/source/project-v4.js";
|
|
42
|
-
import { TASK_V3_MAX_SOURCE_BYTES } from "../../tasks/source-v3.js";
|
|
43
42
|
import { normaliseTaskConceptId, normaliseTaskId } from "../../tasks/task-id.js";
|
|
44
43
|
import { applyAutonomyGate, configuredDirectAutonomyLanes, describeGatedLanes } from "../improve/autonomy-gate.js";
|
|
45
44
|
import { resolveImproveStrategy } from "../improve/improve-strategies.js";
|
|
@@ -971,13 +970,7 @@ function captureTaskSourceExpectation(filePathInput, rootInput) {
|
|
|
971
970
|
if (!rootStat.isDirectory()) {
|
|
972
971
|
throw new UsageError(`${root} is not a task source directory.`, "INVALID_FLAG_VALUE");
|
|
973
972
|
}
|
|
974
|
-
const common = {
|
|
975
|
-
filePath,
|
|
976
|
-
rootRealPath,
|
|
977
|
-
rootPhysicalIdentity: rootStat.ino === 0n ? `path:${rootRealPath}` : `inode:${rootStat.dev}:${rootStat.ino}`,
|
|
978
|
-
rootMtimeNs: String(rootStat.mtimeNs),
|
|
979
|
-
rootCtimeNs: String(rootStat.ctimeNs),
|
|
980
|
-
};
|
|
973
|
+
const common = { filePath, rootRealPath };
|
|
981
974
|
let descriptor;
|
|
982
975
|
try {
|
|
983
976
|
const noFollow = "O_NOFOLLOW" in fs.constants ? fs.constants.O_NOFOLLOW : 0;
|
|
@@ -986,9 +979,6 @@ function captureTaskSourceExpectation(filePathInput, rootInput) {
|
|
|
986
979
|
if (!before.isFile()) {
|
|
987
980
|
throw new UsageError(`${filePath} is not a regular task source.`, "INVALID_FLAG_VALUE");
|
|
988
981
|
}
|
|
989
|
-
if (before.size > BigInt(TASK_V3_MAX_SOURCE_BYTES)) {
|
|
990
|
-
throw new UsageError(`${filePath} exceeds the 1 MiB (${TASK_V3_MAX_SOURCE_BYTES}-byte) task source limit.`, "INVALID_FLAG_VALUE");
|
|
991
|
-
}
|
|
992
982
|
const bytes = fs.readFileSync(descriptor);
|
|
993
983
|
const after = fs.fstatSync(descriptor, { bigint: true });
|
|
994
984
|
if (!sameTaskSourceStat(before, after) || BigInt(bytes.byteLength) !== before.size) {
|
|
@@ -1010,10 +1000,7 @@ function captureTaskSourceExpectation(filePathInput, rootInput) {
|
|
|
1010
1000
|
state: "present",
|
|
1011
1001
|
...common,
|
|
1012
1002
|
realPath,
|
|
1013
|
-
physicalIdentity: before.ino === 0n ? `path:${realPath}` : `inode:${before.dev}:${before.ino}`,
|
|
1014
1003
|
size: bytes.byteLength,
|
|
1015
|
-
mtimeNs: String(before.mtimeNs),
|
|
1016
|
-
ctimeNs: String(before.ctimeNs),
|
|
1017
1004
|
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
1018
1005
|
bytesBase64: bytes.toString("base64"),
|
|
1019
1006
|
content,
|
|
@@ -1035,6 +1022,11 @@ function captureTaskSourceExpectation(filePathInput, rootInput) {
|
|
|
1035
1022
|
fs.closeSync(descriptor);
|
|
1036
1023
|
}
|
|
1037
1024
|
}
|
|
1025
|
+
// TOCTOU note: this compares CONTENT (state + sha256), not filesystem
|
|
1026
|
+
// identity (inode/mtime/ctime/directory timestamps) — the same split already
|
|
1027
|
+
// applied to the task migrator in 0.9.5. An unrelated touch to the file or
|
|
1028
|
+
// its containing directory must not trip a "changed after planning" refusal;
|
|
1029
|
+
// only a real content change should.
|
|
1038
1030
|
function assertTaskSourceExpectation(expected) {
|
|
1039
1031
|
const actual = captureTaskSourceExpectation(expected.filePath, expected.rootRealPath);
|
|
1040
1032
|
if (!sameTaskSourceExpectation(actual, expected)) {
|
|
@@ -1042,12 +1034,16 @@ function assertTaskSourceExpectation(expected) {
|
|
|
1042
1034
|
}
|
|
1043
1035
|
}
|
|
1044
1036
|
function sameTaskSourceExpectation(left, right) {
|
|
1045
|
-
|
|
1037
|
+
if (left.state !== right.state || left.filePath !== right.filePath || left.rootRealPath !== right.rootRealPath) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
if (left.state === "absent")
|
|
1041
|
+
return true;
|
|
1042
|
+
return left.sha256 === right.sha256;
|
|
1046
1043
|
}
|
|
1047
1044
|
function assertTaskSourceRestored(expected) {
|
|
1048
1045
|
const actual = captureTaskSourceExpectation(expected.filePath, expected.rootRealPath);
|
|
1049
1046
|
const restored = actual.state === expected.state &&
|
|
1050
|
-
actual.rootPhysicalIdentity === expected.rootPhysicalIdentity &&
|
|
1051
1047
|
(actual.state === "absent" ||
|
|
1052
1048
|
(expected.state === "present" && actual.sha256 === expected.sha256 && actual.content === expected.content));
|
|
1053
1049
|
if (!restored) {
|
|
@@ -20,7 +20,7 @@ import { deliverRendered } from "../output/html-render.js";
|
|
|
20
20
|
import { shapeForCommand } from "../output/shapes.js";
|
|
21
21
|
import { formatPlain } from "../output/text.js";
|
|
22
22
|
import { assertWorkflowMarkdownName, createWorkflowAsset, getWorkflowTemplate } from "../workflows/authoring/authoring.js";
|
|
23
|
-
import {
|
|
23
|
+
import { WORKFLOW_MAX_TIMEOUT_MS } from "../workflows/ir/schema.js";
|
|
24
24
|
import { abandonWorkflowRun, getWorkflowStatus, hasWorkflowRun, listWorkflowRuns, resumeWorkflowRun, } from "../workflows/runtime/runs.js";
|
|
25
25
|
import { akmWorkflowPlan } from "./workflow/plan.js";
|
|
26
26
|
const workflowStatusCommand = defineJsonCommand({
|
|
@@ -168,7 +168,7 @@ const workflowRunCommand = defineJsonCommand({
|
|
|
168
168
|
const { runWorkflowSteps } = await import("../workflows/exec/run-workflow.js");
|
|
169
169
|
const parameterFlags = parseWorkflowParameterFlags(rawArgs, args.target);
|
|
170
170
|
const maxSteps = parseIntegerFlag(getStringArg(args, "max-steps"), "--max-steps", 1);
|
|
171
|
-
const maxRetries = parseIntegerFlag(getStringArg(args, "max-retries"), "--max-retries", 0
|
|
171
|
+
const maxRetries = parseIntegerFlag(getStringArg(args, "max-retries"), "--max-retries", 0);
|
|
172
172
|
const timeoutMs = parseWorkflowTimeout(getStringArg(args, "timeout"));
|
|
173
173
|
const controller = new AbortController();
|
|
174
174
|
let signalExitCode;
|
package/dist/core/common.js
CHANGED
|
@@ -10,8 +10,6 @@ import { getConfigPath, getDefaultStashDir, getRegistryCacheDir, getRegistryInde
|
|
|
10
10
|
// Moved to the platform leaf so paths.ts can use it without a common↔paths
|
|
11
11
|
// cycle (chunk-8 WI-8.6, DoD 11); re-exported here for the existing surface.
|
|
12
12
|
export { IS_WINDOWS } from "./platform.js";
|
|
13
|
-
export const MAX_CONFIG_FILE_BYTES = 1024 * 1024;
|
|
14
|
-
export const MAX_LOCAL_METADATA_BYTES = 1024 * 1024;
|
|
15
13
|
export const MAX_LOCK_METADATA_BYTES = 64 * 1024;
|
|
16
14
|
export function isHttpUrl(value) {
|
|
17
15
|
return !!value && /^https?:\/\//.test(value);
|
|
@@ -51,10 +49,13 @@ export function readTextFileDescriptorWithLimit(fd, maxBytes, label = "File", di
|
|
|
51
49
|
}
|
|
52
50
|
return buffer.subarray(0, total).toString("utf8");
|
|
53
51
|
}
|
|
54
|
-
export function
|
|
52
|
+
export function readTextFile(filePath, label = "File") {
|
|
55
53
|
const fd = fs.openSync(filePath, "r");
|
|
56
54
|
try {
|
|
57
|
-
|
|
55
|
+
const stat = fs.fstatSync(fd);
|
|
56
|
+
if (!stat.isFile())
|
|
57
|
+
throw new ConfigError(`${label} is not a regular file: ${filePath}.`, "INVALID_CONFIG_FILE");
|
|
58
|
+
return fs.readFileSync(fd, "utf8");
|
|
58
59
|
}
|
|
59
60
|
finally {
|
|
60
61
|
fs.closeSync(fd);
|
|
@@ -290,7 +291,7 @@ function isValidDirectory(dir) {
|
|
|
290
291
|
function readStashDirFromConfig() {
|
|
291
292
|
try {
|
|
292
293
|
const configPath = getConfigPath();
|
|
293
|
-
const text =
|
|
294
|
+
const text = readTextFile(configPath, "Config file");
|
|
294
295
|
// The config loader accepts JSONC, so a commented config.json is valid and
|
|
295
296
|
// in use. Parsing it raw here threw, the catch swallowed it, and every
|
|
296
297
|
// caller silently fell back — operating on the wrong bundle or failing with
|
|
@@ -544,21 +545,59 @@ export async function fetchWithTimeout(url, opts, timeoutMs = 30_000, signal) {
|
|
|
544
545
|
clearTimeout(timer);
|
|
545
546
|
}
|
|
546
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* Cap on how long a retry loop will wait between attempts, even when a
|
|
550
|
+
* server-supplied `Retry-After` claims a longer delay. Prevents an
|
|
551
|
+
* attacker-controlled or misconfigured server from parking a caller
|
|
552
|
+
* indefinitely.
|
|
553
|
+
*/
|
|
554
|
+
export const DEFAULT_RETRY_MAX_DELAY_MS = 30_000;
|
|
555
|
+
export function shouldRetry(status) {
|
|
556
|
+
return status === 429 || status >= 500;
|
|
557
|
+
}
|
|
558
|
+
/** Jittered exponential backoff, capped at `maxDelayMs`. */
|
|
559
|
+
export function backoffDelay(attempt, baseDelay = 500, maxDelayMs = DEFAULT_RETRY_MAX_DELAY_MS) {
|
|
560
|
+
return Math.min(maxDelayMs, baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5));
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Determine the delay before the next retry attempt.
|
|
564
|
+
*
|
|
565
|
+
* Honors a server-supplied `Retry-After` header in both its numeric-seconds
|
|
566
|
+
* and HTTP-date forms, but always clamps the result to `maxDelayMs` — an
|
|
567
|
+
* unclamped `Retry-After` lets an attacker/misconfigured server park a
|
|
568
|
+
* caller for an arbitrarily long time. Falls back to jittered exponential
|
|
569
|
+
* backoff when the header is absent or unparseable.
|
|
570
|
+
*/
|
|
571
|
+
export function computeRetryDelay(response, attempt, options) {
|
|
572
|
+
const maxDelayMs = options?.maxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;
|
|
573
|
+
const baseDelay = options?.baseDelay ?? 500;
|
|
574
|
+
const retryAfter = response.headers.get("retry-after");
|
|
575
|
+
if (retryAfter) {
|
|
576
|
+
const seconds = Number(retryAfter);
|
|
577
|
+
if (Number.isFinite(seconds)) {
|
|
578
|
+
return seconds >= 0 ? Math.min(maxDelayMs, seconds * 1_000) : backoffDelay(attempt, baseDelay, maxDelayMs);
|
|
579
|
+
}
|
|
580
|
+
const date = Date.parse(retryAfter);
|
|
581
|
+
if (Number.isFinite(date))
|
|
582
|
+
return Math.min(maxDelayMs, Math.max(0, date - Date.now()));
|
|
583
|
+
}
|
|
584
|
+
return backoffDelay(attempt, baseDelay, maxDelayMs);
|
|
585
|
+
}
|
|
547
586
|
/**
|
|
548
587
|
* Fetch with retry and exponential backoff.
|
|
549
588
|
* Retries on network errors, 429, and 5xx responses.
|
|
550
|
-
* Honors Retry-After header
|
|
589
|
+
* Honors Retry-After header, capped at `maxDelayMs`.
|
|
551
590
|
*/
|
|
552
591
|
export async function fetchWithRetry(url, init, options) {
|
|
553
592
|
const maxRetries = options?.retries ?? 3;
|
|
554
593
|
const baseDelay = options?.baseDelay ?? 500;
|
|
594
|
+
const maxDelayMs = options?.maxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;
|
|
555
595
|
const timeout = options?.timeout ?? 30_000;
|
|
556
596
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
557
597
|
try {
|
|
558
598
|
const response = await fetchWithTimeout(url, init, timeout, init?.signal ?? undefined);
|
|
559
599
|
if (attempt < maxRetries && shouldRetry(response.status)) {
|
|
560
|
-
const
|
|
561
|
-
const delay = retryAfter ?? baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
|
|
600
|
+
const delay = computeRetryDelay(response, attempt, { baseDelay, maxDelayMs });
|
|
562
601
|
await response.body?.cancel().catch(() => undefined);
|
|
563
602
|
await abortableDelay(delay, init?.signal);
|
|
564
603
|
continue;
|
|
@@ -571,8 +610,7 @@ export async function fetchWithRetry(url, init, options) {
|
|
|
571
610
|
// A caller-supplied abort is terminal: never keep retrying past it.
|
|
572
611
|
if (init?.signal?.aborted)
|
|
573
612
|
throw err;
|
|
574
|
-
|
|
575
|
-
await abortableDelay(delay, init?.signal);
|
|
613
|
+
await abortableDelay(backoffDelay(attempt, baseDelay, maxDelayMs), init?.signal);
|
|
576
614
|
}
|
|
577
615
|
}
|
|
578
616
|
throw new Error("fetchWithRetry: unreachable");
|
|
@@ -584,17 +622,19 @@ export async function fetchWithRetry(url, init, options) {
|
|
|
584
622
|
* large. Sleeping it out with a bare `setTimeout` ignored the caller's abort
|
|
585
623
|
* signal entirely, so a single `429` could park an operation far past any
|
|
586
624
|
* deadline its caller believed it had imposed — the request timeout bounds
|
|
587
|
-
* only the request, never the wait between attempts.
|
|
625
|
+
* only the request, never the wait between attempts. Callers using this for
|
|
626
|
+
* a retry delay should pass a `maxDelayMs`-capped `ms` (see
|
|
627
|
+
* {@link computeRetryDelay}) to bound the wait itself.
|
|
588
628
|
*/
|
|
589
|
-
function abortableDelay(ms, signal) {
|
|
629
|
+
export function abortableDelay(ms, signal, abortMessage = "Aborted") {
|
|
590
630
|
if (!signal)
|
|
591
631
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
592
632
|
if (signal.aborted)
|
|
593
|
-
return Promise.reject(signal.reason ?? new Error(
|
|
633
|
+
return Promise.reject(signal.reason ?? new Error(abortMessage));
|
|
594
634
|
return new Promise((resolve, reject) => {
|
|
595
635
|
const onAbort = () => {
|
|
596
636
|
clearTimeout(timer);
|
|
597
|
-
reject(signal.reason ?? new Error(
|
|
637
|
+
reject(signal.reason ?? new Error(abortMessage));
|
|
598
638
|
};
|
|
599
639
|
const timer = setTimeout(() => {
|
|
600
640
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -603,9 +643,6 @@ function abortableDelay(ms, signal) {
|
|
|
603
643
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
604
644
|
});
|
|
605
645
|
}
|
|
606
|
-
function shouldRetry(status) {
|
|
607
|
-
return status === 429 || status >= 500;
|
|
608
|
-
}
|
|
609
646
|
/**
|
|
610
647
|
* Read stdin as UTF-8 text if something is piped in. Returns `undefined`
|
|
611
648
|
* when stdin is a TTY (no pipe) or when the piped content is empty.
|
|
@@ -789,13 +826,6 @@ export async function jsonWithByteCap(response, maxBytes = DEFAULT_RESPONSE_BYTE
|
|
|
789
826
|
const text = await readBodyWithByteCap(response, maxBytes, limits);
|
|
790
827
|
return JSON.parse(text);
|
|
791
828
|
}
|
|
792
|
-
function parseRetryAfter(response) {
|
|
793
|
-
const header = response.headers.get("retry-after");
|
|
794
|
-
if (!header)
|
|
795
|
-
return undefined;
|
|
796
|
-
const seconds = parseInt(header, 10);
|
|
797
|
-
return Number.isNaN(seconds) ? undefined : seconds * 1000;
|
|
798
|
-
}
|
|
799
829
|
export function toErrorMessage(error) {
|
|
800
830
|
return error instanceof Error ? error.message : String(error);
|
|
801
831
|
}
|
|
@@ -871,8 +901,15 @@ export function stringArray(value) {
|
|
|
871
901
|
* reported alive. Treating it as dead let a lock held by a live process in a
|
|
872
902
|
* shared data dir (agent sandboxes, containers, service accounts — a
|
|
873
903
|
* configuration managed-db.ts explicitly supports) be reclaimed as stale.
|
|
904
|
+
*
|
|
905
|
+
* `pid` is `unknown` because callers reading it out of untrusted on-disk
|
|
906
|
+
* JSON (e.g. a lease file) cannot guarantee it parsed as a valid PID; a
|
|
907
|
+
* non-positive-integer value is reported dead without ever reaching
|
|
908
|
+
* `process.kill`.
|
|
874
909
|
*/
|
|
875
910
|
export function isProcessAlive(pid) {
|
|
911
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
|
|
912
|
+
return false;
|
|
876
913
|
try {
|
|
877
914
|
process.kill(pid, 0);
|
|
878
915
|
return true;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import { sleepSync } from "../../runtime.js";
|
|
18
|
-
import {
|
|
18
|
+
import { readTextFile, stripJsonComments, writeFileAtomic } from "../common.js";
|
|
19
19
|
import { ConfigError } from "../errors.js";
|
|
20
20
|
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync } from "../file-lock.js";
|
|
21
21
|
import { getCacheDir, getConfigDir } from "../paths.js";
|
|
@@ -25,7 +25,7 @@ import { getCacheDir, getConfigDir } from "../paths.js";
|
|
|
25
25
|
*/
|
|
26
26
|
export function readConfigText(configPath) {
|
|
27
27
|
try {
|
|
28
|
-
return
|
|
28
|
+
return readTextFile(configPath, "Config file");
|
|
29
29
|
}
|
|
30
30
|
catch (err) {
|
|
31
31
|
if (err.code === "ENOENT")
|
|
@@ -23,6 +23,16 @@ export function bundleComponentConfig(bundle) {
|
|
|
23
23
|
* `defaultBundle` first, then map insertion order. Each entry's `name` is its
|
|
24
24
|
* bundle key. Returns `undefined` when no bundles map is configured.
|
|
25
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* A bundle's true identity: its configured `path` plus its component's
|
|
28
|
+
* `root` (default `"."`), fully resolved. Two bundle entries whose bare
|
|
29
|
+
* `path` differs (relative vs. absolute, trailing slash, `~` vs. expanded)
|
|
30
|
+
* can still resolve to this same directory — this is the identity akm
|
|
31
|
+
* compares before registering or reconciling a bundle (issue #870).
|
|
32
|
+
*/
|
|
33
|
+
export function bundleContentRoot(entryPath, componentRoot) {
|
|
34
|
+
return path.resolve(entryPath, componentRoot ?? ".");
|
|
35
|
+
}
|
|
26
36
|
/**
|
|
27
37
|
* The resolved primary stash path — the `defaultBundle`'s filesystem `path`
|
|
28
38
|
* (spec §10.1) — or `undefined` when no filesystem primary is configured.
|
|
@@ -36,9 +46,9 @@ export function primaryBundlePath(config) {
|
|
|
36
46
|
if (!entry || typeof entry.path !== "string" || entry.path.length === 0)
|
|
37
47
|
return undefined;
|
|
38
48
|
const componentRoot = bundleComponentConfig(entry)?.root;
|
|
39
|
-
if (!componentRoot || componentRoot === ".")
|
|
40
|
-
return entry.path;
|
|
41
49
|
const bundleRoot = path.resolve(entry.path);
|
|
50
|
+
if (!componentRoot || componentRoot === ".")
|
|
51
|
+
return bundleRoot;
|
|
42
52
|
const resolved = path.resolve(bundleRoot, componentRoot);
|
|
43
53
|
const relative = path.relative(bundleRoot, resolved);
|
|
44
54
|
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
@@ -46,6 +56,26 @@ export function primaryBundlePath(config) {
|
|
|
46
56
|
}
|
|
47
57
|
return resolved;
|
|
48
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Every configured filesystem bundle's id paired with its resolved content
|
|
61
|
+
* root. Used to detect two bundle ids that resolve to the same directory
|
|
62
|
+
* (issue #870) and to find the id that already owns a given root before a
|
|
63
|
+
* new one is registered.
|
|
64
|
+
*/
|
|
65
|
+
export function bundleContentRoots(config) {
|
|
66
|
+
const bundles = config.bundles ?? {};
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const [id, entry] of Object.entries(bundles)) {
|
|
69
|
+
if (typeof entry.path !== "string" || entry.path.length === 0)
|
|
70
|
+
continue;
|
|
71
|
+
out.push({ id, contentRoot: bundleContentRoot(entry.path, bundleComponentConfig(entry)?.root) });
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/** The bundle id whose resolved content root already matches `resolvedContentRoot`, if any. */
|
|
76
|
+
export function bundleKeyForContentRoot(config, resolvedContentRoot) {
|
|
77
|
+
return bundleContentRoots(config).find((entry) => entry.contentRoot === resolvedContentRoot)?.id;
|
|
78
|
+
}
|
|
49
79
|
export function bundlesToSourceEntries(config) {
|
|
50
80
|
const bundles = config.bundles;
|
|
51
81
|
if (!bundles)
|
|
@@ -376,7 +376,7 @@ export function getIndexPassConfig(config, passName) {
|
|
|
376
376
|
return entry;
|
|
377
377
|
}
|
|
378
378
|
// Re-export source runtime helpers — implementation lives in config-sources.ts.
|
|
379
|
-
export { bundleComponentConfig, bundleEntryToSourceEntry, bundlesToSourceEntries, installedSourceDescriptor, parseSourceSpec, primaryBundlePath, resolveConfiguredSources, } from "./config-sources.js";
|
|
379
|
+
export { bundleComponentConfig, bundleContentRoot, bundleContentRoots, bundleEntryToSourceEntry, bundleKeyForContentRoot, bundlesToSourceEntries, installedSourceDescriptor, parseSourceSpec, primaryBundlePath, resolveConfiguredSources, } from "./config-sources.js";
|
|
380
380
|
/**
|
|
381
381
|
* Merge a partial user-config override onto a base config. Used by
|
|
382
382
|
* {@link loadUserConfig} (DEFAULT_CONFIG + on-disk) and {@link updateConfig}
|
|
@@ -15,7 +15,7 @@ import { z } from "zod";
|
|
|
15
15
|
// a config-schema ↔ config-types type cycle that collapses inference.
|
|
16
16
|
import { HARNESS_AGENT_DISPATCH_IDS, VALID_HARNESS_IDS } from "../../../integrations/harnesses/ids.js";
|
|
17
17
|
import { WORKFLOW_MAX_TIMEOUT_MS } from "../../../workflows/resource-limits.js";
|
|
18
|
-
import { chatCompletionsEndpoint, ENV_REFERENCE_PATTERN, ExtraParamsSchema, engineName,
|
|
18
|
+
import { chatCompletionsEndpoint, ENV_REFERENCE_PATTERN, ExtraParamsSchema, engineName, nonEmptyString, positiveInt, } from "./primitives.js";
|
|
19
19
|
/**
|
|
20
20
|
* Engine-config timeouts share the workflow ceiling.
|
|
21
21
|
*
|
|
@@ -43,16 +43,20 @@ export const LlmConnectionConfigSchema = z
|
|
|
43
43
|
maxTokens: positiveInt.optional(),
|
|
44
44
|
timeoutMs: timeoutMsField,
|
|
45
45
|
concurrency: positiveInt.optional(),
|
|
46
|
-
|
|
46
|
+
// User-settable override, not a cached probe verdict: attempt-then-
|
|
47
|
+
// fallback in llm/client.ts tries `response_format: json_schema` whenever
|
|
48
|
+
// a schema is supplied, degrading to plain text on an unsupported-4xx and
|
|
49
|
+
// remembering that in-memory for the rest of the process. `false` here
|
|
50
|
+
// opts a known-incompatible endpoint out of even the first attempt;
|
|
51
|
+
// `true` is advisory only.
|
|
52
|
+
supportsJsonSchema: z.boolean().optional(),
|
|
47
53
|
extraParams: ExtraParamsSchema.optional(),
|
|
48
54
|
contextLength: positiveInt.optional(),
|
|
49
55
|
enableThinking: z.boolean().optional(),
|
|
50
56
|
reasoningEffort: nonEmptyString.optional(),
|
|
51
57
|
})
|
|
52
58
|
.passthrough();
|
|
53
|
-
export const LlmProfileConfigSchema = LlmConnectionConfigSchema.
|
|
54
|
-
supportsJsonSchema: z.boolean().optional(),
|
|
55
|
-
}).passthrough();
|
|
59
|
+
export const LlmProfileConfigSchema = LlmConnectionConfigSchema.passthrough();
|
|
56
60
|
// ── Agent engines ───────────────────────────────────────────────────────────
|
|
57
61
|
// Derives from the canonical VALID_HARNESS_IDS (#565) so the Zod gate cannot
|
|
58
62
|
// drift from the TS union / parse check / setup detection.
|
|
@@ -68,7 +72,6 @@ const LlmEngineSchema = z
|
|
|
68
72
|
maxTokens: positiveInt.optional(),
|
|
69
73
|
timeoutMs: timeoutMsField,
|
|
70
74
|
concurrency: positiveInt.optional(),
|
|
71
|
-
supportsJsonSchema: z.boolean().optional(),
|
|
72
75
|
extraParams: ExtraParamsSchema.optional(),
|
|
73
76
|
contextLength: positiveInt.optional(),
|
|
74
77
|
enableThinking: z.boolean().optional(),
|
|
@@ -103,7 +106,6 @@ const AgentEngineSchema = z
|
|
|
103
106
|
"temperature",
|
|
104
107
|
"maxTokens",
|
|
105
108
|
"concurrency",
|
|
106
|
-
"supportsJsonSchema",
|
|
107
109
|
"extraParams",
|
|
108
110
|
"contextLength",
|
|
109
111
|
"enableThinking",
|
|
@@ -54,11 +54,6 @@ export const ExtraParamsSchema = z.record(z.unknown()).superRefine((value, ctx)
|
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
// ── Shared connection/invocation building blocks ────────────────────────────
|
|
57
|
-
export const LlmCapabilitiesSchema = z
|
|
58
|
-
.object({
|
|
59
|
-
structuredOutput: z.boolean().optional(),
|
|
60
|
-
})
|
|
61
|
-
.passthrough();
|
|
62
57
|
export const LlmInvocationOverridesSchema = z
|
|
63
58
|
.object({
|
|
64
59
|
temperature: z.number().finite().optional(),
|
|
@@ -16,7 +16,8 @@ const SearchGraphBoostSchema = z
|
|
|
16
16
|
hopBoostCap: nonNegativeNumber.optional(),
|
|
17
17
|
/** Hard-capped at 3; values > 3 hard-error so users see the typo. */
|
|
18
18
|
maxHops: positiveInt.max(3).optional(),
|
|
19
|
-
|
|
19
|
+
/** Only "blend" is exercised; "off"/"multiply" were never set in practice and were removed. */
|
|
20
|
+
confidenceMode: z.enum(["blend"]).default("blend").optional(),
|
|
20
21
|
/** Range [0, 1]; values > 1 hard-error (no silent clamp). */
|
|
21
22
|
confidenceWeight: z.number().finite().min(0).max(1).default(0.2).optional(),
|
|
22
23
|
})
|
package/dist/core/file-lock.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { sleepSync } from "../runtime.js";
|
|
7
8
|
import { openDatabase } from "../storage/database.js";
|
|
8
9
|
import { isProcessAlive, MAX_LOCK_METADATA_BYTES, readTextFileDescriptorWithLimit } from "./common.js";
|
|
9
10
|
function readLockSnapshot(lockPath) {
|
|
@@ -51,7 +52,7 @@ function withLockOperationMutex(lockPath, run) {
|
|
|
51
52
|
db.exec("BEGIN IMMEDIATE");
|
|
52
53
|
began = db.inTransaction;
|
|
53
54
|
if (!began)
|
|
54
|
-
|
|
55
|
+
sleepSync(2 ** attempt);
|
|
55
56
|
}
|
|
56
57
|
if (!began)
|
|
57
58
|
throw new Error(`Could not acquire lock operation mutex for ${lockPath}.`);
|