lobstah 0.5.12 → 0.5.13
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/README.md +2 -1
- package/dist/main.js +2678 -1991
- package/dist/runner.js +238 -139
- package/docs/configuration.md +3 -3
- package/docs/design.md +1 -1
- package/docs/harness/claude-code.md +3 -3
- package/docs/harness/codex.md +6 -4
- package/docs/man.md +106 -17
- package/docs/vocabulary.md +11 -8
- package/package.json +1 -1
package/dist/runner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// packages/runner/src/index.ts
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as fs18 from "node:fs";
|
|
3
|
+
import * as path16 from "node:path";
|
|
4
4
|
|
|
5
5
|
// packages/core/dist/types.js
|
|
6
6
|
var VERBS = ["working", "needs-decision", "blocked", "paused", "done", "failed"];
|
|
@@ -401,32 +401,65 @@ function mergeEvidence(id, lane, patch) {
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
// packages/core/dist/inbox.js
|
|
404
|
+
import * as fs7 from "node:fs";
|
|
405
|
+
import * as path5 from "node:path";
|
|
406
|
+
|
|
407
|
+
// packages/core/dist/soak.js
|
|
404
408
|
import * as fs6 from "node:fs";
|
|
405
409
|
import * as path4 from "node:path";
|
|
410
|
+
function regPath(trapId) {
|
|
411
|
+
return path4.join(soakingDir(), `${trapId}.json`);
|
|
412
|
+
}
|
|
413
|
+
function readTrap(trapId) {
|
|
414
|
+
try {
|
|
415
|
+
return JSON.parse(fs6.readFileSync(regPath(trapId), "utf8"));
|
|
416
|
+
} catch {
|
|
417
|
+
return void 0;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function listTraps() {
|
|
421
|
+
try {
|
|
422
|
+
return fs6.readdirSync(soakingDir()).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs6.readFileSync(path4.join(soakingDir(), f), "utf8"))).filter((r) => typeof r.trapId === "string");
|
|
423
|
+
} catch {
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function claimPath(id, lane) {
|
|
428
|
+
return path4.join(laneDirs(lane).active, id, "claim.json");
|
|
429
|
+
}
|
|
430
|
+
function readSessionClaim(id, lane) {
|
|
431
|
+
try {
|
|
432
|
+
return JSON.parse(fs6.readFileSync(claimPath(id, lane), "utf8"));
|
|
433
|
+
} catch {
|
|
434
|
+
return void 0;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// packages/core/dist/inbox.js
|
|
406
439
|
var metaName = (file) => file.replace(/\.msg$/, ".meta.json");
|
|
407
440
|
function inboxDir(id, lane) {
|
|
408
|
-
return
|
|
441
|
+
return path5.join(laneDirs(lane).inbox, id);
|
|
409
442
|
}
|
|
410
443
|
function unhandled(id, lane) {
|
|
411
444
|
const dir = inboxDir(id, lane);
|
|
412
|
-
if (!
|
|
445
|
+
if (!fs7.existsSync(dir))
|
|
413
446
|
return [];
|
|
414
|
-
return
|
|
447
|
+
return fs7.readdirSync(dir).filter((f) => f.endsWith(".msg")).sort().map((f) => ({ file: f, text: fs7.readFileSync(path5.join(dir, f), "utf8") }));
|
|
415
448
|
}
|
|
416
449
|
function acknowledge(id, lane, file) {
|
|
417
450
|
const dir = inboxDir(id, lane);
|
|
418
|
-
|
|
419
|
-
|
|
451
|
+
fs7.mkdirSync(path5.join(dir, "handled"), { recursive: true });
|
|
452
|
+
fs7.renameSync(path5.join(dir, file), path5.join(dir, "handled", file));
|
|
420
453
|
try {
|
|
421
|
-
|
|
454
|
+
fs7.renameSync(path5.join(dir, metaName(file)), path5.join(dir, "handled", metaName(file)));
|
|
422
455
|
} catch {
|
|
423
456
|
}
|
|
424
457
|
}
|
|
425
458
|
|
|
426
459
|
// packages/core/dist/config.js
|
|
427
|
-
import * as
|
|
460
|
+
import * as fs8 from "node:fs";
|
|
428
461
|
import * as os2 from "node:os";
|
|
429
|
-
import * as
|
|
462
|
+
import * as path6 from "node:path";
|
|
430
463
|
|
|
431
464
|
// node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/date.js
|
|
432
465
|
var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
|
|
@@ -1120,14 +1153,14 @@ var DEFAULT_LIMITS = {
|
|
|
1120
1153
|
releaseOnMerge: false
|
|
1121
1154
|
};
|
|
1122
1155
|
function configPath() {
|
|
1123
|
-
return
|
|
1156
|
+
return path6.join(lobstahHome(), "config.toml");
|
|
1124
1157
|
}
|
|
1125
1158
|
function expandHome(p) {
|
|
1126
|
-
return p.startsWith("~/") ?
|
|
1159
|
+
return p.startsWith("~/") ? path6.join(os2.homedir(), p.slice(2)) : p;
|
|
1127
1160
|
}
|
|
1128
1161
|
function loadConfig() {
|
|
1129
1162
|
const file = configPath();
|
|
1130
|
-
const raw =
|
|
1163
|
+
const raw = fs8.existsSync(file) ? parse(fs8.readFileSync(file, "utf8")) : {};
|
|
1131
1164
|
const reposRaw = raw.repos ?? {};
|
|
1132
1165
|
const repos = {};
|
|
1133
1166
|
for (const [key, r] of Object.entries(reposRaw)) {
|
|
@@ -1185,21 +1218,21 @@ function resolveDispatch(d, cfg) {
|
|
|
1185
1218
|
}
|
|
1186
1219
|
|
|
1187
1220
|
// packages/core/dist/harness.js
|
|
1188
|
-
import * as
|
|
1189
|
-
import * as
|
|
1221
|
+
import * as fs9 from "node:fs";
|
|
1222
|
+
import * as path7 from "node:path";
|
|
1190
1223
|
import { createRequire } from "node:module";
|
|
1191
1224
|
function resolveOnPath(bin, pathVar = process.env.PATH) {
|
|
1192
1225
|
if (!pathVar)
|
|
1193
1226
|
return void 0;
|
|
1194
1227
|
const names = process.platform === "win32" ? [bin, `${bin}.exe`, `${bin}.cmd`, `${bin}.bat`] : [bin];
|
|
1195
|
-
for (const dir of pathVar.split(
|
|
1228
|
+
for (const dir of pathVar.split(path7.delimiter)) {
|
|
1196
1229
|
if (!dir)
|
|
1197
1230
|
continue;
|
|
1198
1231
|
for (const name of names) {
|
|
1199
|
-
const candidate =
|
|
1232
|
+
const candidate = path7.join(dir, name);
|
|
1200
1233
|
try {
|
|
1201
|
-
|
|
1202
|
-
if (
|
|
1234
|
+
fs9.accessSync(candidate, fs9.constants.X_OK);
|
|
1235
|
+
if (fs9.statSync(candidate).isFile())
|
|
1203
1236
|
return candidate;
|
|
1204
1237
|
} catch {
|
|
1205
1238
|
}
|
|
@@ -1220,39 +1253,28 @@ function codexInvocation(argv) {
|
|
|
1220
1253
|
} catch {
|
|
1221
1254
|
}
|
|
1222
1255
|
for (const dir of require_.resolve.paths("@openai/codex") ?? []) {
|
|
1223
|
-
const cli =
|
|
1224
|
-
if (
|
|
1256
|
+
const cli = path7.join(dir, "@openai", "codex", "bin", "codex.js");
|
|
1257
|
+
if (fs9.existsSync(cli))
|
|
1225
1258
|
return { file: process.execPath, argv: [cli, ...argv] };
|
|
1226
1259
|
}
|
|
1227
1260
|
return void 0;
|
|
1228
1261
|
}
|
|
1229
1262
|
|
|
1230
|
-
// packages/core/dist/
|
|
1231
|
-
import * as
|
|
1232
|
-
import * as
|
|
1233
|
-
function
|
|
1234
|
-
return
|
|
1263
|
+
// packages/core/dist/watch.js
|
|
1264
|
+
import * as fs10 from "node:fs";
|
|
1265
|
+
import * as path8 from "node:path";
|
|
1266
|
+
function watchesDir() {
|
|
1267
|
+
return path8.join(lobstahHome(), "watches");
|
|
1235
1268
|
}
|
|
1236
|
-
function
|
|
1237
|
-
|
|
1238
|
-
return JSON.parse(fs9.readFileSync(regPath(trapId), "utf8"));
|
|
1239
|
-
} catch {
|
|
1240
|
-
return void 0;
|
|
1241
|
-
}
|
|
1269
|
+
function slug(key) {
|
|
1270
|
+
return key.replace(/[^A-Za-z0-9._-]+/g, "-");
|
|
1242
1271
|
}
|
|
1243
|
-
function
|
|
1244
|
-
|
|
1245
|
-
return fs9.readdirSync(soakingDir()).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs9.readFileSync(path7.join(soakingDir(), f), "utf8"))).filter((r) => typeof r.trapId === "string");
|
|
1246
|
-
} catch {
|
|
1247
|
-
return [];
|
|
1248
|
-
}
|
|
1272
|
+
function watchPath(key) {
|
|
1273
|
+
return path8.join(watchesDir(), `${slug(key)}.json`);
|
|
1249
1274
|
}
|
|
1250
|
-
function
|
|
1251
|
-
return path7.join(laneDirs(lane).active, id, "claim.json");
|
|
1252
|
-
}
|
|
1253
|
-
function readSessionClaim(id, lane) {
|
|
1275
|
+
function readWatch(key) {
|
|
1254
1276
|
try {
|
|
1255
|
-
return JSON.parse(
|
|
1277
|
+
return JSON.parse(fs10.readFileSync(watchPath(key), "utf8"));
|
|
1256
1278
|
} catch {
|
|
1257
1279
|
return void 0;
|
|
1258
1280
|
}
|
|
@@ -1268,13 +1290,70 @@ var COMPILED_BINARY = (() => {
|
|
|
1268
1290
|
})();
|
|
1269
1291
|
|
|
1270
1292
|
// packages/core/dist/pr.js
|
|
1293
|
+
function parsePrRef(s) {
|
|
1294
|
+
const m = /^(?:pr:)?([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)#(\d+)$/.exec(s.trim()) ?? /^https?:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/(\d+)(?:[/?#].*)?$/.exec(s.trim());
|
|
1295
|
+
if (!m)
|
|
1296
|
+
return void 0;
|
|
1297
|
+
const [, owner, repo, n] = m;
|
|
1298
|
+
const number = Number(n);
|
|
1299
|
+
return { owner, repo, number, key: `pr:${owner}/${repo}#${number}`, url: `https://github.com/${owner}/${repo}/pull/${number}` };
|
|
1300
|
+
}
|
|
1271
1301
|
var PR_VIEW_FIELDS = "state,isDraft,headRefOid,baseRefName,headRefName,mergeStateStatus,reviewDecision,statusCheckRollup,mergedAt,closedAt,updatedAt,reviews";
|
|
1272
1302
|
var PR_VIEW_FIELDS_NO_CHECKS = PR_VIEW_FIELDS.split(",").filter((f) => f !== "statusCheckRollup").join(",");
|
|
1273
1303
|
|
|
1304
|
+
// packages/core/dist/prs.js
|
|
1305
|
+
import * as fs11 from "node:fs";
|
|
1306
|
+
import * as path9 from "node:path";
|
|
1307
|
+
function prsDir() {
|
|
1308
|
+
return path9.join(lobstahHome(), "prs");
|
|
1309
|
+
}
|
|
1310
|
+
function readPrs() {
|
|
1311
|
+
let files;
|
|
1312
|
+
try {
|
|
1313
|
+
files = fs11.readdirSync(prsDir()).filter((f) => f.endsWith(".json"));
|
|
1314
|
+
} catch {
|
|
1315
|
+
return [];
|
|
1316
|
+
}
|
|
1317
|
+
return files.flatMap((f) => {
|
|
1318
|
+
try {
|
|
1319
|
+
const r = JSON.parse(fs11.readFileSync(path9.join(prsDir(), f), "utf8"));
|
|
1320
|
+
return typeof r.key === "string" && typeof r.url === "string" ? [r] : [];
|
|
1321
|
+
} catch {
|
|
1322
|
+
return [];
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// packages/core/dist/chain-pr.js
|
|
1328
|
+
function chainPr(id, lane) {
|
|
1329
|
+
const ancestry = [];
|
|
1330
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1331
|
+
let at = id;
|
|
1332
|
+
while (at && !seen.has(at)) {
|
|
1333
|
+
ancestry.push(at);
|
|
1334
|
+
seen.add(at);
|
|
1335
|
+
at = storedDescriptor(at, lane)?.followUp;
|
|
1336
|
+
}
|
|
1337
|
+
const records = readPrs();
|
|
1338
|
+
for (const member of ancestry.reverse()) {
|
|
1339
|
+
const evidence = readEvidence(member, lane);
|
|
1340
|
+
const url = evidence.prUrl ?? evidence.pr?.url;
|
|
1341
|
+
const ref = url && parsePrRef(url);
|
|
1342
|
+
if (ref) {
|
|
1343
|
+
const record2 = records.find((pr) => pr.key === ref.key);
|
|
1344
|
+
return { url: ref.url, headRefName: record2?.headRefName ?? evidence.pr?.headRefName ?? evidence.branch };
|
|
1345
|
+
}
|
|
1346
|
+
const record = records.find((pr) => pr.dispatches.includes(member));
|
|
1347
|
+
if (record)
|
|
1348
|
+
return { url: record.url, headRefName: record.headRefName };
|
|
1349
|
+
}
|
|
1350
|
+
return void 0;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1274
1353
|
// packages/core/dist/resume.js
|
|
1275
|
-
import * as
|
|
1354
|
+
import * as fs12 from "node:fs";
|
|
1276
1355
|
import * as os3 from "node:os";
|
|
1277
|
-
import * as
|
|
1356
|
+
import * as path10 from "node:path";
|
|
1278
1357
|
import { spawnSync } from "node:child_process";
|
|
1279
1358
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-([0-9a-f])[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1280
1359
|
function harnessFromSessionId(sessionId) {
|
|
@@ -1285,7 +1364,7 @@ function claimOf(id, lane) {
|
|
|
1285
1364
|
const dirs = laneDirs(lane);
|
|
1286
1365
|
for (const dir of [dirs.active, dirs.done]) {
|
|
1287
1366
|
try {
|
|
1288
|
-
return JSON.parse(
|
|
1367
|
+
return JSON.parse(fs12.readFileSync(path10.join(dir, id, "claim.json"), "utf8"));
|
|
1289
1368
|
} catch {
|
|
1290
1369
|
}
|
|
1291
1370
|
}
|
|
@@ -1335,7 +1414,7 @@ function isUnresumable(error) {
|
|
|
1335
1414
|
return error !== void 0 && UNRESUMABLE.test(error);
|
|
1336
1415
|
}
|
|
1337
1416
|
function worktreeProgress(worktree, trunk) {
|
|
1338
|
-
if (!worktree || !
|
|
1417
|
+
if (!worktree || !fs12.existsSync(worktree))
|
|
1339
1418
|
return "No worktree progress recorded.";
|
|
1340
1419
|
const git2 = (...a) => spawnSync("git", a, { cwd: worktree, encoding: "utf8" }).stdout?.trim() ?? "";
|
|
1341
1420
|
const commits = trunk ? git2("log", "--oneline", `origin/${trunk}..HEAD`) : git2("log", "--oneline", "-15");
|
|
@@ -1368,31 +1447,31 @@ function handoffNote(fromHarness, progress, why) {
|
|
|
1368
1447
|
${progress}`;
|
|
1369
1448
|
}
|
|
1370
1449
|
function codexHome(env = process.env) {
|
|
1371
|
-
return env.CODEX_HOME ??
|
|
1450
|
+
return env.CODEX_HOME ?? path10.join(os3.homedir(), ".codex");
|
|
1372
1451
|
}
|
|
1373
1452
|
function codexRolloutFile(threadId, home = codexHome()) {
|
|
1374
1453
|
const suffix = `-${threadId}.jsonl`;
|
|
1375
1454
|
const walk = (dir, depth) => {
|
|
1376
1455
|
let entries;
|
|
1377
1456
|
try {
|
|
1378
|
-
entries =
|
|
1457
|
+
entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
1379
1458
|
} catch {
|
|
1380
1459
|
return void 0;
|
|
1381
1460
|
}
|
|
1382
1461
|
for (const e of entries) {
|
|
1383
1462
|
if (e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(suffix))
|
|
1384
|
-
return
|
|
1463
|
+
return path10.join(dir, e.name);
|
|
1385
1464
|
}
|
|
1386
1465
|
if (depth === 0)
|
|
1387
1466
|
return void 0;
|
|
1388
1467
|
for (const e of entries.filter((x) => x.isDirectory()).sort((a, b) => b.name.localeCompare(a.name))) {
|
|
1389
|
-
const hit = walk(
|
|
1468
|
+
const hit = walk(path10.join(dir, e.name), depth - 1);
|
|
1390
1469
|
if (hit)
|
|
1391
1470
|
return hit;
|
|
1392
1471
|
}
|
|
1393
1472
|
return void 0;
|
|
1394
1473
|
};
|
|
1395
|
-
return walk(
|
|
1474
|
+
return walk(path10.join(home, "sessions"), 3) ?? walk(path10.join(home, "archived_sessions"), 0);
|
|
1396
1475
|
}
|
|
1397
1476
|
function codexDesktopThread(threadId, home = codexHome()) {
|
|
1398
1477
|
const file = codexRolloutFile(threadId, home);
|
|
@@ -1400,12 +1479,12 @@ function codexDesktopThread(threadId, home = codexHome()) {
|
|
|
1400
1479
|
return void 0;
|
|
1401
1480
|
let head = "";
|
|
1402
1481
|
try {
|
|
1403
|
-
const fd =
|
|
1482
|
+
const fd = fs12.openSync(file, "r");
|
|
1404
1483
|
try {
|
|
1405
1484
|
const buf = Buffer.alloc(4096);
|
|
1406
|
-
head = buf.subarray(0,
|
|
1485
|
+
head = buf.subarray(0, fs12.readSync(fd, buf, 0, buf.length, 0)).toString("utf8");
|
|
1407
1486
|
} finally {
|
|
1408
|
-
|
|
1487
|
+
fs12.closeSync(fd);
|
|
1409
1488
|
}
|
|
1410
1489
|
} catch {
|
|
1411
1490
|
return void 0;
|
|
@@ -1436,23 +1515,23 @@ function droppedModelNote(harness, dropped) {
|
|
|
1436
1515
|
}
|
|
1437
1516
|
|
|
1438
1517
|
// packages/core/dist/worktrees.js
|
|
1439
|
-
import * as
|
|
1440
|
-
import * as
|
|
1518
|
+
import * as fs13 from "node:fs";
|
|
1519
|
+
import * as path11 from "node:path";
|
|
1441
1520
|
var LANES = ["work", "chore"];
|
|
1442
1521
|
function worktreePath(id) {
|
|
1443
|
-
return
|
|
1522
|
+
return path11.join(lobstahHome(), "worktrees", id);
|
|
1444
1523
|
}
|
|
1445
1524
|
function readRecord(file) {
|
|
1446
1525
|
try {
|
|
1447
|
-
const r = JSON.parse(
|
|
1526
|
+
const r = JSON.parse(fs13.readFileSync(file, "utf8"));
|
|
1448
1527
|
return typeof r.path === "string" ? r : void 0;
|
|
1449
1528
|
} catch {
|
|
1450
1529
|
return void 0;
|
|
1451
1530
|
}
|
|
1452
1531
|
}
|
|
1453
1532
|
function ownerOfPath(p) {
|
|
1454
|
-
const rel =
|
|
1455
|
-
if (!rel || rel.startsWith("..") ||
|
|
1533
|
+
const rel = path11.relative(path11.join(lobstahHome(), "worktrees"), p);
|
|
1534
|
+
if (!rel || rel.startsWith("..") || path11.isAbsolute(rel))
|
|
1456
1535
|
return void 0;
|
|
1457
1536
|
return rel.split(/[\\/]/)[0];
|
|
1458
1537
|
}
|
|
@@ -1465,7 +1544,7 @@ function dispatchWorktree(id, lane) {
|
|
|
1465
1544
|
}
|
|
1466
1545
|
const dirs = laneDirs(l);
|
|
1467
1546
|
for (const dir of [dirs.active, dirs.done]) {
|
|
1468
|
-
const rec = readRecord(
|
|
1547
|
+
const rec = readRecord(path11.join(dir, id, "worktree.json"));
|
|
1469
1548
|
if (rec) {
|
|
1470
1549
|
const owner = rec.of ?? ownerOfPath(rec.path) ?? id;
|
|
1471
1550
|
return { path: rec.path, owner, reused: owner !== id };
|
|
@@ -1477,7 +1556,7 @@ function dispatchWorktree(id, lane) {
|
|
|
1477
1556
|
function laneOf2(id) {
|
|
1478
1557
|
for (const l of LANES) {
|
|
1479
1558
|
const d = laneDirs(l);
|
|
1480
|
-
if (
|
|
1559
|
+
if (fs13.existsSync(path11.join(d.queue, `${id}.json`)) || fs13.existsSync(path11.join(d.active, id)) || fs13.existsSync(path11.join(d.done, id))) {
|
|
1481
1560
|
return l;
|
|
1482
1561
|
}
|
|
1483
1562
|
}
|
|
@@ -1497,39 +1576,39 @@ function followUpAncestors(id, lane) {
|
|
|
1497
1576
|
function chainWorktree(followUp) {
|
|
1498
1577
|
for (const id of [followUp, ...followUpAncestors(followUp)]) {
|
|
1499
1578
|
const wt = dispatchWorktree(id, laneOf2(id));
|
|
1500
|
-
if (
|
|
1579
|
+
if (fs13.existsSync(wt.path))
|
|
1501
1580
|
return { ...wt, from: id };
|
|
1502
1581
|
}
|
|
1503
1582
|
return void 0;
|
|
1504
1583
|
}
|
|
1505
1584
|
function worktreeGitDir(wt) {
|
|
1506
|
-
const dotGit =
|
|
1585
|
+
const dotGit = path11.join(wt, ".git");
|
|
1507
1586
|
try {
|
|
1508
|
-
const st =
|
|
1587
|
+
const st = fs13.statSync(dotGit);
|
|
1509
1588
|
if (st.isDirectory())
|
|
1510
1589
|
return dotGit;
|
|
1511
|
-
const m = /^gitdir:\s*(.+?)\s*$/m.exec(
|
|
1512
|
-
return m ?
|
|
1590
|
+
const m = /^gitdir:\s*(.+?)\s*$/m.exec(fs13.readFileSync(dotGit, "utf8"));
|
|
1591
|
+
return m ? path11.resolve(wt, m[1]) : void 0;
|
|
1513
1592
|
} catch {
|
|
1514
1593
|
return void 0;
|
|
1515
1594
|
}
|
|
1516
1595
|
}
|
|
1517
1596
|
function worktreeLockFile(wt) {
|
|
1518
1597
|
const gitDir = worktreeGitDir(wt);
|
|
1519
|
-
return gitDir ?
|
|
1598
|
+
return gitDir ? path11.join(gitDir, "lobstah.lock") : void 0;
|
|
1520
1599
|
}
|
|
1521
1600
|
function readWorktreeLock(wt) {
|
|
1522
1601
|
const file = worktreeLockFile(wt);
|
|
1523
1602
|
if (!file)
|
|
1524
1603
|
return void 0;
|
|
1525
1604
|
try {
|
|
1526
|
-
return JSON.parse(
|
|
1605
|
+
return JSON.parse(fs13.readFileSync(file, "utf8"));
|
|
1527
1606
|
} catch {
|
|
1528
1607
|
return void 0;
|
|
1529
1608
|
}
|
|
1530
1609
|
}
|
|
1531
1610
|
function lockIsLive(lock) {
|
|
1532
|
-
return
|
|
1611
|
+
return fs13.existsSync(path11.join(laneDirs(lock.lane).active, lock.id));
|
|
1533
1612
|
}
|
|
1534
1613
|
function worktreeHolder(wt) {
|
|
1535
1614
|
const lock = readWorktreeLock(wt);
|
|
@@ -1542,7 +1621,7 @@ function acquireWorktreeLock(wt, id, lane) {
|
|
|
1542
1621
|
return { id: "(no git dir)", lane, pid: 0, at: mine.at };
|
|
1543
1622
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1544
1623
|
try {
|
|
1545
|
-
|
|
1624
|
+
fs13.writeFileSync(file, JSON.stringify(mine, null, 2), { flag: "wx" });
|
|
1546
1625
|
return void 0;
|
|
1547
1626
|
} catch (err) {
|
|
1548
1627
|
if (err.code !== "EEXIST")
|
|
@@ -1550,7 +1629,7 @@ function acquireWorktreeLock(wt, id, lane) {
|
|
|
1550
1629
|
}
|
|
1551
1630
|
let raw;
|
|
1552
1631
|
try {
|
|
1553
|
-
raw =
|
|
1632
|
+
raw = fs13.readFileSync(file, "utf8");
|
|
1554
1633
|
} catch {
|
|
1555
1634
|
continue;
|
|
1556
1635
|
}
|
|
@@ -1561,14 +1640,14 @@ function acquireWorktreeLock(wt, id, lane) {
|
|
|
1561
1640
|
held2 = void 0;
|
|
1562
1641
|
}
|
|
1563
1642
|
if (held2?.id === id) {
|
|
1564
|
-
|
|
1643
|
+
fs13.writeFileSync(file, JSON.stringify(mine, null, 2));
|
|
1565
1644
|
return void 0;
|
|
1566
1645
|
}
|
|
1567
1646
|
if (held2 && lockIsLive(held2))
|
|
1568
1647
|
return held2;
|
|
1569
1648
|
try {
|
|
1570
|
-
if (
|
|
1571
|
-
|
|
1649
|
+
if (fs13.readFileSync(file, "utf8") === raw)
|
|
1650
|
+
fs13.rmSync(file, { force: true });
|
|
1572
1651
|
} catch {
|
|
1573
1652
|
}
|
|
1574
1653
|
}
|
|
@@ -1580,7 +1659,7 @@ function releaseWorktreeLock(wt, id) {
|
|
|
1580
1659
|
if (!file)
|
|
1581
1660
|
return;
|
|
1582
1661
|
if (readWorktreeLock(wt)?.id === id)
|
|
1583
|
-
|
|
1662
|
+
fs13.rmSync(file, { force: true });
|
|
1584
1663
|
}
|
|
1585
1664
|
|
|
1586
1665
|
// packages/core/dist/slots.js
|
|
@@ -1589,8 +1668,8 @@ function isTrapCatch(id, lane) {
|
|
|
1589
1668
|
}
|
|
1590
1669
|
|
|
1591
1670
|
// packages/runner/src/run.ts
|
|
1592
|
-
import * as
|
|
1593
|
-
import * as
|
|
1671
|
+
import * as fs17 from "node:fs";
|
|
1672
|
+
import * as path15 from "node:path";
|
|
1594
1673
|
import { execFileSync } from "node:child_process";
|
|
1595
1674
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1596
1675
|
|
|
@@ -1884,25 +1963,25 @@ function createClaudeAdapter() {
|
|
|
1884
1963
|
}
|
|
1885
1964
|
|
|
1886
1965
|
// packages/adapters/dist/codex.js
|
|
1887
|
-
import * as
|
|
1966
|
+
import * as fs14 from "node:fs";
|
|
1888
1967
|
import * as os4 from "node:os";
|
|
1889
|
-
import * as
|
|
1968
|
+
import * as path12 from "node:path";
|
|
1890
1969
|
import { spawn as spawn2 } from "node:child_process";
|
|
1891
1970
|
function isolatedCodexHome() {
|
|
1892
|
-
const dir =
|
|
1893
|
-
|
|
1894
|
-
const auth =
|
|
1895
|
-
const realAuth =
|
|
1896
|
-
if (!
|
|
1971
|
+
const dir = path12.join(lobstahHome(), "codex-home");
|
|
1972
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
1973
|
+
const auth = path12.join(dir, "auth.json");
|
|
1974
|
+
const realAuth = path12.join(os4.homedir(), ".codex", "auth.json");
|
|
1975
|
+
if (!fs14.existsSync(auth) && fs14.existsSync(realAuth)) {
|
|
1897
1976
|
try {
|
|
1898
|
-
|
|
1977
|
+
fs14.symlinkSync(realAuth, auth);
|
|
1899
1978
|
} catch {
|
|
1900
|
-
|
|
1979
|
+
fs14.copyFileSync(realAuth, auth);
|
|
1901
1980
|
}
|
|
1902
1981
|
}
|
|
1903
|
-
const cfg =
|
|
1904
|
-
if (!
|
|
1905
|
-
|
|
1982
|
+
const cfg = path12.join(dir, "config.toml");
|
|
1983
|
+
if (!fs14.existsSync(cfg)) {
|
|
1984
|
+
fs14.writeFileSync(cfg, "# lobstah-managed: auth inherited from ~/.codex, interactive config not\n");
|
|
1906
1985
|
}
|
|
1907
1986
|
return dir;
|
|
1908
1987
|
}
|
|
@@ -2103,8 +2182,8 @@ function loadAdapter(name) {
|
|
|
2103
2182
|
}
|
|
2104
2183
|
|
|
2105
2184
|
// packages/worktree/dist/index.js
|
|
2106
|
-
import * as
|
|
2107
|
-
import * as
|
|
2185
|
+
import * as fs15 from "node:fs";
|
|
2186
|
+
import * as path13 from "node:path";
|
|
2108
2187
|
import { createHash } from "node:crypto";
|
|
2109
2188
|
import { exec, execFile } from "node:child_process";
|
|
2110
2189
|
import { promisify } from "node:util";
|
|
@@ -2161,10 +2240,10 @@ ${gitStderr(err)}`, { cause: err });
|
|
|
2161
2240
|
}
|
|
2162
2241
|
async function allocate(repo, id) {
|
|
2163
2242
|
const dir = worktreePath(id);
|
|
2164
|
-
if (
|
|
2243
|
+
if (fs15.existsSync(dir)) {
|
|
2165
2244
|
throw new Error(`worktree for ${id} already exists at ${dir} \u2014 never allocate a second`);
|
|
2166
2245
|
}
|
|
2167
|
-
if (!
|
|
2246
|
+
if (!fs15.existsSync(repo.path)) {
|
|
2168
2247
|
if (!repo.origin)
|
|
2169
2248
|
throw new Error(`repo path ${repo.path} missing and no origin configured`);
|
|
2170
2249
|
await run("git", ["clone", repo.origin, repo.path], { env: process.env });
|
|
@@ -2200,38 +2279,40 @@ function setupHash(repo, dir) {
|
|
|
2200
2279
|
const h = createHash("sha256");
|
|
2201
2280
|
h.update(JSON.stringify(repo.setup ?? []));
|
|
2202
2281
|
for (const name of LOCKFILES) {
|
|
2203
|
-
const file =
|
|
2204
|
-
if (!
|
|
2282
|
+
const file = path13.join(dir, name);
|
|
2283
|
+
if (!fs15.existsSync(file))
|
|
2205
2284
|
continue;
|
|
2206
2285
|
h.update(`\0${name}\0`);
|
|
2207
|
-
h.update(
|
|
2286
|
+
h.update(fs15.readFileSync(file));
|
|
2208
2287
|
}
|
|
2209
2288
|
return h.digest("hex");
|
|
2210
2289
|
}
|
|
2211
2290
|
function setupRecordFile(dir) {
|
|
2212
2291
|
const gitDir = worktreeGitDir(dir);
|
|
2213
|
-
return gitDir ?
|
|
2292
|
+
return gitDir ? path13.join(gitDir, "lobstah-setup.json") : void 0;
|
|
2214
2293
|
}
|
|
2215
2294
|
function recordSetup(repo, dir) {
|
|
2216
2295
|
const file = setupRecordFile(dir);
|
|
2217
2296
|
if (file)
|
|
2218
|
-
|
|
2297
|
+
fs15.writeFileSync(file, JSON.stringify({ hash: setupHash(repo, dir), at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
2219
2298
|
}
|
|
2220
2299
|
function recordedSetupHash(dir) {
|
|
2221
2300
|
const file = setupRecordFile(dir);
|
|
2222
2301
|
try {
|
|
2223
|
-
return file ? JSON.parse(
|
|
2302
|
+
return file ? JSON.parse(fs15.readFileSync(file, "utf8")).hash : void 0;
|
|
2224
2303
|
} catch {
|
|
2225
2304
|
return void 0;
|
|
2226
2305
|
}
|
|
2227
2306
|
}
|
|
2228
2307
|
var short = (s) => s.slice(0, 8);
|
|
2229
2308
|
function realpath(p) {
|
|
2309
|
+
let r;
|
|
2230
2310
|
try {
|
|
2231
|
-
|
|
2311
|
+
r = fs15.realpathSync.native(p);
|
|
2232
2312
|
} catch {
|
|
2233
|
-
|
|
2313
|
+
r = path13.resolve(p);
|
|
2234
2314
|
}
|
|
2315
|
+
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
2235
2316
|
}
|
|
2236
2317
|
function norm(p) {
|
|
2237
2318
|
return p.replace(/^"(.*)"$/, "$1").replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
@@ -2271,8 +2352,8 @@ async function chooseWorktree(input) {
|
|
|
2271
2352
|
for (const t of listTraps()) {
|
|
2272
2353
|
if (typeof t.worktree !== "string")
|
|
2273
2354
|
continue;
|
|
2274
|
-
const rel =
|
|
2275
|
-
if (!rel.startsWith("..") && !
|
|
2355
|
+
const rel = path13.relative(here, realpath(t.worktree));
|
|
2356
|
+
if (!rel.startsWith("..") && !path13.isAbsolute(rel))
|
|
2276
2357
|
return { reuse: false, reason: "origin worktree is a trap\u2019s" };
|
|
2277
2358
|
}
|
|
2278
2359
|
for (const l of ["work", "chore"]) {
|
|
@@ -2325,9 +2406,11 @@ function buildPrompt(brief, opts) {
|
|
|
2325
2406
|
reporting,
|
|
2326
2407
|
`Use \`needs-decision\` when you are blocked on a question only a human can answer, then stop. New operator messages may arrive between your turns as user messages; treat them as instructions from the dispatcher.`,
|
|
2327
2408
|
`Before you wait on something outside lobstah (a human review, a PR review, a deploy), report \`lobstah report ${opts.id} paused "<note>" --waiting-on ${WAITING_ON.join("|")} --link <url>\`. Report \`working\` when you resume.`,
|
|
2328
|
-
`Commit your work with clear messages. The runner pushes committed HEAD early and opens or adopts one draft PR for the branch when available; do not create a duplicate PR. When finished, mark the draft ready for review if appropriate, then report done with its URL. Do not merge anything.`,
|
|
2409
|
+
opts.existingPr ? `Commit your work with clear messages. This chain already has PR ${opts.existingPr.url}. The runner does not push or open another PR. Push your changes to its existing head branch${opts.existingPr.headRefName ? ` ${opts.existingPr.headRefName}` : " (inspect the PR to find it)"}; do not create a duplicate PR. Report done with the same PR URL. Do not merge anything.` : `Commit your work with clear messages. The runner pushes committed HEAD early and opens or adopts one draft PR for the branch when available; do not create a duplicate PR. When finished, mark the draft ready for review if appropriate, then report done with its URL. Do not merge anything.`,
|
|
2329
2410
|
`--- BRIEF ---`,
|
|
2330
|
-
brief
|
|
2411
|
+
opts.existingPr ? `${brief}
|
|
2412
|
+
|
|
2413
|
+
Existing chain PR: ${opts.existingPr.url}. Head branch: ${opts.existingPr.headRefName ?? "inspect the PR"}. The runner will not push this follow-up.` : brief
|
|
2331
2414
|
];
|
|
2332
2415
|
if (opts.attachments?.length) parts.push(attachmentBlock(opts.attachments));
|
|
2333
2416
|
if (opts.nudge) parts.push(`--- SUPERVISOR NOTE ---`, opts.nudge);
|
|
@@ -2607,8 +2690,8 @@ function startWallClock(opts) {
|
|
|
2607
2690
|
import { execFile as execFile2 } from "node:child_process";
|
|
2608
2691
|
import { promisify as promisify2 } from "node:util";
|
|
2609
2692
|
import { fileURLToPath } from "node:url";
|
|
2610
|
-
import * as
|
|
2611
|
-
import * as
|
|
2693
|
+
import * as fs16 from "node:fs";
|
|
2694
|
+
import * as path14 from "node:path";
|
|
2612
2695
|
var exec2 = promisify2(execFile2);
|
|
2613
2696
|
function firstLine(err) {
|
|
2614
2697
|
const stderr = err && typeof err === "object" && "stderr" in err ? String(err.stderr).trim() : "";
|
|
@@ -2623,9 +2706,9 @@ function checkpointAllowed(file) {
|
|
|
2623
2706
|
return true;
|
|
2624
2707
|
}
|
|
2625
2708
|
function keepRemote(opts) {
|
|
2626
|
-
const { id, lane, cwd, trunk, policy } = opts;
|
|
2709
|
+
const { id, lane, cwd, trunk, policy, existingPrUrl } = opts;
|
|
2627
2710
|
const command = async (bin, args, timeout = 12e4) => {
|
|
2628
|
-
const file = bin === "gh" ? resolveOnPath(
|
|
2711
|
+
const file = bin === "gh" || bin === "lobstah" ? resolveOnPath(bin) ?? bin : bin;
|
|
2629
2712
|
return (await exec2(file, args, {
|
|
2630
2713
|
cwd,
|
|
2631
2714
|
encoding: "utf8",
|
|
@@ -2636,6 +2719,14 @@ function keepRemote(opts) {
|
|
|
2636
2719
|
};
|
|
2637
2720
|
const git2 = (...args) => command("git", args);
|
|
2638
2721
|
const note = (message) => mergeEvidence(id, lane, { note: message.slice(0, 300) });
|
|
2722
|
+
if (existingPrUrl) mergeEvidence(id, lane, { prUrl: existingPrUrl });
|
|
2723
|
+
const registerWatch = async (url) => {
|
|
2724
|
+
const root = path14.resolve(path14.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
2725
|
+
const localCli = path14.join(root, "apps", "cli", "dist", "main.js");
|
|
2726
|
+
const args = ["watch", "add", url, "--for", id];
|
|
2727
|
+
if (fs16.existsSync(localCli)) await command(process.execPath, [localCli, ...args], 15e3);
|
|
2728
|
+
else await command("lobstah", args, 15e3);
|
|
2729
|
+
};
|
|
2639
2730
|
let lastSeenHead;
|
|
2640
2731
|
let pushedHead;
|
|
2641
2732
|
let busy;
|
|
@@ -2645,6 +2736,10 @@ function keepRemote(opts) {
|
|
|
2645
2736
|
let pushProblem;
|
|
2646
2737
|
let draftProblem;
|
|
2647
2738
|
const trapClaimed = () => isTrapCatch(id, lane);
|
|
2739
|
+
const existingWatch = existingPrUrl && parsePrRef(existingPrUrl);
|
|
2740
|
+
const watching = existingWatch && !readWatch(existingWatch.key) ? (async () => {
|
|
2741
|
+
await registerWatch(existingWatch.url).catch((err) => note(`PR watch unavailable: ${firstLine(err)}`));
|
|
2742
|
+
})() : void 0;
|
|
2648
2743
|
const branchName = async () => {
|
|
2649
2744
|
const branch = await git2("symbolic-ref", "--quiet", "--short", "HEAD").catch(() => "");
|
|
2650
2745
|
if (!branch || branch === trunk || branch === `origin/${trunk}`) return void 0;
|
|
@@ -2683,12 +2778,11 @@ function keepRemote(opts) {
|
|
|
2683
2778
|
opened = true;
|
|
2684
2779
|
draftProblem = void 0;
|
|
2685
2780
|
mergeEvidence(id, lane, { prUrl: match[0] });
|
|
2686
|
-
|
|
2687
|
-
await command(fs14.existsSync(bin) ? bin : "lobstah", ["watch", "add", match[0], "--for", id], 15e3).catch(() => {
|
|
2781
|
+
await registerWatch(match[0]).catch(() => {
|
|
2688
2782
|
});
|
|
2689
2783
|
};
|
|
2690
2784
|
const check = async () => {
|
|
2691
|
-
if (!policy.pushEarly || trapClaimed()) return;
|
|
2785
|
+
if (!policy.pushEarly || trapClaimed() || existingPrUrl) return;
|
|
2692
2786
|
const branch = await branchName();
|
|
2693
2787
|
if (!branch) return;
|
|
2694
2788
|
const head = await git2("rev-parse", "HEAD");
|
|
@@ -2727,6 +2821,7 @@ function keepRemote(opts) {
|
|
|
2727
2821
|
await busy;
|
|
2728
2822
|
await check().catch((err) => note(`final push unavailable: ${firstLine(err)}`));
|
|
2729
2823
|
await opening;
|
|
2824
|
+
await watching;
|
|
2730
2825
|
},
|
|
2731
2826
|
saveBeforeStop: async () => {
|
|
2732
2827
|
stopped = true;
|
|
@@ -2757,9 +2852,10 @@ function keepRemote(opts) {
|
|
|
2757
2852
|
}
|
|
2758
2853
|
await check().catch((err) => note(`final push unavailable: ${firstLine(err)}`));
|
|
2759
2854
|
await opening;
|
|
2855
|
+
await watching;
|
|
2760
2856
|
const head = await git2("rev-parse", "--short", "HEAD").catch(() => "unknown");
|
|
2761
2857
|
const pr = readEvidence(id, lane).prUrl;
|
|
2762
|
-
const message = `${saved}; ${branch}@${head}${pr ? `; draft PR ${pr}` : ""}${pushProblem ? `; ${pushProblem}` : ""}${draftProblem ? `; ${draftProblem}` : ""}`;
|
|
2858
|
+
const message = `${saved}; ${branch}@${head}${pr ? `; ${existingPrUrl ? "existing" : "draft"} PR ${pr}` : ""}${pushProblem ? `; ${pushProblem}` : ""}${draftProblem ? `; ${draftProblem}` : ""}`;
|
|
2763
2859
|
note(message);
|
|
2764
2860
|
return message;
|
|
2765
2861
|
}
|
|
@@ -2776,23 +2872,23 @@ function coldNote(cold, cwd, trunk) {
|
|
|
2776
2872
|
}
|
|
2777
2873
|
async function main(activeDir, lane, seams = {}) {
|
|
2778
2874
|
const deps = { ...defaultDeps, ...seams };
|
|
2779
|
-
const id =
|
|
2875
|
+
const id = path15.basename(activeDir);
|
|
2780
2876
|
if (isTrapCatch(id, lane)) throw new Error(`refusing headless runner for trap catch ${id}`);
|
|
2781
2877
|
const status = (verb, note) => appendStatus(id, lane, verb, note);
|
|
2782
2878
|
const cfg = loadConfig();
|
|
2783
|
-
const descriptor = JSON.parse(
|
|
2879
|
+
const descriptor = JSON.parse(fs17.readFileSync(path15.join(activeDir, "descriptor.json"), "utf8"));
|
|
2784
2880
|
const resolved = resolveDispatch(descriptor, cfg);
|
|
2785
2881
|
const repo = cfg.repos[descriptor.repo];
|
|
2786
|
-
const briefFile =
|
|
2787
|
-
if (!
|
|
2788
|
-
const brief =
|
|
2882
|
+
const briefFile = path15.join(activeDir, "brief.md");
|
|
2883
|
+
if (!fs17.existsSync(briefFile)) fs17.writeFileSync(briefFile, descriptor.brief);
|
|
2884
|
+
const brief = fs17.readFileSync(briefFile, "utf8");
|
|
2789
2885
|
const attempts = Number(process.env.LOBSTAH_ATTEMPTS ?? "1");
|
|
2790
2886
|
const runnerInfo = {
|
|
2791
2887
|
pid: process.pid,
|
|
2792
2888
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2793
2889
|
attempts
|
|
2794
2890
|
};
|
|
2795
|
-
|
|
2891
|
+
fs17.writeFileSync(path15.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
|
|
2796
2892
|
const plan = planStart({
|
|
2797
2893
|
id,
|
|
2798
2894
|
lane,
|
|
@@ -2801,13 +2897,13 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
2801
2897
|
resolvedHarness: resolved.harness,
|
|
2802
2898
|
envResume: process.env.LOBSTAH_RESUME
|
|
2803
2899
|
});
|
|
2804
|
-
const wtFile =
|
|
2900
|
+
const wtFile = path15.join(activeDir, "worktree.json");
|
|
2805
2901
|
let recorded;
|
|
2806
2902
|
let choice;
|
|
2807
|
-
if (
|
|
2808
|
-
recorded = JSON.parse(
|
|
2809
|
-
if (!
|
|
2810
|
-
} else if (
|
|
2903
|
+
if (fs17.existsSync(wtFile)) {
|
|
2904
|
+
recorded = JSON.parse(fs17.readFileSync(wtFile, "utf8")).path;
|
|
2905
|
+
if (!fs17.existsSync(recorded)) throw new Error(`recorded worktree ${recorded} is gone`);
|
|
2906
|
+
} else if (fs17.existsSync(worktreePath(id))) {
|
|
2811
2907
|
throw new Error(`unrecorded worktree already exists for ${id} \u2014 refusing to proceed`);
|
|
2812
2908
|
} else if (descriptor.followUp && cfg.limits.reuseWorktree !== false) {
|
|
2813
2909
|
choice = await deps.chooseWorktree({ id, lane, repoKey: descriptor.repo, repo, followUp: descriptor.followUp });
|
|
@@ -2841,14 +2937,16 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
2841
2937
|
type: "runner",
|
|
2842
2938
|
data: { worktree: cwd, worktreeOf: choice.owner, setupRan }
|
|
2843
2939
|
});
|
|
2844
|
-
|
|
2940
|
+
fs17.writeFileSync(wtFile, JSON.stringify({ path: cwd, of: choice.owner }, null, 2));
|
|
2845
2941
|
} else {
|
|
2846
2942
|
cwd = await deps.allocate(repo, id);
|
|
2847
2943
|
acquireWorktreeLock(cwd, id, lane);
|
|
2848
|
-
|
|
2944
|
+
fs17.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
|
|
2849
2945
|
}
|
|
2850
|
-
const worktreeOf = JSON.parse(
|
|
2946
|
+
const worktreeOf = JSON.parse(fs17.readFileSync(wtFile, "utf8")).of;
|
|
2851
2947
|
mergeEvidence(id, lane, { worktree: cwd, worktreeOf });
|
|
2948
|
+
const existingPr = descriptor.followUp ? chainPr(descriptor.followUp, lane) : void 0;
|
|
2949
|
+
if (existingPr) mergeEvidence(id, lane, { prUrl: existingPr.url });
|
|
2852
2950
|
const remotePolicy = {
|
|
2853
2951
|
pushEarly: repo.pushEarly ?? cfg.limits.pushEarly,
|
|
2854
2952
|
draftPr: repo.draftPr ?? cfg.limits.draftPr,
|
|
@@ -2861,19 +2959,20 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
2861
2959
|
cwd,
|
|
2862
2960
|
trunk: repo.trunk,
|
|
2863
2961
|
title: descriptor.brief.split("\n")[0]?.trim() || `Lobstah dispatch ${short3(id)}`,
|
|
2864
|
-
policy: remotePolicy
|
|
2962
|
+
policy: remotePolicy,
|
|
2963
|
+
existingPrUrl: existingPr?.url
|
|
2865
2964
|
});
|
|
2866
2965
|
const envNudge = process.env.LOBSTAH_NUDGE;
|
|
2867
2966
|
const planNudge = plan.cold && !envNudge ? coldNote(plan.cold, cwd, repo.trunk) : void 0;
|
|
2868
|
-
const promptWith = (nudge) => buildPrompt(brief, { id, nudge, attachments: descriptor.attachments });
|
|
2869
|
-
const binDir =
|
|
2870
|
-
const workerPath =
|
|
2967
|
+
const promptWith = (nudge) => buildPrompt(brief, { id, nudge, attachments: descriptor.attachments, existingPr });
|
|
2968
|
+
const binDir = path15.resolve(path15.dirname(fileURLToPath2(import.meta.url)), "..", "..", "..", "bin");
|
|
2969
|
+
const workerPath = fs17.existsSync(path15.join(binDir, "lobstah")) ? `${binDir}${path15.delimiter}${process.env.PATH ?? ""}` : void 0;
|
|
2871
2970
|
let wallClockHit = false;
|
|
2872
2971
|
let current;
|
|
2873
|
-
const wallFile =
|
|
2972
|
+
const wallFile = path15.join(activeDir, "wallclock.json");
|
|
2874
2973
|
const wallState = (() => {
|
|
2875
2974
|
try {
|
|
2876
|
-
return JSON.parse(
|
|
2975
|
+
return JSON.parse(fs17.readFileSync(wallFile, "utf8"));
|
|
2877
2976
|
} catch {
|
|
2878
2977
|
return { elapsedMs: 0, windowMs: resolved.limits.wallClockSecs * 1e3 };
|
|
2879
2978
|
}
|
|
@@ -2892,8 +2991,8 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
2892
2991
|
) * 1e3;
|
|
2893
2992
|
const persistWall = (elapsedMs, windowMs) => {
|
|
2894
2993
|
const tmp = `${wallFile}.tmp-${process.pid}`;
|
|
2895
|
-
|
|
2896
|
-
|
|
2994
|
+
fs17.writeFileSync(tmp, JSON.stringify({ ...wallState, elapsedMs, windowMs }));
|
|
2995
|
+
fs17.renameSync(tmp, wallFile);
|
|
2897
2996
|
};
|
|
2898
2997
|
const madeProgress = () => {
|
|
2899
2998
|
const activity = readActivity(id, lane);
|
|
@@ -3013,10 +3112,10 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
3013
3112
|
function runRunner(activeDirArg2, laneArg2) {
|
|
3014
3113
|
const lane = laneArg2 === "chore" ? "chore" : "work";
|
|
3015
3114
|
main(activeDirArg2, lane).catch((err) => {
|
|
3016
|
-
const id =
|
|
3115
|
+
const id = path16.basename(activeDirArg2);
|
|
3017
3116
|
try {
|
|
3018
3117
|
appendStatus(id, lane, "failed", String(err instanceof Error ? err.message : err).slice(0, 500));
|
|
3019
|
-
|
|
3118
|
+
fs18.renameSync(activeDirArg2, path16.join(laneDirs(lane).done, id));
|
|
3020
3119
|
} catch {
|
|
3021
3120
|
}
|
|
3022
3121
|
process.exitCode = 1;
|