@kynver-app/runtime 0.1.16 → 0.1.17

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,
@@ -907,8 +907,8 @@ function observeRunnerResourceGate(input) {
907
907
  }
908
908
 
909
909
  // src/supervisor.ts
910
- import { existsSync as existsSync8, mkdirSync as mkdirSync3 } from "node:fs";
911
- import path7 from "node:path";
910
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3 } from "node:fs";
911
+ import path9 from "node:path";
912
912
 
913
913
  // src/prompt.ts
914
914
  function buildPrompt(input) {
@@ -1082,6 +1082,369 @@ function resolveWorkerProvider(name) {
1082
1082
  return provider;
1083
1083
  }
1084
1084
 
1085
+ // src/auto-complete.ts
1086
+ import { spawn as spawn3 } from "node:child_process";
1087
+ import { existsSync as existsSync8, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
1088
+ import path8 from "node:path";
1089
+ import { fileURLToPath } from "node:url";
1090
+
1091
+ // src/worker-ops.ts
1092
+ import path7 from "node:path";
1093
+ async function postCompletion(url, secret, body) {
1094
+ const res = await fetch(url, {
1095
+ method: "POST",
1096
+ headers: buildHarnessCallbackHeaders(secret),
1097
+ body: JSON.stringify(body)
1098
+ });
1099
+ let parsed = null;
1100
+ try {
1101
+ parsed = await res.json();
1102
+ } catch {
1103
+ parsed = null;
1104
+ }
1105
+ return { ok: res.ok, status: res.status, parsed };
1106
+ }
1107
+ function completionErrorText(parsed) {
1108
+ if (parsed && typeof parsed === "object") {
1109
+ const err = parsed.error;
1110
+ if (typeof err === "string" && err.trim()) return err.trim();
1111
+ }
1112
+ return void 0;
1113
+ }
1114
+ function persistCompletionBlocker(worker, reason) {
1115
+ const current = worker.completionBlocker;
1116
+ if ((current ?? void 0) === (reason ?? void 0)) return;
1117
+ if (reason) worker.completionBlocker = reason;
1118
+ else delete worker.completionBlocker;
1119
+ saveWorker(worker.runId, worker);
1120
+ }
1121
+ async function tryCompleteWorker(args) {
1122
+ const worker = loadWorker(String(args.run), String(args.name));
1123
+ const status = computeWorkerStatus(worker);
1124
+ const agentOsId = (args.agentOsId ? String(args.agentOsId) : worker.agentOsId) || "";
1125
+ const taskId = (args.taskId ? String(args.taskId) : worker.taskId) || null;
1126
+ if (!agentOsId) {
1127
+ return { ok: false, reason: "missing agentOsId" };
1128
+ }
1129
+ if (!isFinishedWorkerStatus(status)) {
1130
+ return { ok: true, skipped: true, reason: "worker-not-finished" };
1131
+ }
1132
+ const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1133
+ const explicitSecret = args.secret ? String(args.secret) : void 0;
1134
+ let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
1135
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
1136
+ const body = {
1137
+ source: "openclaw-harness",
1138
+ agentOsId,
1139
+ runId: worker.runId,
1140
+ workerName: worker.name,
1141
+ taskId,
1142
+ startedAt: worker.startedAt,
1143
+ finishedAt: status.lastActivityAt || (/* @__PURE__ */ new Date()).toISOString(),
1144
+ status
1145
+ };
1146
+ let result = await postCompletion(url, secret, body);
1147
+ if ((result.status === 401 || result.status === 403) && !explicitSecret) {
1148
+ const refreshed = await refreshRunnerToken(agentOsId, { baseUrl: base });
1149
+ if (refreshed && refreshed !== secret) {
1150
+ secret = refreshed;
1151
+ result = await postCompletion(url, secret, body);
1152
+ }
1153
+ }
1154
+ if (result.ok) {
1155
+ persistCompletionBlocker(worker, void 0);
1156
+ return { ok: true, httpStatus: result.status, response: result.parsed };
1157
+ }
1158
+ const authRejected = result.status === 401 || result.status === 403;
1159
+ const detail = completionErrorText(result.parsed) ?? (authRejected ? "runner token unauthorized" : "non-2xx response");
1160
+ const reason = authRejected ? `completion replay rejected (${result.status}): ${detail}` : `completion replay failed (${result.status}): ${detail}`;
1161
+ persistCompletionBlocker(worker, reason);
1162
+ return { ok: false, httpStatus: result.status, response: result.parsed, completionBlocked: true };
1163
+ }
1164
+ async function completeWorker(args) {
1165
+ try {
1166
+ const worker = loadWorker(String(args.run), String(args.name));
1167
+ const status = computeWorkerStatus(worker);
1168
+ const agentOsId = (args.agentOsId ? String(args.agentOsId) : worker.agentOsId) || "";
1169
+ const taskId = (args.taskId ? String(args.taskId) : worker.taskId) || null;
1170
+ if (!agentOsId) {
1171
+ console.error("worker complete requires --agent-os-id (or an agentOsId persisted at worker start)");
1172
+ process.exit(1);
1173
+ }
1174
+ if (!isFinishedWorkerStatus(status)) {
1175
+ console.log(
1176
+ JSON.stringify(
1177
+ {
1178
+ worker: worker.name,
1179
+ runId: worker.runId,
1180
+ status: "skipped",
1181
+ reason: "worker-not-finished",
1182
+ workerStatus: status.status,
1183
+ alive: status.alive
1184
+ },
1185
+ null,
1186
+ 2
1187
+ )
1188
+ );
1189
+ return;
1190
+ }
1191
+ const result = await tryCompleteWorker(args);
1192
+ console.log(
1193
+ JSON.stringify(
1194
+ {
1195
+ worker: worker.name,
1196
+ runId: worker.runId,
1197
+ agentOsId,
1198
+ taskId,
1199
+ httpStatus: result.httpStatus,
1200
+ response: result.response
1201
+ },
1202
+ null,
1203
+ 2
1204
+ )
1205
+ );
1206
+ if (!result.ok) process.exit(1);
1207
+ } catch (error) {
1208
+ console.error(`worker complete failed: ${error.message}`);
1209
+ process.exit(1);
1210
+ }
1211
+ }
1212
+ function workerStatus(args) {
1213
+ const worker = loadWorker(String(args.run), String(args.name));
1214
+ const status = computeWorkerStatus(worker);
1215
+ writeJson(path7.join(worker.workerDir, "last-status.json"), status);
1216
+ console.log(JSON.stringify(status, null, 2));
1217
+ }
1218
+ function runStatus(args) {
1219
+ const run = loadRun(String(args.run));
1220
+ const names = Object.keys(run.workers || {});
1221
+ const workers = names.map((name) => {
1222
+ const worker = readJson(
1223
+ path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1224
+ void 0
1225
+ );
1226
+ if (!worker) {
1227
+ return { worker: name, status: "missing", attention: "needs_attention", attentionReason: "worker.json not found" };
1228
+ }
1229
+ const status = computeWorkerStatus(worker, { base: run.base });
1230
+ const rawBlocker = worker.completionBlocker;
1231
+ const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
1232
+ return {
1233
+ worker: status.worker,
1234
+ status: completionBlocker ? "blocked" : status.status,
1235
+ attention: completionBlocker ? "blocked" : status.attention.state,
1236
+ attentionReason: completionBlocker ?? status.attention.reason,
1237
+ pid: status.pid,
1238
+ alive: status.alive,
1239
+ currentTool: status.currentTool,
1240
+ lastActivityAt: status.lastActivityAt,
1241
+ lastHeartbeatPhase: status.lastHeartbeatPhase,
1242
+ lastHeartbeatSummary: status.lastHeartbeatSummary,
1243
+ heartbeatBlocker: status.heartbeatBlocker,
1244
+ changedFileCount: status.changedFiles.length,
1245
+ branch: status.branch,
1246
+ ancestry: status.gitAncestry.relation,
1247
+ ancestryChecked: status.gitAncestry.checked
1248
+ };
1249
+ });
1250
+ const board = {
1251
+ runId: run.id,
1252
+ name: run.name,
1253
+ status: deriveRunStatus(run.status, workers),
1254
+ repo: run.repo,
1255
+ workerCount: workers.length,
1256
+ needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
1257
+ workers
1258
+ };
1259
+ writeJson(path7.join(runDirectory(run.id), "last-board.json"), board);
1260
+ console.log(JSON.stringify(board, null, 2));
1261
+ }
1262
+ function tailWorker(args) {
1263
+ const worker = loadWorker(String(args.run), String(args.name));
1264
+ const raw = tailFile(worker.stdoutPath, Number(args.lines || 40));
1265
+ if (args.raw === true || args.raw === "true") {
1266
+ process.stdout.write(raw);
1267
+ return;
1268
+ }
1269
+ for (const line of raw.split("\n").filter(Boolean)) {
1270
+ const event = safeJson(line);
1271
+ const summary = event ? summarizeEvent(event) : line;
1272
+ if (summary) console.log(summary);
1273
+ }
1274
+ }
1275
+ function stopWorker(args) {
1276
+ const worker = loadWorker(String(args.run), String(args.name));
1277
+ if (!isPidAlive(worker.pid)) {
1278
+ console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "not_running" }, null, 2));
1279
+ return;
1280
+ }
1281
+ killWorkerProcess(worker.pid, "SIGTERM");
1282
+ sleepMs(1500);
1283
+ if (isPidAlive(worker.pid)) {
1284
+ killWorkerProcess(worker.pid, "SIGKILL");
1285
+ console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "sigkill_sent" }, null, 2));
1286
+ return;
1287
+ }
1288
+ console.log(JSON.stringify({ worker: worker.name, pid: worker.pid, status: "stopped" }, null, 2));
1289
+ }
1290
+
1291
+ // src/auto-complete.ts
1292
+ var DEFAULT_POLL_MS = 5e3;
1293
+ var DEFAULT_MAX_TOTAL_MS = 6 * 60 * 60 * 1e3;
1294
+ var DEFAULT_COMPLETE_ATTEMPTS = 3;
1295
+ var DEFAULT_COMPLETE_BACKOFF_MS = 5e3;
1296
+ function readArgs(raw) {
1297
+ return {
1298
+ run: String(raw.run || ""),
1299
+ name: String(raw.name || ""),
1300
+ agentOsId: raw.agentOsId ? String(raw.agentOsId) : void 0,
1301
+ pollMs: Number(raw.pollMs) > 0 ? Math.floor(Number(raw.pollMs)) : void 0,
1302
+ maxTotalMs: Number(raw.maxTotalMs) > 0 ? Math.floor(Number(raw.maxTotalMs)) : void 0,
1303
+ completeAttempts: Number(raw.completeAttempts) > 0 ? Math.floor(Number(raw.completeAttempts)) : void 0,
1304
+ completeBackoffMs: Number(raw.completeBackoffMs) > 0 ? Math.floor(Number(raw.completeBackoffMs)) : void 0,
1305
+ baseUrl: raw.baseUrl ? String(raw.baseUrl) : void 0,
1306
+ secret: raw.secret ? String(raw.secret) : void 0
1307
+ };
1308
+ }
1309
+ async function autoCompleteWorker(raw) {
1310
+ const args = readArgs(raw);
1311
+ const pollMs = args.pollMs ?? DEFAULT_POLL_MS;
1312
+ const maxTotalMs = args.maxTotalMs ?? DEFAULT_MAX_TOTAL_MS;
1313
+ const completeAttempts = args.completeAttempts ?? DEFAULT_COMPLETE_ATTEMPTS;
1314
+ const completeBackoffMs = args.completeBackoffMs ?? DEFAULT_COMPLETE_BACKOFF_MS;
1315
+ const worker = loadWorker(args.run, args.name);
1316
+ if (!worker.agentOsId || !worker.taskId) {
1317
+ return {
1318
+ worker: worker.name,
1319
+ runId: worker.runId,
1320
+ outcome: "missing_link",
1321
+ attempts: 0,
1322
+ reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
1323
+ };
1324
+ }
1325
+ const startMs = Date.now();
1326
+ while (true) {
1327
+ const status = computeWorkerStatus(worker);
1328
+ if (isFinishedWorkerStatus(status)) break;
1329
+ if (!isPidAlive(worker.pid)) break;
1330
+ if (Date.now() - startMs > maxTotalMs) {
1331
+ return {
1332
+ worker: worker.name,
1333
+ runId: worker.runId,
1334
+ outcome: "timed_out",
1335
+ attempts: 0,
1336
+ reason: `worker did not finish within ${maxTotalMs}ms`
1337
+ };
1338
+ }
1339
+ sleepMs(pollMs);
1340
+ }
1341
+ let lastHttpStatus;
1342
+ let lastReason;
1343
+ for (let attempt = 1; attempt <= completeAttempts; attempt++) {
1344
+ const result = await tryCompleteWorker({
1345
+ run: args.run,
1346
+ name: args.name,
1347
+ ...args.agentOsId ? { agentOsId: args.agentOsId } : {},
1348
+ ...args.baseUrl ? { baseUrl: args.baseUrl } : {},
1349
+ ...args.secret ? { secret: args.secret } : {}
1350
+ });
1351
+ lastHttpStatus = result.httpStatus;
1352
+ if (result.ok) {
1353
+ return {
1354
+ worker: worker.name,
1355
+ runId: worker.runId,
1356
+ outcome: "completed",
1357
+ httpStatus: result.httpStatus,
1358
+ attempts: attempt
1359
+ };
1360
+ }
1361
+ const authRejected = result.httpStatus === 401 || result.httpStatus === 403;
1362
+ if (authRejected) {
1363
+ lastReason = typeof result.reason === "string" ? result.reason : "completion replay refused";
1364
+ return {
1365
+ worker: worker.name,
1366
+ runId: worker.runId,
1367
+ outcome: "blocked",
1368
+ httpStatus: result.httpStatus,
1369
+ attempts: attempt,
1370
+ reason: lastReason
1371
+ };
1372
+ }
1373
+ lastReason = typeof result.reason === "string" ? result.reason : "transient failure";
1374
+ if (attempt < completeAttempts) sleepMs(completeBackoffMs);
1375
+ }
1376
+ return {
1377
+ worker: worker.name,
1378
+ runId: worker.runId,
1379
+ outcome: "blocked",
1380
+ httpStatus: lastHttpStatus,
1381
+ attempts: completeAttempts,
1382
+ reason: lastReason ?? "completion failed after retries"
1383
+ };
1384
+ }
1385
+ async function autoCompleteWorkerCli(raw) {
1386
+ try {
1387
+ const outcome = await autoCompleteWorker(raw);
1388
+ console.log(JSON.stringify(outcome, null, 2));
1389
+ if (outcome.outcome === "missing_link" || outcome.outcome === "timed_out") {
1390
+ process.exitCode = 1;
1391
+ }
1392
+ } catch (error) {
1393
+ console.error(`worker auto-complete failed: ${error.message}`);
1394
+ process.exitCode = 1;
1395
+ }
1396
+ }
1397
+ function resolveDefaultCliPath() {
1398
+ return path8.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
1399
+ }
1400
+ function spawnCompletionSidecar(opts) {
1401
+ const cliPath = opts.cliPath ?? resolveDefaultCliPath();
1402
+ if (!existsSync8(cliPath)) return void 0;
1403
+ const logPath = path8.join(opts.workerDir, "auto-complete.log");
1404
+ let logFd;
1405
+ try {
1406
+ logFd = openSync3(logPath, "a");
1407
+ } catch {
1408
+ logFd = void 0;
1409
+ }
1410
+ const stdio = [
1411
+ "ignore",
1412
+ logFd ?? "ignore",
1413
+ logFd ?? "ignore"
1414
+ ];
1415
+ const nodeExecutable = opts.nodeExecutable ?? process.execPath;
1416
+ const args = [
1417
+ cliPath,
1418
+ "worker",
1419
+ "auto-complete",
1420
+ "--run",
1421
+ opts.runId,
1422
+ "--name",
1423
+ opts.workerName
1424
+ ];
1425
+ if (opts.agentOsId) args.push("--agent-os-id", opts.agentOsId);
1426
+ if (opts.baseUrl) args.push("--base-url", opts.baseUrl);
1427
+ if (opts.secret) args.push("--secret", opts.secret);
1428
+ try {
1429
+ const child = spawn3(nodeExecutable, args, {
1430
+ detached: true,
1431
+ stdio,
1432
+ env: process.env
1433
+ });
1434
+ if (logFd !== void 0) closeSync3(logFd);
1435
+ child.unref();
1436
+ return { pid: child.pid, logPath, cliPath };
1437
+ } catch {
1438
+ if (logFd !== void 0) {
1439
+ try {
1440
+ closeSync3(logFd);
1441
+ } catch {
1442
+ }
1443
+ }
1444
+ return void 0;
1445
+ }
1446
+ }
1447
+
1085
1448
  // src/supervisor.ts
1086
1449
  function spawnWorkerProcess(run, opts) {
1087
1450
  const rawName = typeof opts.name === "string" ? opts.name.trim() : "";
@@ -1092,16 +1455,16 @@ function spawnWorkerProcess(run, opts) {
1092
1455
  if (run.workers?.[name]) throw new Error(`worker already exists in run ${run.id}: ${name}`);
1093
1456
  if (!opts.task) throw new Error(`missing task text for worker ${name}`);
1094
1457
  const { worktreesDir } = getPaths();
1095
- const workerDir = path7.join(runDirectory(run.id), "workers", name);
1458
+ const workerDir = path9.join(runDirectory(run.id), "workers", name);
1096
1459
  mkdirSync3(workerDir, { recursive: true });
1097
- const worktreePath = path7.join(worktreesDir, run.id, name);
1460
+ const worktreePath = path9.join(worktreesDir, run.id, name);
1098
1461
  const branch = opts.branch || `agent/${run.id}/${name}`;
1099
- if (existsSync8(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
1462
+ if (existsSync9(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
1100
1463
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
1101
1464
  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");
1465
+ const stdoutPath = path9.join(workerDir, "stdout.jsonl");
1466
+ const stderrPath = path9.join(workerDir, "stderr.log");
1467
+ const heartbeatPath = path9.join(workerDir, "heartbeat.jsonl");
1105
1468
  const prompt = buildPrompt({
1106
1469
  task: opts.task,
1107
1470
  ownedPaths: opts.ownedPaths || [],
@@ -1151,9 +1514,20 @@ function spawnWorkerProcess(run, opts) {
1151
1514
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
1152
1515
  };
1153
1516
  saveWorker(run.id, worker);
1154
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path7.join(workerDir, "worker.json") } };
1517
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path9.join(workerDir, "worker.json") } };
1155
1518
  run.status = "running";
1156
1519
  saveRun(run);
1520
+ if (worker.agentOsId && worker.taskId) {
1521
+ try {
1522
+ spawnCompletionSidecar({
1523
+ runId: run.id,
1524
+ workerName: name,
1525
+ workerDir,
1526
+ agentOsId: worker.agentOsId
1527
+ });
1528
+ } catch {
1529
+ }
1530
+ }
1157
1531
  return worker;
1158
1532
  }
1159
1533
  function startWorker(args) {
@@ -1354,7 +1728,7 @@ async function dispatchRun(args) {
1354
1728
  }
1355
1729
 
1356
1730
  // src/sweep.ts
1357
- import path8 from "node:path";
1731
+ import path10 from "node:path";
1358
1732
  async function sweepRun(args) {
1359
1733
  const pipeline = args.pipeline === true || args.pipeline === "true";
1360
1734
  try {
@@ -1366,7 +1740,7 @@ async function sweepRun(args) {
1366
1740
  const releasedLocalOrphans = [];
1367
1741
  for (const name of Object.keys(run.workers || {})) {
1368
1742
  const worker = readJson(
1369
- path8.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1743
+ path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1370
1744
  void 0
1371
1745
  );
1372
1746
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -1409,11 +1783,11 @@ async function sweepRun(args) {
1409
1783
  }
1410
1784
 
1411
1785
  // src/worktree.ts
1412
- import { existsSync as existsSync9, mkdirSync as mkdirSync4 } from "node:fs";
1413
- import path10 from "node:path";
1786
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4 } from "node:fs";
1787
+ import path12 from "node:path";
1414
1788
 
1415
1789
  // src/validate.ts
1416
- import path9 from "node:path";
1790
+ import path11 from "node:path";
1417
1791
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
1418
1792
  function validateRunId(runId) {
1419
1793
  const trimmed = runId.trim();
@@ -1421,7 +1795,7 @@ function validateRunId(runId) {
1421
1795
  return trimmed;
1422
1796
  }
1423
1797
  function validateRepo(repo) {
1424
- const resolved = path9.resolve(repo);
1798
+ const resolved = path11.resolve(repo);
1425
1799
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
1426
1800
  return resolved;
1427
1801
  }
@@ -1432,7 +1806,7 @@ function createRun(args) {
1432
1806
  ensureGitRepo(repo);
1433
1807
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
1434
1808
  const dir = runDirectory(id);
1435
- if (existsSync9(dir)) failExists(`run already exists: ${id}`);
1809
+ if (existsSync10(dir)) failExists(`run already exists: ${id}`);
1436
1810
  mkdirSync4(dir, { recursive: true });
1437
1811
  const base = String(args.base || "origin/main");
1438
1812
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -1446,12 +1820,12 @@ function createRun(args) {
1446
1820
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1447
1821
  workers: {}
1448
1822
  };
1449
- writeJson(path10.join(dir, "run.json"), run);
1823
+ writeJson(path12.join(dir, "run.json"), run);
1450
1824
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
1451
1825
  }
1452
1826
  function listRuns() {
1453
1827
  const { runsDir } = getPaths();
1454
- const rows = listRunIds(runsDir).map((id) => readJson(path10.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
1828
+ const rows = listRunIds(runsDir).map((id) => readJson(path12.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
1455
1829
  id: run.id,
1456
1830
  name: run.name,
1457
1831
  status: run.status,
@@ -1465,211 +1839,11 @@ function failExists(message) {
1465
1839
  process.exit(1);
1466
1840
  }
1467
1841
 
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
1842
  // src/pipeline-tick.ts
1669
- import path14 from "node:path";
1843
+ import path15 from "node:path";
1670
1844
 
1671
1845
  // src/finalize.ts
1672
- import path12 from "node:path";
1846
+ import path13 from "node:path";
1673
1847
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
1674
1848
  function terminalStatusFor(run) {
1675
1849
  const names = Object.keys(run.workers || {});
@@ -1679,7 +1853,7 @@ function terminalStatusFor(run) {
1679
1853
  let anyCompletionBlocked = false;
1680
1854
  for (const name of names) {
1681
1855
  const worker = readJson(
1682
- path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1856
+ path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1683
1857
  void 0
1684
1858
  );
1685
1859
  if (!worker) continue;
@@ -1712,7 +1886,7 @@ function finalizeStaleRuns() {
1712
1886
  }
1713
1887
 
1714
1888
  // src/plan-progress-daemon-sync.ts
1715
- import path13 from "node:path";
1889
+ import path14 from "node:path";
1716
1890
 
1717
1891
  // src/plan-progress-sync.ts
1718
1892
  async function syncPlanProgress(args) {
@@ -1736,7 +1910,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
1736
1910
  const outcomes = [];
1737
1911
  for (const name of Object.keys(run.workers || {})) {
1738
1912
  const worker = readJson(
1739
- path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1913
+ path14.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1740
1914
  void 0
1741
1915
  );
1742
1916
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -1790,7 +1964,7 @@ async function completeFinishedWorkers(runId, args) {
1790
1964
  const outcomes = [];
1791
1965
  for (const name of Object.keys(run.workers || {})) {
1792
1966
  const worker = readJson(
1793
- path14.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1967
+ path15.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1794
1968
  void 0
1795
1969
  );
1796
1970
  if (!worker?.taskId) continue;
@@ -2033,6 +2207,7 @@ function usage(code = 0) {
2033
2207
  " kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
2034
2208
  " kynver worker stop --run RUN_ID --name worker",
2035
2209
  " kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
2210
+ " 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
2211
  " 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
2212
  " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]"
2038
2213
  ].join("\n")
@@ -2071,9 +2246,10 @@ async function main(argv = process.argv.slice(2)) {
2071
2246
  if (scope === "worker" && action === "tail") return tailWorker(args);
2072
2247
  if (scope === "worker" && action === "stop") return stopWorker(args);
2073
2248
  if (scope === "worker" && action === "complete") return void await completeWorker(args);
2249
+ if (scope === "worker" && action === "auto-complete") return void await autoCompleteWorkerCli(args);
2074
2250
  unknownCommand(scope, action);
2075
2251
  }
2076
- var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath(import.meta.url));
2252
+ var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath2(import.meta.url));
2077
2253
  if (isCliEntry) {
2078
2254
  void main().catch((error) => {
2079
2255
  console.error(error);