@kynver-app/runtime 0.1.21 → 0.1.22

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/cli.js CHANGED
@@ -182,7 +182,7 @@ function resolveConfiguredBaseUrl(argsBaseUrl) {
182
182
  return baseUrl ? trimTrailingSlash(String(baseUrl)) : void 0;
183
183
  }
184
184
  function resolveConfiguredCallbackSecret(argsSecret, agentOsId) {
185
- const scoped = argsSecret || loadRunnerToken(agentOsId) || loadRunnerToken(loadUserConfig().agentOsId);
185
+ const scoped = argsSecret || loadRunnerToken(agentOsId) || (agentOsId ? void 0 : loadRunnerToken(loadUserConfig().agentOsId));
186
186
  if (scoped) return String(scoped);
187
187
  const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.OPENCLAW_CRON_SECRET;
188
188
  if (globalSecret) {
@@ -223,6 +223,23 @@ async function refreshRunnerToken(agentOsId, opts) {
223
223
  return null;
224
224
  }
225
225
  }
226
+ async function refreshRunnerTokenForAuthFailure(rejectedSecret, agentOsId, opts) {
227
+ const apiKey = loadApiKey();
228
+ const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
229
+ if (!apiKey) return { ok: false, reason: "KYNVER_API_KEY is required to refresh a rejected runner token" };
230
+ if (!agentOsId) return { ok: false, reason: "agentOsId is required to refresh a rejected runner token" };
231
+ if (!baseUrl) return { ok: false, reason: "KYNVER_API_URL or --base-url is required to refresh a rejected runner token" };
232
+ try {
233
+ const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
234
+ if (token && token !== rejectedSecret) {
235
+ saveRunnerToken(agentOsId, token);
236
+ return { ok: true, token };
237
+ }
238
+ return { ok: false, reason: "runner credential refresh returned the rejected token" };
239
+ } catch (error) {
240
+ return { ok: false, reason: error.message };
241
+ }
242
+ }
226
243
  async function fetchRunnerCredential(agentOsId, opts) {
227
244
  const apiKey = opts?.apiKey || loadApiKey();
228
245
  if (!apiKey) throw new Error("API key required \u2014 run `kynver login` first");
@@ -375,6 +392,14 @@ async function postJson(url, secret, body) {
375
392
  }
376
393
  return { ok: res.ok, status: res.status, response };
377
394
  }
395
+ async function postJsonWithCredentialRefresh(url, secret, body, opts) {
396
+ const first = await postJson(url, secret, body);
397
+ if (first.ok || first.status !== 401) return first;
398
+ const refreshed = await refreshRunnerTokenForAuthFailure(secret, opts.agentOsId, { baseUrl: opts.baseUrl });
399
+ if (!refreshed.ok) return { ...first, authRefreshFailure: refreshed.reason };
400
+ const retry = await postJson(url, refreshed.token, body);
401
+ return { ...retry, refreshedAuth: true };
402
+ }
378
403
  async function getJson(url, secret) {
379
404
  const res = await fetch(url, {
380
405
  method: "GET",
@@ -1055,6 +1080,59 @@ function observeRunnerResourceGate(input) {
1055
1080
  };
1056
1081
  }
1057
1082
 
1083
+ // src/model-routing-task-enrich.ts
1084
+ function taskString(task, key) {
1085
+ const v = task[key];
1086
+ return typeof v === "string" ? v.trim() : "";
1087
+ }
1088
+ function normalize(value) {
1089
+ return value.toLowerCase();
1090
+ }
1091
+ var PERSONA_DEFAULT_LANE = {
1092
+ dalton: "implementer",
1093
+ lorentz: "report_reviewer"
1094
+ };
1095
+ function inferRoleLaneFromTask(task) {
1096
+ const existing = taskString(task, "roleLane");
1097
+ if (existing) return existing;
1098
+ const ref = normalize(taskString(task, "executorRef"));
1099
+ const title = normalize(taskString(task, "title"));
1100
+ const persona = normalize(taskString(task, "personaSlug"));
1101
+ const combined = `${ref} ${title}`;
1102
+ if (combined.includes("deep review") || combined.includes("security review") || ref.includes("deep-reviewer")) {
1103
+ return "deep_reviewer";
1104
+ }
1105
+ if (combined.includes("plan author") || combined.includes("plan-author") || title.includes("strategy plan")) {
1106
+ return "plan_author";
1107
+ }
1108
+ if (combined.includes("plan review") || ref.includes("plan-reviewer")) {
1109
+ return "plan_reviewer";
1110
+ }
1111
+ if (combined.includes("report review") || combined.includes("completion report")) {
1112
+ return "report_reviewer";
1113
+ }
1114
+ if (combined.includes("repair") || title.startsWith("fix ") || ref.includes("repair")) {
1115
+ return "repair_implementer";
1116
+ }
1117
+ if (ref.includes("cursor") || ref.includes("codex") || ref.includes("composer") || title.includes("implement") || title.includes("land:")) {
1118
+ return "implementer";
1119
+ }
1120
+ if (persona && PERSONA_DEFAULT_LANE[persona]) {
1121
+ const base = PERSONA_DEFAULT_LANE[persona];
1122
+ if (persona === "lorentz" && (combined.includes("deep") || combined.includes("security"))) {
1123
+ return "deep_reviewer";
1124
+ }
1125
+ return base;
1126
+ }
1127
+ if (combined.includes("review")) return "report_reviewer";
1128
+ return void 0;
1129
+ }
1130
+ function enrichTaskForModelRouting(task) {
1131
+ const roleLane = inferRoleLaneFromTask(task);
1132
+ if (!roleLane) return task;
1133
+ return { ...task, roleLane };
1134
+ }
1135
+
1058
1136
  // src/providers/claude.ts
1059
1137
  import { closeSync, openSync } from "node:fs";
1060
1138
  import { spawn } from "node:child_process";
@@ -1161,7 +1239,7 @@ var claudeProvider = {
1161
1239
 
1162
1240
  // src/model-routing.ts
1163
1241
  var GLOBAL_DEFAULT_MODEL = "claude-sonnet-4-6";
1164
- function taskString(task, key) {
1242
+ function taskString2(task, key) {
1165
1243
  const v = task[key];
1166
1244
  return typeof v === "string" ? v.trim() : "";
1167
1245
  }
@@ -1194,10 +1272,10 @@ function isOpusLane(ref, title) {
1194
1272
  return false;
1195
1273
  }
1196
1274
  function inferModelRoutingFromTask(task) {
1197
- const ref = normalizeRef(taskString(task, "executorRef"));
1198
- const title = taskString(task, "title").toLowerCase();
1199
- const priority = taskString(task, "priority") || "normal";
1200
- const roleLane = normalizeRef(taskString(task, "roleLane"));
1275
+ const ref = normalizeRef(taskString2(task, "executorRef"));
1276
+ const title = taskString2(task, "title").toLowerCase();
1277
+ const priority = taskString2(task, "priority") || "normal";
1278
+ const roleLane = normalizeRef(taskString2(task, "roleLane"));
1201
1279
  if (ref.includes("cursor") || ref.includes("codex") || ref.includes("composer") || ref.includes("copilot") || roleLane === "implementer" || roleLane === "repair_implementer") {
1202
1280
  return { provider: "cursor", rule: "lane:implementation" };
1203
1281
  }
@@ -1220,6 +1298,9 @@ function inferModelRoutingFromTask(task) {
1220
1298
  if (priority === "critical") {
1221
1299
  return { model: "claude-opus-4-7", provider: "claude", rule: "priority:critical" };
1222
1300
  }
1301
+ if (priority === "high") {
1302
+ return { model: "claude-sonnet-4-6", provider: "claude", rule: "priority:high" };
1303
+ }
1223
1304
  if (priority === "low") {
1224
1305
  return {
1225
1306
  model: "claude-haiku-4-5-20251001",
@@ -1296,6 +1377,14 @@ function buildPrompt(input) {
1296
1377
  "- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
1297
1378
  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."
1298
1379
  ];
1380
+ const planArtifactLines = compact ? [
1381
+ "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."
1382
+ ] : [
1383
+ "PR-first plan artifacts (when authoring or revising docs/superpowers/plans/):",
1384
+ "- 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.",
1385
+ "- Iterate review on that PR branch; link prUrl on the AgentOS task and plan progress evidence (`--evidence pr:<url>`).",
1386
+ "- See docs/superpowers/plans/2026-05-25-pr-first-plan-artifact-preservation.md for the full checklist."
1387
+ ];
1299
1388
  return [
1300
1389
  "You are running under the Kynver AgentOS runtime.",
1301
1390
  "Immediately state your plan before editing.",
@@ -1307,6 +1396,8 @@ function buildPrompt(input) {
1307
1396
  "",
1308
1397
  ...progressLines,
1309
1398
  "",
1399
+ ...planArtifactLines,
1400
+ "",
1310
1401
  "Task:",
1311
1402
  input.task
1312
1403
  ].join("\n");
@@ -1618,8 +1709,8 @@ function workerStatus(args) {
1618
1709
  writeJson(path8.join(worker.workerDir, "last-status.json"), status);
1619
1710
  console.log(JSON.stringify(status, null, 2));
1620
1711
  }
1621
- function runStatus(args) {
1622
- const run = loadRun(String(args.run));
1712
+ function buildRunBoard(runId) {
1713
+ const run = loadRun(runId);
1623
1714
  const names = Object.keys(run.workers || {});
1624
1715
  const workers = names.map((name) => {
1625
1716
  const worker = readJson(
@@ -1674,6 +1765,34 @@ function runStatus(args) {
1674
1765
  workers
1675
1766
  };
1676
1767
  writeJson(path8.join(runDirectory(run.id), "last-board.json"), board);
1768
+ return board;
1769
+ }
1770
+ async function publishHarnessBoardSnapshot(args, source) {
1771
+ const runId = String(args.run || "");
1772
+ const agentOsId = String(args.agentOsId || "");
1773
+ if (!runId || !agentOsId) return null;
1774
+ const board = buildRunBoard(runId);
1775
+ const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1776
+ const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, {
1777
+ baseUrl: base
1778
+ });
1779
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/snapshot`;
1780
+ const res = await postJsonWithCredentialRefresh(
1781
+ url,
1782
+ secret,
1783
+ { agentOsId, runId, source, snapshot: board },
1784
+ { agentOsId, baseUrl: base }
1785
+ );
1786
+ return {
1787
+ ok: res.ok,
1788
+ httpStatus: res.status,
1789
+ response: res.response,
1790
+ authRefreshed: res.refreshedAuth,
1791
+ authRefreshFailure: res.authRefreshFailure
1792
+ };
1793
+ }
1794
+ function runStatus(args) {
1795
+ const board = buildRunBoard(String(args.run));
1677
1796
  console.log(JSON.stringify(board, null, 2));
1678
1797
  }
1679
1798
  function tailWorker(args) {
@@ -2066,9 +2185,10 @@ async function dispatchRun(args) {
2066
2185
  runnerDiskGate,
2067
2186
  runnerResourceGate,
2068
2187
  ...args.lane ? { lane: String(args.lane) } : {},
2188
+ executor: args.executor ? String(args.executor) : "harness",
2069
2189
  ...args.diskPath ? { diskPath: String(args.diskPath) } : {}
2070
2190
  };
2071
- const dispatch = await postJson(dispatchUrl, secret, body);
2191
+ const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, body, { agentOsId, baseUrl: base });
2072
2192
  const responseBody = dispatch.response;
2073
2193
  if (!dispatch.ok || !responseBody?.result) {
2074
2194
  const failure = {
@@ -2076,7 +2196,9 @@ async function dispatchRun(args) {
2076
2196
  agentOsId,
2077
2197
  action: "dispatch",
2078
2198
  httpStatus: dispatch.status,
2079
- response: dispatch.response
2199
+ response: dispatch.response,
2200
+ authRefreshed: dispatch.refreshedAuth === true,
2201
+ authRefreshFailure: dispatch.authRefreshFailure
2080
2202
  };
2081
2203
  if (pipeline) return { ok: false, ...failure };
2082
2204
  console.log(JSON.stringify(failure, null, 2));
@@ -2133,7 +2255,7 @@ async function dispatchRun(args) {
2133
2255
  const name = safeSlug(`t-${task.id}-a${task.attempt}`);
2134
2256
  const routing = resolveWorkerLaunch({
2135
2257
  explicitModel: args.model ? String(args.model) : void 0,
2136
- task
2258
+ task: enrichTaskForModelRouting(task)
2137
2259
  });
2138
2260
  try {
2139
2261
  const planId = task.planId ? String(task.planId) : void 0;
@@ -2165,7 +2287,7 @@ async function dispatchRun(args) {
2165
2287
  const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(String(task.id))}/release`;
2166
2288
  let release;
2167
2289
  try {
2168
- release = await postJson(releaseUrl, secret, { agentOsId, leaseOwner });
2290
+ release = await postJsonWithCredentialRefresh(releaseUrl, secret, { agentOsId, leaseOwner }, { agentOsId, baseUrl: base });
2169
2291
  } catch (relErr) {
2170
2292
  release = { ok: false, error: relErr.message };
2171
2293
  }
@@ -2214,6 +2336,7 @@ async function sweepRun(args) {
2214
2336
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
2215
2337
  const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
2216
2338
  const leaseOwner = `openclaw-harness:${run.id}`;
2339
+ const snapshotPublished = await publishHarnessBoardSnapshot({ run: run.id, agentOsId, ...args }, "run_sweep");
2217
2340
  const releasedLocalOrphans = [];
2218
2341
  for (const name of Object.keys(run.workers || {})) {
2219
2342
  const worker = readJson(
@@ -2223,11 +2346,11 @@ async function sweepRun(args) {
2223
2346
  if (!worker || !worker.dispatched || !worker.taskId) continue;
2224
2347
  const status = computeWorkerStatus(worker);
2225
2348
  if (status.alive) continue;
2226
- if (status.finalResult) continue;
2349
+ if (status.finalResult || worker.completionReportedAt) continue;
2227
2350
  const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(String(worker.taskId))}/release`;
2228
2351
  let release;
2229
2352
  try {
2230
- release = await postJson(releaseUrl, secret, { agentOsId, leaseOwner });
2353
+ release = await postJsonWithCredentialRefresh(releaseUrl, secret, { agentOsId, leaseOwner }, { agentOsId, baseUrl: base });
2231
2354
  } catch (relErr) {
2232
2355
  release = { ok: false, error: relErr.message };
2233
2356
  }
@@ -2242,14 +2365,14 @@ async function sweepRun(args) {
2242
2365
  const reapUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/reap`;
2243
2366
  let reap;
2244
2367
  try {
2245
- reap = await postJson(reapUrl, secret, {
2368
+ reap = await postJsonWithCredentialRefresh(reapUrl, secret, {
2246
2369
  agentOsId,
2247
2370
  ...Number(args.graceMs) >= 0 && args.graceMs !== void 0 && args.graceMs !== true ? { graceMs: Math.floor(Number(args.graceMs)) } : {}
2248
- });
2371
+ }, { agentOsId, baseUrl: base });
2249
2372
  } catch (reapErr) {
2250
2373
  reap = { ok: false, error: reapErr.message };
2251
2374
  }
2252
- const summary = { runId: run.id, agentOsId, leaseOwner, releasedLocalOrphans, reap: reap.response ?? reap };
2375
+ const summary = { runId: run.id, agentOsId, leaseOwner, snapshotPublished, releasedLocalOrphans, reap: reap.response ?? reap };
2253
2376
  if (pipeline) return { ok: true, ...summary };
2254
2377
  console.log(JSON.stringify(summary, null, 2));
2255
2378
  } catch (error) {
@@ -2455,12 +2578,12 @@ async function syncPlanProgress(args) {
2455
2578
  const base = resolveBaseUrl(args.baseUrl);
2456
2579
  const secret = await resolveCallbackSecretWithMint(args.secret, args.agentOsId, { baseUrl: base });
2457
2580
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(args.agentOsId)}/tasks/${encodeURIComponent(args.taskId)}/plan-progress-sync`;
2458
- const res = await postJson(url, secret, {
2581
+ const res = await postJsonWithCredentialRefresh(url, secret, {
2459
2582
  phase: args.phase,
2460
2583
  taskId: args.taskId,
2461
2584
  blocker: args.blocker,
2462
2585
  artifact: args.artifact
2463
- });
2586
+ }, { agentOsId: args.agentOsId, baseUrl: base });
2464
2587
  return { ok: res.ok, status: res.status, response: res.response };
2465
2588
  }
2466
2589
 
@@ -2551,6 +2674,7 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args) {
2551
2674
  agentOsId,
2552
2675
  runId,
2553
2676
  ingestHarness: true,
2677
+ harnessBoardSnapshot: buildRunBoard(runId),
2554
2678
  resourceGate
2555
2679
  });
2556
2680
  return { ok: res.ok, httpStatus: res.status, response: res.response };
@@ -2762,7 +2886,7 @@ function usage(code = 0) {
2762
2886
  " kynver run create --repo /path/repo [--name name] [--base origin/main]",
2763
2887
  " kynver run list",
2764
2888
  " kynver run status --run RUN_ID",
2765
- " 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 /]",
2889
+ " 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 /]",
2766
2890
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
2767
2891
  ' 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]',
2768
2892
  " kynver worker status --run RUN_ID --name worker",