lobstah 0.6.9 → 0.7.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
@@ -9,8 +9,8 @@ var WAITING_ON = ["review", "pr", "deploy", "person", "external"];
9
9
  var WAITING_ON_VERBS = ["paused", "needs-decision", "blocked"];
10
10
 
11
11
  // packages/core/dist/stats.js
12
- import * as fs2 from "node:fs";
13
- import * as path2 from "node:path";
12
+ import * as fs12 from "node:fs";
13
+ import * as path10 from "node:path";
14
14
 
15
15
  // packages/core/dist/paths.js
16
16
  import * as os from "node:os";
@@ -58,1472 +58,1481 @@ function evidencePath(id, lane) {
58
58
  return path.join(laneDirs(lane).state, `${id}.evidence`);
59
59
  }
60
60
 
61
- // packages/core/dist/stats.js
62
- function statsPath() {
63
- return path2.join(lobstahHome(), "stats.json");
61
+ // packages/core/dist/worker-metadata.js
62
+ var WORKER_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
63
+ var WORKER_PERMISSIONS = ["default", "acceptEdits", "plan", "dontAsk", "bypassPermissions", "auto"];
64
+ function localModel(value) {
65
+ return typeof value === "string" && /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,127}$/.test(value) ? value : null;
64
66
  }
65
- function localDay(at = Date.now()) {
66
- const d = new Date(at);
67
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
67
+ function choice(value, values) {
68
+ return typeof value === "string" && values.includes(value) ? value : null;
68
69
  }
69
- function readCatchFacts(stateDir, id) {
70
- let last;
71
- try {
72
- for (const line of fs2.readFileSync(path2.join(stateDir, `${id}.status`), "utf8").split("\n")) {
73
- if (!line.trim())
74
- continue;
75
- try {
76
- last = JSON.parse(line);
77
- } catch {
78
- }
79
- }
80
- } catch {
81
- return { done: false };
82
- }
83
- if (last?.verb !== "done")
84
- return { done: false };
85
- let trap;
86
- try {
87
- const to = JSON.parse(fs2.readFileSync(path2.join(stateDir, `${id}.evidence`), "utf8")).deliveredTo;
88
- if (typeof to === "string" && to.startsWith("wt:"))
89
- trap = to;
90
- } catch {
91
- }
92
- return { done: true, ...typeof last.at === "string" ? { at: last.at } : {}, ...trap ? { trap } : {} };
70
+ function workerMetadata(input = {}) {
71
+ return {
72
+ harness: localModel(input.harness),
73
+ model: localModel(input.model),
74
+ config: { effort: choice(input.config?.effort, WORKER_EFFORTS), permissionMode: choice(input.config?.permissionMode, WORKER_PERMISSIONS) },
75
+ ...typeof input.observedAt === "string" && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(input.observedAt) && Number.isFinite(Date.parse(input.observedAt)) ? { observedAt: input.observedAt } : {}
76
+ };
93
77
  }
94
- function emptyStore(now2) {
95
- return { version: 1, totalCatches: 0, perTrap: {}, day: localDay(now2), catchesToday: 0, counted: [] };
78
+
79
+ // packages/core/dist/worker-profile.js
80
+ var WORKER_HARNESSES = ["claude", "codex", "other"];
81
+ var WORKER_MODEL_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
82
+ var WORKER_MODELS = [
83
+ "other",
84
+ "opus",
85
+ "sonnet",
86
+ "haiku",
87
+ "fable",
88
+ "opusplan",
89
+ "claude-opus-4",
90
+ "claude-opus-4-1",
91
+ "claude-opus-4-5",
92
+ "claude-opus-4-6",
93
+ "claude-opus-5",
94
+ "claude-opus-5-5",
95
+ "claude-sonnet-4",
96
+ "claude-sonnet-4-5",
97
+ "claude-sonnet-4-6",
98
+ "claude-sonnet-5",
99
+ "claude-haiku-4-5",
100
+ "claude-opus-4-20250514",
101
+ "claude-sonnet-4-20250514",
102
+ "claude-sonnet-4-5-20250929",
103
+ "claude-haiku-4-5-20251001",
104
+ "claude-opus-4-1-20250805",
105
+ "claude-opus-4-5-20251101",
106
+ "gpt-5",
107
+ "gpt-5-codex",
108
+ "gpt-5.1",
109
+ "gpt-5.1-codex",
110
+ "gpt-5.1-codex-mini",
111
+ "gpt-5.1-codex-max",
112
+ "gpt-5.2",
113
+ "gpt-5.2-codex",
114
+ "gpt-5.3-codex",
115
+ "gpt-5.4",
116
+ "gpt-5.4-mini",
117
+ "gpt-5.5",
118
+ "gpt-5.6-sol",
119
+ "gpt-5.6-terra",
120
+ "gpt-5.6-luna",
121
+ "gpt-6",
122
+ "gpt-6-sol",
123
+ "gpt-6-astra",
124
+ "gpt-6-luna",
125
+ "gpt-6.1-sol",
126
+ "codex-mini-latest",
127
+ "o1",
128
+ "o3",
129
+ "o3-mini",
130
+ "o4-mini"
131
+ ];
132
+ function choice2(value, choices) {
133
+ return typeof value === "string" && choices.includes(value) ? value : null;
96
134
  }
97
- function add(store, facts, now2) {
98
- store.totalCatches++;
99
- if (facts.trap)
100
- store.perTrap[facts.trap] = (store.perTrap[facts.trap] ?? 0) + 1;
101
- const today = localDay(now2);
102
- if (store.day !== today) {
103
- store.day = today;
104
- store.catchesToday = 0;
135
+ function safeModel(value) {
136
+ if (value === void 0 || value === null || value === "")
137
+ return null;
138
+ return typeof value === "string" && WORKER_MODEL_RE.test(value) && WORKER_MODELS.includes(value) ? value : "other";
139
+ }
140
+ function workerProfile(input = {}) {
141
+ return {
142
+ harness: input.harness === void 0 || input.harness === null ? null : choice2(input.harness, WORKER_HARNESSES) ?? "other",
143
+ model: safeModel(input.model),
144
+ config: { effort: choice2(input.effort, WORKER_EFFORTS), permissionMode: choice2(input.permissionMode, WORKER_PERMISSIONS) }
145
+ };
146
+ }
147
+ function sanitizeWorker(value) {
148
+ return workerProfile({ harness: value?.harness, model: value?.model, effort: value?.config?.effort, permissionMode: value?.config?.permissionMode });
149
+ }
150
+
151
+ // packages/core/dist/soak.js
152
+ import * as fs11 from "node:fs";
153
+ import * as path9 from "node:path";
154
+
155
+ // packages/core/dist/queue.js
156
+ import * as fs4 from "node:fs";
157
+ import * as path3 from "node:path";
158
+
159
+ // packages/core/dist/status.js
160
+ import * as fs3 from "node:fs";
161
+
162
+ // packages/core/dist/activity.js
163
+ import * as fs2 from "node:fs";
164
+ import * as path2 from "node:path";
165
+ var ACTIVITY_KINDS = ["tool", "message", "thinking", "waiting"];
166
+ var ACTIVITY_SUMMARY_MAX = 80;
167
+ var SECRET_PATTERNS = [
168
+ /\b(?:sk|pk|rk)[-_][A-Za-z0-9_-]{8,}/i,
169
+ /\b(?:gh[posur]_|github_pat_|glpat-|xox[abprs]-|npm_|hf_)[A-Za-z0-9_-]{16,}/,
170
+ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/,
171
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/,
172
+ /\bbearer\b/i,
173
+ /(?:token|secret|passw(?:or)?d|pwd|api[-_]?key|auth|credential|private[-_]?key)[^\s]*[=:]/i
174
+ ];
175
+ function looksSecret(word) {
176
+ if (SECRET_PATTERNS.some((re) => re.test(word)))
177
+ return true;
178
+ for (const run2 of word.match(/[A-Za-z0-9+/_=-]{24,}/g) ?? []) {
179
+ if (/[0-9]/.test(run2) && /[A-Za-z]/.test(run2))
180
+ return true;
105
181
  }
106
- const at = facts.at && Number.isFinite(Date.parse(facts.at)) ? facts.at : void 0;
107
- if ((at ? localDay(at) : today) === today)
108
- store.catchesToday++;
182
+ return false;
109
183
  }
110
- function backfillSources() {
111
- const live = ["work", "chore"].map((lane) => ({ dir: laneDirs(lane).state, live: true }));
112
- const backups = readDirIfPresent(lobstahHome()).filter((name) => name.startsWith("state-backup-")).sort().map((name) => ({ dir: path2.join(lobstahHome(), name), live: false })).filter((s) => fs2.statSync(s.dir).isDirectory());
113
- return [...live, ...backups];
184
+ function redactSummary(s, max = ACTIVITY_SUMMARY_MAX) {
185
+ const clean = s.replace(/[\r\n\t]+/g, " ").split(" ").filter(Boolean).map((w) => looksSecret(w) ? "[redacted]" : w).join(" ");
186
+ return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
114
187
  }
115
- function backfillStats(now2 = Date.now()) {
116
- const store = emptyStore(now2);
117
- const seen = /* @__PURE__ */ new Set();
118
- for (const { dir, live } of backfillSources()) {
119
- for (const file of readDirIfPresent(dir).sort()) {
120
- if (!file.endsWith(".status"))
121
- continue;
122
- const id = file.slice(0, -".status".length);
123
- if (seen.has(id))
124
- continue;
125
- seen.add(id);
126
- const facts = readCatchFacts(dir, id);
127
- if (!facts.done)
128
- continue;
129
- add(store, facts, now2);
130
- if (live)
131
- store.counted.push(id);
132
- }
188
+ var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "pwsh", "powershell", "cmd"]);
189
+ function commandWord(command) {
190
+ let words2 = command.trim().split(/\s+/).filter(Boolean);
191
+ while (words2.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words2[0]))
192
+ words2 = words2.slice(1);
193
+ if (words2[0] === "env") {
194
+ words2 = words2.slice(1);
195
+ while (words2.length > 0 && (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words2[0]) || words2[0].startsWith("-")))
196
+ words2 = words2.slice(1);
133
197
  }
134
- return store;
198
+ const first = words2[0];
199
+ if (!first)
200
+ return void 0;
201
+ const name = baseName(first.replace(/^["']|["']$/g, "")).replace(/\.(exe|cmd|bat)$/i, "");
202
+ if (SHELLS.has(name.toLowerCase()) && words2[1] && /^(-\w*c|\/c|-command)$/i.test(words2[1])) {
203
+ return commandWord(words2.slice(2).join(" ").replace(/^["']/, "")) ?? name;
204
+ }
205
+ return name || void 0;
135
206
  }
136
- function parseStore(text) {
207
+ function baseName(p) {
208
+ const parts = p.split(/[\\/]/).filter(Boolean);
209
+ return parts.at(-1) ?? p;
210
+ }
211
+ function isWindowsPath(p) {
212
+ return /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("\\\\");
213
+ }
214
+ function relativeTarget(p, root) {
215
+ if (!root)
216
+ return baseName(p);
217
+ const win = isWindowsPath(p) || isWindowsPath(root);
218
+ const lib = win ? path2.win32 : path2.posix;
219
+ if (!lib.isAbsolute(p))
220
+ return p.replace(/\\/g, "/");
221
+ const rel = lib.relative(win ? root : root.replace(/\\/g, "/"), p);
222
+ if (!rel || rel.startsWith("..") || lib.isAbsolute(rel))
223
+ return `\u2026/${baseName(p)}`;
224
+ return rel.replace(/\\/g, "/");
225
+ }
226
+ function urlHost(u) {
137
227
  try {
138
- const s = JSON.parse(text);
139
- if (s.version !== 1 || typeof s.totalCatches !== "number" || typeof s.catchesToday !== "number" || typeof s.day !== "string")
140
- return void 0;
141
- return { version: 1, totalCatches: s.totalCatches, perTrap: s.perTrap ?? {}, day: s.day, catchesToday: s.catchesToday, counted: s.counted ?? [] };
228
+ return new URL(u).host || void 0;
142
229
  } catch {
143
230
  return void 0;
144
231
  }
145
232
  }
146
- function writeStore(store) {
147
- const file = statsPath();
148
- fs2.mkdirSync(path2.dirname(file), { recursive: true });
149
- const tmp = `${file}.tmp-${process.pid}`;
150
- fs2.writeFileSync(tmp, JSON.stringify(store));
151
- fs2.renameSync(tmp, file);
233
+ function toolTarget(input) {
234
+ if (!input || typeof input !== "object")
235
+ return void 0;
236
+ const i = input;
237
+ const str = (k) => typeof i[k] === "string" && i[k].length > 0 ? i[k] : void 0;
238
+ const file = str("file_path") ?? str("notebook_path") ?? str("path");
239
+ if (file)
240
+ return file;
241
+ const command = str("command") ?? (Array.isArray(i.command) ? i.command.map(String).join(" ") : void 0);
242
+ if (command)
243
+ return commandWord(command);
244
+ const url = str("url");
245
+ if (url)
246
+ return urlHost(url);
247
+ const changes = i.changes;
248
+ if (Array.isArray(changes) && changes.length > 0) {
249
+ const first = changes[0];
250
+ if (typeof first?.path === "string")
251
+ return first.path;
252
+ }
253
+ return str("subagent_type") ?? str("skill");
152
254
  }
153
- function withStatsLock(action) {
154
- fs2.mkdirSync(lobstahHome(), { recursive: true });
155
- const lock = `${statsPath()}.lock`;
156
- const deadline = Date.now() + 1e4;
157
- for (; ; ) {
158
- try {
159
- fs2.mkdirSync(lock);
160
- break;
161
- } catch (err) {
162
- if (err.code !== "EEXIST")
163
- throw err;
164
- try {
165
- if (Date.now() - fs2.statSync(lock).mtimeMs > 12e4)
166
- fs2.rmdirSync(lock);
167
- } catch {
168
- }
169
- if (Date.now() >= deadline)
170
- throw new Error("stats store locked");
171
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
255
+ function toolSummary(name, target, root) {
256
+ const looksPath = target !== void 0 && (/[\\/]/.test(target) || isWindowsPath(target));
257
+ const shown = target === void 0 ? "" : looksPath ? relativeTarget(target, root) : target;
258
+ return redactSummary(shown ? `${name} ${shown}` : name);
259
+ }
260
+ function activityFromEvent(ev, root) {
261
+ const data = ev.data ?? {};
262
+ switch (ev.type) {
263
+ case "tool-start": {
264
+ const name = typeof data.name === "string" && data.name ? data.name : "tool";
265
+ const target = typeof data.target === "string" ? data.target : void 0;
266
+ return { kind: "tool", summary: toolSummary(name, target, root) };
172
267
  }
173
- }
174
- try {
175
- return action();
176
- } finally {
177
- fs2.rmdirSync(lock);
268
+ case "text":
269
+ return { kind: "message", summary: "writing a message" };
270
+ case "thinking":
271
+ return { kind: "thinking", summary: "thinking" };
272
+ case "runner": {
273
+ if (typeof data.on === "string")
274
+ return { kind: "waiting", summary: redactSummary(`waiting on ${data.on}`) };
275
+ if (typeof data.waiting === "string")
276
+ return { kind: "waiting", summary: redactSummary(`waiting for an answer (${data.waiting})`) };
277
+ if (data.holding === "background")
278
+ return { kind: "waiting", summary: "waiting on background work" };
279
+ return void 0;
280
+ }
281
+ default:
282
+ return void 0;
178
283
  }
179
284
  }
180
- function loadOrBackfill(now2) {
181
- let text;
285
+ function atomicWrite(file, content) {
286
+ const tmp = `${file}.tmp-${process.pid}`;
287
+ fs2.writeFileSync(tmp, content);
288
+ fs2.renameSync(tmp, file);
289
+ }
290
+ function writeActivity(id, lane, a) {
291
+ const safe = { at: a.at, kind: a.kind, summary: redactSummary(a.summary) };
292
+ atomicWrite(activityPath(id, lane), JSON.stringify(safe));
293
+ }
294
+ function readActivity(id, lane) {
182
295
  try {
183
- text = fs2.readFileSync(statsPath(), "utf8");
184
- } catch (err) {
185
- if (err.code !== "ENOENT")
186
- throw err;
296
+ const parsed = JSON.parse(fs2.readFileSync(activityPath(id, lane), "utf8"));
297
+ if (typeof parsed.at !== "string" || typeof parsed.summary !== "string")
298
+ return void 0;
299
+ if (!ACTIVITY_KINDS.includes(parsed.kind))
300
+ return void 0;
301
+ return { ...parsed, summary: redactSummary(parsed.summary) };
302
+ } catch {
303
+ return void 0;
187
304
  }
188
- const saved = text === void 0 ? void 0 : parseStore(text);
189
- if (saved)
190
- return saved;
191
- if (text !== void 0)
192
- fs2.renameSync(statsPath(), `${statsPath()}.unreadable-${now2}`);
193
- const store = backfillStats(now2);
194
- writeStore(store);
195
- return store;
196
305
  }
197
- function recordCatch(id, lane, now2 = Date.now()) {
198
- withStatsLock(() => {
199
- const store = loadOrBackfill(now2);
200
- if (store.counted.includes(id))
201
- return;
202
- const facts = readCatchFacts(laneDirs(lane).state, id);
203
- if (!facts.done)
204
- return;
205
- add(store, facts, now2);
206
- store.counted.push(id);
207
- writeStore(store);
208
- });
209
- }
210
-
211
- // packages/core/dist/attachments.js
212
- function attachmentBlock(attachments) {
213
- if (attachments.length === 0)
214
- return "";
215
- return [
216
- "--- ATTACHMENTS ---",
217
- ...attachments.map((a) => `${a.name} (${a.type}, ${a.bytes} bytes) at ${a.path}`),
218
- "Images and PDFs can be read directly with your file tools."
219
- ].join("\n");
220
- }
221
-
222
- // packages/core/dist/decisions.js
223
- var DECISION_DETAIL_MAX = 64 * 1024;
224
- var ANSWER_TYPES = {
225
- ".png": "image/png",
226
- ".jpg": "image/jpeg",
227
- ".jpeg": "image/jpeg",
228
- ".gif": "image/gif",
229
- ".webp": "image/webp",
230
- ".pdf": "application/pdf",
231
- ".txt": "text/plain",
232
- ".md": "text/markdown",
233
- ".csv": "text/csv",
234
- ".json": "application/json",
235
- ".log": "text/plain",
236
- ".diff": "text/plain",
237
- ".patch": "text/plain",
238
- ".yaml": "text/plain",
239
- ".yml": "text/plain",
240
- ".toml": "text/plain",
241
- ".zip": "application/zip"
242
- };
243
- var ANSWER_EXTENSIONS = Object.keys(ANSWER_TYPES);
244
-
245
- // packages/core/dist/config.js
246
- import * as fs3 from "node:fs";
247
- import * as os2 from "node:os";
248
- import * as path3 from "node:path";
249
-
250
- // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/date.js
251
- var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
252
- var TomlDate = class _TomlDate extends Date {
253
- #hasDate = false;
254
- #hasTime = false;
255
- #offset = null;
256
- constructor(date) {
257
- let hasDate = true;
258
- let hasTime = true;
259
- let offset = "Z";
260
- if (typeof date === "string") {
261
- let match = date.match(DATE_TIME_RE);
262
- if (match) {
263
- if (!match[1]) {
264
- hasDate = false;
265
- date = `0000-01-01T${date}`;
266
- }
267
- hasTime = !!match[2];
268
- hasTime && date[10] === " " && (date = date.replace(" ", "T"));
269
- if (match[2] && +match[2] > 23) {
270
- date = "";
271
- } else {
272
- offset = match[3] || null;
273
- date = date.toUpperCase();
274
- if (!offset && hasTime)
275
- date += "Z";
276
- }
277
- } else {
278
- date = "";
279
- }
280
- }
281
- super(date);
282
- if (!isNaN(this.getTime())) {
283
- this.#hasDate = hasDate;
284
- this.#hasTime = hasTime;
285
- this.#offset = offset;
286
- }
287
- }
288
- isDateTime() {
289
- return this.#hasDate && this.#hasTime;
290
- }
291
- isLocal() {
292
- return !this.#hasDate || !this.#hasTime || !this.#offset;
293
- }
294
- isDate() {
295
- return this.#hasDate && !this.#hasTime;
296
- }
297
- isTime() {
298
- return this.#hasTime && !this.#hasDate;
299
- }
300
- isValid() {
301
- return this.#hasDate || this.#hasTime;
306
+ var ActivityTracker = class {
307
+ write;
308
+ opts;
309
+ lastWriteAt = -Infinity;
310
+ lastKind;
311
+ pending;
312
+ timer;
313
+ throttleMs;
314
+ now;
315
+ constructor(write, opts = {}) {
316
+ this.write = write;
317
+ this.opts = opts;
318
+ this.throttleMs = opts.throttleMs ?? 1e4;
319
+ this.now = opts.now ?? Date.now;
302
320
  }
303
- toISOString() {
304
- let iso = super.toISOString();
305
- if (this.isDate())
306
- return iso.slice(0, 10);
307
- if (this.isTime())
308
- return iso.slice(11, 23);
309
- if (this.#offset === null)
310
- return iso.slice(0, -1);
311
- if (this.#offset === "Z")
312
- return iso;
313
- let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
314
- offset = this.#offset[0] === "-" ? offset : -offset;
315
- let offsetDate = new Date(this.getTime() - offset * 6e4);
316
- return offsetDate.toISOString().slice(0, -1) + this.#offset;
321
+ observe(ev) {
322
+ const derived = activityFromEvent(ev, this.opts.root);
323
+ if (derived)
324
+ this.offer({ at: new Date(this.now()).toISOString(), ...derived });
317
325
  }
318
- static wrapAsOffsetDateTime(jsDate, offset = "Z") {
319
- let date = new _TomlDate(jsDate);
320
- date.#offset = offset;
321
- return date;
326
+ offer(a) {
327
+ const t = this.now();
328
+ if (a.kind !== this.lastKind || t - this.lastWriteAt >= this.throttleMs) {
329
+ this.emit(a, t);
330
+ return;
331
+ }
332
+ this.pending = a;
333
+ if (!this.timer) {
334
+ this.timer = setTimeout(() => {
335
+ this.timer = void 0;
336
+ this.flush();
337
+ }, Math.max(0, this.throttleMs - (t - this.lastWriteAt)));
338
+ this.timer.unref?.();
339
+ }
322
340
  }
323
- static wrapAsLocalDateTime(jsDate) {
324
- let date = new _TomlDate(jsDate);
325
- date.#offset = null;
326
- return date;
341
+ /** Write a held record now. */
342
+ flush() {
343
+ if (!this.pending)
344
+ return;
345
+ const a = this.pending;
346
+ this.emit(a, this.now());
327
347
  }
328
- static wrapAsLocalDate(jsDate) {
329
- let date = new _TomlDate(jsDate);
330
- date.#hasTime = false;
331
- date.#offset = null;
332
- return date;
348
+ stop() {
349
+ if (this.timer)
350
+ clearTimeout(this.timer);
351
+ this.timer = void 0;
352
+ this.pending = void 0;
333
353
  }
334
- static wrapAsLocalTime(jsDate) {
335
- let date = new _TomlDate(jsDate);
336
- date.#hasDate = false;
337
- date.#offset = null;
338
- return date;
354
+ emit(a, t) {
355
+ this.pending = void 0;
356
+ if (this.timer) {
357
+ clearTimeout(this.timer);
358
+ this.timer = void 0;
359
+ }
360
+ this.lastWriteAt = t;
361
+ this.lastKind = a.kind;
362
+ try {
363
+ this.write(a);
364
+ } catch {
365
+ }
339
366
  }
340
367
  };
341
368
 
342
- // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/error.js
343
- function getLineColFromPtr(string, ptr) {
344
- let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
345
- return [lines.length, lines.pop().length + 1];
369
+ // packages/core/dist/status.js
370
+ function isVerb(v) {
371
+ return VERBS.includes(v);
346
372
  }
347
- function makeCodeBlock(string, line, column) {
348
- let lines = string.split(/\r\n|\n|\r/g);
349
- let codeblock = "";
350
- let numberLen = (Math.log10(line + 1) | 0) + 1;
351
- for (let i = line - 1; i <= line + 1; i++) {
352
- let l = lines[i - 1];
353
- if (!l)
354
- continue;
355
- codeblock += i.toString().padEnd(numberLen, " ");
356
- codeblock += ": ";
357
- codeblock += l;
358
- codeblock += "\n";
359
- if (i === line) {
360
- codeblock += " ".repeat(numberLen + column + 2);
361
- codeblock += "^\n";
373
+ function isWaitingOn(v) {
374
+ return WAITING_ON.includes(v);
375
+ }
376
+ var DURATION = /^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i;
377
+ var UNIT_MS = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
378
+ function parseUntil(v, now2 = Date.now()) {
379
+ const d = DURATION.exec(v.trim());
380
+ if (d)
381
+ return new Date(now2 + Number(d[1]) * UNIT_MS[d[2].toLowerCase()]).toISOString();
382
+ const t = Date.parse(v);
383
+ if (Number.isNaN(t))
384
+ throw new Error(`invalid --until "${v}" \u2014 an ISO time (2026-10-01T09:00Z) or a duration (30m, 4h, 2d)`);
385
+ return new Date(t).toISOString();
386
+ }
387
+ function waitingFields(verb, w, now2 = Date.now()) {
388
+ const out = {};
389
+ if ((w.waitingOn !== void 0 || w.link !== void 0) && !WAITING_ON_VERBS.includes(verb)) {
390
+ throw new Error(`--waiting-on and --link are valid only with ${WAITING_ON_VERBS.join(", ")} \u2014 not ${verb}`);
391
+ }
392
+ if (w.until !== void 0 && verb !== "paused")
393
+ throw new Error(`--until is valid only with paused \u2014 not ${verb}`);
394
+ if (w.waitingOn !== void 0) {
395
+ if (!isWaitingOn(w.waitingOn)) {
396
+ throw new Error(`invalid --waiting-on "${w.waitingOn}" \u2014 must be one of: ${WAITING_ON.join(", ")}`);
362
397
  }
398
+ out.waitingOn = w.waitingOn;
363
399
  }
364
- return codeblock;
365
- }
366
- var TomlError = class extends Error {
367
- line;
368
- column;
369
- codeblock;
370
- constructor(message, options) {
371
- const [line, column] = getLineColFromPtr(options.toml, options.ptr);
372
- const codeblock = makeCodeBlock(options.toml, line, column);
373
- super(`Invalid TOML document: ${message}
374
-
375
- ${codeblock}`, options);
376
- this.line = line;
377
- this.column = column;
378
- this.codeblock = codeblock;
400
+ if (w.link !== void 0) {
401
+ let url;
402
+ try {
403
+ url = new URL(w.link);
404
+ } catch {
405
+ url = void 0;
406
+ }
407
+ if (!url || url.protocol !== "http:" && url.protocol !== "https:") {
408
+ throw new Error(`invalid --link "${w.link}" \u2014 must be an http or https URL`);
409
+ }
410
+ out.link = url.href;
379
411
  }
380
- };
381
-
382
- // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/util.js
383
- function indexOfNewline(str, start = 0) {
384
- let idx = str.indexOf("\n", start);
385
- if (str.charCodeAt(idx - 1) === 13)
386
- idx--;
387
- return idx;
412
+ if (w.until !== void 0)
413
+ out.until = parseUntil(w.until, now2);
414
+ return out;
388
415
  }
389
- function skipComment(ctx) {
390
- for (; ctx.p < ctx.s.length; ctx.p++) {
391
- let c = ctx.s.charCodeAt(ctx.p);
392
- if (c === 10)
393
- break;
394
- if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) {
395
- ctx.p++;
396
- break;
397
- }
398
- if (c < 32 && c !== 9 || c === 127) {
399
- throw new TomlError("control characters are not allowed in comments", {
400
- toml: ctx.s,
401
- ptr: ctx.p
402
- });
416
+ function appendStatus(id, lane, verb, note, at, waiting, reported = false) {
417
+ if (!isVerb(verb)) {
418
+ throw new Error(`invalid status verb "${verb}" \u2014 must be one of: ${VERBS.join(", ")}`);
419
+ }
420
+ const extra = waiting ? waitingFields(verb, waiting) : {};
421
+ const entry = {
422
+ at: at ?? (/* @__PURE__ */ new Date()).toISOString(),
423
+ verb,
424
+ ...note ? { note } : {},
425
+ ...extra,
426
+ ...reported ? { reported: true } : {}
427
+ };
428
+ fs3.appendFileSync(statusPath(id, lane), `${JSON.stringify(entry)}
429
+ `);
430
+ if (verb === "done") {
431
+ try {
432
+ recordCatch(id, lane);
433
+ } catch {
403
434
  }
404
435
  }
436
+ return entry;
405
437
  }
406
- function skipVoid(ctx, banNewLines, banComments) {
407
- let c;
408
- while (1) {
409
- while ((c = ctx.s.charCodeAt(ctx.p)) === 32 || c === 9 || !banNewLines && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10))
410
- ctx.p++;
411
- if (banComments || c !== 35)
412
- break;
413
- skipComment(ctx);
414
- }
438
+ function readStatusLog(id, lane) {
439
+ const file = statusPath(id, lane);
440
+ if (!fs3.existsSync(file))
441
+ return [];
442
+ return fs3.readFileSync(file, "utf8").split("\n").filter(Boolean).flatMap((line) => {
443
+ try {
444
+ const parsed = JSON.parse(line);
445
+ return isVerb(parsed.verb) ? [parsed] : [];
446
+ } catch {
447
+ return [];
448
+ }
449
+ });
415
450
  }
416
- function skipUntil(ctx, sep, end) {
417
- let ptr = ctx.p;
418
- if (!end) {
419
- ptr = indexOfNewline(ctx.s, ptr);
420
- ctx.p = ptr < 0 ? ctx.s.length : ptr;
421
- return;
422
- }
423
- for (; ctx.p < ctx.s.length; ctx.p++) {
424
- let c = ctx.s.charCodeAt(ctx.p);
425
- if (c === 35) {
426
- skipComment(ctx);
427
- } else if (c === end || c === sep) {
428
- return;
451
+ function pausedWaiting(entry) {
452
+ return entry?.verb === "paused" && entry.waitingOn !== void 0;
453
+ }
454
+
455
+ // packages/core/dist/queue.js
456
+ function storedDescriptor(id, lane) {
457
+ const dirs = laneDirs(lane);
458
+ for (const file of [
459
+ path3.join(dirs.queue, `${id}.json`),
460
+ path3.join(dirs.active, id, "descriptor.json"),
461
+ path3.join(dirs.done, id, "descriptor.json")
462
+ ]) {
463
+ try {
464
+ return JSON.parse(fs4.readFileSync(file, "utf8"));
465
+ } catch {
429
466
  }
430
467
  }
431
- throw new TomlError("cannot find end of structure", {
432
- toml: ctx.s,
433
- ptr
434
- });
468
+ return void 0;
469
+ }
470
+ function activeIds(lane) {
471
+ return readDirIfPresent(laneDirs(lane).active).filter((f) => !f.startsWith("."));
472
+ }
473
+ function complete(id, lane) {
474
+ const dirs = laneDirs(lane);
475
+ fs4.renameSync(path3.join(dirs.active, id), path3.join(dirs.done, id));
476
+ }
477
+ function cancelRequested(id, lane) {
478
+ return fs4.existsSync(path3.join(laneDirs(lane).active, id, "cancel"));
435
479
  }
436
480
 
437
- // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/primitive.js
438
- var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
439
- var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
440
- var LEADING_ZERO = /^[+-]?0[0-9_]/;
441
- function parseString(ctx) {
442
- let start = ctx.p;
443
- let c = ctx.s.charCodeAt(ctx.p++);
444
- let first = c;
445
- let isLiteral = c === 39;
446
- let isMultiline = c === ctx.s.charCodeAt(ctx.p) && c === ctx.s.charCodeAt(ctx.p + 1);
447
- if (isMultiline) {
448
- if ((c = ctx.s.charCodeAt(ctx.p += 2)) === 10)
449
- ctx.p++;
450
- else if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)
451
- ctx.p += 2;
481
+ // packages/core/dist/evidence.js
482
+ import * as fs5 from "node:fs";
483
+ function readEvidence(id, lane) {
484
+ try {
485
+ return JSON.parse(fs5.readFileSync(evidencePath(id, lane), "utf8"));
486
+ } catch {
487
+ return {};
452
488
  }
453
- let parsed = "";
454
- let sliceStart = ctx.p;
455
- let state = 0;
456
- for (; ctx.p < ctx.s.length; ctx.p++) {
457
- c = ctx.s.charCodeAt(ctx.p);
458
- if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) {
459
- state = state && 3;
460
- } else if (c < 32 && c !== 9 || c === 127) {
461
- throw new TomlError("control characters are not allowed in strings", {
462
- toml: ctx.s,
463
- ptr: ctx.p
464
- });
465
- } else if ((!state || state === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) {
466
- if (isMultiline) {
467
- if (ctx.s.charCodeAt(ctx.p + 3) === first)
468
- ctx.p++;
469
- if (ctx.s.charCodeAt(ctx.p + 3) === first)
470
- ctx.p++;
471
- }
472
- if (!state)
473
- parsed += ctx.s.slice(sliceStart, ctx.p);
474
- ctx.p += isMultiline ? 3 : 1;
475
- return parsed;
476
- } else if (!state) {
477
- if (!isLiteral && c === 92) {
478
- parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p);
479
- state = 1;
480
- }
481
- } else if (state === 1) {
482
- if (c === 120 || c === 117 || c === 85) {
483
- let value = 0;
484
- let len = c === 120 ? 2 : c === 117 ? 4 : 8;
485
- for (let j = 0; j < len; j++, ctx.p++) {
486
- let hex = ctx.s.charCodeAt(ctx.p + 1);
487
- let digit = (
488
- /* 0-9 */
489
- hex >= 48 && hex <= 57 ? hex - 48 : (
490
- /* A-F */
491
- hex >= 65 && hex <= 70 ? hex - 65 + 10 : (
492
- /* a-f */
493
- hex >= 97 && hex <= 102 ? hex - 97 + 10 : -1
494
- )
495
- )
496
- );
497
- if (digit < 0)
498
- throw new TomlError("invalid non-hex character in unicode escape", { toml: ctx.s, ptr: ctx.p + 1 });
499
- value = value << 4 | digit;
500
- }
501
- if (value < 0 || value > 1114111 || value >= 55296 && value <= 57343) {
502
- throw new TomlError("invalid unicode escape", { toml: ctx.s, ptr: ctx.p });
503
- }
504
- parsed += String.fromCodePoint(value);
505
- sliceStart = ctx.p + 1;
506
- state = 0;
507
- } else if (c === 32 || c === 9) {
508
- state = 2;
509
- } else {
510
- if (c === 98)
511
- parsed += "\b";
512
- else if (c === 116)
513
- parsed += " ";
514
- else if (c === 110)
515
- parsed += "\n";
516
- else if (c === 102)
517
- parsed += "\f";
518
- else if (c === 114)
519
- parsed += "\r";
520
- else if (c === 101)
521
- parsed += "\x1B";
522
- else if (c === 34)
523
- parsed += '"';
524
- else if (c === 92)
525
- parsed += "\\";
526
- else
527
- throw new TomlError("unrecognized escape sequence", { toml: ctx.s, ptr: ctx.p });
528
- sliceStart = ctx.p + 1;
529
- state = 0;
530
- }
531
- } else if (c !== 32 && c !== 9) {
532
- if (state === 2) {
533
- throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
534
- toml: ctx.s,
535
- ptr: sliceStart
536
- });
489
+ }
490
+ function mergeEvidence(id, lane, patch) {
491
+ const file = evidencePath(id, lane);
492
+ const tmp = `${file}.tmp-${process.pid}`;
493
+ fs5.writeFileSync(tmp, JSON.stringify({ ...readEvidence(id, lane), ...patch }, null, 2));
494
+ fs5.renameSync(tmp, file);
495
+ }
496
+
497
+ // packages/core/dist/pushes.js
498
+ import * as fs6 from "node:fs";
499
+ import * as path4 from "node:path";
500
+ var MAX_PUSHES = 20;
501
+ var PUSH_HEAD = "HEAD";
502
+ var VALUE_OPTIONS = /* @__PURE__ */ new Set(["-o", "--push-option", "--repo", "--receive-pack", "--exec"]);
503
+ function words(segment) {
504
+ const out = [];
505
+ const re = /"((?:[^"\\]|\\.)*)"|'([^']*)'|(\S+)/g;
506
+ for (let m = re.exec(segment); m; m = re.exec(segment))
507
+ out.push(m[1] ?? m[2] ?? m[3] ?? "");
508
+ return out;
509
+ }
510
+ function branchOf(ref) {
511
+ const name = ref.replace(/^\+/, "").replace(/^refs\/heads\//, "");
512
+ return name === "@" || name === "" ? PUSH_HEAD : name;
513
+ }
514
+ function gitPushTargets(command) {
515
+ if (command === void 0)
516
+ return void 0;
517
+ const text = typeof command === "string" ? command : command.join(" ");
518
+ if (!/\bgit\b[^\n]*\bpush\b/.test(text))
519
+ return void 0;
520
+ const targets = [];
521
+ let found = false;
522
+ for (const segment of text.split(/&&|\|\||[;|\n]/)) {
523
+ const w = words(segment);
524
+ let i = w.findIndex((x) => x === "git" || /[\\/]git(?:\.exe)?$/.test(x));
525
+ if (i < 0)
526
+ continue;
527
+ for (i++; i < w.length && w[i].startsWith("-"); i++)
528
+ if (w[i] === "-C" || w[i] === "-c")
529
+ i++;
530
+ if (w[i] !== "push")
531
+ continue;
532
+ found = true;
533
+ const positional = [];
534
+ for (let j = i + 1; j < w.length; j++) {
535
+ const arg = w[j];
536
+ if (arg === "--")
537
+ continue;
538
+ if (arg.startsWith("-")) {
539
+ if (VALUE_OPTIONS.has(arg))
540
+ j++;
541
+ continue;
537
542
  }
538
- state = !isLiteral && c === 92 ? 1 : 0;
539
- sliceStart = ctx.p;
543
+ positional.push(arg);
540
544
  }
545
+ const refspecs = positional.slice(1);
546
+ if (refspecs.length === 0)
547
+ targets.push(PUSH_HEAD);
548
+ for (const spec of refspecs)
549
+ targets.push(branchOf(spec.includes(":") ? spec.slice(spec.indexOf(":") + 1) : spec));
541
550
  }
542
- throw new TomlError("unfinished string", { toml: ctx.s, ptr: start });
551
+ return found ? [...new Set(targets)] : void 0;
543
552
  }
544
- function sliceAndTrimEndOf(ctx, start, end) {
545
- let value = ctx.s.slice(start, end);
546
- let commentIdx = value.indexOf("#");
547
- if (commentIdx > 0) {
548
- skipComment({ s: value, p: commentIdx, d: 0 });
549
- value = value.slice(0, commentIdx);
553
+ function gitDirAt(dir) {
554
+ let at = path4.resolve(dir);
555
+ for (; ; ) {
556
+ const dotGit = path4.join(at, ".git");
557
+ try {
558
+ const stat = fs6.statSync(dotGit);
559
+ if (stat.isDirectory())
560
+ return dotGit;
561
+ const m = /^gitdir:\s*(.+)$/m.exec(fs6.readFileSync(dotGit, "utf8"));
562
+ return m ? path4.resolve(at, m[1].trim()) : void 0;
563
+ } catch {
564
+ }
565
+ const up = path4.dirname(at);
566
+ if (up === at)
567
+ return void 0;
568
+ at = up;
550
569
  }
551
- return value.trimEnd();
552
570
  }
553
- function parseValue(ctx, integersAsBigInt, end) {
554
- let ptr = ctx.p;
555
- let err = { toml: ctx.s, ptr };
556
- skipUntil(ctx, 44, end);
557
- let value = sliceAndTrimEndOf(ctx, ptr, ctx.p);
558
- if (!value)
559
- throw new TomlError("incomplete declaration: value expected", err);
560
- if (value === "-inf")
561
- return -Infinity;
562
- if (value === "inf" || value === "+inf")
563
- return Infinity;
564
- if (value === "nan" || value === "+nan" || value === "-nan")
565
- return NaN;
566
- if (value === "-0")
567
- return integersAsBigInt ? 0n : 0;
568
- let isInt = INT_REGEX.test(value);
569
- if (isInt || FLOAT_REGEX.test(value)) {
570
- if (LEADING_ZERO.test(value)) {
571
- throw new TomlError("leading zeroes are not allowed", err);
572
- }
573
- value = value.replace(/_/g, "");
574
- let numeric = +value;
575
- if (isNaN(numeric)) {
576
- throw new TomlError("invalid number", err);
577
- }
578
- if (isInt) {
579
- if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
580
- throw new TomlError("integer value cannot be represented losslessly", err);
571
+ function currentBranchAt(dir) {
572
+ const gitDir = gitDirAt(dir);
573
+ if (!gitDir)
574
+ return void 0;
575
+ try {
576
+ const m = /^ref:\s*refs\/heads\/(.+)$/m.exec(fs6.readFileSync(path4.join(gitDir, "HEAD"), "utf8"));
577
+ return m ? m[1].trim() : void 0;
578
+ } catch {
579
+ return void 0;
580
+ }
581
+ }
582
+ function resolvePushTargets(targets, cwd) {
583
+ const head = cwd ? currentBranchAt(cwd) : void 0;
584
+ return [...new Set(targets.flatMap((t) => t === PUSH_HEAD ? head ? [head] : [] : [t]))];
585
+ }
586
+ function recordPush(id, lane, branches, at = (/* @__PURE__ */ new Date()).toISOString()) {
587
+ if (branches.length === 0)
588
+ return;
589
+ const kept = (readEvidence(id, lane).pushes ?? []).filter((p) => !branches.includes(p.branch));
590
+ const pushes = [...kept, ...branches.map((branch) => ({ branch, at }))].slice(-MAX_PUSHES);
591
+ mergeEvidence(id, lane, { pushes });
592
+ }
593
+
594
+ // packages/core/dist/config.js
595
+ import * as fs7 from "node:fs";
596
+ import * as os2 from "node:os";
597
+ import * as path5 from "node:path";
598
+
599
+ // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/date.js
600
+ var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
601
+ var TomlDate = class _TomlDate extends Date {
602
+ #hasDate = false;
603
+ #hasTime = false;
604
+ #offset = null;
605
+ constructor(date) {
606
+ let hasDate = true;
607
+ let hasTime = true;
608
+ let offset = "Z";
609
+ if (typeof date === "string") {
610
+ let match = date.match(DATE_TIME_RE);
611
+ if (match) {
612
+ if (!match[1]) {
613
+ hasDate = false;
614
+ date = `0000-01-01T${date}`;
615
+ }
616
+ hasTime = !!match[2];
617
+ hasTime && date[10] === " " && (date = date.replace(" ", "T"));
618
+ if (match[2] && +match[2] > 23) {
619
+ date = "";
620
+ } else {
621
+ offset = match[3] || null;
622
+ date = date.toUpperCase();
623
+ if (!offset && hasTime)
624
+ date += "Z";
625
+ }
626
+ } else {
627
+ date = "";
581
628
  }
582
- if (isInt || integersAsBigInt === true)
583
- numeric = BigInt(value);
584
629
  }
585
- return numeric;
586
- }
587
- const date = new TomlDate(value);
588
- if (!date.isValid())
589
- throw new TomlError("invalid value", err);
590
- return date;
591
- }
592
-
593
- // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/extract.js
594
- function extractValue(ctx, end, integersAsBigInt) {
595
- let ptr = ctx.p;
596
- let c = ctx.s.charCodeAt(ptr);
597
- if (c === 91 || c === 123) {
598
- if (!ctx.d--) {
599
- throw new TomlError("document contains excessively nested structures. aborting.", {
600
- toml: ctx.s,
601
- ptr
602
- });
630
+ super(date);
631
+ if (!isNaN(this.getTime())) {
632
+ this.#hasDate = hasDate;
633
+ this.#hasTime = hasTime;
634
+ this.#offset = offset;
603
635
  }
604
- let value = c === 91 ? parseArray(ctx, integersAsBigInt) : parseInlineTable(ctx, integersAsBigInt);
605
- ctx.d++;
606
- return value;
607
636
  }
608
- if (c === 34 || c === 39) {
609
- return parseString(ctx);
637
+ isDateTime() {
638
+ return this.#hasDate && this.#hasTime;
610
639
  }
611
- if (c === 116) {
612
- if (ctx.s.charCodeAt(++ctx.p) !== 114 || ctx.s.charCodeAt(++ctx.p) !== 117 || ctx.s.charCodeAt(++ctx.p) !== 101)
613
- throw new TomlError("invalid value", { toml: ctx.s, ptr });
614
- ctx.p++;
615
- return true;
640
+ isLocal() {
641
+ return !this.#hasDate || !this.#hasTime || !this.#offset;
616
642
  }
617
- if (c === 102) {
618
- if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 108 || ctx.s.charCodeAt(++ctx.p) !== 115 || ctx.s.charCodeAt(++ctx.p) !== 101)
619
- throw new TomlError("invalid value", { toml: ctx.s, ptr });
620
- ctx.p++;
621
- return false;
643
+ isDate() {
644
+ return this.#hasDate && !this.#hasTime;
622
645
  }
623
- return parseValue(ctx, integersAsBigInt, end);
646
+ isTime() {
647
+ return this.#hasTime && !this.#hasDate;
648
+ }
649
+ isValid() {
650
+ return this.#hasDate || this.#hasTime;
651
+ }
652
+ toISOString() {
653
+ let iso = super.toISOString();
654
+ if (this.isDate())
655
+ return iso.slice(0, 10);
656
+ if (this.isTime())
657
+ return iso.slice(11, 23);
658
+ if (this.#offset === null)
659
+ return iso.slice(0, -1);
660
+ if (this.#offset === "Z")
661
+ return iso;
662
+ let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
663
+ offset = this.#offset[0] === "-" ? offset : -offset;
664
+ let offsetDate = new Date(this.getTime() - offset * 6e4);
665
+ return offsetDate.toISOString().slice(0, -1) + this.#offset;
666
+ }
667
+ static wrapAsOffsetDateTime(jsDate, offset = "Z") {
668
+ let date = new _TomlDate(jsDate);
669
+ date.#offset = offset;
670
+ return date;
671
+ }
672
+ static wrapAsLocalDateTime(jsDate) {
673
+ let date = new _TomlDate(jsDate);
674
+ date.#offset = null;
675
+ return date;
676
+ }
677
+ static wrapAsLocalDate(jsDate) {
678
+ let date = new _TomlDate(jsDate);
679
+ date.#hasTime = false;
680
+ date.#offset = null;
681
+ return date;
682
+ }
683
+ static wrapAsLocalTime(jsDate) {
684
+ let date = new _TomlDate(jsDate);
685
+ date.#hasDate = false;
686
+ date.#offset = null;
687
+ return date;
688
+ }
689
+ };
690
+
691
+ // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/error.js
692
+ function getLineColFromPtr(string, ptr) {
693
+ let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
694
+ return [lines.length, lines.pop().length + 1];
695
+ }
696
+ function makeCodeBlock(string, line, column) {
697
+ let lines = string.split(/\r\n|\n|\r/g);
698
+ let codeblock = "";
699
+ let numberLen = (Math.log10(line + 1) | 0) + 1;
700
+ for (let i = line - 1; i <= line + 1; i++) {
701
+ let l = lines[i - 1];
702
+ if (!l)
703
+ continue;
704
+ codeblock += i.toString().padEnd(numberLen, " ");
705
+ codeblock += ": ";
706
+ codeblock += l;
707
+ codeblock += "\n";
708
+ if (i === line) {
709
+ codeblock += " ".repeat(numberLen + column + 2);
710
+ codeblock += "^\n";
711
+ }
712
+ }
713
+ return codeblock;
624
714
  }
715
+ var TomlError = class extends Error {
716
+ line;
717
+ column;
718
+ codeblock;
719
+ constructor(message, options) {
720
+ const [line, column] = getLineColFromPtr(options.toml, options.ptr);
721
+ const codeblock = makeCodeBlock(options.toml, line, column);
722
+ super(`Invalid TOML document: ${message}
625
723
 
626
- // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/struct.js
627
- var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
628
- function parseKey(ctx, end = "=") {
629
- let start = ctx.p;
630
- let dot = start - 1;
631
- let parsed = [];
632
- let endPtr = ctx.s.indexOf(end, start);
633
- if (endPtr < 0) {
634
- throw new TomlError("incomplete key-value: cannot find end of key", {
635
- toml: ctx.s,
636
- ptr: start
637
- });
724
+ ${codeblock}`, options);
725
+ this.line = line;
726
+ this.column = column;
727
+ this.codeblock = codeblock;
638
728
  }
639
- do {
640
- let c = ctx.s.charCodeAt(ctx.p = ++dot);
641
- if (c !== 32 && c !== 9) {
642
- if (c === 34 || c === 39) {
643
- if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2)) {
644
- throw new TomlError("multiline strings are not allowed in keys", {
645
- toml: ctx.s,
646
- ptr: ctx.p
647
- });
648
- }
649
- let part = parseString(ctx);
650
- dot = ctx.s.indexOf(".", ctx.p);
651
- let strEnd = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot);
652
- let newLine = indexOfNewline(strEnd);
653
- if (newLine > -1) {
654
- throw new TomlError("newlines are not allowed in keys", {
655
- toml: ctx.s,
656
- ptr: newLine
657
- });
658
- }
659
- if (strEnd.trimStart()) {
660
- throw new TomlError("found extra tokens after the string part", {
661
- toml: ctx.s,
662
- ptr: ctx.p
663
- });
664
- }
665
- if (endPtr < ctx.p) {
666
- endPtr = ctx.s.indexOf(end, ctx.p);
667
- if (endPtr < 0) {
668
- throw new TomlError("incomplete key-value: cannot find end of key", {
669
- toml: ctx.s,
670
- ptr: start
671
- });
672
- }
673
- }
674
- parsed.push(part);
675
- } else {
676
- dot = ctx.s.indexOf(".", ctx.p);
677
- let part = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot);
678
- if (!KEY_PART_RE.test(part)) {
679
- throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
680
- toml: ctx.s,
681
- ptr: ctx.p
682
- });
683
- }
684
- parsed.push(part.trimEnd());
685
- }
686
- }
687
- } while (dot + 1 && dot < endPtr);
688
- ctx.p = endPtr + 1;
689
- skipVoid(ctx, true, true);
690
- return parsed;
729
+ };
730
+
731
+ // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/util.js
732
+ function indexOfNewline(str, start = 0) {
733
+ let idx = str.indexOf("\n", start);
734
+ if (str.charCodeAt(idx - 1) === 13)
735
+ idx--;
736
+ return idx;
691
737
  }
692
- function parseInlineTable(ctx, integersAsBigInt) {
693
- let res = {};
694
- let seen = /* @__PURE__ */ new Set();
695
- let c;
696
- ctx.p++;
697
- while (ctx.p < ctx.s.length) {
698
- skipVoid(ctx);
699
- if ((c = ctx.s.charCodeAt(ctx.p)) === 125) {
738
+ function skipComment(ctx) {
739
+ for (; ctx.p < ctx.s.length; ctx.p++) {
740
+ let c = ctx.s.charCodeAt(ctx.p);
741
+ if (c === 10)
742
+ break;
743
+ if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) {
700
744
  ctx.p++;
701
- return res;
702
- }
703
- let k;
704
- let t = res;
705
- let hasOwn = false;
706
- let p = ctx.p;
707
- let key = parseKey(ctx);
708
- for (let i = 0; i < key.length; i++) {
709
- if (i)
710
- t = hasOwn ? t[k] : t[k] = {};
711
- k = key[i];
712
- if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
713
- throw new TomlError("trying to redefine an already defined value", {
714
- toml: ctx.s,
715
- ptr: p
716
- });
717
- }
718
- if (!hasOwn && k === "__proto__") {
719
- Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
720
- }
745
+ break;
721
746
  }
722
- if (hasOwn) {
723
- throw new TomlError("trying to redefine an already defined value", {
747
+ if (c < 32 && c !== 9 || c === 127) {
748
+ throw new TomlError("control characters are not allowed in comments", {
724
749
  toml: ctx.s,
725
750
  ptr: ctx.p
726
751
  });
727
752
  }
728
- let value = extractValue(ctx, 125, integersAsBigInt);
729
- seen.add(t[k] = value);
730
- skipVoid(ctx);
731
- if ((c = ctx.s.charCodeAt(ctx.p++)) === 125) {
732
- return res;
733
- }
734
- if (c !== 44) {
735
- throw new TomlError("expected comma or end of structure", { toml: ctx.s, ptr: ctx.p - 1 });
736
- }
737
753
  }
738
- throw new TomlError("unfinished table encountered", {
739
- toml: ctx.s,
740
- ptr: ctx.p
741
- });
742
754
  }
743
- function parseArray(ctx, integersAsBigInt) {
744
- let res = [];
755
+ function skipVoid(ctx, banNewLines, banComments) {
745
756
  let c;
746
- ctx.p++;
747
- while (ctx.p < ctx.s.length) {
748
- skipVoid(ctx);
749
- if ((c = ctx.s.charCodeAt(ctx.p)) === 93) {
757
+ while (1) {
758
+ while ((c = ctx.s.charCodeAt(ctx.p)) === 32 || c === 9 || !banNewLines && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10))
750
759
  ctx.p++;
751
- return res;
752
- }
753
- res.push(extractValue(ctx, 93, integersAsBigInt));
754
- skipVoid(ctx);
755
- if ((c = ctx.s.charCodeAt(ctx.p++)) === 93) {
756
- return res;
757
- }
758
- if (c !== 44) {
759
- throw new TomlError("expected comma or end of structure", { toml: ctx.s, ptr: ctx.p - 1 });
760
+ if (banComments || c !== 35)
761
+ break;
762
+ skipComment(ctx);
763
+ }
764
+ }
765
+ function skipUntil(ctx, sep, end) {
766
+ let ptr = ctx.p;
767
+ if (!end) {
768
+ ptr = indexOfNewline(ctx.s, ptr);
769
+ ctx.p = ptr < 0 ? ctx.s.length : ptr;
770
+ return;
771
+ }
772
+ for (; ctx.p < ctx.s.length; ctx.p++) {
773
+ let c = ctx.s.charCodeAt(ctx.p);
774
+ if (c === 35) {
775
+ skipComment(ctx);
776
+ } else if (c === end || c === sep) {
777
+ return;
760
778
  }
761
779
  }
762
- throw new TomlError("unfinished array encountered", {
780
+ throw new TomlError("cannot find end of structure", {
763
781
  toml: ctx.s,
764
- ptr: ctx.p
782
+ ptr
765
783
  });
766
784
  }
767
785
 
768
- // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/parse.js
769
- function peekTable(key, table, meta, type) {
770
- let t = table;
771
- let m = meta;
772
- let k;
773
- let hasOwn = false;
774
- let state;
775
- for (let i = 0; i < key.length; i++) {
776
- if (i) {
777
- t = hasOwn ? t[k] : t[k] = {};
778
- m = (state = m[k]).c;
779
- if (type === 0 && (state.t === 1 || state.t === 2)) {
780
- return null;
781
- }
782
- if (state.t === 2) {
783
- let l = t.length - 1;
784
- t = t[l];
785
- m = m[l].c;
786
+ // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/primitive.js
787
+ var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
788
+ var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
789
+ var LEADING_ZERO = /^[+-]?0[0-9_]/;
790
+ function parseString(ctx) {
791
+ let start = ctx.p;
792
+ let c = ctx.s.charCodeAt(ctx.p++);
793
+ let first = c;
794
+ let isLiteral = c === 39;
795
+ let isMultiline = c === ctx.s.charCodeAt(ctx.p) && c === ctx.s.charCodeAt(ctx.p + 1);
796
+ if (isMultiline) {
797
+ if ((c = ctx.s.charCodeAt(ctx.p += 2)) === 10)
798
+ ctx.p++;
799
+ else if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)
800
+ ctx.p += 2;
801
+ }
802
+ let parsed = "";
803
+ let sliceStart = ctx.p;
804
+ let state = 0;
805
+ for (; ctx.p < ctx.s.length; ctx.p++) {
806
+ c = ctx.s.charCodeAt(ctx.p);
807
+ if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) {
808
+ state = state && 3;
809
+ } else if (c < 32 && c !== 9 || c === 127) {
810
+ throw new TomlError("control characters are not allowed in strings", {
811
+ toml: ctx.s,
812
+ ptr: ctx.p
813
+ });
814
+ } else if ((!state || state === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) {
815
+ if (isMultiline) {
816
+ if (ctx.s.charCodeAt(ctx.p + 3) === first)
817
+ ctx.p++;
818
+ if (ctx.s.charCodeAt(ctx.p + 3) === first)
819
+ ctx.p++;
786
820
  }
787
- }
788
- k = key[i];
789
- if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
790
- return null;
791
- }
792
- if (!hasOwn) {
793
- if (k === "__proto__") {
794
- Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
795
- Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
821
+ if (!state)
822
+ parsed += ctx.s.slice(sliceStart, ctx.p);
823
+ ctx.p += isMultiline ? 3 : 1;
824
+ return parsed;
825
+ } else if (!state) {
826
+ if (!isLiteral && c === 92) {
827
+ parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p);
828
+ state = 1;
796
829
  }
797
- m[k] = {
798
- t: i < key.length - 1 && type === 2 ? 3 : type,
799
- d: false,
800
- i: 0,
801
- c: {}
802
- };
803
- }
804
- }
805
- state = m[k];
806
- if (state.t !== type && !(type === 1 && state.t === 3)) {
807
- return null;
808
- }
809
- if (type === 2) {
810
- if (!state.d) {
811
- state.d = true;
812
- t[k] = [];
813
- }
814
- t[k].push(t = {});
815
- state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
816
- }
817
- if (state.d) {
818
- return null;
819
- }
820
- state.d = true;
821
- if (type === 1) {
822
- t = hasOwn ? t[k] : t[k] = {};
823
- } else if (type === 0 && hasOwn) {
824
- return null;
825
- }
826
- return [k, t, state.c];
827
- }
828
- function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
829
- let ctx = { s: toml, p: 0, d: maxDepth };
830
- let res = {};
831
- let meta = {};
832
- let tmp;
833
- let tbl = res;
834
- let m = meta;
835
- skipVoid(ctx);
836
- while (ctx.p < toml.length) {
837
- if (toml.charCodeAt(ctx.p) === 91) {
838
- let isTableArray = toml.charCodeAt(++ctx.p) === 91;
839
- tmp = ctx.p += +isTableArray;
840
- let k = parseKey(ctx, "]");
841
- if (isTableArray) {
842
- if (toml.charCodeAt(ctx.p - 1) !== 93) {
843
- throw new TomlError("expected end of table declaration", {
844
- toml,
845
- ptr: ctx.p - 1
846
- });
830
+ } else if (state === 1) {
831
+ if (c === 120 || c === 117 || c === 85) {
832
+ let value = 0;
833
+ let len = c === 120 ? 2 : c === 117 ? 4 : 8;
834
+ for (let j = 0; j < len; j++, ctx.p++) {
835
+ let hex = ctx.s.charCodeAt(ctx.p + 1);
836
+ let digit = (
837
+ /* 0-9 */
838
+ hex >= 48 && hex <= 57 ? hex - 48 : (
839
+ /* A-F */
840
+ hex >= 65 && hex <= 70 ? hex - 65 + 10 : (
841
+ /* a-f */
842
+ hex >= 97 && hex <= 102 ? hex - 97 + 10 : -1
843
+ )
844
+ )
845
+ );
846
+ if (digit < 0)
847
+ throw new TomlError("invalid non-hex character in unicode escape", { toml: ctx.s, ptr: ctx.p + 1 });
848
+ value = value << 4 | digit;
847
849
  }
848
- ctx.p++;
849
- }
850
- let p = peekTable(
851
- k,
852
- res,
853
- meta,
854
- isTableArray ? 2 : 1
855
- /* Type.EXPLICIT */
856
- );
857
- if (!p) {
858
- throw new TomlError("trying to redefine an already defined table or value", {
859
- toml,
860
- ptr: tmp
861
- });
850
+ if (value < 0 || value > 1114111 || value >= 55296 && value <= 57343) {
851
+ throw new TomlError("invalid unicode escape", { toml: ctx.s, ptr: ctx.p });
852
+ }
853
+ parsed += String.fromCodePoint(value);
854
+ sliceStart = ctx.p + 1;
855
+ state = 0;
856
+ } else if (c === 32 || c === 9) {
857
+ state = 2;
858
+ } else {
859
+ if (c === 98)
860
+ parsed += "\b";
861
+ else if (c === 116)
862
+ parsed += " ";
863
+ else if (c === 110)
864
+ parsed += "\n";
865
+ else if (c === 102)
866
+ parsed += "\f";
867
+ else if (c === 114)
868
+ parsed += "\r";
869
+ else if (c === 101)
870
+ parsed += "\x1B";
871
+ else if (c === 34)
872
+ parsed += '"';
873
+ else if (c === 92)
874
+ parsed += "\\";
875
+ else
876
+ throw new TomlError("unrecognized escape sequence", { toml: ctx.s, ptr: ctx.p });
877
+ sliceStart = ctx.p + 1;
878
+ state = 0;
862
879
  }
863
- m = p[2];
864
- tbl = p[1];
865
- } else {
866
- tmp = ctx.p;
867
- let k = parseKey(ctx);
868
- let p = peekTable(
869
- k,
870
- tbl,
871
- m,
872
- 0
873
- /* Type.DOTTED */
874
- );
875
- if (!p) {
876
- throw new TomlError("trying to redefine an already defined table or value", {
877
- toml,
878
- ptr: tmp
880
+ } else if (c !== 32 && c !== 9) {
881
+ if (state === 2) {
882
+ throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
883
+ toml: ctx.s,
884
+ ptr: sliceStart
879
885
  });
880
886
  }
881
- p[1][p[0]] = extractValue(ctx, void 0, integersAsBigInt);
882
- }
883
- skipVoid(ctx, true);
884
- if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 10 && tmp !== 13) {
885
- throw new TomlError("each key-value declaration must be followed by an end-of-line", {
886
- toml,
887
- ptr: ctx.p
888
- });
887
+ state = !isLiteral && c === 92 ? 1 : 0;
888
+ sliceStart = ctx.p;
889
889
  }
890
- skipVoid(ctx);
891
890
  }
892
- return res;
891
+ throw new TomlError("unfinished string", { toml: ctx.s, ptr: start });
893
892
  }
894
-
895
- // packages/core/dist/config.js
896
- var BRIEF_KINDS = ["conflict", "checks", "review", "ciFix", "rebase"];
897
- function parseBriefHooks(raw) {
898
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
899
- return void 0;
900
- const out = {};
901
- for (const kind of [...BRIEF_KINDS, "all"]) {
902
- const text = raw[kind];
903
- if (typeof text === "string" && text.trim())
904
- out[kind] = text.trim();
893
+ function sliceAndTrimEndOf(ctx, start, end) {
894
+ let value = ctx.s.slice(start, end);
895
+ let commentIdx = value.indexOf("#");
896
+ if (commentIdx > 0) {
897
+ skipComment({ s: value, p: commentIdx, d: 0 });
898
+ value = value.slice(0, commentIdx);
905
899
  }
906
- return Object.keys(out).length ? out : void 0;
900
+ return value.trimEnd();
907
901
  }
908
- var ATTENTION_KINDS = ["question", "decision", "landed", "pr:draft", "pr:review", "pr:checks", "pr:conflict", "pr:ready", "report"];
909
- var DEFAULT_ATTENTION_KINDS = ["question", "decision", "pr:ready", "pr:review", "pr:conflict", "pr:checks"];
910
- function parseAttentionKinds(raw) {
911
- if (raw === void 0)
912
- return [...DEFAULT_ATTENTION_KINDS];
913
- if (!Array.isArray(raw)) {
914
- throw new Error(`attentionKinds must be an array of kinds (${ATTENTION_KINDS.join(", ")}) in ${configPath()}`);
915
- }
916
- for (const k of raw) {
917
- if (!ATTENTION_KINDS.includes(k)) {
918
- throw new Error(`attentionKinds: unknown kind "${String(k)}" in ${configPath()} \u2014 valid kinds: ${ATTENTION_KINDS.join(", ")}`);
902
+ function parseValue(ctx, integersAsBigInt, end) {
903
+ let ptr = ctx.p;
904
+ let err = { toml: ctx.s, ptr };
905
+ skipUntil(ctx, 44, end);
906
+ let value = sliceAndTrimEndOf(ctx, ptr, ctx.p);
907
+ if (!value)
908
+ throw new TomlError("incomplete declaration: value expected", err);
909
+ if (value === "-inf")
910
+ return -Infinity;
911
+ if (value === "inf" || value === "+inf")
912
+ return Infinity;
913
+ if (value === "nan" || value === "+nan" || value === "-nan")
914
+ return NaN;
915
+ if (value === "-0")
916
+ return integersAsBigInt ? 0n : 0;
917
+ let isInt = INT_REGEX.test(value);
918
+ if (isInt || FLOAT_REGEX.test(value)) {
919
+ if (LEADING_ZERO.test(value)) {
920
+ throw new TomlError("leading zeroes are not allowed", err);
919
921
  }
920
- }
921
- return [...new Set(raw)];
922
- }
923
- var DEFAULT_SOAK = {
924
- deferSecs: 90,
925
- claimIdleNoticeSecs: 180,
926
- ttlSecs: 1800,
927
- beat: true,
928
- pausedTtlSecs: 86400,
929
- signOffGraceSecs: 600
930
- };
931
- var DEFAULT_HELM = {
932
- ttlSecs: 1800,
933
- reportSecs: 900,
934
- armGraceSecs: 5
935
- };
936
- var DEFAULT_GLASS = { port: 4949 };
937
- var DEFAULT_WATCH = { maxForksPerCycle: 3, autoRepair: true, conflicts: true, checks: true, maxRepairsPerPr: 2, maxRepairsWithoutProgress: 2, repairSettleSecs: 600, repairTrapWaitSecs: 600 };
938
- var DEFAULT_LIMITS = {
939
- maxConcurrent: 2,
940
- choreConcurrent: 1,
941
- wedgeThresholdSecs: 600,
942
- maxRestartAttempts: 2,
943
- wallClockSecs: 3600,
944
- pushEarly: true,
945
- draftPr: true,
946
- checkpointOnStop: true,
947
- backgroundWaitSecs: 1800,
948
- exitGraceSecs: 30,
949
- choreRetentionDays: 7,
950
- attachmentMaxBytes: 25 * 1024 * 1024,
951
- retentionDays: 0,
952
- minFreeGB: 0,
953
- reuseWorktree: true,
954
- releaseOnMerge: false
955
- };
956
- function configPath() {
957
- return path3.join(lobstahHome(), "config.toml");
958
- }
959
- function expandHome(p) {
960
- return p.startsWith("~/") ? path3.join(os2.homedir(), p.slice(2)) : p;
961
- }
962
- function loadConfig() {
963
- const file = configPath();
964
- const raw = fs3.existsSync(file) ? parse(fs3.readFileSync(file, "utf8")) : {};
965
- const reposRaw = raw.repos ?? {};
966
- const repos = {};
967
- for (const [key, r] of Object.entries(reposRaw)) {
968
- repos[key] = {
969
- path: expandHome(String(r.path ?? "")),
970
- origin: r.origin ? String(r.origin) : void 0,
971
- trunk: String(r.trunk ?? "main"),
972
- setup: Array.isArray(r.setup) ? r.setup.map(String) : void 0,
973
- env: r.env ?? void 0,
974
- harness: r.harness ?? void 0,
975
- pickup: r.pickup === void 0 ? void 0 : Boolean(r.pickup),
976
- scratch: Array.isArray(r.scratch) ? r.scratch.map(String) : void 0,
977
- pushEarly: r.pushEarly === void 0 ? void 0 : Boolean(r.pushEarly),
978
- draftPr: r.draftPr === void 0 ? void 0 : Boolean(r.draftPr),
979
- checkpointOnStop: r.checkpointOnStop === void 0 ? void 0 : Boolean(r.checkpointOnStop),
980
- humanGateChecks: Array.isArray(r.humanGateChecks) ? r.humanGateChecks.map(String).filter(Boolean) : void 0,
981
- briefHooks: parseBriefHooks(r.briefHooks)
982
- };
983
- }
984
- const groundsRaw = raw.grounds ?? {};
985
- const grounds = {};
986
- for (const [key, g] of Object.entries(groundsRaw)) {
987
- grounds[key] = { repos: Array.isArray(g.repos) ? g.repos.map(String) : [] };
988
- }
989
- return {
990
- repos,
991
- harness: raw.harness ?? {},
992
- limits: { ...DEFAULT_LIMITS, ...raw.limits ?? {} },
993
- soak: { ...DEFAULT_SOAK, ...raw.soak ?? {} },
994
- helm: { ...DEFAULT_HELM, ...raw.helm ?? {} },
995
- glass: { ...DEFAULT_GLASS, ...raw.glass ?? {} },
996
- watch: { ...DEFAULT_WATCH, ...raw.watch ?? {} },
997
- grounds,
998
- notifyCommand: raw.notifyCommand ? String(raw.notifyCommand) : void 0,
999
- notifyVerbs: Array.isArray(raw.notifyVerbs) ? raw.notifyVerbs.map(String) : void 0,
1000
- remindSecs: raw.remindSecs !== void 0 ? Number(raw.remindSecs) : void 0,
1001
- attentionKinds: parseAttentionKinds(raw.attentionKinds)
1002
- };
1003
- }
1004
- function resolveDispatch(d, cfg) {
1005
- const repo = cfg.repos[d.repo];
1006
- if (!repo)
1007
- throw new Error(`unknown repo key "${d.repo}" \u2014 add it to ${configPath()}`);
1008
- const rh = repo.harness ?? {};
1009
- const gh = cfg.harness;
1010
- return {
1011
- harness: d.harness ?? rh.default ?? gh.default ?? "claude",
1012
- model: d.model ?? rh.model ?? gh.model,
1013
- effort: d.effort ?? rh.effort ?? gh.effort,
1014
- limits: {
1015
- wallClockSecs: cfg.limits.wallClockSecs,
1016
- ...d.limits ?? {}
1017
- },
1018
- env: { ...repo.env ?? {}, ...d.env ?? {} },
1019
- flags: d.flags ?? []
1020
- };
1021
- }
1022
-
1023
- // packages/core/dist/disk.js
1024
- var GB = 1024 ** 3;
1025
-
1026
- // packages/core/dist/watch.js
1027
- import * as fs4 from "node:fs";
1028
- import * as path4 from "node:path";
1029
-
1030
- // packages/core/dist/gh-errors.js
1031
- function firstMeaningfulLine(...outputs) {
1032
- for (const out of outputs) {
1033
- for (const raw of (out ?? "").split("\n")) {
1034
- let line = raw.trim();
1035
- if (!line || /^(warning:|hint:|\(node:|\{|\})/i.test(line))
1036
- continue;
1037
- const m = /^error:\s*(.*)$/.exec(line);
1038
- if (m)
1039
- line = m[1].replace(/^"(.*)"$/, "$1").trim();
1040
- line = line.replace(/^(gh|GraphQL):\s*/i, "");
1041
- if (line)
1042
- return line;
922
+ value = value.replace(/_/g, "");
923
+ let numeric = +value;
924
+ if (isNaN(numeric)) {
925
+ throw new TomlError("invalid number", err);
1043
926
  }
927
+ if (isInt) {
928
+ if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
929
+ throw new TomlError("integer value cannot be represented losslessly", err);
930
+ }
931
+ if (isInt || integersAsBigInt === true)
932
+ numeric = BigInt(value);
933
+ }
934
+ return numeric;
1044
935
  }
1045
- return void 0;
1046
- }
1047
-
1048
- // packages/core/dist/watch.js
1049
- function watchesDir() {
1050
- return path4.join(lobstahHome(), "watches");
1051
- }
1052
- function slug(key) {
1053
- return key.replace(/[^A-Za-z0-9._-]+/g, "-");
1054
- }
1055
- function watchPath(key) {
1056
- return path4.join(watchesDir(), `${slug(key)}.json`);
1057
- }
1058
- function readWatch(key) {
1059
- try {
1060
- return JSON.parse(fs4.readFileSync(watchPath(key), "utf8"));
1061
- } catch {
1062
- return void 0;
1063
- }
936
+ const date = new TomlDate(value);
937
+ if (!date.isValid())
938
+ throw new TomlError("invalid value", err);
939
+ return date;
1064
940
  }
1065
941
 
1066
- // packages/core/dist/queue.js
1067
- import * as fs7 from "node:fs";
1068
- import * as path6 from "node:path";
1069
-
1070
- // packages/core/dist/status.js
1071
- import * as fs6 from "node:fs";
1072
-
1073
- // packages/core/dist/activity.js
1074
- import * as fs5 from "node:fs";
1075
- import * as path5 from "node:path";
1076
- var ACTIVITY_KINDS = ["tool", "message", "thinking", "waiting"];
1077
- var ACTIVITY_SUMMARY_MAX = 80;
1078
- var SECRET_PATTERNS = [
1079
- /\b(?:sk|pk|rk)[-_][A-Za-z0-9_-]{8,}/i,
1080
- /\b(?:gh[posur]_|github_pat_|glpat-|xox[abprs]-|npm_|hf_)[A-Za-z0-9_-]{16,}/,
1081
- /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/,
1082
- /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/,
1083
- /\bbearer\b/i,
1084
- /(?:token|secret|passw(?:or)?d|pwd|api[-_]?key|auth|credential|private[-_]?key)[^\s]*[=:]/i
1085
- ];
1086
- function looksSecret(word) {
1087
- if (SECRET_PATTERNS.some((re) => re.test(word)))
1088
- return true;
1089
- for (const run2 of word.match(/[A-Za-z0-9+/_=-]{24,}/g) ?? []) {
1090
- if (/[0-9]/.test(run2) && /[A-Za-z]/.test(run2))
1091
- return true;
942
+ // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/extract.js
943
+ function extractValue(ctx, end, integersAsBigInt) {
944
+ let ptr = ctx.p;
945
+ let c = ctx.s.charCodeAt(ptr);
946
+ if (c === 91 || c === 123) {
947
+ if (!ctx.d--) {
948
+ throw new TomlError("document contains excessively nested structures. aborting.", {
949
+ toml: ctx.s,
950
+ ptr
951
+ });
952
+ }
953
+ let value = c === 91 ? parseArray(ctx, integersAsBigInt) : parseInlineTable(ctx, integersAsBigInt);
954
+ ctx.d++;
955
+ return value;
1092
956
  }
1093
- return false;
1094
- }
1095
- function redactSummary(s, max = ACTIVITY_SUMMARY_MAX) {
1096
- const clean = s.replace(/[\r\n\t]+/g, " ").split(" ").filter(Boolean).map((w) => looksSecret(w) ? "[redacted]" : w).join(" ");
1097
- return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
1098
- }
1099
- var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "pwsh", "powershell", "cmd"]);
1100
- function commandWord(command) {
1101
- let words2 = command.trim().split(/\s+/).filter(Boolean);
1102
- while (words2.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words2[0]))
1103
- words2 = words2.slice(1);
1104
- if (words2[0] === "env") {
1105
- words2 = words2.slice(1);
1106
- while (words2.length > 0 && (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words2[0]) || words2[0].startsWith("-")))
1107
- words2 = words2.slice(1);
957
+ if (c === 34 || c === 39) {
958
+ return parseString(ctx);
1108
959
  }
1109
- const first = words2[0];
1110
- if (!first)
1111
- return void 0;
1112
- const name = baseName(first.replace(/^["']|["']$/g, "")).replace(/\.(exe|cmd|bat)$/i, "");
1113
- if (SHELLS.has(name.toLowerCase()) && words2[1] && /^(-\w*c|\/c|-command)$/i.test(words2[1])) {
1114
- return commandWord(words2.slice(2).join(" ").replace(/^["']/, "")) ?? name;
960
+ if (c === 116) {
961
+ if (ctx.s.charCodeAt(++ctx.p) !== 114 || ctx.s.charCodeAt(++ctx.p) !== 117 || ctx.s.charCodeAt(++ctx.p) !== 101)
962
+ throw new TomlError("invalid value", { toml: ctx.s, ptr });
963
+ ctx.p++;
964
+ return true;
1115
965
  }
1116
- return name || void 0;
1117
- }
1118
- function baseName(p) {
1119
- const parts = p.split(/[\\/]/).filter(Boolean);
1120
- return parts.at(-1) ?? p;
1121
- }
1122
- function isWindowsPath(p) {
1123
- return /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("\\\\");
1124
- }
1125
- function relativeTarget(p, root) {
1126
- if (!root)
1127
- return baseName(p);
1128
- const win = isWindowsPath(p) || isWindowsPath(root);
1129
- const lib = win ? path5.win32 : path5.posix;
1130
- if (!lib.isAbsolute(p))
1131
- return p.replace(/\\/g, "/");
1132
- const rel = lib.relative(win ? root : root.replace(/\\/g, "/"), p);
1133
- if (!rel || rel.startsWith("..") || lib.isAbsolute(rel))
1134
- return `\u2026/${baseName(p)}`;
1135
- return rel.replace(/\\/g, "/");
1136
- }
1137
- function urlHost(u) {
1138
- try {
1139
- return new URL(u).host || void 0;
1140
- } catch {
1141
- return void 0;
966
+ if (c === 102) {
967
+ if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 108 || ctx.s.charCodeAt(++ctx.p) !== 115 || ctx.s.charCodeAt(++ctx.p) !== 101)
968
+ throw new TomlError("invalid value", { toml: ctx.s, ptr });
969
+ ctx.p++;
970
+ return false;
1142
971
  }
972
+ return parseValue(ctx, integersAsBigInt, end);
1143
973
  }
1144
- function toolTarget(input) {
1145
- if (!input || typeof input !== "object")
1146
- return void 0;
1147
- const i = input;
1148
- const str = (k) => typeof i[k] === "string" && i[k].length > 0 ? i[k] : void 0;
1149
- const file = str("file_path") ?? str("notebook_path") ?? str("path");
1150
- if (file)
1151
- return file;
1152
- const command = str("command") ?? (Array.isArray(i.command) ? i.command.map(String).join(" ") : void 0);
1153
- if (command)
1154
- return commandWord(command);
1155
- const url = str("url");
1156
- if (url)
1157
- return urlHost(url);
1158
- const changes = i.changes;
1159
- if (Array.isArray(changes) && changes.length > 0) {
1160
- const first = changes[0];
1161
- if (typeof first?.path === "string")
1162
- return first.path;
974
+
975
+ // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/struct.js
976
+ var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
977
+ function parseKey(ctx, end = "=") {
978
+ let start = ctx.p;
979
+ let dot = start - 1;
980
+ let parsed = [];
981
+ let endPtr = ctx.s.indexOf(end, start);
982
+ if (endPtr < 0) {
983
+ throw new TomlError("incomplete key-value: cannot find end of key", {
984
+ toml: ctx.s,
985
+ ptr: start
986
+ });
1163
987
  }
1164
- return str("subagent_type") ?? str("skill");
1165
- }
1166
- function toolSummary(name, target, root) {
1167
- const looksPath = target !== void 0 && (/[\\/]/.test(target) || isWindowsPath(target));
1168
- const shown = target === void 0 ? "" : looksPath ? relativeTarget(target, root) : target;
1169
- return redactSummary(shown ? `${name} ${shown}` : name);
988
+ do {
989
+ let c = ctx.s.charCodeAt(ctx.p = ++dot);
990
+ if (c !== 32 && c !== 9) {
991
+ if (c === 34 || c === 39) {
992
+ if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2)) {
993
+ throw new TomlError("multiline strings are not allowed in keys", {
994
+ toml: ctx.s,
995
+ ptr: ctx.p
996
+ });
997
+ }
998
+ let part = parseString(ctx);
999
+ dot = ctx.s.indexOf(".", ctx.p);
1000
+ let strEnd = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot);
1001
+ let newLine = indexOfNewline(strEnd);
1002
+ if (newLine > -1) {
1003
+ throw new TomlError("newlines are not allowed in keys", {
1004
+ toml: ctx.s,
1005
+ ptr: newLine
1006
+ });
1007
+ }
1008
+ if (strEnd.trimStart()) {
1009
+ throw new TomlError("found extra tokens after the string part", {
1010
+ toml: ctx.s,
1011
+ ptr: ctx.p
1012
+ });
1013
+ }
1014
+ if (endPtr < ctx.p) {
1015
+ endPtr = ctx.s.indexOf(end, ctx.p);
1016
+ if (endPtr < 0) {
1017
+ throw new TomlError("incomplete key-value: cannot find end of key", {
1018
+ toml: ctx.s,
1019
+ ptr: start
1020
+ });
1021
+ }
1022
+ }
1023
+ parsed.push(part);
1024
+ } else {
1025
+ dot = ctx.s.indexOf(".", ctx.p);
1026
+ let part = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot);
1027
+ if (!KEY_PART_RE.test(part)) {
1028
+ throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
1029
+ toml: ctx.s,
1030
+ ptr: ctx.p
1031
+ });
1032
+ }
1033
+ parsed.push(part.trimEnd());
1034
+ }
1035
+ }
1036
+ } while (dot + 1 && dot < endPtr);
1037
+ ctx.p = endPtr + 1;
1038
+ skipVoid(ctx, true, true);
1039
+ return parsed;
1170
1040
  }
1171
- function activityFromEvent(ev, root) {
1172
- const data = ev.data ?? {};
1173
- switch (ev.type) {
1174
- case "tool-start": {
1175
- const name = typeof data.name === "string" && data.name ? data.name : "tool";
1176
- const target = typeof data.target === "string" ? data.target : void 0;
1177
- return { kind: "tool", summary: toolSummary(name, target, root) };
1041
+ function parseInlineTable(ctx, integersAsBigInt) {
1042
+ let res = {};
1043
+ let seen = /* @__PURE__ */ new Set();
1044
+ let c;
1045
+ ctx.p++;
1046
+ while (ctx.p < ctx.s.length) {
1047
+ skipVoid(ctx);
1048
+ if ((c = ctx.s.charCodeAt(ctx.p)) === 125) {
1049
+ ctx.p++;
1050
+ return res;
1178
1051
  }
1179
- case "text":
1180
- return { kind: "message", summary: "writing a message" };
1181
- case "thinking":
1182
- return { kind: "thinking", summary: "thinking" };
1183
- case "runner": {
1184
- if (typeof data.on === "string")
1185
- return { kind: "waiting", summary: redactSummary(`waiting on ${data.on}`) };
1186
- if (typeof data.waiting === "string")
1187
- return { kind: "waiting", summary: redactSummary(`waiting for an answer (${data.waiting})`) };
1188
- if (data.holding === "background")
1189
- return { kind: "waiting", summary: "waiting on background work" };
1190
- return void 0;
1052
+ let k;
1053
+ let t = res;
1054
+ let hasOwn = false;
1055
+ let p = ctx.p;
1056
+ let key = parseKey(ctx);
1057
+ for (let i = 0; i < key.length; i++) {
1058
+ if (i)
1059
+ t = hasOwn ? t[k] : t[k] = {};
1060
+ k = key[i];
1061
+ if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
1062
+ throw new TomlError("trying to redefine an already defined value", {
1063
+ toml: ctx.s,
1064
+ ptr: p
1065
+ });
1066
+ }
1067
+ if (!hasOwn && k === "__proto__") {
1068
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
1069
+ }
1070
+ }
1071
+ if (hasOwn) {
1072
+ throw new TomlError("trying to redefine an already defined value", {
1073
+ toml: ctx.s,
1074
+ ptr: ctx.p
1075
+ });
1076
+ }
1077
+ let value = extractValue(ctx, 125, integersAsBigInt);
1078
+ seen.add(t[k] = value);
1079
+ skipVoid(ctx);
1080
+ if ((c = ctx.s.charCodeAt(ctx.p++)) === 125) {
1081
+ return res;
1082
+ }
1083
+ if (c !== 44) {
1084
+ throw new TomlError("expected comma or end of structure", { toml: ctx.s, ptr: ctx.p - 1 });
1191
1085
  }
1192
- default:
1193
- return void 0;
1194
1086
  }
1087
+ throw new TomlError("unfinished table encountered", {
1088
+ toml: ctx.s,
1089
+ ptr: ctx.p
1090
+ });
1195
1091
  }
1196
- function atomicWrite(file, content) {
1197
- const tmp = `${file}.tmp-${process.pid}`;
1198
- fs5.writeFileSync(tmp, content);
1199
- fs5.renameSync(tmp, file);
1200
- }
1201
- function writeActivity(id, lane, a) {
1202
- const safe = { at: a.at, kind: a.kind, summary: redactSummary(a.summary) };
1203
- atomicWrite(activityPath(id, lane), JSON.stringify(safe));
1204
- }
1205
- function readActivity(id, lane) {
1206
- try {
1207
- const parsed = JSON.parse(fs5.readFileSync(activityPath(id, lane), "utf8"));
1208
- if (typeof parsed.at !== "string" || typeof parsed.summary !== "string")
1209
- return void 0;
1210
- if (!ACTIVITY_KINDS.includes(parsed.kind))
1211
- return void 0;
1212
- return { ...parsed, summary: redactSummary(parsed.summary) };
1213
- } catch {
1214
- return void 0;
1092
+ function parseArray(ctx, integersAsBigInt) {
1093
+ let res = [];
1094
+ let c;
1095
+ ctx.p++;
1096
+ while (ctx.p < ctx.s.length) {
1097
+ skipVoid(ctx);
1098
+ if ((c = ctx.s.charCodeAt(ctx.p)) === 93) {
1099
+ ctx.p++;
1100
+ return res;
1101
+ }
1102
+ res.push(extractValue(ctx, 93, integersAsBigInt));
1103
+ skipVoid(ctx);
1104
+ if ((c = ctx.s.charCodeAt(ctx.p++)) === 93) {
1105
+ return res;
1106
+ }
1107
+ if (c !== 44) {
1108
+ throw new TomlError("expected comma or end of structure", { toml: ctx.s, ptr: ctx.p - 1 });
1109
+ }
1215
1110
  }
1111
+ throw new TomlError("unfinished array encountered", {
1112
+ toml: ctx.s,
1113
+ ptr: ctx.p
1114
+ });
1216
1115
  }
1217
- var ActivityTracker = class {
1218
- write;
1219
- opts;
1220
- lastWriteAt = -Infinity;
1221
- lastKind;
1222
- pending;
1223
- timer;
1224
- throttleMs;
1225
- now;
1226
- constructor(write, opts = {}) {
1227
- this.write = write;
1228
- this.opts = opts;
1229
- this.throttleMs = opts.throttleMs ?? 1e4;
1230
- this.now = opts.now ?? Date.now;
1116
+
1117
+ // node_modules/.pnpm/smol-toml@1.8.0/node_modules/smol-toml/dist/parse.js
1118
+ function peekTable(key, table, meta, type) {
1119
+ let t = table;
1120
+ let m = meta;
1121
+ let k;
1122
+ let hasOwn = false;
1123
+ let state;
1124
+ for (let i = 0; i < key.length; i++) {
1125
+ if (i) {
1126
+ t = hasOwn ? t[k] : t[k] = {};
1127
+ m = (state = m[k]).c;
1128
+ if (type === 0 && (state.t === 1 || state.t === 2)) {
1129
+ return null;
1130
+ }
1131
+ if (state.t === 2) {
1132
+ let l = t.length - 1;
1133
+ t = t[l];
1134
+ m = m[l].c;
1135
+ }
1136
+ }
1137
+ k = key[i];
1138
+ if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
1139
+ return null;
1140
+ }
1141
+ if (!hasOwn) {
1142
+ if (k === "__proto__") {
1143
+ Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
1144
+ Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
1145
+ }
1146
+ m[k] = {
1147
+ t: i < key.length - 1 && type === 2 ? 3 : type,
1148
+ d: false,
1149
+ i: 0,
1150
+ c: {}
1151
+ };
1152
+ }
1231
1153
  }
1232
- observe(ev) {
1233
- const derived = activityFromEvent(ev, this.opts.root);
1234
- if (derived)
1235
- this.offer({ at: new Date(this.now()).toISOString(), ...derived });
1154
+ state = m[k];
1155
+ if (state.t !== type && !(type === 1 && state.t === 3)) {
1156
+ return null;
1236
1157
  }
1237
- offer(a) {
1238
- const t = this.now();
1239
- if (a.kind !== this.lastKind || t - this.lastWriteAt >= this.throttleMs) {
1240
- this.emit(a, t);
1241
- return;
1242
- }
1243
- this.pending = a;
1244
- if (!this.timer) {
1245
- this.timer = setTimeout(() => {
1246
- this.timer = void 0;
1247
- this.flush();
1248
- }, Math.max(0, this.throttleMs - (t - this.lastWriteAt)));
1249
- this.timer.unref?.();
1158
+ if (type === 2) {
1159
+ if (!state.d) {
1160
+ state.d = true;
1161
+ t[k] = [];
1250
1162
  }
1163
+ t[k].push(t = {});
1164
+ state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
1251
1165
  }
1252
- /** Write a held record now. */
1253
- flush() {
1254
- if (!this.pending)
1255
- return;
1256
- const a = this.pending;
1257
- this.emit(a, this.now());
1166
+ if (state.d) {
1167
+ return null;
1258
1168
  }
1259
- stop() {
1260
- if (this.timer)
1261
- clearTimeout(this.timer);
1262
- this.timer = void 0;
1263
- this.pending = void 0;
1169
+ state.d = true;
1170
+ if (type === 1) {
1171
+ t = hasOwn ? t[k] : t[k] = {};
1172
+ } else if (type === 0 && hasOwn) {
1173
+ return null;
1264
1174
  }
1265
- emit(a, t) {
1266
- this.pending = void 0;
1267
- if (this.timer) {
1268
- clearTimeout(this.timer);
1269
- this.timer = void 0;
1175
+ return [k, t, state.c];
1176
+ }
1177
+ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
1178
+ let ctx = { s: toml, p: 0, d: maxDepth };
1179
+ let res = {};
1180
+ let meta = {};
1181
+ let tmp;
1182
+ let tbl = res;
1183
+ let m = meta;
1184
+ skipVoid(ctx);
1185
+ while (ctx.p < toml.length) {
1186
+ if (toml.charCodeAt(ctx.p) === 91) {
1187
+ let isTableArray = toml.charCodeAt(++ctx.p) === 91;
1188
+ tmp = ctx.p += +isTableArray;
1189
+ let k = parseKey(ctx, "]");
1190
+ if (isTableArray) {
1191
+ if (toml.charCodeAt(ctx.p - 1) !== 93) {
1192
+ throw new TomlError("expected end of table declaration", {
1193
+ toml,
1194
+ ptr: ctx.p - 1
1195
+ });
1196
+ }
1197
+ ctx.p++;
1198
+ }
1199
+ let p = peekTable(
1200
+ k,
1201
+ res,
1202
+ meta,
1203
+ isTableArray ? 2 : 1
1204
+ /* Type.EXPLICIT */
1205
+ );
1206
+ if (!p) {
1207
+ throw new TomlError("trying to redefine an already defined table or value", {
1208
+ toml,
1209
+ ptr: tmp
1210
+ });
1211
+ }
1212
+ m = p[2];
1213
+ tbl = p[1];
1214
+ } else {
1215
+ tmp = ctx.p;
1216
+ let k = parseKey(ctx);
1217
+ let p = peekTable(
1218
+ k,
1219
+ tbl,
1220
+ m,
1221
+ 0
1222
+ /* Type.DOTTED */
1223
+ );
1224
+ if (!p) {
1225
+ throw new TomlError("trying to redefine an already defined table or value", {
1226
+ toml,
1227
+ ptr: tmp
1228
+ });
1229
+ }
1230
+ p[1][p[0]] = extractValue(ctx, void 0, integersAsBigInt);
1270
1231
  }
1271
- this.lastWriteAt = t;
1272
- this.lastKind = a.kind;
1273
- try {
1274
- this.write(a);
1275
- } catch {
1232
+ skipVoid(ctx, true);
1233
+ if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 10 && tmp !== 13) {
1234
+ throw new TomlError("each key-value declaration must be followed by an end-of-line", {
1235
+ toml,
1236
+ ptr: ctx.p
1237
+ });
1276
1238
  }
1239
+ skipVoid(ctx);
1277
1240
  }
1278
- };
1279
-
1280
- // packages/core/dist/status.js
1281
- function isVerb(v) {
1282
- return VERBS.includes(v);
1283
- }
1284
- function isWaitingOn(v) {
1285
- return WAITING_ON.includes(v);
1286
- }
1287
- var DURATION = /^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i;
1288
- var UNIT_MS = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
1289
- function parseUntil(v, now2 = Date.now()) {
1290
- const d = DURATION.exec(v.trim());
1291
- if (d)
1292
- return new Date(now2 + Number(d[1]) * UNIT_MS[d[2].toLowerCase()]).toISOString();
1293
- const t = Date.parse(v);
1294
- if (Number.isNaN(t))
1295
- throw new Error(`invalid --until "${v}" \u2014 an ISO time (2026-10-01T09:00Z) or a duration (30m, 4h, 2d)`);
1296
- return new Date(t).toISOString();
1241
+ return res;
1297
1242
  }
1298
- function waitingFields(verb, w, now2 = Date.now()) {
1243
+
1244
+ // packages/core/dist/config.js
1245
+ var BRIEF_KINDS = ["conflict", "checks", "review", "ciFix", "rebase"];
1246
+ function parseBriefHooks(raw) {
1247
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1248
+ return void 0;
1299
1249
  const out = {};
1300
- if ((w.waitingOn !== void 0 || w.link !== void 0) && !WAITING_ON_VERBS.includes(verb)) {
1301
- throw new Error(`--waiting-on and --link are valid only with ${WAITING_ON_VERBS.join(", ")} \u2014 not ${verb}`);
1250
+ for (const kind of [...BRIEF_KINDS, "all"]) {
1251
+ const text = raw[kind];
1252
+ if (typeof text === "string" && text.trim())
1253
+ out[kind] = text.trim();
1302
1254
  }
1303
- if (w.until !== void 0 && verb !== "paused")
1304
- throw new Error(`--until is valid only with paused \u2014 not ${verb}`);
1305
- if (w.waitingOn !== void 0) {
1306
- if (!isWaitingOn(w.waitingOn)) {
1307
- throw new Error(`invalid --waiting-on "${w.waitingOn}" \u2014 must be one of: ${WAITING_ON.join(", ")}`);
1255
+ return Object.keys(out).length ? out : void 0;
1256
+ }
1257
+ var ATTENTION_KINDS = ["question", "decision", "landed", "pr:draft", "pr:review", "pr:checks", "pr:conflict", "pr:ready", "report", "stack-ready"];
1258
+ var DEFAULT_ATTENTION_KINDS = ["question", "decision", "pr:ready", "pr:review", "pr:conflict", "pr:checks"];
1259
+ function parseAttentionKinds(raw) {
1260
+ if (raw === void 0)
1261
+ return [...DEFAULT_ATTENTION_KINDS];
1262
+ if (!Array.isArray(raw)) {
1263
+ throw new Error(`attentionKinds must be an array of kinds (${ATTENTION_KINDS.join(", ")}) in ${configPath()}`);
1264
+ }
1265
+ for (const k of raw) {
1266
+ if (!ATTENTION_KINDS.includes(k)) {
1267
+ throw new Error(`attentionKinds: unknown kind "${String(k)}" in ${configPath()} \u2014 valid kinds: ${ATTENTION_KINDS.join(", ")}`);
1308
1268
  }
1309
- out.waitingOn = w.waitingOn;
1310
1269
  }
1311
- if (w.link !== void 0) {
1312
- let url;
1313
- try {
1314
- url = new URL(w.link);
1315
- } catch {
1316
- url = void 0;
1317
- }
1318
- if (!url || url.protocol !== "http:" && url.protocol !== "https:") {
1319
- throw new Error(`invalid --link "${w.link}" \u2014 must be an http or https URL`);
1320
- }
1321
- out.link = url.href;
1270
+ return [...new Set(raw)];
1271
+ }
1272
+ function parseSoak(raw) {
1273
+ const soak = { ...DEFAULT_SOAK, ...raw ?? {} };
1274
+ if (soak.terminal !== void 0 && soak.terminal !== "terminal" && soak.terminal !== "iterm") {
1275
+ throw new Error(`soak.terminal: unknown terminal "${String(soak.terminal)}" in ${configPath()} \u2014 use "terminal" or "iterm"`);
1276
+ }
1277
+ return soak;
1278
+ }
1279
+ var DEFAULT_SOAK = {
1280
+ deferSecs: 90,
1281
+ claimIdleNoticeSecs: 180,
1282
+ ttlSecs: 1800,
1283
+ beat: true,
1284
+ pausedTtlSecs: 86400,
1285
+ signOffGraceSecs: 600
1286
+ };
1287
+ var DEFAULT_HELM = {
1288
+ ttlSecs: 1800,
1289
+ reportSecs: 900,
1290
+ armGraceSecs: 5
1291
+ };
1292
+ var DEFAULT_GLASS = { port: 4949 };
1293
+ var DEFAULT_WATCH = { maxForksPerCycle: 3, autoRepair: true, conflicts: true, checks: true, maxRepairsPerPr: 2, maxRepairsWithoutProgress: 2, repairSettleSecs: 600, repairTrapWaitSecs: 600 };
1294
+ var DEFAULT_LIMITS = {
1295
+ maxConcurrent: 2,
1296
+ choreConcurrent: 1,
1297
+ wedgeThresholdSecs: 600,
1298
+ maxRestartAttempts: 2,
1299
+ wallClockSecs: 3600,
1300
+ pushEarly: true,
1301
+ draftPr: true,
1302
+ checkpointOnStop: true,
1303
+ backgroundWaitSecs: 1800,
1304
+ exitGraceSecs: 30,
1305
+ choreRetentionDays: 7,
1306
+ attachmentMaxBytes: 25 * 1024 * 1024,
1307
+ retentionDays: 0,
1308
+ minFreeGB: 0,
1309
+ reuseWorktree: true,
1310
+ releaseOnMerge: false
1311
+ };
1312
+ function configPath() {
1313
+ return path5.join(lobstahHome(), "config.toml");
1314
+ }
1315
+ function expandHome(p) {
1316
+ return p.startsWith("~/") ? path5.join(os2.homedir(), p.slice(2)) : p;
1317
+ }
1318
+ function loadConfig() {
1319
+ const file = configPath();
1320
+ const raw = fs7.existsSync(file) ? parse(fs7.readFileSync(file, "utf8")) : {};
1321
+ const readySettleSecs = raw.readySettleSecs ?? 600;
1322
+ if (typeof readySettleSecs !== "number" || !Number.isFinite(readySettleSecs) || readySettleSecs < 0) {
1323
+ throw new Error(`readySettleSecs must be a non-negative number in ${configPath()}`);
1324
+ }
1325
+ const reposRaw = raw.repos ?? {};
1326
+ const repos = {};
1327
+ for (const [key, r] of Object.entries(reposRaw)) {
1328
+ repos[key] = {
1329
+ path: expandHome(String(r.path ?? "")),
1330
+ origin: r.origin ? String(r.origin) : void 0,
1331
+ trunk: String(r.trunk ?? "main"),
1332
+ setup: Array.isArray(r.setup) ? r.setup.map(String) : void 0,
1333
+ env: r.env ?? void 0,
1334
+ harness: r.harness ?? void 0,
1335
+ pickup: r.pickup === void 0 ? void 0 : Boolean(r.pickup),
1336
+ scratch: Array.isArray(r.scratch) ? r.scratch.map(String) : void 0,
1337
+ pushEarly: r.pushEarly === void 0 ? void 0 : Boolean(r.pushEarly),
1338
+ draftPr: r.draftPr === void 0 ? void 0 : Boolean(r.draftPr),
1339
+ checkpointOnStop: r.checkpointOnStop === void 0 ? void 0 : Boolean(r.checkpointOnStop),
1340
+ humanGateChecks: Array.isArray(r.humanGateChecks) ? r.humanGateChecks.map(String).filter(Boolean) : void 0,
1341
+ briefHooks: parseBriefHooks(r.briefHooks)
1342
+ };
1322
1343
  }
1323
- if (w.until !== void 0)
1324
- out.until = parseUntil(w.until, now2);
1325
- return out;
1326
- }
1327
- function appendStatus(id, lane, verb, note, at, waiting, reported = false) {
1328
- if (!isVerb(verb)) {
1329
- throw new Error(`invalid status verb "${verb}" \u2014 must be one of: ${VERBS.join(", ")}`);
1344
+ const groundsRaw = raw.grounds ?? {};
1345
+ const grounds = {};
1346
+ for (const [key, g] of Object.entries(groundsRaw)) {
1347
+ grounds[key] = { repos: Array.isArray(g.repos) ? g.repos.map(String) : [] };
1330
1348
  }
1331
- const extra = waiting ? waitingFields(verb, waiting) : {};
1332
- const entry = {
1333
- at: at ?? (/* @__PURE__ */ new Date()).toISOString(),
1334
- verb,
1335
- ...note ? { note } : {},
1336
- ...extra,
1337
- ...reported ? { reported: true } : {}
1349
+ return {
1350
+ repos,
1351
+ harness: raw.harness ?? {},
1352
+ limits: { ...DEFAULT_LIMITS, ...raw.limits ?? {} },
1353
+ soak: parseSoak(raw.soak),
1354
+ helm: { ...DEFAULT_HELM, ...raw.helm ?? {} },
1355
+ glass: { ...DEFAULT_GLASS, ...raw.glass ?? {} },
1356
+ watch: { ...DEFAULT_WATCH, ...raw.watch ?? {} },
1357
+ grounds,
1358
+ notifyCommand: raw.notifyCommand ? String(raw.notifyCommand) : void 0,
1359
+ notifyVerbs: Array.isArray(raw.notifyVerbs) ? raw.notifyVerbs.map(String) : void 0,
1360
+ remindSecs: raw.remindSecs !== void 0 ? Number(raw.remindSecs) : void 0,
1361
+ readySettleSecs,
1362
+ attentionKinds: parseAttentionKinds(raw.attentionKinds)
1338
1363
  };
1339
- fs6.appendFileSync(statusPath(id, lane), `${JSON.stringify(entry)}
1340
- `);
1341
- if (verb === "done") {
1342
- try {
1343
- recordCatch(id, lane);
1344
- } catch {
1345
- }
1346
- }
1347
- return entry;
1348
- }
1349
- function readStatusLog(id, lane) {
1350
- const file = statusPath(id, lane);
1351
- if (!fs6.existsSync(file))
1352
- return [];
1353
- return fs6.readFileSync(file, "utf8").split("\n").filter(Boolean).flatMap((line) => {
1354
- try {
1355
- const parsed = JSON.parse(line);
1356
- return isVerb(parsed.verb) ? [parsed] : [];
1357
- } catch {
1358
- return [];
1359
- }
1360
- });
1361
1364
  }
1362
- function pausedWaiting(entry) {
1363
- return entry?.verb === "paused" && entry.waitingOn !== void 0;
1365
+ function resolveDispatch(d, cfg) {
1366
+ const repo = cfg.repos[d.repo];
1367
+ if (!repo)
1368
+ throw new Error(`unknown repo key "${d.repo}" \u2014 add it to ${configPath()}`);
1369
+ const rh = repo.harness ?? {};
1370
+ const gh = cfg.harness;
1371
+ return {
1372
+ harness: d.harness ?? rh.default ?? gh.default ?? "claude",
1373
+ model: d.model ?? rh.model ?? gh.model,
1374
+ effort: d.effort ?? rh.effort ?? gh.effort,
1375
+ limits: {
1376
+ wallClockSecs: cfg.limits.wallClockSecs,
1377
+ ...d.limits ?? {}
1378
+ },
1379
+ env: { ...repo.env ?? {}, ...d.env ?? {} },
1380
+ flags: d.flags ?? []
1381
+ };
1364
1382
  }
1365
1383
 
1366
- // packages/core/dist/queue.js
1367
- function storedDescriptor(id, lane) {
1368
- const dirs = laneDirs(lane);
1369
- for (const file of [
1370
- path6.join(dirs.queue, `${id}.json`),
1371
- path6.join(dirs.active, id, "descriptor.json"),
1372
- path6.join(dirs.done, id, "descriptor.json")
1373
- ]) {
1374
- try {
1375
- return JSON.parse(fs7.readFileSync(file, "utf8"));
1376
- } catch {
1384
+ // packages/core/dist/pr.js
1385
+ import { spawnSync } from "node:child_process";
1386
+
1387
+ // packages/core/dist/gh-errors.js
1388
+ function firstMeaningfulLine(...outputs) {
1389
+ for (const out of outputs) {
1390
+ for (const raw of (out ?? "").split("\n")) {
1391
+ let line = raw.trim();
1392
+ if (!line || /^(warning:|hint:|\(node:|\{|\})/i.test(line))
1393
+ continue;
1394
+ const m = /^error:\s*(.*)$/.exec(line);
1395
+ if (m)
1396
+ line = m[1].replace(/^"(.*)"$/, "$1").trim();
1397
+ line = line.replace(/^(gh|GraphQL):\s*/i, "");
1398
+ if (line)
1399
+ return line;
1377
1400
  }
1378
1401
  }
1379
1402
  return void 0;
1380
1403
  }
1381
- function activeIds(lane) {
1382
- return readDirIfPresent(laneDirs(lane).active).filter((f) => !f.startsWith("."));
1404
+
1405
+ // packages/core/dist/pr.js
1406
+ function parsePrRef(s) {
1407
+ 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());
1408
+ if (!m)
1409
+ return void 0;
1410
+ const [, owner, repo, n] = m;
1411
+ const number = Number(n);
1412
+ return { owner, repo, number, key: `pr:${owner}/${repo}#${number}`, url: `https://github.com/${owner}/${repo}/pull/${number}` };
1383
1413
  }
1384
- function complete(id, lane) {
1385
- const dirs = laneDirs(lane);
1386
- fs7.renameSync(path6.join(dirs.active, id), path6.join(dirs.done, id));
1414
+ var PR_VIEW_FIELDS = "title,state,isDraft,isCrossRepository,headRefOid,baseRefName,baseRefOid,headRefName,mergeStateStatus,reviewDecision,statusCheckRollup,mergedAt,closedAt,updatedAt,reviews";
1415
+ var PR_VIEW_FIELDS_NO_CHECKS = PR_VIEW_FIELDS.split(",").filter((f) => f !== "statusCheckRollup").join(",");
1416
+ var FORBIDDEN = /resource not accessible by (integration|personal access token)/i;
1417
+ function ghPrView(ref) {
1418
+ const view1 = (fields2) => spawnSync("gh", ["pr", "view", String(ref.number), "--repo", `${ref.owner}/${ref.repo}`, "--json", fields2], {
1419
+ encoding: "utf8",
1420
+ timeout: 6e4
1421
+ });
1422
+ const reason = (r) => firstMeaningfulLine(r.stderr) ?? `gh exited ${r.status}`;
1423
+ const unknownBase = (r) => r.status !== 0 && /unknown json field.*baseRefOid/i.test(r.stderr ?? "");
1424
+ const withoutBase = (fields2) => fields2.split(",").filter((f) => f !== "baseRefOid").join(",");
1425
+ let fields = PR_VIEW_FIELDS;
1426
+ let res = view1(fields);
1427
+ if (unknownBase(res))
1428
+ res = view1(fields = withoutBase(fields));
1429
+ if (res.error)
1430
+ throw new Error(`gh: ${res.error.message}`);
1431
+ let checksError;
1432
+ if (res.status !== 0 && FORBIDDEN.test(res.stderr ?? "")) {
1433
+ checksError = reason(res);
1434
+ const retry = view1(fields === PR_VIEW_FIELDS ? PR_VIEW_FIELDS_NO_CHECKS : withoutBase(PR_VIEW_FIELDS_NO_CHECKS));
1435
+ if (retry.error)
1436
+ throw new Error(`gh: ${retry.error.message}`);
1437
+ if (retry.status !== 0) {
1438
+ throw new Error(FORBIDDEN.test(retry.stderr ?? "") ? `${reason(retry)} (fails even without check results)` : reason(retry));
1439
+ }
1440
+ res = retry;
1441
+ }
1442
+ if (res.status !== 0)
1443
+ throw new Error(reason(res));
1444
+ const view = JSON.parse(res.stdout);
1445
+ if (checksError)
1446
+ view.checksError = checksError;
1447
+ if (view.state === "OPEN") {
1448
+ const threads = ghUnresolvedThreads(ref);
1449
+ if (threads !== void 0)
1450
+ view.unresolvedThreads = threads;
1451
+ }
1452
+ return view;
1387
1453
  }
1388
- function cancelRequested(id, lane) {
1389
- return fs7.existsSync(path6.join(laneDirs(lane).active, id, "cancel"));
1454
+ var THREADS_QUERY = "query($owner:String!,$repo:String!,$n:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$n){reviewThreads(first:100){nodes{isResolved}}}}}";
1455
+ function ghUnresolvedThreads(ref) {
1456
+ const res = spawnSync("gh", ["api", "graphql", "-f", `query=${THREADS_QUERY}`, "-f", `owner=${ref.owner}`, "-f", `repo=${ref.repo}`, "-F", `n=${ref.number}`], { encoding: "utf8", timeout: 6e4 });
1457
+ if (res.error || res.status !== 0)
1458
+ return void 0;
1459
+ return parseUnresolvedThreads(res.stdout);
1390
1460
  }
1391
-
1392
- // packages/core/dist/soak.js
1393
- import * as fs11 from "node:fs";
1394
- import * as path9 from "node:path";
1395
-
1396
- // packages/core/dist/evidence.js
1397
- import * as fs8 from "node:fs";
1398
- function readEvidence(id, lane) {
1461
+ function parseUnresolvedThreads(stdout) {
1399
1462
  try {
1400
- return JSON.parse(fs8.readFileSync(evidencePath(id, lane), "utf8"));
1463
+ const nodes = JSON.parse(stdout).data?.repository?.pullRequest?.reviewThreads?.nodes;
1464
+ return Array.isArray(nodes) ? nodes.filter((t) => t.isResolved === false).length : void 0;
1401
1465
  } catch {
1402
- return {};
1403
- }
1404
- }
1405
- function mergeEvidence(id, lane, patch) {
1406
- const file = evidencePath(id, lane);
1407
- const tmp = `${file}.tmp-${process.pid}`;
1408
- fs8.writeFileSync(tmp, JSON.stringify({ ...readEvidence(id, lane), ...patch }, null, 2));
1409
- fs8.renameSync(tmp, file);
1410
- }
1411
-
1412
- // packages/core/dist/pushes.js
1413
- import * as fs9 from "node:fs";
1414
- import * as path7 from "node:path";
1415
- var MAX_PUSHES = 20;
1416
- var PUSH_HEAD = "HEAD";
1417
- var VALUE_OPTIONS = /* @__PURE__ */ new Set(["-o", "--push-option", "--repo", "--receive-pack", "--exec"]);
1418
- function words(segment) {
1419
- const out = [];
1420
- const re = /"((?:[^"\\]|\\.)*)"|'([^']*)'|(\S+)/g;
1421
- for (let m = re.exec(segment); m; m = re.exec(segment))
1422
- out.push(m[1] ?? m[2] ?? m[3] ?? "");
1423
- return out;
1424
- }
1425
- function branchOf(ref) {
1426
- const name = ref.replace(/^\+/, "").replace(/^refs\/heads\//, "");
1427
- return name === "@" || name === "" ? PUSH_HEAD : name;
1428
- }
1429
- function gitPushTargets(command) {
1430
- if (command === void 0)
1431
- return void 0;
1432
- const text = typeof command === "string" ? command : command.join(" ");
1433
- if (!/\bgit\b[^\n]*\bpush\b/.test(text))
1434
1466
  return void 0;
1435
- const targets = [];
1436
- let found = false;
1437
- for (const segment of text.split(/&&|\|\||[;|\n]/)) {
1438
- const w = words(segment);
1439
- let i = w.findIndex((x) => x === "git" || /[\\/]git(?:\.exe)?$/.test(x));
1440
- if (i < 0)
1441
- continue;
1442
- for (i++; i < w.length && w[i].startsWith("-"); i++)
1443
- if (w[i] === "-C" || w[i] === "-c")
1444
- i++;
1445
- if (w[i] !== "push")
1446
- continue;
1447
- found = true;
1448
- const positional = [];
1449
- for (let j = i + 1; j < w.length; j++) {
1450
- const arg = w[j];
1451
- if (arg === "--")
1452
- continue;
1453
- if (arg.startsWith("-")) {
1454
- if (VALUE_OPTIONS.has(arg))
1455
- j++;
1456
- continue;
1457
- }
1458
- positional.push(arg);
1459
- }
1460
- const refspecs = positional.slice(1);
1461
- if (refspecs.length === 0)
1462
- targets.push(PUSH_HEAD);
1463
- for (const spec of refspecs)
1464
- targets.push(branchOf(spec.includes(":") ? spec.slice(spec.indexOf(":") + 1) : spec));
1465
1467
  }
1466
- return found ? [...new Set(targets)] : void 0;
1467
1468
  }
1468
- function gitDirAt(dir) {
1469
- let at = path7.resolve(dir);
1470
- for (; ; ) {
1471
- const dotGit = path7.join(at, ".git");
1472
- try {
1473
- const stat = fs9.statSync(dotGit);
1474
- if (stat.isDirectory())
1475
- return dotGit;
1476
- const m = /^gitdir:\s*(.+)$/m.exec(fs9.readFileSync(dotGit, "utf8"));
1477
- return m ? path7.resolve(at, m[1].trim()) : void 0;
1478
- } catch {
1479
- }
1480
- const up = path7.dirname(at);
1481
- if (up === at)
1482
- return void 0;
1483
- at = up;
1484
- }
1469
+
1470
+ // packages/core/dist/prs.js
1471
+ import * as fs8 from "node:fs";
1472
+ import * as path6 from "node:path";
1473
+ function prsDir() {
1474
+ return path6.join(lobstahHome(), "prs");
1485
1475
  }
1486
- function currentBranchAt(dir) {
1487
- const gitDir = gitDirAt(dir);
1488
- if (!gitDir)
1489
- return void 0;
1476
+ function prRecordFile(key) {
1477
+ const ref = parsePrRef(key);
1478
+ if (!ref)
1479
+ throw new Error(`not a PR key: ${key}`);
1480
+ const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "_");
1481
+ return path6.join(prsDir(), `${safe(ref.owner)}__${safe(ref.repo)}__${ref.number}.json`);
1482
+ }
1483
+ function readPr(key) {
1490
1484
  try {
1491
- const m = /^ref:\s*refs\/heads\/(.+)$/m.exec(fs9.readFileSync(path7.join(gitDir, "HEAD"), "utf8"));
1492
- return m ? m[1].trim() : void 0;
1485
+ return JSON.parse(fs8.readFileSync(prRecordFile(key), "utf8"));
1493
1486
  } catch {
1494
1487
  return void 0;
1495
1488
  }
1496
1489
  }
1497
- function resolvePushTargets(targets, cwd) {
1498
- const head = cwd ? currentBranchAt(cwd) : void 0;
1499
- return [...new Set(targets.flatMap((t) => t === PUSH_HEAD ? head ? [head] : [] : [t]))];
1490
+ function readPrs() {
1491
+ let files;
1492
+ try {
1493
+ files = fs8.readdirSync(prsDir()).filter((f) => f.endsWith(".json"));
1494
+ } catch {
1495
+ return [];
1496
+ }
1497
+ return files.flatMap((f) => {
1498
+ try {
1499
+ const r = JSON.parse(fs8.readFileSync(path6.join(prsDir(), f), "utf8"));
1500
+ return typeof r.key === "string" && typeof r.url === "string" ? [r] : [];
1501
+ } catch {
1502
+ return [];
1503
+ }
1504
+ });
1500
1505
  }
1501
- function recordPush(id, lane, branches, at = (/* @__PURE__ */ new Date()).toISOString()) {
1502
- if (branches.length === 0)
1503
- return;
1504
- const kept = (readEvidence(id, lane).pushes ?? []).filter((p) => !branches.includes(p.branch));
1505
- const pushes = [...kept, ...branches.map((branch) => ({ branch, at }))].slice(-MAX_PUSHES);
1506
- mergeEvidence(id, lane, { pushes });
1506
+
1507
+ // packages/core/dist/attachments.js
1508
+ function attachmentBlock(attachments) {
1509
+ if (attachments.length === 0)
1510
+ return "";
1511
+ return [
1512
+ "--- ATTACHMENTS ---",
1513
+ ...attachments.map((a) => `${a.name} (${a.type}, ${a.bytes} bytes) at ${a.path}`),
1514
+ "Images and PDFs can be read directly with your file tools."
1515
+ ].join("\n");
1507
1516
  }
1508
1517
 
1509
1518
  // packages/core/dist/worktrees.js
1510
- import * as fs10 from "node:fs";
1511
- import * as path8 from "node:path";
1519
+ import * as fs9 from "node:fs";
1520
+ import * as path7 from "node:path";
1512
1521
  var LANES = ["work", "chore"];
1513
1522
  function worktreePath(id) {
1514
- return path8.join(lobstahHome(), "worktrees", id);
1523
+ return path7.join(lobstahHome(), "worktrees", id);
1515
1524
  }
1516
1525
  function readRecord(file) {
1517
1526
  try {
1518
- const r = JSON.parse(fs10.readFileSync(file, "utf8"));
1527
+ const r = JSON.parse(fs9.readFileSync(file, "utf8"));
1519
1528
  return typeof r.path === "string" ? r : void 0;
1520
1529
  } catch {
1521
1530
  return void 0;
1522
1531
  }
1523
1532
  }
1524
1533
  function ownerOfPath(p) {
1525
- const rel = path8.relative(path8.join(lobstahHome(), "worktrees"), p);
1526
- if (!rel || rel.startsWith("..") || path8.isAbsolute(rel))
1534
+ const rel = path7.relative(path7.join(lobstahHome(), "worktrees"), p);
1535
+ if (!rel || rel.startsWith("..") || path7.isAbsolute(rel))
1527
1536
  return void 0;
1528
1537
  return rel.split(/[\\/]/)[0];
1529
1538
  }
@@ -1536,7 +1545,7 @@ function dispatchWorktree(id, lane) {
1536
1545
  }
1537
1546
  const dirs = laneDirs(l);
1538
1547
  for (const dir of [dirs.active, dirs.done]) {
1539
- const rec = readRecord(path8.join(dir, id, "worktree.json"));
1548
+ const rec = readRecord(path7.join(dir, id, "worktree.json"));
1540
1549
  if (rec) {
1541
1550
  const owner = rec.of ?? ownerOfPath(rec.path) ?? id;
1542
1551
  return { path: rec.path, owner, reused: owner !== id };
@@ -1548,7 +1557,7 @@ function dispatchWorktree(id, lane) {
1548
1557
  function laneOf(id) {
1549
1558
  for (const l of LANES) {
1550
1559
  const d = laneDirs(l);
1551
- if (fs10.existsSync(path8.join(d.queue, `${id}.json`)) || fs10.existsSync(path8.join(d.active, id)) || fs10.existsSync(path8.join(d.done, id))) {
1560
+ if (fs9.existsSync(path7.join(d.queue, `${id}.json`)) || fs9.existsSync(path7.join(d.active, id)) || fs9.existsSync(path7.join(d.done, id))) {
1552
1561
  return l;
1553
1562
  }
1554
1563
  }
@@ -1568,174 +1577,478 @@ function followUpAncestors(id, lane) {
1568
1577
  function chainWorktree(followUp) {
1569
1578
  for (const id of [followUp, ...followUpAncestors(followUp)]) {
1570
1579
  const wt = dispatchWorktree(id, laneOf(id));
1571
- if (fs10.existsSync(wt.path))
1580
+ if (fs9.existsSync(wt.path))
1572
1581
  return { ...wt, from: id };
1573
1582
  }
1574
1583
  return void 0;
1575
1584
  }
1576
1585
  function worktreeGitDir(wt) {
1577
- const dotGit = path8.join(wt, ".git");
1586
+ const dotGit = path7.join(wt, ".git");
1578
1587
  try {
1579
- const st = fs10.statSync(dotGit);
1588
+ const st = fs9.statSync(dotGit);
1580
1589
  if (st.isDirectory())
1581
1590
  return dotGit;
1582
- const m = /^gitdir:\s*(.+?)\s*$/m.exec(fs10.readFileSync(dotGit, "utf8"));
1583
- return m ? path8.resolve(wt, m[1]) : void 0;
1591
+ const m = /^gitdir:\s*(.+?)\s*$/m.exec(fs9.readFileSync(dotGit, "utf8"));
1592
+ return m ? path7.resolve(wt, m[1]) : void 0;
1584
1593
  } catch {
1585
1594
  return void 0;
1586
1595
  }
1587
1596
  }
1588
1597
  function worktreeLockFile(wt) {
1589
1598
  const gitDir = worktreeGitDir(wt);
1590
- return gitDir ? path8.join(gitDir, "lobstah.lock") : void 0;
1599
+ return gitDir ? path7.join(gitDir, "lobstah.lock") : void 0;
1591
1600
  }
1592
1601
  function readWorktreeLock(wt) {
1593
1602
  const file = worktreeLockFile(wt);
1594
1603
  if (!file)
1595
1604
  return void 0;
1596
1605
  try {
1597
- return JSON.parse(fs10.readFileSync(file, "utf8"));
1606
+ return JSON.parse(fs9.readFileSync(file, "utf8"));
1607
+ } catch {
1608
+ return void 0;
1609
+ }
1610
+ }
1611
+ function lockIsLive(lock) {
1612
+ return fs9.existsSync(path7.join(laneDirs(lock.lane).active, lock.id));
1613
+ }
1614
+ function worktreeHolder(wt) {
1615
+ const lock = readWorktreeLock(wt);
1616
+ return lock && lockIsLive(lock) ? lock : void 0;
1617
+ }
1618
+ function acquireWorktreeLock(wt, id, lane) {
1619
+ const file = worktreeLockFile(wt);
1620
+ const mine = { id, lane, pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() };
1621
+ if (!file)
1622
+ return { id: "(no git dir)", lane, pid: 0, at: mine.at };
1623
+ for (let attempt = 0; attempt < 3; attempt++) {
1624
+ try {
1625
+ fs9.writeFileSync(file, JSON.stringify(mine, null, 2), { flag: "wx" });
1626
+ return void 0;
1627
+ } catch (err) {
1628
+ if (err.code !== "EEXIST")
1629
+ throw err;
1630
+ }
1631
+ let raw;
1632
+ try {
1633
+ raw = fs9.readFileSync(file, "utf8");
1634
+ } catch {
1635
+ continue;
1636
+ }
1637
+ let held2;
1638
+ try {
1639
+ held2 = JSON.parse(raw);
1640
+ } catch {
1641
+ held2 = void 0;
1642
+ }
1643
+ if (held2?.id === id) {
1644
+ fs9.writeFileSync(file, JSON.stringify(mine, null, 2));
1645
+ return void 0;
1646
+ }
1647
+ if (held2 && lockIsLive(held2))
1648
+ return held2;
1649
+ try {
1650
+ if (fs9.readFileSync(file, "utf8") === raw)
1651
+ fs9.rmSync(file, { force: true });
1652
+ } catch {
1653
+ }
1654
+ }
1655
+ const held = readWorktreeLock(wt);
1656
+ return held ?? { id: "(contended)", lane, pid: 0, at: mine.at };
1657
+ }
1658
+ function releaseWorktreeLock(wt, id) {
1659
+ const file = worktreeLockFile(wt);
1660
+ if (!file)
1661
+ return;
1662
+ if (readWorktreeLock(wt)?.id === id)
1663
+ fs9.rmSync(file, { force: true });
1664
+ }
1665
+
1666
+ // packages/core/dist/disk.js
1667
+ var GB = 1024 ** 3;
1668
+
1669
+ // packages/core/dist/watch.js
1670
+ import * as fs10 from "node:fs";
1671
+ import * as path8 from "node:path";
1672
+ function watchesDir() {
1673
+ return path8.join(lobstahHome(), "watches");
1674
+ }
1675
+ function slug(key) {
1676
+ return key.replace(/[^A-Za-z0-9._-]+/g, "-");
1677
+ }
1678
+ function watchPath(key) {
1679
+ return path8.join(watchesDir(), `${slug(key)}.json`);
1680
+ }
1681
+ function readWatch(key) {
1682
+ try {
1683
+ return JSON.parse(fs10.readFileSync(watchPath(key), "utf8"));
1684
+ } catch {
1685
+ return void 0;
1686
+ }
1687
+ }
1688
+
1689
+ // packages/core/dist/soak.js
1690
+ function regPath(trapId) {
1691
+ return path9.join(soakingDir(), `${trapId}.json`);
1692
+ }
1693
+ function readTrap(trapId) {
1694
+ try {
1695
+ return JSON.parse(fs11.readFileSync(regPath(trapId), "utf8"));
1696
+ } catch {
1697
+ return void 0;
1698
+ }
1699
+ }
1700
+ function listTraps() {
1701
+ try {
1702
+ return fs11.readdirSync(soakingDir()).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs11.readFileSync(path9.join(soakingDir(), f), "utf8"))).filter((r) => typeof r.trapId === "string");
1703
+ } catch {
1704
+ return [];
1705
+ }
1706
+ }
1707
+ function claimPath(id, lane) {
1708
+ return path9.join(laneDirs(lane).active, id, "claim.json");
1709
+ }
1710
+ function readSessionClaim(id, lane) {
1711
+ try {
1712
+ return JSON.parse(fs11.readFileSync(claimPath(id, lane), "utf8"));
1713
+ } catch {
1714
+ return void 0;
1715
+ }
1716
+ }
1717
+
1718
+ // packages/core/dist/stats.js
1719
+ var DAILY_RETENTION_DAYS = 400;
1720
+ function statsPath() {
1721
+ return path10.join(lobstahHome(), "stats.json");
1722
+ }
1723
+ function shiftDay(day, days) {
1724
+ const [y, m, d] = day.split("-").map(Number);
1725
+ return localDay(new Date(y, m - 1, d - days));
1726
+ }
1727
+ function localDay(at = Date.now()) {
1728
+ const d = new Date(at);
1729
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1730
+ }
1731
+ function readCatchFacts(stateDir, id) {
1732
+ let last;
1733
+ try {
1734
+ for (const line of fs12.readFileSync(path10.join(stateDir, `${id}.status`), "utf8").split("\n")) {
1735
+ if (!line.trim())
1736
+ continue;
1737
+ try {
1738
+ last = JSON.parse(line);
1739
+ } catch {
1740
+ }
1741
+ }
1742
+ } catch {
1743
+ return { done: false };
1744
+ }
1745
+ if (last?.verb !== "done")
1746
+ return { done: false };
1747
+ let trap;
1748
+ let worker = workerProfile();
1749
+ try {
1750
+ const evidence = JSON.parse(fs12.readFileSync(path10.join(stateDir, `${id}.evidence`), "utf8"));
1751
+ const to = evidence.deliveredTo;
1752
+ if (typeof to === "string" && to.startsWith("wt:"))
1753
+ trap = to;
1754
+ const reg = trap ? readTrap(trap.slice("wt:".length)) : void 0;
1755
+ worker = sanitizeWorker(reg ?? evidence.worker ?? workerProfile({ harness: evidence.harness }));
1756
+ } catch {
1757
+ }
1758
+ return { done: true, worker, ...typeof last.at === "string" ? { at: last.at } : {}, ...trap ? { trap } : {} };
1759
+ }
1760
+ function emptyStore(now2) {
1761
+ return {
1762
+ version: 1,
1763
+ totalCatches: 0,
1764
+ perTrap: {},
1765
+ day: localDay(now2),
1766
+ catchesToday: 0,
1767
+ counted: [],
1768
+ daily: {},
1769
+ utc: { date: new Date(now2).toISOString().slice(0, 10), catches: 0, perTrap: {}, trapWorkers: {}, byWorker: [] }
1770
+ };
1771
+ }
1772
+ function catchDay(facts, now2) {
1773
+ return facts.at && Number.isFinite(Date.parse(facts.at)) ? localDay(facts.at) : localDay(now2);
1774
+ }
1775
+ function addDaily(store, day, now2) {
1776
+ if (day < shiftDay(localDay(now2), DAILY_RETENTION_DAYS - 1))
1777
+ return;
1778
+ store.daily[day] = (store.daily[day] ?? 0) + 1;
1779
+ }
1780
+ function pruneDaily(store, now2) {
1781
+ const oldest = shiftDay(localDay(now2), DAILY_RETENTION_DAYS - 1);
1782
+ for (const day of Object.keys(store.daily))
1783
+ if (day < oldest)
1784
+ delete store.daily[day];
1785
+ }
1786
+ function add(store, facts, now2) {
1787
+ store.totalCatches++;
1788
+ if (facts.trap)
1789
+ store.perTrap[facts.trap] = (store.perTrap[facts.trap] ?? 0) + 1;
1790
+ const today = localDay(now2);
1791
+ if (store.day !== today) {
1792
+ store.day = today;
1793
+ store.catchesToday = 0;
1794
+ }
1795
+ const day = catchDay(facts, now2);
1796
+ if (day === today)
1797
+ store.catchesToday++;
1798
+ addDaily(store, day, now2);
1799
+ pruneDaily(store, now2);
1800
+ const at = facts.at && Number.isFinite(Date.parse(facts.at)) ? facts.at : void 0;
1801
+ const utcDay = new Date(now2).toISOString().slice(0, 10);
1802
+ if (store.utc?.date !== utcDay)
1803
+ store.utc = { date: utcDay, catches: 0, perTrap: {}, trapWorkers: {}, byWorker: [] };
1804
+ if ((at ? new Date(at).toISOString().slice(0, 10) : utcDay) === utcDay) {
1805
+ store.utc.catches++;
1806
+ const worker = sanitizeWorker(facts.worker);
1807
+ if (facts.trap) {
1808
+ store.utc.perTrap[facts.trap] = (store.utc.perTrap[facts.trap] ?? 0) + 1;
1809
+ (store.utc.trapWorkers ??= {})[facts.trap] = worker;
1810
+ } else {
1811
+ const rows = store.utc.byWorker ??= [];
1812
+ const key = JSON.stringify(worker);
1813
+ const row = rows.find((r) => JSON.stringify(sanitizeWorker(r)) === key);
1814
+ if (row)
1815
+ row.today++;
1816
+ else
1817
+ rows.push({ ...worker, today: 1 });
1818
+ }
1819
+ }
1820
+ }
1821
+ function backfillSources() {
1822
+ const live = ["work", "chore"].map((lane) => ({ dir: laneDirs(lane).state, live: true }));
1823
+ const backups = readDirIfPresent(lobstahHome()).filter((name) => name.startsWith("state-backup-")).sort().map((name) => ({ dir: path10.join(lobstahHome(), name), live: false })).filter((s) => fs12.statSync(s.dir).isDirectory());
1824
+ return [...live, ...backups];
1825
+ }
1826
+ function backfillStats(now2 = Date.now(), alreadyCounted) {
1827
+ const store = emptyStore(now2);
1828
+ const seen = /* @__PURE__ */ new Set();
1829
+ for (const { dir, live } of backfillSources()) {
1830
+ for (const file of readDirIfPresent(dir).sort()) {
1831
+ if (!file.endsWith(".status"))
1832
+ continue;
1833
+ const id = file.slice(0, -".status".length);
1834
+ if (seen.has(id))
1835
+ continue;
1836
+ seen.add(id);
1837
+ if (live && alreadyCounted && !alreadyCounted.has(id))
1838
+ continue;
1839
+ const facts = readCatchFacts(dir, id);
1840
+ if (!facts.done)
1841
+ continue;
1842
+ add(store, facts, now2);
1843
+ if (live)
1844
+ store.counted.push(id);
1845
+ }
1846
+ }
1847
+ return store;
1848
+ }
1849
+ function backfillDaily(store, now2 = Date.now()) {
1850
+ const daily = {};
1851
+ const counted = new Set(store.counted);
1852
+ const seen = /* @__PURE__ */ new Set();
1853
+ const into = { ...store, daily };
1854
+ for (const { dir, live } of backfillSources()) {
1855
+ for (const file of readDirIfPresent(dir).sort()) {
1856
+ if (!file.endsWith(".status"))
1857
+ continue;
1858
+ const id = file.slice(0, -".status".length);
1859
+ if (seen.has(id))
1860
+ continue;
1861
+ seen.add(id);
1862
+ if (live && !counted.has(id))
1863
+ continue;
1864
+ const facts = readCatchFacts(dir, id);
1865
+ if (facts.done)
1866
+ addDaily(into, catchDay(facts, now2), now2);
1867
+ }
1868
+ }
1869
+ pruneDaily(into, now2);
1870
+ return into;
1871
+ }
1872
+ var isDaily = (v) => !!v && typeof v === "object" && !Array.isArray(v) && Object.values(v).every((n) => typeof n === "number");
1873
+ function parseStore(text) {
1874
+ try {
1875
+ const s = JSON.parse(text);
1876
+ if (s.version !== 1 || typeof s.totalCatches !== "number" || typeof s.catchesToday !== "number" || typeof s.day !== "string")
1877
+ return void 0;
1878
+ return {
1879
+ version: 1,
1880
+ totalCatches: s.totalCatches,
1881
+ perTrap: s.perTrap ?? {},
1882
+ day: s.day,
1883
+ catchesToday: s.catchesToday,
1884
+ counted: s.counted ?? [],
1885
+ ...isDaily(s.daily) ? { daily: s.daily } : {},
1886
+ ...s.utc ? { utc: s.utc } : {}
1887
+ };
1598
1888
  } catch {
1599
1889
  return void 0;
1600
1890
  }
1601
1891
  }
1602
- function lockIsLive(lock) {
1603
- return fs10.existsSync(path8.join(laneDirs(lock.lane).active, lock.id));
1604
- }
1605
- function worktreeHolder(wt) {
1606
- const lock = readWorktreeLock(wt);
1607
- return lock && lockIsLive(lock) ? lock : void 0;
1892
+ var complete2 = (s) => !!s && s.daily !== void 0;
1893
+ function writeStore(store) {
1894
+ const file = statsPath();
1895
+ fs12.mkdirSync(path10.dirname(file), { recursive: true });
1896
+ const tmp = `${file}.tmp-${process.pid}`;
1897
+ fs12.writeFileSync(tmp, JSON.stringify(store));
1898
+ fs12.renameSync(tmp, file);
1608
1899
  }
1609
- function acquireWorktreeLock(wt, id, lane) {
1610
- const file = worktreeLockFile(wt);
1611
- const mine = { id, lane, pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() };
1612
- if (!file)
1613
- return { id: "(no git dir)", lane, pid: 0, at: mine.at };
1614
- for (let attempt = 0; attempt < 3; attempt++) {
1900
+ function withStatsLock(action) {
1901
+ fs12.mkdirSync(lobstahHome(), { recursive: true });
1902
+ const lock = `${statsPath()}.lock`;
1903
+ const deadline = Date.now() + 1e4;
1904
+ for (; ; ) {
1615
1905
  try {
1616
- fs10.writeFileSync(file, JSON.stringify(mine, null, 2), { flag: "wx" });
1617
- return void 0;
1906
+ fs12.mkdirSync(lock);
1907
+ break;
1618
1908
  } catch (err) {
1619
1909
  if (err.code !== "EEXIST")
1620
1910
  throw err;
1621
- }
1622
- let raw;
1623
- try {
1624
- raw = fs10.readFileSync(file, "utf8");
1625
- } catch {
1626
- continue;
1627
- }
1628
- let held2;
1629
- try {
1630
- held2 = JSON.parse(raw);
1631
- } catch {
1632
- held2 = void 0;
1633
- }
1634
- if (held2?.id === id) {
1635
- fs10.writeFileSync(file, JSON.stringify(mine, null, 2));
1636
- return void 0;
1637
- }
1638
- if (held2 && lockIsLive(held2))
1639
- return held2;
1640
- try {
1641
- if (fs10.readFileSync(file, "utf8") === raw)
1642
- fs10.rmSync(file, { force: true });
1643
- } catch {
1911
+ try {
1912
+ if (Date.now() - fs12.statSync(lock).mtimeMs > 12e4)
1913
+ fs12.rmdirSync(lock);
1914
+ } catch {
1915
+ }
1916
+ if (Date.now() >= deadline)
1917
+ throw new Error("stats store locked");
1918
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
1644
1919
  }
1645
1920
  }
1646
- const held = readWorktreeLock(wt);
1647
- return held ?? { id: "(contended)", lane, pid: 0, at: mine.at };
1648
- }
1649
- function releaseWorktreeLock(wt, id) {
1650
- const file = worktreeLockFile(wt);
1651
- if (!file)
1652
- return;
1653
- if (readWorktreeLock(wt)?.id === id)
1654
- fs10.rmSync(file, { force: true });
1655
- }
1656
-
1657
- // packages/core/dist/soak.js
1658
- function regPath(trapId) {
1659
- return path9.join(soakingDir(), `${trapId}.json`);
1660
- }
1661
- function readTrap(trapId) {
1662
1921
  try {
1663
- return JSON.parse(fs11.readFileSync(regPath(trapId), "utf8"));
1664
- } catch {
1665
- return void 0;
1922
+ return action();
1923
+ } finally {
1924
+ fs12.rmdirSync(lock);
1666
1925
  }
1667
1926
  }
1668
- function listTraps() {
1927
+ function loadOrBackfill(now2) {
1928
+ let text;
1669
1929
  try {
1670
- return fs11.readdirSync(soakingDir()).filter((f) => f.endsWith(".json")).map((f) => JSON.parse(fs11.readFileSync(path9.join(soakingDir(), f), "utf8"))).filter((r) => typeof r.trapId === "string");
1671
- } catch {
1672
- return [];
1930
+ text = fs12.readFileSync(statsPath(), "utf8");
1931
+ } catch (err) {
1932
+ if (err.code !== "ENOENT")
1933
+ throw err;
1673
1934
  }
1674
- }
1675
- function claimPath(id, lane) {
1676
- return path9.join(laneDirs(lane).active, id, "claim.json");
1677
- }
1678
- function readSessionClaim(id, lane) {
1679
- try {
1680
- return JSON.parse(fs11.readFileSync(claimPath(id, lane), "utf8"));
1681
- } catch {
1682
- return void 0;
1935
+ const saved = text === void 0 ? void 0 : parseStore(text);
1936
+ if (complete2(saved) && saved.utc?.trapWorkers !== void 0 && saved.utc.byWorker !== void 0)
1937
+ return saved;
1938
+ if (saved) {
1939
+ const store2 = complete2(saved) ? saved : backfillDaily({ ...saved, daily: {} }, now2);
1940
+ if (!store2.utc) {
1941
+ store2.utc = backfillStats(now2, new Set(store2.counted)).utc;
1942
+ }
1943
+ if (store2.utc.trapWorkers === void 0 || store2.utc.byWorker === void 0) {
1944
+ const history = backfillStats(now2, new Set(store2.counted)).utc;
1945
+ if (history?.date === store2.utc.date) {
1946
+ store2.utc.trapWorkers ??= history.trapWorkers ?? {};
1947
+ store2.utc.byWorker ??= history.byWorker ?? [];
1948
+ } else {
1949
+ store2.utc.trapWorkers ??= {};
1950
+ store2.utc.byWorker ??= [];
1951
+ }
1952
+ }
1953
+ writeStore(store2);
1954
+ return store2;
1683
1955
  }
1956
+ if (text !== void 0)
1957
+ fs12.renameSync(statsPath(), `${statsPath()}.unreadable-${now2}`);
1958
+ const store = backfillStats(now2);
1959
+ writeStore(store);
1960
+ return store;
1961
+ }
1962
+ function recordCatch(id, lane, now2 = Date.now()) {
1963
+ withStatsLock(() => {
1964
+ const store = loadOrBackfill(now2);
1965
+ if (store.counted.includes(id))
1966
+ return;
1967
+ const facts = readCatchFacts(laneDirs(lane).state, id);
1968
+ if (!facts.done)
1969
+ return;
1970
+ add(store, facts, now2);
1971
+ store.counted.push(id);
1972
+ writeStore(store);
1973
+ });
1684
1974
  }
1685
1975
 
1976
+ // packages/core/dist/decisions.js
1977
+ var DECISION_DETAIL_MAX = 64 * 1024;
1978
+ var ANSWER_TYPES = {
1979
+ ".png": "image/png",
1980
+ ".jpg": "image/jpeg",
1981
+ ".jpeg": "image/jpeg",
1982
+ ".gif": "image/gif",
1983
+ ".webp": "image/webp",
1984
+ ".pdf": "application/pdf",
1985
+ ".txt": "text/plain",
1986
+ ".md": "text/markdown",
1987
+ ".csv": "text/csv",
1988
+ ".json": "application/json",
1989
+ ".log": "text/plain",
1990
+ ".diff": "text/plain",
1991
+ ".patch": "text/plain",
1992
+ ".yaml": "text/plain",
1993
+ ".yml": "text/plain",
1994
+ ".toml": "text/plain",
1995
+ ".zip": "application/zip"
1996
+ };
1997
+ var ANSWER_EXTENSIONS = Object.keys(ANSWER_TYPES);
1998
+
1686
1999
  // packages/core/dist/inbox.js
1687
- import * as fs12 from "node:fs";
1688
- import * as path10 from "node:path";
2000
+ import * as fs13 from "node:fs";
2001
+ import * as path11 from "node:path";
1689
2002
  var metaName = (file) => file.replace(/\.msg$/, ".meta.json");
1690
2003
  function inboxDir(id, lane) {
1691
- return path10.join(laneDirs(lane).inbox, id);
2004
+ return path11.join(laneDirs(lane).inbox, id);
1692
2005
  }
1693
2006
  function unhandled(id, lane) {
1694
2007
  const dir = inboxDir(id, lane);
1695
- return readDirIfPresent(dir).filter((f) => f.endsWith(".msg")).sort().map((f) => ({ file: f, text: fs12.readFileSync(path10.join(dir, f), "utf8") }));
2008
+ return readDirIfPresent(dir).filter((f) => f.endsWith(".msg")).sort().map((f) => ({ file: f, text: fs13.readFileSync(path11.join(dir, f), "utf8") }));
1696
2009
  }
1697
2010
  function acknowledge(id, lane, file) {
1698
2011
  const dir = inboxDir(id, lane);
1699
- fs12.mkdirSync(path10.join(dir, "handled"), { recursive: true });
1700
- fs12.renameSync(path10.join(dir, file), path10.join(dir, "handled", file));
2012
+ fs13.mkdirSync(path11.join(dir, "handled"), { recursive: true });
2013
+ fs13.renameSync(path11.join(dir, file), path11.join(dir, "handled", file));
1701
2014
  try {
1702
- fs12.renameSync(path10.join(dir, metaName(file)), path10.join(dir, "handled", metaName(file)));
2015
+ fs13.renameSync(path11.join(dir, metaName(file)), path11.join(dir, "handled", metaName(file)));
1703
2016
  } catch {
1704
2017
  }
1705
2018
  }
1706
2019
 
1707
2020
  // packages/core/dist/events.js
1708
- import * as fs13 from "node:fs";
2021
+ import * as fs14 from "node:fs";
1709
2022
  function appendEvent(id, lane, ev) {
1710
- fs13.appendFileSync(eventsPath(id, lane), `${JSON.stringify(ev)}
2023
+ fs14.appendFileSync(eventsPath(id, lane), `${JSON.stringify(ev)}
1711
2024
  `);
1712
2025
  }
1713
2026
  function touchEvents(id, lane, at = /* @__PURE__ */ new Date()) {
1714
2027
  const file = eventsPath(id, lane);
1715
2028
  try {
1716
- fs13.utimesSync(file, at, at);
2029
+ fs14.utimesSync(file, at, at);
1717
2030
  } catch {
1718
- fs13.appendFileSync(file, "");
1719
- fs13.utimesSync(file, at, at);
2031
+ fs14.appendFileSync(file, "");
2032
+ fs14.utimesSync(file, at, at);
1720
2033
  }
1721
2034
  }
1722
2035
 
1723
2036
  // packages/core/dist/harness.js
1724
- import * as fs14 from "node:fs";
1725
- import * as path11 from "node:path";
2037
+ import * as fs15 from "node:fs";
2038
+ import * as path12 from "node:path";
1726
2039
  import { createRequire } from "node:module";
1727
2040
  function resolveOnPath(bin, pathVar = process.env.PATH) {
1728
2041
  if (!pathVar)
1729
2042
  return void 0;
1730
2043
  const names = process.platform === "win32" ? [bin, `${bin}.exe`, `${bin}.cmd`, `${bin}.bat`] : [bin];
1731
- for (const dir of pathVar.split(path11.delimiter)) {
2044
+ for (const dir of pathVar.split(path12.delimiter)) {
1732
2045
  if (!dir)
1733
2046
  continue;
1734
2047
  for (const name of names) {
1735
- const candidate = path11.join(dir, name);
2048
+ const candidate = path12.join(dir, name);
1736
2049
  try {
1737
- fs14.accessSync(candidate, fs14.constants.X_OK);
1738
- if (fs14.statSync(candidate).isFile())
2050
+ fs15.accessSync(candidate, fs15.constants.X_OK);
2051
+ if (fs15.statSync(candidate).isFile())
1739
2052
  return candidate;
1740
2053
  } catch {
1741
2054
  }
@@ -1756,8 +2069,8 @@ function codexInvocation(argv) {
1756
2069
  } catch {
1757
2070
  }
1758
2071
  for (const dir of require_.resolve.paths("@openai/codex") ?? []) {
1759
- const cli = path11.join(dir, "@openai", "codex", "bin", "codex.js");
1760
- if (fs14.existsSync(cli))
2072
+ const cli = path12.join(dir, "@openai", "codex", "bin", "codex.js");
2073
+ if (fs15.existsSync(cli))
1761
2074
  return { file: process.execPath, argv: [cli, ...argv] };
1762
2075
  }
1763
2076
  return void 0;
@@ -1769,109 +2082,6 @@ var COMPILED_BINARY = (() => {
1769
2082
  return bun !== void 0 && String(bun.main ?? "").includes("$bunfs");
1770
2083
  })();
1771
2084
 
1772
- // packages/core/dist/pr.js
1773
- import { spawnSync } from "node:child_process";
1774
- function parsePrRef(s) {
1775
- 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());
1776
- if (!m)
1777
- return void 0;
1778
- const [, owner, repo, n] = m;
1779
- const number = Number(n);
1780
- return { owner, repo, number, key: `pr:${owner}/${repo}#${number}`, url: `https://github.com/${owner}/${repo}/pull/${number}` };
1781
- }
1782
- var PR_VIEW_FIELDS = "title,state,isDraft,headRefOid,baseRefName,baseRefOid,headRefName,mergeStateStatus,reviewDecision,statusCheckRollup,mergedAt,closedAt,updatedAt,reviews";
1783
- var PR_VIEW_FIELDS_NO_CHECKS = PR_VIEW_FIELDS.split(",").filter((f) => f !== "statusCheckRollup").join(",");
1784
- var FORBIDDEN = /resource not accessible by (integration|personal access token)/i;
1785
- function ghPrView(ref) {
1786
- const view1 = (fields2) => spawnSync("gh", ["pr", "view", String(ref.number), "--repo", `${ref.owner}/${ref.repo}`, "--json", fields2], {
1787
- encoding: "utf8",
1788
- timeout: 6e4
1789
- });
1790
- const reason = (r) => firstMeaningfulLine(r.stderr) ?? `gh exited ${r.status}`;
1791
- const unknownBase = (r) => r.status !== 0 && /unknown json field.*baseRefOid/i.test(r.stderr ?? "");
1792
- const withoutBase = (fields2) => fields2.split(",").filter((f) => f !== "baseRefOid").join(",");
1793
- let fields = PR_VIEW_FIELDS;
1794
- let res = view1(fields);
1795
- if (unknownBase(res))
1796
- res = view1(fields = withoutBase(fields));
1797
- if (res.error)
1798
- throw new Error(`gh: ${res.error.message}`);
1799
- let checksError;
1800
- if (res.status !== 0 && FORBIDDEN.test(res.stderr ?? "")) {
1801
- checksError = reason(res);
1802
- const retry = view1(fields === PR_VIEW_FIELDS ? PR_VIEW_FIELDS_NO_CHECKS : withoutBase(PR_VIEW_FIELDS_NO_CHECKS));
1803
- if (retry.error)
1804
- throw new Error(`gh: ${retry.error.message}`);
1805
- if (retry.status !== 0) {
1806
- throw new Error(FORBIDDEN.test(retry.stderr ?? "") ? `${reason(retry)} (fails even without check results)` : reason(retry));
1807
- }
1808
- res = retry;
1809
- }
1810
- if (res.status !== 0)
1811
- throw new Error(reason(res));
1812
- const view = JSON.parse(res.stdout);
1813
- if (checksError)
1814
- view.checksError = checksError;
1815
- if (view.state === "OPEN") {
1816
- const threads = ghUnresolvedThreads(ref);
1817
- if (threads !== void 0)
1818
- view.unresolvedThreads = threads;
1819
- }
1820
- return view;
1821
- }
1822
- var THREADS_QUERY = "query($owner:String!,$repo:String!,$n:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$n){reviewThreads(first:100){nodes{isResolved}}}}}";
1823
- function ghUnresolvedThreads(ref) {
1824
- const res = spawnSync("gh", ["api", "graphql", "-f", `query=${THREADS_QUERY}`, "-f", `owner=${ref.owner}`, "-f", `repo=${ref.repo}`, "-F", `n=${ref.number}`], { encoding: "utf8", timeout: 6e4 });
1825
- if (res.error || res.status !== 0)
1826
- return void 0;
1827
- return parseUnresolvedThreads(res.stdout);
1828
- }
1829
- function parseUnresolvedThreads(stdout) {
1830
- try {
1831
- const nodes = JSON.parse(stdout).data?.repository?.pullRequest?.reviewThreads?.nodes;
1832
- return Array.isArray(nodes) ? nodes.filter((t) => t.isResolved === false).length : void 0;
1833
- } catch {
1834
- return void 0;
1835
- }
1836
- }
1837
-
1838
- // packages/core/dist/prs.js
1839
- import * as fs15 from "node:fs";
1840
- import * as path12 from "node:path";
1841
- function prsDir() {
1842
- return path12.join(lobstahHome(), "prs");
1843
- }
1844
- function prRecordFile(key) {
1845
- const ref = parsePrRef(key);
1846
- if (!ref)
1847
- throw new Error(`not a PR key: ${key}`);
1848
- const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "_");
1849
- return path12.join(prsDir(), `${safe(ref.owner)}__${safe(ref.repo)}__${ref.number}.json`);
1850
- }
1851
- function readPr(key) {
1852
- try {
1853
- return JSON.parse(fs15.readFileSync(prRecordFile(key), "utf8"));
1854
- } catch {
1855
- return void 0;
1856
- }
1857
- }
1858
- function readPrs() {
1859
- let files;
1860
- try {
1861
- files = fs15.readdirSync(prsDir()).filter((f) => f.endsWith(".json"));
1862
- } catch {
1863
- return [];
1864
- }
1865
- return files.flatMap((f) => {
1866
- try {
1867
- const r = JSON.parse(fs15.readFileSync(path12.join(prsDir(), f), "utf8"));
1868
- return typeof r.key === "string" && typeof r.url === "string" ? [r] : [];
1869
- } catch {
1870
- return [];
1871
- }
1872
- });
1873
- }
1874
-
1875
2085
  // packages/core/dist/chain-pr.js
1876
2086
  function chainPr(id, lane) {
1877
2087
  const ancestry = [];
@@ -2177,7 +2387,7 @@ function pumpClaudeMessage(msg, push, onSession) {
2177
2387
  if (msg.type === "system" && msg.subtype === "init") {
2178
2388
  if (msg.session_id)
2179
2389
  onSession(msg.session_id);
2180
- push({ at, type: "session", data: { sessionId: msg.session_id } });
2390
+ push({ at, type: "session", data: { sessionId: msg.session_id, ...msg.model === void 0 ? {} : { model: localModel(msg.model) } } });
2181
2391
  } else if (msg.type === "system" && msg.subtype === "background_tasks_changed") {
2182
2392
  const live = (msg.tasks ?? []).filter((t) => !t.ambient).length;
2183
2393
  push({ at, type: "background", data: { live } });
@@ -2788,6 +2998,21 @@ async function allocate(repo, id, fromRemoteBranch = repo.trunk) {
2788
2998
  await runSetup(repo, dir);
2789
2999
  return dir;
2790
3000
  }
3001
+ async function recoverWorktree(repo, id) {
3002
+ const dir = worktreePath(id);
3003
+ try {
3004
+ if (fs18.statSync(path15.join(dir, ".git")).isFile()) {
3005
+ const branch = await git(dir, "symbolic-ref", "--quiet", "HEAD");
3006
+ const root = await git(dir, "rev-parse", "--show-toplevel");
3007
+ const common = await git(dir, "rev-parse", "--path-format=absolute", "--git-common-dir");
3008
+ const mine = await git(repo.path, "rev-parse", "--path-format=absolute", "--git-common-dir");
3009
+ if (branch === `refs/heads/lobstah/${id}` && realpath(root) === realpath(dir) && realpath(common) === realpath(mine))
3010
+ return dir;
3011
+ }
3012
+ } catch {
3013
+ }
3014
+ throw new Error(`unrecorded worktree at ${dir} for ${id} \u2014 ownership not proven (expected lobstah/${id} in ${repo.path}); inspect and preserve it, then relocate the conflicting checkout before retrying`);
3015
+ }
2791
3016
  async function runSetup(repo, dir) {
2792
3017
  for (const cmd of repo.setup ?? []) {
2793
3018
  await shell(cmd, { cwd: dir, env: { ...process.env, ...repo.env ?? {} } });
@@ -3116,7 +3341,11 @@ async function drive(run2, opts) {
3116
3341
  recordPush(id, lane, resolvePushTargets(ev.data.pushes.map(String), opts.cwd), ev.at);
3117
3342
  }
3118
3343
  if (ev.type === "session" && ev.data?.sessionId) {
3119
- mergeEvidence(id, lane, { sessionId: String(ev.data.sessionId) });
3344
+ const prior = readEvidence(id, lane).worker;
3345
+ mergeEvidence(id, lane, {
3346
+ sessionId: String(ev.data.sessionId),
3347
+ ...ev.data.model === void 0 ? {} : { worker: workerMetadata({ ...prior, model: ev.data.model, observedAt: ev.at }) }
3348
+ });
3120
3349
  }
3121
3350
  if (ev.type !== "turn-end" || cancelled || stopped() || final || parked) continue;
3122
3351
  const lastVerb = readStatusLog(id, lane).at(-1)?.verb;
@@ -3545,16 +3774,18 @@ async function main(activeDir, lane, seams = {}) {
3545
3774
  });
3546
3775
  const wtFile = path17.join(activeDir, "worktree.json");
3547
3776
  let recorded;
3548
- let choice;
3777
+ let recovered = false;
3778
+ let choice3;
3549
3779
  if (fs20.existsSync(wtFile)) {
3550
3780
  recorded = JSON.parse(fs20.readFileSync(wtFile, "utf8")).path;
3551
3781
  if (!fs20.existsSync(recorded)) throw new Error(`recorded worktree ${recorded} is gone`);
3552
3782
  } else if (fs20.existsSync(worktreePath(id))) {
3553
- throw new Error(`unrecorded worktree already exists for ${id} \u2014 refusing to proceed`);
3783
+ recorded = await recoverWorktree(repo, id);
3784
+ recovered = true;
3554
3785
  } else if (descriptor.followUp && cfg.limits.reuseWorktree !== false) {
3555
- choice = await deps.chooseWorktree({ id, lane, repoKey: descriptor.repo, repo, followUp: descriptor.followUp });
3786
+ choice3 = await deps.chooseWorktree({ id, lane, repoKey: descriptor.repo, repo, followUp: descriptor.followUp });
3556
3787
  }
3557
- const worktreeNote = choice ? choice.reuse ? `reusing worktree of ${short3(choice.from)}` : `fresh worktree (${choice.reason})` : void 0;
3788
+ const worktreeNote = choice3 ? choice3.reuse ? `reusing worktree of ${short3(choice3.from)}` : `fresh worktree (${choice3.reason})` : recovered ? "recovered own unrecorded worktree" : void 0;
3558
3789
  const firstModel = modelForHarness(plan.harness, resolved.model);
3559
3790
  const firstNote = [
3560
3791
  wake ? `woke from pause: ${wake}` : attempts > 1 ? `attempt ${attempts}` : void 0,
@@ -3573,17 +3804,22 @@ async function main(activeDir, lane, seams = {}) {
3573
3804
  let cwd;
3574
3805
  if (recorded) {
3575
3806
  cwd = recorded;
3576
- acquireWorktreeLock(cwd, id, lane);
3577
- } else if (choice?.reuse) {
3578
- cwd = choice.path;
3807
+ const held = acquireWorktreeLock(cwd, id, lane);
3808
+ if (held) throw new Error(`worktree at ${cwd} is in use by ${held.id} \u2014 wait for that dispatch to release it before retrying`);
3809
+ if (recovered) {
3810
+ await deps.prepareReuse(repo, cwd);
3811
+ fs20.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
3812
+ }
3813
+ } else if (choice3?.reuse) {
3814
+ cwd = choice3.path;
3579
3815
  const { setupRan } = await deps.prepareReuse(repo, cwd);
3580
3816
  if (setupRan) console.log(`[runner] lockfile changed since setup last ran in ${cwd}: setup re-run`);
3581
3817
  appendEvent(id, lane, {
3582
3818
  at: (/* @__PURE__ */ new Date()).toISOString(),
3583
3819
  type: "runner",
3584
- data: { worktree: cwd, worktreeOf: choice.owner, setupRan }
3820
+ data: { worktree: cwd, worktreeOf: choice3.owner, setupRan }
3585
3821
  });
3586
- fs20.writeFileSync(wtFile, JSON.stringify({ path: cwd, of: choice.owner }, null, 2));
3822
+ fs20.writeFileSync(wtFile, JSON.stringify({ path: cwd, of: choice3.owner }, null, 2));
3587
3823
  } else {
3588
3824
  cwd = await deps.allocate(repo, id, descriptor.systemRepair ? descriptor.pr?.headRefName : void 0);
3589
3825
  acquireWorktreeLock(cwd, id, lane);
@@ -3692,6 +3928,13 @@ async function main(activeDir, lane, seams = {}) {
3692
3928
  return;
3693
3929
  }
3694
3930
  const runOnce = async (harness, prompt, resumeSession) => {
3931
+ const explicit = resolved.flags.length === 0;
3932
+ mergeEvidence(id, lane, { worker: workerMetadata({
3933
+ harness,
3934
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
3935
+ model: explicit ? modelForHarness(harness, resolved.model).model : void 0,
3936
+ config: { effort: explicit ? resolved.effort : null, permissionMode: explicit && harness === "claude" ? "bypassPermissions" : null }
3937
+ }) });
3695
3938
  const run2 = await deps.loadAdapter(harness).start({
3696
3939
  id,
3697
3940
  cwd,