@kynver-app/runtime 0.1.16 → 0.1.18

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { mkdirSync as mkdirSync5, realpathSync } from "node:fs";
5
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6
6
 
7
7
  // src/config.ts
8
8
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
@@ -392,12 +392,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
392
392
  var DEFAULT_MAX_USED_PERCENT = 80;
393
393
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
394
394
  function observeRunnerDiskGate(input = {}) {
395
- const path15 = input.diskPath?.trim() || "/";
395
+ const path16 = input.diskPath?.trim() || "/";
396
396
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
397
397
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
398
398
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
399
399
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
400
- const stats = statfsSync(path15);
400
+ const stats = statfsSync(path16);
401
401
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
402
402
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
403
403
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -417,7 +417,7 @@ function observeRunnerDiskGate(input = {}) {
417
417
  }
418
418
  return {
419
419
  ok,
420
- path: path15,
420
+ path: path16,
421
421
  freeBytes,
422
422
  totalBytes,
423
423
  usedPercent,
@@ -604,6 +604,52 @@ function summarizeEvent(event) {
604
604
  return void 0;
605
605
  }
606
606
 
607
+ // src/exit-classify.ts
608
+ var FAILURE_PATTERNS = [
609
+ {
610
+ test: /\b(?:invalid|unknown|unsupported|unrecognized)\b[^.\n]*\bmodel\b/i,
611
+ label: "provider rejected the requested model"
612
+ },
613
+ {
614
+ test: /\bmodel\b[^.\n]*\b(?:not\s+(?:found|supported|available|recognized|valid)|is\s+not\s+valid|does\s+not\s+exist)/i,
615
+ label: "provider rejected the requested model"
616
+ },
617
+ {
618
+ test: /\b(?:did you mean|available models|choose (?:a|one of)|supported models)\b/i,
619
+ label: "provider rejected the requested model"
620
+ },
621
+ {
622
+ test: /model preflight failed/i,
623
+ label: "model/provider preflight failed"
624
+ },
625
+ {
626
+ test: /\b(?:command not found|ENOENT|is the .*CLI on PATH|executable not found|no such file or directory)\b/i,
627
+ label: "provider CLI is missing or not on PATH"
628
+ },
629
+ {
630
+ test: /\bfailed to spawn\b/i,
631
+ label: "provider failed to spawn the worker process"
632
+ },
633
+ {
634
+ test: /\b(?:not logged in|unauthorized|authentication (?:failed|required)|invalid api key|missing api key|401)\b/i,
635
+ label: "provider authentication failed"
636
+ }
637
+ ];
638
+ function tidy(errorText, max = 240) {
639
+ const oneLine2 = errorText.replace(/\s+/g, " ").trim();
640
+ return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
641
+ }
642
+ function classifyExitFailure(errorText) {
643
+ const text = (errorText ?? "").trim();
644
+ if (!text) return null;
645
+ for (const pattern of FAILURE_PATTERNS) {
646
+ if (pattern.test.test(text)) {
647
+ return { blocked: true, reason: `${pattern.label}: ${tidy(text)}` };
648
+ }
649
+ }
650
+ return null;
651
+ }
652
+
607
653
  // src/git.ts
608
654
  import { spawnSync } from "node:child_process";
609
655
  function git(cwd, args, options = {}) {
@@ -719,7 +765,15 @@ var STALE_MS = 6e5;
719
765
  function computeAttention(input) {
720
766
  const now = Date.now();
721
767
  if (input.finalResult) return { state: "done", reason: "final result recorded" };
722
- if (!input.alive) return { state: "needs_attention", reason: "process exited without a final result" };
768
+ if (!input.alive) {
769
+ const classified = classifyExitFailure(input.error);
770
+ if (classified) return { state: "blocked", reason: classified.reason };
771
+ const tail = input.error?.trim();
772
+ return {
773
+ state: "needs_attention",
774
+ reason: tail ? `process exited without a final result: ${tail}` : "process exited without a final result"
775
+ };
776
+ }
723
777
  if (input.heartbeatBlocker) {
724
778
  return { state: "blocked", reason: `worker heartbeat reported blocker: ${input.heartbeatBlocker}` };
725
779
  }
@@ -749,6 +803,7 @@ function computeWorkerStatus(worker, options = {}) {
749
803
  fileMtime(worker.stderrPath),
750
804
  fileMtime(worker.heartbeatPath)
751
805
  ]);
806
+ const error = parsed.error || (!alive && !parsed.finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
752
807
  const attention = computeAttention({
753
808
  alive,
754
809
  finalResult: parsed.finalResult,
@@ -757,7 +812,8 @@ function computeWorkerStatus(worker, options = {}) {
757
812
  heartbeatBytes,
758
813
  lastActivityAt,
759
814
  heartbeatBlocker: heartbeat.heartbeatBlocker,
760
- startedAt: worker.startedAt
815
+ startedAt: worker.startedAt,
816
+ error
761
817
  });
762
818
  return {
763
819
  runId: worker.runId,
@@ -782,7 +838,7 @@ function computeWorkerStatus(worker, options = {}) {
782
838
  lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
783
839
  heartbeatBlocker: heartbeat.heartbeatBlocker,
784
840
  finalResult: parsed.finalResult,
785
- error: parsed.error || (!alive && !parsed.finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0),
841
+ error,
786
842
  changedFiles,
787
843
  gitAncestry
788
844
  };
@@ -907,8 +963,8 @@ function observeRunnerResourceGate(input) {
907
963
  }
908
964
 
909
965
  // src/supervisor.ts
910
- import { existsSync as existsSync8, mkdirSync as mkdirSync3 } from "node:fs";
911
- import path7 from "node:path";
966
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3 } from "node:fs";
967
+ import path9 from "node:path";
912
968
 
913
969
  // src/prompt.ts
914
970
  function buildPrompt(input) {
@@ -942,10 +998,74 @@ function buildPrompt(input) {
942
998
  // src/providers/claude.ts
943
999
  import { closeSync, openSync } from "node:fs";
944
1000
  import { spawn } from "node:child_process";
1001
+
1002
+ // src/providers/model-preflight.ts
1003
+ var REASONING_SUFFIX_RE = /-(?:thinking(?:-(?:high|medium|low|minimal|max|none))?|high|medium|low|minimal)$/i;
1004
+ function stripReasoningSuffix(model) {
1005
+ return model.replace(REASONING_SUFFIX_RE, "");
1006
+ }
1007
+ var FOREIGN_MODEL_RE = /^(?:gpt-|gpt5|o1|o3|o4|gemini-|grok-|composer|deepseek|llama|mistral|qwen|command-)/i;
1008
+ function looksLikeClaudeModel(model) {
1009
+ return /^claude[-_]/i.test(model) || /^(?:opus|sonnet|haiku)\b/i.test(model);
1010
+ }
1011
+ function preflightClaudeModel(model, defaultModel) {
1012
+ const requested = (model ?? "").trim();
1013
+ if (!requested) {
1014
+ return { ok: true, model: defaultModel, normalized: false };
1015
+ }
1016
+ const stripped = stripReasoningSuffix(requested).trim();
1017
+ const launch = stripped || defaultModel;
1018
+ if (FOREIGN_MODEL_RE.test(launch) || !looksLikeClaudeModel(launch) && launch !== defaultModel) {
1019
+ return {
1020
+ ok: false,
1021
+ model: requested,
1022
+ normalized: false,
1023
+ requested,
1024
+ note: `model "${requested}" is not a Claude model \u2014 the "claude" provider drives the Claude CLI, which only accepts claude-* model ids (got "${launch}"). Pick a Claude model or switch the worker provider.`
1025
+ };
1026
+ }
1027
+ if (launch !== requested) {
1028
+ return {
1029
+ ok: true,
1030
+ model: launch,
1031
+ normalized: true,
1032
+ requested,
1033
+ note: `normalized model "${requested}" \u2192 "${launch}" (the Claude CLI rejects reasoning-effort suffixes)`
1034
+ };
1035
+ }
1036
+ return { ok: true, model: launch, normalized: false };
1037
+ }
1038
+ function preflightCursorModel(model, defaultModel) {
1039
+ const requested = (model ?? "").trim();
1040
+ if (!requested) {
1041
+ return { ok: true, model: defaultModel, normalized: false };
1042
+ }
1043
+ if (looksLikeClaudeModel(requested)) {
1044
+ return {
1045
+ ok: false,
1046
+ model: requested,
1047
+ normalized: false,
1048
+ requested,
1049
+ note: `model "${requested}" is a Claude model but the worker provider is "cursor". Switch the provider to "claude" or pick a Cursor model.`
1050
+ };
1051
+ }
1052
+ return { ok: true, model: requested, normalized: false };
1053
+ }
1054
+
1055
+ // src/providers/claude.ts
1056
+ var CLAUDE_DEFAULT_MODEL = "claude-opus-4-7";
945
1057
  var claudeProvider = {
946
1058
  name: "claude",
1059
+ defaultModel: CLAUDE_DEFAULT_MODEL,
1060
+ preflightModel(model) {
1061
+ return preflightClaudeModel(model, CLAUDE_DEFAULT_MODEL);
1062
+ },
947
1063
  start(opts) {
948
- const model = opts.model || "claude-opus-4-7";
1064
+ const preflight = preflightClaudeModel(opts.model, CLAUDE_DEFAULT_MODEL);
1065
+ if (!preflight.ok) {
1066
+ throw new Error(`claude provider model preflight failed: ${preflight.note}`);
1067
+ }
1068
+ const model = preflight.model;
949
1069
  const stdoutFd = openSync(opts.stdoutPath, "a");
950
1070
  const stderrFd = openSync(opts.stderrPath, "a");
951
1071
  const child = spawn(
@@ -1021,8 +1141,16 @@ function resolveAgentBin() {
1021
1141
  }
1022
1142
  var cursorProvider = {
1023
1143
  name: "cursor",
1144
+ defaultModel: DEFAULT_CURSOR_MODEL,
1145
+ preflightModel(model) {
1146
+ return preflightCursorModel(model, DEFAULT_CURSOR_MODEL);
1147
+ },
1024
1148
  start(opts) {
1025
- const model = opts.model || DEFAULT_CURSOR_MODEL;
1149
+ const preflight = preflightCursorModel(opts.model, DEFAULT_CURSOR_MODEL);
1150
+ if (!preflight.ok) {
1151
+ throw new Error(`cursor provider model preflight failed: ${preflight.note}`);
1152
+ }
1153
+ const model = preflight.model;
1026
1154
  const stdoutFd = openSync2(opts.stdoutPath, "a");
1027
1155
  const stderrFd = openSync2(opts.stderrPath, "a");
1028
1156
  const agentBin = resolveAgentBin();
@@ -1082,6 +1210,369 @@ function resolveWorkerProvider(name) {
1082
1210
  return provider;
1083
1211
  }
1084
1212
 
1213
+ // src/auto-complete.ts
1214
+ import { spawn as spawn3 } from "node:child_process";
1215
+ import { existsSync as existsSync8, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
1216
+ import path8 from "node:path";
1217
+ import { fileURLToPath } from "node:url";
1218
+
1219
+ // src/worker-ops.ts
1220
+ import path7 from "node:path";
1221
+ async function postCompletion(url, secret, body) {
1222
+ const res = await fetch(url, {
1223
+ method: "POST",
1224
+ headers: buildHarnessCallbackHeaders(secret),
1225
+ body: JSON.stringify(body)
1226
+ });
1227
+ let parsed = null;
1228
+ try {
1229
+ parsed = await res.json();
1230
+ } catch {
1231
+ parsed = null;
1232
+ }
1233
+ return { ok: res.ok, status: res.status, parsed };
1234
+ }
1235
+ function completionErrorText(parsed) {
1236
+ if (parsed && typeof parsed === "object") {
1237
+ const err = parsed.error;
1238
+ if (typeof err === "string" && err.trim()) return err.trim();
1239
+ }
1240
+ return void 0;
1241
+ }
1242
+ function persistCompletionBlocker(worker, reason) {
1243
+ const current = worker.completionBlocker;
1244
+ if ((current ?? void 0) === (reason ?? void 0)) return;
1245
+ if (reason) worker.completionBlocker = reason;
1246
+ else delete worker.completionBlocker;
1247
+ saveWorker(worker.runId, worker);
1248
+ }
1249
+ async function tryCompleteWorker(args) {
1250
+ const worker = loadWorker(String(args.run), String(args.name));
1251
+ const status = computeWorkerStatus(worker);
1252
+ const agentOsId = (args.agentOsId ? String(args.agentOsId) : worker.agentOsId) || "";
1253
+ const taskId = (args.taskId ? String(args.taskId) : worker.taskId) || null;
1254
+ if (!agentOsId) {
1255
+ return { ok: false, reason: "missing agentOsId" };
1256
+ }
1257
+ if (!isFinishedWorkerStatus(status)) {
1258
+ return { ok: true, skipped: true, reason: "worker-not-finished" };
1259
+ }
1260
+ const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1261
+ const explicitSecret = args.secret ? String(args.secret) : void 0;
1262
+ let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
1263
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
1264
+ const body = {
1265
+ source: "openclaw-harness",
1266
+ agentOsId,
1267
+ runId: worker.runId,
1268
+ workerName: worker.name,
1269
+ taskId,
1270
+ startedAt: worker.startedAt,
1271
+ finishedAt: status.lastActivityAt || (/* @__PURE__ */ new Date()).toISOString(),
1272
+ status
1273
+ };
1274
+ let result = await postCompletion(url, secret, body);
1275
+ if ((result.status === 401 || result.status === 403) && !explicitSecret) {
1276
+ const refreshed = await refreshRunnerToken(agentOsId, { baseUrl: base });
1277
+ if (refreshed && refreshed !== secret) {
1278
+ secret = refreshed;
1279
+ result = await postCompletion(url, secret, body);
1280
+ }
1281
+ }
1282
+ if (result.ok) {
1283
+ persistCompletionBlocker(worker, void 0);
1284
+ return { ok: true, httpStatus: result.status, response: result.parsed };
1285
+ }
1286
+ const authRejected = result.status === 401 || result.status === 403;
1287
+ const detail = completionErrorText(result.parsed) ?? (authRejected ? "runner token unauthorized" : "non-2xx response");
1288
+ const reason = authRejected ? `completion replay rejected (${result.status}): ${detail}` : `completion replay failed (${result.status}): ${detail}`;
1289
+ persistCompletionBlocker(worker, reason);
1290
+ return { ok: false, httpStatus: result.status, response: result.parsed, completionBlocked: true };
1291
+ }
1292
+ async function completeWorker(args) {
1293
+ try {
1294
+ const worker = loadWorker(String(args.run), String(args.name));
1295
+ const status = computeWorkerStatus(worker);
1296
+ const agentOsId = (args.agentOsId ? String(args.agentOsId) : worker.agentOsId) || "";
1297
+ const taskId = (args.taskId ? String(args.taskId) : worker.taskId) || null;
1298
+ if (!agentOsId) {
1299
+ console.error("worker complete requires --agent-os-id (or an agentOsId persisted at worker start)");
1300
+ process.exit(1);
1301
+ }
1302
+ if (!isFinishedWorkerStatus(status)) {
1303
+ console.log(
1304
+ JSON.stringify(
1305
+ {
1306
+ worker: worker.name,
1307
+ runId: worker.runId,
1308
+ status: "skipped",
1309
+ reason: "worker-not-finished",
1310
+ workerStatus: status.status,
1311
+ alive: status.alive
1312
+ },
1313
+ null,
1314
+ 2
1315
+ )
1316
+ );
1317
+ return;
1318
+ }
1319
+ const result = await tryCompleteWorker(args);
1320
+ console.log(
1321
+ JSON.stringify(
1322
+ {
1323
+ worker: worker.name,
1324
+ runId: worker.runId,
1325
+ agentOsId,
1326
+ taskId,
1327
+ httpStatus: result.httpStatus,
1328
+ response: result.response
1329
+ },
1330
+ null,
1331
+ 2
1332
+ )
1333
+ );
1334
+ if (!result.ok) process.exit(1);
1335
+ } catch (error) {
1336
+ console.error(`worker complete failed: ${error.message}`);
1337
+ process.exit(1);
1338
+ }
1339
+ }
1340
+ function workerStatus(args) {
1341
+ const worker = loadWorker(String(args.run), String(args.name));
1342
+ const status = computeWorkerStatus(worker);
1343
+ writeJson(path7.join(worker.workerDir, "last-status.json"), status);
1344
+ console.log(JSON.stringify(status, null, 2));
1345
+ }
1346
+ function runStatus(args) {
1347
+ const run = loadRun(String(args.run));
1348
+ const names = Object.keys(run.workers || {});
1349
+ const workers = names.map((name) => {
1350
+ const worker = readJson(
1351
+ path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1352
+ void 0
1353
+ );
1354
+ if (!worker) {
1355
+ return { worker: name, status: "missing", attention: "needs_attention", attentionReason: "worker.json not found" };
1356
+ }
1357
+ const status = computeWorkerStatus(worker, { base: run.base });
1358
+ const rawBlocker = worker.completionBlocker;
1359
+ const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
1360
+ return {
1361
+ worker: status.worker,
1362
+ status: completionBlocker ? "blocked" : status.status,
1363
+ attention: completionBlocker ? "blocked" : status.attention.state,
1364
+ attentionReason: completionBlocker ?? status.attention.reason,
1365
+ pid: status.pid,
1366
+ alive: status.alive,
1367
+ currentTool: status.currentTool,
1368
+ lastActivityAt: status.lastActivityAt,
1369
+ lastHeartbeatPhase: status.lastHeartbeatPhase,
1370
+ lastHeartbeatSummary: status.lastHeartbeatSummary,
1371
+ heartbeatBlocker: status.heartbeatBlocker,
1372
+ changedFileCount: status.changedFiles.length,
1373
+ branch: status.branch,
1374
+ ancestry: status.gitAncestry.relation,
1375
+ ancestryChecked: status.gitAncestry.checked
1376
+ };
1377
+ });
1378
+ const board = {
1379
+ runId: run.id,
1380
+ name: run.name,
1381
+ status: deriveRunStatus(run.status, workers),
1382
+ repo: run.repo,
1383
+ workerCount: workers.length,
1384
+ needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
1385
+ workers
1386
+ };
1387
+ writeJson(path7.join(runDirectory(run.id), "last-board.json"), board);
1388
+ console.log(JSON.stringify(board, null, 2));
1389
+ }
1390
+ function tailWorker(args) {
1391
+ const worker = loadWorker(String(args.run), String(args.name));
1392
+ const raw = tailFile(worker.stdoutPath, Number(args.lines || 40));
1393
+ if (args.raw === true || args.raw === "true") {
1394
+ process.stdout.write(raw);
1395
+ return;
1396
+ }
1397
+ for (const line of raw.split("\n").filter(Boolean)) {
1398
+ const event = safeJson(line);
1399
+ const summary = event ? summarizeEvent(event) : line;
1400
+ if (summary) console.log(summary);
1401
+ }
1402
+ }
1403
+ function stopWorker(args) {
1404
+ const worker = loadWorker(String(args.run), String(args.name));
1405
+ if (!isPidAlive(worker.pid)) {
1406
+ console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "not_running" }, null, 2));
1407
+ return;
1408
+ }
1409
+ killWorkerProcess(worker.pid, "SIGTERM");
1410
+ sleepMs(1500);
1411
+ if (isPidAlive(worker.pid)) {
1412
+ killWorkerProcess(worker.pid, "SIGKILL");
1413
+ console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "sigkill_sent" }, null, 2));
1414
+ return;
1415
+ }
1416
+ console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "stopped" }, null, 2));
1417
+ }
1418
+
1419
+ // src/auto-complete.ts
1420
+ var DEFAULT_POLL_MS = 5e3;
1421
+ var DEFAULT_MAX_TOTAL_MS = 6 * 60 * 60 * 1e3;
1422
+ var DEFAULT_COMPLETE_ATTEMPTS = 3;
1423
+ var DEFAULT_COMPLETE_BACKOFF_MS = 5e3;
1424
+ function readArgs(raw) {
1425
+ return {
1426
+ run: String(raw.run || ""),
1427
+ name: String(raw.name || ""),
1428
+ agentOsId: raw.agentOsId ? String(raw.agentOsId) : void 0,
1429
+ pollMs: Number(raw.pollMs) > 0 ? Math.floor(Number(raw.pollMs)) : void 0,
1430
+ maxTotalMs: Number(raw.maxTotalMs) > 0 ? Math.floor(Number(raw.maxTotalMs)) : void 0,
1431
+ completeAttempts: Number(raw.completeAttempts) > 0 ? Math.floor(Number(raw.completeAttempts)) : void 0,
1432
+ completeBackoffMs: Number(raw.completeBackoffMs) > 0 ? Math.floor(Number(raw.completeBackoffMs)) : void 0,
1433
+ baseUrl: raw.baseUrl ? String(raw.baseUrl) : void 0,
1434
+ secret: raw.secret ? String(raw.secret) : void 0
1435
+ };
1436
+ }
1437
+ async function autoCompleteWorker(raw) {
1438
+ const args = readArgs(raw);
1439
+ const pollMs = args.pollMs ?? DEFAULT_POLL_MS;
1440
+ const maxTotalMs = args.maxTotalMs ?? DEFAULT_MAX_TOTAL_MS;
1441
+ const completeAttempts = args.completeAttempts ?? DEFAULT_COMPLETE_ATTEMPTS;
1442
+ const completeBackoffMs = args.completeBackoffMs ?? DEFAULT_COMPLETE_BACKOFF_MS;
1443
+ const worker = loadWorker(args.run, args.name);
1444
+ if (!worker.agentOsId || !worker.taskId) {
1445
+ return {
1446
+ worker: worker.name,
1447
+ runId: worker.runId,
1448
+ outcome: "missing_link",
1449
+ attempts: 0,
1450
+ reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
1451
+ };
1452
+ }
1453
+ const startMs = Date.now();
1454
+ while (true) {
1455
+ const status = computeWorkerStatus(worker);
1456
+ if (isFinishedWorkerStatus(status)) break;
1457
+ if (!isPidAlive(worker.pid)) break;
1458
+ if (Date.now() - startMs > maxTotalMs) {
1459
+ return {
1460
+ worker: worker.name,
1461
+ runId: worker.runId,
1462
+ outcome: "timed_out",
1463
+ attempts: 0,
1464
+ reason: `worker did not finish within ${maxTotalMs}ms`
1465
+ };
1466
+ }
1467
+ sleepMs(pollMs);
1468
+ }
1469
+ let lastHttpStatus;
1470
+ let lastReason;
1471
+ for (let attempt = 1; attempt <= completeAttempts; attempt++) {
1472
+ const result = await tryCompleteWorker({
1473
+ run: args.run,
1474
+ name: args.name,
1475
+ ...args.agentOsId ? { agentOsId: args.agentOsId } : {},
1476
+ ...args.baseUrl ? { baseUrl: args.baseUrl } : {},
1477
+ ...args.secret ? { secret: args.secret } : {}
1478
+ });
1479
+ lastHttpStatus = result.httpStatus;
1480
+ if (result.ok) {
1481
+ return {
1482
+ worker: worker.name,
1483
+ runId: worker.runId,
1484
+ outcome: "completed",
1485
+ httpStatus: result.httpStatus,
1486
+ attempts: attempt
1487
+ };
1488
+ }
1489
+ const authRejected = result.httpStatus === 401 || result.httpStatus === 403;
1490
+ if (authRejected) {
1491
+ lastReason = typeof result.reason === "string" ? result.reason : "completion replay refused";
1492
+ return {
1493
+ worker: worker.name,
1494
+ runId: worker.runId,
1495
+ outcome: "blocked",
1496
+ httpStatus: result.httpStatus,
1497
+ attempts: attempt,
1498
+ reason: lastReason
1499
+ };
1500
+ }
1501
+ lastReason = typeof result.reason === "string" ? result.reason : "transient failure";
1502
+ if (attempt < completeAttempts) sleepMs(completeBackoffMs);
1503
+ }
1504
+ return {
1505
+ worker: worker.name,
1506
+ runId: worker.runId,
1507
+ outcome: "blocked",
1508
+ httpStatus: lastHttpStatus,
1509
+ attempts: completeAttempts,
1510
+ reason: lastReason ?? "completion failed after retries"
1511
+ };
1512
+ }
1513
+ async function autoCompleteWorkerCli(raw) {
1514
+ try {
1515
+ const outcome = await autoCompleteWorker(raw);
1516
+ console.log(JSON.stringify(outcome, null, 2));
1517
+ if (outcome.outcome === "missing_link" || outcome.outcome === "timed_out") {
1518
+ process.exitCode = 1;
1519
+ }
1520
+ } catch (error) {
1521
+ console.error(`worker auto-complete failed: ${error.message}`);
1522
+ process.exitCode = 1;
1523
+ }
1524
+ }
1525
+ function resolveDefaultCliPath() {
1526
+ return path8.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
1527
+ }
1528
+ function spawnCompletionSidecar(opts) {
1529
+ const cliPath = opts.cliPath ?? resolveDefaultCliPath();
1530
+ if (!existsSync8(cliPath)) return void 0;
1531
+ const logPath = path8.join(opts.workerDir, "auto-complete.log");
1532
+ let logFd;
1533
+ try {
1534
+ logFd = openSync3(logPath, "a");
1535
+ } catch {
1536
+ logFd = void 0;
1537
+ }
1538
+ const stdio = [
1539
+ "ignore",
1540
+ logFd ?? "ignore",
1541
+ logFd ?? "ignore"
1542
+ ];
1543
+ const nodeExecutable = opts.nodeExecutable ?? process.execPath;
1544
+ const args = [
1545
+ cliPath,
1546
+ "worker",
1547
+ "auto-complete",
1548
+ "--run",
1549
+ opts.runId,
1550
+ "--name",
1551
+ opts.workerName
1552
+ ];
1553
+ if (opts.agentOsId) args.push("--agent-os-id", opts.agentOsId);
1554
+ if (opts.baseUrl) args.push("--base-url", opts.baseUrl);
1555
+ if (opts.secret) args.push("--secret", opts.secret);
1556
+ try {
1557
+ const child = spawn3(nodeExecutable, args, {
1558
+ detached: true,
1559
+ stdio,
1560
+ env: process.env
1561
+ });
1562
+ if (logFd !== void 0) closeSync3(logFd);
1563
+ child.unref();
1564
+ return { pid: child.pid, logPath, cliPath };
1565
+ } catch {
1566
+ if (logFd !== void 0) {
1567
+ try {
1568
+ closeSync3(logFd);
1569
+ } catch {
1570
+ }
1571
+ }
1572
+ return void 0;
1573
+ }
1574
+ }
1575
+
1085
1576
  // src/supervisor.ts
1086
1577
  function spawnWorkerProcess(run, opts) {
1087
1578
  const rawName = typeof opts.name === "string" ? opts.name.trim() : "";
@@ -1091,31 +1582,44 @@ function spawnWorkerProcess(run, opts) {
1091
1582
  const name = safeSlug(rawName);
1092
1583
  if (run.workers?.[name]) throw new Error(`worker already exists in run ${run.id}: ${name}`);
1093
1584
  if (!opts.task) throw new Error(`missing task text for worker ${name}`);
1585
+ const provider = resolveWorkerProvider(opts.provider);
1586
+ let launchModel = opts.model;
1587
+ if (provider.preflightModel) {
1588
+ const preflight = provider.preflightModel(opts.model);
1589
+ if (!preflight.ok) {
1590
+ throw new Error(
1591
+ `model preflight failed for provider "${provider.name}": ${preflight.note ?? "invalid model/provider combination"}`
1592
+ );
1593
+ }
1594
+ if (preflight.normalized) {
1595
+ console.error(`[supervisor] ${name}: ${preflight.note}`);
1596
+ }
1597
+ launchModel = preflight.model;
1598
+ }
1094
1599
  const { worktreesDir } = getPaths();
1095
- const workerDir = path7.join(runDirectory(run.id), "workers", name);
1600
+ const workerDir = path9.join(runDirectory(run.id), "workers", name);
1096
1601
  mkdirSync3(workerDir, { recursive: true });
1097
- const worktreePath = path7.join(worktreesDir, run.id, name);
1602
+ const worktreePath = path9.join(worktreesDir, run.id, name);
1098
1603
  const branch = opts.branch || `agent/${run.id}/${name}`;
1099
- if (existsSync8(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
1604
+ if (existsSync9(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
1100
1605
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
1101
1606
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
1102
- const stdoutPath = path7.join(workerDir, "stdout.jsonl");
1103
- const stderrPath = path7.join(workerDir, "stderr.log");
1104
- const heartbeatPath = path7.join(workerDir, "heartbeat.jsonl");
1607
+ const stdoutPath = path9.join(workerDir, "stdout.jsonl");
1608
+ const stderrPath = path9.join(workerDir, "stderr.log");
1609
+ const heartbeatPath = path9.join(workerDir, "heartbeat.jsonl");
1105
1610
  const prompt = buildPrompt({
1106
1611
  task: opts.task,
1107
1612
  ownedPaths: opts.ownedPaths || [],
1108
1613
  worktreePath,
1109
1614
  heartbeatPath
1110
1615
  });
1111
- const provider = resolveWorkerProvider(opts.provider);
1112
1616
  let started;
1113
1617
  try {
1114
1618
  started = provider.start({
1115
1619
  name,
1116
1620
  task: opts.task,
1117
1621
  ownedPaths: opts.ownedPaths,
1118
- model: opts.model,
1622
+ model: launchModel,
1119
1623
  branch,
1120
1624
  worktreePath,
1121
1625
  workerDir,
@@ -1129,7 +1633,7 @@ function spawnWorkerProcess(run, opts) {
1129
1633
  git(run.repo, ["branch", "-D", branch], { allowFailure: true });
1130
1634
  throw error;
1131
1635
  }
1132
- const model = started.model || opts.model || "claude-opus-4-7";
1636
+ const model = started.model || launchModel || provider.defaultModel || "claude-opus-4-7";
1133
1637
  const worker = {
1134
1638
  name,
1135
1639
  runId: run.id,
@@ -1151,9 +1655,20 @@ function spawnWorkerProcess(run, opts) {
1151
1655
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
1152
1656
  };
1153
1657
  saveWorker(run.id, worker);
1154
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path7.join(workerDir, "worker.json") } };
1658
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path9.join(workerDir, "worker.json") } };
1155
1659
  run.status = "running";
1156
1660
  saveRun(run);
1661
+ if (worker.agentOsId && worker.taskId) {
1662
+ try {
1663
+ spawnCompletionSidecar({
1664
+ runId: run.id,
1665
+ workerName: name,
1666
+ workerDir,
1667
+ agentOsId: worker.agentOsId
1668
+ });
1669
+ } catch {
1670
+ }
1671
+ }
1157
1672
  return worker;
1158
1673
  }
1159
1674
  function startWorker(args) {
@@ -1354,7 +1869,7 @@ async function dispatchRun(args) {
1354
1869
  }
1355
1870
 
1356
1871
  // src/sweep.ts
1357
- import path8 from "node:path";
1872
+ import path10 from "node:path";
1358
1873
  async function sweepRun(args) {
1359
1874
  const pipeline = args.pipeline === true || args.pipeline === "true";
1360
1875
  try {
@@ -1366,7 +1881,7 @@ async function sweepRun(args) {
1366
1881
  const releasedLocalOrphans = [];
1367
1882
  for (const name of Object.keys(run.workers || {})) {
1368
1883
  const worker = readJson(
1369
- path8.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1884
+ path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1370
1885
  void 0
1371
1886
  );
1372
1887
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -1409,11 +1924,11 @@ async function sweepRun(args) {
1409
1924
  }
1410
1925
 
1411
1926
  // src/worktree.ts
1412
- import { existsSync as existsSync9, mkdirSync as mkdirSync4 } from "node:fs";
1413
- import path10 from "node:path";
1927
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4 } from "node:fs";
1928
+ import path12 from "node:path";
1414
1929
 
1415
1930
  // src/validate.ts
1416
- import path9 from "node:path";
1931
+ import path11 from "node:path";
1417
1932
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
1418
1933
  function validateRunId(runId) {
1419
1934
  const trimmed = runId.trim();
@@ -1421,7 +1936,7 @@ function validateRunId(runId) {
1421
1936
  return trimmed;
1422
1937
  }
1423
1938
  function validateRepo(repo) {
1424
- const resolved = path9.resolve(repo);
1939
+ const resolved = path11.resolve(repo);
1425
1940
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
1426
1941
  return resolved;
1427
1942
  }
@@ -1432,7 +1947,7 @@ function createRun(args) {
1432
1947
  ensureGitRepo(repo);
1433
1948
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
1434
1949
  const dir = runDirectory(id);
1435
- if (existsSync9(dir)) failExists(`run already exists: ${id}`);
1950
+ if (existsSync10(dir)) failExists(`run already exists: ${id}`);
1436
1951
  mkdirSync4(dir, { recursive: true });
1437
1952
  const base = String(args.base || "origin/main");
1438
1953
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -1446,12 +1961,12 @@ function createRun(args) {
1446
1961
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1447
1962
  workers: {}
1448
1963
  };
1449
- writeJson(path10.join(dir, "run.json"), run);
1964
+ writeJson(path12.join(dir, "run.json"), run);
1450
1965
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
1451
1966
  }
1452
1967
  function listRuns() {
1453
1968
  const { runsDir } = getPaths();
1454
- const rows = listRunIds(runsDir).map((id) => readJson(path10.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
1969
+ const rows = listRunIds(runsDir).map((id) => readJson(path12.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
1455
1970
  id: run.id,
1456
1971
  name: run.name,
1457
1972
  status: run.status,
@@ -1465,211 +1980,11 @@ function failExists(message) {
1465
1980
  process.exit(1);
1466
1981
  }
1467
1982
 
1468
- // src/worker-ops.ts
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
- }
1498
- async function tryCompleteWorker(args) {
1499
- const worker = loadWorker(String(args.run), String(args.name));
1500
- const status = computeWorkerStatus(worker);
1501
- const agentOsId = (args.agentOsId ? String(args.agentOsId) : worker.agentOsId) || "";
1502
- const taskId = (args.taskId ? String(args.taskId) : worker.taskId) || null;
1503
- if (!agentOsId) {
1504
- return { ok: false, reason: "missing agentOsId" };
1505
- }
1506
- if (!isFinishedWorkerStatus(status)) {
1507
- return { ok: true, skipped: true, reason: "worker-not-finished" };
1508
- }
1509
- const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1510
- const explicitSecret = args.secret ? String(args.secret) : void 0;
1511
- let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
1512
- const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
1513
- const body = {
1514
- source: "openclaw-harness",
1515
- agentOsId,
1516
- runId: worker.runId,
1517
- workerName: worker.name,
1518
- taskId,
1519
- startedAt: worker.startedAt,
1520
- finishedAt: status.lastActivityAt || (/* @__PURE__ */ new Date()).toISOString(),
1521
- status
1522
- };
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
- }
1530
- }
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 };
1540
- }
1541
- async function completeWorker(args) {
1542
- try {
1543
- const worker = loadWorker(String(args.run), String(args.name));
1544
- const status = computeWorkerStatus(worker);
1545
- const agentOsId = (args.agentOsId ? String(args.agentOsId) : worker.agentOsId) || "";
1546
- const taskId = (args.taskId ? String(args.taskId) : worker.taskId) || null;
1547
- if (!agentOsId) {
1548
- console.error("worker complete requires --agent-os-id (or an agentOsId persisted at worker start)");
1549
- process.exit(1);
1550
- }
1551
- if (!isFinishedWorkerStatus(status)) {
1552
- console.log(
1553
- JSON.stringify(
1554
- {
1555
- worker: worker.name,
1556
- runId: worker.runId,
1557
- status: "skipped",
1558
- reason: "worker-not-finished",
1559
- workerStatus: status.status,
1560
- alive: status.alive
1561
- },
1562
- null,
1563
- 2
1564
- )
1565
- );
1566
- return;
1567
- }
1568
- const result = await tryCompleteWorker(args);
1569
- console.log(
1570
- JSON.stringify(
1571
- {
1572
- worker: worker.name,
1573
- runId: worker.runId,
1574
- agentOsId,
1575
- taskId,
1576
- httpStatus: result.httpStatus,
1577
- response: result.response
1578
- },
1579
- null,
1580
- 2
1581
- )
1582
- );
1583
- if (!result.ok) process.exit(1);
1584
- } catch (error) {
1585
- console.error(`worker complete failed: ${error.message}`);
1586
- process.exit(1);
1587
- }
1588
- }
1589
- function workerStatus(args) {
1590
- const worker = loadWorker(String(args.run), String(args.name));
1591
- const status = computeWorkerStatus(worker);
1592
- writeJson(path11.join(worker.workerDir, "last-status.json"), status);
1593
- console.log(JSON.stringify(status, null, 2));
1594
- }
1595
- function runStatus(args) {
1596
- const run = loadRun(String(args.run));
1597
- const names = Object.keys(run.workers || {});
1598
- const workers = names.map((name) => {
1599
- const worker = readJson(
1600
- path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1601
- void 0
1602
- );
1603
- if (!worker) {
1604
- return { worker: name, status: "missing", attention: "needs_attention", attentionReason: "worker.json not found" };
1605
- }
1606
- const status = computeWorkerStatus(worker, { base: run.base });
1607
- const rawBlocker = worker.completionBlocker;
1608
- const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
1609
- return {
1610
- worker: status.worker,
1611
- status: completionBlocker ? "blocked" : status.status,
1612
- attention: completionBlocker ? "blocked" : status.attention.state,
1613
- attentionReason: completionBlocker ?? status.attention.reason,
1614
- pid: status.pid,
1615
- alive: status.alive,
1616
- currentTool: status.currentTool,
1617
- lastActivityAt: status.lastActivityAt,
1618
- lastHeartbeatPhase: status.lastHeartbeatPhase,
1619
- lastHeartbeatSummary: status.lastHeartbeatSummary,
1620
- heartbeatBlocker: status.heartbeatBlocker,
1621
- changedFileCount: status.changedFiles.length,
1622
- branch: status.branch,
1623
- ancestry: status.gitAncestry.relation,
1624
- ancestryChecked: status.gitAncestry.checked
1625
- };
1626
- });
1627
- const board = {
1628
- runId: run.id,
1629
- name: run.name,
1630
- status: deriveRunStatus(run.status, workers),
1631
- repo: run.repo,
1632
- workerCount: workers.length,
1633
- needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
1634
- workers
1635
- };
1636
- writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
1637
- console.log(JSON.stringify(board, null, 2));
1638
- }
1639
- function tailWorker(args) {
1640
- const worker = loadWorker(String(args.run), String(args.name));
1641
- const raw = tailFile(worker.stdoutPath, Number(args.lines || 40));
1642
- if (args.raw === true || args.raw === "true") {
1643
- process.stdout.write(raw);
1644
- return;
1645
- }
1646
- for (const line of raw.split("\n").filter(Boolean)) {
1647
- const event = safeJson(line);
1648
- const summary = event ? summarizeEvent(event) : line;
1649
- if (summary) console.log(summary);
1650
- }
1651
- }
1652
- function stopWorker(args) {
1653
- const worker = loadWorker(String(args.run), String(args.name));
1654
- if (!isPidAlive(worker.pid)) {
1655
- console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "not_running" }, null, 2));
1656
- return;
1657
- }
1658
- killWorkerProcess(worker.pid, "SIGTERM");
1659
- sleepMs(1500);
1660
- if (isPidAlive(worker.pid)) {
1661
- killWorkerProcess(worker.pid, "SIGKILL");
1662
- console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "sigkill_sent" }, null, 2));
1663
- return;
1664
- }
1665
- console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "stopped" }, null, 2));
1666
- }
1667
-
1668
1983
  // src/pipeline-tick.ts
1669
- import path14 from "node:path";
1984
+ import path15 from "node:path";
1670
1985
 
1671
1986
  // src/finalize.ts
1672
- import path12 from "node:path";
1987
+ import path13 from "node:path";
1673
1988
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
1674
1989
  function terminalStatusFor(run) {
1675
1990
  const names = Object.keys(run.workers || {});
@@ -1679,7 +1994,7 @@ function terminalStatusFor(run) {
1679
1994
  let anyCompletionBlocked = false;
1680
1995
  for (const name of names) {
1681
1996
  const worker = readJson(
1682
- path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1997
+ path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1683
1998
  void 0
1684
1999
  );
1685
2000
  if (!worker) continue;
@@ -1712,7 +2027,7 @@ function finalizeStaleRuns() {
1712
2027
  }
1713
2028
 
1714
2029
  // src/plan-progress-daemon-sync.ts
1715
- import path13 from "node:path";
2030
+ import path14 from "node:path";
1716
2031
 
1717
2032
  // src/plan-progress-sync.ts
1718
2033
  async function syncPlanProgress(args) {
@@ -1736,7 +2051,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
1736
2051
  const outcomes = [];
1737
2052
  for (const name of Object.keys(run.workers || {})) {
1738
2053
  const worker = readJson(
1739
- path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2054
+ path14.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1740
2055
  void 0
1741
2056
  );
1742
2057
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -1790,7 +2105,7 @@ async function completeFinishedWorkers(runId, args) {
1790
2105
  const outcomes = [];
1791
2106
  for (const name of Object.keys(run.workers || {})) {
1792
2107
  const worker = readJson(
1793
- path14.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2108
+ path15.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1794
2109
  void 0
1795
2110
  );
1796
2111
  if (!worker?.taskId) continue;
@@ -2033,6 +2348,7 @@ function usage(code = 0) {
2033
2348
  " kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
2034
2349
  " kynver worker stop --run RUN_ID --name worker",
2035
2350
  " kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
2351
+ " kynver worker auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--poll-ms 5000] [--max-total-ms 21600000] [--complete-attempts 3] [--complete-backoff-ms 5000] [--base-url URL] [--secret SECRET]",
2036
2352
  " kynver plan progress --plan PLAN_ID --row ROW_KEY --role ROLE --status STATUS [--task TASK_ID] [--note NOTE] [--evidence type:value] [--agent-os-id AOS_ID]",
2037
2353
  " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]"
2038
2354
  ].join("\n")
@@ -2071,9 +2387,10 @@ async function main(argv = process.argv.slice(2)) {
2071
2387
  if (scope === "worker" && action === "tail") return tailWorker(args);
2072
2388
  if (scope === "worker" && action === "stop") return stopWorker(args);
2073
2389
  if (scope === "worker" && action === "complete") return void await completeWorker(args);
2390
+ if (scope === "worker" && action === "auto-complete") return void await autoCompleteWorkerCli(args);
2074
2391
  unknownCommand(scope, action);
2075
2392
  }
2076
- var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath(import.meta.url));
2393
+ var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath2(import.meta.url));
2077
2394
  if (isCliEntry) {
2078
2395
  void main().catch((error) => {
2079
2396
  console.error(error);