akm-cli 0.9.15 → 0.9.16-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/dist/assets/hints/cli-hints-full.md +13 -6
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
- package/dist/cli/retired-commands.js +0 -2
- package/dist/commands/env/env-binding.js +4 -4
- package/dist/commands/env/env-cli.js +3 -3
- package/dist/commands/improve/improve-cli.js +19 -14
- package/dist/commands/improve/reflect.js +23 -2
- package/dist/commands/lint/base-linter.js +9 -0
- package/dist/commands/lint/env-key-rules.js +2 -2
- package/dist/commands/proposal/propose.js +15 -1
- package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
- package/dist/commands/proposal/validators/proposal-validators.js +5 -4
- package/dist/commands/read/search.js +33 -4
- package/dist/commands/read/show.js +21 -2
- package/dist/commands/registry-cli.js +5 -5
- package/dist/commands/sources/add-cli.js +59 -16
- package/dist/commands/sources/bundle-cli.js +35 -11
- package/dist/commands/sources/bundle-config-ops.js +30 -0
- package/dist/commands/sources/dangerous-env-audit.js +4 -4
- package/dist/commands/sources/installed-stashes.js +43 -28
- package/dist/commands/sources/source-add.js +33 -17
- package/dist/commands/sources/source-manage.js +34 -12
- package/dist/commands/sources/stash-skeleton.js +6 -3
- package/dist/commands/tasks/explain.js +4 -1
- package/dist/commands/tasks/tasks-cli.js +31 -9
- package/dist/commands/tasks/tasks.js +239 -194
- package/dist/commands/tasks/validate.js +20 -32
- package/dist/core/activation-policy.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +5 -0
- package/dist/core/adapter/execution-source.js +10 -29
- package/dist/core/config/config-schema.js +64 -8
- package/dist/core/config/config-sources.js +96 -2
- package/dist/core/config/config.js +190 -24
- package/dist/core/config/legacy-source-shape-shim.js +9 -0
- package/dist/core/config/schema/execution.js +23 -0
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/scheduler.js +20 -0
- package/dist/core/config/schema/search.js +1 -1
- package/dist/core/config/schema/sources-bundles.js +32 -1
- package/dist/core/content-safety.js +52 -0
- package/dist/core/maintenance-barrier.js +6 -6
- package/dist/core/type-presentation.js +1 -1
- package/dist/core/write-source.js +13 -8
- package/dist/indexer/bundle-identity-guard.js +45 -8
- package/dist/indexer/indexer.js +1 -1
- package/dist/indexer/materialize-embeddings.js +15 -1
- package/dist/indexer/search/search-source.js +29 -11
- package/dist/integrations/agent/execution-lowering.js +3 -2
- package/dist/integrations/agent/execution-preparation.js +32 -1
- package/dist/integrations/agent/prompts.js +1 -1
- package/dist/integrations/agent/request-lowering.js +3 -2
- package/dist/llm/client.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/registry/resolve.js +37 -10
- package/dist/scripts/akm-migrate-node.js +13644 -9894
- package/dist/scripts/akm-migrate.js +12626 -8876
- package/dist/setup/setup.js +3 -3
- package/dist/setup/steps/tasks.js +29 -36
- package/dist/sources/providers/git-install.js +17 -11
- package/dist/sources/providers/git-provider.js +12 -5
- package/dist/sources/providers/git-stash.js +38 -16
- package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
- package/dist/storage/repositories/index-vec-repository.js +100 -0
- package/dist/tasks/activation-config.js +90 -0
- package/dist/tasks/backends/cron.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -0
- package/dist/tasks/backends/schtasks.js +2 -0
- package/dist/tasks/embedded.js +4 -5
- package/dist/tasks/scheduler-binding.js +2 -2
- package/dist/tasks/scheduler-sync-preview.js +8 -1
- package/dist/tasks/scheduler-sync.js +19 -10
- package/dist/tasks/source/parse-task-source.js +10 -113
- package/dist/tasks/source/project-v4.js +2 -2
- package/dist/tasks/source/task-source-v4.js +4 -12
- package/dist/tasks/source/task-to-v3.js +4 -12
- package/dist/tasks/source/task-to-v4.js +40 -7
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.16.md +72 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
- package/docs/reference/cli.md +37 -29
- package/docs/reference/configuration.md +48 -5
- package/docs/reference/tasks.md +34 -29
- package/package.json +1 -1
- package/schemas/akm-config.json +112 -4
- package/schemas/akm-task.json +1 -2
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* comparison over a non-empty index so the steady-state cost is a boolean check.
|
|
29
29
|
*/
|
|
30
30
|
import fs from "node:fs";
|
|
31
|
+
import path from "node:path";
|
|
31
32
|
import { loadConfig } from "../core/config/config.js";
|
|
32
33
|
import { rethrowIfTestIsolationError } from "../core/errors.js";
|
|
33
34
|
import { getDbPath } from "../core/paths.js";
|
|
@@ -39,8 +40,8 @@ let guardSettled = false;
|
|
|
39
40
|
export function resetBundleIdentityGuardForTests() {
|
|
40
41
|
guardSettled = false;
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
-
function
|
|
43
|
+
/** Bundle/path ownership persisted in the index, or `undefined` when unreadable. */
|
|
44
|
+
function indexedBundlePaths(dbPath) {
|
|
44
45
|
if (!fs.existsSync(dbPath))
|
|
45
46
|
return undefined;
|
|
46
47
|
let db;
|
|
@@ -51,8 +52,9 @@ function indexBundlePrefixes(dbPath) {
|
|
|
51
52
|
if (!isCanonicalIndexGeneration(db))
|
|
52
53
|
return undefined;
|
|
53
54
|
return db
|
|
54
|
-
.prepare("SELECT DISTINCT bundle_id AS
|
|
55
|
-
|
|
55
|
+
.prepare("SELECT DISTINCT bundle_id AS bundleId, file_path AS filePath FROM entries " +
|
|
56
|
+
"WHERE bundle_id IS NOT NULL AND bundle_id != '' AND file_path IS NOT NULL AND file_path != ''")
|
|
57
|
+
.all();
|
|
56
58
|
}
|
|
57
59
|
catch (error) {
|
|
58
60
|
rethrowIfTestIsolationError(error);
|
|
@@ -80,20 +82,55 @@ export function warnOnBundleRenameDrift(config = loadConfig()) {
|
|
|
80
82
|
const configIds = new Set(Object.keys(bundles));
|
|
81
83
|
if (configIds.size === 0)
|
|
82
84
|
return;
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
+
const indexedPaths = indexedBundlePaths(getDbPath());
|
|
86
|
+
if (indexedPaths === undefined || indexedPaths.length === 0)
|
|
85
87
|
return; // nothing indexed yet — re-check later
|
|
86
88
|
// A real comparison happened over a populated index: settle so the steady
|
|
87
89
|
// state is one boolean check.
|
|
88
90
|
guardSettled = true;
|
|
91
|
+
const indexIds = [...new Set(indexedPaths.map((row) => row.bundleId))];
|
|
89
92
|
const indexIdSet = new Set(indexIds);
|
|
90
93
|
const configuredMissingFromIndex = [...configIds].filter((id) => !indexIdSet.has(id));
|
|
91
94
|
const indexedNotConfigured = indexIds.filter((id) => !configIds.has(id));
|
|
92
95
|
if (configuredMissingFromIndex.length === 0 || indexedNotConfigured.length === 0)
|
|
93
96
|
return;
|
|
97
|
+
// A mismatched id set alone is also the ordinary signature of adding a new
|
|
98
|
+
// bundle while stale rows from a removed bundle still exist. Treat it as a
|
|
99
|
+
// hand rename only when the old rows physically live beneath the newly named
|
|
100
|
+
// filesystem bundle's root. This keeps the guard useful without warning on
|
|
101
|
+
// unrelated new bundles (#971).
|
|
102
|
+
const renamedConfiguredIds = configuredMissingFromIndex.filter((configuredId) => {
|
|
103
|
+
const configuredPath = config.bundles?.[configuredId]?.path;
|
|
104
|
+
if (typeof configuredPath !== "string")
|
|
105
|
+
return false;
|
|
106
|
+
const root = path.resolve(configuredPath);
|
|
107
|
+
return indexedPaths.some((row) => {
|
|
108
|
+
if (!indexedNotConfigured.includes(row.bundleId))
|
|
109
|
+
return false;
|
|
110
|
+
const relative = path.relative(root, path.resolve(row.filePath));
|
|
111
|
+
return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
if (renamedConfiguredIds.length === 0)
|
|
115
|
+
return;
|
|
116
|
+
const renamedOldIds = [
|
|
117
|
+
...new Set(indexedPaths
|
|
118
|
+
.filter((row) => {
|
|
119
|
+
if (!indexedNotConfigured.includes(row.bundleId))
|
|
120
|
+
return false;
|
|
121
|
+
return renamedConfiguredIds.some((configuredId) => {
|
|
122
|
+
const configuredPath = config.bundles?.[configuredId]?.path;
|
|
123
|
+
if (typeof configuredPath !== "string")
|
|
124
|
+
return false;
|
|
125
|
+
const relative = path.relative(path.resolve(configuredPath), path.resolve(row.filePath));
|
|
126
|
+
return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
127
|
+
});
|
|
128
|
+
})
|
|
129
|
+
.map((row) => row.bundleId)),
|
|
130
|
+
];
|
|
94
131
|
warn("WARNING: bundle identity drift detected. " +
|
|
95
|
-
`Configured bundle(s) with no indexed content: ${
|
|
96
|
-
`indexed content under unconfigured bundle id(s): ${
|
|
132
|
+
`Configured bundle(s) with no indexed content: ${renamedConfiguredIds.map((id) => `"${id}"`).join(", ")}; ` +
|
|
133
|
+
`indexed content under unconfigured bundle id(s): ${renamedOldIds.map((id) => `"${id}"`).join(", ")}. ` +
|
|
97
134
|
"This is the signature of a hand-renamed bundle id (spec §11.5). AKM will NOT silently re-mint fresh state " +
|
|
98
135
|
"under the new id. There is no rekey command in 0.9.0: either restore the previous bundle id in config.json " +
|
|
99
136
|
"to reattach the existing rows, or keep the new id and run `akm index --full` to re-mint under it, accepting " +
|
package/dist/indexer/indexer.js
CHANGED
|
@@ -1641,7 +1641,7 @@ function verifyIndexState(db, config, embeddableEntries, embeddingResult) {
|
|
|
1641
1641
|
message: `Semantic search ready (${embeddingCount}/${embeddableEntries} embeddings, ${vecActive
|
|
1642
1642
|
? "sqlite-vec active"
|
|
1643
1643
|
: vecAvailable
|
|
1644
|
-
? "JS fallback active — vec fast path degraded
|
|
1644
|
+
? "JS fallback active — vec fast path degraded; run 'akm index' to repair valid stored vectors or 'akm index --reembed' to regenerate incompatible ones"
|
|
1645
1645
|
: "JS fallback active"}).`,
|
|
1646
1646
|
semanticSearchEnabled: true,
|
|
1647
1647
|
semanticSearchMode: config.semanticSearchMode,
|
|
@@ -11,7 +11,8 @@ import { cosineSimilarity } from "../llm/embedders/types.js";
|
|
|
11
11
|
import { purgeEmbeddingSalvage, relabelEmbeddingSalvageFingerprint, reuseSalvagedEmbeddings, } from "../storage/repositories/embedding-salvage-repository.js";
|
|
12
12
|
import { getEmbeddableEntryCount } from "../storage/repositories/index-entries-repository.js";
|
|
13
13
|
import { deleteMeta, getMeta, setMeta } from "../storage/repositories/index-meta-repository.js";
|
|
14
|
-
import {
|
|
14
|
+
import { EMBEDDING_DIM } from "../storage/repositories/index-schema.js";
|
|
15
|
+
import { getAllEntriesForEmbedding, getEmbeddingCount, isVecFastPathComplete, isVecFastPathReady, purgeEmbeddings, repairVecFastPath, sampleEmbeddedEntriesForCanary, setVecFastPathReady, upsertEmbedding, } from "../storage/repositories/index-vec-repository.js";
|
|
15
16
|
import { reclassifyIndexDbContention } from "./index-db-contention.js";
|
|
16
17
|
/** Identifies the embedding provider+model+dimension a stored vector was generated with. */
|
|
17
18
|
export function deriveSemanticProviderFingerprint(embedding) {
|
|
@@ -360,6 +361,19 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
360
361
|
}
|
|
361
362
|
try {
|
|
362
363
|
throwIfAborted(signal);
|
|
364
|
+
if (entryIds === undefined && (!vecFastPathWasReady || !isVecFastPathComplete(db))) {
|
|
365
|
+
const storedDim = Number(getMeta(db, "embeddingDim"));
|
|
366
|
+
const expectedDim = Number.isInteger(storedDim) && storedDim > 0 ? storedDim : (config.embedding?.dimension ?? EMBEDDING_DIM);
|
|
367
|
+
const repair = repairVecFastPath(db, expectedDim);
|
|
368
|
+
if (repair.available &&
|
|
369
|
+
(repair.repaired > 0 || repair.removedOrphans > 0 || repair.rejected > 0 || repair.error !== undefined)) {
|
|
370
|
+
const detail = repair.error ? `; repair stopped: ${repair.error}` : "";
|
|
371
|
+
onProgress({
|
|
372
|
+
phase: "embeddings",
|
|
373
|
+
message: `[embed] Repaired ${repair.repaired} missing sqlite-vec row${repair.repaired === 1 ? "" : "s"}; removed ${repair.removedOrphans} orphan${repair.removedOrphans === 1 ? "" : "s"}; ${repair.rejected} rejected${detail}.`,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
363
377
|
const allEntries = getAllEntriesForEmbedding(db, targetEntryIds);
|
|
364
378
|
let vecFailedCount = 0;
|
|
365
379
|
let vecUnavailableCount = 0;
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { isWithin, resolveStashDir } from "../../core/common.js";
|
|
7
|
-
import { bundleComponentConfig, bundlesToSourceEntries, getSources, loadConfig } from "../../core/config/config.js";
|
|
7
|
+
import { bundleComponentConfig, bundleKeyForContentRoot, bundlesToSourceEntries, getSources, isBundleEnabled, loadConfig, } from "../../core/config/config.js";
|
|
8
|
+
import { ConfigError } from "../../core/errors.js";
|
|
8
9
|
import { getUnresolvedSourcesDir } from "../../core/paths.js";
|
|
9
10
|
import { resolveGitContentRoot, resolveWritable } from "../../core/write-source.js";
|
|
10
11
|
import { lockContentRootFor } from "../../integrations/lockfile.js";
|
|
@@ -23,7 +24,7 @@ import { warn } from "../../core/warn.js";
|
|
|
23
24
|
* 2. The configured `defaultBundle`, after component-root validation.
|
|
24
25
|
* 3. Remaining configured bundles in installation-priority order.
|
|
25
26
|
*
|
|
26
|
-
* Disabled entries (`enabled: false`) are
|
|
27
|
+
* Disabled entries (`enabled: false`) are excluded before materialization.
|
|
27
28
|
* Missing configured roots remain in the result so the
|
|
28
29
|
* indexer can classify their scan as incomplete instead of mistaking them for
|
|
29
30
|
* removed sources.
|
|
@@ -39,23 +40,39 @@ export function resolveSourceEntries(overrideStashDir, existingConfig) {
|
|
|
39
40
|
: config.defaultBundle
|
|
40
41
|
? undefined
|
|
41
42
|
: resolveStashDir();
|
|
43
|
+
const implicitConfiguredBundle = implicitStashDir ? bundleKeyForContentRoot(config, implicitStashDir) : undefined;
|
|
44
|
+
if (implicitConfiguredBundle && !isBundleEnabled(config, implicitConfiguredBundle)) {
|
|
45
|
+
throw new ConfigError(`The requested working source is disabled as bundle ${JSON.stringify(implicitConfiguredBundle)}.`, "INVALID_CONFIG_FILE");
|
|
46
|
+
}
|
|
42
47
|
// Explicit and environment overrides stay first. Without either override,
|
|
43
48
|
// the configured default enters through the validated loop below.
|
|
44
|
-
const sources = implicitStashDir ? [{ path: implicitStashDir, writable: true }] : [];
|
|
45
|
-
const
|
|
46
|
-
|
|
49
|
+
const sources = implicitStashDir ? [{ path: implicitStashDir, writable: true, isDefault: true }] : [];
|
|
50
|
+
const sourceIdentity = (dir) => {
|
|
51
|
+
const resolved = path.resolve(dir);
|
|
52
|
+
try {
|
|
53
|
+
return fs.realpathSync.native(resolved);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const seen = new Set(implicitStashDir ? [sourceIdentity(implicitStashDir)] : []);
|
|
60
|
+
const addSource = (dir, registryId, writable, type, adapterId, unresolved = false, isDefault = false) => {
|
|
47
61
|
const resolved = path.resolve(dir);
|
|
48
|
-
|
|
62
|
+
const identity = sourceIdentity(resolved);
|
|
63
|
+
if (seen.has(identity)) {
|
|
49
64
|
// Already in the source list — typically the primary stash injected at
|
|
50
65
|
// sources[0] before this loop. Enrich that entry with whatever metadata
|
|
51
66
|
// the matching config source carries so `--from <config-name>` can
|
|
52
67
|
// find it via registryId. Without this, the primary stash entry stays
|
|
53
68
|
// identity-less and a user-named primary source ("name": "my-stash")
|
|
54
69
|
// would validate but match zero entries when filtering.
|
|
55
|
-
const existing = sources.find((
|
|
70
|
+
const existing = sources.find((source) => sourceIdentity(source.path) === identity);
|
|
56
71
|
if (existing && existing.type === undefined) {
|
|
57
72
|
if (registryId)
|
|
58
73
|
existing.registryId = registryId;
|
|
74
|
+
if (isDefault)
|
|
75
|
+
existing.isDefault = true;
|
|
59
76
|
existing.type = type;
|
|
60
77
|
existing.writable = writable;
|
|
61
78
|
existing.adapterId = adapterId;
|
|
@@ -64,13 +81,14 @@ export function resolveSourceEntries(overrideStashDir, existingConfig) {
|
|
|
64
81
|
existing.unresolved = true;
|
|
65
82
|
return;
|
|
66
83
|
}
|
|
67
|
-
seen.add(
|
|
84
|
+
seen.add(identity);
|
|
68
85
|
if (isSuspiciousStashRoot(dir)) {
|
|
69
86
|
warn(`Warning: stash root "${dir}" appears to be a system directory. This may be unintentional.`);
|
|
70
87
|
}
|
|
71
88
|
sources.push({
|
|
72
89
|
path: resolved,
|
|
73
90
|
...(registryId ? { registryId } : {}),
|
|
91
|
+
...(isDefault ? { isDefault: true } : {}),
|
|
74
92
|
writable,
|
|
75
93
|
type,
|
|
76
94
|
...(adapterId ? { adapterId } : {}),
|
|
@@ -88,17 +106,17 @@ export function resolveSourceEntries(overrideStashDir, existingConfig) {
|
|
|
88
106
|
const contentRoot = resolveEntryContentDir(entry);
|
|
89
107
|
if (contentRoot == null) {
|
|
90
108
|
const unresolvedPath = path.join(getUnresolvedSourcesDir(implicitStashDir ?? process.cwd()), entry.name ?? entry.type);
|
|
91
|
-
addSource(unresolvedPath, entry.name, component?.writable ?? resolveWritable(entry), entry.type, component?.adapter, true);
|
|
109
|
+
addSource(unresolvedPath, entry.name, component?.writable ?? resolveWritable(entry), entry.type, component?.adapter, true, entry.name === config.defaultBundle);
|
|
92
110
|
continue;
|
|
93
111
|
}
|
|
94
112
|
const dir = path.resolve(contentRoot, component?.root ?? ".");
|
|
95
113
|
if (!isWithin(dir, contentRoot)) {
|
|
96
114
|
warn(`Warning: component root "${component?.root}" escapes bundle "${entry.name}"; skipping source.`);
|
|
97
115
|
const unresolvedPath = path.join(getUnresolvedSourcesDir(contentRoot), entry.name ?? entry.type);
|
|
98
|
-
addSource(unresolvedPath, entry.name, component?.writable ?? resolveWritable(entry), entry.type, component?.adapter, true);
|
|
116
|
+
addSource(unresolvedPath, entry.name, component?.writable ?? resolveWritable(entry), entry.type, component?.adapter, true, entry.name === config.defaultBundle);
|
|
99
117
|
continue;
|
|
100
118
|
}
|
|
101
|
-
addSource(dir, entry.name, component?.writable ?? resolveWritable(entry), entry.type, component?.adapter);
|
|
119
|
+
addSource(dir, entry.name, component?.writable ?? resolveWritable(entry), entry.type, component?.adapter, false, entry.name === config.defaultBundle);
|
|
102
120
|
}
|
|
103
121
|
return sources;
|
|
104
122
|
}
|
|
@@ -581,8 +581,9 @@ function lowerLlm(request, base) {
|
|
|
581
581
|
(typeof tools === "object" && tools !== null && !Array.isArray(tools) && Object.keys(tools).length === 0);
|
|
582
582
|
if (empty)
|
|
583
583
|
translated.add("tools");
|
|
584
|
-
else
|
|
585
|
-
|
|
584
|
+
else {
|
|
585
|
+
throw new ConfigError("The direct LLM transport cannot enforce the resolved tool policy.", "INVALID_CONFIG_FILE");
|
|
586
|
+
}
|
|
586
587
|
}
|
|
587
588
|
if (own(request.runtime, "workspace")) {
|
|
588
589
|
if (request.runtime.workspace === null || request.runtime.workspace === "")
|
|
@@ -9,6 +9,37 @@ import { loadModelMap } from "./model-map.js";
|
|
|
9
9
|
function own(value, key) {
|
|
10
10
|
return Object.hasOwn(value, key);
|
|
11
11
|
}
|
|
12
|
+
function requestedToolNames(tools) {
|
|
13
|
+
if (tools === null)
|
|
14
|
+
return Object.freeze([]);
|
|
15
|
+
if (typeof tools === "string") {
|
|
16
|
+
return Object.freeze(tools
|
|
17
|
+
.split(",")
|
|
18
|
+
.map((tool) => tool.trim())
|
|
19
|
+
.filter(Boolean));
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(tools))
|
|
22
|
+
return Object.freeze(tools.map((tool) => tool.trim()).filter(Boolean));
|
|
23
|
+
const structured = tools;
|
|
24
|
+
// The portable structured spelling is a boolean allow/deny map. Opaque or
|
|
25
|
+
// nested provider policy cannot be reduced to tool names without guessing,
|
|
26
|
+
// so a host name allowlist must deny it (an explicit "*" may still allow it).
|
|
27
|
+
if (Object.values(structured).some((value) => typeof value !== "boolean"))
|
|
28
|
+
return undefined;
|
|
29
|
+
return Object.freeze(Object.keys(structured).filter((tool) => structured[tool] === true));
|
|
30
|
+
}
|
|
31
|
+
/** The production authorizer: executable assets may only narrow a host-owned ceiling. */
|
|
32
|
+
export function toolAuthorizerFromConfig(config) {
|
|
33
|
+
const allowed = new Set(config.execution?.allowedTools ?? []);
|
|
34
|
+
return (input) => {
|
|
35
|
+
const requested = requestedToolNames(input.tools);
|
|
36
|
+
const permitted = allowed.has("*") || requested?.every((tool) => allowed.has(tool)) === true;
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
status: permitted ? "allowed" : "denied",
|
|
39
|
+
policy: "config-execution-allowed-tools",
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
}
|
|
12
43
|
/** Pure common cascade composition once caller-specific sources are rendered. */
|
|
13
44
|
export function planPreparedExecution(options) {
|
|
14
45
|
return planExecutionCascade({
|
|
@@ -56,7 +87,7 @@ export function prepareResolvedExecution(options) {
|
|
|
56
87
|
engines: executionEngineDefinitionsFromConfig(config),
|
|
57
88
|
modelMap: options.modelMap ?? loadModelMap({ engines: config.engines }).map,
|
|
58
89
|
invocationKind: options.invocationKind,
|
|
59
|
-
|
|
90
|
+
authorizeTools: options.authorizeTools ?? toolAuthorizerFromConfig(config),
|
|
60
91
|
});
|
|
61
92
|
return Object.freeze({
|
|
62
93
|
plan,
|
|
@@ -324,7 +324,7 @@ export function buildReflectPrompt(input) {
|
|
|
324
324
|
sections.push(lines.join("\n"));
|
|
325
325
|
}
|
|
326
326
|
if (input.avoidPatterns && input.avoidPatterns.length > 0) {
|
|
327
|
-
sections.push(`## Avoid These Patterns\nPrevious assets in this run produced these errors — do not repeat them:\n${input.avoidPatterns.map((e) => `- ${e}`).join("\n")}`);
|
|
327
|
+
sections.push(`## Avoid These Patterns\nRun-only guidance: do not copy this heading, explanation, or any diagnostic below into the proposed asset.\nPrevious assets in this run produced these errors — do not repeat them:\n${input.avoidPatterns.map((e) => `- ${e}`).join("\n")}`);
|
|
328
328
|
}
|
|
329
329
|
// R-1 / #372: Self-Refine (arXiv:2303.17651) — inject prior draft as critique target.
|
|
330
330
|
// On refinement iterations (iter > 0), the agent is shown its previous proposal
|
|
@@ -162,8 +162,9 @@ export function createAgentRequestLowerer(options) {
|
|
|
162
162
|
const tools = request.tools;
|
|
163
163
|
if (translatesTools(options.tools, tools))
|
|
164
164
|
translated.add("tools");
|
|
165
|
-
else
|
|
166
|
-
|
|
165
|
+
else {
|
|
166
|
+
throw new ConfigError(`The ${options.adapter} transport cannot enforce the resolved tool policy.`, "INVALID_CONFIG_FILE");
|
|
167
|
+
}
|
|
167
168
|
dispatch.tools = tools;
|
|
168
169
|
}
|
|
169
170
|
if (own(request.runtime, "settings")) {
|
package/dist/llm/client.js
CHANGED
|
@@ -444,11 +444,12 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
|
|
|
444
444
|
* attempts the schema request fresh every time and falls back once per call
|
|
445
445
|
* on a 4xx — see the in-memory tracker below.
|
|
446
446
|
*/
|
|
447
|
-
export async function probeLlmReachable(config) {
|
|
447
|
+
export async function probeLlmReachable(config, timeoutMs) {
|
|
448
448
|
try {
|
|
449
449
|
const raw = await chatCompletion(config, [{ role: "user", content: "Respond with just the word: ok" }], {
|
|
450
450
|
maxTokens: 16,
|
|
451
451
|
temperature: 0,
|
|
452
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
452
453
|
});
|
|
453
454
|
return raw.length > 0 ? { reachable: true } : { reachable: false, error: "empty response" };
|
|
454
455
|
}
|
package/dist/registry/resolve.js
CHANGED
|
@@ -11,6 +11,29 @@ import { NotFoundError, UsageError } from "../core/errors.js";
|
|
|
11
11
|
import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github.js";
|
|
12
12
|
import { cancelRegistryResponse, fetchRegistryResponse } from "./network.js";
|
|
13
13
|
import { isExactSemver, isSemverRange, maxSatisfying } from "./semver.js";
|
|
14
|
+
/**
|
|
15
|
+
* Pass an HTTPS bearer credential to Git without placing it in the remote URL
|
|
16
|
+
* or argv. The value exists only in the child-process environment and is never
|
|
17
|
+
* persisted or included in diagnostics.
|
|
18
|
+
*/
|
|
19
|
+
export function gitCredentialEnvironment(credential) {
|
|
20
|
+
if (!credential)
|
|
21
|
+
return { ...process.env };
|
|
22
|
+
if (/[\r\n\0]/.test(credential)) {
|
|
23
|
+
throw new UsageError("Git credential contains an invalid control character.");
|
|
24
|
+
}
|
|
25
|
+
const configuredCount = process.env.GIT_CONFIG_COUNT;
|
|
26
|
+
const nextIndex = configuredCount === undefined ? 0 : Number(configuredCount);
|
|
27
|
+
if (!Number.isSafeInteger(nextIndex) || nextIndex < 0) {
|
|
28
|
+
throw new UsageError("GIT_CONFIG_COUNT must be a non-negative integer before a Git credential can be added.");
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...process.env,
|
|
32
|
+
GIT_CONFIG_COUNT: String(nextIndex + 1),
|
|
33
|
+
[`GIT_CONFIG_KEY_${nextIndex}`]: "http.extraHeader",
|
|
34
|
+
[`GIT_CONFIG_VALUE_${nextIndex}`]: `Authorization: Bearer ${credential}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
14
37
|
/**
|
|
15
38
|
* Validate that a URL is safe to pass to git.
|
|
16
39
|
* Allowlists https:, http:, ssh:, git: schemes and git@ SSH shorthand.
|
|
@@ -122,16 +145,16 @@ function detectRegistrySearchId(ref) {
|
|
|
122
145
|
lines.push("Or search for the installable ref:", ` akm search "${segments.length > 2 ? segments[segments.length - 1] : rest}" --from registry`, "Then install using the installRef value from the result:", " akm bundle add github:owner/repo", " akm bundle add npm:package-name");
|
|
123
146
|
return lines.join("\n");
|
|
124
147
|
}
|
|
125
|
-
export async function resolveRegistryArtifact(parsed) {
|
|
148
|
+
export async function resolveRegistryArtifact(parsed, options) {
|
|
126
149
|
switch (parsed.source) {
|
|
127
150
|
case "npm":
|
|
128
151
|
return resolveNpmArtifact(parsed);
|
|
129
152
|
case "local":
|
|
130
153
|
return resolveLocalArtifact(parsed);
|
|
131
154
|
case "git":
|
|
132
|
-
return resolveGitArtifact(parsed);
|
|
155
|
+
return resolveGitArtifact(parsed, options?.gitCredential);
|
|
133
156
|
case "github":
|
|
134
|
-
return resolveGithubArtifact(parsed);
|
|
157
|
+
return resolveGithubArtifact(parsed, options?.gitCredential);
|
|
135
158
|
}
|
|
136
159
|
}
|
|
137
160
|
function parseNpmRef(input, originalRef) {
|
|
@@ -427,12 +450,12 @@ async function resolveNpmArtifact(parsed) {
|
|
|
427
450
|
allowPrivateRegistryOrigin: npmPolicy.allowPrivateRegistryOrigin,
|
|
428
451
|
};
|
|
429
452
|
}
|
|
430
|
-
async function resolveGithubArtifact(parsed) {
|
|
453
|
+
async function resolveGithubArtifact(parsed, credential) {
|
|
431
454
|
const gitUrl = `https://github.com/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}.git`;
|
|
432
455
|
const repoBase = `${GITHUB_API_BASE}/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}`;
|
|
433
456
|
// Prefer git-backed installs so private GitHub repos work with the user's
|
|
434
457
|
// normal git credential helper rather than requiring API-specific auth.
|
|
435
|
-
const gitResolvedRevision = resolveGitRevisionFromRemote(gitUrl, parsed.requestedRef);
|
|
458
|
+
const gitResolvedRevision = resolveGitRevisionFromRemote(gitUrl, parsed.requestedRef, credential);
|
|
436
459
|
if (gitResolvedRevision) {
|
|
437
460
|
return {
|
|
438
461
|
id: parsed.id,
|
|
@@ -443,7 +466,7 @@ async function resolveGithubArtifact(parsed) {
|
|
|
443
466
|
resolvedRevision: gitResolvedRevision,
|
|
444
467
|
};
|
|
445
468
|
}
|
|
446
|
-
const headers = githubHeaders();
|
|
469
|
+
const headers = { ...githubHeaders(), ...(credential ? { Authorization: `Bearer ${credential}` } : {}) };
|
|
447
470
|
if (parsed.requestedRef) {
|
|
448
471
|
const commit = await tryFetchJson(`${repoBase}/commits/${encodeURIComponent(parsed.requestedRef)}`, headers, GITHUB_API_POLICY);
|
|
449
472
|
const resolvedRevision = asString(commit?.sha) ?? parsed.requestedRef;
|
|
@@ -485,19 +508,23 @@ async function resolveGithubArtifact(parsed) {
|
|
|
485
508
|
resolvedRevision: asString(commit?.sha) ?? defaultBranch,
|
|
486
509
|
};
|
|
487
510
|
}
|
|
488
|
-
function resolveGitRevisionFromRemote(url, requestedRef) {
|
|
511
|
+
function resolveGitRevisionFromRemote(url, requestedRef, credential) {
|
|
489
512
|
validateGitUrl(url);
|
|
490
513
|
const ref = requestedRef ?? "HEAD";
|
|
491
514
|
if (requestedRef)
|
|
492
515
|
validateGitRef(requestedRef);
|
|
493
|
-
const result = spawnSync("git", ["ls-remote", url, ref], {
|
|
516
|
+
const result = spawnSync("git", ["ls-remote", url, ref], {
|
|
517
|
+
encoding: "utf8",
|
|
518
|
+
timeout: 30_000,
|
|
519
|
+
env: gitCredentialEnvironment(credential),
|
|
520
|
+
});
|
|
494
521
|
if (result.status !== 0)
|
|
495
522
|
return undefined;
|
|
496
523
|
const firstLine = result.stdout.trim().split(/\r?\n/)[0];
|
|
497
524
|
return firstLine?.split(/\s/)[0] || undefined;
|
|
498
525
|
}
|
|
499
|
-
async function resolveGitArtifact(parsed) {
|
|
500
|
-
const resolvedRevision = resolveGitRevisionFromRemote(parsed.url, parsed.requestedRef);
|
|
526
|
+
async function resolveGitArtifact(parsed, credential) {
|
|
527
|
+
const resolvedRevision = resolveGitRevisionFromRemote(parsed.url, parsed.requestedRef, credential);
|
|
501
528
|
if (!resolvedRevision) {
|
|
502
529
|
// Unlike the GitHub path (which falls back to the REST API on a failed
|
|
503
530
|
// `git ls-remote`), a plain git source has no fallback resolver — an
|