akm-cli 0.9.11 → 0.9.12
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 +166 -0
- package/STABILITY.md +6 -1
- package/dist/assets/hints/cli-hints-full.md +1 -1
- package/dist/assets/improve-strategies/consolidate.json +1 -1
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/thorough.json +1 -2
- package/dist/cli/shared.js +16 -4
- package/dist/cli.js +15 -13
- package/dist/commands/agent/agent-dispatch.js +8 -0
- package/dist/commands/command/execution-source-loader.js +25 -22
- package/dist/commands/command/portable-template.js +4 -26
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env-binding.js +10 -3
- package/dist/commands/env/env-cli.js +7 -0
- package/dist/commands/env/secret-cli.js +15 -4
- package/dist/commands/health/checks.js +186 -71
- package/dist/commands/health.js +16 -4
- package/dist/commands/improve/distill/quality-gate.js +2 -2
- package/dist/commands/improve/distill.js +28 -12
- package/dist/commands/improve/execution.js +1 -2
- package/dist/commands/improve/extract.js +82 -56
- package/dist/commands/improve/improve-strategies.js +26 -8
- package/dist/commands/improve/improve.js +13 -0
- package/dist/commands/improve/preparation.js +9 -6
- package/dist/commands/improve/reflect.js +61 -77
- package/dist/commands/lint/index.js +3 -1
- package/dist/commands/migrate-cli.js +6 -4
- package/dist/commands/proposal/drain-policies.js +22 -2
- package/dist/commands/proposal/repository.js +4 -4
- package/dist/commands/proposal/validators/proposal-quality-validators.js +23 -2
- package/dist/commands/proposal/validators/proposals.js +10 -19
- package/dist/commands/read/show.js +42 -31
- package/dist/commands/registry-cli.js +4 -2
- package/dist/commands/sources/init.js +4 -8
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-clone.js +5 -7
- package/dist/commands/sources/sources-cli.js +3 -5
- package/dist/commands/tasks/tasks-cli.js +4 -12
- package/dist/commands/tasks/tasks.js +38 -35
- package/dist/commands/workflow-cli.js +17 -15
- package/dist/core/activation-policy.js +31 -3
- package/dist/core/adapter/execution-source.js +39 -11
- package/dist/core/asset/stash-meta.js +7 -41
- package/dist/core/common.js +8 -17
- package/dist/core/config/config-schema.js +3 -23
- package/dist/core/config/config-walker.js +50 -4
- package/dist/core/config/config.js +21 -5
- package/dist/core/config/legacy-source-shape-shim.js +79 -0
- package/dist/core/config/schema/embedding.js +2 -2
- package/dist/core/config/schema/engines.js +2 -2
- package/dist/core/config/schema/index-config.js +19 -21
- package/dist/core/config/schema/primitives.js +21 -10
- package/dist/core/config/schema/sources-bundles.js +1 -6
- package/dist/core/errors.js +2 -3
- package/dist/core/improve-types.js +17 -0
- package/dist/core/json-schema.js +1 -11
- package/dist/core/maintenance-barrier.js +17 -2
- package/dist/core/paths.js +12 -15
- package/dist/core/state/migrations.js +28 -0
- package/dist/core/state-db.js +28 -1
- package/dist/core/write-source.js +6 -6
- package/dist/indexer/bundle-identity-guard.js +3 -0
- package/dist/indexer/ensure-index.js +5 -0
- package/dist/indexer/indexer.js +11 -3
- package/dist/indexer/lookup/adapter-concept-owner.js +14 -3
- package/dist/indexer/passes/metadata.js +16 -5
- package/dist/indexer/search/search-fields.js +1 -30
- package/dist/integrations/agent/engine-resolution.js +15 -1
- package/dist/integrations/agent/model-map.js +16 -10
- package/dist/integrations/agent/prompts.js +13 -6
- package/dist/integrations/lockfile.js +22 -7
- package/dist/llm/client.js +16 -0
- package/dist/llm/index-passes.js +3 -2
- package/dist/output/shapes/passthrough.js +9 -3
- package/dist/output/shapes.js +5 -3
- package/dist/output/text/workflow-format.js +8 -1
- package/dist/scripts/akm-migrate-node.js +1461 -1267
- package/dist/scripts/akm-migrate.js +1460 -1266
- package/dist/setup/setup.js +14 -21
- package/dist/sources/include.js +150 -20
- package/dist/sources/providers/git-install.js +14 -12
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/snapshot-fetchers/website-ingest.js +54 -16
- package/dist/sources/website-url.js +12 -4
- package/dist/storage/engines/sqlite-migrations.js +40 -10
- package/dist/storage/like-pattern.js +7 -0
- package/dist/storage/repositories/extract-sessions-repository.js +23 -0
- package/dist/storage/repositories/index-connection.js +27 -10
- package/dist/storage/repositories/index-entry-schema.js +19 -2
- package/dist/storage/repositories/index-schema.js +30 -9
- package/dist/storage/repositories/proposals-repository.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +14 -7
- package/dist/storage/repositories/workflow-runs-repository.js +15 -1
- package/dist/storage/sqlite-read-snapshot.js +11 -9
- package/dist/tasks/backends/cron.js +34 -5
- package/dist/tasks/backends/launchd.js +23 -26
- package/dist/tasks/backends/schtasks.js +50 -3
- package/dist/tasks/frozen-script.js +2 -0
- package/dist/tasks/prepare/prepare.js +2 -7
- package/dist/tasks/prepare/script-capture.js +38 -6
- package/dist/tasks/schedule.js +154 -13
- package/dist/tasks/source/task-source-v3-frozen.js +0 -1
- package/dist/tasks/source/task-source-v4.js +0 -1
- package/dist/workflows/exec/child-workflow.js +2 -3
- package/dist/workflows/exec/exec-unit.js +3 -4
- package/dist/workflows/exec/run-workflow.js +19 -10
- package/dist/workflows/exec/step-work.js +35 -56
- package/dist/workflows/freeze/resolve-steps.js +19 -11
- package/dist/workflows/freeze/source-freeze.js +7 -0
- package/dist/workflows/freeze/targets/child-workflow.js +12 -18
- package/dist/workflows/freeze/targets/command.js +14 -2
- package/dist/workflows/ir/environment-v4.js +4 -2
- package/dist/workflows/ir/freeze-v4.js +2 -5
- package/dist/workflows/ir/plan-hash.js +0 -3
- package/dist/workflows/ir/schema-v4.js +14 -9
- package/dist/workflows/ir/schema.js +1 -3
- package/dist/workflows/resource-limits.js +35 -48
- package/dist/workflows/runtime/plan-classifier.js +89 -41
- package/dist/workflows/runtime/run-outputs.js +1 -21
- package/dist/workflows/runtime/runs.js +76 -150
- package/dist/workflows/source-files.js +28 -54
- package/dist/workflows/source-ir/program.js +2 -2
- package/dist/workflows/source-ir/semantics.js +5 -23
- package/docs/migration/v0.9.1-to-v0.9.2.md +20 -0
- package/docs/reference/cli.md +74 -17
- package/package.json +1 -1
- package/schemas/akm-config.json +5 -10
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { loadConfig } from "../../core/config/config.js";
|
|
6
6
|
import { ConfigError } from "../../core/errors.js";
|
|
7
|
+
import { EXTRACT_INFRASTRUCTURE_SKIP_REASONS } from "../../core/improve-types.js";
|
|
7
8
|
import { listPendingStateMigrations } from "../../core/state-db.js";
|
|
8
9
|
import { withEngineFallback } from "../../integrations/agent/engine-fallback.js";
|
|
9
10
|
import { lookupApiKeyFileValue, resolveEngine } from "../../integrations/agent/engine-resolution.js";
|
|
@@ -11,6 +12,37 @@ import { executionEngineDefinitionsFromConfig } from "../../integrations/agent/e
|
|
|
11
12
|
import { loadModelMap, mergeModelMapLayers, parseModelMapLayer, readInstalledModelMapText, resolveModelMapAlias, userModelMapPath, } from "../../integrations/agent/model-map.js";
|
|
12
13
|
import { resolveImprovePlan } from "../improve/improve-strategies.js";
|
|
13
14
|
import { ACTIVE_RUN_WARN_MS, TASK_FAIL_RATE_WARN } from "./types.js";
|
|
15
|
+
/** Probe one connection's reachability, once per endpoint; `undefined` when no probe seam is supplied. */
|
|
16
|
+
function probeConnectionReachable(connection, deps, cache) {
|
|
17
|
+
if (!deps.probeReachable)
|
|
18
|
+
return Promise.resolve(undefined);
|
|
19
|
+
const key = connection.endpoint.replace(/\/+$/, "");
|
|
20
|
+
let pending = cache.get(key);
|
|
21
|
+
if (!pending) {
|
|
22
|
+
pending = deps.probeReachable(connection);
|
|
23
|
+
cache.set(key, pending);
|
|
24
|
+
}
|
|
25
|
+
return pending;
|
|
26
|
+
}
|
|
27
|
+
function reachabilityEvidence(reach) {
|
|
28
|
+
return reach
|
|
29
|
+
? { probed: true, reachable: reach.reachable, ...(reach.error ? { error: reach.error } : {}) }
|
|
30
|
+
: { probed: false, reachable: null };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* #914: an unreachable LLM engine is a hard `fail` when it is the resolved
|
|
34
|
+
* `default-llm-engine` (extraction and improve depend on it) but only a
|
|
35
|
+
* `warn` elsewhere. Only a reachability warn escalates; a credential warn
|
|
36
|
+
* stays a warn.
|
|
37
|
+
*/
|
|
38
|
+
function escalateDefaultLlmEngineFailure(result) {
|
|
39
|
+
if (result.status === "warn" && result.evidence?.reachable === false) {
|
|
40
|
+
return { ...result, status: "fail" };
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
/** Rolling window the `session-extraction` check reads from the extract ledger (#914). */
|
|
45
|
+
export const SESSION_EXTRACTION_LEDGER_WINDOW_DAYS = 7;
|
|
14
46
|
function credentialAvailable(credential, env, apiKeyFile) {
|
|
15
47
|
if (credential?.required)
|
|
16
48
|
return credential.names.some((name) => Boolean(env[name]?.trim()));
|
|
@@ -21,7 +53,7 @@ function credentialAvailable(credential, env, apiKeyFile) {
|
|
|
21
53
|
return lookupApiKeyFileValue(apiKeyFile) !== undefined;
|
|
22
54
|
return true;
|
|
23
55
|
}
|
|
24
|
-
function runConfiguredEngineProbe(checkName, engineName, config, deps) {
|
|
56
|
+
async function runConfiguredEngineProbe(checkName, engineName, config, deps, reachabilityCache) {
|
|
25
57
|
if (!engineName) {
|
|
26
58
|
return {
|
|
27
59
|
name: checkName,
|
|
@@ -102,52 +134,84 @@ function runConfiguredEngineProbe(checkName, engineName, config, deps) {
|
|
|
102
134
|
fallbackEngine && !fallback ? "configured fallback LLM connection" : undefined,
|
|
103
135
|
!fallbackCredentialAvailable ? "required fallback credential" : undefined,
|
|
104
136
|
].filter((value) => value !== undefined);
|
|
137
|
+
const sdkEvidence = {
|
|
138
|
+
engine: engineName,
|
|
139
|
+
platform: configuredEngine.platform,
|
|
140
|
+
runtimeKind: "sdk",
|
|
141
|
+
binary,
|
|
142
|
+
binaryAvailable,
|
|
143
|
+
package: "@opencode-ai/sdk",
|
|
144
|
+
packageAvailable,
|
|
145
|
+
model: effectiveModel ?? null,
|
|
146
|
+
configuredModel: configuredModel ?? null,
|
|
147
|
+
modelSource: configuredModel ? "sdk" : effectiveModel ? "fallback" : null,
|
|
148
|
+
fallbackEngine: fallbackEngine ?? null,
|
|
149
|
+
fallbackEndpoint: fallback?.connection.endpoint ?? null,
|
|
150
|
+
fallbackModel: fallback?.connection.model ?? null,
|
|
151
|
+
requiredCredentialAvailable: fallbackCredentialAvailable,
|
|
152
|
+
};
|
|
153
|
+
if (missing.length > 0) {
|
|
154
|
+
return {
|
|
155
|
+
name: checkName,
|
|
156
|
+
kind: "deterministic",
|
|
157
|
+
status: "warn",
|
|
158
|
+
confidence: "high",
|
|
159
|
+
message: `SDK engine "${engineName}" is incomplete: missing ${missing.join(", ")}.`,
|
|
160
|
+
evidence: sdkEvidence,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// The SDK's LLM fallback is the only endpoint this engine talks to;
|
|
164
|
+
// probe it once every local precondition above is satisfied (#914).
|
|
165
|
+
const reach = fallback ? await probeConnectionReachable(fallback.connection, deps, reachabilityCache) : undefined;
|
|
105
166
|
return {
|
|
106
167
|
name: checkName,
|
|
107
168
|
kind: "deterministic",
|
|
108
|
-
status:
|
|
169
|
+
status: reach && !reach.reachable ? "warn" : "pass",
|
|
109
170
|
confidence: "high",
|
|
110
|
-
message:
|
|
171
|
+
message: !fallback
|
|
111
172
|
? `SDK engine "${engineName}" is available.`
|
|
112
|
-
:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
binaryAvailable,
|
|
119
|
-
package: "@opencode-ai/sdk",
|
|
120
|
-
packageAvailable,
|
|
121
|
-
model: effectiveModel ?? null,
|
|
122
|
-
configuredModel: configuredModel ?? null,
|
|
123
|
-
modelSource: configuredModel ? "sdk" : effectiveModel ? "fallback" : null,
|
|
124
|
-
fallbackEngine: fallbackEngine ?? null,
|
|
125
|
-
fallbackEndpoint: fallback?.connection.endpoint ?? null,
|
|
126
|
-
fallbackModel: fallback?.connection.model ?? null,
|
|
127
|
-
requiredCredentialAvailable: fallbackCredentialAvailable,
|
|
128
|
-
},
|
|
173
|
+
: !reach
|
|
174
|
+
? `SDK engine "${engineName}" is available. Reachability was not probed.`
|
|
175
|
+
: reach.reachable
|
|
176
|
+
? `SDK engine "${engineName}" is available and its LLM fallback is reachable.`
|
|
177
|
+
: `SDK engine "${engineName}" is available, but its LLM fallback is not reachable: ${reach.error ?? "unknown error"}`,
|
|
178
|
+
evidence: fallback ? { ...sdkEvidence, ...reachabilityEvidence(reach) } : sdkEvidence,
|
|
129
179
|
};
|
|
130
180
|
}
|
|
131
181
|
try {
|
|
132
182
|
const runner = (deps.resolveEngine ?? resolveEngine)(engineName, config);
|
|
133
183
|
if (runner.kind === "llm") {
|
|
134
184
|
const requiredCredentialAvailable = credentialAvailable(runner.credential, env, runner.apiKeyFile);
|
|
185
|
+
const llmEvidence = {
|
|
186
|
+
engine: engineName,
|
|
187
|
+
platform: null,
|
|
188
|
+
runtimeKind: "llm",
|
|
189
|
+
model: runner.connection.model,
|
|
190
|
+
endpoint: runner.connection.endpoint,
|
|
191
|
+
requiredCredentialAvailable,
|
|
192
|
+
};
|
|
193
|
+
if (!requiredCredentialAvailable) {
|
|
194
|
+
return {
|
|
195
|
+
name: checkName,
|
|
196
|
+
kind: "deterministic",
|
|
197
|
+
status: "warn",
|
|
198
|
+
confidence: "high",
|
|
199
|
+
message: `LLM engine "${engineName}" is configured, but its required credential is unavailable.`,
|
|
200
|
+
evidence: llmEvidence,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const reach = await probeConnectionReachable(runner.connection, deps, reachabilityCache);
|
|
135
204
|
return {
|
|
136
205
|
name: checkName,
|
|
137
206
|
kind: "deterministic",
|
|
138
|
-
status:
|
|
207
|
+
status: reach && !reach.reachable ? "warn" : "pass",
|
|
139
208
|
confidence: "high",
|
|
140
|
-
message:
|
|
141
|
-
? `LLM engine "${engineName}" is configured.`
|
|
142
|
-
:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
runtimeKind: "llm",
|
|
147
|
-
model: runner.connection.model,
|
|
148
|
-
endpoint: runner.connection.endpoint,
|
|
149
|
-
requiredCredentialAvailable,
|
|
150
|
-
},
|
|
209
|
+
message: !reach
|
|
210
|
+
? `LLM engine "${engineName}" is configured. Reachability was not probed.`
|
|
211
|
+
: reach.reachable
|
|
212
|
+
? `LLM engine "${engineName}" is configured and reachable.`
|
|
213
|
+
: `LLM engine "${engineName}" is not reachable: ${reach.error ?? "unknown error"}`,
|
|
214
|
+
evidence: { ...llmEvidence, ...reachabilityEvidence(reach) },
|
|
151
215
|
};
|
|
152
216
|
}
|
|
153
217
|
const profile = runner.profile;
|
|
@@ -198,8 +262,10 @@ function projectEngineProbe(result, name) {
|
|
|
198
262
|
}
|
|
199
263
|
function projectSelectedEngineProbe(availability, engineName, checkName) {
|
|
200
264
|
const result = availability.get(engineName);
|
|
201
|
-
if (result)
|
|
202
|
-
|
|
265
|
+
if (result) {
|
|
266
|
+
const projected = projectEngineProbe(result, checkName);
|
|
267
|
+
return checkName === "default-llm-engine" ? escalateDefaultLlmEngineFailure(projected) : projected;
|
|
268
|
+
}
|
|
203
269
|
return {
|
|
204
270
|
name: checkName,
|
|
205
271
|
kind: "deterministic",
|
|
@@ -245,30 +311,36 @@ function configuredEnginesProjection(engineNames, availability) {
|
|
|
245
311
|
evidence: { engines },
|
|
246
312
|
};
|
|
247
313
|
}
|
|
248
|
-
export function runDefaultEngineProbe(deps = {}) {
|
|
314
|
+
export async function runDefaultEngineProbe(deps = {}) {
|
|
249
315
|
// Probe the effective view: an install with no `defaults.engine` but a usable
|
|
250
316
|
// opencode binary DOES have a working default, and reporting otherwise would
|
|
251
317
|
// contradict what `workflow run` / `task run` actually do.
|
|
252
318
|
const { config } = withEngineFallback(deps.loadConfig?.() ?? loadConfig(), deps.which);
|
|
253
|
-
return runConfiguredEngineProbe("default-engine", config.defaults?.engine, config, deps);
|
|
319
|
+
return runConfiguredEngineProbe("default-engine", config.defaults?.engine, config, deps, new Map());
|
|
254
320
|
}
|
|
255
|
-
export function runDefaultLlmEngineProbe(deps = {}) {
|
|
321
|
+
export async function runDefaultLlmEngineProbe(deps = {}) {
|
|
256
322
|
const config = deps.loadConfig?.() ?? loadConfig();
|
|
257
|
-
|
|
323
|
+
const engineName = config.defaults?.llmEngine;
|
|
324
|
+
if (!engineName)
|
|
325
|
+
return unconfiguredEngineProbe("default-llm-engine");
|
|
326
|
+
const result = await runConfiguredEngineProbe("configured-engine", engineName, config, deps, new Map());
|
|
327
|
+
return projectSelectedEngineProbe(new Map([[engineName, result]]), engineName, "default-llm-engine");
|
|
258
328
|
}
|
|
259
329
|
/** Probe every explicitly configured engine without exposing connection or model material. */
|
|
260
|
-
export function runConfiguredEnginesProbe(deps = {}) {
|
|
330
|
+
export async function runConfiguredEnginesProbe(deps = {}) {
|
|
261
331
|
const config = deps.loadConfig?.() ?? loadConfig();
|
|
262
332
|
const engineNames = Object.keys(config.engines ?? {}).sort();
|
|
263
|
-
const
|
|
333
|
+
const cache = new Map();
|
|
334
|
+
const availability = new Map(await Promise.all(engineNames.map(async (engine) => [engine, await runConfiguredEngineProbe("configured-engine", engine, config, deps, cache)])));
|
|
264
335
|
return configuredEnginesProjection(engineNames, availability);
|
|
265
336
|
}
|
|
266
337
|
/**
|
|
267
338
|
* Build the per-health-run availability snapshot. Each distinct selected or
|
|
268
|
-
* explicitly configured engine is probed once
|
|
269
|
-
*
|
|
339
|
+
* explicitly configured engine is probed once (reachability probes further
|
|
340
|
+
* de-duped by endpoint, see {@link ReachabilityCache}); the three public
|
|
341
|
+
* checks are projections of that immutable result set.
|
|
270
342
|
*/
|
|
271
|
-
export function runHealthEngineProbes(deps = {}) {
|
|
343
|
+
export async function runHealthEngineProbes(deps = {}) {
|
|
272
344
|
const configured = deps.loadConfig?.() ?? loadConfig();
|
|
273
345
|
const effective = withEngineFallback(configured, deps.which).config;
|
|
274
346
|
const defaultEngineName = effective.defaults?.engine;
|
|
@@ -277,7 +349,8 @@ export function runHealthEngineProbes(deps = {}) {
|
|
|
277
349
|
const probeNames = [...new Set([...explicitEngineNames, defaultEngineName, defaultLlmEngineName])]
|
|
278
350
|
.filter((name) => name !== undefined)
|
|
279
351
|
.sort();
|
|
280
|
-
const
|
|
352
|
+
const cache = new Map();
|
|
353
|
+
const availability = new Map(await Promise.all(probeNames.map(async (engine) => [engine, await runConfiguredEngineProbe("configured-engine", engine, effective, deps, cache)])));
|
|
281
354
|
return Object.freeze({
|
|
282
355
|
defaultEngine: defaultEngineName
|
|
283
356
|
? projectSelectedEngineProbe(availability, defaultEngineName, "default-engine")
|
|
@@ -314,17 +387,32 @@ export function runActiveImproveStrategyProbe(deps = {}) {
|
|
|
314
387
|
unavailableProcesses.push("triage.judgment");
|
|
315
388
|
}
|
|
316
389
|
}
|
|
390
|
+
// #913: name the engine each process actually resolved to, so a
|
|
391
|
+
// strategy-level `engine` pin that shadows `defaults.llmEngine` is
|
|
392
|
+
// visible on `akm health` instead of requiring config archaeology.
|
|
393
|
+
const engines = {};
|
|
394
|
+
for (const [name, process] of Object.entries(plan.processes)) {
|
|
395
|
+
if (process.enabled && process.runner)
|
|
396
|
+
engines[name] = process.runner.engine;
|
|
397
|
+
}
|
|
398
|
+
if (plan.triageJudgment)
|
|
399
|
+
engines["triage.judgment"] = plan.triageJudgment.engine;
|
|
400
|
+
const engineList = Object.entries(engines)
|
|
401
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
402
|
+
.map(([process, engine]) => `${process}: "${engine}"`)
|
|
403
|
+
.join(", ");
|
|
317
404
|
return {
|
|
318
405
|
name: "active-improve-strategy",
|
|
319
406
|
kind: "deterministic",
|
|
320
407
|
status: unavailableProcesses.length === 0 ? "pass" : "warn",
|
|
321
408
|
confidence: "high",
|
|
322
409
|
message: unavailableProcesses.length === 0
|
|
323
|
-
? `Active improve strategy "${plan.strategy.name}" has available process engines.`
|
|
324
|
-
: `Active improve strategy "${plan.strategy.name}" has unavailable required credentials for: ${unavailableProcesses.join(", ")}.`,
|
|
410
|
+
? `Active improve strategy "${plan.strategy.name}" has available process engines${engineList ? ` (${engineList})` : ""}.`
|
|
411
|
+
: `Active improve strategy "${plan.strategy.name}" has unavailable required credentials for: ${unavailableProcesses.join(", ")}${engineList ? ` (engines: ${engineList})` : ""}.`,
|
|
325
412
|
evidence: {
|
|
326
413
|
strategy: plan.strategy.name,
|
|
327
414
|
unavailableProcesses,
|
|
415
|
+
engines,
|
|
328
416
|
},
|
|
329
417
|
};
|
|
330
418
|
}
|
|
@@ -437,8 +525,11 @@ export function runSelectedModelAliasesProbe(deps = {}) {
|
|
|
437
525
|
const outcomes = selected.map(({ engine, alias, modelMapKey }) => {
|
|
438
526
|
try {
|
|
439
527
|
const resolution = resolveModelMapAlias(alias, modelMapKey, modelMap.map);
|
|
440
|
-
|
|
441
|
-
|
|
528
|
+
if (resolution.interpretation === "alias") {
|
|
529
|
+
return { kind: "alias", evidence: { engine, alias, modelMapKey } };
|
|
530
|
+
}
|
|
531
|
+
return resolution.unmappedForEngine
|
|
532
|
+
? { kind: "missing", evidence: { engine, alias, modelMapKey } }
|
|
442
533
|
: { kind: "exact" };
|
|
443
534
|
}
|
|
444
535
|
catch {
|
|
@@ -664,34 +755,58 @@ export const HEALTH_CHECKS = [
|
|
|
664
755
|
},
|
|
665
756
|
},
|
|
666
757
|
{
|
|
758
|
+
// #914: derived from the `extract_sessions_seen` LEDGER, not
|
|
759
|
+
// `improve_runs.result_json` — the hook-driven `akm proposal extract
|
|
760
|
+
// --session-id ...` (the standard Claude Code plugin setup) never writes
|
|
761
|
+
// an `improve_runs` row, so the old improve_runs-only source reported
|
|
762
|
+
// "not active" as `pass` unconditionally on a plugin-driven machine, no
|
|
763
|
+
// matter how healthy or broken extraction actually was.
|
|
667
764
|
name: "session-extraction",
|
|
668
765
|
channel: "advisory",
|
|
669
766
|
run: (ctx) => {
|
|
670
|
-
const
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
sxWarnReasons.push(`${sx.warnings} harness error(s)`);
|
|
674
|
-
if (sx.ran && sx.sessionsScanned >= 5 && sx.proposalsCreated === 0)
|
|
675
|
-
sxWarnReasons.push("no proposals generated across scanned sessions");
|
|
676
|
-
return {
|
|
767
|
+
const { since: ledgerSince, rows } = ctx.sessionExtractionLedger;
|
|
768
|
+
const days = SESSION_EXTRACTION_LEDGER_WINDOW_DAYS;
|
|
769
|
+
const base = {
|
|
677
770
|
name: "session-extraction",
|
|
678
771
|
kind: "heuristic",
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
772
|
+
confidence: "medium",
|
|
773
|
+
evidence: { ledgerSince, ledgerRows: rows },
|
|
774
|
+
};
|
|
775
|
+
const totalRows = rows.reduce((sum, row) => sum + row.count, 0);
|
|
776
|
+
if (totalRows === 0) {
|
|
777
|
+
return { ...base, status: "unknown", message: `No extraction recorded in the last ${days} days.` };
|
|
778
|
+
}
|
|
779
|
+
// `read_failed` / `exception` land in the ledger as `failed`, not `skipped`.
|
|
780
|
+
const nothingHarvested = rows.every((row) => row.outcome === "skipped" || row.outcome === "failed");
|
|
781
|
+
const knownSkipReasons = [...new Set(rows.flatMap((row) => (row.skipReason ? [row.skipReason] : [])))].sort();
|
|
782
|
+
const allKnownReasonsAreInfra = knownSkipReasons.every((reason) => EXTRACT_INFRASTRUCTURE_SKIP_REASONS.includes(reason));
|
|
783
|
+
if (nothingHarvested && allKnownReasonsAreInfra) {
|
|
784
|
+
const reasonClauses = knownSkipReasons.map((reason) => {
|
|
785
|
+
const engines = [
|
|
786
|
+
...new Set(rows.flatMap((row) => (row.skipReason === reason && row.engine ? [row.engine] : []))),
|
|
787
|
+
].sort();
|
|
788
|
+
return engines.length > 0 ? `${reason} (engine ${engines.map((e) => `"${e}"`).join(", ")})` : reason;
|
|
789
|
+
});
|
|
790
|
+
if (rows.some((row) => row.skipReason === null))
|
|
791
|
+
reasonClauses.push("unknown reason");
|
|
792
|
+
const verb = rows.some((row) => row.outcome === "failed") ? "skipped or failed" : "skipped";
|
|
793
|
+
return {
|
|
794
|
+
...base,
|
|
795
|
+
status: "warn",
|
|
796
|
+
message: `${totalRows} of ${totalRows} sessions in the last ${days} days ${verb}: ${reasonClauses.join(", ")}.`,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
const outcomeCounts = new Map();
|
|
800
|
+
for (const row of rows)
|
|
801
|
+
outcomeCounts.set(row.outcome, (outcomeCounts.get(row.outcome) ?? 0) + row.count);
|
|
802
|
+
const outcomeSummary = [...outcomeCounts.entries()]
|
|
803
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
804
|
+
.map(([outcome, count]) => `${outcome}: ${count}`)
|
|
805
|
+
.join(", ");
|
|
806
|
+
return {
|
|
807
|
+
...base,
|
|
808
|
+
status: "pass",
|
|
809
|
+
message: `Session extraction active in the last ${days} days (${outcomeSummary}).`,
|
|
695
810
|
};
|
|
696
811
|
},
|
|
697
812
|
},
|
package/dist/commands/health.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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 { resolveStashDir } from "../core/common.js";
|
|
6
|
+
import { daysToMs, resolveStashDir } from "../core/common.js";
|
|
7
7
|
import { loadConfig } from "../core/config/config.js";
|
|
8
8
|
import { ConfigError, UsageError } from "../core/errors.js";
|
|
9
9
|
import { readEvents } from "../core/events.js";
|
|
@@ -12,12 +12,14 @@ import { classifyPathAccess, describeInaccessiblePath } from "../core/path-acces
|
|
|
12
12
|
import { getConfigPath, getDataDir, getDbPath, getStateDbPathInDataDir } from "../core/paths.js";
|
|
13
13
|
import { listExistingTableNames, listPendingStateMigrations, openStateDatabase } from "../core/state-db.js";
|
|
14
14
|
import { DURATION_UNITS, parseDuration, parseSinceToIso } from "../core/time.js";
|
|
15
|
+
import { probeLlmEndpoint } from "../llm/client.js";
|
|
16
|
+
import { getExtractOutcomeCountsSince } from "../storage/repositories/extract-sessions-repository.js";
|
|
15
17
|
import { closeDatabase, openReadonlyExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
16
18
|
import { getAllEntries } from "../storage/repositories/index-entries-repository.js";
|
|
17
19
|
import { queryTaskHistory } from "../storage/repositories/task-history-repository.js";
|
|
18
20
|
import { pkgVersion } from "../version.js";
|
|
19
21
|
import { collectImproveAdvisories } from "./health/advisories.js";
|
|
20
|
-
import { HEALTH_CHECKS, runHealthEngineProbes, runPendingStateMigrationsCheck, } from "./health/checks.js";
|
|
22
|
+
import { HEALTH_CHECKS, runHealthEngineProbes, runPendingStateMigrationsCheck, SESSION_EXTRACTION_LEDGER_WINDOW_DAYS, } from "./health/checks.js";
|
|
21
23
|
import { collectDataDirUsageAdvisory } from "./health/data-dir-usage.js";
|
|
22
24
|
import { buildImproveSkipSummary, computeWallTimeStats, isAgentTaskHistoryRow, roundRate, summarizeImproveCompleted, summarizeImproveRuns, taskFailureDetail, } from "./health/improve-metrics.js";
|
|
23
25
|
import { emptyLlmUsageAggregate, readLlmUsageAggregate } from "./health/llm-usage.js";
|
|
@@ -166,6 +168,11 @@ function gatherEgressConfigPhase() {
|
|
|
166
168
|
}
|
|
167
169
|
return { egressConfigView };
|
|
168
170
|
}
|
|
171
|
+
/** Extract-ledger outcome counts for the `session-extraction` check's window, independent of `--since`. */
|
|
172
|
+
function gatherSessionExtractionLedgerPhase(db, now) {
|
|
173
|
+
const since = new Date(now() - daysToMs(SESSION_EXTRACTION_LEDGER_WINDOW_DAYS)).toISOString();
|
|
174
|
+
return { since, rows: getExtractOutcomeCountsSince(db, since) };
|
|
175
|
+
}
|
|
169
176
|
/**
|
|
170
177
|
* Assemble the window's improve-pipeline summary: invoked/completed/skipped
|
|
171
178
|
* counts from events, the per-run result_json aggregate, wall-time stats, and
|
|
@@ -466,7 +473,7 @@ function unreadableStateDbCheck(detail) {
|
|
|
466
473
|
evidence: { detail },
|
|
467
474
|
};
|
|
468
475
|
}
|
|
469
|
-
export function akmHealth(options = {}) {
|
|
476
|
+
export async function akmHealth(options = {}) {
|
|
470
477
|
validateAkmHealthOptions(options);
|
|
471
478
|
const now = options.now ?? (() => Date.now());
|
|
472
479
|
const since = parseHealthSince(options.since);
|
|
@@ -519,12 +526,16 @@ export function akmHealth(options = {}) {
|
|
|
519
526
|
logsDb = undefined;
|
|
520
527
|
}
|
|
521
528
|
try {
|
|
529
|
+
// Network probes overlap the local database phases below; awaited where consumed.
|
|
530
|
+
const engineProbesPromise = runHealthEngineProbes({ probeReachable: options.probe ? probeLlmEndpoint : undefined });
|
|
531
|
+
engineProbesPromise.catch(() => undefined);
|
|
522
532
|
const taskHistory = gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now);
|
|
523
533
|
const { tableNames, missingTables, probe } = taskHistory;
|
|
524
534
|
const { egressConfigView } = gatherEgressConfigPhase();
|
|
525
535
|
const { improveSummary } = gatherImproveSummaryPhase(db, stateDbPath, since, now);
|
|
526
536
|
advisories.push(...gatherAncillaryAdvisories(db, stateDbPath, since, improveSummary, options, egressConfigView));
|
|
527
|
-
const
|
|
537
|
+
const sessionExtractionLedger = gatherSessionExtractionLedgerPhase(db, now);
|
|
538
|
+
const engineProbes = await engineProbesPromise;
|
|
528
539
|
// Run the ordered health-check registry. Each check projects the shared
|
|
529
540
|
// context computed above into one HealthCheckResult; `channel` routes it to
|
|
530
541
|
// hardChecks or advisories. Declaration order in HEALTH_CHECKS is the
|
|
@@ -544,6 +555,7 @@ export function akmHealth(options = {}) {
|
|
|
544
555
|
stuckActiveTasks: taskHistory.stuckActiveTasks,
|
|
545
556
|
worstTaskFailRate: taskHistory.worstTaskFailRate,
|
|
546
557
|
sessionExtraction: improveSummary.sessionExtraction,
|
|
558
|
+
sessionExtractionLedger,
|
|
547
559
|
autoAccept: improveSummary.autoAccept,
|
|
548
560
|
engineProbes,
|
|
549
561
|
};
|
|
@@ -200,7 +200,7 @@ async function runQualityJudge(feature, config, prompt, chat, options = {}) {
|
|
|
200
200
|
parsed.score < 1 ||
|
|
201
201
|
parsed.score > 5 ||
|
|
202
202
|
typeof parsed.reason !== "string") {
|
|
203
|
-
return { pass: false, score: -1, reason: "judge parse failed —
|
|
203
|
+
return { pass: false, score: -1, reason: "judge parse failed — routed to review", reviewNeeded: true };
|
|
204
204
|
}
|
|
205
205
|
// D-5 / #388: Three-band system (MT-Bench arXiv:2306.05685 — ~±0.5 judge variance).
|
|
206
206
|
// >= 3.5: auto-queue as pending (pass: true)
|
|
@@ -219,7 +219,7 @@ async function runQualityJudge(feature, config, prompt, chat, options = {}) {
|
|
|
219
219
|
// content verdict. Provider/runtime failures retain the fail-closed result.
|
|
220
220
|
if (error instanceof ConfigError)
|
|
221
221
|
throw error;
|
|
222
|
-
return { pass: false, score: -1, reason: "judge timeout/error —
|
|
222
|
+
return { pass: false, score: -1, reason: "judge timeout/error — routed to review", reviewNeeded: true };
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
/**
|
|
@@ -778,7 +778,7 @@ export async function akmDistill(options) {
|
|
|
778
778
|
eventsCtx: options.eventsCtx,
|
|
779
779
|
}));
|
|
780
780
|
}
|
|
781
|
-
const
|
|
781
|
+
const assembled = assembleAndValidateDistillContent({
|
|
782
782
|
raw,
|
|
783
783
|
effectiveProposalKind,
|
|
784
784
|
inputRef,
|
|
@@ -789,7 +789,11 @@ export async function akmDistill(options) {
|
|
|
789
789
|
filteredFeedbackCount,
|
|
790
790
|
eligMeta,
|
|
791
791
|
eventsCtx: options.eventsCtx,
|
|
792
|
+
stash,
|
|
792
793
|
});
|
|
794
|
+
if ("rejection" in assembled)
|
|
795
|
+
return withNotices(assembled.rejection);
|
|
796
|
+
const { content, descriptionSwapped } = assembled;
|
|
793
797
|
const gate = await applyDistillQualityGate({
|
|
794
798
|
config,
|
|
795
799
|
options,
|
|
@@ -968,7 +972,7 @@ async function emitDistillLessonProposal(args) {
|
|
|
968
972
|
* throwing a `UsageError` on any finding. Extracted verbatim from `akmDistill`.
|
|
969
973
|
*/
|
|
970
974
|
function assembleAndValidateDistillContent(args) {
|
|
971
|
-
const { raw, effectiveProposalKind, inputRef, durableInputRef, itemRef, effectiveLessonRef, exclusionSet, filteredFeedbackCount, eligMeta, eventsCtx, } = args;
|
|
975
|
+
const { raw, effectiveProposalKind, inputRef, durableInputRef, itemRef, effectiveLessonRef, exclusionSet, filteredFeedbackCount, eligMeta, eventsCtx, stash, } = args;
|
|
972
976
|
// Structured-output path: when the provider honoured the JSON schema, `raw`
|
|
973
977
|
// is a JSON object string (not a markdown blob). Try to parse it and assemble
|
|
974
978
|
// the canonical `---\nfm\n---\n\nbody` form before using the markdown
|
|
@@ -1003,18 +1007,19 @@ function assembleAndValidateDistillContent(args) {
|
|
|
1003
1007
|
content = repairLessonDescriptionTruncation(content);
|
|
1004
1008
|
}
|
|
1005
1009
|
// Parse + lint the lesson before creating the proposal. The lint is the
|
|
1006
|
-
// canonical gate for required frontmatter (v1 spec §13)
|
|
1007
|
-
//
|
|
1008
|
-
// `distill_invoked` so
|
|
1009
|
-
|
|
1010
|
+
// canonical gate for required frontmatter (v1 spec §13): a field that is
|
|
1011
|
+
// genuinely missing or empty means there is no valid asset to write, so
|
|
1012
|
+
// that failure stays a hard reject — but still emit `distill_invoked` so
|
|
1013
|
+
// the failure is observable.
|
|
1014
|
+
const structuralFindings = effectiveProposalKind === "knowledge"
|
|
1010
1015
|
? validateKnowledgeContent(content, inputRef)
|
|
1011
1016
|
: lintLessonContent(content, `distill:${inputRef}`).findings;
|
|
1012
1017
|
// Additional lesson-only quality validators — reject the systematic failure
|
|
1013
1018
|
// modes seen across 323 archived rejected proposals (see distill/content-repair).
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
if (
|
|
1019
|
+
const qualityFindings = effectiveProposalKind !== "knowledge" && structuralFindings.length === 0
|
|
1020
|
+
? collectLessonQualityFindings(content, inputRef)
|
|
1021
|
+
: [];
|
|
1022
|
+
if (structuralFindings.length > 0) {
|
|
1018
1023
|
appendEvent({
|
|
1019
1024
|
eventType: "distill_invoked",
|
|
1020
1025
|
// Use item_ref when resolved, otherwise the input conceptId.
|
|
@@ -1023,16 +1028,27 @@ function assembleAndValidateDistillContent(args) {
|
|
|
1023
1028
|
outcome: "validation_failed",
|
|
1024
1029
|
proposalRef: effectiveLessonRef,
|
|
1025
1030
|
proposalKind: effectiveProposalKind,
|
|
1026
|
-
findingKinds:
|
|
1031
|
+
findingKinds: structuralFindings.map((f) => f.kind),
|
|
1027
1032
|
...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
|
|
1028
1033
|
...eligMeta,
|
|
1029
1034
|
},
|
|
1030
1035
|
}, eventsCtx);
|
|
1031
|
-
const message =
|
|
1036
|
+
const message = structuralFindings.map((f) => f.message).join("\n");
|
|
1032
1037
|
throw new UsageError(`Distilled ${effectiveProposalKind} failed validation:\n${message}`, "MISSING_REQUIRED_ARGUMENT", effectiveProposalKind === "knowledge"
|
|
1033
1038
|
? "Knowledge proposals require a non-empty markdown body."
|
|
1034
1039
|
: "Lessons require non-empty `description` and `when_to_use` frontmatter fields. See v1 spec §13.");
|
|
1035
1040
|
}
|
|
1041
|
+
if (qualityFindings.length > 0) {
|
|
1042
|
+
return {
|
|
1043
|
+
rejection: writeQualityRejection(stash, inputRef, effectiveLessonRef, content, 2.0, // below auto-accept threshold, signals review needed — no judge score exists for a structural/heuristic finding
|
|
1044
|
+
qualityFindings.map((f) => f.message).join("\n"), {
|
|
1045
|
+
reviewNeeded: true,
|
|
1046
|
+
proposalKind: effectiveProposalKind,
|
|
1047
|
+
findingKinds: qualityFindings.map((f) => f.kind),
|
|
1048
|
+
...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
|
|
1049
|
+
}, eligMeta.eligibilitySource, eventsCtx),
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1036
1052
|
return { content, descriptionSwapped };
|
|
1037
1053
|
}
|
|
1038
1054
|
/**
|
|
@@ -2,7 +2,6 @@
|
|
|
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
4
|
import { deepMergeConfig } from "../../core/config/deep-merge.js";
|
|
5
|
-
import { ConfigError } from "../../core/errors.js";
|
|
6
5
|
import { lowerResolvedExecutionRequest } from "../../integrations/agent/execution-lowering.js";
|
|
7
6
|
import { prepareInlineExecution } from "../../integrations/agent/inline-execution.js";
|
|
8
7
|
function own(value, key) {
|
|
@@ -51,7 +50,7 @@ export function resolveImproveLlmExecution(options) {
|
|
|
51
50
|
if (!resolved)
|
|
52
51
|
return null;
|
|
53
52
|
if (resolved.runner.kind !== "llm") {
|
|
54
|
-
|
|
53
|
+
return null;
|
|
55
54
|
}
|
|
56
55
|
return { runner: resolved.runner, notices: resolved.notices };
|
|
57
56
|
}
|