@kynver-app/runtime 0.1.13 → 0.1.16

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
@@ -207,6 +207,18 @@ async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
207
207
  "requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / OPENCLAW_CRON_SECRET"
208
208
  );
209
209
  }
210
+ async function refreshRunnerToken(agentOsId, opts) {
211
+ const apiKey = loadApiKey();
212
+ const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
213
+ if (!apiKey || !agentOsId || !baseUrl) return null;
214
+ try {
215
+ const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
216
+ saveRunnerToken(agentOsId, token);
217
+ return token;
218
+ } catch {
219
+ return null;
220
+ }
221
+ }
210
222
  async function fetchRunnerCredential(agentOsId, opts) {
211
223
  const apiKey = opts?.apiKey || loadApiKey();
212
224
  if (!apiKey) throw new Error("API key required \u2014 run `kynver login` first");
@@ -1455,6 +1467,34 @@ function failExists(message) {
1455
1467
 
1456
1468
  // src/worker-ops.ts
1457
1469
  import path11 from "node:path";
1470
+ async function postCompletion(url, secret, body) {
1471
+ const res = await fetch(url, {
1472
+ method: "POST",
1473
+ headers: buildHarnessCallbackHeaders(secret),
1474
+ body: JSON.stringify(body)
1475
+ });
1476
+ let parsed = null;
1477
+ try {
1478
+ parsed = await res.json();
1479
+ } catch {
1480
+ parsed = null;
1481
+ }
1482
+ return { ok: res.ok, status: res.status, parsed };
1483
+ }
1484
+ function completionErrorText(parsed) {
1485
+ if (parsed && typeof parsed === "object") {
1486
+ const err = parsed.error;
1487
+ if (typeof err === "string" && err.trim()) return err.trim();
1488
+ }
1489
+ return void 0;
1490
+ }
1491
+ function persistCompletionBlocker(worker, reason) {
1492
+ const current = worker.completionBlocker;
1493
+ if ((current ?? void 0) === (reason ?? void 0)) return;
1494
+ if (reason) worker.completionBlocker = reason;
1495
+ else delete worker.completionBlocker;
1496
+ saveWorker(worker.runId, worker);
1497
+ }
1458
1498
  async function tryCompleteWorker(args) {
1459
1499
  const worker = loadWorker(String(args.run), String(args.name));
1460
1500
  const status = computeWorkerStatus(worker);
@@ -1467,7 +1507,8 @@ async function tryCompleteWorker(args) {
1467
1507
  return { ok: true, skipped: true, reason: "worker-not-finished" };
1468
1508
  }
1469
1509
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1470
- const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
1510
+ const explicitSecret = args.secret ? String(args.secret) : void 0;
1511
+ let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
1471
1512
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
1472
1513
  const body = {
1473
1514
  source: "openclaw-harness",
@@ -1479,18 +1520,23 @@ async function tryCompleteWorker(args) {
1479
1520
  finishedAt: status.lastActivityAt || (/* @__PURE__ */ new Date()).toISOString(),
1480
1521
  status
1481
1522
  };
1482
- const res = await fetch(url, {
1483
- method: "POST",
1484
- headers: buildHarnessCallbackHeaders(secret),
1485
- body: JSON.stringify(body)
1486
- });
1487
- let parsed = null;
1488
- try {
1489
- parsed = await res.json();
1490
- } catch {
1491
- parsed = null;
1523
+ let result = await postCompletion(url, secret, body);
1524
+ if ((result.status === 401 || result.status === 403) && !explicitSecret) {
1525
+ const refreshed = await refreshRunnerToken(agentOsId, { baseUrl: base });
1526
+ if (refreshed && refreshed !== secret) {
1527
+ secret = refreshed;
1528
+ result = await postCompletion(url, secret, body);
1529
+ }
1492
1530
  }
1493
- return { ok: res.ok, httpStatus: res.status, response: parsed };
1531
+ if (result.ok) {
1532
+ persistCompletionBlocker(worker, void 0);
1533
+ return { ok: true, httpStatus: result.status, response: result.parsed };
1534
+ }
1535
+ const authRejected = result.status === 401 || result.status === 403;
1536
+ const detail = completionErrorText(result.parsed) ?? (authRejected ? "runner token unauthorized" : "non-2xx response");
1537
+ const reason = authRejected ? `completion replay rejected (${result.status}): ${detail}` : `completion replay failed (${result.status}): ${detail}`;
1538
+ persistCompletionBlocker(worker, reason);
1539
+ return { ok: false, httpStatus: result.status, response: result.parsed, completionBlocked: true };
1494
1540
  }
1495
1541
  async function completeWorker(args) {
1496
1542
  try {
@@ -1558,11 +1604,13 @@ function runStatus(args) {
1558
1604
  return { worker: name, status: "missing", attention: "needs_attention", attentionReason: "worker.json not found" };
1559
1605
  }
1560
1606
  const status = computeWorkerStatus(worker, { base: run.base });
1607
+ const rawBlocker = worker.completionBlocker;
1608
+ const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
1561
1609
  return {
1562
1610
  worker: status.worker,
1563
- status: status.status,
1564
- attention: status.attention.state,
1565
- attentionReason: status.attention.reason,
1611
+ status: completionBlocker ? "blocked" : status.status,
1612
+ attention: completionBlocker ? "blocked" : status.attention.state,
1613
+ attentionReason: completionBlocker ?? status.attention.reason,
1566
1614
  pid: status.pid,
1567
1615
  alive: status.alive,
1568
1616
  currentTool: status.currentTool,
@@ -1628,6 +1676,7 @@ function terminalStatusFor(run) {
1628
1676
  if (names.length === 0) return "failed";
1629
1677
  let anyAlive = false;
1630
1678
  let anyResult = false;
1679
+ let anyCompletionBlocked = false;
1631
1680
  for (const name of names) {
1632
1681
  const worker = readJson(
1633
1682
  path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
@@ -1639,9 +1688,13 @@ function terminalStatusFor(run) {
1639
1688
  anyAlive = true;
1640
1689
  break;
1641
1690
  }
1691
+ if (typeof worker.completionBlocker === "string" && worker.completionBlocker) {
1692
+ anyCompletionBlocked = true;
1693
+ }
1642
1694
  if (status.finalResult) anyResult = true;
1643
1695
  }
1644
1696
  if (anyAlive) return null;
1697
+ if (anyCompletionBlocked) return null;
1645
1698
  return anyResult ? "completed" : "failed";
1646
1699
  }
1647
1700
  function finalizeStaleRuns() {
@@ -1740,9 +1793,10 @@ async function completeFinishedWorkers(runId, args) {
1740
1793
  path14.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1741
1794
  void 0
1742
1795
  );
1743
- if (!worker?.dispatched || !worker.taskId) continue;
1796
+ if (!worker?.taskId) continue;
1744
1797
  const status = computeWorkerStatus(worker);
1745
1798
  if (!isFinishedWorkerStatus(status)) continue;
1799
+ if (!worker.dispatched && !status.finalResult) continue;
1746
1800
  const result = await tryCompleteWorker({
1747
1801
  run: runId,
1748
1802
  name,
@@ -1770,8 +1824,8 @@ async function runPipelineTick(args) {
1770
1824
  const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
1771
1825
  const execute = args.execute !== false && args.execute !== "false";
1772
1826
  runStatus({ run: runId });
1773
- const finalizedStaleRuns = finalizeStaleRuns();
1774
1827
  const completedWorkers = await completeFinishedWorkers(runId, args);
1828
+ const finalizedStaleRuns = finalizeStaleRuns();
1775
1829
  const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
1776
1830
  const workspacePrefs = await fetchWorkspaceRuntimePreferences(agentOsId, args);
1777
1831
  const resourceGate = observeRunnerResourceGate({