akm-cli 0.9.24 → 0.9.25-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 +173 -0
- package/dist/cli.js +1 -1
- package/dist/commands/health/checks.js +10 -11
- package/dist/commands/improve/consolidate/pair-pass.js +4 -2
- package/dist/commands/improve/consolidate.js +10 -4
- package/dist/commands/improve/execution.js +4 -11
- package/dist/commands/improve/extract-prompt.js +4 -4
- package/dist/commands/improve/extract.js +11 -13
- package/dist/commands/improve/improve-cli.js +65 -34
- package/dist/commands/improve/improve-strategies.js +49 -43
- package/dist/commands/improve/improve-usage-report.js +8 -17
- package/dist/commands/improve/loop-stages.js +3 -0
- package/dist/commands/improve/preparation.js +3 -1
- package/dist/commands/improve/reflect-noise.js +125 -0
- package/dist/commands/improve/reflect.js +105 -172
- package/dist/commands/improve/retrieval-gate.js +7 -2
- package/dist/commands/improve/stage.js +67 -24
- package/dist/commands/proposal/drain.js +11 -2
- package/dist/commands/proposal/proposal-cli.js +1 -5
- package/dist/commands/proposal/propose-cli.js +2 -2
- package/dist/commands/proposal/propose.js +72 -84
- package/dist/commands/proposal/validators/proposal-quality-validators.js +4 -2
- package/dist/commands/read/search-cli.js +0 -38
- package/dist/commands/remember.js +3 -3
- package/dist/commands/sources/schema-repair.js +1 -1
- package/dist/core/config/config-schema.js +37 -60
- package/dist/core/config/engine-semantics.js +15 -11
- package/dist/core/config/schema/improve-processes.js +18 -2
- package/dist/core/improve-result.js +3 -3
- package/dist/core/redaction.js +4 -0
- package/dist/core/spawn-env.js +25 -0
- package/dist/core/structured.js +11 -1
- package/dist/execution/source.js +10 -0
- package/dist/indexer/passes/memory-inference.js +2 -1
- package/dist/integrations/agent/builder-shared.js +15 -0
- package/dist/integrations/agent/config.js +1 -1
- package/dist/integrations/agent/engine-resolution.js +13 -31
- package/dist/integrations/agent/execution.js +48 -22
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +2 -2
- package/dist/integrations/agent/prompts.js +55 -114
- package/dist/integrations/agent/request-lowering.js +21 -8
- package/dist/integrations/agent/runner-dispatch.js +96 -3
- package/dist/integrations/agent/runner.js +8 -2
- package/dist/integrations/harnesses/aider/agent-builder.js +10 -23
- package/dist/integrations/harnesses/aider/index.js +0 -5
- package/dist/integrations/harnesses/amazonq/agent-builder.js +9 -21
- package/dist/integrations/harnesses/amazonq/index.js +0 -5
- package/dist/integrations/harnesses/claude/agent-builder.js +47 -36
- package/dist/integrations/harnesses/claude/index.js +0 -14
- package/dist/integrations/harnesses/codex/agent-builder.js +3 -4
- package/dist/integrations/harnesses/codex/index.js +0 -4
- package/dist/integrations/harnesses/copilot/agent-builder.js +4 -13
- package/dist/integrations/harnesses/copilot/index.js +2 -7
- package/dist/integrations/harnesses/gemini/agent-builder.js +4 -13
- package/dist/integrations/harnesses/gemini/index.js +0 -5
- package/dist/integrations/harnesses/ids.js +12 -10
- package/dist/integrations/harnesses/opencode/agent-builder.js +41 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -8
- package/dist/integrations/harnesses/opencode/model-config.js +33 -0
- package/dist/integrations/harnesses/opencode/model-work-agent.js +115 -0
- package/dist/integrations/harnesses/opencode-sdk/harness.js +2 -7
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +114 -28
- package/dist/integrations/harnesses/openhands/agent-builder.js +7 -21
- package/dist/integrations/harnesses/openhands/index.js +0 -5
- package/dist/integrations/harnesses/pi/agent-builder.js +7 -21
- package/dist/integrations/harnesses/pi/index.js +0 -5
- package/dist/llm/client.js +5 -0
- package/dist/llm/feature-gate.js +12 -9
- package/dist/llm/index-passes.js +2 -5
- package/dist/llm/memory-infer.js +6 -5
- package/dist/llm/structured-call.js +33 -11
- package/dist/output/shapes/passthrough.js +1 -0
- package/dist/scripts/akm-migrate-node.js +298 -228
- package/dist/scripts/akm-migrate.js +298 -228
- package/dist/workflows/exec/step-work.js +6 -5
- package/dist/workflows/exec/unit-dispatch.js +4 -13
- package/dist/workflows/freeze/step-values.js +1 -1
- package/docs/reference/cli.md +41 -18
- package/docs/reference/configuration.md +165 -12
- package/docs/reference/data-and-telemetry.md +2 -3
- package/docs/reference/workflow-schema.md +10 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +108 -0
|
@@ -81,20 +81,6 @@ export const searchCommand = defineJsonCommand({
|
|
|
81
81
|
description: "Include session assets (excluded from default search results via config.search.defaultExcludeTypes).",
|
|
82
82
|
default: false,
|
|
83
83
|
},
|
|
84
|
-
// Declared as the POSITIVE name with `default: true` so citty's native
|
|
85
|
-
// `--no-<name>` negation (it strips a leading `--no-` from ANY token and
|
|
86
|
-
// negates the remainder BEFORE consulting the declared-args table — see
|
|
87
|
-
// node_modules/citty/dist/index.mjs) does the work, the same pattern
|
|
88
|
-
// `sync --push/--no-push` uses. A flag DECLARED as `no-track-usage` could
|
|
89
|
-
// never be negated: `--no-track-usage` parses as "negate `track-usage`",
|
|
90
|
-
// a name nothing declared, leaving the real key at its default forever
|
|
91
|
-
// (F1/A1).
|
|
92
|
-
"track-usage": {
|
|
93
|
-
type: "boolean",
|
|
94
|
-
default: true,
|
|
95
|
-
description: "A successful search records usage-events telemetry. Default: on. Use --no-track-usage to run a " +
|
|
96
|
-
"search that records nothing.",
|
|
97
|
-
},
|
|
98
84
|
},
|
|
99
85
|
async run({ args }) {
|
|
100
86
|
rejectRetiredSourceFlag();
|
|
@@ -108,7 +94,6 @@ export const searchCommand = defineJsonCommand({
|
|
|
108
94
|
const filters = parseScopeFilterFlags(filterTokens, "--filter");
|
|
109
95
|
const includeProposed = args["include-proposed"] === true;
|
|
110
96
|
const belief = parseBeliefFilterMode(typeof args.belief === "string" ? args.belief : undefined);
|
|
111
|
-
const skipLogging = args["track-usage"] === false;
|
|
112
97
|
const includeSessions = args["include-sessions"];
|
|
113
98
|
const assets = args.assets === true;
|
|
114
99
|
const outputMode = getOutputMode();
|
|
@@ -121,7 +106,6 @@ export const searchCommand = defineJsonCommand({
|
|
|
121
106
|
includeProposed,
|
|
122
107
|
belief,
|
|
123
108
|
includeSessions,
|
|
124
|
-
skipLogging,
|
|
125
109
|
assets,
|
|
126
110
|
eventSource: resolveUsageEventSource(),
|
|
127
111
|
attributionProjection: outputMode.shape === "agent" ? "agent" : outputMode.detail,
|
|
@@ -154,15 +138,6 @@ export const curateCommand = defineJsonCommand({
|
|
|
154
138
|
"with a workflow asset's own `budget` field (a run-cost cap) — this is a context-size target for this " +
|
|
155
139
|
"one curate call.",
|
|
156
140
|
},
|
|
157
|
-
// Declared as the POSITIVE name with `default: true` — see the
|
|
158
|
-
// `track-usage` comment on `searchCommand` above for why a flag NAME
|
|
159
|
-
// must never start with `no-`.
|
|
160
|
-
"track-usage": {
|
|
161
|
-
type: "boolean",
|
|
162
|
-
default: true,
|
|
163
|
-
description: "A successful curate records usage-events telemetry for the curated items. Default: on. Use " +
|
|
164
|
-
"--no-track-usage to run a curate that records nothing.",
|
|
165
|
-
},
|
|
166
141
|
},
|
|
167
142
|
async run({ args }) {
|
|
168
143
|
rejectRetiredSourceFlag();
|
|
@@ -173,7 +148,6 @@ export const curateCommand = defineJsonCommand({
|
|
|
173
148
|
const limitParsed = parsePositiveIntFlag(args.limit ?? undefined);
|
|
174
149
|
const limit = limitParsed && limitParsed > 0 ? limitParsed : 4;
|
|
175
150
|
const source = parseSearchSource(args.from ?? "local");
|
|
176
|
-
const skipLogging = args["track-usage"] === false;
|
|
177
151
|
const outputMode = getOutputMode();
|
|
178
152
|
const packBudget = parsePositiveIntFlag(args.pack ?? undefined, "--pack");
|
|
179
153
|
const curated = await akmCurate({
|
|
@@ -181,7 +155,6 @@ export const curateCommand = defineJsonCommand({
|
|
|
181
155
|
type,
|
|
182
156
|
limit,
|
|
183
157
|
source,
|
|
184
|
-
skipLogging,
|
|
185
158
|
eventSource: resolveUsageEventSource(),
|
|
186
159
|
attributionProjection: outputMode.shape === "agent" ? "agent" : outputMode.detail,
|
|
187
160
|
});
|
|
@@ -285,15 +258,6 @@ export const showCommand = defineJsonCommand({
|
|
|
285
258
|
type: "string",
|
|
286
259
|
description: "Exact context budget in characters. Requires --context lead; mutually exclusive with --max-tokens.",
|
|
287
260
|
},
|
|
288
|
-
// Declared as the POSITIVE name with `default: true` — see the
|
|
289
|
-
// `track-usage` comment on `searchCommand` above for why a flag NAME
|
|
290
|
-
// must never start with `no-`.
|
|
291
|
-
"track-usage": {
|
|
292
|
-
type: "boolean",
|
|
293
|
-
default: true,
|
|
294
|
-
description: "A successful show records usage-events telemetry, including the search-selection linkage when this " +
|
|
295
|
-
"show follows a recent search. Default: on. Use --no-track-usage to run a show that records nothing.",
|
|
296
|
-
},
|
|
297
261
|
},
|
|
298
262
|
async run({ args }) {
|
|
299
263
|
// `[origin//]meta[:name]` targets the stash `.meta/` convention, which is
|
|
@@ -342,14 +306,12 @@ export const showCommand = defineJsonCommand({
|
|
|
342
306
|
if (maxContextChars !== undefined && !Number.isSafeInteger(maxContextChars)) {
|
|
343
307
|
throw new UsageError("Fragment context budget is too large.", "INVALID_FLAG_VALUE");
|
|
344
308
|
}
|
|
345
|
-
const skipLogging = args["track-usage"] === false;
|
|
346
309
|
const result = await akmShowUnified({
|
|
347
310
|
ref: args.ref,
|
|
348
311
|
detail: showDetail,
|
|
349
312
|
contextMode,
|
|
350
313
|
maxContextChars,
|
|
351
314
|
scope,
|
|
352
|
-
skipLogging,
|
|
353
315
|
eventSource: resolveUsageEventSource(),
|
|
354
316
|
});
|
|
355
317
|
output("show", result);
|
|
@@ -19,7 +19,7 @@ import { warn } from "../core/warn.js";
|
|
|
19
19
|
import { SCOPE_KEYS } from "../indexer/passes/metadata.js";
|
|
20
20
|
import { callStructured } from "../llm/structured-call.js";
|
|
21
21
|
import { withLlmStage } from "../llm/usage-telemetry.js";
|
|
22
|
-
import {
|
|
22
|
+
import { resolveImproveExecution } from "./improve/execution.js";
|
|
23
23
|
/**
|
|
24
24
|
* Parse a shorthand duration string to a number of milliseconds.
|
|
25
25
|
* Supports the CLI-wide canonical grammar: `30d` (days), `12h` (hours),
|
|
@@ -224,9 +224,9 @@ const LLM_ENRICH_TIMEOUT_MS = 10_000;
|
|
|
224
224
|
*/
|
|
225
225
|
export async function runLlmEnrich(body) {
|
|
226
226
|
const config = loadConfig();
|
|
227
|
-
const resolved =
|
|
227
|
+
const resolved = resolveImproveExecution({ config, processName: "remember-enrich" });
|
|
228
228
|
if (!resolved) {
|
|
229
|
-
warn("Warning: --enrich requires an
|
|
229
|
+
warn("Warning: --enrich requires an engine to be configured. Run `akm setup` to configure one.");
|
|
230
230
|
return { tags: [] };
|
|
231
231
|
}
|
|
232
232
|
const runner = resolved.runner;
|
|
@@ -105,7 +105,7 @@ export async function runSchemaRepairPass(failures, options) {
|
|
|
105
105
|
const { startMs, budgetMs, stashDir, findFilePath = defaultFindFilePath, isLessonCandidateFn = defaultIsLessonCandidate, chatFn, } = options;
|
|
106
106
|
const llmRunner = options.llmRunner ?? null;
|
|
107
107
|
if (!llmRunner)
|
|
108
|
-
throw new Error("runSchemaRepairPass requires a resolved
|
|
108
|
+
throw new Error("runSchemaRepairPass requires a resolved runner");
|
|
109
109
|
if (!stashDir) {
|
|
110
110
|
throw new Error("runSchemaRepairPass requires stashDir so repairs route through the proposal queue");
|
|
111
111
|
}
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
* enforced at save time via `superRefine` on the top-level schema.
|
|
40
40
|
*/
|
|
41
41
|
import { z } from "zod";
|
|
42
|
-
import {
|
|
42
|
+
import { HARNESS_MODEL_WORK_IDS } from "../../integrations/harnesses/ids.js";
|
|
43
|
+
import { BUILTIN_IMPROVE_STRATEGY_NAMES, IMPROVE_ENGINE_PROCESSES } from "./engine-semantics.js";
|
|
43
44
|
import { EmbeddingConnectionConfigSchema } from "./schema/embedding.js";
|
|
44
45
|
import { EnginesSchema } from "./schema/engines.js";
|
|
45
46
|
import { ExecutionPolicyConfigSchema } from "./schema/execution.js";
|
|
@@ -222,13 +223,32 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
|
|
|
222
223
|
message: "engine does not name a configured engine",
|
|
223
224
|
});
|
|
224
225
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
// One rule for every key unattended model work reads its engine from: the
|
|
227
|
+
// engine must confine the model-work tool policy (an LLM, or an agent whose
|
|
228
|
+
// harness does), because that work runs over generated content with no one
|
|
229
|
+
// watching.
|
|
230
|
+
const confining = [...HARNESS_MODEL_WORK_IDS];
|
|
231
|
+
const confiningPlatforms = `${confining.slice(0, -1).join(", ")} or ${confining.at(-1)}`;
|
|
232
|
+
const modelWorkEngine = (name, path) => {
|
|
233
|
+
if (!name)
|
|
234
|
+
return;
|
|
235
|
+
const engine = config.engines?.[name];
|
|
236
|
+
if (!engine) {
|
|
237
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path, message: "engine does not name a configured engine" });
|
|
238
|
+
}
|
|
239
|
+
else if (engine.kind !== "llm" && !HARNESS_MODEL_WORK_IDS.has(engine.platform)) {
|
|
240
|
+
ctx.addIssue({
|
|
241
|
+
code: z.ZodIssueCode.custom,
|
|
242
|
+
path,
|
|
243
|
+
message: `engine "${name}" (platform ${engine.platform}) cannot confine the model-work tool policy, which unattended model work requires. Use an LLM engine, or an agent engine on ${confiningPlatforms}.`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
modelWorkEngine(config.defaults?.llmEngine, ["defaults", "llmEngine"]);
|
|
248
|
+
for (const [passName, pass] of Object.entries(config.index ?? {})) {
|
|
249
|
+
const engine = pass?.engine;
|
|
250
|
+
if (typeof engine === "string")
|
|
251
|
+
modelWorkEngine(engine, ["index", passName, "engine"]);
|
|
232
252
|
}
|
|
233
253
|
const workflowJudge = config.workflow?.judgeEngine;
|
|
234
254
|
if (workflowJudge && !config.engines?.[workflowJudge]) {
|
|
@@ -256,68 +276,25 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
|
|
|
256
276
|
});
|
|
257
277
|
}
|
|
258
278
|
for (const [strategyName, strategy] of Object.entries(config.improve?.strategies ?? {})) {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
const engine = config.engines?.[strategyEngine];
|
|
262
|
-
if (!engine || engine.kind !== "llm") {
|
|
263
|
-
ctx.addIssue({
|
|
264
|
-
code: z.ZodIssueCode.custom,
|
|
265
|
-
path: ["improve", "strategies", strategyName, "engine"],
|
|
266
|
-
message: engine ? "strategy engine must be an LLM engine" : "engine does not name a configured engine",
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
279
|
+
const strategyPath = ["improve", "strategies", strategyName];
|
|
280
|
+
modelWorkEngine(strategy.engine, [...strategyPath, "engine"]);
|
|
270
281
|
for (const [processName, process] of Object.entries(strategy.processes ?? {})) {
|
|
271
282
|
const processConfig = process;
|
|
272
|
-
const
|
|
273
|
-
if (processConfig.engine &&
|
|
283
|
+
const processPath = [...strategyPath, "processes", processName];
|
|
284
|
+
if (processConfig.engine && !IMPROVE_ENGINE_PROCESSES.includes(processName)) {
|
|
274
285
|
ctx.addIssue({
|
|
275
286
|
code: z.ZodIssueCode.custom,
|
|
276
|
-
path: [
|
|
287
|
+
path: [...processPath, "engine"],
|
|
277
288
|
message: `${processName} does not dispatch an engine`,
|
|
278
289
|
});
|
|
279
290
|
}
|
|
280
291
|
else {
|
|
281
|
-
|
|
282
|
-
if (processEngine && capability === "llm") {
|
|
283
|
-
const engine = config.engines?.[processEngine];
|
|
284
|
-
if (!engine || engine.kind !== "llm") {
|
|
285
|
-
ctx.addIssue({
|
|
286
|
-
code: z.ZodIssueCode.custom,
|
|
287
|
-
path: ["improve", "strategies", strategyName, "processes", processName, "engine"],
|
|
288
|
-
message: engine ? `${processName} requires an LLM engine` : "engine does not name a configured engine",
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
else if (processConfig.engine && capability === "runner" && !config.engines?.[processConfig.engine]) {
|
|
293
|
-
ctx.addIssue({
|
|
294
|
-
code: z.ZodIssueCode.custom,
|
|
295
|
-
path: ["improve", "strategies", strategyName, "processes", processName, "engine"],
|
|
296
|
-
message: "engine does not name a configured engine",
|
|
297
|
-
});
|
|
298
|
-
}
|
|
292
|
+
modelWorkEngine(processConfig.engine, [...processPath, "engine"]);
|
|
299
293
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const engine = config.engines?.[judgmentEngine];
|
|
303
|
-
if (!engine) {
|
|
304
|
-
ctx.addIssue({
|
|
305
|
-
code: z.ZodIssueCode.custom,
|
|
306
|
-
path: ["improve", "strategies", strategyName, "processes", processName, "judgment", "engine"],
|
|
307
|
-
message: "engine does not name a configured engine",
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
const gateEngine = processConfig.qualityGate?.engine;
|
|
312
|
-
if (gateEngine && config.engines?.[gateEngine]?.kind !== "llm") {
|
|
313
|
-
ctx.addIssue({
|
|
314
|
-
code: z.ZodIssueCode.custom,
|
|
315
|
-
path: ["improve", "strategies", strategyName, "processes", processName, "qualityGate", "engine"],
|
|
316
|
-
message: config.engines?.[gateEngine]
|
|
317
|
-
? "a quality-gate judge must be an LLM engine"
|
|
318
|
-
: "engine does not name a configured engine",
|
|
319
|
-
});
|
|
294
|
+
if (processConfig.judgment?.enabled === true) {
|
|
295
|
+
modelWorkEngine(processConfig.judgment.engine, [...processPath, "judgment", "engine"]);
|
|
320
296
|
}
|
|
297
|
+
modelWorkEngine(processConfig.qualityGate?.engine, [...processPath, "qualityGate", "engine"]);
|
|
321
298
|
}
|
|
322
299
|
}
|
|
323
300
|
// #464.a: defaultWriteTarget must name a configured source. 0.9.0 (spec
|
|
@@ -11,14 +11,18 @@ export const BUILTIN_IMPROVE_STRATEGY_NAMES = [
|
|
|
11
11
|
"reflect-distill",
|
|
12
12
|
"proactive-maintenance",
|
|
13
13
|
];
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The improve processes that use an engine. Triage's engine is its judgment's;
|
|
16
|
+
* each of the others makes the process's own model calls.
|
|
17
|
+
*/
|
|
18
|
+
export const IMPROVE_ENGINE_PROCESSES = [
|
|
19
|
+
"reflect",
|
|
20
|
+
"distill",
|
|
21
|
+
"consolidate",
|
|
22
|
+
"memoryInference",
|
|
23
|
+
"extract",
|
|
24
|
+
"validation",
|
|
25
|
+
"triage",
|
|
26
|
+
];
|
|
27
|
+
/** Every improve process, in plan order: the engine processes and `proactiveMaintenance`, which uses none. */
|
|
28
|
+
export const IMPROVE_PROCESS_NAMES = [...IMPROVE_ENGINE_PROCESSES, "proactiveMaintenance"];
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* verbatim from the former `config-schema.ts` monolith — no behavior change.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import {
|
|
9
|
+
import { IMPROVE_PROCESS_NAMES } from "../engine-semantics.js";
|
|
10
10
|
import { engineName, LlmInvocationOverridesSchema, nonEmptyString, positiveInt } from "./primitives.js";
|
|
11
11
|
// ── Improve profile / process ──────────────────────────────────────────────
|
|
12
12
|
//
|
|
@@ -103,6 +103,21 @@ const fidelityCheckField = z.object({ enabled: z.boolean().optional() }).passthr
|
|
|
103
103
|
* byte-identical behaviour). Reflect process only.
|
|
104
104
|
*/
|
|
105
105
|
const lowValueFilterField = z.object({ enabled: z.boolean().optional() }).passthrough().optional();
|
|
106
|
+
/**
|
|
107
|
+
* The wording lists of reflect's pre-judge defect filter (`findReflectDefect`).
|
|
108
|
+
* Each list is optional: one that is set replaces that rule's default list, and
|
|
109
|
+
* an empty one turns the rule off. Phrases (`placeholders`, `metaCommentary`)
|
|
110
|
+
* match as whole words in any case; `frontmatterKeys` are exact key names.
|
|
111
|
+
* Reflect process only.
|
|
112
|
+
*/
|
|
113
|
+
const defectFilterField = z
|
|
114
|
+
.object({
|
|
115
|
+
placeholders: z.array(nonEmptyString).optional(),
|
|
116
|
+
metaCommentary: z.array(nonEmptyString).optional(),
|
|
117
|
+
frontmatterKeys: z.array(nonEmptyString).optional(),
|
|
118
|
+
})
|
|
119
|
+
.passthrough()
|
|
120
|
+
.optional();
|
|
106
121
|
/**
|
|
107
122
|
* #626 — extract process: pre-LLM heuristic triage gate. When enabled, a
|
|
108
123
|
* deterministic scorer decides BEFORE the extraction LLM call whether a
|
|
@@ -159,6 +174,7 @@ const REFLECT_PROCESS_FIELDS = {
|
|
|
159
174
|
limit: processLimitField,
|
|
160
175
|
qualityGate: qualityGateField,
|
|
161
176
|
lowValueFilter: lowValueFilterField,
|
|
177
|
+
defectFilter: defectFilterField,
|
|
162
178
|
};
|
|
163
179
|
const DISTILL_PROCESS_FIELDS = {
|
|
164
180
|
allowedTypes: allowedTypesField,
|
|
@@ -319,7 +335,7 @@ const ImproveProfileProcessesSchema = z
|
|
|
319
335
|
});
|
|
320
336
|
}
|
|
321
337
|
for (const [name, process] of Object.entries(val)) {
|
|
322
|
-
if (!(name
|
|
338
|
+
if (!IMPROVE_PROCESS_NAMES.includes(name) &&
|
|
323
339
|
!RETIRED_PROCESS_NAMES.has(name) &&
|
|
324
340
|
process !== null &&
|
|
325
341
|
typeof process === "object" &&
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { cloneExecutionJsonObject } from "../execution/json.js";
|
|
5
5
|
import { isRecord } from "./common.js";
|
|
6
|
-
import {
|
|
6
|
+
import { IMPROVE_PROCESS_NAMES } from "./config/engine-semantics.js";
|
|
7
7
|
const COMMON_FIELDS = [
|
|
8
8
|
"schemaVersion",
|
|
9
9
|
"ok",
|
|
@@ -192,11 +192,11 @@ function validateProactivePlan(value) {
|
|
|
192
192
|
fail("plan.proactive.selected must equal plan.proactive.selectedRefs.length");
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
|
-
/** #947 — plan.processes: one row per
|
|
195
|
+
/** #947 — plan.processes: one row per IMPROVE_PROCESS_NAMES name, plus an optional "triage.judgment" row. */
|
|
196
196
|
function validateProcessRoutingRows(value) {
|
|
197
197
|
if (!Array.isArray(value))
|
|
198
198
|
fail("plan.processes must be an array");
|
|
199
|
-
const canonicalNames =
|
|
199
|
+
const canonicalNames = IMPROVE_PROCESS_NAMES;
|
|
200
200
|
const engineKinds = new Set(["llm", "agent", "sdk"]);
|
|
201
201
|
const seen = new Set();
|
|
202
202
|
for (const row of value) {
|
package/dist/core/redaction.js
CHANGED
|
@@ -19,6 +19,10 @@ const ENV_PASSTHROUGH_REDACTION_POLICY = {
|
|
|
19
19
|
OPENCODE_CONFIG: "path",
|
|
20
20
|
CLAUDE_CONFIG: "path",
|
|
21
21
|
CODEX_CONFIG: "path",
|
|
22
|
+
XDG_CONFIG_HOME: "path",
|
|
23
|
+
XDG_DATA_HOME: "path",
|
|
24
|
+
XDG_CACHE_HOME: "path",
|
|
25
|
+
XDG_STATE_HOME: "path",
|
|
22
26
|
AWS_PROFILE: "identifier",
|
|
23
27
|
AWS_REGION: "identifier",
|
|
24
28
|
LLM_MODEL: "identifier",
|
package/dist/core/spawn-env.js
CHANGED
|
@@ -45,6 +45,31 @@ export const COMMON_SPAWN_ENV_PASSTHROUGH = [
|
|
|
45
45
|
"TMPDIR",
|
|
46
46
|
"AKM_EVENT_SOURCE",
|
|
47
47
|
];
|
|
48
|
+
/**
|
|
49
|
+
* The XDG base-directory variables. opencode resolves its config, data, cache
|
|
50
|
+
* and state directories from them, so it must receive them: an akm that runs
|
|
51
|
+
* under a custom `XDG_CONFIG_HOME` otherwise spawns an opencode that reads
|
|
52
|
+
* `$HOME/.config/opencode` and misses the provider config its caller named.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately NOT part of {@link COMMON_SPAWN_ENV_PASSTHROUGH}, which is every
|
|
55
|
+
* harness's baseline, the workflow exec unit's default allowlist (a documented
|
|
56
|
+
* list) and, through profile `envPassthrough`, frozen into workflow plans.
|
|
57
|
+
* codex, gemini and pi keep their own dotdirs under `$HOME`, and handing the
|
|
58
|
+
* names to a shell command would redirect the `git` and `gh` config it reads. A
|
|
59
|
+
* harness that reads them asks for them by name: the opencode profile's list,
|
|
60
|
+
* and the opencode-sdk server's allowlist (`opencodeSdkServerEnvironmentNames`).
|
|
61
|
+
*
|
|
62
|
+
* A name added to a profile's list changes the plans frozen after it (their
|
|
63
|
+
* bytes, so their `plan_hash`); a stored plan keeps the list it was frozen with
|
|
64
|
+
* and still resumes, because nothing gates on that hash. The SDK server's
|
|
65
|
+
* allowlist is not part of a plan, so it takes the names by code.
|
|
66
|
+
*/
|
|
67
|
+
export const XDG_BASE_DIR_ENV_PASSTHROUGH = [
|
|
68
|
+
"XDG_CONFIG_HOME",
|
|
69
|
+
"XDG_DATA_HOME",
|
|
70
|
+
"XDG_CACHE_HOME",
|
|
71
|
+
"XDG_STATE_HOME",
|
|
72
|
+
];
|
|
48
73
|
/**
|
|
49
74
|
* The names Windows itself requires of ANY child, whatever the caller's
|
|
50
75
|
* allowlist says. Applied at build time rather than added to
|
package/dist/core/structured.js
CHANGED
|
@@ -24,7 +24,17 @@
|
|
|
24
24
|
* jittered retry; `runAgent` has timeout/abort semantics).
|
|
25
25
|
*/
|
|
26
26
|
import { parseEmbeddedJsonResponse } from "./parse.js";
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Append the one structured-output instruction to a prompt. Agent lowering
|
|
29
|
+
* appends it to every schema-bearing request, alongside a harness's native
|
|
30
|
+
* channel where one exists (codex `--output-schema`); the workflow engine
|
|
31
|
+
* appends it for a direct-LLM unit. Resumed workflow runs depend on these
|
|
32
|
+
* exact bytes, so do not reword it.
|
|
33
|
+
*/
|
|
34
|
+
export function withSchemaInstruction(prompt, schema) {
|
|
35
|
+
return `${prompt}\n\nRespond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${JSON.stringify(schema)}`;
|
|
36
|
+
}
|
|
37
|
+
export function defaultFeedback(failure) {
|
|
28
38
|
if (failure.reason === "parse_error") {
|
|
29
39
|
return "Your previous response contained no parseable JSON. Respond with ONLY a JSON value that matches the requested schema — no prose, no code fences.";
|
|
30
40
|
}
|
package/dist/execution/source.js
CHANGED
|
@@ -5,6 +5,16 @@ import { cloneExecutionJson, cloneExecutionJsonObject } from "./json.js";
|
|
|
5
5
|
export const EXECUTION_SOURCE_SCHEMA_VERSION = 1;
|
|
6
6
|
/** Current internal adapter identifiers are lowercase kebab-case registry keys. */
|
|
7
7
|
export const EXECUTION_ADAPTER_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
8
|
+
/**
|
|
9
|
+
* The model-work tool policy: unattended model work (improve, the judges, index
|
|
10
|
+
* passes, remember) may read, edit only inside the dispatch's own scratch
|
|
11
|
+
* working directory, and run `akm search` and `akm show`; the stash stays
|
|
12
|
+
* read-only to it. A transport grants what it can confine and refuses the policy
|
|
13
|
+
* at build when it can confine nothing; an LLM has no tools. A request carries it
|
|
14
|
+
* as `authorization.policy.id`, set only when its caller asks (`modelWork`): no
|
|
15
|
+
* `tools` value names it, so an asset's own `tools:` cannot.
|
|
16
|
+
*/
|
|
17
|
+
export const MODEL_WORK_POLICY_ID = "model-work";
|
|
8
18
|
function requireRecord(value, path) {
|
|
9
19
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
10
20
|
throw new TypeError(`${path} must be an object`);
|
|
@@ -48,6 +48,7 @@ import { ConfigError } from "../../core/errors.js";
|
|
|
48
48
|
import { warn } from "../../core/warn.js";
|
|
49
49
|
import { beginWriteProvenance, recordWrittenPath } from "../../core/write-provenance.js";
|
|
50
50
|
import { writeAssetToSource } from "../../core/write-source.js";
|
|
51
|
+
import { runnerLlmConnection } from "../../integrations/agent/runner.js";
|
|
51
52
|
import { assertRunnerCredentials } from "../../integrations/agent/runner-dispatch.js";
|
|
52
53
|
import { isProcessEnabled } from "../../llm/feature-gate.js";
|
|
53
54
|
import { resolveIndexPassExecution } from "../../llm/index-passes.js";
|
|
@@ -283,7 +284,7 @@ async function runMemoryInferencePassBody(ctx, provenance) {
|
|
|
283
284
|
}),
|
|
284
285
|
// Caller-set connection concurrency or 1: `resolveLlmEngineUse` does
|
|
285
286
|
// not forward `engines.<name>.concurrency`, so config cannot raise this.
|
|
286
|
-
llmRunner
|
|
287
|
+
runnerLlmConnection(llmRunner)?.concurrency ?? 1);
|
|
287
288
|
if (configFailure)
|
|
288
289
|
throw configFailure;
|
|
289
290
|
for (let i = 0; i < perRecordResults.length; i++) {
|
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
export function resolveDispatchModel(request, _profile, _platform) {
|
|
6
6
|
return request.model;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* The model an engine's own `args` select, as `--model X` or `--model=X` (the
|
|
10
|
+
* last one wins), for a builder that writes its own argv in place of those args.
|
|
11
|
+
*/
|
|
12
|
+
export function modelFromArgs(args) {
|
|
13
|
+
let model;
|
|
14
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
15
|
+
const arg = args[index];
|
|
16
|
+
if (arg === "--model")
|
|
17
|
+
model = args[index + 1];
|
|
18
|
+
else if (arg?.startsWith("--model="))
|
|
19
|
+
model = arg.slice("--model=".length);
|
|
20
|
+
}
|
|
21
|
+
return model;
|
|
22
|
+
}
|
|
8
23
|
/**
|
|
9
24
|
* Normalize a toolPolicy value to a comma-separated string suitable for a
|
|
10
25
|
* CLI flag. Structured policy objects are JSON-serialized.
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/** Default agent CLI timeout; null means agents run until they finish. */
|
|
5
5
|
export const DEFAULT_AGENT_TIMEOUT_MS = null;
|
|
6
|
-
/** Default hard timeout for direct LLM
|
|
6
|
+
/** Default hard timeout for a direct LLM call, and the bound on model work on every runner kind, when no engine/use override exists. */
|
|
7
7
|
export const DEFAULT_LLM_TIMEOUT_MS = 600_000;
|
|
@@ -10,10 +10,9 @@ import { SECRET_STORE_REFERENCE_PATTERN } from "../../core/config/schema/primiti
|
|
|
10
10
|
import { ConfigError } from "../../core/errors.js";
|
|
11
11
|
import { formatExtraParamsIssue, validateExtraParams } from "../../core/extra-params.js";
|
|
12
12
|
import { collectSensitiveValues } from "../../core/redaction.js";
|
|
13
|
-
import { warn } from "../../core/warn.js";
|
|
14
13
|
import { resolveSecretFromStore } from "../../sources/snapshot-fetchers/secret-seam.js";
|
|
15
14
|
import { getHarness } from "../harnesses/index.js";
|
|
16
|
-
import {
|
|
15
|
+
import { DEFAULT_LLM_TIMEOUT_MS } from "./config.js";
|
|
17
16
|
import { getBuiltinAgentProfile, OPENCODE_SDK_SERVER_BIN } from "./profiles.js";
|
|
18
17
|
const LLM_CONNECTION_FIELDS = [
|
|
19
18
|
"provider",
|
|
@@ -217,29 +216,15 @@ function rawLlmConnection(engine) {
|
|
|
217
216
|
}
|
|
218
217
|
return connection;
|
|
219
218
|
}
|
|
220
|
-
|
|
219
|
+
/** Resolve one selected LLM engine and overlays without materializing credentials. */
|
|
220
|
+
export function resolveLlmEngineUse(config, layers) {
|
|
221
221
|
const name = selectedEngineName(config, layers, true);
|
|
222
222
|
if (!name) {
|
|
223
|
-
if (options.optional)
|
|
224
|
-
return undefined;
|
|
225
223
|
throw new ConfigError("No LLM engine is selected. Set defaults.llmEngine or specify engine.", "LLM_NOT_CONFIGURED");
|
|
226
224
|
}
|
|
227
225
|
const engine = configuredEngine(name, config);
|
|
228
|
-
if (engine.kind !== "llm")
|
|
229
|
-
|
|
230
|
-
const fallbackEngine = fallbackName ? configuredEngine(fallbackName, config) : undefined;
|
|
231
|
-
if (!fallbackEngine || fallbackEngine.kind !== "llm") {
|
|
232
|
-
if (options.optional)
|
|
233
|
-
return undefined;
|
|
234
|
-
throw new ConfigError(fallbackName
|
|
235
|
-
? `Engine "${name}" is not an LLM engine, and its llmEngine fallback "${fallbackName}" is not one either.`
|
|
236
|
-
: `Engine "${name}" is not an LLM engine, and has no llmEngine fallback configured.`, "INVALID_CONFIG_FILE");
|
|
237
|
-
}
|
|
238
|
-
warn(`[akm] Engine "${name}" is an agent engine, not an LLM engine; using its llmEngine "${fallbackName}" instead.`);
|
|
239
|
-
return options.optional
|
|
240
|
-
? resolveLlmEngineUse(config, [{ engine: fallbackName }], { optional: true })
|
|
241
|
-
: resolveLlmEngineUse(config, [{ engine: fallbackName }]);
|
|
242
|
-
}
|
|
226
|
+
if (engine.kind !== "llm")
|
|
227
|
+
throw new ConfigError(`Engine "${name}" is not an LLM engine.`, "INVALID_CONFIG_FILE");
|
|
243
228
|
let connection = rawLlmConnection(engine);
|
|
244
229
|
for (const layer of layers) {
|
|
245
230
|
if (layer.llm)
|
|
@@ -301,19 +286,16 @@ function lowerAgentEngine(name, engine, config) {
|
|
|
301
286
|
...(engine.workspace ? { workspace: path.resolve(engine.workspace) } : {}),
|
|
302
287
|
...(engine.model ? { model: engine.model } : {}),
|
|
303
288
|
};
|
|
289
|
+
// An engine that sets no timeoutMs leaves it unset, so a caller's own default
|
|
290
|
+
// (model work's 600 s) can apply; a dispatch with none runs unbounded.
|
|
304
291
|
const ownTimeout = Object.hasOwn(engine, "timeoutMs") ? (engine.timeoutMs ?? null) : undefined;
|
|
305
292
|
if (!sdk) {
|
|
306
|
-
return {
|
|
307
|
-
kind: "agent",
|
|
308
|
-
engine: name,
|
|
309
|
-
profile,
|
|
310
|
-
timeoutMs: ownTimeout !== undefined ? ownTimeout : DEFAULT_AGENT_TIMEOUT_MS,
|
|
311
|
-
};
|
|
293
|
+
return { kind: "agent", engine: name, profile, ...(ownTimeout !== undefined ? { timeoutMs: ownTimeout } : {}) };
|
|
312
294
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
295
|
+
// The fallback connection is the engine's own `llmEngine` and nothing else. `defaults.llmEngine`
|
|
296
|
+
// is the default engine for model work, not a connection every SDK engine borrows: with no
|
|
297
|
+
// `llmEngine`, opencode resolves provider, model and auth from its own configuration.
|
|
298
|
+
const fallback = engine.llmEngine ? resolveLlmEngineUse(config, [{ engine: engine.llmEngine }]) : undefined;
|
|
317
299
|
return {
|
|
318
300
|
kind: "sdk",
|
|
319
301
|
engine: name,
|
|
@@ -327,7 +309,7 @@ function lowerAgentEngine(name, engine, config) {
|
|
|
327
309
|
fallbackTimeoutMs: fallback.timeoutMs,
|
|
328
310
|
}
|
|
329
311
|
: {}),
|
|
330
|
-
|
|
312
|
+
...(ownTimeout !== undefined ? { timeoutMs: ownTimeout } : fallback ? { timeoutMs: fallback.timeoutMs } : {}),
|
|
331
313
|
};
|
|
332
314
|
}
|
|
333
315
|
/** Resolve a configured engine name to its runner: an LLM connection, a spawned agent, or the SDK. */
|