@kynver-app/runtime 0.1.21 → 0.1.23

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/dist/index.js CHANGED
@@ -176,7 +176,7 @@ function resolveConfiguredBaseUrl(argsBaseUrl) {
176
176
  return baseUrl ? trimTrailingSlash(String(baseUrl)) : void 0;
177
177
  }
178
178
  function resolveConfiguredCallbackSecret(argsSecret, agentOsId) {
179
- const scoped = argsSecret || loadRunnerToken(agentOsId) || loadRunnerToken(loadUserConfig().agentOsId);
179
+ const scoped = argsSecret || loadRunnerToken(agentOsId) || (agentOsId ? void 0 : loadRunnerToken(loadUserConfig().agentOsId));
180
180
  if (scoped) return String(scoped);
181
181
  const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.OPENCLAW_CRON_SECRET;
182
182
  if (globalSecret) {
@@ -224,6 +224,23 @@ async function refreshRunnerToken(agentOsId, opts) {
224
224
  return null;
225
225
  }
226
226
  }
227
+ async function refreshRunnerTokenForAuthFailure(rejectedSecret, agentOsId, opts) {
228
+ const apiKey = loadApiKey();
229
+ const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
230
+ if (!apiKey) return { ok: false, reason: "KYNVER_API_KEY is required to refresh a rejected runner token" };
231
+ if (!agentOsId) return { ok: false, reason: "agentOsId is required to refresh a rejected runner token" };
232
+ if (!baseUrl) return { ok: false, reason: "KYNVER_API_URL or --base-url is required to refresh a rejected runner token" };
233
+ try {
234
+ const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
235
+ if (token && token !== rejectedSecret) {
236
+ saveRunnerToken(agentOsId, token);
237
+ return { ok: true, token };
238
+ }
239
+ return { ok: false, reason: "runner credential refresh returned the rejected token" };
240
+ } catch (error) {
241
+ return { ok: false, reason: error.message };
242
+ }
243
+ }
227
244
  async function fetchRunnerCredential(agentOsId, opts) {
228
245
  const apiKey = opts?.apiKey || loadApiKey();
229
246
  if (!apiKey) throw new Error("API key required \u2014 run `kynver login` first");
@@ -376,6 +393,14 @@ async function postJson(url, secret, body) {
376
393
  }
377
394
  return { ok: res.ok, status: res.status, response };
378
395
  }
396
+ async function postJsonWithCredentialRefresh(url, secret, body, opts) {
397
+ const first = await postJson(url, secret, body);
398
+ if (first.ok || first.status !== 401) return first;
399
+ const refreshed = await refreshRunnerTokenForAuthFailure(secret, opts.agentOsId, { baseUrl: opts.baseUrl });
400
+ if (!refreshed.ok) return { ...first, authRefreshFailure: refreshed.reason };
401
+ const retry = await postJson(url, refreshed.token, body);
402
+ return { ...retry, refreshedAuth: true };
403
+ }
379
404
  async function getJson(url, secret) {
380
405
  const res = await fetch(url, {
381
406
  method: "GET",
@@ -1056,6 +1081,59 @@ function observeRunnerResourceGate(input) {
1056
1081
  };
1057
1082
  }
1058
1083
 
1084
+ // src/model-routing-task-enrich.ts
1085
+ function taskString(task, key) {
1086
+ const v = task[key];
1087
+ return typeof v === "string" ? v.trim() : "";
1088
+ }
1089
+ function normalize(value) {
1090
+ return value.toLowerCase();
1091
+ }
1092
+ var PERSONA_DEFAULT_LANE = {
1093
+ dalton: "implementer",
1094
+ lorentz: "report_reviewer"
1095
+ };
1096
+ function inferRoleLaneFromTask(task) {
1097
+ const existing = taskString(task, "roleLane");
1098
+ if (existing) return existing;
1099
+ const ref = normalize(taskString(task, "executorRef"));
1100
+ const title = normalize(taskString(task, "title"));
1101
+ const persona = normalize(taskString(task, "personaSlug"));
1102
+ const combined = `${ref} ${title}`;
1103
+ if (combined.includes("deep review") || combined.includes("security review") || ref.includes("deep-reviewer")) {
1104
+ return "deep_reviewer";
1105
+ }
1106
+ if (combined.includes("plan author") || combined.includes("plan-author") || title.includes("strategy plan")) {
1107
+ return "plan_author";
1108
+ }
1109
+ if (combined.includes("plan review") || ref.includes("plan-reviewer")) {
1110
+ return "plan_reviewer";
1111
+ }
1112
+ if (combined.includes("report review") || combined.includes("completion report")) {
1113
+ return "report_reviewer";
1114
+ }
1115
+ if (combined.includes("repair") || title.startsWith("fix ") || ref.includes("repair")) {
1116
+ return "repair_implementer";
1117
+ }
1118
+ if (ref.includes("cursor") || ref.includes("codex") || ref.includes("composer") || title.includes("implement") || title.includes("land:")) {
1119
+ return "implementer";
1120
+ }
1121
+ if (persona && PERSONA_DEFAULT_LANE[persona]) {
1122
+ const base = PERSONA_DEFAULT_LANE[persona];
1123
+ if (persona === "lorentz" && (combined.includes("deep") || combined.includes("security"))) {
1124
+ return "deep_reviewer";
1125
+ }
1126
+ return base;
1127
+ }
1128
+ if (combined.includes("review")) return "report_reviewer";
1129
+ return void 0;
1130
+ }
1131
+ function enrichTaskForModelRouting(task) {
1132
+ const roleLane = inferRoleLaneFromTask(task);
1133
+ if (!roleLane) return task;
1134
+ return { ...task, roleLane };
1135
+ }
1136
+
1059
1137
  // src/providers/claude.ts
1060
1138
  import { closeSync, openSync } from "node:fs";
1061
1139
  import { spawn } from "node:child_process";
@@ -1162,7 +1240,7 @@ var claudeProvider = {
1162
1240
 
1163
1241
  // src/model-routing.ts
1164
1242
  var GLOBAL_DEFAULT_MODEL = "claude-sonnet-4-6";
1165
- function taskString(task, key) {
1243
+ function taskString2(task, key) {
1166
1244
  const v = task[key];
1167
1245
  return typeof v === "string" ? v.trim() : "";
1168
1246
  }
@@ -1195,10 +1273,10 @@ function isOpusLane(ref, title) {
1195
1273
  return false;
1196
1274
  }
1197
1275
  function inferModelRoutingFromTask(task) {
1198
- const ref = normalizeRef(taskString(task, "executorRef"));
1199
- const title = taskString(task, "title").toLowerCase();
1200
- const priority = taskString(task, "priority") || "normal";
1201
- const roleLane = normalizeRef(taskString(task, "roleLane"));
1276
+ const ref = normalizeRef(taskString2(task, "executorRef"));
1277
+ const title = taskString2(task, "title").toLowerCase();
1278
+ const priority = taskString2(task, "priority") || "normal";
1279
+ const roleLane = normalizeRef(taskString2(task, "roleLane"));
1202
1280
  if (ref.includes("cursor") || ref.includes("codex") || ref.includes("composer") || ref.includes("copilot") || roleLane === "implementer" || roleLane === "repair_implementer") {
1203
1281
  return { provider: "cursor", rule: "lane:implementation" };
1204
1282
  }
@@ -1221,6 +1299,9 @@ function inferModelRoutingFromTask(task) {
1221
1299
  if (priority === "critical") {
1222
1300
  return { model: "claude-opus-4-7", provider: "claude", rule: "priority:critical" };
1223
1301
  }
1302
+ if (priority === "high") {
1303
+ return { model: "claude-sonnet-4-6", provider: "claude", rule: "priority:high" };
1304
+ }
1224
1305
  if (priority === "low") {
1225
1306
  return {
1226
1307
  model: "claude-haiku-4-5-20251001",
@@ -1297,6 +1378,14 @@ function buildPrompt(input) {
1297
1378
  "- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
1298
1379
  input.planId ? `Active planId: ${input.planId}${input.taskId ? ` \xB7 taskId: ${input.taskId}` : ""}` : "No planId on this worker \u2014 still emit progress when you touch plan-scoped work."
1299
1380
  ];
1381
+ const planArtifactLines = compact ? [
1382
+ "Plan artifacts: when authoring/revising docs/superpowers/plans/, open a GitHub PR early and iterate from that PR branch; do not leave the canonical plan only in the harness worktree."
1383
+ ] : [
1384
+ "PR-first plan artifacts (when authoring or revising docs/superpowers/plans/):",
1385
+ "- Before substantial plan drafting: create a feature branch, open a GitHub PR (draft OK), commit and push the plan file \u2014 do not leave the canonical plan only in this harness worktree.",
1386
+ "- Iterate review on that PR branch; link prUrl on the AgentOS task and plan progress evidence (`--evidence pr:<url>`).",
1387
+ "- See docs/superpowers/plans/2026-05-25-pr-first-plan-artifact-preservation.md for the full checklist."
1388
+ ];
1300
1389
  return [
1301
1390
  "You are running under the Kynver AgentOS runtime.",
1302
1391
  "Immediately state your plan before editing.",
@@ -1308,6 +1397,8 @@ function buildPrompt(input) {
1308
1397
  "",
1309
1398
  ...progressLines,
1310
1399
  "",
1400
+ ...planArtifactLines,
1401
+ "",
1311
1402
  "Task:",
1312
1403
  input.task
1313
1404
  ].join("\n");
@@ -1619,8 +1710,8 @@ function workerStatus(args) {
1619
1710
  writeJson(path8.join(worker.workerDir, "last-status.json"), status);
1620
1711
  console.log(JSON.stringify(status, null, 2));
1621
1712
  }
1622
- function runStatus(args) {
1623
- const run = loadRun(String(args.run));
1713
+ function buildRunBoard(runId) {
1714
+ const run = loadRun(runId);
1624
1715
  const names = Object.keys(run.workers || {});
1625
1716
  const workers = names.map((name) => {
1626
1717
  const worker = readJson(
@@ -1675,6 +1766,34 @@ function runStatus(args) {
1675
1766
  workers
1676
1767
  };
1677
1768
  writeJson(path8.join(runDirectory(run.id), "last-board.json"), board);
1769
+ return board;
1770
+ }
1771
+ async function publishHarnessBoardSnapshot(args, source) {
1772
+ const runId = String(args.run || "");
1773
+ const agentOsId = String(args.agentOsId || "");
1774
+ if (!runId || !agentOsId) return null;
1775
+ const board = buildRunBoard(runId);
1776
+ const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1777
+ const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, {
1778
+ baseUrl: base
1779
+ });
1780
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/snapshot`;
1781
+ const res = await postJsonWithCredentialRefresh(
1782
+ url,
1783
+ secret,
1784
+ { agentOsId, runId, source, snapshot: board },
1785
+ { agentOsId, baseUrl: base }
1786
+ );
1787
+ return {
1788
+ ok: res.ok,
1789
+ httpStatus: res.status,
1790
+ response: res.response,
1791
+ authRefreshed: res.refreshedAuth,
1792
+ authRefreshFailure: res.authRefreshFailure
1793
+ };
1794
+ }
1795
+ function runStatus(args) {
1796
+ const board = buildRunBoard(String(args.run));
1678
1797
  console.log(JSON.stringify(board, null, 2));
1679
1798
  }
1680
1799
  function tailWorker(args) {
@@ -2067,9 +2186,10 @@ async function dispatchRun(args) {
2067
2186
  runnerDiskGate,
2068
2187
  runnerResourceGate,
2069
2188
  ...args.lane ? { lane: String(args.lane) } : {},
2189
+ executor: args.executor ? String(args.executor) : "harness",
2070
2190
  ...args.diskPath ? { diskPath: String(args.diskPath) } : {}
2071
2191
  };
2072
- const dispatch = await postJson(dispatchUrl, secret, body);
2192
+ const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, body, { agentOsId, baseUrl: base });
2073
2193
  const responseBody = dispatch.response;
2074
2194
  if (!dispatch.ok || !responseBody?.result) {
2075
2195
  const failure = {
@@ -2077,7 +2197,9 @@ async function dispatchRun(args) {
2077
2197
  agentOsId,
2078
2198
  action: "dispatch",
2079
2199
  httpStatus: dispatch.status,
2080
- response: dispatch.response
2200
+ response: dispatch.response,
2201
+ authRefreshed: dispatch.refreshedAuth === true,
2202
+ authRefreshFailure: dispatch.authRefreshFailure
2081
2203
  };
2082
2204
  if (pipeline) return { ok: false, ...failure };
2083
2205
  console.log(JSON.stringify(failure, null, 2));
@@ -2134,7 +2256,7 @@ async function dispatchRun(args) {
2134
2256
  const name = safeSlug(`t-${task.id}-a${task.attempt}`);
2135
2257
  const routing = resolveWorkerLaunch({
2136
2258
  explicitModel: args.model ? String(args.model) : void 0,
2137
- task
2259
+ task: enrichTaskForModelRouting(task)
2138
2260
  });
2139
2261
  try {
2140
2262
  const planId = task.planId ? String(task.planId) : void 0;
@@ -2166,7 +2288,7 @@ async function dispatchRun(args) {
2166
2288
  const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(String(task.id))}/release`;
2167
2289
  let release;
2168
2290
  try {
2169
- release = await postJson(releaseUrl, secret, { agentOsId, leaseOwner });
2291
+ release = await postJsonWithCredentialRefresh(releaseUrl, secret, { agentOsId, leaseOwner }, { agentOsId, baseUrl: base });
2170
2292
  } catch (relErr) {
2171
2293
  release = { ok: false, error: relErr.message };
2172
2294
  }
@@ -2299,6 +2421,7 @@ async function sweepRun(args) {
2299
2421
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
2300
2422
  const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
2301
2423
  const leaseOwner = `openclaw-harness:${run.id}`;
2424
+ const snapshotPublished = await publishHarnessBoardSnapshot({ run: run.id, agentOsId, ...args }, "run_sweep");
2302
2425
  const releasedLocalOrphans = [];
2303
2426
  for (const name of Object.keys(run.workers || {})) {
2304
2427
  const worker = readJson(
@@ -2308,11 +2431,11 @@ async function sweepRun(args) {
2308
2431
  if (!worker || !worker.dispatched || !worker.taskId) continue;
2309
2432
  const status = computeWorkerStatus(worker);
2310
2433
  if (status.alive) continue;
2311
- if (status.finalResult) continue;
2434
+ if (status.finalResult || worker.completionReportedAt) continue;
2312
2435
  const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(String(worker.taskId))}/release`;
2313
2436
  let release;
2314
2437
  try {
2315
- release = await postJson(releaseUrl, secret, { agentOsId, leaseOwner });
2438
+ release = await postJsonWithCredentialRefresh(releaseUrl, secret, { agentOsId, leaseOwner }, { agentOsId, baseUrl: base });
2316
2439
  } catch (relErr) {
2317
2440
  release = { ok: false, error: relErr.message };
2318
2441
  }
@@ -2327,14 +2450,14 @@ async function sweepRun(args) {
2327
2450
  const reapUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/reap`;
2328
2451
  let reap;
2329
2452
  try {
2330
- reap = await postJson(reapUrl, secret, {
2453
+ reap = await postJsonWithCredentialRefresh(reapUrl, secret, {
2331
2454
  agentOsId,
2332
2455
  ...Number(args.graceMs) >= 0 && args.graceMs !== void 0 && args.graceMs !== true ? { graceMs: Math.floor(Number(args.graceMs)) } : {}
2333
- });
2456
+ }, { agentOsId, baseUrl: base });
2334
2457
  } catch (reapErr) {
2335
2458
  reap = { ok: false, error: reapErr.message };
2336
2459
  }
2337
- const summary = { runId: run.id, agentOsId, leaseOwner, releasedLocalOrphans, reap: reap.response ?? reap };
2460
+ const summary = { runId: run.id, agentOsId, leaseOwner, snapshotPublished, releasedLocalOrphans, reap: reap.response ?? reap };
2338
2461
  if (pipeline) return { ok: true, ...summary };
2339
2462
  console.log(JSON.stringify(summary, null, 2));
2340
2463
  } catch (error) {
@@ -2487,12 +2610,12 @@ async function syncPlanProgress(args) {
2487
2610
  const base = resolveBaseUrl(args.baseUrl);
2488
2611
  const secret = await resolveCallbackSecretWithMint(args.secret, args.agentOsId, { baseUrl: base });
2489
2612
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(args.agentOsId)}/tasks/${encodeURIComponent(args.taskId)}/plan-progress-sync`;
2490
- const res = await postJson(url, secret, {
2613
+ const res = await postJsonWithCredentialRefresh(url, secret, {
2491
2614
  phase: args.phase,
2492
2615
  taskId: args.taskId,
2493
2616
  blocker: args.blocker,
2494
2617
  artifact: args.artifact
2495
- });
2618
+ }, { agentOsId: args.agentOsId, baseUrl: base });
2496
2619
  return { ok: res.ok, status: res.status, response: res.response };
2497
2620
  }
2498
2621
 
@@ -2583,6 +2706,7 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args) {
2583
2706
  agentOsId,
2584
2707
  runId,
2585
2708
  ingestHarness: true,
2709
+ harnessBoardSnapshot: buildRunBoard(runId),
2586
2710
  resourceGate
2587
2711
  });
2588
2712
  return { ok: res.ok, httpStatus: res.status, response: res.response };
@@ -2592,15 +2716,15 @@ async function runPipelineTick(args) {
2592
2716
  const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
2593
2717
  const execute = args.execute !== false && args.execute !== "false";
2594
2718
  runStatus({ run: runId });
2595
- const completedWorkers = await completeFinishedWorkers(runId, args);
2596
- const staleReconcile = reconcileStaleWorkers();
2597
- const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
2598
2719
  const workspacePrefs = await fetchWorkspaceRuntimePreferences(agentOsId, args);
2599
2720
  const resourceGate = observeRunnerResourceGate({
2600
2721
  runId,
2601
2722
  configuredMaxWorkersOverride: workspacePrefs?.maxConcurrentWorkers
2602
2723
  });
2603
2724
  const operatorTick = await postOperatorTick(agentOsId, runId, resourceGate, args);
2725
+ const completedWorkers = await completeFinishedWorkers(runId, args);
2726
+ const staleReconcile = reconcileStaleWorkers();
2727
+ const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
2604
2728
  let maxStarts = resourceGate.slotsAvailable;
2605
2729
  const tickBody = operatorTick;
2606
2730
  const advised = tickBody.response?.dispatch?.recommendedMaxStarts;
@@ -2794,7 +2918,7 @@ function usage(code = 0) {
2794
2918
  " kynver run create --repo /path/repo [--name name] [--base origin/main]",
2795
2919
  " kynver run list",
2796
2920
  " kynver run status --run RUN_ID",
2797
- " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-7] [--disk-path /]",
2921
+ " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-7] [--disk-path /]",
2798
2922
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
2799
2923
  ' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID]',
2800
2924
  " kynver worker status --run RUN_ID --name worker",