akm-cli 0.10.261017-beta → 0.10.261019

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.
@@ -141,7 +141,7 @@ export function listV2Sessions(db, sinceMs) {
141
141
  }
142
142
  /**
143
143
  * The tokens one session's turns used: OpenCode records them on each assistant message and keeps their sums on the
144
- * session row (equal, measured on 2.1.4). Undefined when the session is not in the file.
144
+ * session row (equal, measured on 2.1.4 and 2.2.0). Undefined when the session is not in the file.
145
145
  */
146
146
  export function v2SessionTokens(db, sessionId) {
147
147
  const row = db
@@ -149,6 +149,16 @@ export function v2SessionTokens(db, sessionId) {
149
149
  .get(sessionId);
150
150
  return row ? { input: row.tokens_input, output: row.tokens_output, reasoning: row.tokens_reasoning } : undefined;
151
151
  }
152
+ /**
153
+ * The message a session's turn failed with, as OpenCode recorded it on the session's `idle` message (an error a plugin
154
+ * hook threw, a provider's refusal). Undefined when the session is not in the file or its turn did not fail.
155
+ */
156
+ export function v2SessionFailure(db, sessionId) {
157
+ const row = db
158
+ .prepare("SELECT json_extract(data, '$.error.message') AS message FROM session_message WHERE session_id = ? AND type = 'idle' AND json_extract(data, '$.outcome') = 'failed' ORDER BY seq DESC LIMIT 1")
159
+ .get(sessionId);
160
+ return typeof row?.message === "string" && row.message.trim() !== "" ? row.message : undefined;
161
+ }
152
162
  /** One session's events, from its messages in order. Throws when the session is not in the file. */
153
163
  export function readV2Session(db, harness, sessionId, filePath) {
154
164
  const row = db
@@ -30476,6 +30476,7 @@ function modelWorkConfig() {
30476
30476
  }
30477
30477
  const config2 = {
30478
30478
  ...user,
30479
+ plugins: [...Array.isArray(user?.plugins) ? user.plugins : [], WITHOUT_BROWSER_PLUGIN],
30479
30480
  agents: { ...asRecord(user?.agents), [MODEL_WORK_AGENT]: MODEL_WORK_AGENT_DEFINITION }
30480
30481
  };
30481
30482
  const legacy = asRecord(user?.agent);
@@ -30508,7 +30509,7 @@ function withEnvReferences(content) {
30508
30509
  return replaced(true);
30509
30510
  }
30510
30511
  }
30511
- var MODEL_WORK_AGENT = "akm-model-work", MODEL_WORK_AGENT_DEFINITION, CONFIG_CONTENT_ENV = "OPENCODE_CONFIG_CONTENT", NO_FREE_MODELS, opencodeBuilder;
30512
+ var MODEL_WORK_AGENT = "akm-model-work", MODEL_WORK_AGENT_DEFINITION, CONFIG_CONTENT_ENV = "OPENCODE_CONFIG_CONTENT", WITHOUT_BROWSER_PLUGIN = "-opencode.browser", NO_FREE_MODELS, opencodeBuilder;
30512
30513
  var init_agent_builder = __esm(() => {
30513
30514
  init_common();
30514
30515
  init_warn();
@@ -32114,13 +32115,22 @@ function improveStrategyDefault(out, defaults, improve) {
32114
32115
  out.improve = { ...isRecord(improve) ? improve : {} };
32115
32116
  out.improve.strategy ??= defaults.improveStrategy;
32116
32117
  }
32117
- function dropJudgeEngine(out, conversion) {
32118
+ function dropJudgeEngine(out, conversion, target, defaultEngine, judge) {
32118
32119
  const workflow = out.workflow;
32119
- if (!isRecord(workflow) || !("judgeEngine" in workflow))
32120
- return;
32121
- delete workflow.judgeEngine;
32122
- conversion.changed = true;
32123
- conversion.notes.push("workflow.judgeEngine: a gate's judge is the default connection or harness now; it is dropped.");
32120
+ const named = isRecord(workflow) && "judgeEngine" in workflow;
32121
+ const engine = named ? str(workflow.judgeEngine) : defaultEngine;
32122
+ if (named) {
32123
+ delete workflow.judgeEngine;
32124
+ conversion.changed = true;
32125
+ }
32126
+ const became = engine === undefined ? undefined : target(engine);
32127
+ const stays = became !== undefined && judge !== undefined && isDeepStrictEqual(became, judge);
32128
+ const kind = judge === undefined ? undefined : ("connection" in judge) ? "connection" : "harness";
32129
+ if (named) {
32130
+ conversion.notes.push(stays ? `workflow.judgeEngine: "${engine}" is the default ${kind}, where a gate's judge runs now; the setting is dropped.` : `workflow.judgeEngine${engine === undefined ? "" : ` "${engine}"`}: dropped. A gate's judge runs on the default connection, else the default harness, so a judge that ran on another engine has moved; to keep it, make that engine the default.`);
32131
+ } else if (became !== undefined && judge !== undefined && !stays) {
32132
+ conversion.notes.push(`workflow: with no workflow.judgeEngine, a gate's judge ran on the default engine "${engine}"; it is model work now and runs on the default ${kind}, "${"connection" in judge ? judge.connection : judge.harness}".`);
32133
+ }
32124
32134
  }
32125
32135
  function convertIndexEngines(out, conversion, retarget) {
32126
32136
  const index = out.index;
@@ -32187,6 +32197,8 @@ function foldModelsOverlay(modelsOverlay, fold) {
32187
32197
  const writtenHarnesses = written.harnesses;
32188
32198
  const configNames = (own2, base, name, alias) => hasModel(own2[name], alias) || hasModel(base[name], alias);
32189
32199
  const platformOf = (name) => String((mergedHarnesses[name] ?? inheritedHarnesses[name])?.platform ?? name);
32200
+ const columnOf = (name) => renamed.sdkHarness.includes(name) ? "opencode-sdk" : platformOf(name);
32201
+ const notedOwn = new Set;
32190
32202
  let folded = false;
32191
32203
  for (const { alias, column, profile } of overlayRows(modelsOverlay)) {
32192
32204
  const p = typeof profile === "string" ? { model: profile } : isRecord(profile) ? profile : {};
@@ -32198,13 +32210,18 @@ function foldModelsOverlay(modelsOverlay, fold) {
32198
32210
  continue;
32199
32211
  }
32200
32212
  const candidates = column === "*" ? [...new Set([...Object.keys(mergedConnections), ...Object.keys(inheritedConnections)])] : column === "opencode-sdk" ? [...renamed.runtime] : renamed.connection(column) !== undefined ? [renamed.connection(column)] : (column in writtenConnections) || (column in inheritedConnections) ? [column] : [];
32201
- const harnessNamed = [...new Set([...Object.keys(mergedHarnesses), ...Object.keys(inheritedHarnesses)])].filter((name) => platformOf(name) === column);
32213
+ const harnessNamed = [...new Set([...Object.keys(mergedHarnesses), ...Object.keys(inheritedHarnesses)])].filter((name) => columnOf(name) === column);
32202
32214
  if (candidates.length === 0 && harnessNamed.length === 0) {
32203
32215
  notes2.push(`${where}: names no configured connection or harness, so it is not carried over.`);
32204
32216
  continue;
32205
32217
  }
32206
32218
  const connectionNames = candidates.filter((name) => !configNames(writtenConnections, inheritedConnections, name, alias) && !(column === "*" && hasModel(mergedConnections[name], alias)));
32207
- const harnessNames = harnessNamed.filter((name) => !configNames(writtenHarnesses, inheritedHarnesses, name, alias));
32219
+ const unnamed = harnessNamed.filter((name) => !configNames(writtenHarnesses, inheritedHarnesses, name, alias));
32220
+ const harnessNames = unnamed.filter((name) => !ownModels.harnesses.has(name));
32221
+ for (const name of unnamed.filter((n) => ownModels.harnesses.has(n) && !notedOwn.has(`${column}:${n}`))) {
32222
+ notedOwn.add(`${column}:${name}`);
32223
+ notes2.push(`models.json (${column === "*" ? "all" : column}): harness "${name}" has a model of its own, which names every alias, so the column is not carried over.`);
32224
+ }
32208
32225
  if (connectionNames.length === 0 && harnessNames.length === 0) {
32209
32226
  const keptByConfig = candidates.some((name) => configNames(writtenConnections, inheritedConnections, name, alias)) || harnessNamed.some((name) => configNames(writtenHarnesses, inheritedHarnesses, name, alias));
32210
32227
  if (keptByConfig)
@@ -32264,15 +32281,23 @@ function implicitKeyVariables(name, defaults) {
32264
32281
  const own2 = `AKM_ENGINE_${name.toUpperCase().replaceAll("-", "_")}_API_KEY`;
32265
32282
  return name === str(defaults.llmEngine) ? [own2, "AKM_LLM_API_KEY"] : [own2];
32266
32283
  }
32267
- function runtimeEngine(name, engine, converted, conversion) {
32268
- converted.runtimes.set(name, str(engine.llmEngine));
32269
- conversion.notes.push(`engines.${name}: opencode-sdk is akm's own runtime now, with no entry of its own; its model, timeout and workspace are dropped.`);
32270
- }
32271
- function agentEngineToHarness(name, engine, converted) {
32272
- const harness = { platform: engine.platform };
32284
+ function runtimeEngine(name, engine, llmEngine, converted, conversion) {
32285
+ converted.runtimes.set(name, llmEngine);
32286
+ const dropped = ["model", "timeoutMs", "workspace", "bin", "agent"].filter((key) => engine[key] !== undefined);
32287
+ const gone = dropped.length === 0 ? "" : `; its ${dropped.join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped`;
32288
+ conversion.notes.push(`engines.${name}: opencode-sdk over llmEngine "${llmEngine}" is akm's own runtime over connection "${llmEngine}" now, with no entry of its own${gone}.`);
32289
+ }
32290
+ function agentEngineToHarness(name, engine, converted, conversion) {
32291
+ const sdk = engine.platform === "opencode-sdk";
32292
+ const harness = { platform: sdk ? "opencode" : engine.platform };
32293
+ if (sdk)
32294
+ converted.sdkHarnesses.add(name);
32273
32295
  for (const key of ["bin", "args", "workspace", "agent", "timeoutMs"])
32274
- if (engine[key] !== undefined)
32296
+ if (engine[key] !== undefined && !(sdk && key === "args"))
32275
32297
  harness[key] = engine[key];
32298
+ if (sdk && engine.args !== undefined) {
32299
+ conversion.notes.push(`engines.${name}: args is dropped. 0.9 did not pass it to OpenCode, and the opencode harness would pass it in place of \`run\`.`);
32300
+ }
32276
32301
  const model = str(engine.model);
32277
32302
  if (model !== undefined)
32278
32303
  converted.ownModelOf.push({ kind: "harnesses", engine: name, model });
@@ -32323,6 +32348,7 @@ function convertEngines(engines, defaults, env, conversion) {
32323
32348
  connections: {},
32324
32349
  harnesses: {},
32325
32350
  runtimes: new Map,
32351
+ sdkHarnesses: new Set,
32326
32352
  keyHolders: [],
32327
32353
  ownModelOf: []
32328
32354
  };
@@ -32332,12 +32358,13 @@ function convertEngines(engines, defaults, env, conversion) {
32332
32358
  const unknown2 = Object.keys(engine).filter((k) => !(engine.kind === "agent" ? AGENT_ENGINE_KEYS : LLM_ENGINE_KEYS).has(k));
32333
32359
  if (unknown2.length > 0)
32334
32360
  conversion.notes.push(`engines.${name}: dropped ${unknown2.join(", ")}, which akm does not read.`);
32361
+ const llmEngine = str(engine.llmEngine);
32335
32362
  if (engine.kind !== "agent")
32336
32363
  llmEngineToConnection(name, engine, defaults, env, converted, conversion);
32337
- else if (engine.platform === "opencode-sdk")
32338
- runtimeEngine(name, engine, converted, conversion);
32364
+ else if (engine.platform === "opencode-sdk" && llmEngine !== undefined)
32365
+ runtimeEngine(name, engine, llmEngine, converted, conversion);
32339
32366
  else
32340
- agentEngineToHarness(name, engine, converted);
32367
+ agentEngineToHarness(name, engine, converted, conversion);
32341
32368
  }
32342
32369
  return converted;
32343
32370
  }
@@ -32355,11 +32382,21 @@ function renameMap(names, taken, from) {
32355
32382
  }
32356
32383
  return (name) => moved.get(name) ?? name;
32357
32384
  }
32385
+ function agentWorkMoved(defaultEngine, runtimes, harness) {
32386
+ if (defaultEngine !== undefined) {
32387
+ return runtimes.has(defaultEngine) && harness !== undefined ? `Agent work ran on akm's own runtime (engines.${defaultEngine}); with the harness "${harness}" configured it now runs there. Remove that harness to keep agent work on akm's runtime.` : undefined;
32388
+ }
32389
+ const ran = "Without defaults.engine, agent work ran on your own OpenCode (the implicit fallback), when it was installed";
32390
+ return harness !== undefined ? `${ran}; with the harness "${harness}" configured it now runs there.` : `${ran}; it now runs on akm's own runtime. To keep it on your OpenCode, enable the harness: \`akm setup --harness opencode\`.`;
32391
+ }
32358
32392
  function nameDefaults(defaults, converted, conversion, written) {
32359
- const { connections, harnesses, runtimes, keyHolders } = converted;
32360
- const defaultEngine = str(defaults.engine);
32393
+ const { connections, harnesses, runtimes, sdkHarnesses, keyHolders } = converted;
32394
+ const implicit = sdkHarnesses.has(IMPLICIT_AGENT_ENGINE) || runtimes.has(IMPLICIT_AGENT_ENGINE);
32395
+ const defaultEngine = str(defaults.engine) ?? (implicit ? IMPLICIT_AGENT_ENGINE : undefined);
32396
+ const modelWorkEngine = str(defaults.llmEngine);
32361
32397
  const runtimeLlm = defaultEngine === undefined ? undefined : runtimes.get(defaultEngine);
32362
- const defaultConnection = [str(defaults.llmEngine), defaultEngine, runtimeLlm].find((n) => n !== undefined && (n in connections));
32398
+ const modelWorkRuntimeLlm = modelWorkEngine === undefined ? undefined : runtimes.get(modelWorkEngine);
32399
+ const defaultConnection = [modelWorkEngine, modelWorkRuntimeLlm, defaultEngine, runtimeLlm].find((n) => n !== undefined && (n in connections));
32363
32400
  const defaultHarness = defaultEngine !== undefined && defaultEngine in harnesses && Object.keys(harnesses).length > 1 ? defaultEngine : undefined;
32364
32401
  const connectionName = renameMap(Object.keys(connections), Object.keys(written.connections), defaultConnection);
32365
32402
  const harnessName = renameMap(Object.keys(harnesses), Object.keys(written.harnesses), defaultHarness);
@@ -32368,9 +32405,16 @@ function nameDefaults(defaults, converted, conversion, written) {
32368
32405
  connections: renamed(connections, connectionName),
32369
32406
  harnesses: renamed(harnesses, harnessName)
32370
32407
  };
32371
- const defaultHarnessName = "default" in named.harnesses ? "default" : Object.keys(named.harnesses).length === 1 ? Object.keys(named.harnesses)[0] : undefined;
32372
- if (defaultHarnessName !== undefined && (defaultEngine === undefined || runtimes.has(defaultEngine))) {
32373
- conversion.notes.push(`Agent work ran on akm's own runtime${defaultEngine === undefined ? "" : ` (engines.${defaultEngine})`}; with the harness "${defaultHarnessName}" configured it now runs there. Remove that harness to keep agent work on akm's runtime.`);
32408
+ const defaultHarnessName = defaultName(named.harnesses);
32409
+ const hasEngines = Object.keys(connections).length + Object.keys(harnesses).length + runtimes.size > 0;
32410
+ const moved = hasEngines ? agentWorkMoved(defaultEngine, runtimes, defaultHarnessName) : undefined;
32411
+ if (moved !== undefined)
32412
+ conversion.notes.push(moved);
32413
+ if (modelWorkEngine !== undefined && modelWorkEngine in harnesses) {
32414
+ const now = harnessName(modelWorkEngine);
32415
+ if (defaultName({ ...named.connections, ...written.connections }) !== undefined || defaultHarnessName !== now) {
32416
+ conversion.notes.push(`defaults.llmEngine "${modelWorkEngine}" named an agent engine, which ran akm's model work. Model work now runs on the default connection, else the default harness; to keep it on the harness "${now}", set improve.harness to it.`);
32417
+ }
32374
32418
  }
32375
32419
  for (const [kind, rename] of [
32376
32420
  ["connections", connectionName],
@@ -32382,10 +32426,9 @@ function nameDefaults(defaults, converted, conversion, written) {
32382
32426
  conversion.notes.push(`engines.default is now ${kind}.${aside}, and "default" names another engine: an engine: default key is rewritten by \`akm migrate apply\` where it can be, but a script or scheduled command that passes --engine default now reaches the new "default"; change it to the name ${aside}.`);
32383
32427
  }
32384
32428
  const target = (engine) => {
32385
- if (runtimes.has(engine)) {
32386
- const runtime = runtimes.get(engine);
32387
- return runtime !== undefined && runtime in connections ? { connection: connectionName(runtime) } : undefined;
32388
- }
32429
+ const runtime = runtimes.get(engine);
32430
+ if (runtime !== undefined)
32431
+ return runtime in connections ? { connection: connectionName(runtime) } : undefined;
32389
32432
  if (engine in harnesses)
32390
32433
  return { harness: harnessName(engine) };
32391
32434
  if (engine in connections)
@@ -32397,7 +32440,7 @@ function nameDefaults(defaults, converted, conversion, written) {
32397
32440
  connection: connectionName(engine),
32398
32441
  field
32399
32442
  }));
32400
- return { defaultConnection, defaultHarness, connectionName, harnessName, ...named, keys, target };
32443
+ return { defaultEngine, defaultConnection, defaultHarness, connectionName, harnessName, ...named, keys, target };
32401
32444
  }
32402
32445
  function mergeWritten(out, naming) {
32403
32446
  for (const [name, harness] of Object.entries(naming.harnesses)) {
@@ -32416,6 +32459,19 @@ function ownModelsOf(converted, naming) {
32416
32459
  }
32417
32460
  return ownModels;
32418
32461
  }
32462
+ function modelWorkTarget(defaults, naming, merged, inherited, conversion) {
32463
+ const connection = defaultName({ ...inherited?.connections, ...merged.connections });
32464
+ if (connection !== undefined)
32465
+ return { connection };
32466
+ const harness = defaultName({ ...inherited?.harnesses, ...merged.harnesses });
32467
+ const entry = harness === undefined ? undefined : merged.harnesses[harness];
32468
+ if (harness === undefined || !isRecord(entry))
32469
+ return;
32470
+ if (str(defaults.llmEngine) === undefined && harness in naming.harnesses && entry.modelWork === undefined) {
32471
+ conversion.notes.push(`harnesses.${harness}: akm's own model work (improve, index passes, extraction, judges) now runs on this harness, the default for it with no connection. Without defaults.llmEngine, 0.9 ran none of it unless a setting named an engine (a workflow gate's judge ran on defaults.engine). To keep the 0.9 behaviour, set \`modelWork: false\` on this harness: a call that names it still runs there, but a workflow gate's judge then needs a connection (\`akm setup --endpoint URL\`).`);
32472
+ }
32473
+ return entry.modelWork === false ? undefined : { harness };
32474
+ }
32419
32475
  function convertEngineConfig(raw, options = {}) {
32420
32476
  const out = structuredClone(raw);
32421
32477
  const engines = asDict(out.engines);
@@ -32438,7 +32494,8 @@ function convertEngineConfig(raw, options = {}) {
32438
32494
  written: { connections: asDict(out.connections), harnesses: asDict(out.harnesses) },
32439
32495
  inherited: { connections: options.inherited?.connections ?? {}, harnesses: options.inherited?.harnesses ?? {} },
32440
32496
  renamed: {
32441
- runtime: [...converted.runtimes.values()].filter((name) => name !== undefined && (name in converted.connections)).map((name) => naming.connectionName(name)),
32497
+ runtime: [...converted.runtimes.values()].filter((name) => (name in converted.connections)).map((name) => naming.connectionName(name)),
32498
+ sdkHarness: [...converted.sdkHarnesses].map((name) => naming.harnessName(name)),
32442
32499
  connection: (engine) => (engine in converted.connections) ? naming.connectionName(engine) : undefined
32443
32500
  }
32444
32501
  });
@@ -32448,7 +32505,8 @@ function convertEngineConfig(raw, options = {}) {
32448
32505
  if (isRecord(improve))
32449
32506
  moveStrategyExecution(improve, conversion, retarget);
32450
32507
  improveStrategyDefault(out, defaults, improve);
32451
- dropJudgeEngine(out, conversion);
32508
+ const judge = modelWorkTarget(defaults, naming, merged, options.inherited, conversion);
32509
+ dropJudgeEngine(out, conversion, naming.target, naming.defaultEngine, judge);
32452
32510
  convertIndexEngines(out, conversion, retarget);
32453
32511
  const config2 = {};
32454
32512
  let placed = false;
@@ -32479,7 +32537,7 @@ function convertEngineConfig(raw, options = {}) {
32479
32537
  targets
32480
32538
  };
32481
32539
  }
32482
- var LLM_ENGINE_KEYS, AGENT_ENGINE_KEYS, CARRIED;
32540
+ var LLM_ENGINE_KEYS, AGENT_ENGINE_KEYS, CARRIED, defaultName = (entries) => ("default" in entries) ? "default" : Object.keys(entries).length === 1 ? Object.keys(entries)[0] : undefined, IMPLICIT_AGENT_ENGINE = "opencode-sdk";
32483
32541
  var init_convert_engines = __esm(() => {
32484
32542
  init_common();
32485
32543
  init_convert_engine_references();
@@ -30475,6 +30475,7 @@ function modelWorkConfig() {
30475
30475
  }
30476
30476
  const config2 = {
30477
30477
  ...user,
30478
+ plugins: [...Array.isArray(user?.plugins) ? user.plugins : [], WITHOUT_BROWSER_PLUGIN],
30478
30479
  agents: { ...asRecord(user?.agents), [MODEL_WORK_AGENT]: MODEL_WORK_AGENT_DEFINITION }
30479
30480
  };
30480
30481
  const legacy = asRecord(user?.agent);
@@ -30507,7 +30508,7 @@ function withEnvReferences(content) {
30507
30508
  return replaced(true);
30508
30509
  }
30509
30510
  }
30510
- var MODEL_WORK_AGENT = "akm-model-work", MODEL_WORK_AGENT_DEFINITION, CONFIG_CONTENT_ENV = "OPENCODE_CONFIG_CONTENT", NO_FREE_MODELS, opencodeBuilder;
30511
+ var MODEL_WORK_AGENT = "akm-model-work", MODEL_WORK_AGENT_DEFINITION, CONFIG_CONTENT_ENV = "OPENCODE_CONFIG_CONTENT", WITHOUT_BROWSER_PLUGIN = "-opencode.browser", NO_FREE_MODELS, opencodeBuilder;
30511
30512
  var init_agent_builder = __esm(() => {
30512
30513
  init_common();
30513
30514
  init_warn();
@@ -32113,13 +32114,22 @@ function improveStrategyDefault(out, defaults, improve) {
32113
32114
  out.improve = { ...isRecord(improve) ? improve : {} };
32114
32115
  out.improve.strategy ??= defaults.improveStrategy;
32115
32116
  }
32116
- function dropJudgeEngine(out, conversion) {
32117
+ function dropJudgeEngine(out, conversion, target, defaultEngine, judge) {
32117
32118
  const workflow = out.workflow;
32118
- if (!isRecord(workflow) || !("judgeEngine" in workflow))
32119
- return;
32120
- delete workflow.judgeEngine;
32121
- conversion.changed = true;
32122
- conversion.notes.push("workflow.judgeEngine: a gate's judge is the default connection or harness now; it is dropped.");
32119
+ const named = isRecord(workflow) && "judgeEngine" in workflow;
32120
+ const engine = named ? str(workflow.judgeEngine) : defaultEngine;
32121
+ if (named) {
32122
+ delete workflow.judgeEngine;
32123
+ conversion.changed = true;
32124
+ }
32125
+ const became = engine === undefined ? undefined : target(engine);
32126
+ const stays = became !== undefined && judge !== undefined && isDeepStrictEqual(became, judge);
32127
+ const kind = judge === undefined ? undefined : ("connection" in judge) ? "connection" : "harness";
32128
+ if (named) {
32129
+ conversion.notes.push(stays ? `workflow.judgeEngine: "${engine}" is the default ${kind}, where a gate's judge runs now; the setting is dropped.` : `workflow.judgeEngine${engine === undefined ? "" : ` "${engine}"`}: dropped. A gate's judge runs on the default connection, else the default harness, so a judge that ran on another engine has moved; to keep it, make that engine the default.`);
32130
+ } else if (became !== undefined && judge !== undefined && !stays) {
32131
+ conversion.notes.push(`workflow: with no workflow.judgeEngine, a gate's judge ran on the default engine "${engine}"; it is model work now and runs on the default ${kind}, "${"connection" in judge ? judge.connection : judge.harness}".`);
32132
+ }
32123
32133
  }
32124
32134
  function convertIndexEngines(out, conversion, retarget) {
32125
32135
  const index = out.index;
@@ -32186,6 +32196,8 @@ function foldModelsOverlay(modelsOverlay, fold) {
32186
32196
  const writtenHarnesses = written.harnesses;
32187
32197
  const configNames = (own2, base, name, alias) => hasModel(own2[name], alias) || hasModel(base[name], alias);
32188
32198
  const platformOf = (name) => String((mergedHarnesses[name] ?? inheritedHarnesses[name])?.platform ?? name);
32199
+ const columnOf = (name) => renamed.sdkHarness.includes(name) ? "opencode-sdk" : platformOf(name);
32200
+ const notedOwn = new Set;
32189
32201
  let folded = false;
32190
32202
  for (const { alias, column, profile } of overlayRows(modelsOverlay)) {
32191
32203
  const p = typeof profile === "string" ? { model: profile } : isRecord(profile) ? profile : {};
@@ -32197,13 +32209,18 @@ function foldModelsOverlay(modelsOverlay, fold) {
32197
32209
  continue;
32198
32210
  }
32199
32211
  const candidates = column === "*" ? [...new Set([...Object.keys(mergedConnections), ...Object.keys(inheritedConnections)])] : column === "opencode-sdk" ? [...renamed.runtime] : renamed.connection(column) !== undefined ? [renamed.connection(column)] : (column in writtenConnections) || (column in inheritedConnections) ? [column] : [];
32200
- const harnessNamed = [...new Set([...Object.keys(mergedHarnesses), ...Object.keys(inheritedHarnesses)])].filter((name) => platformOf(name) === column);
32212
+ const harnessNamed = [...new Set([...Object.keys(mergedHarnesses), ...Object.keys(inheritedHarnesses)])].filter((name) => columnOf(name) === column);
32201
32213
  if (candidates.length === 0 && harnessNamed.length === 0) {
32202
32214
  notes2.push(`${where}: names no configured connection or harness, so it is not carried over.`);
32203
32215
  continue;
32204
32216
  }
32205
32217
  const connectionNames = candidates.filter((name) => !configNames(writtenConnections, inheritedConnections, name, alias) && !(column === "*" && hasModel(mergedConnections[name], alias)));
32206
- const harnessNames = harnessNamed.filter((name) => !configNames(writtenHarnesses, inheritedHarnesses, name, alias));
32218
+ const unnamed = harnessNamed.filter((name) => !configNames(writtenHarnesses, inheritedHarnesses, name, alias));
32219
+ const harnessNames = unnamed.filter((name) => !ownModels.harnesses.has(name));
32220
+ for (const name of unnamed.filter((n) => ownModels.harnesses.has(n) && !notedOwn.has(`${column}:${n}`))) {
32221
+ notedOwn.add(`${column}:${name}`);
32222
+ notes2.push(`models.json (${column === "*" ? "all" : column}): harness "${name}" has a model of its own, which names every alias, so the column is not carried over.`);
32223
+ }
32207
32224
  if (connectionNames.length === 0 && harnessNames.length === 0) {
32208
32225
  const keptByConfig = candidates.some((name) => configNames(writtenConnections, inheritedConnections, name, alias)) || harnessNamed.some((name) => configNames(writtenHarnesses, inheritedHarnesses, name, alias));
32209
32226
  if (keptByConfig)
@@ -32263,15 +32280,23 @@ function implicitKeyVariables(name, defaults) {
32263
32280
  const own2 = `AKM_ENGINE_${name.toUpperCase().replaceAll("-", "_")}_API_KEY`;
32264
32281
  return name === str(defaults.llmEngine) ? [own2, "AKM_LLM_API_KEY"] : [own2];
32265
32282
  }
32266
- function runtimeEngine(name, engine, converted, conversion) {
32267
- converted.runtimes.set(name, str(engine.llmEngine));
32268
- conversion.notes.push(`engines.${name}: opencode-sdk is akm's own runtime now, with no entry of its own; its model, timeout and workspace are dropped.`);
32269
- }
32270
- function agentEngineToHarness(name, engine, converted) {
32271
- const harness = { platform: engine.platform };
32283
+ function runtimeEngine(name, engine, llmEngine, converted, conversion) {
32284
+ converted.runtimes.set(name, llmEngine);
32285
+ const dropped = ["model", "timeoutMs", "workspace", "bin", "agent"].filter((key) => engine[key] !== undefined);
32286
+ const gone = dropped.length === 0 ? "" : `; its ${dropped.join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped`;
32287
+ conversion.notes.push(`engines.${name}: opencode-sdk over llmEngine "${llmEngine}" is akm's own runtime over connection "${llmEngine}" now, with no entry of its own${gone}.`);
32288
+ }
32289
+ function agentEngineToHarness(name, engine, converted, conversion) {
32290
+ const sdk = engine.platform === "opencode-sdk";
32291
+ const harness = { platform: sdk ? "opencode" : engine.platform };
32292
+ if (sdk)
32293
+ converted.sdkHarnesses.add(name);
32272
32294
  for (const key of ["bin", "args", "workspace", "agent", "timeoutMs"])
32273
- if (engine[key] !== undefined)
32295
+ if (engine[key] !== undefined && !(sdk && key === "args"))
32274
32296
  harness[key] = engine[key];
32297
+ if (sdk && engine.args !== undefined) {
32298
+ conversion.notes.push(`engines.${name}: args is dropped. 0.9 did not pass it to OpenCode, and the opencode harness would pass it in place of \`run\`.`);
32299
+ }
32275
32300
  const model = str(engine.model);
32276
32301
  if (model !== undefined)
32277
32302
  converted.ownModelOf.push({ kind: "harnesses", engine: name, model });
@@ -32322,6 +32347,7 @@ function convertEngines(engines, defaults, env, conversion) {
32322
32347
  connections: {},
32323
32348
  harnesses: {},
32324
32349
  runtimes: new Map,
32350
+ sdkHarnesses: new Set,
32325
32351
  keyHolders: [],
32326
32352
  ownModelOf: []
32327
32353
  };
@@ -32331,12 +32357,13 @@ function convertEngines(engines, defaults, env, conversion) {
32331
32357
  const unknown2 = Object.keys(engine).filter((k) => !(engine.kind === "agent" ? AGENT_ENGINE_KEYS : LLM_ENGINE_KEYS).has(k));
32332
32358
  if (unknown2.length > 0)
32333
32359
  conversion.notes.push(`engines.${name}: dropped ${unknown2.join(", ")}, which akm does not read.`);
32360
+ const llmEngine = str(engine.llmEngine);
32334
32361
  if (engine.kind !== "agent")
32335
32362
  llmEngineToConnection(name, engine, defaults, env, converted, conversion);
32336
- else if (engine.platform === "opencode-sdk")
32337
- runtimeEngine(name, engine, converted, conversion);
32363
+ else if (engine.platform === "opencode-sdk" && llmEngine !== undefined)
32364
+ runtimeEngine(name, engine, llmEngine, converted, conversion);
32338
32365
  else
32339
- agentEngineToHarness(name, engine, converted);
32366
+ agentEngineToHarness(name, engine, converted, conversion);
32340
32367
  }
32341
32368
  return converted;
32342
32369
  }
@@ -32354,11 +32381,21 @@ function renameMap(names, taken, from) {
32354
32381
  }
32355
32382
  return (name) => moved.get(name) ?? name;
32356
32383
  }
32384
+ function agentWorkMoved(defaultEngine, runtimes, harness) {
32385
+ if (defaultEngine !== undefined) {
32386
+ return runtimes.has(defaultEngine) && harness !== undefined ? `Agent work ran on akm's own runtime (engines.${defaultEngine}); with the harness "${harness}" configured it now runs there. Remove that harness to keep agent work on akm's runtime.` : undefined;
32387
+ }
32388
+ const ran = "Without defaults.engine, agent work ran on your own OpenCode (the implicit fallback), when it was installed";
32389
+ return harness !== undefined ? `${ran}; with the harness "${harness}" configured it now runs there.` : `${ran}; it now runs on akm's own runtime. To keep it on your OpenCode, enable the harness: \`akm setup --harness opencode\`.`;
32390
+ }
32357
32391
  function nameDefaults(defaults, converted, conversion, written) {
32358
- const { connections, harnesses, runtimes, keyHolders } = converted;
32359
- const defaultEngine = str(defaults.engine);
32392
+ const { connections, harnesses, runtimes, sdkHarnesses, keyHolders } = converted;
32393
+ const implicit = sdkHarnesses.has(IMPLICIT_AGENT_ENGINE) || runtimes.has(IMPLICIT_AGENT_ENGINE);
32394
+ const defaultEngine = str(defaults.engine) ?? (implicit ? IMPLICIT_AGENT_ENGINE : undefined);
32395
+ const modelWorkEngine = str(defaults.llmEngine);
32360
32396
  const runtimeLlm = defaultEngine === undefined ? undefined : runtimes.get(defaultEngine);
32361
- const defaultConnection = [str(defaults.llmEngine), defaultEngine, runtimeLlm].find((n) => n !== undefined && (n in connections));
32397
+ const modelWorkRuntimeLlm = modelWorkEngine === undefined ? undefined : runtimes.get(modelWorkEngine);
32398
+ const defaultConnection = [modelWorkEngine, modelWorkRuntimeLlm, defaultEngine, runtimeLlm].find((n) => n !== undefined && (n in connections));
32362
32399
  const defaultHarness = defaultEngine !== undefined && defaultEngine in harnesses && Object.keys(harnesses).length > 1 ? defaultEngine : undefined;
32363
32400
  const connectionName = renameMap(Object.keys(connections), Object.keys(written.connections), defaultConnection);
32364
32401
  const harnessName = renameMap(Object.keys(harnesses), Object.keys(written.harnesses), defaultHarness);
@@ -32367,9 +32404,16 @@ function nameDefaults(defaults, converted, conversion, written) {
32367
32404
  connections: renamed(connections, connectionName),
32368
32405
  harnesses: renamed(harnesses, harnessName)
32369
32406
  };
32370
- const defaultHarnessName = "default" in named.harnesses ? "default" : Object.keys(named.harnesses).length === 1 ? Object.keys(named.harnesses)[0] : undefined;
32371
- if (defaultHarnessName !== undefined && (defaultEngine === undefined || runtimes.has(defaultEngine))) {
32372
- conversion.notes.push(`Agent work ran on akm's own runtime${defaultEngine === undefined ? "" : ` (engines.${defaultEngine})`}; with the harness "${defaultHarnessName}" configured it now runs there. Remove that harness to keep agent work on akm's runtime.`);
32407
+ const defaultHarnessName = defaultName(named.harnesses);
32408
+ const hasEngines = Object.keys(connections).length + Object.keys(harnesses).length + runtimes.size > 0;
32409
+ const moved = hasEngines ? agentWorkMoved(defaultEngine, runtimes, defaultHarnessName) : undefined;
32410
+ if (moved !== undefined)
32411
+ conversion.notes.push(moved);
32412
+ if (modelWorkEngine !== undefined && modelWorkEngine in harnesses) {
32413
+ const now = harnessName(modelWorkEngine);
32414
+ if (defaultName({ ...named.connections, ...written.connections }) !== undefined || defaultHarnessName !== now) {
32415
+ conversion.notes.push(`defaults.llmEngine "${modelWorkEngine}" named an agent engine, which ran akm's model work. Model work now runs on the default connection, else the default harness; to keep it on the harness "${now}", set improve.harness to it.`);
32416
+ }
32373
32417
  }
32374
32418
  for (const [kind, rename] of [
32375
32419
  ["connections", connectionName],
@@ -32381,10 +32425,9 @@ function nameDefaults(defaults, converted, conversion, written) {
32381
32425
  conversion.notes.push(`engines.default is now ${kind}.${aside}, and "default" names another engine: an engine: default key is rewritten by \`akm migrate apply\` where it can be, but a script or scheduled command that passes --engine default now reaches the new "default"; change it to the name ${aside}.`);
32382
32426
  }
32383
32427
  const target = (engine) => {
32384
- if (runtimes.has(engine)) {
32385
- const runtime = runtimes.get(engine);
32386
- return runtime !== undefined && runtime in connections ? { connection: connectionName(runtime) } : undefined;
32387
- }
32428
+ const runtime = runtimes.get(engine);
32429
+ if (runtime !== undefined)
32430
+ return runtime in connections ? { connection: connectionName(runtime) } : undefined;
32388
32431
  if (engine in harnesses)
32389
32432
  return { harness: harnessName(engine) };
32390
32433
  if (engine in connections)
@@ -32396,7 +32439,7 @@ function nameDefaults(defaults, converted, conversion, written) {
32396
32439
  connection: connectionName(engine),
32397
32440
  field
32398
32441
  }));
32399
- return { defaultConnection, defaultHarness, connectionName, harnessName, ...named, keys, target };
32442
+ return { defaultEngine, defaultConnection, defaultHarness, connectionName, harnessName, ...named, keys, target };
32400
32443
  }
32401
32444
  function mergeWritten(out, naming) {
32402
32445
  for (const [name, harness] of Object.entries(naming.harnesses)) {
@@ -32415,6 +32458,19 @@ function ownModelsOf(converted, naming) {
32415
32458
  }
32416
32459
  return ownModels;
32417
32460
  }
32461
+ function modelWorkTarget(defaults, naming, merged, inherited, conversion) {
32462
+ const connection = defaultName({ ...inherited?.connections, ...merged.connections });
32463
+ if (connection !== undefined)
32464
+ return { connection };
32465
+ const harness = defaultName({ ...inherited?.harnesses, ...merged.harnesses });
32466
+ const entry = harness === undefined ? undefined : merged.harnesses[harness];
32467
+ if (harness === undefined || !isRecord(entry))
32468
+ return;
32469
+ if (str(defaults.llmEngine) === undefined && harness in naming.harnesses && entry.modelWork === undefined) {
32470
+ conversion.notes.push(`harnesses.${harness}: akm's own model work (improve, index passes, extraction, judges) now runs on this harness, the default for it with no connection. Without defaults.llmEngine, 0.9 ran none of it unless a setting named an engine (a workflow gate's judge ran on defaults.engine). To keep the 0.9 behaviour, set \`modelWork: false\` on this harness: a call that names it still runs there, but a workflow gate's judge then needs a connection (\`akm setup --endpoint URL\`).`);
32471
+ }
32472
+ return entry.modelWork === false ? undefined : { harness };
32473
+ }
32418
32474
  function convertEngineConfig(raw, options = {}) {
32419
32475
  const out = structuredClone(raw);
32420
32476
  const engines = asDict(out.engines);
@@ -32437,7 +32493,8 @@ function convertEngineConfig(raw, options = {}) {
32437
32493
  written: { connections: asDict(out.connections), harnesses: asDict(out.harnesses) },
32438
32494
  inherited: { connections: options.inherited?.connections ?? {}, harnesses: options.inherited?.harnesses ?? {} },
32439
32495
  renamed: {
32440
- runtime: [...converted.runtimes.values()].filter((name) => name !== undefined && (name in converted.connections)).map((name) => naming.connectionName(name)),
32496
+ runtime: [...converted.runtimes.values()].filter((name) => (name in converted.connections)).map((name) => naming.connectionName(name)),
32497
+ sdkHarness: [...converted.sdkHarnesses].map((name) => naming.harnessName(name)),
32441
32498
  connection: (engine) => (engine in converted.connections) ? naming.connectionName(engine) : undefined
32442
32499
  }
32443
32500
  });
@@ -32447,7 +32504,8 @@ function convertEngineConfig(raw, options = {}) {
32447
32504
  if (isRecord(improve))
32448
32505
  moveStrategyExecution(improve, conversion, retarget);
32449
32506
  improveStrategyDefault(out, defaults, improve);
32450
- dropJudgeEngine(out, conversion);
32507
+ const judge = modelWorkTarget(defaults, naming, merged, options.inherited, conversion);
32508
+ dropJudgeEngine(out, conversion, naming.target, naming.defaultEngine, judge);
32451
32509
  convertIndexEngines(out, conversion, retarget);
32452
32510
  const config2 = {};
32453
32511
  let placed = false;
@@ -32478,7 +32536,7 @@ function convertEngineConfig(raw, options = {}) {
32478
32536
  targets
32479
32537
  };
32480
32538
  }
32481
- var LLM_ENGINE_KEYS, AGENT_ENGINE_KEYS, CARRIED;
32539
+ var LLM_ENGINE_KEYS, AGENT_ENGINE_KEYS, CARRIED, defaultName = (entries) => ("default" in entries) ? "default" : Object.keys(entries).length === 1 ? Object.keys(entries)[0] : undefined, IMPLICIT_AGENT_ENGINE = "opencode-sdk";
32482
32540
  var init_convert_engines = __esm(() => {
32483
32541
  init_common();
32484
32542
  init_convert_engine_references();
@@ -639,7 +639,8 @@ Agent programs are reachable by two execution paths, both behind
639
639
  - `runAgent(profile, options)` is the single shell-out entry point. It owns
640
640
  process spawn, captured/interactive stdio, hard timeout, and structured
641
641
  failure reasons. The child's environment is the one akm runs in, with the
642
- directories this akm resolved and the call's own variables set over it.
642
+ directories this akm resolved and the call's own variables set over it, and
643
+ `PWD` naming the directory of a run given one, as `cd` sets it.
643
644
  - The `opencode` harness is the user's installed OpenCode, both majors, through
644
645
  `opencode run` with the prompt on stdin
645
646
  (`src/integrations/harnesses/opencode/agent-builder.ts`). Their OpenCode owns
@@ -686,9 +687,14 @@ Agent programs are reachable by two execution paths, both behind
686
687
  a JSON array: the executable alone when compiled, the runtime and the script
687
688
  otherwise), the
688
689
  connection's request fields are laid over each model request, with OpenCode's
689
- own output cap taken off, and the variables akm set for the process (its
690
+ own output cap taken off, the variables akm set for the process (its
690
691
  directories and its OpenCode settings) are put back to akm's own, or removed,
691
- in the environment of the commands the agent runs.
692
+ in the environment of the commands the agent runs, and a run whose model has
693
+ made the same tool call and been given the same result three turns in a row
694
+ adds a message to its next request that tells it so (a connection's
695
+ temperature below 0.7 is raised to 0.7 for that request), and fails before the
696
+ request that would follow if it makes the same call again (OpenCode has no such
697
+ check).
692
698
  - The connection's key is in no environment: the inline config references it as
693
699
  `{file:<data>/opencode-runtime/keys/<session>}`, a 0600 file written for the
694
700
  run and removed after it.
@@ -346,7 +346,7 @@ tool call, releases OpenCode's claim on the session and leaves no process
346
346
  behind. Agent work is the plain invocation, which OpenCode 2 runs through a
347
347
  background service on one port for every `HOME`: those cases end the service
348
348
  they start, skip when something else holds the port, and record that a stop ends
349
- the `opencode` command and the service's turn (2.1.4) and leaves the service up.
349
+ the `opencode` command and the service's turn (2.1.4 and 2.2.0) and leaves the service up.
350
350
  A provider defined only in the
351
351
  user's `OPENCODE_CONFIG_CONTENT` works for agent work and model work alike. Run
352
352
  it when changing the OpenCode builder or the subprocess kill ladder, and against
@@ -5,7 +5,7 @@ Upgrade guides and per-release migration notes.
5
5
  ## Current
6
6
 
7
7
  - [v0.9 -> v0.10 migration guide](v0.9-to-v0.10.md) -- Upgrade order for a 0.9.15+ install, every removed CLI spelling, strategy, config key and workflow feature with its replacement, changed `akm improve` defaults, the OpenCode plugins, and `0.10.YYMMNN` version numbers
8
- - [v0.10.0 release note](release-notes/0.10.0.md) -- Summary of the 0.10 series: the 0.9.15 version floor, `--shape`/`--target`/`help agents` removals, `default` and `consolidate` as the only built-in strategies, OpenCode 2, feedback-scaled ranking, and what is gone
8
+ - [v0.10.0 release note](release-notes/0.10.261019.md) -- Summary of the 0.10 series: the 0.9.15 version floor, `--shape`/`--target`/`help agents` removals, `default` and `consolidate` as the only built-in strategies, OpenCode 2, feedback-scaled ranking, and what is gone
9
9
  - [Release notes index](release-notes/README.md) -- The short per-release notes `akm help migrate <version>` prints, with links to every bundled note
10
10
 
11
11
  ## Historical