lobstah 0.3.1 → 0.5.0

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/runner.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // packages/runner/src/index.ts
2
- import * as fs9 from "node:fs";
3
- import * as path7 from "node:path";
2
+ import * as fs10 from "node:fs";
3
+ import * as path8 from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  // packages/core/dist/types.js
@@ -37,18 +37,11 @@ function evidencePath(id, lane) {
37
37
  }
38
38
 
39
39
  // packages/core/dist/queue.js
40
- import * as fs from "node:fs";
40
+ import * as fs2 from "node:fs";
41
41
  import * as path2 from "node:path";
42
- function complete(id, lane) {
43
- const dirs = laneDirs(lane);
44
- fs.renameSync(path2.join(dirs.active, id), path2.join(dirs.done, id));
45
- }
46
- function cancelRequested(id, lane) {
47
- return fs.existsSync(path2.join(laneDirs(lane).active, id, "cancel"));
48
- }
49
42
 
50
43
  // packages/core/dist/status.js
51
- import * as fs2 from "node:fs";
44
+ import * as fs from "node:fs";
52
45
  function isVerb(v) {
53
46
  return VERBS.includes(v);
54
47
  }
@@ -57,15 +50,15 @@ function appendStatus(id, lane, verb, note) {
57
50
  throw new Error(`invalid status verb "${verb}" \u2014 must be one of: ${VERBS.join(", ")}`);
58
51
  }
59
52
  const entry = { at: (/* @__PURE__ */ new Date()).toISOString(), verb, ...note ? { note } : {} };
60
- fs2.appendFileSync(statusPath(id, lane), `${JSON.stringify(entry)}
53
+ fs.appendFileSync(statusPath(id, lane), `${JSON.stringify(entry)}
61
54
  `);
62
55
  return entry;
63
56
  }
64
57
  function readStatusLog(id, lane) {
65
58
  const file = statusPath(id, lane);
66
- if (!fs2.existsSync(file))
59
+ if (!fs.existsSync(file))
67
60
  return [];
68
- return fs2.readFileSync(file, "utf8").split("\n").filter(Boolean).flatMap((line) => {
61
+ return fs.readFileSync(file, "utf8").split("\n").filter(Boolean).flatMap((line) => {
69
62
  try {
70
63
  const parsed = JSON.parse(line);
71
64
  return isVerb(parsed.verb) ? [parsed] : [];
@@ -75,6 +68,15 @@ function readStatusLog(id, lane) {
75
68
  });
76
69
  }
77
70
 
71
+ // packages/core/dist/queue.js
72
+ function complete(id, lane) {
73
+ const dirs = laneDirs(lane);
74
+ fs2.renameSync(path2.join(dirs.active, id), path2.join(dirs.done, id));
75
+ }
76
+ function cancelRequested(id, lane) {
77
+ return fs2.existsSync(path2.join(laneDirs(lane).active, id, "cancel"));
78
+ }
79
+
78
80
  // packages/core/dist/events.js
79
81
  import * as fs3 from "node:fs";
80
82
  function appendEvent(id, lane, ev) {
@@ -771,6 +773,10 @@ var DEFAULT_SOAK = {
771
773
  deferSecs: 90,
772
774
  ttlSecs: 1800
773
775
  };
776
+ var DEFAULT_HELM = {
777
+ ttlSecs: 1800,
778
+ reportSecs: 900
779
+ };
774
780
  var DEFAULT_LIMITS = {
775
781
  maxConcurrent: 2,
776
782
  choreConcurrent: 1,
@@ -801,11 +807,18 @@ function loadConfig() {
801
807
  pickup: r.pickup === void 0 ? void 0 : Boolean(r.pickup)
802
808
  };
803
809
  }
810
+ const groundsRaw = raw.grounds ?? {};
811
+ const grounds = {};
812
+ for (const [key, g] of Object.entries(groundsRaw)) {
813
+ grounds[key] = { repos: Array.isArray(g.repos) ? g.repos.map(String) : [] };
814
+ }
804
815
  return {
805
816
  repos,
806
817
  harness: raw.harness ?? {},
807
818
  limits: { ...DEFAULT_LIMITS, ...raw.limits ?? {} },
808
819
  soak: { ...DEFAULT_SOAK, ...raw.soak ?? {} },
820
+ helm: { ...DEFAULT_HELM, ...raw.helm ?? {} },
821
+ grounds,
809
822
  notifyCommand: raw.notifyCommand ? String(raw.notifyCommand) : void 0,
810
823
  notifyVerbs: Array.isArray(raw.notifyVerbs) ? raw.notifyVerbs.map(String) : void 0,
811
824
  remindSecs: raw.remindSecs !== void 0 ? Number(raw.remindSecs) : void 0
@@ -830,6 +843,58 @@ function resolveDispatch(d, cfg) {
830
843
  };
831
844
  }
832
845
 
846
+ // packages/core/dist/harness.js
847
+ import * as fs7 from "node:fs";
848
+ import * as path5 from "node:path";
849
+ import { createRequire } from "node:module";
850
+ function resolveOnPath(bin, pathVar = process.env.PATH) {
851
+ if (!pathVar)
852
+ return void 0;
853
+ const names = process.platform === "win32" ? [bin, `${bin}.exe`, `${bin}.cmd`, `${bin}.bat`] : [bin];
854
+ for (const dir of pathVar.split(path5.delimiter)) {
855
+ if (!dir)
856
+ continue;
857
+ for (const name of names) {
858
+ const candidate = path5.join(dir, name);
859
+ try {
860
+ fs7.accessSync(candidate, fs7.constants.X_OK);
861
+ if (fs7.statSync(candidate).isFile())
862
+ return candidate;
863
+ } catch {
864
+ }
865
+ }
866
+ }
867
+ return void 0;
868
+ }
869
+ function onPath(bin, pathVar = process.env.PATH) {
870
+ return resolveOnPath(bin, pathVar) !== void 0;
871
+ }
872
+ function codexInvocation(argv) {
873
+ if (onPath("codex"))
874
+ return { file: "codex", argv };
875
+ const require_ = createRequire(import.meta.url);
876
+ try {
877
+ const cli = require_.resolve("@openai/codex/bin/codex.js");
878
+ return { file: process.execPath, argv: [cli, ...argv] };
879
+ } catch {
880
+ }
881
+ for (const dir of require_.resolve.paths("@openai/codex") ?? []) {
882
+ const cli = path5.join(dir, "@openai", "codex", "bin", "codex.js");
883
+ if (fs7.existsSync(cli))
884
+ return { file: process.execPath, argv: [cli, ...argv] };
885
+ }
886
+ return void 0;
887
+ }
888
+
889
+ // packages/core/dist/compiled.js
890
+ var COMPILED_BINARY = (() => {
891
+ const bun = globalThis.Bun;
892
+ return bun !== void 0 && String(bun.main ?? "").includes("$bunfs");
893
+ })();
894
+
895
+ // packages/adapters/dist/claude.js
896
+ import { spawn } from "node:child_process";
897
+
833
898
  // packages/adapters/dist/types.js
834
899
  var AsyncQueue = class {
835
900
  buffer = [];
@@ -908,29 +973,154 @@ function flagsToExtraArgs(flags) {
908
973
  }
909
974
  return extra;
910
975
  }
976
+ function pumpClaudeMessage(msg, push, onSession) {
977
+ const at = now();
978
+ if (msg.type === "system" && msg.subtype === "init") {
979
+ if (msg.session_id)
980
+ onSession(msg.session_id);
981
+ push({ at, type: "session", data: { sessionId: msg.session_id } });
982
+ } else if (msg.type === "assistant") {
983
+ for (const block of msg.message?.content ?? []) {
984
+ if (block.type === "tool_use") {
985
+ push({ at, type: "tool-start", data: { name: block.name } });
986
+ } else if (block.type === "text" && block.text) {
987
+ push({ at, type: "text", data: { text: String(block.text).slice(0, 2e3) } });
988
+ }
989
+ }
990
+ } else if (msg.type === "user") {
991
+ const content = msg.message?.content;
992
+ if (Array.isArray(content)) {
993
+ for (const block of content) {
994
+ if (block.type === "tool_result")
995
+ push({ at, type: "tool-end", data: {} });
996
+ }
997
+ }
998
+ } else if (msg.type === "result") {
999
+ push({ at, type: "turn-end", data: { subtype: msg.subtype, costUsd: msg.total_cost_usd } });
1000
+ }
1001
+ }
1002
+ var userMessage = (content) => ({
1003
+ type: "user",
1004
+ message: { role: "user", content },
1005
+ parent_tool_use_id: null,
1006
+ session_id: ""
1007
+ });
1008
+ function claudeCliArgs(opts) {
1009
+ const args = [
1010
+ "-p",
1011
+ "--output-format",
1012
+ "stream-json",
1013
+ "--input-format",
1014
+ "stream-json",
1015
+ "--verbose",
1016
+ "--permission-mode",
1017
+ "bypassPermissions"
1018
+ ];
1019
+ if (opts.model)
1020
+ args.push("--model", opts.model);
1021
+ if (opts.limits.maxTurns)
1022
+ args.push("--max-turns", String(opts.limits.maxTurns));
1023
+ if (opts.resumeSession)
1024
+ args.push("--resume", opts.resumeSession, "--fork-session");
1025
+ args.push(...opts.flags);
1026
+ return args;
1027
+ }
1028
+ function startClaudeCli(invocation, opts) {
1029
+ const child = spawn(invocation.file, [...invocation.argvPrefix, ...claudeCliArgs(opts)], {
1030
+ cwd: opts.cwd,
1031
+ env: { ...process.env, ...opts.env },
1032
+ stdio: ["pipe", "pipe", "pipe"],
1033
+ shell: process.platform === "win32"
1034
+ // .cmd shims need a shell to exec
1035
+ });
1036
+ const events = new AsyncQueue();
1037
+ const input = new InputGate();
1038
+ let sessionId;
1039
+ let stderrTail = "";
1040
+ child.stdin.on("error", () => {
1041
+ });
1042
+ child.stderr.on("data", (d) => {
1043
+ stderrTail = (stderrTail + d.toString()).slice(-2e3);
1044
+ });
1045
+ child.stdin.write(`${JSON.stringify(userMessage(opts.prompt))}
1046
+ `);
1047
+ void (async () => {
1048
+ while (true) {
1049
+ const text = await input.next();
1050
+ if (text === void 0)
1051
+ break;
1052
+ child.stdin.write(`${JSON.stringify(userMessage(text))}
1053
+ `);
1054
+ }
1055
+ child.stdin.end();
1056
+ })();
1057
+ let buf = "";
1058
+ child.stdout.on("data", (d) => {
1059
+ buf += d.toString();
1060
+ let i;
1061
+ while ((i = buf.indexOf("\n")) >= 0) {
1062
+ const line = buf.slice(0, i).trim();
1063
+ buf = buf.slice(i + 1);
1064
+ if (!line)
1065
+ continue;
1066
+ try {
1067
+ pumpClaudeMessage(JSON.parse(line), (e) => events.push(e), (id) => sessionId = id);
1068
+ } catch {
1069
+ }
1070
+ }
1071
+ });
1072
+ const done = new Promise((resolve2) => {
1073
+ child.on("error", (err) => {
1074
+ events.push({ at: now(), type: "error", data: { message: err.message } });
1075
+ events.close();
1076
+ resolve2({ sessionId, error: err.message });
1077
+ });
1078
+ child.on("exit", (code) => {
1079
+ events.close();
1080
+ if (code === 0)
1081
+ resolve2({ sessionId });
1082
+ else {
1083
+ const detail = stderrTail.trim().split("\n").at(-1);
1084
+ const error = `claude exited ${code}${detail ? ` \u2014 ${detail}` : ""}`;
1085
+ resolve2({ sessionId, error });
1086
+ }
1087
+ });
1088
+ });
1089
+ return {
1090
+ events,
1091
+ send: (text) => input.send(text),
1092
+ end: () => input.end(),
1093
+ done,
1094
+ kill: () => {
1095
+ try {
1096
+ child.kill("SIGKILL");
1097
+ } catch {
1098
+ }
1099
+ input.end();
1100
+ }
1101
+ };
1102
+ }
911
1103
  function createClaudeAdapter() {
912
1104
  return {
913
1105
  name: "claude",
914
1106
  async start(opts) {
915
- const sdk = await import("@anthropic-ai/claude-agent-sdk");
1107
+ let sdk;
1108
+ try {
1109
+ sdk = await import("@anthropic-ai/claude-agent-sdk");
1110
+ } catch (err) {
1111
+ if (onPath("claude"))
1112
+ return startClaudeCli({ file: "claude", argvPrefix: [] }, opts);
1113
+ const detail = err instanceof Error ? err.message : String(err);
1114
+ throw new Error(`claude adapter: agent SDK unavailable (${detail}) and no \`claude\` CLI on PATH`);
1115
+ }
916
1116
  const input = new InputGate();
917
1117
  async function* userMessages() {
918
- yield {
919
- type: "user",
920
- message: { role: "user", content: opts.prompt },
921
- parent_tool_use_id: null,
922
- session_id: ""
923
- };
1118
+ yield userMessage(opts.prompt);
924
1119
  while (true) {
925
1120
  const text = await input.next();
926
1121
  if (text === void 0)
927
1122
  return;
928
- yield {
929
- type: "user",
930
- message: { role: "user", content: text },
931
- parent_tool_use_id: null,
932
- session_id: ""
933
- };
1123
+ yield userMessage(text);
934
1124
  }
935
1125
  }
936
1126
  const q = sdk.query({
@@ -951,29 +1141,7 @@ function createClaudeAdapter() {
951
1141
  const done = (async () => {
952
1142
  try {
953
1143
  for await (const msg of q) {
954
- const at = now();
955
- if (msg.type === "system" && msg.subtype === "init") {
956
- sessionId = msg.session_id;
957
- events.push({ at, type: "session", data: { sessionId } });
958
- } else if (msg.type === "assistant") {
959
- for (const block of msg.message?.content ?? []) {
960
- if (block.type === "tool_use") {
961
- events.push({ at, type: "tool-start", data: { name: block.name } });
962
- } else if (block.type === "text" && block.text) {
963
- events.push({ at, type: "text", data: { text: String(block.text).slice(0, 2e3) } });
964
- }
965
- }
966
- } else if (msg.type === "user") {
967
- const content = msg.message?.content;
968
- if (Array.isArray(content)) {
969
- for (const block of content) {
970
- if (block.type === "tool_result")
971
- events.push({ at, type: "tool-end", data: {} });
972
- }
973
- }
974
- } else if (msg.type === "result") {
975
- events.push({ at, type: "turn-end", data: { subtype: msg.subtype, costUsd: msg.total_cost_usd } });
976
- }
1144
+ pumpClaudeMessage(msg, (e) => events.push(e), (id) => sessionId = id);
977
1145
  }
978
1146
  return { sessionId };
979
1147
  } catch (err) {
@@ -1002,32 +1170,159 @@ function createClaudeAdapter() {
1002
1170
  }
1003
1171
 
1004
1172
  // packages/adapters/dist/codex.js
1005
- import * as fs7 from "node:fs";
1173
+ import * as fs8 from "node:fs";
1006
1174
  import * as os3 from "node:os";
1007
- import * as path5 from "node:path";
1175
+ import * as path6 from "node:path";
1176
+ import { spawn as spawn2 } from "node:child_process";
1008
1177
  function isolatedCodexHome() {
1009
- const dir = path5.join(lobstahHome(), "codex-home");
1010
- fs7.mkdirSync(dir, { recursive: true });
1011
- const auth = path5.join(dir, "auth.json");
1012
- const realAuth = path5.join(os3.homedir(), ".codex", "auth.json");
1013
- if (!fs7.existsSync(auth) && fs7.existsSync(realAuth)) {
1178
+ const dir = path6.join(lobstahHome(), "codex-home");
1179
+ fs8.mkdirSync(dir, { recursive: true });
1180
+ const auth = path6.join(dir, "auth.json");
1181
+ const realAuth = path6.join(os3.homedir(), ".codex", "auth.json");
1182
+ if (!fs8.existsSync(auth) && fs8.existsSync(realAuth)) {
1014
1183
  try {
1015
- fs7.symlinkSync(realAuth, auth);
1184
+ fs8.symlinkSync(realAuth, auth);
1016
1185
  } catch {
1017
- fs7.copyFileSync(realAuth, auth);
1186
+ fs8.copyFileSync(realAuth, auth);
1018
1187
  }
1019
1188
  }
1020
- const cfg = path5.join(dir, "config.toml");
1021
- if (!fs7.existsSync(cfg)) {
1022
- fs7.writeFileSync(cfg, "# lobstah-managed: auth inherited from ~/.codex, interactive config not\n");
1189
+ const cfg = path6.join(dir, "config.toml");
1190
+ if (!fs8.existsSync(cfg)) {
1191
+ fs8.writeFileSync(cfg, "# lobstah-managed: auth inherited from ~/.codex, interactive config not\n");
1023
1192
  }
1024
1193
  return dir;
1025
1194
  }
1195
+ function pumpCodexEvent(ev, push, onSession) {
1196
+ const at = now();
1197
+ if (ev.type === "thread.started") {
1198
+ if (ev.thread_id)
1199
+ onSession(ev.thread_id);
1200
+ push({ at, type: "session", data: { sessionId: ev.thread_id } });
1201
+ } else if (ev.type === "turn.started") {
1202
+ push({ at, type: "turn-start", data: {} });
1203
+ } else if (ev.type === "item.started") {
1204
+ push({ at, type: "tool-start", data: { name: ev.item?.type } });
1205
+ } else if (ev.type === "item.completed") {
1206
+ if (ev.item?.type === "agent_message" && ev.item?.text) {
1207
+ push({ at, type: "text", data: { text: String(ev.item.text).slice(0, 2e3) } });
1208
+ } else {
1209
+ push({ at, type: "tool-end", data: { name: ev.item?.type } });
1210
+ }
1211
+ } else if (ev.type === "turn.failed") {
1212
+ const message = ev.error?.message ?? "turn failed";
1213
+ push({ at, type: "error", data: { message } });
1214
+ return message;
1215
+ }
1216
+ return void 0;
1217
+ }
1218
+ function codexExecArgs(opts, prompt, resumeThread) {
1219
+ const args = ["exec"];
1220
+ if (resumeThread)
1221
+ args.push("resume", resumeThread);
1222
+ args.push("--json", "--skip-git-repo-check", "--sandbox", "workspace-write", "--cd", opts.cwd);
1223
+ if (opts.model)
1224
+ args.push("-m", opts.model);
1225
+ if (opts.effort)
1226
+ args.push("-c", `model_reasoning_effort=${JSON.stringify(opts.effort)}`);
1227
+ args.push(prompt);
1228
+ return args;
1229
+ }
1230
+ function startCodexCli(invoke, opts) {
1231
+ const input = new InputGate();
1232
+ const events = new AsyncQueue();
1233
+ let sessionId;
1234
+ let current;
1235
+ let killed = false;
1236
+ function runTurn(prompt, resumeThread) {
1237
+ return new Promise((resolve2) => {
1238
+ const inv = invoke(codexExecArgs(opts, prompt, resumeThread));
1239
+ const child = spawn2(inv.file, inv.argv, {
1240
+ cwd: opts.cwd,
1241
+ env: { ...process.env, ...opts.env, CODEX_HOME: isolatedCodexHome() },
1242
+ stdio: ["ignore", "pipe", "pipe"],
1243
+ shell: process.platform === "win32" && inv.file !== process.execPath
1244
+ });
1245
+ current = child;
1246
+ let turnError;
1247
+ let stderrTail = "";
1248
+ child.stderr?.on("data", (d) => {
1249
+ stderrTail = (stderrTail + d.toString()).slice(-2e3);
1250
+ });
1251
+ let buf = "";
1252
+ child.stdout?.on("data", (d) => {
1253
+ buf += d.toString();
1254
+ let i;
1255
+ while ((i = buf.indexOf("\n")) >= 0) {
1256
+ const line = buf.slice(0, i).trim();
1257
+ buf = buf.slice(i + 1);
1258
+ if (!line)
1259
+ continue;
1260
+ try {
1261
+ const failed = pumpCodexEvent(JSON.parse(line), (e) => events.push(e), (id) => sessionId = id);
1262
+ if (failed)
1263
+ turnError = failed;
1264
+ } catch {
1265
+ }
1266
+ }
1267
+ });
1268
+ child.on("error", (err) => resolve2(err.message));
1269
+ child.on("exit", (code) => {
1270
+ if (turnError)
1271
+ return resolve2(turnError);
1272
+ if (code === 0 || killed)
1273
+ return resolve2(void 0);
1274
+ const detail = stderrTail.trim().split("\n").at(-1);
1275
+ resolve2(`codex exited ${code}${detail ? ` \u2014 ${detail}` : ""}`);
1276
+ });
1277
+ });
1278
+ }
1279
+ const done = (async () => {
1280
+ try {
1281
+ let next = opts.prompt;
1282
+ let thread = opts.resumeSession;
1283
+ while (next !== void 0 && !killed) {
1284
+ const error = await runTurn(next, thread);
1285
+ thread = sessionId ?? thread;
1286
+ if (error) {
1287
+ events.push({ at: now(), type: "error", data: { message: error } });
1288
+ return { sessionId, error };
1289
+ }
1290
+ events.push({ at: now(), type: "turn-end", data: {} });
1291
+ next = await input.next();
1292
+ }
1293
+ return { sessionId };
1294
+ } finally {
1295
+ events.close();
1296
+ }
1297
+ })();
1298
+ return {
1299
+ events,
1300
+ send: (text) => input.send(text),
1301
+ end: () => input.end(),
1302
+ done,
1303
+ kill: () => {
1304
+ killed = true;
1305
+ try {
1306
+ current?.kill("SIGKILL");
1307
+ } catch {
1308
+ }
1309
+ input.end();
1310
+ }
1311
+ };
1312
+ }
1026
1313
  function createCodexAdapter() {
1027
1314
  return {
1028
1315
  name: "codex",
1029
1316
  async start(opts) {
1030
- const mod = await import("@openai/codex-sdk");
1317
+ let mod;
1318
+ try {
1319
+ mod = await import("@openai/codex-sdk");
1320
+ } catch (err) {
1321
+ if (codexInvocation([]))
1322
+ return startCodexCli((argv) => codexInvocation(argv), opts);
1323
+ const detail = err instanceof Error ? err.message : String(err);
1324
+ throw new Error(`codex adapter: SDK unavailable (${detail}) and no codex CLI found (PATH or vendored)`);
1325
+ }
1031
1326
  const codex = new mod.Codex({
1032
1327
  env: { ...process.env, ...opts.env, CODEX_HOME: isolatedCodexHome() }
1033
1328
  });
@@ -1050,24 +1345,9 @@ function createCodexAdapter() {
1050
1345
  while (next !== void 0) {
1051
1346
  const streamed = await thread.runStreamed(next);
1052
1347
  for await (const ev of streamed.events) {
1053
- const at = now();
1054
- if (ev.type === "thread.started") {
1055
- sessionId = ev.thread_id;
1056
- events.push({ at, type: "session", data: { sessionId } });
1057
- } else if (ev.type === "turn.started") {
1058
- events.push({ at, type: "turn-start", data: {} });
1059
- } else if (ev.type === "item.started") {
1060
- events.push({ at, type: "tool-start", data: { name: ev.item?.type } });
1061
- } else if (ev.type === "item.completed") {
1062
- if (ev.item?.type === "agent_message" && ev.item?.text) {
1063
- events.push({ at, type: "text", data: { text: String(ev.item.text).slice(0, 2e3) } });
1064
- } else {
1065
- events.push({ at, type: "tool-end", data: { name: ev.item?.type } });
1066
- }
1067
- } else if (ev.type === "turn.failed") {
1068
- lastTurnError = ev.error?.message ?? "turn failed";
1069
- events.push({ at, type: "error", data: { message: lastTurnError } });
1070
- }
1348
+ const failed = pumpCodexEvent(ev, (e) => events.push(e), (id) => sessionId = id);
1349
+ if (failed)
1350
+ lastTurnError = failed;
1071
1351
  }
1072
1352
  events.push({ at: now(), type: "turn-end", data: {} });
1073
1353
  next = await input.next();
@@ -1105,14 +1385,14 @@ function loadAdapter(name) {
1105
1385
  }
1106
1386
 
1107
1387
  // packages/worktree/dist/index.js
1108
- import * as fs8 from "node:fs";
1109
- import * as path6 from "node:path";
1388
+ import * as fs9 from "node:fs";
1389
+ import * as path7 from "node:path";
1110
1390
  import { exec, execFile } from "node:child_process";
1111
1391
  import { promisify } from "node:util";
1112
1392
  var run = promisify(execFile);
1113
1393
  var shell = promisify(exec);
1114
1394
  function worktreePath(id) {
1115
- return path6.join(lobstahHome(), "worktrees", id);
1395
+ return path7.join(lobstahHome(), "worktrees", id);
1116
1396
  }
1117
1397
  async function git(cwd, ...args) {
1118
1398
  const { stdout } = await run("git", args, { cwd, env: process.env });
@@ -1120,10 +1400,10 @@ async function git(cwd, ...args) {
1120
1400
  }
1121
1401
  async function allocate(repo, id) {
1122
1402
  const dir = worktreePath(id);
1123
- if (fs8.existsSync(dir)) {
1403
+ if (fs9.existsSync(dir)) {
1124
1404
  throw new Error(`worktree for ${id} already exists at ${dir} \u2014 never allocate a second`);
1125
1405
  }
1126
- if (!fs8.existsSync(repo.path)) {
1406
+ if (!fs9.existsSync(repo.path)) {
1127
1407
  if (!repo.origin)
1128
1408
  throw new Error(`repo path ${repo.path} missing and no origin configured`);
1129
1409
  await run("git", ["clone", repo.origin, repo.path], { env: process.env });
@@ -1167,40 +1447,40 @@ function followUpSession(followUp) {
1167
1447
  return void 0;
1168
1448
  }
1169
1449
  async function main(activeDir, lane) {
1170
- const id = path7.basename(activeDir);
1450
+ const id = path8.basename(activeDir);
1171
1451
  const status = (verb, note) => appendStatus(id, lane, verb, note);
1172
1452
  const cfg = loadConfig();
1173
- const descriptor = JSON.parse(fs9.readFileSync(path7.join(activeDir, "descriptor.json"), "utf8"));
1453
+ const descriptor = JSON.parse(fs10.readFileSync(path8.join(activeDir, "descriptor.json"), "utf8"));
1174
1454
  const resolved = resolveDispatch(descriptor, cfg);
1175
1455
  const repo = cfg.repos[descriptor.repo];
1176
- const briefFile = path7.join(activeDir, "brief.md");
1177
- if (!fs9.existsSync(briefFile)) fs9.writeFileSync(briefFile, descriptor.brief);
1178
- const brief = fs9.readFileSync(briefFile, "utf8");
1456
+ const briefFile = path8.join(activeDir, "brief.md");
1457
+ if (!fs10.existsSync(briefFile)) fs10.writeFileSync(briefFile, descriptor.brief);
1458
+ const brief = fs10.readFileSync(briefFile, "utf8");
1179
1459
  const attempts = Number(process.env.LOBSTAH_ATTEMPTS ?? "1");
1180
1460
  const runnerInfo = {
1181
1461
  pid: process.pid,
1182
1462
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1183
1463
  attempts
1184
1464
  };
1185
- fs9.writeFileSync(path7.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
1465
+ fs10.writeFileSync(path8.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
1186
1466
  status("working", attempts > 1 ? `attempt ${attempts}` : void 0);
1187
1467
  appendEvent(id, lane, { at: (/* @__PURE__ */ new Date()).toISOString(), type: "runner", data: { pid: process.pid, attempts } });
1188
- const wtFile = path7.join(activeDir, "worktree.json");
1468
+ const wtFile = path8.join(activeDir, "worktree.json");
1189
1469
  let cwd;
1190
- if (fs9.existsSync(wtFile)) {
1191
- cwd = JSON.parse(fs9.readFileSync(wtFile, "utf8")).path;
1192
- if (!fs9.existsSync(cwd)) throw new Error(`recorded worktree ${cwd} is gone`);
1193
- } else if (fs9.existsSync(worktreePath(id))) {
1470
+ if (fs10.existsSync(wtFile)) {
1471
+ cwd = JSON.parse(fs10.readFileSync(wtFile, "utf8")).path;
1472
+ if (!fs10.existsSync(cwd)) throw new Error(`recorded worktree ${cwd} is gone`);
1473
+ } else if (fs10.existsSync(worktreePath(id))) {
1194
1474
  throw new Error(`unrecorded worktree already exists for ${id} \u2014 refusing to proceed`);
1195
1475
  } else {
1196
1476
  cwd = await allocate(repo, id);
1197
- fs9.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
1477
+ fs10.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
1198
1478
  }
1199
1479
  const resumeSession = process.env.LOBSTAH_RESUME ?? (descriptor.followUp ? followUpSession(descriptor.followUp) : void 0);
1200
1480
  const adapter = loadAdapter(resolved.harness);
1201
1481
  const prompt = buildPrompt(brief, { id, nudge: process.env.LOBSTAH_NUDGE });
1202
- const binDir = path7.resolve(path7.dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "bin");
1203
- const workerPath = fs9.existsSync(path7.join(binDir, "lobstah")) ? `${binDir}${path7.delimiter}${process.env.PATH ?? ""}` : void 0;
1482
+ const binDir = path8.resolve(path8.dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "bin");
1483
+ const workerPath = fs10.existsSync(path8.join(binDir, "lobstah")) ? `${binDir}${path8.delimiter}${process.env.PATH ?? ""}` : void 0;
1204
1484
  const run2 = await adapter.start({
1205
1485
  id,
1206
1486
  cwd,
@@ -1268,21 +1548,23 @@ async function main(activeDir, lane) {
1268
1548
  }
1269
1549
  complete(id, lane);
1270
1550
  }
1271
- var [activeDirArg, laneArg] = process.argv.slice(2);
1272
- if (activeDirArg) {
1273
- const lane = laneArg === "chore" ? "chore" : "work";
1274
- main(activeDirArg, lane).catch((err) => {
1275
- const id = path7.basename(activeDirArg);
1551
+ function runRunner(activeDirArg2, laneArg2) {
1552
+ const lane = laneArg2 === "chore" ? "chore" : "work";
1553
+ main(activeDirArg2, lane).catch((err) => {
1554
+ const id = path8.basename(activeDirArg2);
1276
1555
  try {
1277
1556
  appendStatus(id, lane, "failed", String(err instanceof Error ? err.message : err).slice(0, 500));
1278
- fs9.renameSync(activeDirArg, path7.join(laneDirs(lane).done, id));
1557
+ fs10.renameSync(activeDirArg2, path8.join(laneDirs(lane).done, id));
1279
1558
  } catch {
1280
1559
  }
1281
1560
  process.exitCode = 1;
1282
1561
  });
1283
1562
  }
1563
+ var [activeDirArg, laneArg] = process.argv.slice(2);
1564
+ if (activeDirArg && activeDirArg !== "__runner") runRunner(activeDirArg, laneArg);
1284
1565
  export {
1285
- main
1566
+ main,
1567
+ runRunner
1286
1568
  };
1287
1569
  /*! Bundled license information:
1288
1570
 
Binary file
Binary file
Binary file
Binary file