akm-cli 0.9.2 → 0.9.3
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 +37 -0
- package/dist/core/config/config.js +19 -3
- package/dist/core/extra-params.js +115 -1
- package/dist/scripts/akm-migrate-node.js +110 -11
- package/dist/scripts/akm-migrate.js +110 -11
- package/dist/tasks/run/run-native-task.js +28 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,43 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.3] - 2026-08-29
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **A failing Windows `shell: powershell`/`pwsh` task could lose its exit
|
|
14
|
+
code's fidelity through `-Command` (#845).** `-Command` derives its own
|
|
15
|
+
process exit code from `$?`, so a genuinely failing command already
|
|
16
|
+
produced a nonzero exit and `status: "failed"` — but any native exit code
|
|
17
|
+
outside `{0, 1}` was collapsed to `1`, discarding the real value reported
|
|
18
|
+
in task history. The inner shell invocation built by `shellCommand()` now
|
|
19
|
+
appends a guard that reads `$?` first (reproducing `-Command`'s own
|
|
20
|
+
completed/failed determination, immune to a stale `$LASTEXITCODE` from an
|
|
21
|
+
earlier native call in the same command) and only then upgrades to the
|
|
22
|
+
precise native exit code when the failing last statement actually set
|
|
23
|
+
one — a bare `exit $LASTEXITCODE` was rejected because `$LASTEXITCODE`
|
|
24
|
+
stays `$null` for a pure-PowerShell command, and `exit $null` is exit
|
|
25
|
+
code `0`, which would have turned a failed cmdlet into a false
|
|
26
|
+
`"completed"`. Verified via unit tests asserting the exact argv `-Command`
|
|
27
|
+
string on `platform: "win32"`; the runtime exit-code behavior itself is
|
|
28
|
+
unverified on a real Windows host pending the owner's manual run (see the
|
|
29
|
+
PR body for the procedure) — Windows scheduler paths still have no
|
|
30
|
+
automated coverage (#770).
|
|
31
|
+
- **A valid 0.9.1 config hard-failed to load after upgrading to 0.9.2
|
|
32
|
+
(#852).** `reasoningEffort` became a first-class `engines.<name>` field in
|
|
33
|
+
0.9.2 (#815), so `extraParams.reasoning_effort` — the documented 0.9.1
|
|
34
|
+
workaround for LM Studio, where `enableThinking` is a no-op — started
|
|
35
|
+
tripping the extraParams protected-key check and hard-failing every
|
|
36
|
+
command that loads config, with no migration. Config load now lifts
|
|
37
|
+
`extraParams.{reasoning_effort,temperature,maxtokens,enablethinking}`
|
|
38
|
+
onto their first-class field automatically (in-memory only; the file is
|
|
39
|
+
not rewritten, and a warning names each lifted key) unless the
|
|
40
|
+
extraParams value and the first-class field disagree, in which case
|
|
41
|
+
config load still fails, now naming both values and the field to keep.
|
|
42
|
+
Any other protected `extraParams` key without a first-class equivalent
|
|
43
|
+
still hard-fails, but the error now names the fix instead of only the
|
|
44
|
+
rule.
|
|
45
|
+
|
|
9
46
|
## [0.9.2] - 2026-08-29
|
|
10
47
|
|
|
11
48
|
### Fixed
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { ConfigError } from "../errors.js";
|
|
7
|
+
import { liftLegacyEngineExtraParams } from "../extra-params.js";
|
|
7
8
|
import { acquireConfigLock, backupExistingConfig, parseConfigText, readConfigText, withConfigLock, writeConfigAtomic, } from "./config-io.js";
|
|
8
9
|
import { AkmConfigSchema, CURRENT_CONFIG_VERSION } from "./config-schema.js";
|
|
9
10
|
import { bundlesToSourceEntries } from "./config-sources.js";
|
|
@@ -141,14 +142,29 @@ export function acquireConfigReadFence() {
|
|
|
141
142
|
* canonical shape before defaults are merged.
|
|
142
143
|
*/
|
|
143
144
|
export function parseAndValidateConfigText(text, sourcePath) {
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
145
|
+
const parsedRaw = parseConfigText(text, sourcePath);
|
|
146
|
+
if (parsedRaw.configVersion !== CURRENT_CONFIG_VERSION) {
|
|
146
147
|
throw new ConfigError(`Unsupported configVersion${sourcePath ? ` at ${sourcePath}` : ""}: expected "${CURRENT_CONFIG_VERSION}".`, "UNSUPPORTED_CONFIG_VERSION", "Recreate engines and improve.strategies manually for AKM 0.9.0; profile-based configuration is not translated automatically.");
|
|
147
148
|
}
|
|
149
|
+
// #852 (following #815): lift legacy `extraParams` keys — e.g.
|
|
150
|
+
// `reasoning_effort`, a documented 0.9.1 workaround — onto the first-class
|
|
151
|
+
// engine field they now shadow, before the protected-key check in
|
|
152
|
+
// `ExtraParamsSchema` gets a chance to hard-reject them. In-memory only;
|
|
153
|
+
// never written back to the file.
|
|
154
|
+
const { config: raw, lifted, conflicts } = liftLegacyEngineExtraParams(parsedRaw);
|
|
155
|
+
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
156
|
+
if (conflicts.length > 0) {
|
|
157
|
+
const lines = conflicts
|
|
158
|
+
.map((c) => ` - engines.${c.engine}.extraParams.${c.key} (${JSON.stringify(c.extraParamsValue)}) conflicts with engines.${c.engine}.${c.field} (${JSON.stringify(c.fieldValue)})`)
|
|
159
|
+
.join("\n");
|
|
160
|
+
throw new ConfigError(`Invalid config${where}: extraParams and the first-class field disagree:\n${lines}\n\nEach extraParams key above has a first-class equivalent and akm will not guess which value you meant — remove the extraParams entry once the field carries the value you want.`, "INVALID_CONFIG_FILE");
|
|
161
|
+
}
|
|
162
|
+
if (lifted.length > 0) {
|
|
163
|
+
warn(`Config${where} uses deprecated extraParams keys with first-class equivalents; treating them as the first-class fields for this run (not written back to the file):\n - ${lifted.join("\n - ")}`);
|
|
164
|
+
}
|
|
148
165
|
const parsed = AkmConfigSchema.safeParse(raw);
|
|
149
166
|
if (!parsed.success) {
|
|
150
167
|
const lines = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
151
|
-
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
152
168
|
throw new ConfigError(`Invalid config${where}:\n${lines}`, "INVALID_CONFIG_FILE");
|
|
153
169
|
}
|
|
154
170
|
const merged = deepMergeConfig(DEFAULT_CONFIG, parsed.data);
|
|
@@ -25,6 +25,46 @@ export const EXTRA_PARAMS_CREDENTIAL_KEYS = [
|
|
|
25
25
|
];
|
|
26
26
|
const PROTECTED_TOP_LEVEL_KEYS = new Set(EXTRA_PARAMS_PROTECTED_TOP_LEVEL_KEYS);
|
|
27
27
|
const CREDENTIAL_KEYS = new Set(EXTRA_PARAMS_CREDENTIAL_KEYS);
|
|
28
|
+
// ── Protected-key remedies (#852) ───────────────────────────────────────────
|
|
29
|
+
//
|
|
30
|
+
// `EXTRA_PARAMS_PROTECTED_TOP_LEVEL_KEYS` rightly stops a provider extra from
|
|
31
|
+
// shadowing an AKM-managed field, but naming the rule without naming the fix
|
|
32
|
+
// leaves the reader stuck (#852). Every protected key gets a one-line remedy
|
|
33
|
+
// baked into its issue message; keys with a genuine scalar first-class field
|
|
34
|
+
// are also eligible for the automatic config-load lift below.
|
|
35
|
+
/** normalizeExtraParamKey(key) -> the first-class engine field it shadows. */
|
|
36
|
+
const LEGACY_EXTRA_PARAMS_FIELD = {
|
|
37
|
+
model: "model",
|
|
38
|
+
temperature: "temperature",
|
|
39
|
+
maxtokens: "maxTokens",
|
|
40
|
+
enablethinking: "enableThinking",
|
|
41
|
+
reasoningeffort: "reasoningEffort",
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Subset of {@link LEGACY_EXTRA_PARAMS_FIELD} that {@link liftLegacyEngineExtraParams}
|
|
45
|
+
* will move onto the first-class field automatically. `model` is deliberately
|
|
46
|
+
* excluded: unlike the others it was never a "no first-class field yet"
|
|
47
|
+
* workaround (`model` has always been required on an LLM engine), so a
|
|
48
|
+
* mismatch is far more likely a genuine mistake than a stale 0.9.1 config —
|
|
49
|
+
* it stays a hard rejection rather than being silently reinterpreted.
|
|
50
|
+
*/
|
|
51
|
+
const LIFTABLE_EXTRA_PARAMS_KEYS = new Set(["temperature", "maxtokens", "enablethinking", "reasoningeffort"]);
|
|
52
|
+
/** Remedies for protected keys with no scalar first-class field to lift onto. */
|
|
53
|
+
const EXTRA_PARAMS_NO_FIELD_REMEDY = {
|
|
54
|
+
messages: "AKM builds the request messages internally — remove it from extraParams",
|
|
55
|
+
responseformat: "AKM controls the response format internally — remove it from extraParams",
|
|
56
|
+
stream: "AKM controls streaming internally — remove it from extraParams",
|
|
57
|
+
streamoptions: "AKM controls streaming internally — remove it from extraParams",
|
|
58
|
+
chattemplatekwargs: "set engines.<name>.enableThinking instead of chat_template_kwargs.enable_thinking",
|
|
59
|
+
};
|
|
60
|
+
function protectedKeyRemedy(normalized) {
|
|
61
|
+
const field = LEGACY_EXTRA_PARAMS_FIELD[normalized];
|
|
62
|
+
if (field) {
|
|
63
|
+
const note = normalized === "reasoningeffort" ? " (moved to a first-class field in 0.9.2)" : "";
|
|
64
|
+
return `set engines.<name>.${field} instead${note}`;
|
|
65
|
+
}
|
|
66
|
+
return EXTRA_PARAMS_NO_FIELD_REMEDY[normalized];
|
|
67
|
+
}
|
|
28
68
|
export function normalizeExtraParamKey(key) {
|
|
29
69
|
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
30
70
|
}
|
|
@@ -57,7 +97,11 @@ export function validateExtraParams(value) {
|
|
|
57
97
|
for (const [key, child] of Object.entries(entry)) {
|
|
58
98
|
const normalized = normalizeExtraParamKey(key);
|
|
59
99
|
if (path.length === 0 && PROTECTED_TOP_LEVEL_KEYS.has(normalized)) {
|
|
60
|
-
|
|
100
|
+
const remedy = protectedKeyRemedy(normalized);
|
|
101
|
+
issues.push({
|
|
102
|
+
path: [key],
|
|
103
|
+
message: remedy ? `${key} is protected by AKM — ${remedy}.` : `${key} is protected by AKM`,
|
|
104
|
+
});
|
|
61
105
|
}
|
|
62
106
|
if (CREDENTIAL_KEYS.has(normalized)) {
|
|
63
107
|
issues.push({ path: [...path, key], message: `${key} cannot carry credentials` });
|
|
@@ -72,3 +116,73 @@ export function formatExtraParamsIssue(label, issue) {
|
|
|
72
116
|
const suffix = issue.path.map((part) => (typeof part === "number" ? `[${part}]` : `.${part}`)).join("");
|
|
73
117
|
return `${label}${suffix} ${issue.message}`;
|
|
74
118
|
}
|
|
119
|
+
function isPlainObject(value) {
|
|
120
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Lift legacy `extraParams` keys onto their first-class engine field before
|
|
124
|
+
* schema validation runs, so a 0.9.1-shaped config using (e.g.)
|
|
125
|
+
* `extraParams.reasoning_effort` keeps loading now that `reasoningEffort` is
|
|
126
|
+
* a first-class — and therefore protected — field (#852, following #815).
|
|
127
|
+
*
|
|
128
|
+
* In-memory only: this never rewrites the config file. Callers should warn
|
|
129
|
+
* using the returned `lifted` descriptions so the user knows to update the
|
|
130
|
+
* file by hand, and reject using `conflicts` rather than silently preferring
|
|
131
|
+
* either value.
|
|
132
|
+
*/
|
|
133
|
+
export function liftLegacyEngineExtraParams(raw) {
|
|
134
|
+
const lifted = [];
|
|
135
|
+
const conflicts = [];
|
|
136
|
+
const rawEngines = raw.engines;
|
|
137
|
+
if (!isPlainObject(rawEngines)) {
|
|
138
|
+
return { config: raw, lifted, conflicts };
|
|
139
|
+
}
|
|
140
|
+
const engines = {};
|
|
141
|
+
let anyEngineChanged = false;
|
|
142
|
+
for (const [name, engineValue] of Object.entries(rawEngines)) {
|
|
143
|
+
if (!isPlainObject(engineValue) || !isPlainObject(engineValue.extraParams)) {
|
|
144
|
+
engines[name] = engineValue;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const engine = { ...engineValue };
|
|
148
|
+
const extraParams = { ...engineValue.extraParams };
|
|
149
|
+
let engineChanged = false;
|
|
150
|
+
for (const [rawKey, value] of Object.entries(engineValue.extraParams)) {
|
|
151
|
+
const normalized = normalizeExtraParamKey(rawKey);
|
|
152
|
+
if (!LIFTABLE_EXTRA_PARAMS_KEYS.has(normalized))
|
|
153
|
+
continue;
|
|
154
|
+
const field = LEGACY_EXTRA_PARAMS_FIELD[normalized];
|
|
155
|
+
if (!field)
|
|
156
|
+
continue;
|
|
157
|
+
const existing = engine[field];
|
|
158
|
+
if (existing !== undefined && existing !== value) {
|
|
159
|
+
conflicts.push({ engine: name, key: rawKey, field, extraParamsValue: value, fieldValue: existing });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
delete extraParams[rawKey];
|
|
163
|
+
engineChanged = true;
|
|
164
|
+
if (existing === value) {
|
|
165
|
+
lifted.push(`engines.${name}.extraParams.${rawKey} is redundant — engines.${name}.${field} is already set to the same value; dropped the extraParams entry`);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
engine[field] = value;
|
|
169
|
+
lifted.push(`engines.${name}.extraParams.${rawKey} -> engines.${name}.${field}`);
|
|
170
|
+
}
|
|
171
|
+
if (!engineChanged) {
|
|
172
|
+
engines[name] = engineValue;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
anyEngineChanged = true;
|
|
176
|
+
if (Object.keys(extraParams).length > 0) {
|
|
177
|
+
engine.extraParams = extraParams;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
delete engine.extraParams;
|
|
181
|
+
}
|
|
182
|
+
engines[name] = engine;
|
|
183
|
+
}
|
|
184
|
+
if (!anyEngineChanged) {
|
|
185
|
+
return { config: raw, lifted, conflicts };
|
|
186
|
+
}
|
|
187
|
+
return { config: { ...raw, engines }, lifted, conflicts };
|
|
188
|
+
}
|
|
@@ -16471,6 +16471,29 @@ var EXTRA_PARAMS_CREDENTIAL_KEYS = [
|
|
|
16471
16471
|
];
|
|
16472
16472
|
var PROTECTED_TOP_LEVEL_KEYS = new Set(EXTRA_PARAMS_PROTECTED_TOP_LEVEL_KEYS);
|
|
16473
16473
|
var CREDENTIAL_KEYS = new Set(EXTRA_PARAMS_CREDENTIAL_KEYS);
|
|
16474
|
+
var LEGACY_EXTRA_PARAMS_FIELD = {
|
|
16475
|
+
model: "model",
|
|
16476
|
+
temperature: "temperature",
|
|
16477
|
+
maxtokens: "maxTokens",
|
|
16478
|
+
enablethinking: "enableThinking",
|
|
16479
|
+
reasoningeffort: "reasoningEffort"
|
|
16480
|
+
};
|
|
16481
|
+
var LIFTABLE_EXTRA_PARAMS_KEYS = new Set(["temperature", "maxtokens", "enablethinking", "reasoningeffort"]);
|
|
16482
|
+
var EXTRA_PARAMS_NO_FIELD_REMEDY = {
|
|
16483
|
+
messages: "AKM builds the request messages internally — remove it from extraParams",
|
|
16484
|
+
responseformat: "AKM controls the response format internally — remove it from extraParams",
|
|
16485
|
+
stream: "AKM controls streaming internally — remove it from extraParams",
|
|
16486
|
+
streamoptions: "AKM controls streaming internally — remove it from extraParams",
|
|
16487
|
+
chattemplatekwargs: "set engines.<name>.enableThinking instead of chat_template_kwargs.enable_thinking"
|
|
16488
|
+
};
|
|
16489
|
+
function protectedKeyRemedy(normalized) {
|
|
16490
|
+
const field = LEGACY_EXTRA_PARAMS_FIELD[normalized];
|
|
16491
|
+
if (field) {
|
|
16492
|
+
const note = normalized === "reasoningeffort" ? " (moved to a first-class field in 0.9.2)" : "";
|
|
16493
|
+
return `set engines.<name>.${field} instead${note}`;
|
|
16494
|
+
}
|
|
16495
|
+
return EXTRA_PARAMS_NO_FIELD_REMEDY[normalized];
|
|
16496
|
+
}
|
|
16474
16497
|
function normalizeExtraParamKey(key) {
|
|
16475
16498
|
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
16476
16499
|
}
|
|
@@ -16498,7 +16521,11 @@ function validateExtraParams(value) {
|
|
|
16498
16521
|
for (const [key, child] of Object.entries(entry)) {
|
|
16499
16522
|
const normalized = normalizeExtraParamKey(key);
|
|
16500
16523
|
if (path9.length === 0 && PROTECTED_TOP_LEVEL_KEYS.has(normalized)) {
|
|
16501
|
-
|
|
16524
|
+
const remedy = protectedKeyRemedy(normalized);
|
|
16525
|
+
issues.push({
|
|
16526
|
+
path: [key],
|
|
16527
|
+
message: remedy ? `${key} is protected by AKM — ${remedy}.` : `${key} is protected by AKM`
|
|
16528
|
+
});
|
|
16502
16529
|
}
|
|
16503
16530
|
if (CREDENTIAL_KEYS.has(normalized)) {
|
|
16504
16531
|
issues.push({ path: [...path9, key], message: `${key} cannot carry credentials` });
|
|
@@ -16513,6 +16540,64 @@ function formatExtraParamsIssue(label, issue) {
|
|
|
16513
16540
|
const suffix = issue.path.map((part) => typeof part === "number" ? `[${part}]` : `.${part}`).join("");
|
|
16514
16541
|
return `${label}${suffix} ${issue.message}`;
|
|
16515
16542
|
}
|
|
16543
|
+
function isPlainObject2(value) {
|
|
16544
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16545
|
+
}
|
|
16546
|
+
function liftLegacyEngineExtraParams(raw) {
|
|
16547
|
+
const lifted = [];
|
|
16548
|
+
const conflicts = [];
|
|
16549
|
+
const rawEngines = raw.engines;
|
|
16550
|
+
if (!isPlainObject2(rawEngines)) {
|
|
16551
|
+
return { config: raw, lifted, conflicts };
|
|
16552
|
+
}
|
|
16553
|
+
const engines = {};
|
|
16554
|
+
let anyEngineChanged = false;
|
|
16555
|
+
for (const [name, engineValue] of Object.entries(rawEngines)) {
|
|
16556
|
+
if (!isPlainObject2(engineValue) || !isPlainObject2(engineValue.extraParams)) {
|
|
16557
|
+
engines[name] = engineValue;
|
|
16558
|
+
continue;
|
|
16559
|
+
}
|
|
16560
|
+
const engine = { ...engineValue };
|
|
16561
|
+
const extraParams = { ...engineValue.extraParams };
|
|
16562
|
+
let engineChanged = false;
|
|
16563
|
+
for (const [rawKey, value] of Object.entries(engineValue.extraParams)) {
|
|
16564
|
+
const normalized = normalizeExtraParamKey(rawKey);
|
|
16565
|
+
if (!LIFTABLE_EXTRA_PARAMS_KEYS.has(normalized))
|
|
16566
|
+
continue;
|
|
16567
|
+
const field = LEGACY_EXTRA_PARAMS_FIELD[normalized];
|
|
16568
|
+
if (!field)
|
|
16569
|
+
continue;
|
|
16570
|
+
const existing = engine[field];
|
|
16571
|
+
if (existing !== undefined && existing !== value) {
|
|
16572
|
+
conflicts.push({ engine: name, key: rawKey, field, extraParamsValue: value, fieldValue: existing });
|
|
16573
|
+
continue;
|
|
16574
|
+
}
|
|
16575
|
+
delete extraParams[rawKey];
|
|
16576
|
+
engineChanged = true;
|
|
16577
|
+
if (existing === value) {
|
|
16578
|
+
lifted.push(`engines.${name}.extraParams.${rawKey} is redundant — engines.${name}.${field} is already set to the same value; dropped the extraParams entry`);
|
|
16579
|
+
continue;
|
|
16580
|
+
}
|
|
16581
|
+
engine[field] = value;
|
|
16582
|
+
lifted.push(`engines.${name}.extraParams.${rawKey} -> engines.${name}.${field}`);
|
|
16583
|
+
}
|
|
16584
|
+
if (!engineChanged) {
|
|
16585
|
+
engines[name] = engineValue;
|
|
16586
|
+
continue;
|
|
16587
|
+
}
|
|
16588
|
+
anyEngineChanged = true;
|
|
16589
|
+
if (Object.keys(extraParams).length > 0) {
|
|
16590
|
+
engine.extraParams = extraParams;
|
|
16591
|
+
} else {
|
|
16592
|
+
delete engine.extraParams;
|
|
16593
|
+
}
|
|
16594
|
+
engines[name] = engine;
|
|
16595
|
+
}
|
|
16596
|
+
if (!anyEngineChanged) {
|
|
16597
|
+
return { config: raw, lifted, conflicts };
|
|
16598
|
+
}
|
|
16599
|
+
return { config: { ...raw, engines }, lifted, conflicts };
|
|
16600
|
+
}
|
|
16516
16601
|
|
|
16517
16602
|
// src/workflows/source-ir/compare.ts
|
|
16518
16603
|
function compareWorkflowSourceCodePoints(left, right) {
|
|
@@ -21777,11 +21862,11 @@ var CONSUMED_FRONTMATTER_KEYS = [
|
|
|
21777
21862
|
"stale_after",
|
|
21778
21863
|
"okf_version"
|
|
21779
21864
|
];
|
|
21780
|
-
function
|
|
21865
|
+
function isPlainObject3(value) {
|
|
21781
21866
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
21782
21867
|
}
|
|
21783
21868
|
function parseActorMapping(value) {
|
|
21784
|
-
if (!
|
|
21869
|
+
if (!isPlainObject3(value))
|
|
21785
21870
|
return;
|
|
21786
21871
|
const by = nonEmptyString(value.by);
|
|
21787
21872
|
if (by === undefined)
|
|
@@ -21806,7 +21891,7 @@ function parseOkfSources(value) {
|
|
|
21806
21891
|
return;
|
|
21807
21892
|
const out = [];
|
|
21808
21893
|
for (const item of value) {
|
|
21809
|
-
if (!
|
|
21894
|
+
if (!isPlainObject3(item))
|
|
21810
21895
|
continue;
|
|
21811
21896
|
const resource = nonEmptyString(item.resource);
|
|
21812
21897
|
if (resource === undefined)
|
|
@@ -21912,7 +21997,7 @@ function recognize8(c, file) {
|
|
|
21912
21997
|
const description = nonEmptyString(data.description);
|
|
21913
21998
|
const tags = readTags(data.tags);
|
|
21914
21999
|
const links = resolveOkfLinks(body, file.relPath);
|
|
21915
|
-
const generatedMapping =
|
|
22000
|
+
const generatedMapping = isPlainObject3(data.generated) ? data.generated : undefined;
|
|
21916
22001
|
const generatedAt = generatedMapping ? nonEmptyString(generatedMapping.at) : undefined;
|
|
21917
22002
|
const generatedBy = generatedMapping ? nonEmptyString(generatedMapping.by) : undefined;
|
|
21918
22003
|
const legacyTimestamp = nonEmptyString(data.timestamp);
|
|
@@ -27542,7 +27627,7 @@ var AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) => {
|
|
|
27542
27627
|
|
|
27543
27628
|
// src/core/config/deep-merge.ts
|
|
27544
27629
|
var UNSAFE_KEYS3 = new Set(["__proto__", "constructor", "prototype"]);
|
|
27545
|
-
function
|
|
27630
|
+
function isPlainObject4(value) {
|
|
27546
27631
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
27547
27632
|
return false;
|
|
27548
27633
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -27551,7 +27636,7 @@ function isPlainObject3(value) {
|
|
|
27551
27636
|
function copyConfigValue(value) {
|
|
27552
27637
|
if (Array.isArray(value))
|
|
27553
27638
|
return value.map(copyConfigValue);
|
|
27554
|
-
if (!
|
|
27639
|
+
if (!isPlainObject4(value))
|
|
27555
27640
|
return value;
|
|
27556
27641
|
const copy = {};
|
|
27557
27642
|
for (const key of Object.keys(value)) {
|
|
@@ -27570,7 +27655,7 @@ function deepMergeConfig(base, override) {
|
|
|
27570
27655
|
if (next === undefined)
|
|
27571
27656
|
continue;
|
|
27572
27657
|
const current = result[key];
|
|
27573
|
-
result[key] =
|
|
27658
|
+
result[key] = isPlainObject4(current) && isPlainObject4(next) ? deepMergeConfig(current, next) : copyConfigValue(next);
|
|
27574
27659
|
}
|
|
27575
27660
|
return result;
|
|
27576
27661
|
}
|
|
@@ -27630,15 +27715,29 @@ function loadUserConfig() {
|
|
|
27630
27715
|
return finalConfig;
|
|
27631
27716
|
}
|
|
27632
27717
|
function parseAndValidateConfigText(text, sourcePath) {
|
|
27633
|
-
const
|
|
27634
|
-
if (
|
|
27718
|
+
const parsedRaw = parseConfigText(text, sourcePath);
|
|
27719
|
+
if (parsedRaw.configVersion !== CURRENT_CONFIG_VERSION) {
|
|
27635
27720
|
throw new ConfigError(`Unsupported configVersion${sourcePath ? ` at ${sourcePath}` : ""}: expected "${CURRENT_CONFIG_VERSION}".`, "UNSUPPORTED_CONFIG_VERSION", "Recreate engines and improve.strategies manually for AKM 0.9.0; profile-based configuration is not translated automatically.");
|
|
27636
27721
|
}
|
|
27722
|
+
const { config: raw, lifted, conflicts } = liftLegacyEngineExtraParams(parsedRaw);
|
|
27723
|
+
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
27724
|
+
if (conflicts.length > 0) {
|
|
27725
|
+
const lines = conflicts.map((c) => ` - engines.${c.engine}.extraParams.${c.key} (${JSON.stringify(c.extraParamsValue)}) conflicts with engines.${c.engine}.${c.field} (${JSON.stringify(c.fieldValue)})`).join(`
|
|
27726
|
+
`);
|
|
27727
|
+
throw new ConfigError(`Invalid config${where}: extraParams and the first-class field disagree:
|
|
27728
|
+
${lines}
|
|
27729
|
+
|
|
27730
|
+
Each extraParams key above has a first-class equivalent and akm will not guess which value you meant — remove the extraParams entry once the field carries the value you want.`, "INVALID_CONFIG_FILE");
|
|
27731
|
+
}
|
|
27732
|
+
if (lifted.length > 0) {
|
|
27733
|
+
warn(`Config${where} uses deprecated extraParams keys with first-class equivalents; treating them as the first-class fields for this run (not written back to the file):
|
|
27734
|
+
- ${lifted.join(`
|
|
27735
|
+
- `)}`);
|
|
27736
|
+
}
|
|
27637
27737
|
const parsed = AkmConfigSchema.safeParse(raw);
|
|
27638
27738
|
if (!parsed.success) {
|
|
27639
27739
|
const lines = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join(`
|
|
27640
27740
|
`);
|
|
27641
|
-
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
27642
27741
|
throw new ConfigError(`Invalid config${where}:
|
|
27643
27742
|
${lines}`, "INVALID_CONFIG_FILE");
|
|
27644
27743
|
}
|
|
@@ -16393,6 +16393,29 @@ var EXTRA_PARAMS_CREDENTIAL_KEYS = [
|
|
|
16393
16393
|
];
|
|
16394
16394
|
var PROTECTED_TOP_LEVEL_KEYS = new Set(EXTRA_PARAMS_PROTECTED_TOP_LEVEL_KEYS);
|
|
16395
16395
|
var CREDENTIAL_KEYS = new Set(EXTRA_PARAMS_CREDENTIAL_KEYS);
|
|
16396
|
+
var LEGACY_EXTRA_PARAMS_FIELD = {
|
|
16397
|
+
model: "model",
|
|
16398
|
+
temperature: "temperature",
|
|
16399
|
+
maxtokens: "maxTokens",
|
|
16400
|
+
enablethinking: "enableThinking",
|
|
16401
|
+
reasoningeffort: "reasoningEffort"
|
|
16402
|
+
};
|
|
16403
|
+
var LIFTABLE_EXTRA_PARAMS_KEYS = new Set(["temperature", "maxtokens", "enablethinking", "reasoningeffort"]);
|
|
16404
|
+
var EXTRA_PARAMS_NO_FIELD_REMEDY = {
|
|
16405
|
+
messages: "AKM builds the request messages internally \u2014 remove it from extraParams",
|
|
16406
|
+
responseformat: "AKM controls the response format internally \u2014 remove it from extraParams",
|
|
16407
|
+
stream: "AKM controls streaming internally \u2014 remove it from extraParams",
|
|
16408
|
+
streamoptions: "AKM controls streaming internally \u2014 remove it from extraParams",
|
|
16409
|
+
chattemplatekwargs: "set engines.<name>.enableThinking instead of chat_template_kwargs.enable_thinking"
|
|
16410
|
+
};
|
|
16411
|
+
function protectedKeyRemedy(normalized) {
|
|
16412
|
+
const field = LEGACY_EXTRA_PARAMS_FIELD[normalized];
|
|
16413
|
+
if (field) {
|
|
16414
|
+
const note = normalized === "reasoningeffort" ? " (moved to a first-class field in 0.9.2)" : "";
|
|
16415
|
+
return `set engines.<name>.${field} instead${note}`;
|
|
16416
|
+
}
|
|
16417
|
+
return EXTRA_PARAMS_NO_FIELD_REMEDY[normalized];
|
|
16418
|
+
}
|
|
16396
16419
|
function normalizeExtraParamKey(key) {
|
|
16397
16420
|
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
16398
16421
|
}
|
|
@@ -16420,7 +16443,11 @@ function validateExtraParams(value) {
|
|
|
16420
16443
|
for (const [key, child] of Object.entries(entry)) {
|
|
16421
16444
|
const normalized = normalizeExtraParamKey(key);
|
|
16422
16445
|
if (path9.length === 0 && PROTECTED_TOP_LEVEL_KEYS.has(normalized)) {
|
|
16423
|
-
|
|
16446
|
+
const remedy = protectedKeyRemedy(normalized);
|
|
16447
|
+
issues.push({
|
|
16448
|
+
path: [key],
|
|
16449
|
+
message: remedy ? `${key} is protected by AKM \u2014 ${remedy}.` : `${key} is protected by AKM`
|
|
16450
|
+
});
|
|
16424
16451
|
}
|
|
16425
16452
|
if (CREDENTIAL_KEYS.has(normalized)) {
|
|
16426
16453
|
issues.push({ path: [...path9, key], message: `${key} cannot carry credentials` });
|
|
@@ -16435,6 +16462,64 @@ function formatExtraParamsIssue(label, issue) {
|
|
|
16435
16462
|
const suffix = issue.path.map((part) => typeof part === "number" ? `[${part}]` : `.${part}`).join("");
|
|
16436
16463
|
return `${label}${suffix} ${issue.message}`;
|
|
16437
16464
|
}
|
|
16465
|
+
function isPlainObject2(value) {
|
|
16466
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16467
|
+
}
|
|
16468
|
+
function liftLegacyEngineExtraParams(raw) {
|
|
16469
|
+
const lifted = [];
|
|
16470
|
+
const conflicts = [];
|
|
16471
|
+
const rawEngines = raw.engines;
|
|
16472
|
+
if (!isPlainObject2(rawEngines)) {
|
|
16473
|
+
return { config: raw, lifted, conflicts };
|
|
16474
|
+
}
|
|
16475
|
+
const engines = {};
|
|
16476
|
+
let anyEngineChanged = false;
|
|
16477
|
+
for (const [name, engineValue] of Object.entries(rawEngines)) {
|
|
16478
|
+
if (!isPlainObject2(engineValue) || !isPlainObject2(engineValue.extraParams)) {
|
|
16479
|
+
engines[name] = engineValue;
|
|
16480
|
+
continue;
|
|
16481
|
+
}
|
|
16482
|
+
const engine = { ...engineValue };
|
|
16483
|
+
const extraParams = { ...engineValue.extraParams };
|
|
16484
|
+
let engineChanged = false;
|
|
16485
|
+
for (const [rawKey, value] of Object.entries(engineValue.extraParams)) {
|
|
16486
|
+
const normalized = normalizeExtraParamKey(rawKey);
|
|
16487
|
+
if (!LIFTABLE_EXTRA_PARAMS_KEYS.has(normalized))
|
|
16488
|
+
continue;
|
|
16489
|
+
const field = LEGACY_EXTRA_PARAMS_FIELD[normalized];
|
|
16490
|
+
if (!field)
|
|
16491
|
+
continue;
|
|
16492
|
+
const existing = engine[field];
|
|
16493
|
+
if (existing !== undefined && existing !== value) {
|
|
16494
|
+
conflicts.push({ engine: name, key: rawKey, field, extraParamsValue: value, fieldValue: existing });
|
|
16495
|
+
continue;
|
|
16496
|
+
}
|
|
16497
|
+
delete extraParams[rawKey];
|
|
16498
|
+
engineChanged = true;
|
|
16499
|
+
if (existing === value) {
|
|
16500
|
+
lifted.push(`engines.${name}.extraParams.${rawKey} is redundant \u2014 engines.${name}.${field} is already set to the same value; dropped the extraParams entry`);
|
|
16501
|
+
continue;
|
|
16502
|
+
}
|
|
16503
|
+
engine[field] = value;
|
|
16504
|
+
lifted.push(`engines.${name}.extraParams.${rawKey} -> engines.${name}.${field}`);
|
|
16505
|
+
}
|
|
16506
|
+
if (!engineChanged) {
|
|
16507
|
+
engines[name] = engineValue;
|
|
16508
|
+
continue;
|
|
16509
|
+
}
|
|
16510
|
+
anyEngineChanged = true;
|
|
16511
|
+
if (Object.keys(extraParams).length > 0) {
|
|
16512
|
+
engine.extraParams = extraParams;
|
|
16513
|
+
} else {
|
|
16514
|
+
delete engine.extraParams;
|
|
16515
|
+
}
|
|
16516
|
+
engines[name] = engine;
|
|
16517
|
+
}
|
|
16518
|
+
if (!anyEngineChanged) {
|
|
16519
|
+
return { config: raw, lifted, conflicts };
|
|
16520
|
+
}
|
|
16521
|
+
return { config: { ...raw, engines }, lifted, conflicts };
|
|
16522
|
+
}
|
|
16438
16523
|
|
|
16439
16524
|
// src/workflows/source-ir/compare.ts
|
|
16440
16525
|
function compareWorkflowSourceCodePoints(left, right) {
|
|
@@ -21699,11 +21784,11 @@ var CONSUMED_FRONTMATTER_KEYS = [
|
|
|
21699
21784
|
"stale_after",
|
|
21700
21785
|
"okf_version"
|
|
21701
21786
|
];
|
|
21702
|
-
function
|
|
21787
|
+
function isPlainObject3(value) {
|
|
21703
21788
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
21704
21789
|
}
|
|
21705
21790
|
function parseActorMapping(value) {
|
|
21706
|
-
if (!
|
|
21791
|
+
if (!isPlainObject3(value))
|
|
21707
21792
|
return;
|
|
21708
21793
|
const by = nonEmptyString(value.by);
|
|
21709
21794
|
if (by === undefined)
|
|
@@ -21728,7 +21813,7 @@ function parseOkfSources(value) {
|
|
|
21728
21813
|
return;
|
|
21729
21814
|
const out = [];
|
|
21730
21815
|
for (const item of value) {
|
|
21731
|
-
if (!
|
|
21816
|
+
if (!isPlainObject3(item))
|
|
21732
21817
|
continue;
|
|
21733
21818
|
const resource = nonEmptyString(item.resource);
|
|
21734
21819
|
if (resource === undefined)
|
|
@@ -21834,7 +21919,7 @@ function recognize8(c, file) {
|
|
|
21834
21919
|
const description = nonEmptyString(data.description);
|
|
21835
21920
|
const tags = readTags(data.tags);
|
|
21836
21921
|
const links = resolveOkfLinks(body, file.relPath);
|
|
21837
|
-
const generatedMapping =
|
|
21922
|
+
const generatedMapping = isPlainObject3(data.generated) ? data.generated : undefined;
|
|
21838
21923
|
const generatedAt = generatedMapping ? nonEmptyString(generatedMapping.at) : undefined;
|
|
21839
21924
|
const generatedBy = generatedMapping ? nonEmptyString(generatedMapping.by) : undefined;
|
|
21840
21925
|
const legacyTimestamp = nonEmptyString(data.timestamp);
|
|
@@ -27464,7 +27549,7 @@ var AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) => {
|
|
|
27464
27549
|
|
|
27465
27550
|
// src/core/config/deep-merge.ts
|
|
27466
27551
|
var UNSAFE_KEYS3 = new Set(["__proto__", "constructor", "prototype"]);
|
|
27467
|
-
function
|
|
27552
|
+
function isPlainObject4(value) {
|
|
27468
27553
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
27469
27554
|
return false;
|
|
27470
27555
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -27473,7 +27558,7 @@ function isPlainObject3(value) {
|
|
|
27473
27558
|
function copyConfigValue(value) {
|
|
27474
27559
|
if (Array.isArray(value))
|
|
27475
27560
|
return value.map(copyConfigValue);
|
|
27476
|
-
if (!
|
|
27561
|
+
if (!isPlainObject4(value))
|
|
27477
27562
|
return value;
|
|
27478
27563
|
const copy = {};
|
|
27479
27564
|
for (const key of Object.keys(value)) {
|
|
@@ -27492,7 +27577,7 @@ function deepMergeConfig(base, override) {
|
|
|
27492
27577
|
if (next === undefined)
|
|
27493
27578
|
continue;
|
|
27494
27579
|
const current = result[key];
|
|
27495
|
-
result[key] =
|
|
27580
|
+
result[key] = isPlainObject4(current) && isPlainObject4(next) ? deepMergeConfig(current, next) : copyConfigValue(next);
|
|
27496
27581
|
}
|
|
27497
27582
|
return result;
|
|
27498
27583
|
}
|
|
@@ -27552,15 +27637,29 @@ function loadUserConfig() {
|
|
|
27552
27637
|
return finalConfig;
|
|
27553
27638
|
}
|
|
27554
27639
|
function parseAndValidateConfigText(text, sourcePath) {
|
|
27555
|
-
const
|
|
27556
|
-
if (
|
|
27640
|
+
const parsedRaw = parseConfigText(text, sourcePath);
|
|
27641
|
+
if (parsedRaw.configVersion !== CURRENT_CONFIG_VERSION) {
|
|
27557
27642
|
throw new ConfigError(`Unsupported configVersion${sourcePath ? ` at ${sourcePath}` : ""}: expected "${CURRENT_CONFIG_VERSION}".`, "UNSUPPORTED_CONFIG_VERSION", "Recreate engines and improve.strategies manually for AKM 0.9.0; profile-based configuration is not translated automatically.");
|
|
27558
27643
|
}
|
|
27644
|
+
const { config: raw, lifted, conflicts } = liftLegacyEngineExtraParams(parsedRaw);
|
|
27645
|
+
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
27646
|
+
if (conflicts.length > 0) {
|
|
27647
|
+
const lines = conflicts.map((c) => ` - engines.${c.engine}.extraParams.${c.key} (${JSON.stringify(c.extraParamsValue)}) conflicts with engines.${c.engine}.${c.field} (${JSON.stringify(c.fieldValue)})`).join(`
|
|
27648
|
+
`);
|
|
27649
|
+
throw new ConfigError(`Invalid config${where}: extraParams and the first-class field disagree:
|
|
27650
|
+
${lines}
|
|
27651
|
+
|
|
27652
|
+
Each extraParams key above has a first-class equivalent and akm will not guess which value you meant \u2014 remove the extraParams entry once the field carries the value you want.`, "INVALID_CONFIG_FILE");
|
|
27653
|
+
}
|
|
27654
|
+
if (lifted.length > 0) {
|
|
27655
|
+
warn(`Config${where} uses deprecated extraParams keys with first-class equivalents; treating them as the first-class fields for this run (not written back to the file):
|
|
27656
|
+
- ${lifted.join(`
|
|
27657
|
+
- `)}`);
|
|
27658
|
+
}
|
|
27559
27659
|
const parsed = AkmConfigSchema.safeParse(raw);
|
|
27560
27660
|
if (!parsed.success) {
|
|
27561
27661
|
const lines = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join(`
|
|
27562
27662
|
`);
|
|
27563
|
-
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
27564
27663
|
throw new ConfigError(`Invalid config${where}:
|
|
27565
27664
|
${lines}`, "INVALID_CONFIG_FILE");
|
|
27566
27665
|
}
|
|
@@ -64,13 +64,40 @@ export function shellCommand(task, platform = process.platform, env = process.en
|
|
|
64
64
|
return [task.shell, "-c", command];
|
|
65
65
|
case "pwsh":
|
|
66
66
|
case "powershell":
|
|
67
|
-
return [
|
|
67
|
+
return [
|
|
68
|
+
shellExecutable(task.shell, platform, env),
|
|
69
|
+
"-NoProfile",
|
|
70
|
+
"-NonInteractive",
|
|
71
|
+
"-Command",
|
|
72
|
+
withExitCodePropagation(command),
|
|
73
|
+
];
|
|
68
74
|
case "cmd":
|
|
69
75
|
return [shellExecutable("cmd", platform, env), "/d", "/s", "/c", command];
|
|
70
76
|
default:
|
|
71
77
|
return assertNever(task.shell, "shellCommand");
|
|
72
78
|
}
|
|
73
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* `-Command` (documented identically for powershell.exe 5.1 and pwsh 7+, in
|
|
82
|
+
* about_PowerShell_exe / about_Pwsh) already derives its own process exit
|
|
83
|
+
* code from `$?`, so a genuinely failing last statement already yields a
|
|
84
|
+
* nonzero exit and status "failed" — but any native exit code outside {0, 1}
|
|
85
|
+
* is collapsed to 1, discarding the real value that task history reports.
|
|
86
|
+
*
|
|
87
|
+
* Appending a bare `exit $LASTEXITCODE` to recover it is unsafe on its own:
|
|
88
|
+
* `$LASTEXITCODE` stays `$null` for a command that never runs a native
|
|
89
|
+
* executable (a pure PowerShell/cmdlet command), and `exit $null` resolves
|
|
90
|
+
* to exit code 0 — turning a failed cmdlet into a false "completed".
|
|
91
|
+
*
|
|
92
|
+
* Reading `$?` first, before anything else can run, reproduces -Command's
|
|
93
|
+
* own completed/failed determination exactly (immune to that regression and
|
|
94
|
+
* to a `$LASTEXITCODE` left stale by an earlier native call in the same
|
|
95
|
+
* command), then upgrades to the precise native exit code only when the
|
|
96
|
+
* failing last statement actually set one.
|
|
97
|
+
*/
|
|
98
|
+
function withExitCodePropagation(command) {
|
|
99
|
+
return `${command}; if ($?) { exit 0 } elseif ($LASTEXITCODE -ne $null) { exit $LASTEXITCODE } else { exit 1 }`;
|
|
100
|
+
}
|
|
74
101
|
/**
|
|
75
102
|
* Bind an unambiguous leading bare `akm` (including the task-v2 migrator's
|
|
76
103
|
* quoted form) to this installation. Explicit paths and arbitrary shell
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cursor.",
|
|
6
6
|
"keywords": [
|