akm-cli 0.9.0-rc.0 → 0.9.0-rc.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 +339 -2
- package/SECURITY.md +23 -24
- package/dist/assets/help/help-improve.md +10 -10
- package/dist/assets/hints/cli-hints-full.md +44 -10
- package/dist/assets/hints/cli-hints-short.md +6 -2
- package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
- package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/README.md +28 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
- package/dist/assets/tasks/core/backup.yml +1 -0
- package/dist/assets/tasks/core/extract.yml +1 -0
- package/dist/assets/tasks/core/improve.yml +1 -0
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +1 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
- package/dist/assets/templates/html/health.html +5 -1
- package/dist/cli/config-migrate.js +31 -138
- package/dist/cli/config-validate.js +10 -8
- package/dist/cli.js +48 -14
- package/dist/commands/agent/agent-dispatch.js +17 -16
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +5 -15
- package/dist/commands/backup-cli.js +54 -0
- package/dist/commands/config-cli.js +45 -159
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +8 -65
- package/dist/commands/env/secret.js +8 -5
- package/dist/commands/health/checks.js +130 -83
- package/dist/commands/health/html-report.js +4 -0
- package/dist/commands/health/improve-metrics.js +30 -32
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +4 -0
- package/dist/commands/health/metrics.js +2 -1
- package/dist/commands/health/surfaces.js +5 -4
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/consolidate/chunking.js +2 -2
- package/dist/commands/improve/consolidate.js +28 -25
- package/dist/commands/improve/distill/promote-memory.js +5 -12
- package/dist/commands/improve/distill/quality-gate.js +5 -7
- package/dist/commands/improve/distill.js +16 -5
- package/dist/commands/improve/eligibility.js +22 -12
- package/dist/commands/improve/extract-cli.js +47 -19
- package/dist/commands/improve/extract.js +110 -62
- package/dist/commands/improve/improve-cli.js +38 -16
- package/dist/commands/improve/improve-result-file.js +30 -24
- package/dist/commands/improve/improve-strategies.js +137 -0
- package/dist/commands/improve/improve.js +60 -30
- package/dist/commands/improve/locks.js +66 -45
- package/dist/commands/improve/loop-stages.js +75 -33
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
- package/dist/commands/improve/preparation.js +71 -73
- package/dist/commands/improve/procedural.js +3 -2
- package/dist/commands/improve/recombine.js +2 -1
- package/dist/commands/improve/reflect.js +119 -214
- package/dist/commands/improve/shared.js +11 -5
- package/dist/commands/lint/base-linter.js +152 -42
- package/dist/commands/mv-cli.js +809 -0
- package/dist/commands/proposal/proposal-cli.js +18 -8
- package/dist/commands/proposal/propose.js +64 -69
- package/dist/commands/read/knowledge.js +436 -4
- package/dist/commands/read/remember-cli.js +39 -2
- package/dist/commands/read/search-cli.js +6 -1
- package/dist/commands/registry-cli.js +29 -14
- package/dist/commands/remember.js +2 -0
- package/dist/commands/sources/init.js +13 -14
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +2 -4
- package/dist/commands/sources/source-add.js +62 -73
- package/dist/commands/sources/source-manage.js +50 -46
- package/dist/commands/sources/stash-cli.js +41 -4
- package/dist/commands/tasks/default-tasks.js +12 -12
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +113 -18
- package/dist/commands/wiki-cli.js +9 -10
- package/dist/commands/workflow-cli.js +276 -12
- package/dist/core/asset/asset-spec.js +58 -1
- package/dist/core/asset/frontmatter.js +12 -2
- package/dist/core/common.js +5 -3
- package/dist/core/config/config-io.js +28 -17
- package/dist/core/config/config-schema.js +379 -66
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +98 -27
- package/dist/core/config/config.js +132 -266
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +32 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/extra-params.js +61 -0
- package/dist/core/file-lock.js +201 -56
- package/dist/core/improve-result.js +178 -0
- package/dist/core/json-schema.js +142 -0
- package/dist/core/maintenance-barrier.js +119 -0
- package/dist/core/migration-backup.js +416 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/redaction.js +358 -0
- package/dist/core/state/migrations.js +17 -2
- package/dist/core/state-db.js +44 -1
- package/dist/indexer/db/db.js +118 -2
- package/dist/indexer/graph/graph-extraction.js +28 -16
- package/dist/indexer/index-writer-lock.js +31 -24
- package/dist/indexer/index-written-assets.js +15 -6
- package/dist/indexer/indexer.js +47 -2
- package/dist/indexer/passes/memory-inference.js +10 -6
- package/dist/indexer/passes/metadata.js +250 -0
- package/dist/indexer/search/db-search.js +111 -44
- package/dist/indexer/search/fts-query.js +41 -0
- package/dist/indexer/search/ranking-contributors.js +48 -0
- package/dist/indexer/search/ranking.js +36 -23
- package/dist/indexer/search/search-fields.js +11 -1
- package/dist/indexer/walk/matchers.js +39 -0
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -50
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +202 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +36 -62
- package/dist/integrations/agent/runner-dispatch.js +91 -4
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +141 -20
- package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
- package/dist/integrations/harnesses/aider/index.js +57 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
- package/dist/integrations/harnesses/amazonq/index.js +58 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
- package/dist/integrations/harnesses/claude/index.js +25 -25
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
- package/dist/integrations/harnesses/codex/index.js +62 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
- package/dist/integrations/harnesses/copilot/index.js +59 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
- package/dist/integrations/harnesses/gemini/index.js +59 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +27 -28
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +15 -22
- package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
- package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
- package/dist/integrations/harnesses/openhands/index.js +57 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
- package/dist/integrations/harnesses/pi/index.js +57 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -32
- package/dist/integrations/lockfile.js +32 -21
- package/dist/integrations/session-logs/index.js +24 -11
- package/dist/llm/client.js +48 -14
- package/dist/llm/feature-gate.js +15 -47
- package/dist/llm/graph-extract.js +1 -1
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer-impl.js +1 -1
- package/dist/llm/usage-persist.js +4 -0
- package/dist/llm/usage-telemetry.js +35 -5
- package/dist/output/renderers.js +3 -2
- package/dist/output/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +6 -0
- package/dist/output/text/helpers.js +215 -2
- package/dist/output/text/workflow.js +3 -1
- package/dist/schemas/akm-config.json +16638 -0
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +372 -0
- package/dist/scripts/migrate-storage.js +10944 -8801
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
- package/dist/setup/detected-engines.js +142 -0
- package/dist/setup/engine-config.js +89 -0
- package/dist/setup/setup.js +236 -132
- package/dist/setup/steps/connection.js +61 -32
- package/dist/setup/steps/platforms.js +4 -4
- package/dist/setup/steps.js +3 -2
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +1 -0
- package/dist/storage/repositories/improve-runs-repository.js +5 -5
- package/dist/storage/repositories/task-history-repository.js +78 -0
- package/dist/storage/repositories/workflow-runs-repository.js +190 -1
- package/dist/tasks/parser.js +138 -52
- package/dist/tasks/runner.js +71 -75
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/validator.js +11 -6
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +4 -0
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +200 -13
- package/dist/workflows/exec/brief.js +478 -0
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +1034 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1355 -0
- package/dist/workflows/exec/run-workflow.js +609 -0
- package/dist/workflows/exec/scheduler.js +71 -0
- package/dist/workflows/exec/step-work.js +1212 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +375 -0
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +68 -0
- package/dist/workflows/ir/schema.js +545 -0
- package/dist/workflows/parser.js +10 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +869 -0
- package/dist/workflows/program/project.js +104 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +246 -69
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
- package/dist/workflows/validate-summary.js +24 -3
- package/dist/workflows/validator.js +26 -1
- package/docs/data-and-telemetry.md +4 -3
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +5 -4
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/v0.8-to-v0.9.md +401 -0
- package/package.json +4 -2
- package/schemas/akm-config.json +16638 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +372 -0
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/llm/call-ai.js +0 -62
- package/dist/setup/legacy-config.js +0 -106
- package/docs/README.md +0 -104
- /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { makeAssetRef } from "../../core/asset/asset-ref.js";
|
|
6
|
+
import { acquireMaintenanceActivitySync } from "../../core/maintenance-barrier.js";
|
|
6
7
|
import { getStateDbPath } from "../../core/state-db.js";
|
|
7
8
|
import { openDatabase } from "../../storage/database.js";
|
|
8
9
|
import { getUtilityScoresByIds } from "../db/db.js";
|
|
9
|
-
import { applyContributorAblation, applyScoreContributors, applyUtilityContributors, defaultRankingContributors, defaultUtilityRankingContributors, } from "./ranking-contributors.js";
|
|
10
|
+
import { applyBeliefStateScoreCeiling, applyContributorAblation, applyScoreContributors, applyUtilityContributors, defaultRankingContributors, defaultUtilityRankingContributors, } from "./ranking-contributors.js";
|
|
10
11
|
/**
|
|
11
12
|
* R2 — best-effort load of `asset_salience.rank_score` from state.db for the
|
|
12
13
|
* ranked items. Fail-open: any error (state.db locked by a concurrent improve
|
|
@@ -27,35 +28,41 @@ export function loadSalienceRankScores(items) {
|
|
|
27
28
|
const dbPath = getStateDbPath();
|
|
28
29
|
if (!fs.existsSync(dbPath))
|
|
29
30
|
return result; // improve loop has never run here
|
|
31
|
+
const releaseActivity = acquireMaintenanceActivitySync("state-db");
|
|
30
32
|
const idByRef = new Map();
|
|
31
|
-
for (const item of items) {
|
|
32
|
-
idByRef.set(makeAssetRef(item.entry.type, item.entry.name), item.id);
|
|
33
|
-
}
|
|
34
|
-
const stateDb = openDatabase(dbPath, { readonly: true });
|
|
35
33
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
for (const item of items) {
|
|
35
|
+
idByRef.set(makeAssetRef(item.entry.type, item.entry.name), item.id);
|
|
38
36
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const CHUNK = 500;
|
|
44
|
-
for (let i = 0; i < refs.length; i += CHUNK) {
|
|
45
|
-
const chunk = refs.slice(i, i + CHUNK);
|
|
46
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
47
|
-
const rows = stateDb
|
|
48
|
-
.prepare(`SELECT asset_ref, rank_score FROM asset_salience WHERE asset_ref IN (${placeholders})`)
|
|
49
|
-
.all(...chunk);
|
|
50
|
-
for (const row of rows) {
|
|
51
|
-
const id = idByRef.get(row.asset_ref);
|
|
52
|
-
if (id !== undefined)
|
|
53
|
-
result.set(id, row.rank_score);
|
|
37
|
+
const stateDb = openDatabase(dbPath, { readonly: true });
|
|
38
|
+
try {
|
|
39
|
+
try {
|
|
40
|
+
stateDb.exec("PRAGMA busy_timeout = 250");
|
|
54
41
|
}
|
|
42
|
+
catch {
|
|
43
|
+
// pragma failure on a readonly handle is fine — default timeout applies
|
|
44
|
+
}
|
|
45
|
+
const refs = [...idByRef.keys()];
|
|
46
|
+
const CHUNK = 500;
|
|
47
|
+
for (let i = 0; i < refs.length; i += CHUNK) {
|
|
48
|
+
const chunk = refs.slice(i, i + CHUNK);
|
|
49
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
50
|
+
const rows = stateDb
|
|
51
|
+
.prepare(`SELECT asset_ref, rank_score FROM asset_salience WHERE asset_ref IN (${placeholders})`)
|
|
52
|
+
.all(...chunk);
|
|
53
|
+
for (const row of rows) {
|
|
54
|
+
const id = idByRef.get(row.asset_ref);
|
|
55
|
+
if (id !== undefined)
|
|
56
|
+
result.set(id, row.rank_score);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
stateDb.close();
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
finally {
|
|
58
|
-
|
|
65
|
+
releaseActivity();
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
catch {
|
|
@@ -152,6 +159,12 @@ export function applyRankingRules(options) {
|
|
|
152
159
|
};
|
|
153
160
|
for (const item of options.items) {
|
|
154
161
|
applyUtilityContributors(item, utilityContext, activeUtilityContributors);
|
|
162
|
+
// SPEC-5: demoting belief states (superseded/contradicted/archived/
|
|
163
|
+
// deprecated) cap the FINAL score. The additive belief penalty inside the
|
|
164
|
+
// multiplicative boost sum cannot overcome the FTS min-max normalization
|
|
165
|
+
// spread, so without the ceiling a superseded incumbent that is the best
|
|
166
|
+
// keyword match outranks its own correction forever.
|
|
167
|
+
applyBeliefStateScoreCeiling(item);
|
|
155
168
|
}
|
|
156
169
|
return options.items;
|
|
157
170
|
}
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* - description: entry description
|
|
10
10
|
* - tags: tags + aliases joined
|
|
11
11
|
* - hints: searchHints + examples + usage + intent fields
|
|
12
|
-
* - content: TOC headings
|
|
12
|
+
* - content: TOC headings + parameters + the config-gated body opening
|
|
13
|
+
* (lowest-weight catch-all)
|
|
13
14
|
*/
|
|
14
15
|
// NOTE (R5): the collapse detector's frozen canary queries are built from the
|
|
15
16
|
// same surface this function indexes (name tokens / tags / description) and
|
|
@@ -59,6 +60,15 @@ export function buildSearchFields(entry) {
|
|
|
59
60
|
contentParts.push(param.description);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
63
|
+
// Stash-organization conventions (SPEC-8): the self-situating body opening
|
|
64
|
+
// (captured by the metadata pass only when `index.indexBodyOpening` is on)
|
|
65
|
+
// folds into the lowest-weight catch-all column — never name/description/
|
|
66
|
+
// tags/hints — so orientation prose is retrievable without outranking
|
|
67
|
+
// structured-field matches. The fold is unconditional on the entry field:
|
|
68
|
+
// `rebuildFts` rebuilds FTS rows from stored entry_json and must reproduce
|
|
69
|
+
// the same fields without re-reading config.
|
|
70
|
+
if (entry.bodyOpening)
|
|
71
|
+
contentParts.push(entry.bodyOpening);
|
|
62
72
|
const content = contentParts.join(" ").toLowerCase();
|
|
63
73
|
return { name, description, tags, hints, content };
|
|
64
74
|
}
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { defaultRendererRegistry } from "../../core/asset/asset-registry.js";
|
|
12
12
|
import { SCRIPT_EXTENSIONS } from "../../core/asset/asset-spec.js";
|
|
13
13
|
import { looksLikeWorkflow } from "../../workflows/parser.js";
|
|
14
|
+
import { looksLikeWorkflowProgram } from "../../workflows/program/parser.js";
|
|
15
|
+
import { WORKFLOW_PROGRAM_RENDERER_NAME } from "../../workflows/program/project.js";
|
|
14
16
|
import { registerMatcher } from "./file-context.js";
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
16
18
|
// Private data
|
|
@@ -178,6 +180,35 @@ function classifyBySmartMd(ctx) {
|
|
|
178
180
|
}
|
|
179
181
|
return { type: "knowledge", specificity: 5 };
|
|
180
182
|
}
|
|
183
|
+
/** YAML workflow *programs* (redesign addendum, R1): `.yaml`/`.yml` files. */
|
|
184
|
+
const WORKFLOW_PROGRAM_EXTENSIONS = new Set([".yaml", ".yml"]);
|
|
185
|
+
/**
|
|
186
|
+
* Classify YAML workflow programs. Two claims, mirroring the markdown rules:
|
|
187
|
+
*
|
|
188
|
+
* - any `.yaml`/`.yml` under a `workflows/` dir (the directory rule —
|
|
189
|
+
* specificity 15 when `workflows` is the immediate parent, 10 for a
|
|
190
|
+
* deeper ancestor, same ladder as `matchDirectoryHint`);
|
|
191
|
+
* - anywhere else, a content probe via `looksLikeWorkflowProgram`
|
|
192
|
+
* (`version: 1` + `steps:` at column 0) at specificity 19, the same
|
|
193
|
+
* level as `classifyBySmartMd`'s `looksLikeWorkflow` claim.
|
|
194
|
+
*
|
|
195
|
+
* The fact does NOT go through `toMatchResult` — the workflow TYPE maps to
|
|
196
|
+
* the markdown renderer by default, so this classifier names the
|
|
197
|
+
* `workflow-program-yaml` renderer on its result directly.
|
|
198
|
+
*/
|
|
199
|
+
function classifyByWorkflowProgram(ctx) {
|
|
200
|
+
if (!WORKFLOW_PROGRAM_EXTENSIONS.has(ctx.ext))
|
|
201
|
+
return null;
|
|
202
|
+
if (isTypedDirDocFile(ctx.fileName))
|
|
203
|
+
return null;
|
|
204
|
+
if (ctx.parentDir === "workflows")
|
|
205
|
+
return { type: "workflow", specificity: 15 };
|
|
206
|
+
if (ctx.ancestorDirs.includes("workflows"))
|
|
207
|
+
return { type: "workflow", specificity: 10 };
|
|
208
|
+
if (looksLikeWorkflowProgram(ctx.content()))
|
|
209
|
+
return { type: "workflow", specificity: 19 };
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
181
212
|
function classifyByWiki(ctx) {
|
|
182
213
|
if (ctx.ext !== ".md")
|
|
183
214
|
return null;
|
|
@@ -223,12 +254,20 @@ export function smartMdMatcher(ctx) {
|
|
|
223
254
|
export function wikiMatcher(ctx) {
|
|
224
255
|
return toMatchResult(ctx, classifyByWiki);
|
|
225
256
|
}
|
|
257
|
+
export function workflowProgramMatcher(ctx) {
|
|
258
|
+
const fact = classifyByWorkflowProgram(ctx);
|
|
259
|
+
if (!fact)
|
|
260
|
+
return null;
|
|
261
|
+
// Named directly (not via rendererNameFor) — see classifyByWorkflowProgram.
|
|
262
|
+
return { type: fact.type, specificity: fact.specificity, renderer: WORKFLOW_PROGRAM_RENDERER_NAME };
|
|
263
|
+
}
|
|
226
264
|
const builtinMatchers = [
|
|
227
265
|
extensionMatcher,
|
|
228
266
|
directoryMatcher,
|
|
229
267
|
parentDirHintMatcher,
|
|
230
268
|
smartMdMatcher,
|
|
231
269
|
wikiMatcher,
|
|
270
|
+
workflowProgramMatcher,
|
|
232
271
|
];
|
|
233
272
|
export function registerBuiltinMatchers() {
|
|
234
273
|
for (const matcher of builtinMatchers) {
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
* their previous home in `builders.ts`.
|
|
15
15
|
*/
|
|
16
16
|
import { UsageError } from "../../core/errors.js";
|
|
17
|
+
import { resolveModel } from "./model-aliases.js";
|
|
18
|
+
/** Resolve a raw dispatch model once, while preserving frozen/lowered models verbatim. */
|
|
19
|
+
export function resolveDispatchModel(request, profile, platform) {
|
|
20
|
+
if (!request.model || request.modelIsExact)
|
|
21
|
+
return request.model;
|
|
22
|
+
return resolveModel(request.model, platform, profile.modelAliases, profile.globalModelAliases);
|
|
23
|
+
}
|
|
17
24
|
/**
|
|
18
25
|
* Guard against values that start with `--`, which would be mis-interpreted as
|
|
19
26
|
* CLI flags by the spawned process when used as flag values (model, systemPrompt).
|
|
@@ -14,50 +14,17 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { ConfigError } from "../../core/errors.js";
|
|
16
16
|
import { HARNESS_REGISTRY } from "../harnesses/index.js";
|
|
17
|
-
// Types + shared validation helpers live in the leaf module `builder-shared.ts`
|
|
18
|
-
// so per-harness builders (harnesses/claude/agent-builder.ts) can depend on them
|
|
19
|
-
// without importing this file back — avoiding an init-order cycle through
|
|
20
|
-
// BUILTIN_BUILDERS (#563). Re-exported here so existing `agent/builders` import
|
|
21
|
-
// sites keep working.
|
|
22
|
-
import { assertNotFlag } from "./builder-shared.js";
|
|
23
|
-
import { resolveModel } from "./model-aliases.js";
|
|
24
|
-
import { getBuiltinAgentProfile } from "./profiles.js";
|
|
25
17
|
export { assertNotFlag, normalizeTools } from "./builder-shared.js";
|
|
26
18
|
// ── Platform builders ─────────────────────────────────────────────────────────
|
|
27
19
|
// The OpenCode builder was migrated to its harness directory in #564
|
|
28
20
|
// (`harnesses/opencode/agent-builder.ts`) and the Claude Code builder in #563
|
|
29
21
|
// (`harnesses/claude/agent-builder.ts`). Both are imported back into
|
|
30
22
|
// BUILTIN_BUILDERS below so platform routing is unchanged.
|
|
31
|
-
/**
|
|
32
|
-
* Default builder — used for custom profiles and any platform without a
|
|
33
|
-
* dedicated builder. Passes systemPrompt and model via the same flags as
|
|
34
|
-
* the builtin builders so custom profiles benefit from agent asset metadata.
|
|
35
|
-
* Tools are omitted — no standard cross-platform flag exists.
|
|
36
|
-
*/
|
|
37
|
-
const defaultBuilder = {
|
|
38
|
-
platform: "default",
|
|
39
|
-
build(profile, req) {
|
|
40
|
-
assertNotFlag(req.systemPrompt, "systemPrompt");
|
|
41
|
-
assertNotFlag(req.model, "model");
|
|
42
|
-
const args = [...profile.args];
|
|
43
|
-
if (req.systemPrompt) {
|
|
44
|
-
args.push("--system-prompt", req.systemPrompt);
|
|
45
|
-
}
|
|
46
|
-
if (req.model) {
|
|
47
|
-
const resolved = resolveModel(req.model, profile.name, profile.modelAliases, profile.globalModelAliases);
|
|
48
|
-
args.push("--model", resolved);
|
|
49
|
-
}
|
|
50
|
-
args.push("--");
|
|
51
|
-
args.push(req.prompt);
|
|
52
|
-
return { argv: [profile.bin, ...args] };
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
23
|
// ── Registry ──────────────────────────────────────────────────────────────────
|
|
56
24
|
/**
|
|
57
25
|
* DERIVED from `HARNESS_REGISTRY` (P0.5 registry-drift fix): each harness that
|
|
58
|
-
* owns an `agentBuilder` is registered under its canonical id
|
|
59
|
-
*
|
|
60
|
-
* hand-maintained map that could (and did) drift from the harness registry.
|
|
26
|
+
* owns an `agentBuilder` is registered under its canonical id. Named engines
|
|
27
|
+
* are schema-validated to canonical harness ids before reaching this boundary.
|
|
61
28
|
*/
|
|
62
29
|
const BUILTIN_BUILDERS = (() => {
|
|
63
30
|
const registry = {};
|
|
@@ -65,30 +32,18 @@ const BUILTIN_BUILDERS = (() => {
|
|
|
65
32
|
if (!harness.agentBuilder)
|
|
66
33
|
continue;
|
|
67
34
|
registry[harness.id] = harness.agentBuilder;
|
|
68
|
-
registry[`${harness.id}-headless`] = harness.agentBuilder;
|
|
69
|
-
for (const alias of harness.aliases)
|
|
70
|
-
registry[alias] = harness.agentBuilder;
|
|
71
35
|
}
|
|
72
36
|
return Object.freeze(registry);
|
|
73
37
|
})();
|
|
74
38
|
/**
|
|
75
39
|
* Return the builder for the given platform name.
|
|
76
40
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* contract for user-defined wrappers. A *known built-in agent CLI* with no
|
|
80
|
-
* dedicated builder (codex, gemini, aider + their `-headless` variants) is a
|
|
81
|
-
* loud `ConfigError` instead: the default flag shape is wrong for those CLIs
|
|
82
|
-
* (aider, for one, treats positionals as file names), so the old silent
|
|
83
|
-
* fallback produced a broken command that "ran" and failed downstream.
|
|
84
|
-
* Custom builders injected via tests can be passed as `registry`.
|
|
41
|
+
* Unknown platforms are rejected: there is no safe generic CLI flag shape.
|
|
42
|
+
* Custom builders injected by tests can be passed as `registry`.
|
|
85
43
|
*/
|
|
86
44
|
export function getCommandBuilder(platform, registry = BUILTIN_BUILDERS) {
|
|
87
45
|
const found = registry[platform];
|
|
88
46
|
if (found)
|
|
89
47
|
return found;
|
|
90
|
-
|
|
91
|
-
throw new ConfigError(`agent dispatch for "${platform}" is not supported yet: no command builder exists for this CLI, and the generic flag shape would produce a broken command.`, "INVALID_CONFIG_FILE", 'Use an "opencode" or "claude" profile, or — if your CLI is flag-compatible with one of those — set `profiles.agent.<name>.commandBuilder` to "opencode" or "claude".');
|
|
92
|
-
}
|
|
93
|
-
return defaultBuilder;
|
|
48
|
+
throw new ConfigError(`Agent platform "${platform}" has no registered command builder.`, "INVALID_CONFIG_FILE", "Select a canonical agent platform in engines.<name>.platform.");
|
|
94
49
|
}
|
|
@@ -1,147 +1,7 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
|
|
5
|
-
import { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles.js";
|
|
6
|
-
/**
|
|
7
|
-
* Default hard timeout for an agent CLI (60s — matches the value used in
|
|
8
|
-
* `docs/configuration.md`).
|
|
9
|
-
*/
|
|
4
|
+
/** Default hard timeout for an agent CLI when neither engine nor call overrides it. */
|
|
10
5
|
export const DEFAULT_AGENT_TIMEOUT_MS = 60_000;
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
* user override (`profiles.agent[name]`) on top of the built-in profile (if
|
|
14
|
-
* any). Returns `undefined` when neither yields a usable profile.
|
|
15
|
-
*/
|
|
16
|
-
export function resolveAgentProfile(name, overrides, globalModelAliases) {
|
|
17
|
-
const builtin = getBuiltinAgentProfile(name);
|
|
18
|
-
const platform = overrides?.platform;
|
|
19
|
-
// For opencode-sdk profiles, allow synthesizing without a built-in.
|
|
20
|
-
const sdkMode = platform === "opencode-sdk";
|
|
21
|
-
if (!builtin && !overrides?.bin && !sdkMode)
|
|
22
|
-
return undefined;
|
|
23
|
-
const base = builtin ??
|
|
24
|
-
{
|
|
25
|
-
name,
|
|
26
|
-
bin: overrides?.bin ?? name,
|
|
27
|
-
args: [],
|
|
28
|
-
stdio: "captured",
|
|
29
|
-
envPassthrough: [],
|
|
30
|
-
parseOutput: "text",
|
|
31
|
-
...(sdkMode ? { sdkMode: true } : {}),
|
|
32
|
-
};
|
|
33
|
-
if (!overrides) {
|
|
34
|
-
return globalModelAliases ? { ...base, globalModelAliases } : base;
|
|
35
|
-
}
|
|
36
|
-
// Per-profile aliases from config extend (and shadow) the built-in
|
|
37
|
-
// profile's own entries. (Previously always used base.modelAliases, so the
|
|
38
|
-
// documented `profiles.agent.<name>.modelAliases` config was silently
|
|
39
|
-
// dropped — same bug class as the timeoutMs override below.)
|
|
40
|
-
const modelAliases = overrides.modelAliases || base.modelAliases ? { ...base.modelAliases, ...overrides.modelAliases } : undefined;
|
|
41
|
-
return {
|
|
42
|
-
name,
|
|
43
|
-
bin: overrides.bin ?? base.bin,
|
|
44
|
-
args: overrides.args ?? base.args,
|
|
45
|
-
stdio: base.stdio,
|
|
46
|
-
env: base.env,
|
|
47
|
-
envPassthrough: base.envPassthrough,
|
|
48
|
-
// Honor a user-configured `profiles.agent.<name>.timeoutMs` override; fall
|
|
49
|
-
// back to the built-in profile's value. (Previously always used base, so
|
|
50
|
-
// the documented config override was silently ignored — callers had to pass
|
|
51
|
-
// a CLI flag like `--timeout-ms`.) runAgent (spawn.ts) reads profile.timeoutMs
|
|
52
|
-
// when no per-call timeout is supplied, so this makes the config knob work.
|
|
53
|
-
timeoutMs: overrides.timeoutMs ?? base.timeoutMs,
|
|
54
|
-
parseOutput: base.parseOutput,
|
|
55
|
-
...(sdkMode ? { sdkMode: true } : {}),
|
|
56
|
-
model: overrides.model ?? base.model,
|
|
57
|
-
endpoint: base.endpoint,
|
|
58
|
-
apiKey: base.apiKey,
|
|
59
|
-
commandBuilder: base.commandBuilder,
|
|
60
|
-
modelAliases,
|
|
61
|
-
...(globalModelAliases ? { globalModelAliases } : {}),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Resolve the runnable profile for `name`, or `undefined` if none is
|
|
66
|
-
* available (no built-in and no user override). Threads the config-root
|
|
67
|
-
* `modelAliases` tier table onto the resolved profile.
|
|
68
|
-
*/
|
|
69
|
-
export function resolveProfileFromConfig(name, config) {
|
|
70
|
-
return resolveAgentProfile(name, config?.profiles?.agent?.[name], config?.modelAliases);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Return the names of every agent profile available in `config` — built-ins
|
|
74
|
-
* plus any user-defined entries under `profiles.agent`. Sorted, deduplicated.
|
|
75
|
-
*/
|
|
76
|
-
export function listAgentProfileNames(config) {
|
|
77
|
-
const seen = new Set(BUILTIN_AGENT_PROFILE_NAMES);
|
|
78
|
-
for (const name of Object.keys(config?.profiles?.agent ?? {}))
|
|
79
|
-
seen.add(name);
|
|
80
|
-
return [...seen].sort();
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Resolve the default agent profile name. Order: explicit `requested` arg →
|
|
84
|
-
* `config.defaults.agent` → undefined.
|
|
85
|
-
*/
|
|
86
|
-
export function resolveDefaultProfileName(config, requested) {
|
|
87
|
-
if (requested?.trim())
|
|
88
|
-
return requested.trim();
|
|
89
|
-
const def = config?.defaults?.agent;
|
|
90
|
-
if (typeof def === "string" && def.trim())
|
|
91
|
-
return def.trim();
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Throw a stable `ConfigError` when the caller needs an agent profile but
|
|
96
|
-
* none can be resolved.
|
|
97
|
-
*/
|
|
98
|
-
export function requireAgentProfile(config, requested) {
|
|
99
|
-
if (!config) {
|
|
100
|
-
throw new ConfigError("agent commands are disabled: no agent configuration in config.json.", "INVALID_CONFIG_FILE", "Run `akm setup` to detect and configure an agent CLI, or add an entry under `profiles.agent` and set `defaults.agent`.");
|
|
101
|
-
}
|
|
102
|
-
const name = resolveDefaultProfileName(config, requested);
|
|
103
|
-
if (!name) {
|
|
104
|
-
throw new ConfigError("agent commands require a profile: pass --profile or set `defaults.agent` in config.json.", "INVALID_CONFIG_FILE", `Available profiles: ${listAgentProfileNames(config).join(", ")}.`);
|
|
105
|
-
}
|
|
106
|
-
const profile = resolveProfileFromConfig(name, config);
|
|
107
|
-
if (!profile) {
|
|
108
|
-
throw new ConfigError(`agent profile "${name}" is not built-in and has no \`bin\` override.`, "INVALID_CONFIG_FILE", `Define profiles.agent."${name}".bin in config.json, or pick one of: ${listAgentProfileNames(config).join(", ")}.`);
|
|
109
|
-
}
|
|
110
|
-
return profile;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Resolve the agent profile bound to a named improve process. Reads from
|
|
114
|
-
* `profiles.improve.default.processes.<processName>` for the profile binding,
|
|
115
|
-
* then falls back to `defaults.agent`.
|
|
116
|
-
*/
|
|
117
|
-
export function resolveProcessAgentProfile(processName, config) {
|
|
118
|
-
const processEntry = config?.profiles?.improve?.default?.processes;
|
|
119
|
-
const entry = processEntry?.[processName];
|
|
120
|
-
const profileName = entry?.profile;
|
|
121
|
-
const profile = requireAgentProfile(config, profileName);
|
|
122
|
-
let resolvedTimeoutMs;
|
|
123
|
-
if (entry?.timeoutMs === null) {
|
|
124
|
-
resolvedTimeoutMs = null;
|
|
125
|
-
}
|
|
126
|
-
else if (typeof entry?.timeoutMs === "number") {
|
|
127
|
-
resolvedTimeoutMs = entry.timeoutMs;
|
|
128
|
-
}
|
|
129
|
-
else if (profile.timeoutMs !== undefined) {
|
|
130
|
-
resolvedTimeoutMs = profile.timeoutMs;
|
|
131
|
-
}
|
|
132
|
-
return { profile, timeoutMs: resolvedTimeoutMs };
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Convenience: list every fully-resolved profile (built-ins merged with
|
|
136
|
-
* user overrides). Used by setup detection to enumerate candidates.
|
|
137
|
-
*/
|
|
138
|
-
export function listResolvedAgentProfiles(config) {
|
|
139
|
-
const resolved = [];
|
|
140
|
-
const builtins = listBuiltinAgentProfiles();
|
|
141
|
-
for (const name of listAgentProfileNames(config)) {
|
|
142
|
-
const profile = resolveProfileFromConfig(name, config) ?? builtins[name];
|
|
143
|
-
if (profile)
|
|
144
|
-
resolved.push(profile);
|
|
145
|
-
}
|
|
146
|
-
return resolved;
|
|
147
|
-
}
|
|
6
|
+
/** Default hard timeout for direct LLM calls when no engine/use override exists. */
|
|
7
|
+
export const DEFAULT_LLM_TIMEOUT_MS = 600_000;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import fs from "node:fs";
|
|
17
17
|
import path from "node:path";
|
|
18
|
-
import {
|
|
18
|
+
import { getBuiltinAgentProfile, listBuiltinAgentProfiles } from "./profiles.js";
|
|
19
19
|
/**
|
|
20
20
|
* Default PATH lookup. Walks `process.env.PATH` and returns the first
|
|
21
21
|
* existing executable file. Returns `undefined` when the bin is not on
|
|
@@ -71,7 +71,22 @@ export function _setAgentDetectForTests(fakes) {
|
|
|
71
71
|
export function detectAgentCliProfiles(agent, whichFn = defaultWhich) {
|
|
72
72
|
if (detectOverrides?.detectAgentCliProfiles)
|
|
73
73
|
return detectOverrides.detectAgentCliProfiles(agent, whichFn);
|
|
74
|
-
const
|
|
74
|
+
const profilesByName = new Map(Object.values(listBuiltinAgentProfiles()).map((profile) => [profile.name, profile]));
|
|
75
|
+
for (const [name, engine] of Object.entries(agent?.engines ?? {})) {
|
|
76
|
+
if (engine.kind !== "agent")
|
|
77
|
+
continue;
|
|
78
|
+
const builtin = getBuiltinAgentProfile(engine.platform);
|
|
79
|
+
profilesByName.set(name, {
|
|
80
|
+
name,
|
|
81
|
+
platform: engine.platform,
|
|
82
|
+
bin: engine.bin ?? builtin?.bin ?? (engine.platform === "opencode-sdk" ? "opencode" : engine.platform),
|
|
83
|
+
args: engine.args ?? builtin?.args ?? [],
|
|
84
|
+
stdio: "captured",
|
|
85
|
+
envPassthrough: builtin?.envPassthrough ?? [],
|
|
86
|
+
parseOutput: "text",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const profiles = [...profilesByName.values()];
|
|
75
90
|
return profiles.map((profile) => probeProfile(profile, whichFn));
|
|
76
91
|
}
|
|
77
92
|
function probeProfile(profile, whichFn) {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { deepMergeConfig } from "../../core/config/deep-merge.js";
|
|
6
|
+
import { ConfigError } from "../../core/errors.js";
|
|
7
|
+
import { formatExtraParamsIssue, validateExtraParams } from "../../core/extra-params.js";
|
|
8
|
+
import { collectSensitiveValues } from "../../core/redaction.js";
|
|
9
|
+
import { getHarness } from "../harnesses/index.js";
|
|
10
|
+
import { DEFAULT_AGENT_TIMEOUT_MS, DEFAULT_LLM_TIMEOUT_MS } from "./config.js";
|
|
11
|
+
import { resolveLlmModel, resolveModel } from "./model-aliases.js";
|
|
12
|
+
import { getBuiltinAgentProfile } from "./profiles.js";
|
|
13
|
+
function hasOwn(value, key) {
|
|
14
|
+
return Object.hasOwn(value, key);
|
|
15
|
+
}
|
|
16
|
+
function envName(reference) {
|
|
17
|
+
const match = /^\$(?:\{)?([A-Za-z_][A-Za-z0-9_]*)(?:\})?$/.exec(reference);
|
|
18
|
+
return match?.[1];
|
|
19
|
+
}
|
|
20
|
+
function selectedEngineName(config, layers, llmOnly) {
|
|
21
|
+
for (let index = layers.length - 1; index >= 0; index--) {
|
|
22
|
+
if (layers[index]?.engine !== undefined)
|
|
23
|
+
return layers[index]?.engine;
|
|
24
|
+
}
|
|
25
|
+
return llmOnly ? config.defaults?.llmEngine : config.defaults?.engine;
|
|
26
|
+
}
|
|
27
|
+
function resolveEngineConfig(name, config) {
|
|
28
|
+
const engine = config.engines?.[name];
|
|
29
|
+
if (!engine) {
|
|
30
|
+
throw new ConfigError(`Engine "${name}" is not configured.`, "INVALID_CONFIG_FILE");
|
|
31
|
+
}
|
|
32
|
+
return engine;
|
|
33
|
+
}
|
|
34
|
+
function resolveCredential(name, engine, config) {
|
|
35
|
+
if (engine.apiKey !== undefined) {
|
|
36
|
+
const explicit = envName(engine.apiKey);
|
|
37
|
+
if (!explicit)
|
|
38
|
+
throw new ConfigError(`Engine "${name}" has an invalid symbolic apiKey reference.`, "INVALID_CONFIG_FILE");
|
|
39
|
+
return { names: [explicit], required: true };
|
|
40
|
+
}
|
|
41
|
+
const specific = `AKM_ENGINE_${name.toUpperCase().replaceAll("-", "_")}_API_KEY`;
|
|
42
|
+
return config.defaults?.llmEngine === name
|
|
43
|
+
? { names: [specific, "AKM_LLM_API_KEY"], required: false }
|
|
44
|
+
: { names: [specific], required: false };
|
|
45
|
+
}
|
|
46
|
+
/** Collect materialized engine credentials for output and persistence redaction. */
|
|
47
|
+
export function collectEngineCredentialValues(config, envSource = process.env) {
|
|
48
|
+
const values = new Set();
|
|
49
|
+
for (const [name, engine] of Object.entries(config.engines ?? {})) {
|
|
50
|
+
if (engine.kind !== "llm")
|
|
51
|
+
continue;
|
|
52
|
+
for (const envVar of resolveCredential(name, engine, config)?.names ?? []) {
|
|
53
|
+
const value = envSource[envVar]?.trim();
|
|
54
|
+
if (value)
|
|
55
|
+
values.add(value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return collectSensitiveValues(values);
|
|
59
|
+
}
|
|
60
|
+
function effectiveTimeout(engine, layers, fallback) {
|
|
61
|
+
for (let index = layers.length - 1; index >= 0; index--) {
|
|
62
|
+
if (hasOwn(layers[index] ?? {}, "timeoutMs"))
|
|
63
|
+
return layers[index]?.timeoutMs ?? null;
|
|
64
|
+
}
|
|
65
|
+
return hasOwn(engine, "timeoutMs") ? (engine.timeoutMs ?? null) : fallback;
|
|
66
|
+
}
|
|
67
|
+
export function resolveLlmEngineUse(config, layers, options = {}) {
|
|
68
|
+
const name = selectedEngineName(config, layers, true);
|
|
69
|
+
if (!name) {
|
|
70
|
+
if (options.optional)
|
|
71
|
+
return undefined;
|
|
72
|
+
throw new ConfigError("No LLM engine is selected. Set defaults.llmEngine or specify engine.", "LLM_NOT_CONFIGURED");
|
|
73
|
+
}
|
|
74
|
+
const engine = resolveEngineConfig(name, config);
|
|
75
|
+
if (engine.kind !== "llm") {
|
|
76
|
+
throw new ConfigError(`Engine "${name}" is not an LLM engine.`, "INVALID_CONFIG_FILE");
|
|
77
|
+
}
|
|
78
|
+
let connection = {
|
|
79
|
+
provider: engine.provider,
|
|
80
|
+
endpoint: engine.endpoint,
|
|
81
|
+
model: engine.model,
|
|
82
|
+
temperature: engine.temperature,
|
|
83
|
+
maxTokens: engine.maxTokens,
|
|
84
|
+
supportsJsonSchema: engine.supportsJsonSchema,
|
|
85
|
+
extraParams: engine.extraParams,
|
|
86
|
+
contextLength: engine.contextLength,
|
|
87
|
+
enableThinking: engine.enableThinking,
|
|
88
|
+
};
|
|
89
|
+
for (const layer of layers) {
|
|
90
|
+
if (layer.llm)
|
|
91
|
+
connection = deepMergeConfig(connection, layer.llm);
|
|
92
|
+
if (layer.model !== undefined)
|
|
93
|
+
connection.model = layer.model;
|
|
94
|
+
}
|
|
95
|
+
for (const key of Object.keys(connection)) {
|
|
96
|
+
if (connection[key] === undefined)
|
|
97
|
+
delete connection[key];
|
|
98
|
+
}
|
|
99
|
+
connection.model = resolveLlmModel(connection.model, name, config.modelAliases);
|
|
100
|
+
return {
|
|
101
|
+
engine: name,
|
|
102
|
+
connection: connection,
|
|
103
|
+
credential: resolveCredential(name, engine, config),
|
|
104
|
+
timeoutMs: effectiveTimeout(engine, layers, DEFAULT_LLM_TIMEOUT_MS),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Read a resolved symbolic credential only at the runtime dispatch boundary. */
|
|
108
|
+
export function materializeLlmConnection(resolved) {
|
|
109
|
+
if (resolved.connection.extraParams !== undefined) {
|
|
110
|
+
const issue = validateExtraParams(resolved.connection.extraParams)[0];
|
|
111
|
+
if (issue) {
|
|
112
|
+
throw new ConfigError(formatExtraParamsIssue(`Engine "${resolved.engine}" extraParams`, issue), "INVALID_CONFIG_FILE");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
let apiKey;
|
|
116
|
+
for (const name of resolved.credential?.names ?? []) {
|
|
117
|
+
const candidate = process.env[name]?.trim();
|
|
118
|
+
if (candidate) {
|
|
119
|
+
apiKey = candidate;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (resolved.credential?.required && !apiKey) {
|
|
124
|
+
throw new ConfigError(`Required engine credential ${resolved.credential.names[0]} is not set.`, "INVALID_CONFIG_FILE");
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
...resolved.connection,
|
|
128
|
+
...(apiKey ? { apiKey } : {}),
|
|
129
|
+
timeoutMs: resolved.timeoutMs,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function lowerAgentEngine(name, engine, config) {
|
|
133
|
+
const harness = getHarness(engine.platform);
|
|
134
|
+
if (!harness?.capabilities.agentDispatch) {
|
|
135
|
+
throw new ConfigError(`Engine "${name}" names a platform that cannot dispatch agents: ${engine.platform}.`, "INVALID_CONFIG_FILE");
|
|
136
|
+
}
|
|
137
|
+
const platform = harness.id;
|
|
138
|
+
const sdk = platform === "opencode-sdk";
|
|
139
|
+
const builtin = getBuiltinAgentProfile(platform);
|
|
140
|
+
const profile = {
|
|
141
|
+
name,
|
|
142
|
+
platform,
|
|
143
|
+
bin: engine.bin ?? builtin?.bin ?? (sdk ? "opencode" : platform),
|
|
144
|
+
args: engine.args ?? builtin?.args ?? [],
|
|
145
|
+
stdio: "captured",
|
|
146
|
+
...(builtin?.env ? { env: builtin.env } : {}),
|
|
147
|
+
envPassthrough: builtin?.envPassthrough ?? [],
|
|
148
|
+
parseOutput: "text",
|
|
149
|
+
...(engine.workspace ? { workspace: path.resolve(engine.workspace) } : {}),
|
|
150
|
+
...(engine.model
|
|
151
|
+
? {
|
|
152
|
+
model: resolveModel(engine.model, platform, engine.modelAliases, config.modelAliases),
|
|
153
|
+
modelIsExact: true,
|
|
154
|
+
}
|
|
155
|
+
: {}),
|
|
156
|
+
...(engine.modelAliases ? { modelAliases: engine.modelAliases } : {}),
|
|
157
|
+
...(config.modelAliases ? { globalModelAliases: config.modelAliases } : {}),
|
|
158
|
+
};
|
|
159
|
+
if (!sdk) {
|
|
160
|
+
return {
|
|
161
|
+
kind: "agent",
|
|
162
|
+
engine: name,
|
|
163
|
+
profile,
|
|
164
|
+
timeoutMs: hasOwn(engine, "timeoutMs") ? (engine.timeoutMs ?? null) : DEFAULT_AGENT_TIMEOUT_MS,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const fallback = resolveLlmEngineUse(config, [{ engine: engine.llmEngine ?? config.defaults?.llmEngine }]);
|
|
168
|
+
if (!fallback)
|
|
169
|
+
throw new ConfigError(`SDK engine "${name}" has no fallback LLM engine.`, "LLM_NOT_CONFIGURED");
|
|
170
|
+
return {
|
|
171
|
+
kind: "sdk",
|
|
172
|
+
engine: name,
|
|
173
|
+
profile,
|
|
174
|
+
fallbackConnection: fallback.connection,
|
|
175
|
+
...(fallback.credential ? { fallbackCredential: fallback.credential } : {}),
|
|
176
|
+
fallbackTimeoutMs: fallback.timeoutMs,
|
|
177
|
+
timeoutMs: hasOwn(engine, "timeoutMs") ? (engine.timeoutMs ?? null) : fallback.timeoutMs,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Lower a named engine through the canonical harness platform. */
|
|
181
|
+
export function resolveEngine(name, config) {
|
|
182
|
+
const engine = resolveEngineConfig(name, config);
|
|
183
|
+
if (engine.kind === "llm") {
|
|
184
|
+
const resolved = resolveLlmEngineUse(config, [{ engine: name }]);
|
|
185
|
+
if (!resolved)
|
|
186
|
+
throw new ConfigError(`LLM engine "${name}" could not be resolved.`, "LLM_NOT_CONFIGURED");
|
|
187
|
+
return {
|
|
188
|
+
kind: "llm",
|
|
189
|
+
engine: name,
|
|
190
|
+
connection: resolved.connection,
|
|
191
|
+
...(resolved.credential ? { credential: resolved.credential } : {}),
|
|
192
|
+
timeoutMs: resolved.timeoutMs,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return lowerAgentEngine(name, engine, config);
|
|
196
|
+
}
|
|
197
|
+
export function resolveDefaultEngine(config) {
|
|
198
|
+
const name = config.defaults?.engine;
|
|
199
|
+
if (!name)
|
|
200
|
+
throw new ConfigError("No default engine is configured.", "INVALID_CONFIG_FILE");
|
|
201
|
+
return resolveEngine(name, config);
|
|
202
|
+
}
|