akm-cli 0.9.0-rc.1 → 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 +223 -9
- 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/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/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/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/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 +358 -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/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 +116 -1
- 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/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -52
- 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 +7 -2
- package/dist/integrations/agent/profiles.js +6 -99
- package/dist/integrations/agent/runner-dispatch.js +76 -13
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +4 -6
- package/dist/integrations/harnesses/aider/agent-builder.js +2 -3
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -3
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +0 -2
- package/dist/integrations/harnesses/codex/agent-builder.js +2 -3
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +2 -3
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +2 -3
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +1 -24
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +0 -6
- package/dist/integrations/harnesses/opencode-sdk/harness.js +1 -6
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
- package/dist/integrations/harnesses/openhands/agent-builder.js +2 -3
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +2 -3
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -32
- package/dist/integrations/lockfile.js +32 -21
- 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/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/helpers.js +3 -1
- package/dist/schemas/akm-config.json +11013 -8600
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +57 -13
- package/dist/scripts/migrate-storage.js +8591 -509
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8623 -484
- 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 +9 -8
- 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/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/workflow-program-template.yaml +3 -3
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +65 -13
- package/dist/workflows/exec/brief.js +18 -24
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +124 -65
- package/dist/workflows/exec/report.js +93 -33
- package/dist/workflows/exec/run-workflow.js +38 -25
- package/dist/workflows/exec/scheduler.js +12 -41
- package/dist/workflows/exec/step-work.js +91 -35
- package/dist/workflows/ir/compile.js +13 -26
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/plan-hash.js +40 -5
- package/dist/workflows/ir/schema.js +542 -1
- package/dist/workflows/parser.js +7 -0
- package/dist/workflows/program/parser.js +132 -23
- package/dist/workflows/program/project.js +3 -4
- package/dist/workflows/program/schema.js +2 -2
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +54 -47
- package/dist/workflows/runtime/workflow-asset-loader.js +0 -22
- package/dist/workflows/validator.js +25 -0
- package/docs/data-and-telemetry.md +2 -2
- 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/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
package/dist/workflows/db.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { acquireMaintenanceActivitySync } from "../core/maintenance-barrier.js";
|
|
7
|
+
import { ensureMigrationBackup, getMigrationBackupDir } from "../core/migration-backup.js";
|
|
6
8
|
import { getWorkflowDbPath } from "../core/paths.js";
|
|
7
9
|
import { openDatabase } from "../storage/database.js";
|
|
8
10
|
import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
|
|
@@ -42,18 +44,55 @@ import { applyStandardPragmas } from "../storage/sqlite-pragmas.js";
|
|
|
42
44
|
*/
|
|
43
45
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
44
46
|
export function openWorkflowDatabase(dbPath = getWorkflowDbPath()) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
const isCanonical = path.resolve(dbPath) === path.resolve(getWorkflowDbPath());
|
|
48
|
+
const releaseActivity = isCanonical ? acquireMaintenanceActivitySync("workflow-db") : undefined;
|
|
49
|
+
let db;
|
|
50
|
+
try {
|
|
51
|
+
// Preserve an originally absent workflow.db in the recovery manifest. SQLite
|
|
52
|
+
// creates the file at open time, so the cutover bundle must exist first.
|
|
53
|
+
if (isCanonical && !fs.existsSync(getMigrationBackupDir()))
|
|
54
|
+
ensureMigrationBackup();
|
|
55
|
+
const dir = path.dirname(dbPath);
|
|
56
|
+
if (!fs.existsSync(dir)) {
|
|
57
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
db = openDatabase(dbPath);
|
|
60
|
+
// #589: 30 s busy timeout, matching index.db / state.db. Without it the
|
|
61
|
+
// default is 0 ms, so any concurrent writer fails immediately with
|
|
62
|
+
// SQLITE_BUSY. #628: journal_mode is configurable via AKM_SQLITE_JOURNAL_MODE.
|
|
63
|
+
applyStandardPragmas(db, { dataDir: dir });
|
|
64
|
+
ensureBaseSchema(db);
|
|
65
|
+
runMigrations(db, { ensureCutoverBackup: isCanonical });
|
|
66
|
+
if (!releaseActivity)
|
|
67
|
+
return db;
|
|
68
|
+
const openedDb = db;
|
|
69
|
+
let closed = false;
|
|
70
|
+
return {
|
|
71
|
+
prepare: openedDb.prepare.bind(openedDb),
|
|
72
|
+
exec: openedDb.exec.bind(openedDb),
|
|
73
|
+
run: openedDb.run.bind(openedDb),
|
|
74
|
+
transaction: openedDb.transaction.bind(openedDb),
|
|
75
|
+
get inTransaction() {
|
|
76
|
+
return openedDb.inTransaction;
|
|
77
|
+
},
|
|
78
|
+
close() {
|
|
79
|
+
if (closed)
|
|
80
|
+
return;
|
|
81
|
+
closed = true;
|
|
82
|
+
try {
|
|
83
|
+
openedDb.close();
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
releaseActivity();
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
db?.close();
|
|
93
|
+
releaseActivity?.();
|
|
94
|
+
throw error;
|
|
48
95
|
}
|
|
49
|
-
const db = openDatabase(dbPath);
|
|
50
|
-
// #589: 30 s busy timeout, matching index.db / state.db. Without it the
|
|
51
|
-
// default is 0 ms, so any concurrent writer fails immediately with
|
|
52
|
-
// SQLITE_BUSY. #628: journal_mode is configurable via AKM_SQLITE_JOURNAL_MODE.
|
|
53
|
-
applyStandardPragmas(db, { dataDir: dir });
|
|
54
|
-
ensureBaseSchema(db);
|
|
55
|
-
runMigrations(db);
|
|
56
|
-
return db;
|
|
57
96
|
}
|
|
58
97
|
export function closeWorkflowDatabase(db) {
|
|
59
98
|
db.close();
|
|
@@ -300,6 +339,13 @@ const MIGRATIONS = [
|
|
|
300
339
|
ALTER TABLE workflow_run_units ADD COLUMN claim_expires_at TEXT;
|
|
301
340
|
`,
|
|
302
341
|
},
|
|
342
|
+
{
|
|
343
|
+
id: "010-ir-v3-engine",
|
|
344
|
+
up: `
|
|
345
|
+
ALTER TABLE workflow_runs ADD COLUMN plan_ir_version INTEGER;
|
|
346
|
+
ALTER TABLE workflow_run_units ADD COLUMN engine TEXT;
|
|
347
|
+
`,
|
|
348
|
+
},
|
|
303
349
|
];
|
|
304
350
|
/**
|
|
305
351
|
* Stable id of the scope_key migration. Exported for bootstrap detection and
|
|
@@ -345,6 +391,12 @@ function bootstrapPreVersioningDb(db) {
|
|
|
345
391
|
*
|
|
346
392
|
* Called automatically by {@link openWorkflowDatabase}.
|
|
347
393
|
*/
|
|
348
|
-
export function runMigrations(db) {
|
|
349
|
-
runSqliteMigrations(db, MIGRATIONS, {
|
|
394
|
+
export function runMigrations(db, options) {
|
|
395
|
+
runSqliteMigrations(db, MIGRATIONS, {
|
|
396
|
+
bootstrap: bootstrapPreVersioningDb,
|
|
397
|
+
beforeMigration(migration) {
|
|
398
|
+
if (options?.ensureCutoverBackup && migration.id === "010-ir-v3-engine")
|
|
399
|
+
ensureMigrationBackup();
|
|
400
|
+
},
|
|
401
|
+
});
|
|
350
402
|
}
|
|
@@ -35,10 +35,11 @@ import { NotFoundError, UsageError } from "../../core/errors.js";
|
|
|
35
35
|
import { withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
|
|
36
36
|
import { getCurrentWorkflowScopeKey } from "../authoring/scope-key.js";
|
|
37
37
|
import { assertRunParamsSatisfyPlan } from "../ir/params.js";
|
|
38
|
+
import { frozenStepRows, requireExecutableWorkflowPlan } from "../runtime/plan-classifier.js";
|
|
38
39
|
import { snapshotRunForDriver } from "../runtime/runs.js";
|
|
39
40
|
import { evaluateStaleUnits } from "../runtime/unit-checkin.js";
|
|
40
41
|
import { detectSecretShapedParams } from "./param-secrets.js";
|
|
41
|
-
import { activeGateLoop, assertJournaledRouteSelectionsValid, computeStepWorkList, evaluateRoute, GATE_EVALUATION_PHASE, isWorkListFullyTerminal,
|
|
42
|
+
import { activeGateLoop, assertJournaledRouteSelectionsValid, computeStepWorkList, evaluateRoute, GATE_EVALUATION_PHASE, isWorkListFullyTerminal, recoverGateFeedback, selectUnitAttemptRow, stepOutputsFromEvidence, } from "./step-work.js";
|
|
42
43
|
const EMPTY_WORK_LIST = { isFanOut: false, reducer: null, itemCount: 0, units: [] };
|
|
43
44
|
// ── Entry point ──────────────────────────────────────────────────────────────
|
|
44
45
|
/**
|
|
@@ -54,8 +55,6 @@ export async function buildWorkflowBrief(target) {
|
|
|
54
55
|
// the active step between the spine read and the unit-journal read. A bare run
|
|
55
56
|
// id never auto-starts (we resolved to a concrete id above).
|
|
56
57
|
const { next, run: runRow, units } = await snapshotRunForDriver(runId);
|
|
57
|
-
const planJson = runRow.plan_json;
|
|
58
|
-
const planHash = runRow.plan_hash;
|
|
59
58
|
const leaseHolder = runRow.engine_lease_holder;
|
|
60
59
|
const leaseUntil = runRow.engine_lease_until;
|
|
61
60
|
const run = {
|
|
@@ -144,7 +143,7 @@ export async function buildWorkflowBrief(target) {
|
|
|
144
143
|
// Load the FROZEN plan the engine executes (migration 006). A legacy run
|
|
145
144
|
// (NULL plan_json) has no plan for brief to read — point at engine-driven
|
|
146
145
|
// mode, which still handles pre-006 runs by compiling from the asset.
|
|
147
|
-
const plan =
|
|
146
|
+
const plan = requireExecutableWorkflowPlan(runRow);
|
|
148
147
|
// Reviewer #12: the journaled params row must still satisfy the frozen param
|
|
149
148
|
// schemas — a violation is post-start corruption, loud on the brief surface
|
|
150
149
|
// too (mirrors the frozen-plan hash check and the tampered-params divergence).
|
|
@@ -171,13 +170,16 @@ export async function buildWorkflowBrief(target) {
|
|
|
171
170
|
const gateFeedback = recoverGateFeedback(units, stepState.id, gateLoop);
|
|
172
171
|
const isRouteOnly = !!stepPlan.route && !stepPlan.root;
|
|
173
172
|
const kind = isRouteOnly ? "route" : stepPlan.route ? "execute-and-route" : "execute";
|
|
174
|
-
const criteria =
|
|
173
|
+
const criteria = stepPlan.gate.criteria;
|
|
174
|
+
const instructions = frozenStepRows(plan).find((step) => step.stepId === stepState.id)?.instructions;
|
|
175
|
+
if (instructions === undefined)
|
|
176
|
+
throw new UsageError(`Step "${stepState.id}" has no frozen instructions.`);
|
|
175
177
|
const step = {
|
|
176
178
|
stepId: stepState.id,
|
|
177
|
-
title:
|
|
178
|
-
sequenceIndex:
|
|
179
|
+
title: stepPlan.title,
|
|
180
|
+
sequenceIndex: stepPlan.sequenceIndex,
|
|
179
181
|
kind,
|
|
180
|
-
instructions
|
|
182
|
+
instructions,
|
|
181
183
|
gate: {
|
|
182
184
|
criteria,
|
|
183
185
|
maxLoops: Math.max(1, stepPlan.gate.maxLoops ?? 1),
|
|
@@ -209,6 +211,7 @@ export async function buildWorkflowBrief(target) {
|
|
|
209
211
|
runId: run.id,
|
|
210
212
|
params: run.params,
|
|
211
213
|
stepOutputs,
|
|
214
|
+
engines: plan.execution.engines,
|
|
212
215
|
gateLoop,
|
|
213
216
|
...(gateFeedback ? { gateFeedback } : {}),
|
|
214
217
|
});
|
|
@@ -315,15 +318,19 @@ function makeSpineToken(runId, stepId, gateLoop, watermark) {
|
|
|
315
318
|
}
|
|
316
319
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
317
320
|
function toBriefUnit(runId, unit, stepId, journaled, ctx) {
|
|
321
|
+
if (!unit.engine || !unit.invocation) {
|
|
322
|
+
throw new UsageError(`Unit "${unit.unitId}" has no complete frozen engine attribution.`);
|
|
323
|
+
}
|
|
318
324
|
const action = deriveUnitAction(unit, journaled, ctx);
|
|
319
325
|
const report = reportCommandForAction(runId, unit, stepId, action, journaled?.claim_holder ?? null);
|
|
320
326
|
return {
|
|
321
327
|
unitId: unit.unitId,
|
|
322
328
|
nodeId: unit.nodeId,
|
|
323
329
|
index: unit.index,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
330
|
+
engine: unit.invocation.engine,
|
|
331
|
+
runtimeKind: unit.runner,
|
|
332
|
+
platform: unit.engine.kind === "agent" ? unit.engine.platform : null,
|
|
333
|
+
model: unit.invocation.model,
|
|
327
334
|
timeoutMs: unit.timeoutMs,
|
|
328
335
|
...(unit.schema ? { outputSchema: unit.schema } : {}),
|
|
329
336
|
// Env asset REF names only — brief never resolves bindings, so no secret
|
|
@@ -442,19 +449,6 @@ function buildMessage(step, workList, route, gateLoop, settleState) {
|
|
|
442
449
|
}
|
|
443
450
|
return `Active step "${step.stepId}" expects ${n} unit(s)${loopNote}. Execute them, then report each result.`;
|
|
444
451
|
}
|
|
445
|
-
/**
|
|
446
|
-
* brief-specific frozen-plan loader: unlike the engine's loader, a NULL
|
|
447
|
-
* plan_json is a hard, actionable error rather than a warn-and-compile — brief
|
|
448
|
-
* describes the frozen plan the engine executes, and a legacy run has none.
|
|
449
|
-
*/
|
|
450
|
-
function loadFrozenPlanForBrief(runId, planJson, planHash) {
|
|
451
|
-
if (!planJson) {
|
|
452
|
-
throw new UsageError(`Workflow run ${runId} predates frozen plans (no plan_json on the run row) and cannot be described by ` +
|
|
453
|
-
`\`akm workflow brief\`. Drive it with engine-driven mode instead: \`akm workflow run ${runId}\` ` +
|
|
454
|
-
`(which compiles a legacy run's plan from the live asset).`);
|
|
455
|
-
}
|
|
456
|
-
return parseFrozenPlan(runId, planJson, planHash);
|
|
457
|
-
}
|
|
458
452
|
/**
|
|
459
453
|
* Resolve `target` to a concrete run id WITHOUT starting anything. A run id
|
|
460
454
|
* resolves directly; a workflow ref resolves to its active run in the current
|
|
@@ -0,0 +1,47 @@
|
|
|
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 { deepMergeConfig } from "../../core/config/deep-merge.js";
|
|
5
|
+
import { ConfigError } from "../../core/errors.js";
|
|
6
|
+
/** Build a gate judge from a v3 catalog entry without consulting live config. */
|
|
7
|
+
export function frozenSummaryJudge(plan, invocation) {
|
|
8
|
+
if (!invocation)
|
|
9
|
+
return null;
|
|
10
|
+
const engine = plan.execution?.engines[invocation.engine];
|
|
11
|
+
if (!engine || engine.kind !== "llm")
|
|
12
|
+
throw new ConfigError(`Frozen gate engine "${invocation.engine}" is unavailable.`, "INVALID_CONFIG_FILE");
|
|
13
|
+
return async ({ system, user }) => {
|
|
14
|
+
const { chatCompletion } = await import("../../llm/client.js");
|
|
15
|
+
return chatCompletion(materialize(engine, invocation), [
|
|
16
|
+
{ role: "system", content: system },
|
|
17
|
+
{ role: "user", content: user },
|
|
18
|
+
], {
|
|
19
|
+
timeoutMs: invocation.timeoutMs,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function materialize(engine, invocation) {
|
|
24
|
+
let apiKey;
|
|
25
|
+
for (const name of engine.credential?.names ?? []) {
|
|
26
|
+
const value = process.env[name]?.trim();
|
|
27
|
+
if (value) {
|
|
28
|
+
apiKey = value;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (engine.credential?.required && !apiKey)
|
|
33
|
+
throw new ConfigError(`Required engine credential ${engine.credential.names[0]} is not set.`, "INVALID_CONFIG_FILE");
|
|
34
|
+
const base = {
|
|
35
|
+
provider: engine.provider,
|
|
36
|
+
endpoint: engine.endpoint,
|
|
37
|
+
model: invocation.model ?? engine.model,
|
|
38
|
+
...(engine.temperature !== undefined ? { temperature: engine.temperature } : {}),
|
|
39
|
+
...(engine.maxTokens !== undefined ? { maxTokens: engine.maxTokens } : {}),
|
|
40
|
+
...(engine.supportsJsonSchema !== undefined ? { supportsJsonSchema: engine.supportsJsonSchema } : {}),
|
|
41
|
+
...(engine.extraParams ? { extraParams: engine.extraParams } : {}),
|
|
42
|
+
...(engine.contextLength !== undefined ? { contextLength: engine.contextLength } : {}),
|
|
43
|
+
...(engine.enableThinking !== undefined ? { enableThinking: engine.enableThinking } : {}),
|
|
44
|
+
...(apiKey ? { apiKey } : {}),
|
|
45
|
+
};
|
|
46
|
+
return (invocation.llm ? deepMergeConfig(base, invocation.llm) : base);
|
|
47
|
+
}
|
|
@@ -129,9 +129,11 @@
|
|
|
129
129
|
* - This module NEVER writes step rows: advancing the gated spine is the
|
|
130
130
|
* engine loop's job (`run-workflow.ts`) via `completeWorkflowStep`.
|
|
131
131
|
*/
|
|
132
|
+
import { deepMergeConfig } from "../../core/config/deep-merge.js";
|
|
132
133
|
import { ConfigError } from "../../core/errors.js";
|
|
133
134
|
import { appendEvent } from "../../core/events.js";
|
|
134
135
|
import { validateJsonSchemaSubset } from "../../core/json-schema.js";
|
|
136
|
+
import { collectSensitiveValues, isEnvPassthroughValueSafeToExpose, redactSensitiveValue } from "../../core/redaction.js";
|
|
135
137
|
import { runStructured } from "../../core/structured.js";
|
|
136
138
|
import { warn } from "../../core/warn.js";
|
|
137
139
|
import { withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
|
|
@@ -254,6 +256,7 @@ export async function executeStepPlan(plan, ctx) {
|
|
|
254
256
|
runId: ctx.runId,
|
|
255
257
|
params: ctx.params,
|
|
256
258
|
stepOutputs: stepOutputsFromEvidence(ctx.evidence),
|
|
259
|
+
engines: ctx.engines ?? {},
|
|
257
260
|
...(ctx.gateLoop !== undefined ? { gateLoop: ctx.gateLoop } : {}),
|
|
258
261
|
...(ctx.gateFeedback ? { gateFeedback: ctx.gateFeedback } : {}),
|
|
259
262
|
});
|
|
@@ -310,7 +313,8 @@ export async function executeStepPlan(plan, ctx) {
|
|
|
310
313
|
// attempt in dispatchJournaledAttempt.
|
|
311
314
|
let worktreeBase;
|
|
312
315
|
if (willDispatch && template.isolation === "worktree") {
|
|
313
|
-
|
|
316
|
+
const engine = template.invocation ? ctx.engines?.[template.invocation.engine] : undefined;
|
|
317
|
+
if (engine?.kind === "llm") {
|
|
314
318
|
return failedStep(dispatched, `Step "${plan.stepId}" declares isolation: worktree on an llm unit — the llm runner has no ` +
|
|
315
319
|
`working directory to isolate. Use the agent or sdk runner for worktree-isolated units.`);
|
|
316
320
|
}
|
|
@@ -357,6 +361,12 @@ export async function executeStepPlan(plan, ctx) {
|
|
|
357
361
|
...(onExceeded ? { onExceeded } : {}),
|
|
358
362
|
});
|
|
359
363
|
let outcomes;
|
|
364
|
+
const selectedEngine = template.invocation ? ctx.engines?.[template.invocation.engine] : undefined;
|
|
365
|
+
const selectedLlmEngine = selectedEngine?.kind === "llm"
|
|
366
|
+
? selectedEngine
|
|
367
|
+
: selectedEngine?.kind === "agent" && selectedEngine.fallbackLlmEngine
|
|
368
|
+
? ctx.engines?.[selectedEngine.fallbackLlmEngine]
|
|
369
|
+
: undefined;
|
|
360
370
|
try {
|
|
361
371
|
outcomes = await scheduleUnits(workUnits, (workUnit) => runUnit({
|
|
362
372
|
plan,
|
|
@@ -372,6 +382,7 @@ export async function executeStepPlan(plan, ctx) {
|
|
|
372
382
|
concurrency: workList.list.concurrency,
|
|
373
383
|
signal,
|
|
374
384
|
maxConcurrency: ctx.maxConcurrency,
|
|
385
|
+
...(selectedLlmEngine?.kind === "llm" ? { llmConcurrency: selectedLlmEngine.concurrency } : {}),
|
|
375
386
|
});
|
|
376
387
|
}
|
|
377
388
|
finally {
|
|
@@ -427,22 +438,32 @@ async function runUnit(input) {
|
|
|
427
438
|
if (!workUnit.resolved.ok) {
|
|
428
439
|
return { unitId, ok: false, failureReason: "expression_error", error: workUnit.resolved.error };
|
|
429
440
|
}
|
|
441
|
+
if (!workUnit.engine || !workUnit.invocation) {
|
|
442
|
+
return {
|
|
443
|
+
unitId,
|
|
444
|
+
ok: false,
|
|
445
|
+
failureReason: "dispatch_error",
|
|
446
|
+
error: `unit "${unitId}" has no frozen engine snapshot and cannot be dispatched`,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
430
449
|
// The prompt (and therefore the input hash) was built once with the BASE
|
|
431
450
|
// unit id by computeStepWorkList: a retry re-dispatches the SAME input, the
|
|
432
451
|
// `~r<n>` suffix is journal bookkeeping only.
|
|
433
452
|
const { prompt, inputHash } = workUnit.resolved;
|
|
453
|
+
const sensitiveValues = collectWorkflowDispatchSensitiveValues(workUnit, env);
|
|
434
454
|
const request = {
|
|
435
455
|
runId: ctx.runId,
|
|
436
456
|
stepId: plan.stepId,
|
|
437
457
|
unitId,
|
|
438
458
|
nodeId: workUnit.nodeId,
|
|
439
459
|
prompt,
|
|
440
|
-
|
|
441
|
-
...(workUnit.
|
|
442
|
-
|
|
460
|
+
engine: workUnit.engine,
|
|
461
|
+
...(workUnit.fallbackEngine ? { fallbackEngine: workUnit.fallbackEngine } : {}),
|
|
462
|
+
invocation: workUnit.invocation,
|
|
443
463
|
timeoutMs: workUnit.timeoutMs,
|
|
444
464
|
...(workUnit.schema ? { schema: workUnit.schema } : {}),
|
|
445
465
|
...(env ? { env } : {}),
|
|
466
|
+
...(sensitiveValues.length > 0 ? { sensitiveValues } : {}),
|
|
446
467
|
...(input.signal ? { signal: input.signal } : {}),
|
|
447
468
|
};
|
|
448
469
|
// Bounded retry (IR v2 failure policy): attempt 0 journals under the base
|
|
@@ -560,7 +581,8 @@ async function dispatchJournaledAttempt(input) {
|
|
|
560
581
|
parentUnitId: workUnit.isFanOut ? `${plan.stepId}.map` : null,
|
|
561
582
|
phase: null,
|
|
562
583
|
runner: workUnit.runner,
|
|
563
|
-
|
|
584
|
+
engine: request.engine.name,
|
|
585
|
+
model: request.invocation.model,
|
|
564
586
|
inputHash,
|
|
565
587
|
worktreePath: worktreePath ?? null,
|
|
566
588
|
startedAt: new Date().toISOString(),
|
|
@@ -573,7 +595,7 @@ async function dispatchJournaledAttempt(input) {
|
|
|
573
595
|
ref: ctx.workflowRef,
|
|
574
596
|
metadata: { runId: ctx.runId, stepId: plan.stepId, unitId: attemptId },
|
|
575
597
|
});
|
|
576
|
-
const outcome = await dispatchUnit(request, dispatcher);
|
|
598
|
+
const outcome = redactUnitOutcome(await dispatchUnit(request, dispatcher), request.sensitiveValues ?? []);
|
|
577
599
|
await enqueueUnitWrite(async () => {
|
|
578
600
|
await withWorkflowRunsRepo((repo) => repo.finishUnit({
|
|
579
601
|
runId: ctx.runId,
|
|
@@ -729,8 +751,8 @@ async function resolveEnvBindings(refs) {
|
|
|
729
751
|
* agent → `executeRunner` → `runAgent` (per-harness AgentCommandBuilder)
|
|
730
752
|
* sdk → `executeRunner` → `runOpencodeSdk`
|
|
731
753
|
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
754
|
+
* Every v3 invocation names a frozen engine; no live profile/default fallback
|
|
755
|
+
* is consulted during dispatch.
|
|
734
756
|
*/
|
|
735
757
|
/**
|
|
736
758
|
* Build the platform-agnostic {@link import("../../integrations/agent/builder-shared.js").AgentDispatchRequest}
|
|
@@ -756,15 +778,14 @@ async function resolveEnvBindings(refs) {
|
|
|
756
778
|
export function buildAgentDispatchRequest(request, prompt) {
|
|
757
779
|
return {
|
|
758
780
|
prompt,
|
|
759
|
-
...(request.model ? { model: request.model } : {}),
|
|
781
|
+
...(request.invocation.model ? { model: request.invocation.model } : {}),
|
|
782
|
+
...(request.invocation.model ? { modelIsExact: true } : {}),
|
|
760
783
|
...(request.schema ? { schema: request.schema } : {}),
|
|
761
784
|
};
|
|
762
785
|
}
|
|
763
786
|
export const defaultUnitDispatcher = async (request, feedback) => {
|
|
764
|
-
const { loadConfig } = await import("../../core/config/config.js");
|
|
765
|
-
const config = loadConfig();
|
|
766
787
|
const prompt = feedback ? `${request.prompt}\n\n${feedback}` : request.prompt;
|
|
767
|
-
const resolved =
|
|
788
|
+
const resolved = frozenUnitRunner(request);
|
|
768
789
|
// `env` bindings can only reach a child process. The agent (CLI) runner
|
|
769
790
|
// spawns one per call, and the sdk runner now injects them for real via the
|
|
770
791
|
// env-keyed opencode server registry (sdk-runner.ts module doc, open seam
|
|
@@ -794,15 +815,10 @@ export const defaultUnitDispatcher = async (request, feedback) => {
|
|
|
794
815
|
}
|
|
795
816
|
if (resolved.kind === "llm") {
|
|
796
817
|
const { chatCompletion, LlmCallError } = await import("../../llm/client.js");
|
|
797
|
-
const
|
|
798
|
-
const connection = request.model
|
|
799
|
-
? { ...resolved.connection, model: resolveModel(request.model, "llm", undefined, config.modelAliases) }
|
|
800
|
-
: resolved.connection;
|
|
818
|
+
const connection = resolved.connection;
|
|
801
819
|
try {
|
|
802
820
|
const text = await chatCompletion(connection, [{ role: "user", content: prompt }], {
|
|
803
|
-
|
|
804
|
-
// 32-bit delay (setTimeout's ceiling, ~24.8 days ≈ unbounded here).
|
|
805
|
-
timeoutMs: request.timeoutMs === null ? 2 ** 31 - 1 : request.timeoutMs,
|
|
821
|
+
timeoutMs: request.timeoutMs,
|
|
806
822
|
...(request.signal ? { signal: request.signal } : {}),
|
|
807
823
|
// Native structured output where the connection supports it; the
|
|
808
824
|
// executor's subset validator still runs downstream either way.
|
|
@@ -821,8 +837,16 @@ export const defaultUnitDispatcher = async (request, feedback) => {
|
|
|
821
837
|
}
|
|
822
838
|
}
|
|
823
839
|
const { executeRunner } = await import("../../integrations/agent/runner-dispatch.js");
|
|
824
|
-
const profile = request.
|
|
825
|
-
|
|
840
|
+
const profile = request.invocation.model
|
|
841
|
+
? { ...resolved.profile, model: request.invocation.model, modelIsExact: true }
|
|
842
|
+
: resolved.profile;
|
|
843
|
+
const result = await executeRunner(resolved.kind === "sdk"
|
|
844
|
+
? {
|
|
845
|
+
kind: "sdk",
|
|
846
|
+
profile,
|
|
847
|
+
...(resolved.fallbackConnection ? { fallbackConnection: resolved.fallbackConnection } : {}),
|
|
848
|
+
}
|
|
849
|
+
: { kind: "agent", profile }, prompt, {
|
|
826
850
|
stdio: "captured",
|
|
827
851
|
parseOutput: "text",
|
|
828
852
|
timeoutMs: request.timeoutMs,
|
|
@@ -863,6 +887,36 @@ export const defaultUnitDispatcher = async (request, feedback) => {
|
|
|
863
887
|
...(result.usage ? { usage: result.usage } : {}),
|
|
864
888
|
};
|
|
865
889
|
};
|
|
890
|
+
function collectWorkflowDispatchSensitiveValues(workUnit, env) {
|
|
891
|
+
const values = new Set(Object.values(env ?? {}));
|
|
892
|
+
const addCredential = (engine) => {
|
|
893
|
+
if (!engine)
|
|
894
|
+
return;
|
|
895
|
+
if (engine.kind === "llm") {
|
|
896
|
+
for (const name of engine.credential?.names ?? []) {
|
|
897
|
+
const value = process.env[name]?.trim();
|
|
898
|
+
if (value)
|
|
899
|
+
values.add(value);
|
|
900
|
+
}
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
for (const name of engine.envPassthrough) {
|
|
904
|
+
const value = process.env[name];
|
|
905
|
+
if (!isEnvPassthroughValueSafeToExpose(name, value) && value)
|
|
906
|
+
values.add(value);
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
addCredential(workUnit.engine);
|
|
910
|
+
addCredential(workUnit.fallbackEngine);
|
|
911
|
+
return collectSensitiveValues(values);
|
|
912
|
+
}
|
|
913
|
+
function redactUnitOutcome(outcome, sensitiveValues) {
|
|
914
|
+
const redacted = redactSensitiveValue(outcome, sensitiveValues);
|
|
915
|
+
if (outcome.failureReason !== undefined && redacted.failureReason !== outcome.failureReason) {
|
|
916
|
+
redacted.failureReason = "reported_failure";
|
|
917
|
+
}
|
|
918
|
+
return redacted;
|
|
919
|
+
}
|
|
866
920
|
/**
|
|
867
921
|
* Map a typed {@link import("../../llm/client.js").LlmCallErrorCode} into the
|
|
868
922
|
* persisted `AgentFailureReason` taxonomy (agent/spawn.ts) — the ONLY
|
|
@@ -897,58 +951,63 @@ export function llmFailureReasonFor(code) {
|
|
|
897
951
|
}
|
|
898
952
|
}
|
|
899
953
|
/**
|
|
900
|
-
* Resolve the harness `resultExtractor`
|
|
901
|
-
*
|
|
902
|
-
* `commandBuilder` wins, else its name (with the `-headless` builtin-variant
|
|
903
|
-
* suffix stripped, same derivation as BUILTIN_BUILDERS). Unknown/custom
|
|
904
|
-
* platforms resolve to no extractor — raw stdout passes through unchanged.
|
|
954
|
+
* Resolve the harness `resultExtractor` from the canonical platform frozen
|
|
955
|
+
* from the named engine. Unknown platforms pass raw stdout through unchanged.
|
|
905
956
|
*/
|
|
906
957
|
async function resolveHarnessExtractor(profile) {
|
|
907
958
|
const { getHarness } = await import("../../integrations/harnesses/index.js");
|
|
908
|
-
const
|
|
909
|
-
const harness = getHarness(platform) ?? getHarness(platform.replace(/-headless$/, ""));
|
|
959
|
+
const harness = getHarness(profile.platform ?? profile.name);
|
|
910
960
|
return harness?.resultExtractor;
|
|
911
961
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
: await requireDefaultLlm(config, request);
|
|
918
|
-
if (!connection) {
|
|
919
|
-
throw new ConfigError(`Workflow unit "${request.unitId}" wants llm profile "${request.profile}", which is not in profiles.llm.`, "LLM_NOT_CONFIGURED");
|
|
920
|
-
}
|
|
921
|
-
return { kind: "llm", connection };
|
|
922
|
-
}
|
|
923
|
-
const profileName = request.profile ?? config.defaults?.agent;
|
|
924
|
-
if (profileName) {
|
|
925
|
-
const { resolveProfileFromConfig } = await import("../../integrations/agent/config.js");
|
|
926
|
-
const profile = resolveProfileFromConfig(profileName, config);
|
|
927
|
-
if (!profile) {
|
|
928
|
-
throw new ConfigError(`Workflow unit "${request.unitId}" wants agent profile "${profileName}", which cannot be resolved. ` +
|
|
929
|
-
`Define profiles.agent."${profileName}" or set defaults.agent.`, "INVALID_CONFIG_FILE");
|
|
930
|
-
}
|
|
931
|
-
const inferred = profile.sdkMode ? "sdk" : "agent";
|
|
932
|
-
if (requested !== "inherit" && requested !== inferred) {
|
|
933
|
-
throw new ConfigError(`Workflow unit "${request.unitId}" declares runner "${requested}" but profile "${profileName}" is ${profile.sdkMode ? "an opencode-sdk (sdk) profile" : "a CLI (agent) profile"}.`, "INVALID_CONFIG_FILE");
|
|
934
|
-
}
|
|
935
|
-
return { kind: inferred, profile };
|
|
936
|
-
}
|
|
937
|
-
if (requested === "inherit") {
|
|
938
|
-
const connection = await requireDefaultLlm(config, request, /* soft */ true);
|
|
939
|
-
if (connection)
|
|
940
|
-
return { kind: "llm", connection };
|
|
962
|
+
/** Reconstruct the existing RunnerSpec substrate from the frozen allowlist only. */
|
|
963
|
+
function frozenUnitRunner(request) {
|
|
964
|
+
const snapshot = request.engine;
|
|
965
|
+
if (snapshot.kind === "llm") {
|
|
966
|
+
return { kind: "llm", connection: materializeFrozenLlm(snapshot, request.invocation) };
|
|
941
967
|
}
|
|
942
|
-
|
|
943
|
-
|
|
968
|
+
const profile = {
|
|
969
|
+
name: snapshot.name,
|
|
970
|
+
platform: snapshot.platform,
|
|
971
|
+
bin: snapshot.bin,
|
|
972
|
+
args: snapshot.args,
|
|
973
|
+
stdio: "captured",
|
|
974
|
+
envPassthrough: snapshot.envPassthrough,
|
|
975
|
+
parseOutput: "text",
|
|
976
|
+
...(snapshot.workspace ? { workspace: snapshot.workspace } : {}),
|
|
977
|
+
...(request.invocation?.model ? { model: request.invocation.model } : {}),
|
|
978
|
+
...(request.invocation?.model ? { modelIsExact: true } : {}),
|
|
979
|
+
};
|
|
980
|
+
if (snapshot.runnerKind === "agent")
|
|
981
|
+
return { kind: "agent", profile };
|
|
982
|
+
// The catalog is supplied transitively by the work-list only for hashing; the
|
|
983
|
+
// SDK runner receives a frozen fallback copied into the request by its caller.
|
|
984
|
+
const fallback = request.fallbackEngine ? materializeFrozenLlm(request.fallbackEngine, undefined) : undefined;
|
|
985
|
+
return { kind: "sdk", profile, ...(fallback ? { fallbackConnection: fallback } : {}) };
|
|
944
986
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
const
|
|
948
|
-
|
|
949
|
-
|
|
987
|
+
function materializeFrozenLlm(snapshot, invocation) {
|
|
988
|
+
let apiKey;
|
|
989
|
+
for (const name of snapshot.credential?.names ?? []) {
|
|
990
|
+
const candidate = process.env[name]?.trim();
|
|
991
|
+
if (candidate) {
|
|
992
|
+
apiKey = candidate;
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
950
995
|
}
|
|
951
|
-
|
|
996
|
+
if (snapshot.credential?.required && !apiKey)
|
|
997
|
+
throw new ConfigError(`Required engine credential ${snapshot.credential.names[0]} is not set.`, "INVALID_CONFIG_FILE");
|
|
998
|
+
const base = {
|
|
999
|
+
provider: snapshot.provider,
|
|
1000
|
+
endpoint: snapshot.endpoint,
|
|
1001
|
+
model: invocation?.model ?? snapshot.model,
|
|
1002
|
+
...(snapshot.temperature !== undefined ? { temperature: snapshot.temperature } : {}),
|
|
1003
|
+
...(snapshot.maxTokens !== undefined ? { maxTokens: snapshot.maxTokens } : {}),
|
|
1004
|
+
...(snapshot.supportsJsonSchema !== undefined ? { supportsJsonSchema: snapshot.supportsJsonSchema } : {}),
|
|
1005
|
+
...(snapshot.extraParams ? { extraParams: snapshot.extraParams } : {}),
|
|
1006
|
+
...(snapshot.contextLength !== undefined ? { contextLength: snapshot.contextLength } : {}),
|
|
1007
|
+
...(snapshot.enableThinking !== undefined ? { enableThinking: snapshot.enableThinking } : {}),
|
|
1008
|
+
...(apiKey ? { apiKey } : {}),
|
|
1009
|
+
};
|
|
1010
|
+
return invocation?.llm ? deepMergeConfig(base, invocation.llm) : base;
|
|
952
1011
|
}
|
|
953
1012
|
// ── Small helpers ────────────────────────────────────────────────────────────
|
|
954
1013
|
/**
|