autopilot-ai 2.1.0 → 2.3.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/index.js CHANGED
@@ -75,8 +75,8 @@ async function flushAndCloseForTesting() {
75
75
  const s = currentStream;
76
76
  currentStream = null;
77
77
  currentDate = null;
78
- await new Promise((resolve21) => {
79
- s.end(() => resolve21());
78
+ await new Promise((resolve22) => {
79
+ s.end(() => resolve22());
80
80
  });
81
81
  }
82
82
  function isLogSinkEnabled() {
@@ -1067,10 +1067,10 @@ async function tokenFromFile(file, fetchImpl2, now2) {
1067
1067
  return { token: t.access_token, expiresAt: now2 + (t.expires_in ?? 3600) * 1e3 };
1068
1068
  }
1069
1069
  function gcloudToken() {
1070
- return new Promise((resolve21, reject) => {
1070
+ return new Promise((resolve22, reject) => {
1071
1071
  execFile("gcloud", ["auth", "print-access-token"], { timeout: 15e3 }, (err, stdout) => {
1072
1072
  if (err) reject(new Error("No Google credentials found. Run `gcloud auth application-default login`, or set GOOGLE_APPLICATION_CREDENTIALS."));
1073
- else resolve21(stdout.trim());
1073
+ else resolve22(stdout.trim());
1074
1074
  });
1075
1075
  });
1076
1076
  }
@@ -2322,8 +2322,8 @@ function onShutdown(name, task, phase = SHUTDOWN_PHASE.sessions) {
2322
2322
  }
2323
2323
  function withDeadline(work, ms) {
2324
2324
  let timer2;
2325
- const deadline = new Promise((resolve21) => {
2326
- timer2 = setTimeout(() => resolve21("timeout"), Math.max(0, ms));
2325
+ const deadline = new Promise((resolve22) => {
2326
+ timer2 = setTimeout(() => resolve22("timeout"), Math.max(0, ms));
2327
2327
  });
2328
2328
  return Promise.race([work, deadline]).finally(() => clearTimeout(timer2));
2329
2329
  }
@@ -3782,8 +3782,8 @@ var init_copilot = __esm({
3782
3782
  COPILOT_API_VERSION = "2026-08-01";
3783
3783
  USER_API_VERSION = "2025-04-01";
3784
3784
  COPILOT_DEFAULT_BASE = "https://api.githubcopilot.com";
3785
- sleep = (ms, signal) => new Promise((resolve21, reject) => {
3786
- const t = setTimeout(resolve21, ms);
3785
+ sleep = (ms, signal) => new Promise((resolve22, reject) => {
3786
+ const t = setTimeout(resolve22, ms);
3787
3787
  signal?.addEventListener("abort", () => {
3788
3788
  clearTimeout(t);
3789
3789
  reject(new DOMException("aborted", "AbortError"));
@@ -3874,10 +3874,10 @@ async function startChatGPTSignIn(opts2) {
3874
3874
  const { verifier, challenge } = pkce();
3875
3875
  const state = randomBytes(32).toString("base64url");
3876
3876
  const nonce = randomBytes(32).toString("base64url");
3877
- let resolve21;
3877
+ let resolve22;
3878
3878
  let reject;
3879
3879
  const done = new Promise((res, rej) => {
3880
- resolve21 = res;
3880
+ resolve22 = res;
3881
3881
  reject = rej;
3882
3882
  });
3883
3883
  done.catch(() => void 0);
@@ -3900,7 +3900,7 @@ async function startChatGPTSignIn(opts2) {
3900
3900
  }
3901
3901
  if (!clientId) return finish("No client id returned"), reject(new Error("ChatGPT sign-in did not return a client id. Try again."));
3902
3902
  completeSignIn(code, clientId, redirect, verifier, nonce, opts2.fetchImpl).then(
3903
- (s) => (finish(), resolve21(s)),
3903
+ (s) => (finish(), resolve22(s)),
3904
3904
  (e) => (finish(e.message), reject(e))
3905
3905
  );
3906
3906
  });
@@ -4608,8 +4608,8 @@ async function* runKimi(opts2) {
4608
4608
  if (isAbortError(fetchErr)) throw fetchErr;
4609
4609
  logger.warn("runKimi:fetch_error", { requestId, attempt, error: msg });
4610
4610
  if (attempt < MAX_ATTEMPTS - 1) {
4611
- const delay = Math.random() * (500 * 2 ** attempt);
4612
- await sleep2(delay, opts2.signal);
4611
+ const delay2 = Math.random() * (500 * 2 ** attempt);
4612
+ await sleep2(delay2, opts2.signal);
4613
4613
  continue;
4614
4614
  }
4615
4615
  const local = customEndpoint?.providerId && !preset.keyRequired;
@@ -4652,9 +4652,9 @@ Is ${preset.name} running at ${customEndpoint.baseUrl}? Start it, or set provide
4652
4652
  const isRateLimit = apiErr.httpStatus === 429;
4653
4653
  const baseDelay = isRateLimit ? 2e3 : 500;
4654
4654
  const retryAfterSec = Number(res.headers.get("retry-after"));
4655
- const delay = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.min(retryAfterSec * 1e3, 3e4) : Math.random() * (baseDelay * 2 ** attempt);
4656
- logger.warn("runKimi:retrying", { requestId, attempt, code: apiErr.code, httpStatus: apiErr.httpStatus, delay });
4657
- await sleep2(delay, opts2.signal);
4655
+ const delay2 = Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? Math.min(retryAfterSec * 1e3, 3e4) : Math.random() * (baseDelay * 2 ** attempt);
4656
+ logger.warn("runKimi:retrying", { requestId, attempt, code: apiErr.code, httpStatus: apiErr.httpStatus, delay: delay2 });
4657
+ await sleep2(delay2, opts2.signal);
4658
4658
  continue;
4659
4659
  }
4660
4660
  throw apiErr;
@@ -5106,11 +5106,11 @@ function extractApiError(parsed, rawText) {
5106
5106
  return null;
5107
5107
  }
5108
5108
  function sleep2(ms, signal) {
5109
- return new Promise((resolve21, reject) => {
5109
+ return new Promise((resolve22, reject) => {
5110
5110
  if (signal?.aborted) return reject(new DOMException("aborted", "AbortError"));
5111
5111
  const t = setTimeout(() => {
5112
5112
  signal?.removeEventListener("abort", onAbort);
5113
- resolve21();
5113
+ resolve22();
5114
5114
  }, ms);
5115
5115
  const onAbort = () => {
5116
5116
  clearTimeout(t);
@@ -5260,7 +5260,7 @@ var init_worker_registry = __esm({
5260
5260
  delivered: false
5261
5261
  };
5262
5262
  if (done.background) this.finished.set(info.id, done);
5263
- for (const resolve21 of this.waiters.get(info.id) ?? []) resolve21(done);
5263
+ for (const resolve22 of this.waiters.get(info.id) ?? []) resolve22(done);
5264
5264
  this.waiters.delete(info.id);
5265
5265
  for (const l of this.finishListeners) {
5266
5266
  try {
@@ -5292,14 +5292,14 @@ var init_worker_registry = __esm({
5292
5292
  if (!found) return Promise.reject(new Error(`No subagent ${ref}`));
5293
5293
  if (found.finished) return Promise.resolve(found.finished);
5294
5294
  const id = found.running.id;
5295
- return new Promise((resolve21, reject) => {
5295
+ return new Promise((resolve22, reject) => {
5296
5296
  const onAbort = () => reject(new DOMException("aborted", "AbortError"));
5297
5297
  if (signal?.aborted) return onAbort();
5298
5298
  signal?.addEventListener("abort", onAbort, { once: true });
5299
5299
  const list = this.waiters.get(id) ?? [];
5300
5300
  list.push((w) => {
5301
5301
  signal?.removeEventListener("abort", onAbort);
5302
- resolve21(w);
5302
+ resolve22(w);
5303
5303
  });
5304
5304
  this.waiters.set(id, list);
5305
5305
  });
@@ -5547,7 +5547,7 @@ async function pruneFiles(files, maxAgeDays, maxCount) {
5547
5547
  return removed;
5548
5548
  }
5549
5549
  async function rotateJsonl(path, maxBytes, rotations) {
5550
- const { rename: rename4 } = await import("fs/promises");
5550
+ const { rename: rename5 } = await import("fs/promises");
5551
5551
  let s;
5552
5552
  try {
5553
5553
  s = await stat(path);
@@ -5559,7 +5559,7 @@ async function rotateJsonl(path, maxBytes, rotations) {
5559
5559
  const src = i === 1 ? path : `${path}.${i - 1}`;
5560
5560
  const dst = `${path}.${i}`;
5561
5561
  try {
5562
- await rename4(src, dst);
5562
+ await rename5(src, dst);
5563
5563
  } catch {
5564
5564
  }
5565
5565
  }
@@ -6610,7 +6610,7 @@ function truncateForEmbedding(text3) {
6610
6610
  return text3.slice(0, MAX_EMBED_CHARS);
6611
6611
  }
6612
6612
  async function sleep3(ms) {
6613
- return new Promise((resolve21) => setTimeout(resolve21, ms));
6613
+ return new Promise((resolve22) => setTimeout(resolve22, ms));
6614
6614
  }
6615
6615
  async function fetchWithRetry(url, init, retries = 3) {
6616
6616
  let lastError;
@@ -6619,8 +6619,8 @@ async function fetchWithRetry(url, init, retries = 3) {
6619
6619
  const res = await fetch(url, init);
6620
6620
  if (res.ok) return res;
6621
6621
  if (res.status === 429 || res.status >= 500) {
6622
- const delay = 1e3 * 2 ** i;
6623
- await sleep3(delay);
6622
+ const delay2 = 1e3 * 2 ** i;
6623
+ await sleep3(delay2);
6624
6624
  continue;
6625
6625
  }
6626
6626
  const errText = await res.text().catch(() => "unknown error");
@@ -8050,15 +8050,15 @@ async function gitNumstat(cwd, opts2 = {}) {
8050
8050
  if (opts2.includeUntracked) {
8051
8051
  try {
8052
8052
  const { stdout } = await execFileAsync("git", ["ls-files", "--others", "--exclude-standard"], { cwd, timeout: 5e3 });
8053
- const { readFile: readFile40, stat: stat14 } = await import("fs/promises");
8054
- const { join: join62 } = await import("path");
8053
+ const { readFile: readFile42, stat: stat15 } = await import("fs/promises");
8054
+ const { join: join64 } = await import("path");
8055
8055
  for (const file of stdout.split("\n").filter(Boolean)) {
8056
8056
  let lines = 0;
8057
8057
  try {
8058
- const path = join62(cwd, file);
8059
- const size = (await stat14(path)).size;
8058
+ const path = join64(cwd, file);
8059
+ const size = (await stat15(path)).size;
8060
8060
  if (size > 0 && size <= 1024 * 1024) {
8061
- const text3 = await readFile40(path, "utf8");
8061
+ const text3 = await readFile42(path, "utf8");
8062
8062
  lines = text3.split("\n").length - (text3.endsWith("\n") ? 1 : 0);
8063
8063
  }
8064
8064
  } catch {
@@ -8177,6 +8177,146 @@ var init_git_delta = __esm({
8177
8177
  }
8178
8178
  });
8179
8179
 
8180
+ // src/util/line-diff.ts
8181
+ function splitLines(text3) {
8182
+ if (text3 === "") return [];
8183
+ const lines = text3.split("\n");
8184
+ if (lines[lines.length - 1] === "") lines.pop();
8185
+ return lines;
8186
+ }
8187
+ function myers(a, b) {
8188
+ const n = a.length;
8189
+ const m = b.length;
8190
+ const max = n + m;
8191
+ if (max === 0) return [];
8192
+ const offset = max;
8193
+ let v = new Int32Array(2 * max + 2);
8194
+ const trace = [];
8195
+ for (let d = 0; d <= max; d++) {
8196
+ if ((n + m) * d > MAX_MYERS_COST) return null;
8197
+ trace.push(v.slice());
8198
+ const next = v;
8199
+ for (let k = -d; k <= d; k += 2) {
8200
+ let x;
8201
+ if (k === -d || k !== d && next[offset + k - 1] < next[offset + k + 1]) x = next[offset + k + 1];
8202
+ else x = next[offset + k - 1] + 1;
8203
+ let y = x - k;
8204
+ while (x < n && y < m && a[x] === b[y]) {
8205
+ x++;
8206
+ y++;
8207
+ }
8208
+ next[offset + k] = x;
8209
+ if (x >= n && y >= m) {
8210
+ trace.push(next.slice());
8211
+ return backtrack(trace, a, b, offset);
8212
+ }
8213
+ }
8214
+ v = next;
8215
+ }
8216
+ return null;
8217
+ }
8218
+ function backtrack(trace, a, b, offset) {
8219
+ const ops = [];
8220
+ let x = a.length;
8221
+ let y = b.length;
8222
+ for (let d = trace.length - 2; d >= 0; d--) {
8223
+ const v = trace[d];
8224
+ const k = x - y;
8225
+ let prevK;
8226
+ if (k === -d || k !== d && v[offset + k - 1] < v[offset + k + 1]) prevK = k + 1;
8227
+ else prevK = k - 1;
8228
+ const prevX = d === 0 ? 0 : v[offset + prevK];
8229
+ const prevY = prevX - prevK;
8230
+ while (x > prevX && y > prevY) {
8231
+ ops.push({ kind: " ", text: a[x - 1] });
8232
+ x--;
8233
+ y--;
8234
+ }
8235
+ if (d === 0) break;
8236
+ if (x === prevX) ops.push({ kind: "+", text: b[y - 1] });
8237
+ else ops.push({ kind: "-", text: a[x - 1] });
8238
+ x = prevX;
8239
+ y = prevY;
8240
+ }
8241
+ while (x > 0 && y > 0) {
8242
+ ops.push({ kind: " ", text: a[x - 1] });
8243
+ x--;
8244
+ y--;
8245
+ }
8246
+ return ops.reverse();
8247
+ }
8248
+ function editScript(a, b) {
8249
+ let pre = 0;
8250
+ while (pre < a.length && pre < b.length && a[pre] === b[pre]) pre++;
8251
+ let suf = 0;
8252
+ while (suf < a.length - pre && suf < b.length - pre && a[a.length - 1 - suf] === b[b.length - 1 - suf]) suf++;
8253
+ const midA = a.slice(pre, a.length - suf);
8254
+ const midB = b.slice(pre, b.length - suf);
8255
+ const mid = myers(midA, midB) ?? [
8256
+ ...midA.map((text3) => ({ kind: "-", text: text3 })),
8257
+ ...midB.map((text3) => ({ kind: "+", text: text3 }))
8258
+ ];
8259
+ return [
8260
+ ...a.slice(0, pre).map((text3) => ({ kind: " ", text: text3 })),
8261
+ ...mid,
8262
+ ...a.slice(a.length - suf).map((text3) => ({ kind: " ", text: text3 }))
8263
+ ];
8264
+ }
8265
+ function structuredPatch(oldText, newText, context = DEFAULT_CONTEXT) {
8266
+ const ops = editScript(splitLines(oldText), splitLines(newText));
8267
+ const hunks = [];
8268
+ let oldNo = 1;
8269
+ let newNo = 1;
8270
+ const pos = ops.map((op) => {
8271
+ const at = { old: oldNo, new: newNo };
8272
+ if (op.kind !== "+") oldNo++;
8273
+ if (op.kind !== "-") newNo++;
8274
+ return at;
8275
+ });
8276
+ let i = 0;
8277
+ while (i < ops.length) {
8278
+ if (ops[i].kind === " ") {
8279
+ i++;
8280
+ continue;
8281
+ }
8282
+ const start = Math.max(0, i - context);
8283
+ let end = i;
8284
+ for (; ; ) {
8285
+ while (end < ops.length && ops[end].kind !== " ") end++;
8286
+ let gap = end;
8287
+ while (gap < ops.length && ops[gap].kind === " ") gap++;
8288
+ if (gap < ops.length && gap - end <= 2 * context) {
8289
+ end = gap;
8290
+ continue;
8291
+ }
8292
+ end = Math.min(ops.length, end + context);
8293
+ break;
8294
+ }
8295
+ const slice = ops.slice(start, end);
8296
+ const oldLines = slice.filter((o) => o.kind !== "+").length;
8297
+ const newLines = slice.filter((o) => o.kind !== "-").length;
8298
+ const first = pos[start];
8299
+ hunks.push({
8300
+ // jsdiff's convention: a hunk with no old (or new) lines starts one line earlier.
8301
+ oldStart: oldLines === 0 ? first.old - 1 : first.old,
8302
+ oldLines,
8303
+ newStart: newLines === 0 ? first.new - 1 : first.new,
8304
+ newLines,
8305
+ lines: slice.map((o) => o.kind + o.text)
8306
+ });
8307
+ i = end;
8308
+ }
8309
+ return hunks;
8310
+ }
8311
+ var DEFAULT_CONTEXT, MAX_MYERS_COST;
8312
+ var init_line_diff = __esm({
8313
+ "src/util/line-diff.ts"() {
8314
+ "use strict";
8315
+ DEFAULT_CONTEXT = 3;
8316
+ MAX_MYERS_COST = 4e6;
8317
+ }
8318
+ });
8319
+
8180
8320
  // src/hooks/claude-compat.ts
8181
8321
  function claudeToolName(tool, args) {
8182
8322
  if (tool === "edit" && Array.isArray(args.edits)) return "MultiEdit";
@@ -8261,9 +8401,35 @@ function claudeToolResponse(tool, args, cwd, result2) {
8261
8401
  }
8262
8402
  if ((tool === "write" || tool === "edit") && isStr(args.path) && !failed) {
8263
8403
  response.filePath = resolvePath(cwd, args.path);
8404
+ if (result2.fileChange) Object.assign(response, claudeFileChangeFields(tool, args, result2.fileChange));
8264
8405
  }
8265
8406
  return response;
8266
8407
  }
8408
+ function claudeFileChangeFields(tool, args, change) {
8409
+ const fields = {};
8410
+ if (tool === "write") {
8411
+ fields.type = change.existed ? "update" : "create";
8412
+ if (isStr(args.content)) fields.content = args.content;
8413
+ } else if (Array.isArray(args.edits)) {
8414
+ fields.edits = editPairs(args);
8415
+ } else {
8416
+ fields.oldString = args.old_string;
8417
+ fields.newString = args.new_string;
8418
+ fields.replaceAll = args.replace_all === true;
8419
+ }
8420
+ if (!change.existed) {
8421
+ fields.originalFile = null;
8422
+ fields.structuredPatch = [];
8423
+ } else {
8424
+ if (change.original !== null && Buffer.byteLength(change.original, "utf8") <= ORIGINAL_FILE_CAP_BYTES) {
8425
+ fields.originalFile = change.original;
8426
+ }
8427
+ if (change.original !== null && change.updated !== null) {
8428
+ fields.structuredPatch = structuredPatch(change.original, change.updated);
8429
+ }
8430
+ }
8431
+ return fields;
8432
+ }
8267
8433
  function claudeError(result2) {
8268
8434
  if (isNum(result2.exitCode) && result2.exitCode !== 0) {
8269
8435
  const output = [result2.stdout, result2.stderr].filter((s) => s && s.trim()).map((s) => s.trimEnd()).join("\n");
@@ -8296,12 +8462,13 @@ function claudeToolFields(payload) {
8296
8462
  }
8297
8463
  return { tool_name: toolName, ...fields };
8298
8464
  }
8299
- var CLAUDE_TOOL_NAMES, FILE_EDIT_NAMES, isStr, isNum;
8465
+ var CLAUDE_TOOL_NAMES, FILE_EDIT_NAMES, isStr, isNum, ORIGINAL_FILE_CAP_BYTES;
8300
8466
  var init_claude_compat = __esm({
8301
8467
  "src/hooks/claude-compat.ts"() {
8302
8468
  "use strict";
8303
8469
  init_edit();
8304
8470
  init_paths();
8471
+ init_line_diff();
8305
8472
  CLAUDE_TOOL_NAMES = {
8306
8473
  bash: "Bash",
8307
8474
  edit: "Edit",
@@ -8316,6 +8483,7 @@ var init_claude_compat = __esm({
8316
8483
  FILE_EDIT_NAMES = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
8317
8484
  isStr = (v) => typeof v === "string";
8318
8485
  isNum = (v) => typeof v === "number";
8486
+ ORIGINAL_FILE_CAP_BYTES = 256 * 1024;
8319
8487
  }
8320
8488
  });
8321
8489
 
@@ -10125,8 +10293,8 @@ function applyConfirmedCost(log2, session, turn, snapshot) {
10125
10293
  session.gatewayLogs = logs.slice(-100);
10126
10294
  }
10127
10295
  async function reconcileTurnCost(sessionId, turnId, lookup, fetchImpl2 = fetch, delaysMs = RECONCILE_DELAYS_MS) {
10128
- for (const delay of delaysMs) {
10129
- await new Promise((r) => setTimeout(r, delay));
10296
+ for (const delay2 of delaysMs) {
10297
+ await new Promise((r) => setTimeout(r, delay2));
10130
10298
  let snapshot;
10131
10299
  try {
10132
10300
  snapshot = await fetchGenerationSnapshot(lookup, fetchImpl2);
@@ -10606,15 +10774,15 @@ function makeErrorOutput(message2) {
10606
10774
  return { content: message2, rawBytes: bytes, reducedBytes: bytes };
10607
10775
  }
10608
10776
  async function execOnce(command, cwd) {
10609
- return new Promise((resolve21) => {
10777
+ return new Promise((resolve22) => {
10610
10778
  const child = spawn("bash", ["-lc", command], { cwd });
10611
10779
  let out = "";
10612
10780
  child.stdout.on("data", (d) => {
10613
10781
  out += d.toString("utf8");
10614
10782
  });
10615
10783
  child.on("close", (code) => {
10616
- if (code !== 0) return resolve21(void 0);
10617
- resolve21(out.trim() || void 0);
10784
+ if (code !== 0) return resolve22(void 0);
10785
+ resolve22(out.trim() || void 0);
10618
10786
  });
10619
10787
  });
10620
10788
  }
@@ -10768,7 +10936,7 @@ async function runBash(args, ctx) {
10768
10936
  if (pushGuard) return pushGuard;
10769
10937
  const { shell, args: shellArgs, isPosix } = getShellCommand(ctx.shell);
10770
10938
  const { command, env: coauthorEnv } = isPosix ? injectCoauthor(args.command, ctx.coauthor) : { command: args.command, env: {} };
10771
- return new Promise((resolve21, reject) => {
10939
+ return new Promise((resolve22, reject) => {
10772
10940
  logger.debug("bash:spawn", { command: args.command.slice(0, 200), cwd: ctx.cwd, shell });
10773
10941
  const child = spawn(shell, [...shellArgs, command], {
10774
10942
  cwd: ctx.cwd,
@@ -10833,7 +11001,7 @@ ${stderr}`
10833
11001
  if (hint) parts.push(hint);
10834
11002
  }
10835
11003
  const raw = parts.join("\n");
10836
- resolve21({
11004
+ resolve22({
10837
11005
  content: raw,
10838
11006
  rawBytes: Buffer.byteLength(raw, "utf8"),
10839
11007
  reducedBytes: Buffer.byteLength(raw, "utf8"),
@@ -12231,7 +12399,7 @@ import { tmpdir as tmpdir2 } from "os";
12231
12399
  import { join as join22, dirname as dirname9 } from "path";
12232
12400
  async function autoScroll(page) {
12233
12401
  await page.evaluate(async () => {
12234
- await new Promise((resolve21) => {
12402
+ await new Promise((resolve22) => {
12235
12403
  let totalHeight = 0;
12236
12404
  const distance = 300;
12237
12405
  const timer2 = setInterval(() => {
@@ -12240,12 +12408,12 @@ async function autoScroll(page) {
12240
12408
  totalHeight += distance;
12241
12409
  if (totalHeight >= scrollHeight) {
12242
12410
  clearInterval(timer2);
12243
- resolve21();
12411
+ resolve22();
12244
12412
  }
12245
12413
  }, 100);
12246
12414
  setTimeout(() => {
12247
12415
  clearInterval(timer2);
12248
- resolve21();
12416
+ resolve22();
12249
12417
  }, 1e4);
12250
12418
  });
12251
12419
  });
@@ -14494,8 +14662,8 @@ function waitForProcessStartTime(pid, jobId, exitPath) {
14494
14662
  function readProcessStartTime(pid) {
14495
14663
  if (process.platform === "linux") {
14496
14664
  try {
14497
- const stat14 = readFileSync6(`/proc/${pid}/stat`, "utf8");
14498
- const fieldsAfterCommand = stat14.slice(stat14.lastIndexOf(")") + 1).trim().split(/\s+/);
14665
+ const stat15 = readFileSync6(`/proc/${pid}/stat`, "utf8");
14666
+ const fieldsAfterCommand = stat15.slice(stat15.lastIndexOf(")") + 1).trim().split(/\s+/);
14499
14667
  return fieldsAfterCommand[19] ? `linux:${fieldsAfterCommand[19]}` : null;
14500
14668
  } catch {
14501
14669
  return null;
@@ -15501,8 +15669,8 @@ var init_store = __esm({
15501
15669
  if (timer2.attempts >= timer2.maxAttempts) {
15502
15670
  this.db.prepare("UPDATE run_timers SET status = 'failed', lease_until = NULL, last_error = ? WHERE id = ?").run(error.slice(0, 1e3), id);
15503
15671
  } else {
15504
- const delay = Math.min(MAX_RETRY_MS, DEFAULT_RETRY_MS * 2 ** Math.max(0, timer2.attempts - 1));
15505
- this.db.prepare("UPDATE run_timers SET status = 'scheduled', wake_at = ?, lease_until = NULL, last_error = ? WHERE id = ?").run(now2 + delay, error.slice(0, 1e3), id);
15672
+ const delay2 = Math.min(MAX_RETRY_MS, DEFAULT_RETRY_MS * 2 ** Math.max(0, timer2.attempts - 1));
15673
+ this.db.prepare("UPDATE run_timers SET status = 'scheduled', wake_at = ?, lease_until = NULL, last_error = ? WHERE id = ?").run(now2 + delay2, error.slice(0, 1e3), id);
15506
15674
  }
15507
15675
  });
15508
15676
  }
@@ -15675,7 +15843,7 @@ var init_wait_for = __esm({
15675
15843
  });
15676
15844
 
15677
15845
  // src/tools/executor.ts
15678
- import { realpath as realpath4 } from "fs/promises";
15846
+ import { readFile as readFile17, realpath as realpath4, stat as stat7 } from "fs/promises";
15679
15847
  import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5, sep as sep3 } from "path";
15680
15848
  function getWorkerTools(profile) {
15681
15849
  return profile === "research" ? ALL_TOOLS.filter((tool) => RESEARCH_WORKER_TOOL_NAMES.has(tool.name) && tool.isReadOnly === true).map(restrictResearchTool) : ALL_TOOLS;
@@ -15744,6 +15912,16 @@ function toPermissionResult(d) {
15744
15912
  }
15745
15913
  return d;
15746
15914
  }
15915
+ async function snapshotForHook(abs) {
15916
+ try {
15917
+ const st = await stat7(abs);
15918
+ if (!st.isFile()) return { existed: true, content: null };
15919
+ if (st.size > HOOK_FILE_SNAPSHOT_MAX_BYTES) return { existed: true, content: null };
15920
+ return { existed: true, content: await readFile17(abs, "utf8") };
15921
+ } catch {
15922
+ return { existed: false, content: null };
15923
+ }
15924
+ }
15747
15925
  function shellResultForHook(meta) {
15748
15926
  if (!meta || meta.exitCode === void 0) return {};
15749
15927
  return {
@@ -15762,6 +15940,15 @@ function capContentForHook(s) {
15762
15940
  return `${cut}
15763
15941
  [\u2026truncated for hook payload]`;
15764
15942
  }
15943
+ async function fileChangeAfter(tool, args, cwd, before) {
15944
+ let updated = null;
15945
+ if (tool === "write" && typeof args.content === "string") {
15946
+ updated = Buffer.byteLength(args.content, "utf8") <= HOOK_FILE_SNAPSHOT_MAX_BYTES ? args.content : null;
15947
+ } else {
15948
+ updated = (await snapshotForHook(resolvePath(cwd, args.path))).content;
15949
+ }
15950
+ return { existed: before.existed, original: before.content, updated };
15951
+ }
15765
15952
  function tracePermissionDecision(result2) {
15766
15953
  if (result2.decision === "deny") return "deny";
15767
15954
  if (result2.scope === "session") return "allow_session";
@@ -15785,7 +15972,7 @@ function normalizeToolOutput(result2) {
15785
15972
  }
15786
15973
  return result2;
15787
15974
  }
15788
- var ALL_TOOLS, RESEARCH_WORKER_TOOL_NAMES, SENSITIVE_RESEARCH_PATHS, HOOK_RESULT_CONTENT_CAP_BYTES, ToolExecutor, TRACE_ARGS_INLINE_MAX, ToolTrace;
15975
+ var ALL_TOOLS, RESEARCH_WORKER_TOOL_NAMES, SENSITIVE_RESEARCH_PATHS, HOOK_RESULT_CONTENT_CAP_BYTES, HOOK_FILE_SNAPSHOT_MAX_BYTES, ToolExecutor, TRACE_ARGS_INLINE_MAX, ToolTrace;
15789
15976
  var init_executor = __esm({
15790
15977
  "src/tools/executor.ts"() {
15791
15978
  "use strict";
@@ -15815,6 +16002,7 @@ var init_executor = __esm({
15815
16002
  init_jobs();
15816
16003
  init_wait_for();
15817
16004
  init_workers();
16005
+ init_paths();
15818
16006
  init_store();
15819
16007
  ALL_TOOLS = [
15820
16008
  { ...readTool, isReadOnly: true },
@@ -15869,6 +16057,7 @@ var init_executor = __esm({
15869
16057
  ".git-credentials"
15870
16058
  ]);
15871
16059
  HOOK_RESULT_CONTENT_CAP_BYTES = 4 * 1024;
16060
+ HOOK_FILE_SNAPSHOT_MAX_BYTES = 1024 * 1024;
15872
16061
  ToolExecutor = class {
15873
16062
  sessionAllowed = /* @__PURE__ */ new Set();
15874
16063
  tools;
@@ -16046,8 +16235,10 @@ var init_executor = __esm({
16046
16235
  runStore.recordToolIntent(ctx.runId, call.id, call.name, args);
16047
16236
  intentRecorded = true;
16048
16237
  }
16238
+ const before = this.wantsFileChange(call.name, args) ? await snapshotForHook(resolvePath(ctx.cwd, args.path)) : void 0;
16049
16239
  const result2 = await tool.run(args, ctx);
16050
16240
  toolCompleted = true;
16241
+ const fileChange = before ? await fileChangeAfter(call.name, args, ctx.cwd, before) : void 0;
16051
16242
  const normalized = normalizeToolOutput(result2);
16052
16243
  tr?.raw(normalized);
16053
16244
  if (runStore && ctx.runId) {
@@ -16099,7 +16290,7 @@ var init_executor = __esm({
16099
16290
  artifactId: reduced.artifactId,
16100
16291
  ...normalized.waitRequest ? { waitRequest: normalized.waitRequest } : {}
16101
16292
  };
16102
- this.firePostToolUse(call, args, success, ctx, normalized.meta);
16293
+ this.firePostToolUse(call, args, success, ctx, normalized.meta, fileChange);
16103
16294
  return success;
16104
16295
  } catch (e) {
16105
16296
  if (e instanceof RunToolBudgetExceededError) ctx.onRunBudgetExceeded?.(e.reason);
@@ -16170,7 +16361,7 @@ var init_executor = __esm({
16170
16361
  * `content` field is capped via `capContentForHook` to keep large tool
16171
16362
  * outputs from blowing the OS env-var limits on `KIMIFLARE_HOOK_PAYLOAD`.
16172
16363
  */
16173
- firePostToolUse(call, args, result2, ctx, meta) {
16364
+ firePostToolUse(call, args, result2, ctx, meta, fileChange) {
16174
16365
  const hooks = this.hooks;
16175
16366
  if (!hooks) return;
16176
16367
  const post = hooks.hasEnabledHooks("PostToolUse");
@@ -16180,7 +16371,8 @@ var init_executor = __esm({
16180
16371
  ok: result2.ok,
16181
16372
  content: capContentForHook(result2.content),
16182
16373
  errorCode: result2.errorCode,
16183
- ...shellResultForHook(meta)
16374
+ ...shellResultForHook(meta),
16375
+ ...fileChange ? { fileChange } : {}
16184
16376
  };
16185
16377
  const base = {
16186
16378
  session_id: ctx.sessionId ?? null,
@@ -16198,6 +16390,11 @@ var init_executor = __esm({
16198
16390
  void hooks.fire("PostToolUseFailure", { event: "PostToolUseFailure", ...base, error: claudeError(resultFields) }, call.name, ctx.signal).catch(swallow2);
16199
16391
  }
16200
16392
  }
16393
+ /** True when a PostToolUse hook would get this write / edit call's diff. */
16394
+ wantsFileChange(tool, args) {
16395
+ if (tool !== "write" && tool !== "edit" || typeof args.path !== "string" || !args.path) return false;
16396
+ return !!this.hooks && (this.hooks.hasEnabledHooks("PostToolUse") || this.hooks.hasEnabledHooks("PostToolUseFailure"));
16397
+ }
16201
16398
  permissionKey(tool, args) {
16202
16399
  if (tool.name === "bash" && typeof args.command === "string") {
16203
16400
  const firstToken = args.command.trim().split(/\s+/)[0] ?? "";
@@ -16324,8 +16521,106 @@ var init_executor = __esm({
16324
16521
  }
16325
16522
  });
16326
16523
 
16524
+ // src/intent/classify.ts
16525
+ function classifyIntent(prompt) {
16526
+ let intentScore = 0;
16527
+ let matchedIntent = "other";
16528
+ for (const [intent, pattern] of Object.entries(INTENT_PATTERNS)) {
16529
+ const matches = (prompt.match(pattern) || []).length;
16530
+ if (matches > intentScore) {
16531
+ intentScore = matches;
16532
+ matchedIntent = intent;
16533
+ }
16534
+ }
16535
+ const hasFileMentions = (prompt.match(/@\w+|\b[\w/-]+\.(ts|tsx|js|jsx|py|go|rs)\b/g) || []).length;
16536
+ const hasMutatingVerb = /\b(add|create|write|edit|delete|remove|rename|migrate|implement)\b/i.test(prompt);
16537
+ const isQuestion = prompt.trim().endsWith("?") || /\b(what|how|why|is|does|can)\b/i.test(prompt.split(" ")[0] || "");
16538
+ const hasHeavySignal = HEAVY_SIGNAL.test(prompt);
16539
+ const rawScore = Math.min(
16540
+ 1,
16541
+ intentScore * 0.25 + (hasFileMentions > 2 ? 0.3 : hasFileMentions * 0.1) + (hasMutatingVerb ? 0.25 : 0) + (isQuestion ? 0 : 0.1) + (hasHeavySignal ? 0.6 : 0)
16542
+ );
16543
+ const tier = rawScore < 0.3 ? "light" : rawScore < 0.65 ? "medium" : "heavy";
16544
+ return {
16545
+ intent: hasHeavySignal && matchedIntent === "other" ? "explore" : matchedIntent,
16546
+ rawScore,
16547
+ tier,
16548
+ confidence: 0.5 + (intentScore > 0 || hasHeavySignal ? 0.3 : 0) + (hasFileMentions > 0 ? 0.1 : 0)
16549
+ };
16550
+ }
16551
+ var INTENT_PATTERNS, HEAVY_SIGNAL;
16552
+ var init_classify = __esm({
16553
+ "src/intent/classify.ts"() {
16554
+ "use strict";
16555
+ INTENT_PATTERNS = {
16556
+ qa: /\b(what|how|why|explain|describe|what's|what is)\b/i,
16557
+ diagnose: /\b(broken|failing|error|bug|crash|why.*fail|not working)\b/i,
16558
+ verify: /\b(correct|right|verify|review|check|is this|does this)\b/i,
16559
+ polish: /\b(rename|refactor|extract|move|clean|lint|format)\b/i,
16560
+ small_edit: /\b(add|change|update|fix|remove|delete)\b.+\b(line|here|this|variable|function)\b/i,
16561
+ feature_bounded: /\b(add|implement|create|support)\b.+\b(flag|option|param|arg|field)\b/i,
16562
+ feature_exploratory: /\b(add|implement|migrate|integrate|build)\b.+\b(module|system|auth|oauth|framework|service)\b/i,
16563
+ explore: /\b(how.*work|architecture|structure|where.*used|find.*all|understand)\b/i,
16564
+ meta: /\b(plan|design|strategy|ontology|roadmap|approach)\b/i
16565
+ };
16566
+ HEAVY_SIGNAL = /\b(research|investigat\w*|audit|deep[\s-]?(dive|thinking)|explorat\w*|comprehensive|end[\s-]?to[\s-]?end|entire\s+(code\s?base|repo\w*|project|app)|whole\s+(code\s?base|repo\w*|project|app)|across\s+the\s+(code\s?base|repo\w*|project)|security|vulnerab\w*|loophole|exploit\w*|threat\s?model|attack\s+surface|penetration)\b/i;
16567
+ }
16568
+ });
16569
+
16570
+ // src/hooks/prompt-submit.ts
16571
+ async function submitUserPrompt(info) {
16572
+ let provider;
16573
+ try {
16574
+ provider = info.auth ? providerPresetOf(info.auth).id : void 0;
16575
+ } catch {
16576
+ provider = void 0;
16577
+ }
16578
+ await noteSessionStart({
16579
+ ...info.hooks ? { hooks: info.hooks } : {},
16580
+ sessionId: info.sessionId,
16581
+ cwd: info.cwd,
16582
+ resumed: info.resumed,
16583
+ model: info.model,
16584
+ ...provider ? { provider } : {}
16585
+ });
16586
+ if (!info.hooks?.hasEnabledHooks("UserPromptSubmit")) return { blocked: false };
16587
+ const outcome = await info.hooks.fire(
16588
+ "UserPromptSubmit",
16589
+ {
16590
+ event: "UserPromptSubmit",
16591
+ session_id: info.sessionId,
16592
+ cwd: info.cwd,
16593
+ prompt: info.prompt,
16594
+ tier: info.tier ?? classifyIntent(info.prompt).tier
16595
+ },
16596
+ null
16597
+ );
16598
+ if (!outcome.vetoed) return { blocked: false };
16599
+ return { blocked: true, reason: outcome.vetoReason || PROMPT_BLOCKED_DEFAULT_REASON };
16600
+ }
16601
+ var PROMPT_BLOCKED_EXIT_CODE, PROMPT_BLOCKED_DEFAULT_REASON, PromptBlockedError;
16602
+ var init_prompt_submit = __esm({
16603
+ "src/hooks/prompt-submit.ts"() {
16604
+ "use strict";
16605
+ init_session_lifecycle();
16606
+ init_classify();
16607
+ init_llm_auth();
16608
+ PROMPT_BLOCKED_EXIT_CODE = 44;
16609
+ PROMPT_BLOCKED_DEFAULT_REASON = "UserPromptSubmit hook blocked the prompt";
16610
+ PromptBlockedError = class extends Error {
16611
+ reason;
16612
+ code = "prompt_blocked";
16613
+ constructor(reason) {
16614
+ super(`prompt blocked by a UserPromptSubmit hook: ${reason}`);
16615
+ this.name = "PromptBlockedError";
16616
+ this.reason = reason;
16617
+ }
16618
+ };
16619
+ }
16620
+ });
16621
+
16327
16622
  // src/util/image.ts
16328
- import { readFile as readFile17 } from "fs/promises";
16623
+ import { readFile as readFile18 } from "fs/promises";
16329
16624
  import { basename as basename3 } from "path";
16330
16625
  import jpeg from "jpeg-js";
16331
16626
  import { PNG } from "pngjs";
@@ -16404,7 +16699,7 @@ function imageNote(path, change) {
16404
16699
  return `[Attached image ${basename3(path)} was downscaled to ${change.width}\xD7${change.height} for upload; the full-resolution original is at ${path}]`;
16405
16700
  }
16406
16701
  async function encodeImageFile(filePath, opts2 = {}) {
16407
- const buf = await readFile17(filePath);
16702
+ const buf = await readFile18(filePath);
16408
16703
  if (buf.byteLength > MAX_IMAGE_BYTES) {
16409
16704
  throw new Error(
16410
16705
  `image too large (${(buf.byteLength / 1024 / 1024).toFixed(1)} MB); max is ${MAX_IMAGE_BYTES / 1024 / 1024} MB`
@@ -16448,7 +16743,7 @@ var init_image = __esm({
16448
16743
  });
16449
16744
 
16450
16745
  // src/util/attachments.ts
16451
- import { readFile as readFile18 } from "fs/promises";
16746
+ import { readFile as readFile19 } from "fs/promises";
16452
16747
  import { basename as basename4 } from "path";
16453
16748
  async function buildAttachmentMessage(prompt, files, cwd, imageOptions2 = {}) {
16454
16749
  let text3 = prompt;
@@ -16471,7 +16766,7 @@ async function buildAttachmentMessage(prompt, files, cwd, imageOptions2 = {}) {
16471
16766
  }
16472
16767
  } else {
16473
16768
  try {
16474
- const content = await readFile18(filePath, "utf8");
16769
+ const content = await readFile19(filePath, "utf8");
16475
16770
  const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
16476
16771
  fileContents.push(`
16477
16772
  --- ${relPath} ---
@@ -16571,52 +16866,6 @@ var init_permissions_evaluator = __esm({
16571
16866
  }
16572
16867
  });
16573
16868
 
16574
- // src/intent/classify.ts
16575
- function classifyIntent(prompt) {
16576
- let intentScore = 0;
16577
- let matchedIntent = "other";
16578
- for (const [intent, pattern] of Object.entries(INTENT_PATTERNS)) {
16579
- const matches = (prompt.match(pattern) || []).length;
16580
- if (matches > intentScore) {
16581
- intentScore = matches;
16582
- matchedIntent = intent;
16583
- }
16584
- }
16585
- const hasFileMentions = (prompt.match(/@\w+|\b[\w/-]+\.(ts|tsx|js|jsx|py|go|rs)\b/g) || []).length;
16586
- const hasMutatingVerb = /\b(add|create|write|edit|delete|remove|rename|migrate|implement)\b/i.test(prompt);
16587
- const isQuestion = prompt.trim().endsWith("?") || /\b(what|how|why|is|does|can)\b/i.test(prompt.split(" ")[0] || "");
16588
- const hasHeavySignal = HEAVY_SIGNAL.test(prompt);
16589
- const rawScore = Math.min(
16590
- 1,
16591
- intentScore * 0.25 + (hasFileMentions > 2 ? 0.3 : hasFileMentions * 0.1) + (hasMutatingVerb ? 0.25 : 0) + (isQuestion ? 0 : 0.1) + (hasHeavySignal ? 0.6 : 0)
16592
- );
16593
- const tier = rawScore < 0.3 ? "light" : rawScore < 0.65 ? "medium" : "heavy";
16594
- return {
16595
- intent: hasHeavySignal && matchedIntent === "other" ? "explore" : matchedIntent,
16596
- rawScore,
16597
- tier,
16598
- confidence: 0.5 + (intentScore > 0 || hasHeavySignal ? 0.3 : 0) + (hasFileMentions > 0 ? 0.1 : 0)
16599
- };
16600
- }
16601
- var INTENT_PATTERNS, HEAVY_SIGNAL;
16602
- var init_classify = __esm({
16603
- "src/intent/classify.ts"() {
16604
- "use strict";
16605
- INTENT_PATTERNS = {
16606
- qa: /\b(what|how|why|explain|describe|what's|what is)\b/i,
16607
- diagnose: /\b(broken|failing|error|bug|crash|why.*fail|not working)\b/i,
16608
- verify: /\b(correct|right|verify|review|check|is this|does this)\b/i,
16609
- polish: /\b(rename|refactor|extract|move|clean|lint|format)\b/i,
16610
- small_edit: /\b(add|change|update|fix|remove|delete)\b.+\b(line|here|this|variable|function)\b/i,
16611
- feature_bounded: /\b(add|implement|create|support)\b.+\b(flag|option|param|arg|field)\b/i,
16612
- feature_exploratory: /\b(add|implement|migrate|integrate|build)\b.+\b(module|system|auth|oauth|framework|service)\b/i,
16613
- explore: /\b(how.*work|architecture|structure|where.*used|find.*all|understand)\b/i,
16614
- meta: /\b(plan|design|strategy|ontology|roadmap|approach)\b/i
16615
- };
16616
- HEAVY_SIGNAL = /\b(research|investigat\w*|audit|deep[\s-]?(dive|thinking)|explorat\w*|comprehensive|end[\s-]?to[\s-]?end|entire\s+(code\s?base|repo\w*|project|app)|whole\s+(code\s?base|repo\w*|project|app)|across\s+the\s+(code\s?base|repo\w*|project)|security|vulnerab\w*|loophole|exploit\w*|threat\s?model|attack\s+surface|penetration)\b/i;
16617
- }
16618
- });
16619
-
16620
16869
  // src/agent/session-core.ts
16621
16870
  function lastUserText(messages) {
16622
16871
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -16707,7 +16956,7 @@ __export(retention_exports, {
16707
16956
  listTraceSessions: () => listTraceSessions,
16708
16957
  pruneTraces: () => pruneTraces
16709
16958
  });
16710
- import { readdir as readdir5, rm as rm4, stat as stat7 } from "fs/promises";
16959
+ import { readdir as readdir5, rm as rm4, stat as stat8 } from "fs/promises";
16711
16960
  import { join as join29 } from "path";
16712
16961
  async function dirSize(path) {
16713
16962
  let total = 0;
@@ -16717,7 +16966,7 @@ async function dirSize(path) {
16717
16966
  if (entry.isDirectory()) total += await dirSize(p);
16718
16967
  else if (entry.isFile()) {
16719
16968
  try {
16720
- total += (await stat7(p)).size;
16969
+ total += (await stat8(p)).size;
16721
16970
  } catch {
16722
16971
  }
16723
16972
  }
@@ -16736,11 +16985,11 @@ async function listTraceSessions(root) {
16736
16985
  for (const name of names) {
16737
16986
  const path = join29(base, name);
16738
16987
  try {
16739
- const s = await stat7(path);
16988
+ const s = await stat8(path);
16740
16989
  if (!s.isDirectory()) continue;
16741
16990
  let mtime = s.mtime;
16742
16991
  try {
16743
- mtime = (await stat7(join29(path, TRACE_FILE_NAME))).mtime;
16992
+ mtime = (await stat8(join29(path, TRACE_FILE_NAME))).mtime;
16744
16993
  } catch {
16745
16994
  }
16746
16995
  sessions.push({ sessionId: name, path, mtime, bytes: await dirSize(path) });
@@ -17343,6 +17592,10 @@ function hookWirePayload(payload) {
17343
17592
  if (payload.session_id) Object.assign(wire, hookTranscriptFields(payload.session_id));
17344
17593
  if (isToolEventPayload(payload)) {
17345
17594
  Object.assign(wire, claudeToolFields(payload));
17595
+ if ((payload.event === "PostToolUse" || payload.event === "PostToolUseFailure") && payload.result.fileChange) {
17596
+ const { fileChange: _fileChange, ...result2 } = payload.result;
17597
+ wire.result = result2;
17598
+ }
17346
17599
  if (payload.tool_call_id) wire.tool_use_id = payload.tool_call_id;
17347
17600
  if (payload.event === "PermissionRequest") wire.permission_mode = claudePermissionMode(payload.mode);
17348
17601
  }
@@ -17514,7 +17767,7 @@ var init_runner = __esm({
17514
17767
  DECISION_STDOUT_CAP_BYTES = 64 * 1024;
17515
17768
  HOOK_ENV_PAYLOAD_MAX_BYTES = 32 * 1024;
17516
17769
  HOOK_BLOCK_EXIT_CODE = 2;
17517
- defaultSpawn = (command, payloadJson, env, cwd, timeoutMs, signal) => new Promise((resolve21) => {
17770
+ defaultSpawn = (command, payloadJson, env, cwd, timeoutMs, signal) => new Promise((resolve22) => {
17518
17771
  const child = spawn5(command, {
17519
17772
  shell: true,
17520
17773
  cwd,
@@ -17529,7 +17782,7 @@ var init_runner = __esm({
17529
17782
  settled = true;
17530
17783
  clearTimeout(timer2);
17531
17784
  signal?.removeEventListener("abort", onAbort);
17532
- resolve21({ exitCode, stdout, stderr, timedOut });
17785
+ resolve22({ exitCode, stdout, stderr, timedOut });
17533
17786
  };
17534
17787
  const onAbort = () => {
17535
17788
  child.kill("SIGTERM");
@@ -17677,7 +17930,7 @@ var init_manager2 = __esm({
17677
17930
  });
17678
17931
 
17679
17932
  // src/util/state.ts
17680
- import { readFile as readFile19, writeFile as writeFile11, mkdir as mkdir12 } from "fs/promises";
17933
+ import { readFile as readFile20, writeFile as writeFile11, mkdir as mkdir12 } from "fs/promises";
17681
17934
  import { homedir as homedir19 } from "os";
17682
17935
  import { join as join32 } from "path";
17683
17936
  function statePath() {
@@ -17686,7 +17939,7 @@ function statePath() {
17686
17939
  }
17687
17940
  async function readState() {
17688
17941
  try {
17689
- const raw = await readFile19(statePath(), "utf8");
17942
+ const raw = await readFile20(statePath(), "utf8");
17690
17943
  return JSON.parse(raw);
17691
17944
  } catch {
17692
17945
  return {};
@@ -17766,11 +18019,11 @@ var init_notices = __esm({
17766
18019
  });
17767
18020
 
17768
18021
  // src/eval/config.ts
17769
- import { readFile as readFile20 } from "fs/promises";
18022
+ import { readFile as readFile21 } from "fs/promises";
17770
18023
  import { basename as basename6, resolve as resolve8 } from "path";
17771
18024
  async function loadEvalConfig(file) {
17772
18025
  const path = resolve8(file);
17773
- const raw = JSON.parse(await readFile20(path, "utf8"));
18026
+ const raw = JSON.parse(await readFile21(path, "utf8"));
17774
18027
  if (!raw || typeof raw !== "object") throw new Error(`${file}: config must be a JSON object`);
17775
18028
  const config2 = raw.config ?? {};
17776
18029
  if (typeof config2 !== "object" || Array.isArray(config2)) throw new Error(`${file}: "config" must be an object`);
@@ -17790,7 +18043,7 @@ var init_config2 = __esm({
17790
18043
  });
17791
18044
 
17792
18045
  // src/eval/tasks.ts
17793
- import { readdir as readdir6, readFile as readFile21, stat as stat8 } from "fs/promises";
18046
+ import { readdir as readdir6, readFile as readFile22, stat as stat9 } from "fs/promises";
17794
18047
  import { basename as basename7, join as join33, resolve as resolve9 } from "path";
17795
18048
  function isNonEmptyString(v) {
17796
18049
  return typeof v === "string" && v.trim().length > 0;
@@ -17825,7 +18078,7 @@ async function loadTasks(dir, filter) {
17825
18078
  const seen = /* @__PURE__ */ new Set();
17826
18079
  for (const name of names) {
17827
18080
  const file = join33(root, name);
17828
- const task = validateTask(JSON.parse(await readFile21(file, "utf8")), name);
18081
+ const task = validateTask(JSON.parse(await readFile22(file, "utf8")), name);
17829
18082
  if (task.id !== basename7(name, ".json")) throw new Error(`${name}: "id" (${task.id}) must match the file name`);
17830
18083
  if (seen.has(task.id)) throw new Error(`duplicate task id ${task.id}`);
17831
18084
  seen.add(task.id);
@@ -17834,7 +18087,7 @@ async function loadTasks(dir, filter) {
17834
18087
  for (const [dest, src] of Object.entries(task.files ?? {})) {
17835
18088
  const abs = resolve9(root, src);
17836
18089
  if (!abs.startsWith(root)) throw new Error(`${name}: files["${dest}"] escapes the task directory`);
17837
- await stat8(abs).catch(() => {
18090
+ await stat9(abs).catch(() => {
17838
18091
  throw new Error(`${name}: files["${dest}"] -> ${src} does not exist`);
17839
18092
  });
17840
18093
  resolvedFiles[dest] = abs;
@@ -17855,7 +18108,7 @@ var init_tasks2 = __esm({
17855
18108
  // src/eval/workspace.ts
17856
18109
  import { execFile as execFile8 } from "child_process";
17857
18110
  import { createHash as createHash6 } from "crypto";
17858
- import { mkdir as mkdir13, rm as rm5, stat as stat9 } from "fs/promises";
18111
+ import { mkdir as mkdir13, rm as rm5, stat as stat10 } from "fs/promises";
17859
18112
  import { isAbsolute as isAbsolute4, join as join34, resolve as resolve10 } from "path";
17860
18113
  import { promisify as promisify7 } from "util";
17861
18114
  async function git3(args, cwd, timeoutMs = 6e5) {
@@ -17883,7 +18136,7 @@ async function prepareSourceRepoUncached(repo, cacheDir) {
17883
18136
  const slug = `${repo.replace(/[^A-Za-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(-60)}-${createHash6("sha1").update(repo).digest("hex").slice(0, 8)}`;
17884
18137
  const dir = join34(cacheDir, slug);
17885
18138
  await mkdir13(cacheDir, { recursive: true });
17886
- const exists = await stat9(join34(dir, ".git")).then(() => true).catch(() => false);
18139
+ const exists = await stat10(join34(dir, ".git")).then(() => true).catch(() => false);
17887
18140
  if (!exists) {
17888
18141
  await git3(["clone", "--quiet", "--no-checkout", repo, dir], cacheDir);
17889
18142
  } else {
@@ -19155,7 +19408,7 @@ var init_connection = __esm({
19155
19408
  if (this.child) {
19156
19409
  throw new Error("LSP connection already started");
19157
19410
  }
19158
- return new Promise((resolve21, reject) => {
19411
+ return new Promise((resolve22, reject) => {
19159
19412
  const abortController = new AbortController();
19160
19413
  const spawnTimer = setTimeout(() => {
19161
19414
  abortController.abort();
@@ -19194,7 +19447,7 @@ var init_connection = __esm({
19194
19447
  if (child.pid) {
19195
19448
  clearTimeout(spawnTimer);
19196
19449
  this.child = child;
19197
- resolve21();
19450
+ resolve22();
19198
19451
  }
19199
19452
  });
19200
19453
  } catch (err) {
@@ -19209,12 +19462,12 @@ var init_connection = __esm({
19209
19462
  }
19210
19463
  const id = this.nextId++;
19211
19464
  const msg = { jsonrpc: "2.0", id, method, params };
19212
- return new Promise((resolve21, reject) => {
19465
+ return new Promise((resolve22, reject) => {
19213
19466
  const timer2 = setTimeout(() => {
19214
19467
  this.pending.delete(id);
19215
19468
  reject(toolTimeoutError(`LSP request '${method}'`, this.requestTimeoutMs));
19216
19469
  }, this.requestTimeoutMs);
19217
- const pending = { resolve: resolve21, reject, signal, timer: timer2 };
19470
+ const pending = { resolve: resolve22, reject, signal, timer: timer2 };
19218
19471
  this.pending.set(id, pending);
19219
19472
  if (signal) {
19220
19473
  const onAbort = () => {
@@ -20118,7 +20371,7 @@ __export(openrouter_catalog_exports, {
20118
20371
  loadOpenRouterCatalog: () => loadOpenRouterCatalog,
20119
20372
  mapOpenRouterModel: () => mapOpenRouterModel
20120
20373
  });
20121
- import { readFile as readFile22, writeFile as writeFile12, mkdir as mkdir14 } from "fs/promises";
20374
+ import { readFile as readFile23, writeFile as writeFile12, mkdir as mkdir14 } from "fs/promises";
20122
20375
  import { homedir as homedir20 } from "os";
20123
20376
  import { join as join36, dirname as dirname17 } from "path";
20124
20377
  function catalogCachePath() {
@@ -20174,7 +20427,7 @@ async function fetchOpenRouterCatalog(fetchImpl2 = fetch) {
20174
20427
  }
20175
20428
  async function readCache3() {
20176
20429
  try {
20177
- const raw = await readFile22(catalogCachePath(), "utf8");
20430
+ const raw = await readFile23(catalogCachePath(), "utf8");
20178
20431
  const parsed = JSON.parse(raw);
20179
20432
  return parsed.version === CACHE_VERSION ? parsed : null;
20180
20433
  } catch {
@@ -20233,7 +20486,7 @@ __export(requesty_catalog_exports, {
20233
20486
  loadRequestyCatalog: () => loadRequestyCatalog,
20234
20487
  mapRequestyModel: () => mapRequestyModel
20235
20488
  });
20236
- import { readFile as readFile23, writeFile as writeFile13, mkdir as mkdir15 } from "fs/promises";
20489
+ import { readFile as readFile24, writeFile as writeFile13, mkdir as mkdir15 } from "fs/promises";
20237
20490
  import { homedir as homedir21 } from "os";
20238
20491
  import { join as join37, dirname as dirname18 } from "path";
20239
20492
  function catalogCachePath2() {
@@ -20294,7 +20547,7 @@ async function fetchRequestyCatalog(fetchImpl2 = fetch) {
20294
20547
  }
20295
20548
  async function readCache4() {
20296
20549
  try {
20297
- const raw = await readFile23(catalogCachePath2(), "utf8");
20550
+ const raw = await readFile24(catalogCachePath2(), "utf8");
20298
20551
  const parsed = JSON.parse(raw);
20299
20552
  if (parsed.version !== CACHE_VERSION2) return null;
20300
20553
  return {
@@ -20372,7 +20625,7 @@ __export(provider_catalog_exports, {
20372
20625
  mapModelsDevModel: () => mapModelsDevModel,
20373
20626
  mapModelsDevProvider: () => mapModelsDevProvider
20374
20627
  });
20375
- import { readFile as readFile24, writeFile as writeFile14, mkdir as mkdir16 } from "fs/promises";
20628
+ import { readFile as readFile25, writeFile as writeFile14, mkdir as mkdir16 } from "fs/promises";
20376
20629
  import { homedir as homedir22 } from "os";
20377
20630
  import { join as join38, dirname as dirname19 } from "path";
20378
20631
  function mapModelsDevModel(m) {
@@ -20412,7 +20665,7 @@ function cachePath2() {
20412
20665
  }
20413
20666
  async function readCache5() {
20414
20667
  try {
20415
- const parsed = JSON.parse(await readFile24(cachePath2(), "utf8"));
20668
+ const parsed = JSON.parse(await readFile25(cachePath2(), "utf8"));
20416
20669
  return parsed.version === CACHE_VERSION3 ? parsed : null;
20417
20670
  } catch {
20418
20671
  return null;
@@ -20787,6 +21040,7 @@ var init_session = __esm({
20787
21040
  "use strict";
20788
21041
  init_image();
20789
21042
  init_session_lifecycle();
21043
+ init_prompt_submit();
20790
21044
  init_process_exit();
20791
21045
  init_loop();
20792
21046
  init_system_prompt();
@@ -20930,6 +21184,16 @@ var init_session = __esm({
20930
21184
  return;
20931
21185
  }
20932
21186
  const mode = options?.mode ?? this.currentMode;
21187
+ const gate = await submitUserPrompt({
21188
+ ...this.hooks ? { hooks: this.hooks } : {},
21189
+ sessionId: this.sessionId,
21190
+ cwd: this.cwd,
21191
+ prompt: text3,
21192
+ resumed: this.messages.some((m) => m.role === "assistant"),
21193
+ model: this.model,
21194
+ auth: llmAuthFromConfig(this.config)
21195
+ });
21196
+ if (gate.blocked) throw new PromptBlockedError(gate.reason);
20933
21197
  const messageId = `msg_${this.nextMessageId++}`;
20934
21198
  let userContent2 = text3;
20935
21199
  if (options?.images && options.images.length > 0) {
@@ -21209,12 +21473,12 @@ ${notes.join("\n")}` };
21209
21473
  toolName: req.tool.name,
21210
21474
  args: req.args
21211
21475
  });
21212
- const decision = await new Promise((resolve21) => {
21213
- this.permissionResolvers.set(requestId, resolve21);
21476
+ const decision = await new Promise((resolve22) => {
21477
+ this.permissionResolvers.set(requestId, resolve22);
21214
21478
  setTimeout(() => {
21215
21479
  if (this.permissionResolvers.has(requestId)) {
21216
21480
  this.permissionResolvers.delete(requestId);
21217
- resolve21("deny");
21481
+ resolve22("deny");
21218
21482
  }
21219
21483
  }, 3e5);
21220
21484
  });
@@ -21275,7 +21539,7 @@ ${notes.join("\n")}` };
21275
21539
  this.lspManager?.notifyChange(path, content);
21276
21540
  } else {
21277
21541
  void import("fs/promises").then(
21278
- ({ readFile: readFile40 }) => readFile40(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
21542
+ ({ readFile: readFile42 }) => readFile42(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
21279
21543
  })
21280
21544
  ).catch(() => {
21281
21545
  });
@@ -21307,7 +21571,7 @@ __export(run_exports, {
21307
21571
  summarizeTrace: () => summarizeTrace
21308
21572
  });
21309
21573
  import { spawn as spawn7 } from "child_process";
21310
- import { appendFile as appendFile3, copyFile, mkdir as mkdir17, readFile as readFile25, writeFile as writeFile15 } from "fs/promises";
21574
+ import { appendFile as appendFile3, copyFile, mkdir as mkdir17, readFile as readFile26, writeFile as writeFile15 } from "fs/promises";
21311
21575
  import { dirname as dirname20, join as join40, resolve as resolve12 } from "path";
21312
21576
  import { performance as performance2 } from "perf_hooks";
21313
21577
  function runShell(command, cwd, timeoutMs) {
@@ -21350,7 +21614,7 @@ async function summarizeTrace(traceFile) {
21350
21614
  const out = { cost_usd: 0, prompt_tokens: 0, completion_tokens: 0, turns: 0, tool_calls: 0, overflow: false };
21351
21615
  let raw;
21352
21616
  try {
21353
- raw = await readFile25(traceFile, "utf8");
21617
+ raw = await readFile26(traceFile, "utf8");
21354
21618
  } catch {
21355
21619
  return out;
21356
21620
  }
@@ -21546,9 +21810,9 @@ __export(compare_exports, {
21546
21810
  perTask: () => perTask,
21547
21811
  summarizeArm: () => summarizeArm
21548
21812
  });
21549
- import { readFile as readFile26 } from "fs/promises";
21813
+ import { readFile as readFile27 } from "fs/promises";
21550
21814
  async function loadResults(file) {
21551
- const raw = await readFile26(file, "utf8");
21815
+ const raw = await readFile27(file, "utf8");
21552
21816
  return raw.split("\n").filter((l) => l.trim()).map((l) => JSON.parse(l));
21553
21817
  }
21554
21818
  function median(values) {
@@ -21681,7 +21945,7 @@ __export(cost_verify_exports, {
21681
21945
  verifySession: () => verifySession,
21682
21946
  verifyTurn: () => verifyTurn
21683
21947
  });
21684
- import { readFile as readFile27 } from "fs/promises";
21948
+ import { readFile as readFile28 } from "fs/promises";
21685
21949
  import { homedir as homedir24 } from "os";
21686
21950
  import { join as join41 } from "path";
21687
21951
  function usagePath2() {
@@ -21690,7 +21954,7 @@ function usagePath2() {
21690
21954
  }
21691
21955
  async function loadSessions() {
21692
21956
  try {
21693
- return JSON.parse(await readFile27(usagePath2(), "utf8")).sessions ?? [];
21957
+ return JSON.parse(await readFile28(usagePath2(), "utf8")).sessions ?? [];
21694
21958
  } catch {
21695
21959
  return [];
21696
21960
  }
@@ -22221,7 +22485,7 @@ var init_heuristic = __esm({
22221
22485
  });
22222
22486
 
22223
22487
  // src/cost-attribution/classify-from-session.ts
22224
- import { readFile as readFile28 } from "fs/promises";
22488
+ import { readFile as readFile29 } from "fs/promises";
22225
22489
  import { join as join42 } from "path";
22226
22490
  import { homedir as homedir25 } from "os";
22227
22491
  function sessionsDir2() {
@@ -22240,7 +22504,7 @@ function parseToolCalls(calls) {
22240
22504
  }
22241
22505
  async function classifyFromSessionFile(sessionId) {
22242
22506
  try {
22243
- const raw = await readFile28(join42(sessionsDir2(), `${sessionId}.json`), "utf8");
22507
+ const raw = await readFile29(join42(sessionsDir2(), `${sessionId}.json`), "utf8");
22244
22508
  const session = JSON.parse(raw);
22245
22509
  const messages = session.messages ?? [];
22246
22510
  const turns = [];
@@ -22273,7 +22537,7 @@ var cli_exports = {};
22273
22537
  __export(cli_exports, {
22274
22538
  runCostCommand: () => runCostCommand
22275
22539
  });
22276
- import { readFile as readFile29 } from "fs/promises";
22540
+ import { readFile as readFile30 } from "fs/promises";
22277
22541
  import { join as join43 } from "path";
22278
22542
  import { homedir as homedir26 } from "os";
22279
22543
  function usageDir2() {
@@ -22293,7 +22557,7 @@ function daysAgo(n) {
22293
22557
  }
22294
22558
  async function loadLog2() {
22295
22559
  try {
22296
- const raw = await readFile29(usagePath3(), "utf8");
22560
+ const raw = await readFile30(usagePath3(), "utf8");
22297
22561
  return JSON.parse(raw);
22298
22562
  } catch {
22299
22563
  return { version: 1, days: [], sessions: [] };
@@ -22377,6 +22641,662 @@ var init_cli = __esm({
22377
22641
  }
22378
22642
  });
22379
22643
 
22644
+ // src/remote/client.ts
22645
+ async function* parseEventStream(body) {
22646
+ const decoder = new TextDecoder();
22647
+ let buffer = "";
22648
+ for await (const chunk of body) {
22649
+ buffer += decoder.decode(chunk, { stream: true }).replace(/\r\n/g, "\n");
22650
+ let boundary;
22651
+ while ((boundary = buffer.indexOf("\n\n")) >= 0) {
22652
+ const frame = buffer.slice(0, boundary);
22653
+ buffer = buffer.slice(boundary + 2);
22654
+ const event = parseFrame(frame);
22655
+ if (event) yield event;
22656
+ }
22657
+ }
22658
+ }
22659
+ function parseFrame(frame) {
22660
+ let id;
22661
+ let type = "message";
22662
+ const data = [];
22663
+ for (const line of frame.split("\n")) {
22664
+ if (!line || line.startsWith(":")) continue;
22665
+ const colon = line.indexOf(":");
22666
+ const field = colon < 0 ? line : line.slice(0, colon);
22667
+ const value = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
22668
+ if (field === "id" && /^\d+$/.test(value)) id = Number(value);
22669
+ else if (field === "event") type = value;
22670
+ else if (field === "data") data.push(value);
22671
+ }
22672
+ if (id === void 0) return void 0;
22673
+ let parsed = {};
22674
+ try {
22675
+ parsed = data.length ? JSON.parse(data.join("\n")) : {};
22676
+ } catch {
22677
+ return void 0;
22678
+ }
22679
+ return { id, type, data: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {} };
22680
+ }
22681
+ async function toApiError(response) {
22682
+ let code = `http_${response.status}`;
22683
+ let message2 = `Server returned ${response.status}`;
22684
+ try {
22685
+ const body = await response.json();
22686
+ if (body.error?.code) code = body.error.code;
22687
+ if (body.error?.message) message2 = body.error.message;
22688
+ } catch {
22689
+ }
22690
+ return new RemoteApiError(response.status, code, message2);
22691
+ }
22692
+ function delay(ms, signal) {
22693
+ return new Promise((resolve22, reject) => {
22694
+ if (signal?.aborted) return reject(signal.reason);
22695
+ const timer2 = setTimeout(() => {
22696
+ signal?.removeEventListener("abort", onAbort);
22697
+ resolve22();
22698
+ }, ms);
22699
+ const onAbort = () => {
22700
+ clearTimeout(timer2);
22701
+ reject(signal?.reason);
22702
+ };
22703
+ signal?.addEventListener("abort", onAbort, { once: true });
22704
+ });
22705
+ }
22706
+ var TERMINAL_STATUSES, RemoteApiError, RemoteClient;
22707
+ var init_client3 = __esm({
22708
+ "src/remote/client.ts"() {
22709
+ "use strict";
22710
+ TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "interrupted", "merged", "destroyed"]);
22711
+ RemoteApiError = class extends Error {
22712
+ constructor(status, code, message2) {
22713
+ super(message2);
22714
+ this.status = status;
22715
+ this.code = code;
22716
+ this.name = "RemoteApiError";
22717
+ }
22718
+ status;
22719
+ code;
22720
+ };
22721
+ RemoteClient = class {
22722
+ constructor(baseUrl, token, fetchImpl2 = fetch) {
22723
+ this.baseUrl = baseUrl;
22724
+ this.token = token;
22725
+ this.fetchImpl = fetchImpl2;
22726
+ }
22727
+ baseUrl;
22728
+ token;
22729
+ fetchImpl;
22730
+ startDeviceAuthorization(clientName2) {
22731
+ return this.request("POST", "/auth/device", { body: { clientName: clientName2 }, auth: false });
22732
+ }
22733
+ /** Polls until the owner decides; resolves with the credential, rejects on denial or expiry. */
22734
+ async waitForCredential(device, options = {}) {
22735
+ const sleep6 = options.sleep ?? ((ms) => delay(ms, options.signal));
22736
+ let intervalMs = Math.max(1, device.interval) * 1e3;
22737
+ const deadline = Date.now() + device.expiresIn * 1e3;
22738
+ while (Date.now() < deadline) {
22739
+ await sleep6(intervalMs);
22740
+ try {
22741
+ return await this.request("POST", "/auth/device/token", { body: { deviceCode: device.deviceCode }, auth: false, signal: options.signal });
22742
+ } catch (error) {
22743
+ if (!(error instanceof RemoteApiError)) throw error;
22744
+ if (error.code === "authorization_pending") continue;
22745
+ if (error.code === "slow_down") {
22746
+ intervalMs += 5e3;
22747
+ continue;
22748
+ }
22749
+ throw error;
22750
+ }
22751
+ }
22752
+ throw new RemoteApiError(400, "expired_token", "The pairing code expired before it was approved; run the command again");
22753
+ }
22754
+ health() {
22755
+ return this.request("GET", "/health");
22756
+ }
22757
+ async workspaces() {
22758
+ return (await this.request("GET", "/workspaces")).workspaces;
22759
+ }
22760
+ async models() {
22761
+ return (await this.request("GET", "/models")).models;
22762
+ }
22763
+ createCodeConversation(input) {
22764
+ const { idempotencyKey, ...body } = input;
22765
+ return this.withRetries(() => this.request("POST", "/conversations", {
22766
+ body: { ...body, kind: "code" },
22767
+ headers: { "Idempotency-Key": idempotencyKey }
22768
+ }));
22769
+ }
22770
+ submitTurn(conversationId, text3, clientTurnId) {
22771
+ return this.withRetries(() => this.request("POST", `/conversations/${encodeURIComponent(conversationId)}/turns`, { body: { clientTurnId, text: text3 } }));
22772
+ }
22773
+ getConversation(conversationId) {
22774
+ return this.request("GET", `/conversations/${encodeURIComponent(conversationId)}`);
22775
+ }
22776
+ async listConversations(limit = 50) {
22777
+ return (await this.request("GET", `/conversations?limit=${limit}`)).conversations;
22778
+ }
22779
+ cancel(conversationId) {
22780
+ return this.request("POST", `/conversations/${encodeURIComponent(conversationId)}/cancel`);
22781
+ }
22782
+ /**
22783
+ * Events after `cursor`, reconnecting with the last seen ID when the stream drops,
22784
+ * until the caller stops iterating or the signal aborts.
22785
+ */
22786
+ async *events(conversationId, cursor, signal) {
22787
+ let last = cursor;
22788
+ let failures = 0;
22789
+ while (!signal?.aborted) {
22790
+ try {
22791
+ const response = await this.fetchImpl(this.url(`/conversations/${encodeURIComponent(conversationId)}/events`), {
22792
+ headers: { ...this.authHeaders(), Accept: "text/event-stream", "Last-Event-ID": String(last) },
22793
+ signal
22794
+ });
22795
+ if (!response.ok || !response.body) throw await toApiError(response);
22796
+ failures = 0;
22797
+ for await (const event of parseEventStream(response.body)) {
22798
+ if (event.id <= last) continue;
22799
+ last = event.id;
22800
+ yield event;
22801
+ }
22802
+ } catch (error) {
22803
+ if (signal?.aborted) return;
22804
+ if (error instanceof RemoteApiError && error.status < 500) throw error;
22805
+ if (++failures > 30) throw error;
22806
+ }
22807
+ await delay(Math.min(1e3 * failures, 1e4), signal).catch(() => {
22808
+ });
22809
+ }
22810
+ }
22811
+ async request(method, path, options = {}) {
22812
+ const response = await this.fetchImpl(this.url(path), {
22813
+ method,
22814
+ headers: {
22815
+ ...options.auth === false ? {} : this.authHeaders(),
22816
+ ...options.body === void 0 ? {} : { "Content-Type": "application/json" },
22817
+ ...options.headers
22818
+ },
22819
+ ...options.body === void 0 ? {} : { body: JSON.stringify(options.body) },
22820
+ ...options.signal ? { signal: options.signal } : {}
22821
+ });
22822
+ if (!response.ok) throw await toApiError(response);
22823
+ return await response.json();
22824
+ }
22825
+ /** Retries network failures and 5xx; safe only for requests carrying an idempotency key. */
22826
+ async withRetries(attempt) {
22827
+ for (let tries = 1; ; tries++) {
22828
+ try {
22829
+ return await attempt();
22830
+ } catch (error) {
22831
+ const retryable = !(error instanceof RemoteApiError) || error.status >= 500;
22832
+ if (!retryable || tries >= 3) throw error;
22833
+ await delay(1e3 * tries);
22834
+ }
22835
+ }
22836
+ }
22837
+ url(path) {
22838
+ return `${this.baseUrl}/api/v1${path}`;
22839
+ }
22840
+ authHeaders() {
22841
+ return this.token ? { Authorization: `Bearer ${this.token}` } : {};
22842
+ }
22843
+ };
22844
+ }
22845
+ });
22846
+
22847
+ // src/remote/config.ts
22848
+ import { chmod as chmod4, mkdir as mkdir18, readFile as readFile31, rename as rename4, writeFile as writeFile16 } from "fs/promises";
22849
+ import { homedir as homedir27 } from "os";
22850
+ import { dirname as dirname21, join as join44 } from "path";
22851
+ function remotesPath(env = process.env) {
22852
+ if (env.AUTOPILOT_REMOTES_FILE) return env.AUTOPILOT_REMOTES_FILE;
22853
+ const configHome2 = env.XDG_CONFIG_HOME?.trim() || join44(homedir27(), ".config");
22854
+ return join44(configHome2, "autopilot", "remotes.json");
22855
+ }
22856
+ async function loadRemotes(path = remotesPath()) {
22857
+ let raw;
22858
+ try {
22859
+ raw = await readFile31(path, "utf8");
22860
+ } catch (error) {
22861
+ if (error.code === "ENOENT") return [];
22862
+ throw error;
22863
+ }
22864
+ const parsed = JSON.parse(raw);
22865
+ return Array.isArray(parsed.remotes) ? parsed.remotes.filter(isRemote) : [];
22866
+ }
22867
+ async function saveRemote(remote, path = remotesPath()) {
22868
+ const remotes = (await loadRemotes(path)).filter((entry) => entry.name !== remote.name);
22869
+ remotes.push(remote);
22870
+ await writeRemotes(remotes, path);
22871
+ }
22872
+ async function removeRemote(name, path = remotesPath()) {
22873
+ const remotes = await loadRemotes(path);
22874
+ const kept = remotes.filter((entry) => entry.name !== name);
22875
+ if (kept.length === remotes.length) return false;
22876
+ await writeRemotes(kept, path);
22877
+ return true;
22878
+ }
22879
+ async function resolveRemote(name, path = remotesPath()) {
22880
+ const remotes = await loadRemotes(path);
22881
+ if (remotes.length === 0) throw new Error("No servers paired yet. Run: autopilot remote add https://your-server");
22882
+ if (name === void 0) {
22883
+ if (remotes.length === 1) return remotes[0];
22884
+ throw new Error(`Several servers are paired; pick one with --on (${remotes.map((entry) => entry.name).join(", ")})`);
22885
+ }
22886
+ const remote = remotes.find((entry) => entry.name === name);
22887
+ if (!remote) throw new Error(`No server named "${name}". Paired: ${remotes.map((entry) => entry.name).join(", ")}`);
22888
+ return remote;
22889
+ }
22890
+ function defaultRemoteName(url) {
22891
+ const host = new URL(url).hostname.toLowerCase();
22892
+ const label = /^\d+(\.\d+){3}$/.test(host) ? host.replace(/\./g, "-") : host.split(".")[0] ?? "server";
22893
+ const name = label.replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "").slice(0, 40);
22894
+ return REMOTE_NAME_RE.test(name) ? name : "server";
22895
+ }
22896
+ function normalizeServerUrl(input) {
22897
+ let url;
22898
+ try {
22899
+ url = new URL(/^[a-z]+:\/\//i.test(input) ? input : `https://${input}`);
22900
+ } catch {
22901
+ throw new Error(`Not a URL: ${input}`);
22902
+ }
22903
+ const loopback = url.hostname === "localhost" || url.hostname === "[::1]" || url.hostname.startsWith("127.");
22904
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
22905
+ throw new Error("Use an https:// server URL (http:// is only allowed for localhost)");
22906
+ }
22907
+ if (url.username || url.password || url.search || url.hash) throw new Error("The server URL must not include credentials, a query, or a fragment");
22908
+ return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
22909
+ }
22910
+ async function writeRemotes(remotes, path) {
22911
+ await mkdir18(dirname21(path), { recursive: true, mode: 448 });
22912
+ const temp = `${path}.${process.pid}.tmp`;
22913
+ await writeFile16(temp, `${JSON.stringify({ version: 1, remotes }, null, 2)}
22914
+ `, { mode: 384 });
22915
+ await chmod4(temp, 384);
22916
+ await rename4(temp, path);
22917
+ }
22918
+ function isRemote(value) {
22919
+ if (!value || typeof value !== "object") return false;
22920
+ const entry = value;
22921
+ return ["name", "url", "token", "credentialId", "expiresAt", "addedAt"].every((key) => typeof entry[key] === "string");
22922
+ }
22923
+ var REMOTE_NAME_RE;
22924
+ var init_config4 = __esm({
22925
+ "src/remote/config.ts"() {
22926
+ "use strict";
22927
+ REMOTE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,39}$/;
22928
+ }
22929
+ });
22930
+
22931
+ // src/remote/git.ts
22932
+ import { execFile as execFile9 } from "child_process";
22933
+ import { mkdtemp as mkdtemp3, rm as rm6, copyFile as copyFile2 } from "fs/promises";
22934
+ import { tmpdir as tmpdir5 } from "os";
22935
+ import { join as join45, resolve as resolve13 } from "path";
22936
+ import { promisify as promisify8 } from "util";
22937
+ function parseGitHubRemote(url) {
22938
+ const match = url.trim().match(/^(?:https:\/\/(?:[^@/]+@)?github\.com\/|ssh:\/\/git@github\.com(?::\d+)?\/|git@github\.com:)([A-Za-z0-9-]{1,39})\/([A-Za-z0-9._-]{1,100}?)(?:\.git)?\/?$/);
22939
+ return match ? `${match[1]}/${match[2]}` : void 0;
22940
+ }
22941
+ async function detectGitHubRepository(cwd, remote = "origin") {
22942
+ let url;
22943
+ try {
22944
+ url = await git4(cwd, ["remote", "get-url", remote]);
22945
+ } catch {
22946
+ throw new Error(`This directory has no "${remote}" git remote; pass --repo owner/name`);
22947
+ }
22948
+ const repository = parseGitHubRemote(url);
22949
+ if (!repository) throw new Error(`The "${remote}" remote is not a GitHub repository (${url}); pass --repo owner/name`);
22950
+ return repository;
22951
+ }
22952
+ async function snapshotWorkingTree(cwd) {
22953
+ const root = await git4(cwd, ["rev-parse", "--show-toplevel"]).catch(() => {
22954
+ throw new Error("Not inside a git repository");
22955
+ });
22956
+ await git4(root, ["rev-parse", "--verify", "HEAD"]).catch(() => {
22957
+ throw new Error("This repository has no commits yet; push a first commit before running remotely");
22958
+ });
22959
+ const baseRef = await upstreamRef(root);
22960
+ const baseCommit = await git4(root, ["merge-base", "HEAD", baseRef]).catch(() => {
22961
+ throw new Error(`HEAD shares no history with ${baseRef}`);
22962
+ });
22963
+ const scratch = await mkdtemp3(join45(tmpdir5(), "autopilot-snapshot-"));
22964
+ try {
22965
+ const index = join45(scratch, "index");
22966
+ const realIndex = resolve13(root, await git4(root, ["rev-parse", "--git-path", "index"]));
22967
+ await copyFile2(realIndex, index).catch(() => {
22968
+ });
22969
+ const env = { ...process.env, GIT_INDEX_FILE: index };
22970
+ await git4(root, ["add", "--all"], env);
22971
+ const patch = await git4(root, ["diff", "--cached", "--binary", "--no-color", "--no-ext-diff", baseCommit], env, false);
22972
+ if (patch.length > MAX_PATCH_CHARS) {
22973
+ throw new Error(`Local changes are ${(patch.length / 1024 / 1024).toFixed(1)} MiB, over the 3 MiB limit; commit and push some of them first`);
22974
+ }
22975
+ const files = (await git4(root, ["diff", "--cached", "--name-only", "--no-renames", baseCommit], env)).split("\n").filter(Boolean);
22976
+ return { baseCommit, patch, baseRef, files };
22977
+ } finally {
22978
+ await rm6(scratch, { recursive: true, force: true });
22979
+ }
22980
+ }
22981
+ async function upstreamRef(root) {
22982
+ const upstream = await git4(root, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]).catch(() => "");
22983
+ if (upstream.startsWith("origin/")) return upstream;
22984
+ const originHead = await git4(root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]).catch(() => "");
22985
+ if (originHead) return originHead;
22986
+ for (const candidate of ["origin/main", "origin/master"]) {
22987
+ if (await git4(root, ["rev-parse", "--verify", "--quiet", candidate]).then(() => true, () => false)) return candidate;
22988
+ }
22989
+ throw new Error("Can't find this branch on origin; run `git fetch origin` (or push the branch) first");
22990
+ }
22991
+ async function git4(cwd, args, env, trim = true) {
22992
+ const { stdout } = await execFileAsync6("git", ["-C", cwd, ...args], { env: env ?? process.env, maxBuffer: 64 * 1024 * 1024 });
22993
+ return trim ? stdout.trim() : stdout;
22994
+ }
22995
+ var execFileAsync6, MAX_PATCH_CHARS;
22996
+ var init_git = __esm({
22997
+ "src/remote/git.ts"() {
22998
+ "use strict";
22999
+ execFileAsync6 = promisify8(execFile9);
23000
+ MAX_PATCH_CHARS = 3 * 1024 * 1024;
23001
+ }
23002
+ });
23003
+
23004
+ // src/remote/cli.ts
23005
+ var cli_exports2 = {};
23006
+ __export(cli_exports2, {
23007
+ cancelRun: () => cancelRun,
23008
+ describeError: () => describeError,
23009
+ listRuns: () => listRuns,
23010
+ pullRun: () => pullRun,
23011
+ remoteAdd: () => remoteAdd,
23012
+ remoteList: () => remoteList,
23013
+ remoteRemove: () => remoteRemove,
23014
+ runOnRemote: () => runOnRemote,
23015
+ watchRun: () => watchRun
23016
+ });
23017
+ import { execFile as execFile10 } from "child_process";
23018
+ import { randomUUID as randomUUID8 } from "crypto";
23019
+ import { hostname } from "os";
23020
+ import { promisify as promisify9 } from "util";
23021
+ async function remoteAdd(input, io = processIO) {
23022
+ const url = normalizeServerUrl(input.url);
23023
+ const name = input.name ?? defaultRemoteName(url);
23024
+ if (!REMOTE_NAME_RE.test(name)) throw new Error("Server names are 1-40 lowercase letters, digits, or dashes");
23025
+ const anonymous = new RemoteClient(url, void 0);
23026
+ const device = await anonymous.startDeviceAuthorization(clientName());
23027
+ io.out(`To pair this machine with ${url}, approve code ${device.userCode} at:
23028
+ ${device.verificationUriComplete}
23029
+ `);
23030
+ if (input.open !== false && openBrowser(device.verificationUriComplete)) io.out("(opened in your browser)\n");
23031
+ io.out("Waiting for approval\u2026\n");
23032
+ const credential = await anonymous.waitForCredential(device, {
23033
+ ...input.signal ? { signal: input.signal } : {},
23034
+ ...input.sleep ? { sleep: input.sleep } : {}
23035
+ });
23036
+ const remote = {
23037
+ name,
23038
+ url,
23039
+ token: credential.token,
23040
+ credentialId: credential.credentialId,
23041
+ expiresAt: credential.expiresAt,
23042
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
23043
+ };
23044
+ await new RemoteClient(url, remote.token).health();
23045
+ await saveRemote(remote);
23046
+ io.out(`\u2713 Paired as "${name}" (credential expires ${remote.expiresAt.slice(0, 10)}).
23047
+ Try: autopilot --on ${name} "fix the flaky test"
23048
+ `);
23049
+ return remote;
23050
+ }
23051
+ async function remoteList(io = processIO) {
23052
+ const remotes = await loadRemotes();
23053
+ if (remotes.length === 0) {
23054
+ io.out("No servers paired. Run: autopilot remote add https://your-server\n");
23055
+ return;
23056
+ }
23057
+ for (const remote of remotes) io.out(`${remote.name} ${remote.url} expires ${remote.expiresAt.slice(0, 10)}
23058
+ `);
23059
+ }
23060
+ async function remoteRemove(name, io = processIO) {
23061
+ const removed = await removeRemote(name);
23062
+ if (removed) io.out(`Removed "${name}". To revoke its credential on the server: autopilot aster token revoke <id>
23063
+ `);
23064
+ else io.err(`No server named "${name}".
23065
+ `);
23066
+ return removed;
23067
+ }
23068
+ async function runOnRemote(input, io = processIO) {
23069
+ const remote = await resolveRemote(input.remote);
23070
+ const client = new RemoteClient(remote.url, remote.token);
23071
+ const repository = input.repository ?? await detectGitHubRepository(input.cwd);
23072
+ const snapshot = await snapshotWorkingTree(input.cwd);
23073
+ const [workspaces, models] = await Promise.all([client.workspaces(), client.models()]);
23074
+ const workspace = workspaces[0];
23075
+ if (!workspace) throw new Error(`${remote.name} has no workspace this credential can use`);
23076
+ const model = pickModel(models, input.model, input.preferredModel);
23077
+ io.err(`Starting on ${remote.name}: ${repository} from ${snapshot.baseCommit.slice(0, 7)} (${snapshot.baseRef})${snapshot.files.length ? `, with ${snapshot.files.length} changed file${snapshot.files.length === 1 ? "" : "s"}` : ""} \xB7 ${model}
23078
+ `);
23079
+ const conversation = await client.createCodeConversation({
23080
+ workspaceId: workspace.id,
23081
+ model,
23082
+ repository,
23083
+ baseCommit: snapshot.baseCommit,
23084
+ ...snapshot.patch ? { patch: snapshot.patch } : {},
23085
+ idempotencyKey: `cli-${randomUUID8()}`
23086
+ });
23087
+ await client.submitTurn(conversation.conversationId, input.task, `cli-${randomUUID8()}`);
23088
+ const shortId = conversation.conversationId.slice(0, 8);
23089
+ io.err(`Run ${shortId} \xB7 branch ${conversation.branch ?? "?"}
23090
+ `);
23091
+ if (input.detach) {
23092
+ io.err(`Detached. Follow it with: autopilot runs watch ${shortId}
23093
+ `);
23094
+ return 0;
23095
+ }
23096
+ io.err(`Ctrl-C detaches; the run keeps going on the server (autopilot runs watch ${shortId}).
23097
+
23098
+ `);
23099
+ return watchConversation(client, remote, conversation.conversationId, conversation.lastEventId, input.signal, io);
23100
+ }
23101
+ async function listRuns(input, io = processIO) {
23102
+ const remotes = input.remote ? [await resolveRemote(input.remote)] : await loadRemotes();
23103
+ if (remotes.length === 0) throw new Error("No servers paired yet. Run: autopilot remote add https://your-server");
23104
+ const rows = [];
23105
+ for (const remote of remotes) {
23106
+ try {
23107
+ const runs = await new RemoteClient(remote.url, remote.token).listConversations();
23108
+ rows.push(...runs.filter((run) => run.kind === "code").map((run) => ({ ...run, remote: remote.name })));
23109
+ } catch (error) {
23110
+ io.err(`${remote.name}: ${describeError(error)}
23111
+ `);
23112
+ }
23113
+ }
23114
+ rows.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
23115
+ if (input.json) {
23116
+ io.out(`${JSON.stringify(rows, null, 2)}
23117
+ `);
23118
+ return;
23119
+ }
23120
+ if (rows.length === 0) {
23121
+ io.out("No runs yet.\n");
23122
+ return;
23123
+ }
23124
+ for (const run of rows) {
23125
+ io.out([
23126
+ run.conversationId.slice(0, 8),
23127
+ run.remote,
23128
+ run.status.padEnd(16),
23129
+ run.repository ?? "",
23130
+ run.pullRequestURL ?? run.branch ?? "",
23131
+ run.createdAt.slice(0, 16).replace("T", " ")
23132
+ ].join(" ") + "\n");
23133
+ }
23134
+ }
23135
+ async function watchRun(input, io = processIO) {
23136
+ const { remote, run } = await findRun(input.id, input.remote);
23137
+ const client = new RemoteClient(remote.url, remote.token);
23138
+ const finished = TERMINAL_STATUSES.has(run.status) && !run.activeRunId;
23139
+ return watchConversation(client, remote, run.conversationId, 0, input.signal, io, finished ? run.lastEventId : void 0);
23140
+ }
23141
+ async function cancelRun(input, io = processIO) {
23142
+ const { remote, run } = await findRun(input.id, input.remote);
23143
+ const cancelled = await new RemoteClient(remote.url, remote.token).cancel(run.conversationId);
23144
+ io.out(`${run.conversationId.slice(0, 8)}: ${cancelled.status}
23145
+ `);
23146
+ }
23147
+ async function pullRun(input, io = processIO) {
23148
+ const { run } = await findRun(input.id, input.remote);
23149
+ const branch = run.branch;
23150
+ if (!branch) throw new Error("That run has no branch");
23151
+ if (run.activeRunId) io.err("The run is still going; you'll get what it has pushed so far.\n");
23152
+ const git5 = (args) => execFileAsync7("git", ["-C", input.cwd, ...args]);
23153
+ try {
23154
+ await git5(["fetch", "origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`]);
23155
+ } catch {
23156
+ throw new Error(`Branch ${branch} isn't on origin yet (the run pushes it when a turn finishes)`);
23157
+ }
23158
+ const exists = await git5(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]).then(() => true, () => false);
23159
+ if (exists) {
23160
+ await git5(["switch", branch]);
23161
+ await git5(["merge", "--ff-only", `origin/${branch}`]);
23162
+ } else {
23163
+ await git5(["switch", "-c", branch, "--track", `origin/${branch}`]);
23164
+ }
23165
+ io.out(`On ${branch}${run.pullRequestURL ? ` \xB7 ${run.pullRequestURL}` : ""}
23166
+ `);
23167
+ }
23168
+ async function watchConversation(client, remote, conversationId, cursor, signal, io, replayUntil) {
23169
+ const controller = new AbortController();
23170
+ const stop = () => controller.abort();
23171
+ signal?.addEventListener("abort", stop, { once: true });
23172
+ if (signal?.aborted) stop();
23173
+ const renderer = new EventRenderer(io);
23174
+ let outcome;
23175
+ try {
23176
+ if (replayUntil === void 0 || replayUntil > cursor) {
23177
+ for await (const event of client.events(conversationId, cursor, controller.signal)) {
23178
+ renderer.render(event);
23179
+ if (RUN_EVENT_TYPES.has(event.type)) outcome = event.type;
23180
+ if (replayUntil === void 0 ? RUN_EVENT_TYPES.has(event.type) : event.id >= replayUntil) break;
23181
+ }
23182
+ }
23183
+ if (replayUntil !== void 0 && !controller.signal.aborted) outcome ??= "completed";
23184
+ } finally {
23185
+ signal?.removeEventListener("abort", stop);
23186
+ controller.abort();
23187
+ }
23188
+ renderer.finish();
23189
+ const shortId = conversationId.slice(0, 8);
23190
+ if (!outcome) {
23191
+ io.err(`
23192
+ Detached from ${shortId}; it keeps running on ${remote.name}. Follow it with: autopilot runs watch ${shortId}
23193
+ `);
23194
+ return 0;
23195
+ }
23196
+ const final = await client.getConversation(conversationId);
23197
+ if (outcome === "completed") {
23198
+ io.err(`
23199
+ \u2713 ${shortId} completed${final.pullRequestURL ? `: ${final.pullRequestURL}` : " (no changes, so no pull request)"}
23200
+ `);
23201
+ if (final.pullRequestURL) io.err(` Check it out locally: autopilot pull ${shortId}
23202
+ `);
23203
+ return 0;
23204
+ }
23205
+ io.err(`
23206
+ \u2717 ${shortId} ${outcome}
23207
+ `);
23208
+ return 1;
23209
+ }
23210
+ async function findRun(id, remoteName) {
23211
+ const needle = id.trim().toLowerCase();
23212
+ if (!/^[0-9a-f-]{4,36}$/.test(needle)) throw new Error("Run IDs are the hex IDs `autopilot runs` shows");
23213
+ const remotes = remoteName ? [await resolveRemote(remoteName)] : await loadRemotes();
23214
+ const matches = [];
23215
+ for (const remote of remotes) {
23216
+ const runs = await new RemoteClient(remote.url, remote.token).listConversations(200).catch(() => []);
23217
+ for (const run of runs) if (run.conversationId.startsWith(needle)) matches.push({ remote, run });
23218
+ }
23219
+ if (matches.length === 0) throw new Error(`No run ${id} on ${remoteName ?? "any paired server"}`);
23220
+ if (matches.length > 1) throw new Error(`${id} matches several runs; use more characters`);
23221
+ return matches[0];
23222
+ }
23223
+ function pickModel(models, requested, preferred) {
23224
+ if (requested) {
23225
+ if (!models.includes(requested)) throw new Error(`The server doesn't offer ${requested}`);
23226
+ return requested;
23227
+ }
23228
+ if (preferred && models.includes(preferred)) return preferred;
23229
+ const first = models[0];
23230
+ if (!first) throw new Error("The server offers no models");
23231
+ return first;
23232
+ }
23233
+ function clientName() {
23234
+ const host = hostname().replace(/\.local$/, "").replace(/[^\p{L}\p{N} ._'()@-]/gu, "-").slice(0, 40) || "laptop";
23235
+ return `${host} (autopilot)`;
23236
+ }
23237
+ function oneLine(text3, max) {
23238
+ const flat = text3.replace(/\s+/g, " ").trim();
23239
+ return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
23240
+ }
23241
+ function describeError(error) {
23242
+ if (error instanceof RemoteApiError) return `${error.message} (${error.code})`;
23243
+ return error instanceof Error ? error.message : String(error);
23244
+ }
23245
+ var execFileAsync7, RUN_EVENT_TYPES, processIO, EventRenderer;
23246
+ var init_cli2 = __esm({
23247
+ "src/remote/cli.ts"() {
23248
+ "use strict";
23249
+ init_open_browser();
23250
+ init_client3();
23251
+ init_config4();
23252
+ init_git();
23253
+ execFileAsync7 = promisify9(execFile10);
23254
+ RUN_EVENT_TYPES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "interrupted"]);
23255
+ processIO = {
23256
+ out: (text3) => process.stdout.write(text3),
23257
+ err: (text3) => process.stderr.write(text3)
23258
+ };
23259
+ EventRenderer = class {
23260
+ constructor(io) {
23261
+ this.io = io;
23262
+ }
23263
+ io;
23264
+ midLine = false;
23265
+ render(event) {
23266
+ const data = event.data;
23267
+ if (event.type === "assistant.delta") {
23268
+ const text3 = typeof data.text === "string" ? data.text : typeof data.delta === "string" ? data.delta : "";
23269
+ if (!text3) return;
23270
+ this.io.out(text3);
23271
+ this.midLine = !text3.endsWith("\n");
23272
+ return;
23273
+ }
23274
+ if (event.type === "tool.activity" && data.activity === "started") {
23275
+ const input = typeof data.input === "string" ? ` ${oneLine(data.input, 120)}` : "";
23276
+ this.line(` \u203A ${String(data.tool ?? "tool")}${input}`);
23277
+ return;
23278
+ }
23279
+ if (event.type === "tool.activity" && data.activity === "failed") {
23280
+ this.line(` \u2717 ${String(data.tool ?? "tool")} failed`);
23281
+ return;
23282
+ }
23283
+ if (event.type === "checkpoint.created" && typeof data.message === "string") {
23284
+ this.line(` \u25CF ${oneLine(data.message, 100)}`);
23285
+ }
23286
+ }
23287
+ finish() {
23288
+ if (this.midLine) this.io.out("\n");
23289
+ this.midLine = false;
23290
+ }
23291
+ line(text3) {
23292
+ this.finish();
23293
+ this.io.out(`${text3}
23294
+ `);
23295
+ }
23296
+ };
23297
+ }
23298
+ });
23299
+
22380
23300
  // src/server/aster-workspaces.ts
22381
23301
  var aster_workspaces_exports = {};
22382
23302
  __export(aster_workspaces_exports, {
@@ -22387,23 +23307,23 @@ __export(aster_workspaces_exports, {
22387
23307
  resolveAsterServerConfigPath: () => resolveAsterServerConfigPath
22388
23308
  });
22389
23309
  import { existsSync as existsSync11 } from "fs";
22390
- import { readFile as readFile30, realpath as realpath5, stat as stat10 } from "fs/promises";
22391
- import { homedir as homedir27 } from "os";
22392
- import { isAbsolute as isAbsolute5, join as join44, relative as relative8, resolve as resolve13, sep as sep4 } from "path";
23310
+ import { readFile as readFile32, realpath as realpath5, stat as stat11 } from "fs/promises";
23311
+ import { homedir as homedir28 } from "os";
23312
+ import { isAbsolute as isAbsolute5, join as join46, relative as relative8, resolve as resolve14, sep as sep4 } from "path";
22393
23313
  function localAsterConfigPath(env = process.env) {
22394
- const configHome2 = env.XDG_CONFIG_HOME?.trim() || join44(homedir27(), ".config");
22395
- return resolve13(configHome2, "autopilot", "aster.json");
23314
+ const configHome2 = env.XDG_CONFIG_HOME?.trim() || join46(homedir28(), ".config");
23315
+ return resolve14(configHome2, "autopilot", "aster.json");
22396
23316
  }
22397
23317
  function resolveAsterServerConfigPath(env = process.env) {
22398
23318
  const configuredPath = env.AUTOPILOT_ASTER_CONFIG?.trim();
22399
- if (configuredPath) return resolve13(configuredPath);
23319
+ if (configuredPath) return resolve14(configuredPath);
22400
23320
  const userConfigPath = localAsterConfigPath(env);
22401
23321
  return existsSync11(userConfigPath) ? userConfigPath : SYSTEM_ASTER_CONFIG_PATH;
22402
23322
  }
22403
23323
  async function loadAsterServerConfig(configPath2 = resolveAsterServerConfigPath()) {
22404
23324
  let raw;
22405
23325
  try {
22406
- raw = JSON.parse(await readFile30(configPath2, "utf8"));
23326
+ raw = JSON.parse(await readFile32(configPath2, "utf8"));
22407
23327
  } catch {
22408
23328
  throw new AsterConfigError();
22409
23329
  }
@@ -22440,7 +23360,7 @@ async function loadAsterServerConfig(configPath2 = resolveAsterServerConfigPath(
22440
23360
  let rootPath;
22441
23361
  try {
22442
23362
  rootPath = await realpath5(workspace.rootPath);
22443
- if (!(await stat10(rootPath)).isDirectory()) throw new Error("not a directory");
23363
+ if (!(await stat11(rootPath)).isDirectory()) throw new Error("not a directory");
22444
23364
  } catch {
22445
23365
  throw new AsterConfigError();
22446
23366
  }
@@ -22480,10 +23400,10 @@ __export(aster_setup_exports, {
22480
23400
  setupLocalAsterConfig: () => setupLocalAsterConfig
22481
23401
  });
22482
23402
  import { execFileSync as execFileSync3 } from "child_process";
22483
- import { randomUUID as randomUUID8 } from "crypto";
23403
+ import { randomUUID as randomUUID9 } from "crypto";
22484
23404
  import { existsSync as existsSync12 } from "fs";
22485
- import { chmod as chmod4, link, mkdir as mkdir18, realpath as realpath6, rm as rm6, stat as stat11, writeFile as writeFile16 } from "fs/promises";
22486
- import { basename as basename9, dirname as dirname21, resolve as resolve14 } from "path";
23405
+ import { chmod as chmod5, link, mkdir as mkdir19, realpath as realpath6, rm as rm7, stat as stat12, writeFile as writeFile17 } from "fs/promises";
23406
+ import { basename as basename9, dirname as dirname22, resolve as resolve15 } from "path";
22487
23407
  async function setupLocalAsterConfig(options) {
22488
23408
  if (options.workspacePaths.length === 0 || options.workspacePaths.length > 100) {
22489
23409
  throw new Error("provide between 1 and 100 Git workspace roots");
@@ -22495,7 +23415,7 @@ async function setupLocalAsterConfig(options) {
22495
23415
  const requestedWorkspaces = await createWorkspaceEntries(options.workspacePaths);
22496
23416
  const env = options.env ?? process.env;
22497
23417
  const requestedPath = options.configPath?.trim() || env.AUTOPILOT_ASTER_CONFIG?.trim() || localAsterConfigPath(env);
22498
- const path = resolve14(requestedPath);
23418
+ const path = resolve15(requestedPath);
22499
23419
  if (existsSync12(path)) {
22500
23420
  const config3 = await loadAsterServerConfig(path);
22501
23421
  const selectedWorkspaces = requestedWorkspaces.map((requested) => {
@@ -22511,21 +23431,21 @@ async function setupLocalAsterConfig(options) {
22511
23431
  models: models.length > 0 ? models : "openrouter",
22512
23432
  workspaces: requestedWorkspaces
22513
23433
  };
22514
- const directory = dirname21(path);
22515
- await mkdir18(directory, { recursive: true, mode: 448 });
22516
- await chmod4(directory, 448).catch(() => {
23434
+ const directory = dirname22(path);
23435
+ await mkdir19(directory, { recursive: true, mode: 448 });
23436
+ await chmod5(directory, 448).catch(() => {
22517
23437
  });
22518
- const temporaryPath = `${path}.tmp-${randomUUID8()}`;
23438
+ const temporaryPath = `${path}.tmp-${randomUUID9()}`;
22519
23439
  try {
22520
- await writeFile16(temporaryPath, `${JSON.stringify(config2, null, 2)}
23440
+ await writeFile17(temporaryPath, `${JSON.stringify(config2, null, 2)}
22521
23441
  `, { encoding: "utf8", mode: 384, flag: "wx" });
22522
- await chmod4(temporaryPath, 384);
23442
+ await chmod5(temporaryPath, 384);
22523
23443
  const validated = await loadAsterServerConfig(temporaryPath);
22524
23444
  await link(temporaryPath, path);
22525
- await rm6(temporaryPath);
23445
+ await rm7(temporaryPath);
22526
23446
  return { path, config: validated, selectedWorkspaces: validated.workspaces };
22527
23447
  } catch (error) {
22528
- await rm6(temporaryPath, { force: true }).catch(() => {
23448
+ await rm7(temporaryPath, { force: true }).catch(() => {
22529
23449
  });
22530
23450
  if (isAlreadyExists(error)) {
22531
23451
  return setupLocalAsterConfig(options);
@@ -22537,11 +23457,11 @@ async function createWorkspaceEntries(paths) {
22537
23457
  const usedIds = /* @__PURE__ */ new Set();
22538
23458
  const workspaces = [];
22539
23459
  for (const input of paths) {
22540
- const requested = resolve14(input);
23460
+ const requested = resolve15(input);
22541
23461
  let root;
22542
23462
  try {
22543
23463
  root = await realpath6(requested);
22544
- if (!(await stat11(root)).isDirectory()) throw new Error("not a directory");
23464
+ if (!(await stat12(root)).isDirectory()) throw new Error("not a directory");
22545
23465
  const output = execFileSync3("git", ["-C", root, "rev-parse", "--show-toplevel"], {
22546
23466
  encoding: "utf8",
22547
23467
  stdio: ["ignore", "pipe", "ignore"]
@@ -22588,13 +23508,13 @@ __export(aster_store_exports, {
22588
23508
  normalizeUserCode: () => normalizeUserCode
22589
23509
  });
22590
23510
  import Database4 from "better-sqlite3";
22591
- import { createHash as createHash7, randomBytes as randomBytes3, randomInt, randomUUID as randomUUID9 } from "crypto";
23511
+ import { createHash as createHash7, randomBytes as randomBytes3, randomInt, randomUUID as randomUUID10 } from "crypto";
22592
23512
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync10 } from "fs";
22593
- import { homedir as homedir28 } from "os";
22594
- import { dirname as dirname22, join as join45 } from "path";
23513
+ import { homedir as homedir29 } from "os";
23514
+ import { dirname as dirname23, join as join47 } from "path";
22595
23515
  function defaultAsterDbPath() {
22596
- const root = process.env.XDG_STATE_HOME || join45(homedir28(), ".local", "state");
22597
- return process.env.AUTOPILOT_ASTER_DB || join45(root, "autopilot", "aster.db");
23516
+ const root = process.env.XDG_STATE_HOME || join47(homedir29(), ".local", "state");
23517
+ return process.env.AUTOPILOT_ASTER_DB || join47(root, "autopilot", "aster.db");
22598
23518
  }
22599
23519
  function hashToken(token) {
22600
23520
  return createHash7("sha256").update(token).digest("hex");
@@ -22681,9 +23601,9 @@ var init_aster_store = __esm({
22681
23601
  AsterStore = class {
22682
23602
  db;
22683
23603
  constructor(dbPath = defaultAsterDbPath()) {
22684
- mkdirSync10(dirname22(dbPath), { recursive: true, mode: 448 });
23604
+ mkdirSync10(dirname23(dbPath), { recursive: true, mode: 448 });
22685
23605
  try {
22686
- chmodSync2(dirname22(dbPath), 448);
23606
+ chmodSync2(dirname23(dbPath), 448);
22687
23607
  } catch {
22688
23608
  }
22689
23609
  this.db = new Database4(dbPath);
@@ -22898,7 +23818,7 @@ var init_aster_store = __esm({
22898
23818
  const scopes = [...new Set(input.scopes)];
22899
23819
  if (scopes.length === 0 || scopes.some((scope) => !VALID_SCOPES.has(scope))) throw new Error("credential scopes are invalid");
22900
23820
  if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= Date.now()) throw new Error("credential expiry must be in the future");
22901
- const id = randomUUID9();
23821
+ const id = randomUUID10();
22902
23822
  const token = `aster_${randomBytes3(32).toString("base64url")}`;
22903
23823
  const now2 = Date.now();
22904
23824
  this.db.prepare(`INSERT INTO aster_credentials
@@ -22952,7 +23872,7 @@ var init_aster_store = __esm({
22952
23872
  }
22953
23873
  /** The owner's decision on a user code. An approval fixes the credential the client will receive. */
22954
23874
  resolveDeviceAuthorization(userCode, decision, grant, now2 = Date.now()) {
22955
- const resolve21 = this.db.transaction(() => {
23875
+ const resolve22 = this.db.transaction(() => {
22956
23876
  const row = this.db.prepare("SELECT * FROM aster_device_authorizations WHERE user_code = ?").get(normalizeUserCode(userCode));
22957
23877
  if (!row) return "not_found";
22958
23878
  if (row.status !== "pending") return "already_resolved";
@@ -22965,7 +23885,7 @@ var init_aster_store = __esm({
22965
23885
  SET status = 'approved', workspace_ids_json = ?, credential_ttl_ms = ?, resolved_at = ? WHERE user_code = ?`).run(JSON.stringify(grant.workspaceIds), grant.credentialTtlMs, now2, row.user_code);
22966
23886
  return "approved";
22967
23887
  });
22968
- return resolve21.immediate();
23888
+ return resolve22.immediate();
22969
23889
  }
22970
23890
  /** The client's poll. An approved code is exchanged for its credential exactly once. */
22971
23891
  redeemDeviceAuthorization(deviceCode, now2 = Date.now()) {
@@ -23212,7 +24132,7 @@ var init_aster_store = __esm({
23212
24132
  }));
23213
24133
  }
23214
24134
  createApproval(input) {
23215
- const id = randomUUID9();
24135
+ const id = randomUUID10();
23216
24136
  const now2 = Date.now();
23217
24137
  this.db.prepare(`INSERT INTO aster_approvals
23218
24138
  (id, conversation_id, run_id, tool_name, explanation, arguments_json, status, expires_at, created_at)
@@ -23315,9 +24235,9 @@ async function handleAsterPairing(req, res, url, store) {
23315
24235
  const method = req.method ?? "GET";
23316
24236
  if (path === `${AUTH_PREFIX}/device` && method === "POST") {
23317
24237
  const body = await readJsonBody(req);
23318
- const clientName = typeof body.clientName === "string" ? body.clientName.trim() : "Autopilot CLI";
23319
- if (!CLIENT_NAME_RE.test(clientName)) return sendError(res, 400, "invalid_client_name", "clientName must be 1-60 letters, digits, spaces, or ._'()@-");
23320
- const started2 = store.createDeviceAuthorization({ clientName, ttlMs: DEVICE_CODE_TTL_MS, maxPending: MAX_PENDING_DEVICE_CODES });
24238
+ const clientName2 = typeof body.clientName === "string" ? body.clientName.trim() : "Autopilot CLI";
24239
+ if (!CLIENT_NAME_RE.test(clientName2)) return sendError(res, 400, "invalid_client_name", "clientName must be 1-60 letters, digits, spaces, or ._'()@-");
24240
+ const started2 = store.createDeviceAuthorization({ clientName: clientName2, ttlMs: DEVICE_CODE_TTL_MS, maxPending: MAX_PENDING_DEVICE_CODES });
23321
24241
  if (!started2) return sendError(res, 429, "too_many_pending", "Too many pairings are waiting for approval; try again later");
23322
24242
  const verificationUri = `${publicBaseUrl(req)}${AUTH_PREFIX}/approve`;
23323
24243
  return json(res, 200, {
@@ -23464,9 +24384,9 @@ function notOwnerBody(code) {
23464
24384
  <p>You can also approve from the server directly:</p>
23465
24385
  <pre>autopilot aster pair approve ${escapeHtml2(code ?? "<code>")}</pre>`;
23466
24386
  }
23467
- function approvalForm(userCode, clientName) {
24387
+ function approvalForm(userCode, clientName2) {
23468
24388
  return `
23469
- <p><strong>${escapeHtml2(clientName)}</strong> wants to use this server.</p>
24389
+ <p><strong>${escapeHtml2(clientName2)}</strong> wants to use this server.</p>
23470
24390
  <p class="code">${escapeHtml2(userCode)}</p>
23471
24391
  <p class="muted">Approve only if this matches the code shown on your device.</p>
23472
24392
  <form method="post" action="${AUTH_PREFIX}/approve">
@@ -23587,7 +24507,7 @@ __export(keys_exports, {
23587
24507
  saveProviderKey: () => saveProviderKey,
23588
24508
  switchProvider: () => switchProvider
23589
24509
  });
23590
- import { readFile as readFile31 } from "fs/promises";
24510
+ import { readFile as readFile33 } from "fs/promises";
23591
24511
  function looksLikeProviderKey(id, key) {
23592
24512
  if (id === "openrouter") return looksLikeOpenRouterKey(key);
23593
24513
  return key.length >= 8 && !/\s/.test(key);
@@ -23670,7 +24590,7 @@ async function checkProviderKey(id, key, baseUrl, fetchImpl2 = fetch) {
23670
24590
  }
23671
24591
  async function persistedProviders() {
23672
24592
  try {
23673
- const raw = JSON.parse(await readFile31(configPath(), "utf8"));
24593
+ const raw = JSON.parse(await readFile33(configPath(), "utf8"));
23674
24594
  return raw.providers ?? {};
23675
24595
  } catch {
23676
24596
  return {};
@@ -23948,7 +24868,7 @@ __export(github_auth_exports, {
23948
24868
  authGitHubForTui: () => authGitHubForTui
23949
24869
  });
23950
24870
  function sleep4(ms) {
23951
- return new Promise((resolve21) => setTimeout(resolve21, ms));
24871
+ return new Promise((resolve22) => setTimeout(resolve22, ms));
23952
24872
  }
23953
24873
  async function* authGitHubForTui() {
23954
24874
  yield { message: "Starting GitHub OAuth device flow..." };
@@ -24030,6 +24950,23 @@ var init_github_auth = __esm({
24030
24950
  }
24031
24951
  });
24032
24952
 
24953
+ // src/server/serve-config.ts
24954
+ var serve_config_exports = {};
24955
+ __export(serve_config_exports, {
24956
+ resolveServeConfig: () => resolveServeConfig
24957
+ });
24958
+ function resolveServeConfig(cfg, env = process.env) {
24959
+ if (cfg) return { config: cfg, asterOnly: false };
24960
+ if (!env.AUTOPILOT_HOTCELL_ENDPOINT?.trim()) return void 0;
24961
+ return { config: { model: DEFAULT_MODEL }, asterOnly: true };
24962
+ }
24963
+ var init_serve_config = __esm({
24964
+ "src/server/serve-config.ts"() {
24965
+ "use strict";
24966
+ init_config();
24967
+ }
24968
+ });
24969
+
24033
24970
  // src/runs/notifications.ts
24034
24971
  function resendTransport(apiKey, opts2 = {}) {
24035
24972
  const fetchImpl2 = opts2.fetchImpl ?? fetch;
@@ -24955,13 +25892,13 @@ var init_openapi = __esm({
24955
25892
  });
24956
25893
 
24957
25894
  // src/runs/worktrees.ts
24958
- import { execFile as execFile9 } from "child_process";
24959
- import { access as access2, chmod as chmod5, mkdir as mkdir19, realpath as realpath7 } from "fs/promises";
24960
- import { homedir as homedir29 } from "os";
24961
- import { isAbsolute as isAbsolute6, join as join46, parse, relative as relative9, resolve as resolve15, sep as sep5 } from "path";
25895
+ import { execFile as execFile11 } from "child_process";
25896
+ import { access as access2, chmod as chmod6, mkdir as mkdir20, realpath as realpath7 } from "fs/promises";
25897
+ import { homedir as homedir30 } from "os";
25898
+ import { isAbsolute as isAbsolute6, join as join48, parse, relative as relative9, resolve as resolve16, sep as sep5 } from "path";
24962
25899
  function defaultWorktreesDir() {
24963
- const dataHome = process.env.XDG_DATA_HOME || join46(homedir29(), ".local", "share");
24964
- return join46(dataHome, "autopilot", "worktrees");
25900
+ const dataHome = process.env.XDG_DATA_HOME || join48(homedir30(), ".local", "share");
25901
+ return join48(dataHome, "autopilot", "worktrees");
24965
25902
  }
24966
25903
  function isOutside(path) {
24967
25904
  return path === ".." || path.startsWith(`..${sep5}`) || isAbsolute6(path);
@@ -24972,7 +25909,7 @@ function isWithin3(parent, candidate) {
24972
25909
  }
24973
25910
  function runGit(args, cwd) {
24974
25911
  return new Promise((resolveOutput, reject) => {
24975
- execFile9("git", args, { cwd, encoding: "utf8", timeout: 3e4, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
25912
+ execFile11("git", args, { cwd, encoding: "utf8", timeout: 3e4, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
24976
25913
  if (error) {
24977
25914
  const detail = stderr.trim();
24978
25915
  reject(new Error(`git ${args[0]} failed${detail ? `: ${detail}` : ""}`, { cause: error }));
@@ -25002,17 +25939,17 @@ var init_worktrees = __esm({
25002
25939
  if (isOutside(sourceRelativePath)) {
25003
25940
  throw new Error("sourceCwd must be inside the Git repository");
25004
25941
  }
25005
- const configuredRoot = resolve15(this.rootDir);
25942
+ const configuredRoot = resolve16(this.rootDir);
25006
25943
  if (configuredRoot === parse(configuredRoot).root || isWithin3(repository, configuredRoot)) {
25007
25944
  throw new Error("worktree root must be outside the source repository and not the filesystem root");
25008
25945
  }
25009
- await mkdir19(configuredRoot, { recursive: true, mode: 448 });
25946
+ await mkdir20(configuredRoot, { recursive: true, mode: 448 });
25010
25947
  const worktreesRoot = await realpath7(configuredRoot);
25011
25948
  if (isWithin3(repository, worktreesRoot)) {
25012
25949
  throw new Error("worktree root must be outside the source repository");
25013
25950
  }
25014
- await chmod5(worktreesRoot, 448);
25015
- const worktreePath = join46(worktreesRoot, runId);
25951
+ await chmod6(worktreesRoot, 448);
25952
+ const worktreePath = join48(worktreesRoot, runId);
25016
25953
  try {
25017
25954
  await access2(worktreePath);
25018
25955
  throw new Error(`worktree path already exists for run ${runId}`);
@@ -25026,7 +25963,7 @@ var init_worktrees = __esm({
25026
25963
  repositoryRoot: repository,
25027
25964
  sourceCwd: source,
25028
25965
  worktreePath,
25029
- cwd: resolve15(worktreePath, sourceRelativePath),
25966
+ cwd: resolve16(worktreePath, sourceRelativePath),
25030
25967
  branch
25031
25968
  };
25032
25969
  }
@@ -25108,10 +26045,10 @@ var init_wake_scheduler = __esm({
25108
26045
  scheduleNextTick() {
25109
26046
  if (!this.running || this.disposed) return;
25110
26047
  const nextAt = this.store.nextTimerAt();
25111
- const delay = nextAt === null ? IDLE_RECHECK_MS : Math.max(0, Math.min(MAX_NODE_TIMEOUT_MS, nextAt - Date.now()));
26048
+ const delay2 = nextAt === null ? IDLE_RECHECK_MS : Math.max(0, Math.min(MAX_NODE_TIMEOUT_MS, nextAt - Date.now()));
25112
26049
  this.timer = setTimeout(() => {
25113
26050
  void this.tick();
25114
- }, delay);
26051
+ }, delay2);
25115
26052
  this.timer.unref();
25116
26053
  }
25117
26054
  async processTimer(timer2, now2) {
@@ -25473,9 +26410,9 @@ var init_aster_tools = __esm({
25473
26410
 
25474
26411
  // src/server/aster-memory.ts
25475
26412
  import Database5 from "better-sqlite3";
25476
- import { randomUUID as randomUUID10 } from "crypto";
26413
+ import { randomUUID as randomUUID11 } from "crypto";
25477
26414
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync11 } from "fs";
25478
- import { dirname as dirname23 } from "path";
26415
+ import { dirname as dirname24 } from "path";
25479
26416
  function rowToItem(row) {
25480
26417
  return {
25481
26418
  id: row.id,
@@ -25528,7 +26465,7 @@ var init_aster_memory = __esm({
25528
26465
  AsterMemory = class {
25529
26466
  constructor(dbPath = defaultAsterDbPath(), options = {}) {
25530
26467
  this.options = options;
25531
- mkdirSync11(dirname23(dbPath), { recursive: true, mode: 448 });
26468
+ mkdirSync11(dirname24(dbPath), { recursive: true, mode: 448 });
25532
26469
  this.db = new Database5(dbPath);
25533
26470
  this.db.pragma("journal_mode = WAL");
25534
26471
  this.db.pragma("busy_timeout = 5000");
@@ -25592,7 +26529,7 @@ var init_aster_memory = __esm({
25592
26529
  this.db.prepare("UPDATE aster_memories SET title = ?, content = ?, pinned = ?, updated_at = ? WHERE id = ?").run(title, content, existing.pinned || input.pinned ? 1 : 0, now2, existing.id);
25593
26530
  return { item: this.get(existing.id), change: "updated" };
25594
26531
  }
25595
- const id = randomUUID10();
26532
+ const id = randomUUID11();
25596
26533
  this.db.prepare(`INSERT INTO aster_memories (id, kind, key, title, content, pinned, source_conversation_id, created_at, updated_at)
25597
26534
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.kind, input.key ?? null, title, content, input.pinned ? 1 : 0, input.sourceConversationId ?? null, now2, now2);
25598
26535
  return { item: this.get(id), change: "added" };
@@ -25780,14 +26717,14 @@ function parseReceivePackCommands(data) {
25780
26717
  function isSafeBranch(branch) {
25781
26718
  return /^[A-Za-z0-9._/-]{1,200}$/.test(branch) && !branch.startsWith("-") && !branch.endsWith("/") && !branch.includes("..") && !branch.includes("//") && !branch.includes("@{") && branch.split("/").every((part) => part !== "" && part !== "." && part !== ".." && !part.endsWith(".lock"));
25782
26719
  }
25783
- function upstreamHeaders(req, token, git4) {
26720
+ function upstreamHeaders(req, token, git5) {
25784
26721
  const headers = {};
25785
26722
  for (const name of FORWARDED_REQUEST_HEADERS) {
25786
26723
  const value = req.headers[name];
25787
26724
  if (typeof value === "string") headers[name] = value;
25788
26725
  }
25789
26726
  headers["user-agent"] ??= "autopilot-aster";
25790
- headers.authorization = git4 ? "Basic " + Buffer.from(`x-access-token:${token}`).toString("base64") : `Bearer ${token}`;
26727
+ headers.authorization = git5 ? "Basic " + Buffer.from(`x-access-token:${token}`).toString("base64") : `Bearer ${token}`;
25791
26728
  return headers;
25792
26729
  }
25793
26730
  function pipeResponse(upstreamRes, res) {
@@ -25837,9 +26774,9 @@ var init_aster_github_proxy = __esm({
25837
26774
  gitUpstream;
25838
26775
  async listen(port, host) {
25839
26776
  this.server = createServer2((req, res) => this.handle(req, res));
25840
- await new Promise((resolve21, reject) => {
26777
+ await new Promise((resolve22, reject) => {
25841
26778
  this.server.once("error", reject);
25842
- this.server.listen(port, host, () => resolve21());
26779
+ this.server.listen(port, host, () => resolve22());
25843
26780
  });
25844
26781
  const address = this.server.address();
25845
26782
  return typeof address === "object" && address ? address.port : port;
@@ -25850,8 +26787,8 @@ var init_aster_github_proxy = __esm({
25850
26787
  }
25851
26788
  handle(req, res, pathOverride) {
25852
26789
  const url = new URL(pathOverride ?? req.url ?? "/", "http://proxy.local");
25853
- const git4 = url.pathname.match(GIT_PATH_RE);
25854
- if (!git4) return deny(res, 404, "Only Git Smart HTTP is available through this proxy");
26790
+ const git5 = url.pathname.match(GIT_PATH_RE);
26791
+ if (!git5) return deny(res, 404, "Only Git Smart HTTP is available through this proxy");
25855
26792
  const grantToken = extractToken(req);
25856
26793
  if (!grantToken) {
25857
26794
  res.writeHead(401, { "www-authenticate": 'Basic realm="aster-github"', "content-type": "text/plain" });
@@ -25860,7 +26797,7 @@ var init_aster_github_proxy = __esm({
25860
26797
  }
25861
26798
  const grant = this.options.resolveGrant(grantToken);
25862
26799
  if (!grant) return deny(res, 403, "This repository grant is invalid or was revoked");
25863
- const [, owner, name, service] = git4;
26800
+ const [, owner, name, service] = git5;
25864
26801
  if (`${owner}/${name}`.toLowerCase() !== grant.repository.toLowerCase()) {
25865
26802
  return deny(res, 403, `This conversation can only access ${grant.repository}`);
25866
26803
  }
@@ -26021,7 +26958,7 @@ var init_aster_push = __esm({
26021
26958
  };
26022
26959
  if (this.options.deliver) return this.options.deliver({ host, token, headers, payload });
26023
26960
  const session = this.session(host);
26024
- return await new Promise((resolve21, reject) => {
26961
+ return await new Promise((resolve22, reject) => {
26025
26962
  const stream = session.request({ ":method": "POST", ":path": `/3/device/${token}`, ...headers, "content-type": "application/json" });
26026
26963
  let status = 0;
26027
26964
  stream.setTimeout(15e3, () => stream.close());
@@ -26029,7 +26966,7 @@ var init_aster_push = __esm({
26029
26966
  status = Number(responseHeaders[":status"]) || 0;
26030
26967
  });
26031
26968
  stream.on("error", reject);
26032
- stream.on("close", () => resolve21(status));
26969
+ stream.on("close", () => resolve22(status));
26033
26970
  stream.resume();
26034
26971
  stream.end(payload);
26035
26972
  });
@@ -26060,9 +26997,9 @@ var init_aster_push = __esm({
26060
26997
  });
26061
26998
 
26062
26999
  // src/server/aster-api.ts
26063
- import { randomUUID as randomUUID11 } from "crypto";
27000
+ import { randomUUID as randomUUID12 } from "crypto";
26064
27001
  import { unlink as unlink3 } from "fs/promises";
26065
- import { resolve as resolve16 } from "path";
27002
+ import { resolve as resolve17 } from "path";
26066
27003
  function parseCursor(header, query) {
26067
27004
  const value = (Array.isArray(header) ? header[0] : header) ?? query ?? "0";
26068
27005
  if (!/^\d{1,16}$/.test(value)) return void 0;
@@ -26164,7 +27101,7 @@ function json2(res, status, data) {
26164
27101
  res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
26165
27102
  res.end(JSON.stringify(data));
26166
27103
  }
26167
- var API_PREFIX, MAX_BODY_BYTES, MAX_USER_TURN_CHARS, CLIENT_TURN_ID_RE, CREATE_IDEMPOTENCY_KEY_RE, MAX_APPROVAL_ARG_BYTES, CONVERSATION_ID_RE, ALLOWED_ASTER_TOOLS, BASE_COMMIT_RE, MAX_PATCH_CHARS, MAX_LISTED_CONVERSATIONS, AsterApi;
27104
+ var API_PREFIX, MAX_BODY_BYTES, MAX_USER_TURN_CHARS, CLIENT_TURN_ID_RE, CREATE_IDEMPOTENCY_KEY_RE, MAX_APPROVAL_ARG_BYTES, CONVERSATION_ID_RE, ALLOWED_ASTER_TOOLS, BASE_COMMIT_RE, MAX_PATCH_CHARS2, MAX_LISTED_CONVERSATIONS, AsterApi;
26168
27105
  var init_aster_api = __esm({
26169
27106
  "src/server/aster-api.ts"() {
26170
27107
  "use strict";
@@ -26190,7 +27127,7 @@ var init_aster_api = __esm({
26190
27127
  CONVERSATION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
26191
27128
  ALLOWED_ASTER_TOOLS = ["read", "write", "edit"];
26192
27129
  BASE_COMMIT_RE = /^[0-9a-f]{40}$/;
26193
- MAX_PATCH_CHARS = 3 * 1024 * 1024;
27130
+ MAX_PATCH_CHARS2 = 3 * 1024 * 1024;
26194
27131
  MAX_LISTED_CONVERSATIONS = 200;
26195
27132
  AsterApi = class {
26196
27133
  store;
@@ -26310,7 +27247,7 @@ var init_aster_api = __esm({
26310
27247
  if (body.baseCommit !== void 0 && (typeof body.baseCommit !== "string" || !BASE_COMMIT_RE.test(body.baseCommit))) {
26311
27248
  return sendError2(res, 400, "invalid_base_commit", "baseCommit must be a full 40-character commit SHA");
26312
27249
  }
26313
- if (body.patch !== void 0 && (typeof body.patch !== "string" || body.patch.length > MAX_PATCH_CHARS)) {
27250
+ if (body.patch !== void 0 && (typeof body.patch !== "string" || body.patch.length > MAX_PATCH_CHARS2)) {
26314
27251
  return sendError2(res, 400, "invalid_patch", "patch must be a git diff of at most 3 MiB");
26315
27252
  }
26316
27253
  if (typeof body.patch === "string" && containsLikelyProviderSecret(body.patch)) {
@@ -26675,8 +27612,8 @@ var init_aster_api = __esm({
26675
27612
  };
26676
27613
  }
26677
27614
  async createConversation(res, store, principal, workspace, model, idempotencyKey, kind, repository, changes) {
26678
- const candidateId = randomUUID11();
26679
- const candidateSessionId = randomUUID11();
27615
+ const candidateId = randomUUID12();
27616
+ const candidateSessionId = randomUUID12();
26680
27617
  const reservation = store.reserveCellConversation({
26681
27618
  conversationId: candidateId,
26682
27619
  credentialId: principal.credentialId,
@@ -26817,12 +27754,12 @@ var init_aster_api = __esm({
26817
27754
  sendError2(res, 409, "conversation_cell_unavailable", "Conversation cell is not available");
26818
27755
  return;
26819
27756
  }
26820
- const runId = randomUUID11();
27757
+ const runId = randomUUID12();
26821
27758
  if (!idempotent && !store.beginTurn(conversation.id, runId)) {
26822
27759
  sendError2(res, 409, "conversation_busy", "A turn is already active for this conversation");
26823
27760
  return;
26824
27761
  }
26825
- const sessionPath = resolve16(sessionsDir(), conversation.sessionId + ".json");
27762
+ const sessionPath = resolve17(sessionsDir(), conversation.sessionId + ".json");
26826
27763
  let sessionFile;
26827
27764
  try {
26828
27765
  sessionFile = await loadSession(sessionPath);
@@ -27132,7 +28069,7 @@ var init_aster_api = __esm({
27132
28069
  }
27133
28070
  try {
27134
28071
  if (mapping.cellId) await this.runtime.destroyCell(mapping.cellId);
27135
- await unlink3(resolve16(sessionsDir(), mapping.sessionId + ".json")).catch(() => {
28072
+ await unlink3(resolve17(sessionsDir(), mapping.sessionId + ".json")).catch(() => {
27136
28073
  });
27137
28074
  store.setCellStatus(conversation.id, "destroyed");
27138
28075
  store.setConversationStatus(conversation.id, "destroyed");
@@ -27179,7 +28116,7 @@ var init_aster_api = __esm({
27179
28116
  return void 0;
27180
28117
  }
27181
28118
  try {
27182
- const sessionPath = resolve16(sessionsDir(), conversation.sessionId + ".json");
28119
+ const sessionPath = resolve17(sessionsDir(), conversation.sessionId + ".json");
27183
28120
  const sessionFile = await loadSession(sessionPath);
27184
28121
  sessionFile.model = model;
27185
28122
  await saveSession(sessionFile);
@@ -27318,10 +28255,10 @@ data: ${JSON.stringify(event.data)}
27318
28255
  });
27319
28256
 
27320
28257
  // src/server/aster-cell-runtime.ts
27321
- import { execFile as execFile10 } from "child_process";
27322
- import { readFile as readFile32 } from "fs/promises";
27323
- import { promisify as promisify8 } from "util";
27324
- import { randomUUID as randomUUID12 } from "crypto";
28258
+ import { execFile as execFile12 } from "child_process";
28259
+ import { readFile as readFile34 } from "fs/promises";
28260
+ import { promisify as promisify10 } from "util";
28261
+ import { randomUUID as randomUUID13 } from "crypto";
27325
28262
  async function writeCellFileChunked(cell, path, content) {
27326
28263
  if (Buffer.byteLength(content, "utf8") <= CELL_FILE_WRITE_CHUNK_CHARS) {
27327
28264
  await cell.writeFile(path, content, { mode: "0600" });
@@ -27349,14 +28286,14 @@ async function writeCellFileChunked(cell, path, content) {
27349
28286
  async function readBundledCellSdkBundle() {
27350
28287
  for (const url of [new URL("./sdk/index.js", import.meta.url), new URL("../sdk/index.js", import.meta.url)]) {
27351
28288
  try {
27352
- return await readFile32(url, "utf8");
28289
+ return await readFile34(url, "utf8");
27353
28290
  } catch {
27354
28291
  }
27355
28292
  }
27356
28293
  throw new Error("cell_runtime_bundle_unavailable");
27357
28294
  }
27358
28295
  async function defaultArchiveWorkspace(workspaceRoot) {
27359
- const { stdout } = await execFileAsync6("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
28296
+ const { stdout } = await execFileAsync8("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
27360
28297
  encoding: "buffer",
27361
28298
  maxBuffer: 512 * 1024 * 1024
27362
28299
  });
@@ -27402,15 +28339,15 @@ function boundedToolText(text3, limit) {
27402
28339
  ${redacted.slice(-half)}`;
27403
28340
  }
27404
28341
  function sleep5(ms) {
27405
- return new Promise((resolve21) => setTimeout(resolve21, ms));
28342
+ return new Promise((resolve22) => setTimeout(resolve22, ms));
27406
28343
  }
27407
- var execFileAsync6, CELL_WORKSPACE, CELL_STATE_DIR, CHECKPOINT_MESSAGE_FILE, CELL_RUNTIME_DIR, INSTALLED_RUNTIME_PACKAGE, CELL_RUNTIME_SDK_ENTRY, NOTES_FILE, MEMORY_FILE, MEMORY_EXTRACTION_TIMEOUT_MS, LIVENESS_CHECK_MS, ARTIFACTS_DIR, MAX_ARTIFACTS, MAX_ARTIFACT_BYTES, BRANCH_RE2, REPOSITORY_RE, GIT, COMMIT_SHA_RE, FULL_COMMIT_SHA_RE, LOCAL_CHANGES_PATCH, MAX_TOOL_OUTPUT_CHARS, MAX_TOOL_INPUT_CHARS, MAX_CHECKPOINTS, CELL_CONTROL_DIR, BRIDGE_MARKER, CONTROL_RUNNER, BRIDGE_RUNNER, HEALTH_TIMEOUT_MS, EVENT_POLL_MS, AsterHotcellRuntime, CELL_FILE_WRITE_CHUNK_CHARS, ARTIFACT_MIME_TYPES;
28344
+ var execFileAsync8, CELL_WORKSPACE, CELL_STATE_DIR, CHECKPOINT_MESSAGE_FILE, CELL_RUNTIME_DIR, INSTALLED_RUNTIME_PACKAGE, CELL_RUNTIME_SDK_ENTRY, NOTES_FILE, MEMORY_FILE, MEMORY_EXTRACTION_TIMEOUT_MS, LIVENESS_CHECK_MS, ARTIFACTS_DIR, MAX_ARTIFACTS, MAX_ARTIFACT_BYTES, BRANCH_RE2, REPOSITORY_RE, GIT, COMMIT_SHA_RE, FULL_COMMIT_SHA_RE, LOCAL_CHANGES_PATCH, MAX_TOOL_OUTPUT_CHARS, MAX_TOOL_INPUT_CHARS, MAX_CHECKPOINTS, CELL_CONTROL_DIR, BRIDGE_MARKER, CONTROL_RUNNER, BRIDGE_RUNNER, HEALTH_TIMEOUT_MS, EVENT_POLL_MS, AsterHotcellRuntime, CELL_FILE_WRITE_CHUNK_CHARS, ARTIFACT_MIME_TYPES;
27408
28345
  var init_aster_cell_runtime = __esm({
27409
28346
  "src/server/aster-cell-runtime.ts"() {
27410
28347
  "use strict";
27411
28348
  init_version();
27412
28349
  init_aster_tools();
27413
- execFileAsync6 = promisify8(execFile10);
28350
+ execFileAsync8 = promisify10(execFile12);
27414
28351
  CELL_WORKSPACE = "/workspace";
27415
28352
  CELL_STATE_DIR = "/workspace/.aster";
27416
28353
  CHECKPOINT_MESSAGE_FILE = `${CELL_STATE_DIR}/checkpoint-message`;
@@ -27519,7 +28456,7 @@ var init_aster_cell_runtime = __esm({
27519
28456
  await this.installRuntime(cell);
27520
28457
  await this.ensureBridge(cell);
27521
28458
  await this.control(cell, "POST", "/rpc", {
27522
- id: randomUUID12(),
28459
+ id: randomUUID13(),
27523
28460
  type: "new_session",
27524
28461
  sessionId: input.sessionId,
27525
28462
  config: { model: input.model, cwd: CELL_WORKSPACE }
@@ -27538,7 +28475,7 @@ var init_aster_cell_runtime = __esm({
27538
28475
  /** Switch the cell session's model between turns. The bridge persists it for session restore. */
27539
28476
  async setModel(cellId, model) {
27540
28477
  await this.ensureReady(cellId);
27541
- await this.controlById(cellId, "POST", "/rpc", { id: randomUUID12(), type: "set_model", modelId: model });
28478
+ await this.controlById(cellId, "POST", "/rpc", { id: randomUUID13(), type: "set_model", modelId: model });
27542
28479
  }
27543
28480
  /** Reinstall the runtime if the cell's image lost it and restart the bridge if it isn't running. */
27544
28481
  async ensureReady(cellId) {
@@ -27636,7 +28573,7 @@ var init_aster_cell_runtime = __esm({
27636
28573
  const active = this.activeRuns.get(runId);
27637
28574
  if (!active) return;
27638
28575
  active.stop();
27639
- void this.controlById(active.cellId, "POST", "/rpc", { id: randomUUID12(), type: "abort" }).catch(() => {
28576
+ void this.controlById(active.cellId, "POST", "/rpc", { id: randomUUID13(), type: "abort" }).catch(() => {
27640
28577
  });
27641
28578
  }
27642
28579
  async runTurn(turn, options = {}) {
@@ -27736,7 +28673,7 @@ ${turn.userText}` : turn.userText;
27736
28673
  args: event.args ?? {}
27737
28674
  });
27738
28675
  await this.controlById(cellId, "POST", "/rpc", {
27739
- id: randomUUID12(),
28676
+ id: randomUUID13(),
27740
28677
  type: "resolve_permission",
27741
28678
  requestId: event.requestId,
27742
28679
  decision
@@ -27917,9 +28854,9 @@ ${turn.userText}` : turn.userText;
27917
28854
  if (!isSafeArtifactPath(path)) throw new Error("invalid_artifact_path");
27918
28855
  const cell = await this.provider.getCell(cellId);
27919
28856
  const file = shellQuote2(`${ARTIFACTS_DIR}/${path}`);
27920
- const stat14 = await cell.exec(`[ -f ${file} ] && [ ! -L ${file} ] && stat -c %s -- ${file}`);
27921
- if (stat14.exitCode !== 0) throw new Error("artifact_not_found");
27922
- if (Number(stat14.stdout.trim()) > MAX_ARTIFACT_BYTES) throw new Error("artifact_too_large");
28857
+ const stat15 = await cell.exec(`[ -f ${file} ] && [ ! -L ${file} ] && stat -c %s -- ${file}`);
28858
+ if (stat15.exitCode !== 0) throw new Error("artifact_not_found");
28859
+ if (Number(stat15.stdout.trim()) > MAX_ARTIFACT_BYTES) throw new Error("artifact_too_large");
27923
28860
  const encoded = await cell.exec(`base64 -w0 -- ${file}`);
27924
28861
  if (encoded.exitCode !== 0) throw new Error("artifact_not_found");
27925
28862
  return { data: Buffer.from(encoded.stdout.trim(), "base64"), mimeType: artifactMimeType(path), name: path.split("/").pop() ?? path };
@@ -27998,7 +28935,7 @@ ${text3}` : text3, { mode: "0600" });
27998
28935
  }
27999
28936
  }
28000
28937
  async control(cell, method, path, body) {
28001
- const requestId = randomUUID12();
28938
+ const requestId = randomUUID13();
28002
28939
  await cell.mkdir(CELL_CONTROL_DIR, { parents: true });
28003
28940
  await writeCellFileChunked(cell, `${CELL_CONTROL_DIR}/${requestId}.json`, JSON.stringify({ method, path, body }));
28004
28941
  const result2 = await cell.exec(`${CONTROL_RUNNER} ${requestId}`);
@@ -28214,11 +29151,11 @@ var init_hotcell_provider = __esm({
28214
29151
  });
28215
29152
 
28216
29153
  // src/server/routes.ts
28217
- import { randomUUID as randomUUID13 } from "crypto";
28218
- import { hostname } from "os";
29154
+ import { randomUUID as randomUUID14 } from "crypto";
29155
+ import { hostname as hostname2 } from "os";
28219
29156
  import { URL as URL2 } from "url";
28220
29157
  import { unlink as unlink4 } from "fs/promises";
28221
- import { resolve as resolve17 } from "path";
29158
+ import { resolve as resolve18 } from "path";
28222
29159
  function createHotcellAsterRuntime() {
28223
29160
  try {
28224
29161
  const idlePauseMs = Number(process.env.AUTOPILOT_ASTER_IDLE_PAUSE_MS ?? 15 * 60 * 1e3);
@@ -28282,6 +29219,9 @@ function json3(res, status, data) {
28282
29219
  function badRequest(res, message2) {
28283
29220
  json3(res, 400, { error: message2 });
28284
29221
  }
29222
+ function promptBlocked(res, reason) {
29223
+ json3(res, 403, { error: "prompt_blocked", reason, message: `prompt blocked by a UserPromptSubmit hook: ${reason}` });
29224
+ }
28285
29225
  function notFound(res, message2) {
28286
29226
  json3(res, 404, { error: message2 });
28287
29227
  }
@@ -28301,9 +29241,9 @@ async function resolveFiles2(filePatterns, cwd) {
28301
29241
  const resolved = /* @__PURE__ */ new Set();
28302
29242
  for (const pattern of filePatterns) {
28303
29243
  try {
28304
- const stat14 = await import("fs/promises").then((m) => m.stat(resolve17(cwd, pattern)));
28305
- if (stat14.isFile()) {
28306
- resolved.add(resolve17(cwd, pattern));
29244
+ const stat15 = await import("fs/promises").then((m) => m.stat(resolve18(cwd, pattern)));
29245
+ if (stat15.isFile()) {
29246
+ resolved.add(resolve18(cwd, pattern));
28307
29247
  continue;
28308
29248
  }
28309
29249
  } catch {
@@ -28353,7 +29293,7 @@ function setupRoutes(config2, options = {}) {
28353
29293
  if (!run.sessionId) throw new Error("Cannot restore run without a session id: " + run.id);
28354
29294
  const existing = activeRuns.get(run.id) ?? activeSessions.get(run.sessionId);
28355
29295
  if (existing) return existing;
28356
- const sessionFile = await loadSession(resolve17(sessionsDir(), run.sessionId + ".json"));
29296
+ const sessionFile = await loadSession(resolve18(sessionsDir(), run.sessionId + ".json"));
28357
29297
  const allowedTools = new Set(run.allowedTools);
28358
29298
  allowedTools.add("wait_for");
28359
29299
  const tools = ALL_TOOLS.filter((tool) => allowedTools.has(tool.name));
@@ -28405,7 +29345,7 @@ function setupRoutes(config2, options = {}) {
28405
29345
  }
28406
29346
  });
28407
29347
  const notificationSettings = notificationSettingsFromConfig(config2);
28408
- const stopNotifications = notificationSettings ? startNotificationDispatcher(() => new RunStore(), notificationSettings, { host: hostname() }) : () => {
29348
+ const stopNotifications = notificationSettings ? startNotificationDispatcher(() => new RunStore(), notificationSettings, { host: hostname2() }) : () => {
28409
29349
  };
28410
29350
  const imapSettings = notificationSettings?.decisionsReplyTo ? imapSettingsFromEnv() : null;
28411
29351
  const stopDecisions = imapSettings && notificationSettings ? startDecisionPoller(imapSettings, async (mail) => {
@@ -28420,7 +29360,7 @@ function setupRoutes(config2, options = {}) {
28420
29360
  },
28421
29361
  modelExists: (id) => listModels().some((m) => m.id === id),
28422
29362
  confirm: async (to, subject, text3) => {
28423
- await notificationSettings.transport.send({ to: [to], from: notificationSettings.from, subject, text: text3, idempotencyKey: `confirm_${randomUUID13()}` });
29363
+ await notificationSettings.transport.send({ to: [to], from: notificationSettings.from, subject, text: text3, idempotencyKey: `confirm_${randomUUID14()}` });
28424
29364
  }
28425
29365
  }, { allowedSenders: notificationSettings.to, requireAuthentication: process.env.AUTOPILOT_DECISIONS_REQUIRE_AUTH !== "0" });
28426
29366
  } finally {
@@ -28556,7 +29496,7 @@ function setupRoutes(config2, options = {}) {
28556
29496
  const task = typeof body.task === "string" ? body.task.trim() : "";
28557
29497
  let cwd;
28558
29498
  try {
28559
- cwd = typeof body.cwd === "string" ? resolve17(body.cwd) : process.cwd();
29499
+ cwd = typeof body.cwd === "string" ? resolve18(body.cwd) : process.cwd();
28560
29500
  } catch {
28561
29501
  badRequest(res, "cwd must be a valid path");
28562
29502
  return;
@@ -28620,7 +29560,7 @@ function setupRoutes(config2, options = {}) {
28620
29560
  const executor = new ToolExecutor(tools);
28621
29561
  const { makeSessionId: makeSessionId2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
28622
29562
  const sessionId = makeSessionId2(task);
28623
- const runId = randomUUID13();
29563
+ const runId = randomUUID14();
28624
29564
  const worktreeManager = new RunWorktreeManager();
28625
29565
  let worktree;
28626
29566
  if (useWorktree) {
@@ -28669,7 +29609,7 @@ function setupRoutes(config2, options = {}) {
28669
29609
  store.close();
28670
29610
  }
28671
29611
  } catch (error) {
28672
- await unlink4(resolve17(sessionsDir(), `${sessionId}.json`)).catch(() => {
29612
+ await unlink4(resolve18(sessionsDir(), `${sessionId}.json`)).catch(() => {
28673
29613
  });
28674
29614
  if (worktree) {
28675
29615
  await worktreeManager.discard(worktree).catch((cleanupError) => {
@@ -28812,7 +29752,7 @@ function setupRoutes(config2, options = {}) {
28812
29752
  return;
28813
29753
  }
28814
29754
  try {
28815
- const file = await loadSession(resolve17(sessionsDir(), `${sessionId}.json`));
29755
+ const file = await loadSession(resolve18(sessionsDir(), `${sessionId}.json`));
28816
29756
  json3(res, 200, {
28817
29757
  id: file.id,
28818
29758
  cwd: file.cwd,
@@ -28832,7 +29772,7 @@ function setupRoutes(config2, options = {}) {
28832
29772
  activeSessions.delete(sessionId);
28833
29773
  try {
28834
29774
  const { unlink: unlink7 } = await import("fs/promises");
28835
- await unlink7(resolve17(sessionsDir(), `${sessionId}.json`));
29775
+ await unlink7(resolve18(sessionsDir(), `${sessionId}.json`));
28836
29776
  } catch {
28837
29777
  }
28838
29778
  json3(res, 200, { deleted: sessionId });
@@ -28862,6 +29802,12 @@ function setupRoutes(config2, options = {}) {
28862
29802
  title
28863
29803
  };
28864
29804
  const hooks = new HooksManager(cwd);
29805
+ const gate = await submitUserPrompt({ hooks, sessionId, cwd, prompt, resumed: false, model, auth: llmAuthFromConfig(config2) });
29806
+ if (gate.blocked) {
29807
+ await endSession(sessionId, "exit");
29808
+ promptBlocked(res, gate.reason);
29809
+ return;
29810
+ }
28865
29811
  const executor = new ToolExecutor(ALL_TOOLS, { hooks });
28866
29812
  const messages = [
28867
29813
  {
@@ -28914,6 +29860,19 @@ function setupRoutes(config2, options = {}) {
28914
29860
  badRequest(res, "prompt is required");
28915
29861
  return;
28916
29862
  }
29863
+ const gate = await submitUserPrompt({
29864
+ ...active.hooks ? { hooks: active.hooks } : {},
29865
+ sessionId,
29866
+ cwd: active.sessionFile.cwd,
29867
+ prompt,
29868
+ resumed: active.messages.some((m) => m.role === "assistant"),
29869
+ model: active.sessionFile.model,
29870
+ auth: llmAuthFromConfig(config2)
29871
+ });
29872
+ if (gate.blocked) {
29873
+ promptBlocked(res, gate.reason);
29874
+ return;
29875
+ }
28917
29876
  const resolvedFiles = await resolveFiles2(files, active.sessionFile.cwd);
28918
29877
  const { content: userContent2 } = await buildAttachmentMessage(prompt, resolvedFiles, active.sessionFile.cwd, imageOptions(config2));
28919
29878
  active.messages.push({ role: "user", content: userContent2 });
@@ -29237,6 +30196,7 @@ var init_routes = __esm({
29237
30196
  "use strict";
29238
30197
  init_session_core();
29239
30198
  init_session_lifecycle();
30199
+ init_prompt_submit();
29240
30200
  init_manager2();
29241
30201
  init_exit();
29242
30202
  init_registry();
@@ -29328,7 +30288,7 @@ async function startServer(opts2) {
29328
30288
  server.keepAliveTimeout = 5e3;
29329
30289
  const unregisterShutdown = onShutdown("server", async () => {
29330
30290
  const ended = cleanup();
29331
- const closed = new Promise((resolve21) => server.close(() => resolve21()));
30291
+ const closed = new Promise((resolve22) => server.close(() => resolve22()));
29332
30292
  server.closeAllConnections();
29333
30293
  await Promise.all([ended, closed]);
29334
30294
  });
@@ -29337,13 +30297,13 @@ async function startServer(opts2) {
29337
30297
  void cleanup();
29338
30298
  });
29339
30299
  installExitHandlers({ signals: ["SIGINT", "SIGTERM"], exitCode: () => 0, crashes: true });
29340
- return new Promise((resolve21, reject) => {
30300
+ return new Promise((resolve22, reject) => {
29341
30301
  server.listen(opts2.port, opts2.hostname, () => {
29342
30302
  logger.info("server: listening", { hostname: opts2.hostname, port: opts2.port });
29343
30303
  console.log(`kimiflare serve: http://${opts2.hostname}:${opts2.port}`);
29344
30304
  console.log(` API docs: http://${opts2.hostname}:${opts2.port}/doc`);
29345
30305
  console.log(` SSE stream: http://${opts2.hostname}:${opts2.port}/event`);
29346
- resolve21(server);
30306
+ resolve22(server);
29347
30307
  });
29348
30308
  server.on("error", reject);
29349
30309
  });
@@ -29677,8 +30637,8 @@ async function runEmitMode(opts2) {
29677
30637
  async function nextFollowUp() {
29678
30638
  if (followUpQueue.length > 0) return followUpQueue.shift();
29679
30639
  if (stdinClosed) return null;
29680
- return new Promise((resolve21) => {
29681
- followUpResolver = resolve21;
30640
+ return new Promise((resolve22) => {
30641
+ followUpResolver = resolve22;
29682
30642
  });
29683
30643
  }
29684
30644
  const cwd = process.cwd();
@@ -29708,6 +30668,25 @@ async function runEmitMode(opts2) {
29708
30668
  process.on("SIGINT", sigintHandler);
29709
30669
  async function runTurn(text3) {
29710
30670
  emit("UserMessageCreated", { text: text3 });
30671
+ const gate = await submitUserPrompt({
30672
+ hooks,
30673
+ sessionId,
30674
+ cwd,
30675
+ prompt: text3,
30676
+ resumed: messages.some((m) => m.role === "assistant"),
30677
+ model: opts2.model,
30678
+ auth: llmAuthFromConfig(opts2)
30679
+ });
30680
+ if (gate.blocked) {
30681
+ emit("RuntimeError", {
30682
+ message: `prompt blocked by a UserPromptSubmit hook: ${gate.reason}`,
30683
+ source: "hooks",
30684
+ kind: "generic",
30685
+ severity: "error"
30686
+ });
30687
+ if (!opts2.multiTurn) exitCode = PROMPT_BLOCKED_EXIT_CODE;
30688
+ return;
30689
+ }
29711
30690
  messages.push({ role: "user", content: text3 });
29712
30691
  try {
29713
30692
  const policy = await resolveTurnPolicy({ cfg: opts2, prompt: text3, tools: ALL_TOOLS, signal: controller.signal });
@@ -29830,8 +30809,8 @@ async function runEmitMode(opts2) {
29830
30809
  return fromHook;
29831
30810
  }
29832
30811
  if (opts2.multiTurn) {
29833
- const choice = await new Promise((resolve21) => {
29834
- pendingPermissions.set(reqId, resolve21);
30812
+ const choice = await new Promise((resolve22) => {
30813
+ pendingPermissions.set(reqId, resolve22);
29835
30814
  });
29836
30815
  if (choice === "allow" || choice === "allow_session") {
29837
30816
  emit("PermissionGranted", { request_id: reqId });
@@ -29931,6 +30910,7 @@ var init_emit_mode = __esm({
29931
30910
  init_executor();
29932
30911
  init_errors();
29933
30912
  init_session_lifecycle();
30913
+ init_prompt_submit();
29934
30914
  init_sessions();
29935
30915
  }
29936
30916
  });
@@ -29958,7 +30938,7 @@ async function runAttachMode(opts2) {
29958
30938
  if (!response.ok) {
29959
30939
  const text3 = await response.text().catch(() => "unknown error");
29960
30940
  console.error(`kimiflare attach: ${response.status} ${text3}`);
29961
- process.exit(1);
30941
+ process.exit(response.status === 403 && text3.includes('"prompt_blocked"') ? PROMPT_BLOCKED_EXIT_CODE : 1);
29962
30942
  }
29963
30943
  const result2 = await response.json();
29964
30944
  const sessionId = result2.sessionId;
@@ -29971,7 +30951,7 @@ async function runAttachMode(opts2) {
29971
30951
  async function streamSse(url, sessionId, format) {
29972
30952
  const eventSource = new EventSource(`${url}/event`);
29973
30953
  let done = false;
29974
- return new Promise((resolve21, reject) => {
30954
+ return new Promise((resolve22, reject) => {
29975
30955
  eventSource.onmessage = (event) => {
29976
30956
  try {
29977
30957
  const data = JSON.parse(event.data);
@@ -30018,7 +30998,7 @@ async function streamSse(url, sessionId, format) {
30018
30998
  done = true;
30019
30999
  eventSource.close();
30020
31000
  if (format === "text") process.stdout.write("\n");
30021
- resolve21(void 0);
31001
+ resolve22(void 0);
30022
31002
  break;
30023
31003
  case "error":
30024
31004
  done = true;
@@ -30031,7 +31011,7 @@ async function streamSse(url, sessionId, format) {
30031
31011
  process.stdout.write(JSON.stringify({ event: "error", message: data.message }) + "\n");
30032
31012
  }
30033
31013
  process.exitCode = 1;
30034
- resolve21(void 0);
31014
+ resolve22(void 0);
30035
31015
  break;
30036
31016
  }
30037
31017
  } catch {
@@ -30057,7 +31037,7 @@ async function streamSseJson(url, sessionId) {
30057
31037
  const toolResults = [];
30058
31038
  let usage;
30059
31039
  const eventSource = new EventSource(`${url}/event`);
30060
- return new Promise((resolve21, reject) => {
31040
+ return new Promise((resolve22, reject) => {
30061
31041
  eventSource.onmessage = (event) => {
30062
31042
  try {
30063
31043
  const data = JSON.parse(event.data);
@@ -30094,7 +31074,7 @@ async function streamSseJson(url, sessionId) {
30094
31074
  2
30095
31075
  ) + "\n"
30096
31076
  );
30097
- resolve21(void 0);
31077
+ resolve22(void 0);
30098
31078
  break;
30099
31079
  case "error":
30100
31080
  eventSource.close();
@@ -30102,7 +31082,7 @@ async function streamSseJson(url, sessionId) {
30102
31082
  JSON.stringify({ error: data.message, sessionId }, null, 2) + "\n"
30103
31083
  );
30104
31084
  process.exitCode = 1;
30105
- resolve21(void 0);
31085
+ resolve22(void 0);
30106
31086
  break;
30107
31087
  }
30108
31088
  } catch {
@@ -30122,6 +31102,7 @@ var EventSource;
30122
31102
  var init_attach_mode = __esm({
30123
31103
  "src/attach-mode.ts"() {
30124
31104
  "use strict";
31105
+ init_prompt_submit();
30125
31106
  EventSource = class {
30126
31107
  url;
30127
31108
  controller;
@@ -30279,7 +31260,7 @@ __export(openrouter_oauth_exports, {
30279
31260
  });
30280
31261
  import { createHash as createHash8, randomBytes as randomBytes4 } from "crypto";
30281
31262
  import { createServer as createServer4 } from "http";
30282
- import { hostname as hostname2 } from "os";
31263
+ import { hostname as hostname3 } from "os";
30283
31264
  function createPkce() {
30284
31265
  const verifier = randomBytes4(32).toString("base64url");
30285
31266
  return { verifier, challenge: pkceChallenge(verifier) };
@@ -30291,7 +31272,7 @@ function openRouterAuthPageUrl() {
30291
31272
  return (process.env.OPENROUTER_AUTH_URL?.trim() || "https://openrouter.ai/auth").replace(/\/+$/, "");
30292
31273
  }
30293
31274
  function defaultKeyLabel() {
30294
- return `autopilot (${hostname2()})`.slice(0, 60);
31275
+ return `autopilot (${hostname3()})`.slice(0, 60);
30295
31276
  }
30296
31277
  function buildAuthUrl(opts2) {
30297
31278
  const params = new URLSearchParams();
@@ -30368,9 +31349,9 @@ async function startLoopbackListener(opts2 = {}) {
30368
31349
  rejectCode(e);
30369
31350
  finish();
30370
31351
  }
30371
- await new Promise((resolve21, reject) => {
31352
+ await new Promise((resolve22, reject) => {
30372
31353
  server.once("error", reject);
30373
- server.listen(0, "127.0.0.1", () => resolve21());
31354
+ server.listen(0, "127.0.0.1", () => resolve22());
30374
31355
  });
30375
31356
  const address = server.address();
30376
31357
  const port = typeof address === "object" && address ? address.port : 0;
@@ -30517,11 +31498,11 @@ async function chatgptSignIn(onUrl, signal) {
30517
31498
  }
30518
31499
  function untilAborted(p, signal) {
30519
31500
  if (!signal) return p;
30520
- return new Promise((resolve21, reject) => {
31501
+ return new Promise((resolve22, reject) => {
30521
31502
  const abort = () => reject(new DOMException("sign-in cancelled", "AbortError"));
30522
31503
  if (signal.aborted) return abort();
30523
31504
  signal.addEventListener("abort", abort, { once: true });
30524
- p.then(resolve21, reject).finally(() => signal.removeEventListener("abort", abort));
31505
+ p.then(resolve22, reject).finally(() => signal.removeEventListener("abort", abort));
30525
31506
  });
30526
31507
  }
30527
31508
  async function openRouterSignIn(io) {
@@ -30767,20 +31748,20 @@ var init_help_catalog = __esm({
30767
31748
 
30768
31749
  // src/ui/bang-command.ts
30769
31750
  import { spawn as spawn8 } from "child_process";
30770
- import { mkdtemp as mkdtemp3, readFile as readFile34, rm as rm7 } from "fs/promises";
30771
- import { tmpdir as tmpdir5, platform as platform5 } from "os";
30772
- import { join as join47 } from "path";
31751
+ import { mkdtemp as mkdtemp4, readFile as readFile36, rm as rm8 } from "fs/promises";
31752
+ import { tmpdir as tmpdir6, platform as platform5 } from "os";
31753
+ import { join as join49 } from "path";
30773
31754
  function registerTerminalHandoff(h) {
30774
31755
  terminalHandoff = h;
30775
31756
  }
30776
31757
  function runCaptured(inv, cwd) {
30777
- return new Promise((resolve21) => {
31758
+ return new Promise((resolve22) => {
30778
31759
  const child = spawn8(inv.file, inv.args, { cwd, stdio: ["ignore", "pipe", "pipe"], env: inv.env ?? process.env });
30779
31760
  let output = "";
30780
31761
  child.stdout?.on("data", (c) => output += c.toString("utf8"));
30781
31762
  child.stderr?.on("data", (c) => output += c.toString("utf8"));
30782
- child.once("error", (e) => resolve21({ code: 127, signal: null, output: `failed to start: ${e.message}` }));
30783
- child.once("exit", (code, signal) => resolve21({ code, signal, output }));
31763
+ child.once("error", (e) => resolve22({ code: 127, signal: null, output: `failed to start: ${e.message}` }));
31764
+ child.once("exit", (code, signal) => resolve22({ code, signal, output }));
30784
31765
  });
30785
31766
  }
30786
31767
  function isTerminalHandedOff() {
@@ -30848,10 +31829,10 @@ function swallow() {
30848
31829
  });
30849
31830
  }
30850
31831
  function spawnInherit(inv, cwd) {
30851
- return new Promise((resolve21, reject) => {
31832
+ return new Promise((resolve22, reject) => {
30852
31833
  const child = spawn8(inv.file, inv.args, { cwd, stdio: "inherit", env: inv.env ?? process.env });
30853
31834
  child.once("error", reject);
30854
- child.once("exit", (code, signal) => resolve21({ code, signal }));
31835
+ child.once("exit", (code, signal) => resolve22({ code, signal }));
30855
31836
  });
30856
31837
  }
30857
31838
  async function runBangCommand(command, cwd) {
@@ -30869,8 +31850,8 @@ async function runBangCommand(command, cwd) {
30869
31850
  const origErr = stderr.write;
30870
31851
  const readable = stdin.listeners("readable");
30871
31852
  const wasRaw = stdin.isTTY ? stdin.isRaw : false;
30872
- const logDir2 = isWindows ? null : await mkdtemp3(join47(tmpdir5(), "autopilot-bang-"));
30873
- const logPath = logDir2 ? join47(logDir2, "out.log") : null;
31853
+ const logDir2 = isWindows ? null : await mkdtemp4(join49(tmpdir6(), "autopilot-bang-"));
31854
+ const logPath = logDir2 ? join49(logDir2, "out.log") : null;
30874
31855
  const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
30875
31856
  origOut.call(stdout, `
30876
31857
  $ ${command}
@@ -30911,9 +31892,9 @@ $ ${command}
30911
31892
  }
30912
31893
  let output = spawnError ? `failed to start: ${spawnError}` : null;
30913
31894
  if (captured && logPath) {
30914
- output = cleanTerminalOutput(await readFile34(logPath, "utf8").catch(() => ""));
31895
+ output = cleanTerminalOutput(await readFile36(logPath, "utf8").catch(() => ""));
30915
31896
  }
30916
- if (logDir2) await rm7(logDir2, { recursive: true, force: true }).catch(() => {
31897
+ if (logDir2) await rm8(logDir2, { recursive: true, force: true }).catch(() => {
30917
31898
  });
30918
31899
  return { command, exitCode: result2.code, signal: result2.signal, output };
30919
31900
  }
@@ -31150,8 +32131,8 @@ function activityFromSubagent(worker) {
31150
32131
  };
31151
32132
  }
31152
32133
  function activityTaskPreview(task) {
31153
- const oneLine2 = task.replace(/\s+/g, " ").trim();
31154
- return oneLine2.length > 72 ? `${oneLine2.slice(0, 71)}\u2026` : oneLine2;
32134
+ const oneLine3 = task.replace(/\s+/g, " ").trim();
32135
+ return oneLine3.length > 72 ? `${oneLine3.slice(0, 71)}\u2026` : oneLine3;
31155
32136
  }
31156
32137
  function diffActivityItems(previous, current, snapshotSent) {
31157
32138
  if (!snapshotSent) return [{ type: "ActivitySnapshot", payload: { items: [...current] } }];
@@ -31188,14 +32169,14 @@ __export(camouflage_launch_exports, {
31188
32169
  runWithCrashRestarts: () => runWithCrashRestarts
31189
32170
  });
31190
32171
  import { accessSync, constants, existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
31191
- import { homedir as homedir30 } from "os";
31192
- import { delimiter, dirname as dirname24, isAbsolute as isAbsolute8, join as join48, resolve as resolve18, sep as sep7 } from "path";
32172
+ import { homedir as homedir31 } from "os";
32173
+ import { delimiter, dirname as dirname25, isAbsolute as isAbsolute8, join as join50, resolve as resolve19, sep as sep7 } from "path";
31193
32174
  import { fileURLToPath as fileURLToPath5 } from "url";
31194
32175
  function defaultPackageDir() {
31195
32176
  try {
31196
- let dir = dirname24(fileURLToPath5(import.meta.resolve("camouflage-tui")));
31197
- while (!existsSync14(join48(dir, "package.json")) && dirname24(dir) !== dir) dir = dirname24(dir);
31198
- return existsSync14(join48(dir, "package.json")) ? dir : null;
32177
+ let dir = dirname25(fileURLToPath5(import.meta.resolve("camouflage-tui")));
32178
+ while (!existsSync14(join50(dir, "package.json")) && dirname25(dir) !== dir) dir = dirname25(dir);
32179
+ return existsSync14(join50(dir, "package.json")) ? dir : null;
31199
32180
  } catch {
31200
32181
  return null;
31201
32182
  }
@@ -31209,11 +32190,11 @@ function defaultIsExecutable(path) {
31209
32190
  }
31210
32191
  }
31211
32192
  function findOnPath(bin, env, platform7, isExecutable) {
31212
- if (isAbsolute8(bin) || bin.includes("/") || bin.includes(sep7)) return isExecutable(resolve18(bin));
32193
+ if (isAbsolute8(bin) || bin.includes("/") || bin.includes(sep7)) return isExecutable(resolve19(bin));
31213
32194
  const exts = platform7 === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
31214
32195
  for (const dir of (env.PATH ?? "").split(delimiter)) {
31215
32196
  if (!dir) continue;
31216
- for (const ext of exts) if (isExecutable(join48(dir, bin + ext))) return true;
32197
+ for (const ext of exts) if (isExecutable(join50(dir, bin + ext))) return true;
31217
32198
  }
31218
32199
  return false;
31219
32200
  }
@@ -31222,21 +32203,21 @@ function diagnoseRenderer(deps = {}) {
31222
32203
  const platform7 = deps.platform ?? process.platform;
31223
32204
  const arch = deps.arch ?? process.arch;
31224
32205
  const isExecutable = deps.isExecutable ?? defaultIsExecutable;
31225
- const readFile40 = deps.readFile ?? ((p) => readFileSync9(p, "utf8"));
32206
+ const readFile42 = deps.readFile ?? ((p) => readFileSync9(p, "utf8"));
31226
32207
  const packageDir = (deps.packageDir ?? defaultPackageDir)();
31227
32208
  let packageVersion = null;
31228
32209
  let packageBin = null;
31229
32210
  if (packageDir) {
31230
32211
  try {
31231
- packageVersion = JSON.parse(readFile40(join48(packageDir, "package.json"))).version ?? null;
32212
+ packageVersion = JSON.parse(readFile42(join50(packageDir, "package.json"))).version ?? null;
31232
32213
  } catch {
31233
32214
  }
31234
- packageBin = join48(packageDir, "bin", platform7 === "win32" ? "camouflage-tui.exe" : "camouflage-tui");
32215
+ packageBin = join50(packageDir, "bin", platform7 === "win32" ? "camouflage-tui.exe" : "camouflage-tui");
31235
32216
  }
31236
- const installErrorPath = join48(deps.home ?? homedir30(), ".camouflage", "install-error.txt");
32217
+ const installErrorPath = join50(deps.home ?? homedir31(), ".camouflage", "install-error.txt");
31237
32218
  let installError = null;
31238
32219
  try {
31239
- installError = readFile40(installErrorPath).trim() || null;
32220
+ installError = readFile42(installErrorPath).trim() || null;
31240
32221
  } catch {
31241
32222
  }
31242
32223
  const envBin = env.CAMOUFLAGE_BIN || null;
@@ -31283,7 +32264,7 @@ function formatUnavailable(d, reason) {
31283
32264
  if (d.envBin) lines.push(` - point CAMOUFLAGE_BIN at an existing camouflage-tui binary, or unset it to use the bundled one`);
31284
32265
  if (d.packageDir) {
31285
32266
  lines.push(
31286
- ` - download the renderer again: node "${join48(d.packageDir, "scripts", "install.js")}"`,
32267
+ ` - download the renderer again: node "${join50(d.packageDir, "scripts", "install.js")}"`,
31287
32268
  ` (or run \`npm rebuild camouflage-tui\` where autopilot-ai is installed; \`npm rebuild -g camouflage-tui\` for a global install)`
31288
32269
  );
31289
32270
  }
@@ -31332,7 +32313,7 @@ async function runWithCrashRestarts(run, opts2 = {}) {
31332
32313
  function crashGiveUpMessage(outcome, windowMs = 6e4) {
31333
32314
  const lines = [
31334
32315
  `autopilot: the Camouflage renderer crashed ${outcome.crashes} times within ${Math.round(windowMs / 1e3)} seconds, so autopilot stopped restarting it.`,
31335
- `Crash reports: ${join48(homedir30(), ".camouflage")} (crash-*.txt). Please attach one to an issue: https://github.com/sinameraji/autopilot/issues`
32316
+ `Crash reports: ${join50(homedir31(), ".camouflage")} (crash-*.txt). Please attach one to an issue: https://github.com/sinameraji/autopilot/issues`
31336
32317
  ];
31337
32318
  if (outcome.sessionId) {
31338
32319
  lines.push(
@@ -31414,11 +32395,11 @@ __export(interactive_resume_exports, {
31414
32395
  resumeHint: () => resumeHint
31415
32396
  });
31416
32397
  import { existsSync as existsSync15 } from "fs";
31417
- import { join as join49 } from "path";
32398
+ import { join as join51 } from "path";
31418
32399
  async function resolveInteractiveResume(req, cwd = process.cwd()) {
31419
32400
  if (req.sessionId !== void 0) {
31420
32401
  const id = req.sessionId.trim().replace(/\.json$/, "");
31421
- if (!SESSION_ID.test(id) || !existsSync15(join49(sessionsDir(), `${id}.json`))) {
32402
+ if (!SESSION_ID.test(id) || !existsSync15(join51(sessionsDir(), `${id}.json`))) {
31422
32403
  return { kind: "error", message: `no saved conversation with id "${req.sessionId}". Run \`autopilot resume\` to pick one.` };
31423
32404
  }
31424
32405
  return { kind: "session", sessionId: id };
@@ -31431,7 +32412,7 @@ async function resolveInteractiveResume(req, cwd = process.cwd()) {
31431
32412
  return { kind: "picker" };
31432
32413
  }
31433
32414
  function resumeHint(sessionId) {
31434
- if (!sessionId || !SESSION_ID.test(sessionId) || !existsSync15(join49(sessionsDir(), `${sessionId}.json`))) return null;
32415
+ if (!sessionId || !SESSION_ID.test(sessionId) || !existsSync15(join51(sessionsDir(), `${sessionId}.json`))) return null;
31435
32416
  return `Resume this conversation with: autopilot resume ${sessionId}
31436
32417
  `;
31437
32418
  }
@@ -31471,8 +32452,8 @@ __export(camouflage_view_exports, {
31471
32452
  import { execFileSync as execFileSync4, spawn as spawn9 } from "child_process";
31472
32453
  import { readdir as readdir7 } from "fs/promises";
31473
32454
  import { existsSync as existsSync16, readFileSync as readFileSync10 } from "fs";
31474
- import { homedir as homedir31 } from "os";
31475
- import { dirname as dirname25, join as join50, resolve as resolve19 } from "path";
32455
+ import { homedir as homedir32 } from "os";
32456
+ import { dirname as dirname26, join as join52, resolve as resolve20 } from "path";
31476
32457
  import { fileURLToPath as fileURLToPath6 } from "url";
31477
32458
  function noticeSeverity(e) {
31478
32459
  return e.kind === "info" && e.tone === "warn" ? "warn" : "info";
@@ -31493,10 +32474,10 @@ function rendererInfo(env = process.env) {
31493
32474
  let packageVersion = null;
31494
32475
  let packageBin = "camouflage-tui";
31495
32476
  try {
31496
- let dir = dirname25(fileURLToPath6(import.meta.resolve("camouflage-tui")));
31497
- while (!existsSync16(join50(dir, "package.json")) && dirname25(dir) !== dir) dir = dirname25(dir);
31498
- packageVersion = JSON.parse(readFileSync10(join50(dir, "package.json"), "utf8")).version ?? null;
31499
- const local = join50(dir, "bin", process.platform === "win32" ? "camouflage-tui.exe" : "camouflage-tui");
32477
+ let dir = dirname26(fileURLToPath6(import.meta.resolve("camouflage-tui")));
32478
+ while (!existsSync16(join52(dir, "package.json")) && dirname26(dir) !== dir) dir = dirname26(dir);
32479
+ packageVersion = JSON.parse(readFileSync10(join52(dir, "package.json"), "utf8")).version ?? null;
32480
+ const local = join52(dir, "bin", process.platform === "win32" ? "camouflage-tui.exe" : "camouflage-tui");
31500
32481
  packageBin = local;
31501
32482
  } catch {
31502
32483
  }
@@ -31624,7 +32605,7 @@ async function runCamouflageView(opts2) {
31624
32605
  if (process.stdin.isTTY) process.stdin.setRawMode?.(false);
31625
32606
  process.stderr.write(
31626
32607
  `
31627
- The Camouflage renderer stopped unexpectedly (${signal ?? `exit code ${code}`}). Its crash report is in ${join50(homedir31(), ".camouflage")} (crash-*.txt).
32608
+ The Camouflage renderer stopped unexpectedly (${signal ?? `exit code ${code}`}). Its crash report is in ${join52(homedir32(), ".camouflage")} (crash-*.txt).
31628
32609
  ` + (view.busy ? "Finishing the current turn first\u2026\n" : "")
31629
32610
  );
31630
32611
  await view.settle();
@@ -31635,7 +32616,7 @@ The Camouflage renderer stopped unexpectedly (${signal ?? `exit code ${code}`}).
31635
32616
  let endBeforeApp = () => {
31636
32617
  };
31637
32618
  let quitBeforeApp = false;
31638
- const beforeApp = new Promise((resolve21) => endBeforeApp = resolve21);
32619
+ const beforeApp = new Promise((resolve22) => endBeforeApp = resolve22);
31639
32620
  let setup = null;
31640
32621
  try {
31641
32622
  let cfg = opts2.cfg;
@@ -31680,14 +32661,14 @@ The Camouflage renderer stopped unexpectedly (${signal ?? `exit code ${code}`}).
31680
32661
  return "done";
31681
32662
  }
31682
32663
  function runShell2(command) {
31683
- return new Promise((resolve21) => {
32664
+ return new Promise((resolve22) => {
31684
32665
  const { shell, args } = getShellCommand();
31685
32666
  const child = spawn9(shell, [...args, command], { env: process.env });
31686
32667
  let out = "";
31687
32668
  child.stdout?.on("data", (c) => out += c.toString("utf8"));
31688
32669
  child.stderr?.on("data", (c) => out += c.toString("utf8"));
31689
- child.on("close", (code) => resolve21({ ok: code === 0, output: out || (code === 0 ? "" : `Exit code: ${code}`) }));
31690
- child.on("error", (err) => resolve21({ ok: false, output: err.message }));
32670
+ child.on("close", (code) => resolve22({ ok: code === 0, output: out || (code === 0 ? "" : `Exit code: ${code}`) }));
32671
+ child.on("error", (err) => resolve22({ ok: false, output: err.message }));
31691
32672
  });
31692
32673
  }
31693
32674
  function syncSlashCommands(custom, lastKey, send) {
@@ -31770,8 +32751,8 @@ function truncateText(s, max) {
31770
32751
  async function listMentionDir(cwd, query) {
31771
32752
  const exact = query === "~" || query === "." || query === ".." || query.endsWith("/..");
31772
32753
  const dir = exact ? `${query}/` : query.slice(0, query.lastIndexOf("/") + 1);
31773
- const expanded = dir.startsWith("~") ? join50(homedir31(), dir.slice(1)) : dir;
31774
- const target = resolve19(cwd, expanded || ".");
32754
+ const expanded = dir.startsWith("~") ? join52(homedir32(), dir.slice(1)) : dir;
32755
+ const target = resolve20(cwd, expanded || ".");
31775
32756
  try {
31776
32757
  const items = await readdir7(target, { withFileTypes: true });
31777
32758
  const entries = items.filter((d) => !d.name.startsWith(".")).map((d) => ({ name: d.name, isDir: d.isDirectory() })).sort((a, b) => a.isDir !== b.isDir ? a.isDir ? -1 : 1 : a.name.localeCompare(b.name)).slice(0, 300).map((d) => `${dir}${d.name}${d.isDir ? "/" : ""}`);
@@ -31910,7 +32891,7 @@ function shortModel(id) {
31910
32891
  return id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
31911
32892
  }
31912
32893
  function shortPath(p) {
31913
- const home = homedir31();
32894
+ const home = homedir32();
31914
32895
  return p === home ? "~" : p.startsWith(home + "/") ? `~${p.slice(home.length)}` : p;
31915
32896
  }
31916
32897
  function gitBranch(cwd) {
@@ -32032,15 +33013,15 @@ var init_camouflage_view = __esm({
32032
33013
  /** Resolves once the App is idle (no turn running). */
32033
33014
  settle(maxWaitMs = 12e4) {
32034
33015
  if (!this.busy) return Promise.resolve();
32035
- return new Promise((resolve21) => {
32036
- this.idleWaiters.push(resolve21);
32037
- setTimeout(resolve21, maxWaitMs).unref();
33016
+ return new Promise((resolve22) => {
33017
+ this.idleWaiters.push(resolve22);
33018
+ setTimeout(resolve22, maxWaitMs).unref();
32038
33019
  });
32039
33020
  }
32040
33021
  sync(s) {
32041
33022
  this.busy = s.turnRunning;
32042
33023
  this.sessionId = s.sessionId;
32043
- if (!s.turnRunning) for (const resolve21 of this.idleWaiters.splice(0)) resolve21();
33024
+ if (!s.turnRunning) for (const resolve22 of this.idleWaiters.splice(0)) resolve22();
32044
33025
  if (this.disposed) {
32045
33026
  if (s.permission) this.actions?.decidePermission("deny");
32046
33027
  return;
@@ -32337,7 +33318,7 @@ var init_camouflage_view = __esm({
32337
33318
  */
32338
33319
  async denyFeedback(given, escaped) {
32339
33320
  if (given?.trim()) return given.trim();
32340
- await new Promise((resolve21) => setTimeout(resolve21, 50));
33321
+ await new Promise((resolve22) => setTimeout(resolve22, 50));
32341
33322
  if (escaped() || this.disposed) return "";
32342
33323
  const r = await this.sdk.form(this.cam, {
32343
33324
  id: `deny-${Date.now()}`,
@@ -33447,9 +34428,9 @@ function usePermissionController(getMode, onPlanModeBlocked) {
33447
34428
  const askPermission = useCallback(
33448
34429
  (req, askOpts) => resolvePermission(req, getModeRef.current(), askOpts, {
33449
34430
  onPlanModeBlocked: (toolName) => onPlanModeBlockedRef.current(toolName),
33450
- prompt: () => new Promise((resolve21) => {
33451
- resolveRef.current = resolve21;
33452
- setPending({ tool: req.tool, args: req.args, resolve: resolve21 });
34431
+ prompt: () => new Promise((resolve22) => {
34432
+ resolveRef.current = resolve22;
34433
+ setPending({ tool: req.tool, args: req.args, resolve: resolve22 });
33453
34434
  })
33454
34435
  }),
33455
34436
  []
@@ -33830,14 +34811,14 @@ var init_frontmatter = __esm({
33830
34811
 
33831
34812
  // src/commands/loader.ts
33832
34813
  import { open as open2, realpath as realpath9 } from "fs/promises";
33833
- import { homedir as homedir32 } from "os";
33834
- import { join as join51, relative as relative11, sep as sep8 } from "path";
34814
+ import { homedir as homedir33 } from "os";
34815
+ import { join as join53, relative as relative11, sep as sep8 } from "path";
33835
34816
  function projectCommandsDir(cwd = process.cwd()) {
33836
- return join51(cwd, ".kimiflare", "commands");
34817
+ return join53(cwd, ".kimiflare", "commands");
33837
34818
  }
33838
34819
  function globalCommandsDir() {
33839
- const xdg = process.env.XDG_CONFIG_HOME || join51(homedir32(), ".config");
33840
- return join51(xdg, "kimiflare", "commands");
34820
+ const xdg = process.env.XDG_CONFIG_HOME || join53(homedir33(), ".config");
34821
+ return join53(xdg, "kimiflare", "commands");
33841
34822
  }
33842
34823
  async function loadCustomCommands(cwd = process.cwd()) {
33843
34824
  const warnings = [];
@@ -33991,7 +34972,7 @@ var init_loader = __esm({
33991
34972
  import { exec } from "child_process";
33992
34973
  import { open as open3, realpath as realpath10 } from "fs/promises";
33993
34974
  import { isAbsolute as isAbsolute9, relative as relative12, resolve as resolvePathJoin, basename as pathBasename } from "path";
33994
- import { promisify as promisify9 } from "util";
34975
+ import { promisify as promisify11 } from "util";
33995
34976
  function tokenizeArgs(s) {
33996
34977
  return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
33997
34978
  const token = match[0];
@@ -34144,7 +35125,7 @@ var init_renderer2 = __esm({
34144
35125
  "src/commands/renderer.ts"() {
34145
35126
  "use strict";
34146
35127
  init_paths();
34147
- execAsync = promisify9(exec);
35128
+ execAsync = promisify11(exec);
34148
35129
  ARG_TOKEN_RE = /(?:"[^"]*"|'[^']*'|[^\s"']+)/g;
34149
35130
  POSITIONAL_RE = /\$(\d+)/g;
34150
35131
  HAS_POSITIONAL = /\$\d+/;
@@ -34453,6 +35434,9 @@ Rules:
34453
35434
 
34454
35435
  // src/ui/use-session-manager.ts
34455
35436
  import React, { useCallback as useCallback2, useRef as useRef2, useState as useState4 } from "react";
35437
+ function sessionNameText(messages, firstPrompt) {
35438
+ return firstPrompt?.trim() || extractFirstUserText(messages);
35439
+ }
34456
35440
  function extractFirstUserText(messages) {
34457
35441
  const firstUser = messages.find((m) => m.role === "user");
34458
35442
  if (!firstUser) return "session";
@@ -34485,9 +35469,9 @@ function useSessionManager(deps) {
34485
35469
  const [checkpointList, setCheckpointList] = useState4([]);
34486
35470
  const depsRef = useRef2(deps);
34487
35471
  depsRef.current = deps;
34488
- const ensureSessionId = useCallback2(() => {
35472
+ const ensureSessionId = useCallback2((firstPrompt) => {
34489
35473
  if (sessionIdRef.current) return sessionIdRef.current;
34490
- const text3 = extractFirstUserText(depsRef.current.messagesRef.current);
35474
+ const text3 = sessionNameText(depsRef.current.messagesRef.current, firstPrompt);
34491
35475
  sessionIdRef.current = makeSessionId(text3);
34492
35476
  setLogSessionId(sessionIdRef.current);
34493
35477
  return sessionIdRef.current;
@@ -34986,8 +35970,8 @@ var init_terminal_qr = __esm({
34986
35970
 
34987
35971
  // src/agent/jev-context.ts
34988
35972
  import { execFileSync as execFileSync5 } from "child_process";
34989
- import { readFile as readFile35 } from "fs/promises";
34990
- import { basename as basename11, join as join52, resolve as resolve20 } from "path";
35973
+ import { readFile as readFile37 } from "fs/promises";
35974
+ import { basename as basename11, join as join54, resolve as resolve21 } from "path";
34991
35975
  function messageText(message2) {
34992
35976
  if (typeof message2.content === "string") return message2.content.trim();
34993
35977
  if (!Array.isArray(message2.content)) return "";
@@ -35004,12 +35988,12 @@ function projectRoot(cwd) {
35004
35988
  timeout: 1e3
35005
35989
  }).trim();
35006
35990
  } catch {
35007
- return resolve20(cwd);
35991
+ return resolve21(cwd);
35008
35992
  }
35009
35993
  }
35010
35994
  async function readPackageMetadata(root) {
35011
35995
  try {
35012
- const parsed = JSON.parse(await readFile35(join52(root, "package.json"), "utf8"));
35996
+ const parsed = JSON.parse(await readFile37(join54(root, "package.json"), "utf8"));
35013
35997
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
35014
35998
  const pkg = parsed;
35015
35999
  const metadata = {};
@@ -35026,7 +36010,7 @@ async function readPackageMetadata(root) {
35026
36010
  async function readLicenseEvidence(root) {
35027
36011
  for (const name of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING"]) {
35028
36012
  try {
35029
- const content = (await readFile35(join52(root, name), "utf8")).trim();
36013
+ const content = (await readFile37(join54(root, name), "utf8")).trim();
35030
36014
  if (content) return { source: `project license (${name})`, content: content.slice(0, 700) };
35031
36015
  } catch {
35032
36016
  }
@@ -35059,7 +36043,7 @@ async function buildJevContext(question, messages, cwd = process.cwd()) {
35059
36043
  conversationRemaining -= source.length + content.length + 2;
35060
36044
  }
35061
36045
  }
35062
- const currentDirectory = basename11(resolve20(cwd));
36046
+ const currentDirectory = basename11(resolve21(cwd));
35063
36047
  const escapedDirectory = currentDirectory.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
35064
36048
  const namesCurrentDirectory = currentDirectory.length >= 5 && !/^(?:agent|app|code|lib|project|repo|src|test|tests|workspace)$/i.test(currentDirectory) && new RegExp(`\\b${escapedDirectory}\\b`, "i").test(question);
35065
36049
  if (CURRENT_PROJECT_REFERENCE.test(question) || namesCurrentDirectory) {
@@ -35282,8 +36266,8 @@ var init_frontmatter2 = __esm({
35282
36266
  });
35283
36267
 
35284
36268
  // src/skills/loader.ts
35285
- import { readFile as readFile36, readdir as readdir8, stat as stat12 } from "fs/promises";
35286
- import { join as join53, extname } from "path";
36269
+ import { readFile as readFile38, readdir as readdir8, stat as stat13 } from "fs/promises";
36270
+ import { join as join55, extname } from "path";
35287
36271
  function normalizeManifest(raw, filePath) {
35288
36272
  const name = typeof raw.name === "string" ? raw.name : "";
35289
36273
  const description = typeof raw.description === "string" ? raw.description : "";
@@ -35297,7 +36281,7 @@ function normalizeManifest(raw, filePath) {
35297
36281
  return { name, description, match, scope, priority, enabled: enabled2 };
35298
36282
  }
35299
36283
  async function loadSkillFile(filePath) {
35300
- const raw = await readFile36(filePath, "utf-8");
36284
+ const raw = await readFile38(filePath, "utf-8");
35301
36285
  const parsed = parseFrontmatter2(raw);
35302
36286
  const manifest = normalizeManifest(parsed.data, filePath);
35303
36287
  const body = parsed.content.trim();
@@ -35319,8 +36303,8 @@ async function loadSkillsFromDir(dirPath) {
35319
36303
  const entries = await readdir8(dirPath);
35320
36304
  const files = [];
35321
36305
  for (const entry of entries) {
35322
- const full = join53(dirPath, entry);
35323
- const s = await stat12(full);
36306
+ const full = join55(dirPath, entry);
36307
+ const s = await stat13(full);
35324
36308
  if (s.isFile() && extname(entry) === ".md") {
35325
36309
  files.push(full);
35326
36310
  }
@@ -35347,12 +36331,12 @@ var init_loader2 = __esm({
35347
36331
  });
35348
36332
 
35349
36333
  // src/skills/manager.ts
35350
- import { mkdir as mkdir20, writeFile as writeFile17, unlink as unlink5, readFile as readFile37 } from "fs/promises";
35351
- import { join as join54 } from "path";
36334
+ import { mkdir as mkdir21, writeFile as writeFile18, unlink as unlink5, readFile as readFile39 } from "fs/promises";
36335
+ import { join as join56 } from "path";
35352
36336
  function getSkillDirs(cwd) {
35353
36337
  return {
35354
- projectDir: join54(cwd, ".kimiflare", "skills"),
35355
- globalDir: join54(process.env.HOME ?? "", ".config", "kimiflare", "skills")
36338
+ projectDir: join56(cwd, ".kimiflare", "skills"),
36339
+ globalDir: join56(process.env.HOME ?? "", ".config", "kimiflare", "skills")
35356
36340
  };
35357
36341
  }
35358
36342
  async function listAllSkills(cwd) {
@@ -35366,7 +36350,7 @@ async function listAllSkills(cwd) {
35366
36350
  async function createSkill(opts2) {
35367
36351
  const dirs = getSkillDirs(opts2.cwd);
35368
36352
  const dir = opts2.scope === "project" ? dirs.projectDir : dirs.globalDir;
35369
- const filepath = join54(dir, `${opts2.name}.md`);
36353
+ const filepath = join56(dir, `${opts2.name}.md`);
35370
36354
  const frontmatter = {
35371
36355
  name: opts2.name,
35372
36356
  enabled: true,
@@ -35387,8 +36371,8 @@ ${yaml}
35387
36371
 
35388
36372
  Add your instructions here.
35389
36373
  `;
35390
- await mkdir20(dir, { recursive: true });
35391
- await writeFile17(filepath, content, "utf8");
36374
+ await mkdir21(dir, { recursive: true });
36375
+ await writeFile18(filepath, content, "utf8");
35392
36376
  return { filepath };
35393
36377
  }
35394
36378
  async function deleteSkill(name, cwd) {
@@ -35402,7 +36386,7 @@ async function setSkillEnabled(name, enabled2, cwd) {
35402
36386
  const all = await listAllSkills(cwd);
35403
36387
  const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
35404
36388
  if (!skill) throw new Error(`skill "${name}" not found`);
35405
- const raw = await readFile37(skill.filePath, "utf-8");
36389
+ const raw = await readFile39(skill.filePath, "utf-8");
35406
36390
  const parsed = parseFrontmatter2(raw);
35407
36391
  parsed.data.enabled = enabled2;
35408
36392
  const yaml = Object.entries(parsed.data).map(([k, v]) => {
@@ -35414,7 +36398,7 @@ ${v.map((item) => ` - ${item}`).join("\n")}`;
35414
36398
  ${yaml}
35415
36399
  ---
35416
36400
  ${parsed.content}`;
35417
- await writeFile17(skill.filePath, content, "utf8");
36401
+ await writeFile18(skill.filePath, content, "utf8");
35418
36402
  return { filepath: skill.filePath };
35419
36403
  }
35420
36404
  async function findSkillFile(name, cwd) {
@@ -35495,15 +36479,15 @@ var tui_report_exports = {};
35495
36479
  __export(tui_report_exports, {
35496
36480
  getCategoryReportText: () => getCategoryReportText
35497
36481
  });
35498
- import { readFile as readFile38 } from "fs/promises";
35499
- import { join as join55 } from "path";
35500
- import { homedir as homedir33 } from "os";
36482
+ import { readFile as readFile40 } from "fs/promises";
36483
+ import { join as join57 } from "path";
36484
+ import { homedir as homedir34 } from "os";
35501
36485
  function usageDir3() {
35502
- const xdg = process.env.XDG_DATA_HOME || join55(homedir33(), ".local", "share");
35503
- return join55(xdg, "kimiflare");
36486
+ const xdg = process.env.XDG_DATA_HOME || join57(homedir34(), ".local", "share");
36487
+ return join57(xdg, "kimiflare");
35504
36488
  }
35505
36489
  function usagePath4() {
35506
- return join55(usageDir3(), "usage.json");
36490
+ return join57(usageDir3(), "usage.json");
35507
36491
  }
35508
36492
  function today3() {
35509
36493
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -35515,7 +36499,7 @@ function daysAgo2(n) {
35515
36499
  }
35516
36500
  async function loadLog3() {
35517
36501
  try {
35518
- const raw = await readFile38(usagePath4(), "utf8");
36502
+ const raw = await readFile40(usagePath4(), "utf8");
35519
36503
  return JSON.parse(raw);
35520
36504
  } catch {
35521
36505
  return { version: 1, days: [], sessions: [] };
@@ -35572,7 +36556,7 @@ var init_tui_report = __esm({
35572
36556
  });
35573
36557
 
35574
36558
  // src/ui/slash-commands.ts
35575
- import { join as join56 } from "path";
36559
+ import { join as join58 } from "path";
35576
36560
  function executeFreshStart(ctx, planText, overrideMode, opts2 = {}) {
35577
36561
  const oldSessionId = ctx.sessionIdRef.current;
35578
36562
  if (ctx.cacheStableRef.current && ctx.messagesRef.current.length >= 2) {
@@ -35624,7 +36608,7 @@ function formatRunningSubagents(now2 = Date.now()) {
35624
36608
  if (workers.length === 0) return "no subagents running";
35625
36609
  return [
35626
36610
  `${workers.length} subagent${workers.length === 1 ? "" : "s"} running:`,
35627
- ...workers.map((w) => ` #${w.index} ${formatElapsed2(now2 - w.startedAt)} ${w.status === "cancelling" ? "(cancelling) " : ""}${oneLine(w.task, 90)}`),
36611
+ ...workers.map((w) => ` #${w.index} ${formatElapsed2(now2 - w.startedAt)} ${w.status === "cancelling" ? "(cancelling) " : ""}${oneLine2(w.task, 90)}`),
35628
36612
  "cancel one with /subagents cancel <n>, or all with /subagents cancel all \u2014 the turn keeps going"
35629
36613
  ].join("\n");
35630
36614
  }
@@ -35637,7 +36621,7 @@ function cancelSubagents(target) {
35637
36621
  const ref = target.replace(/^#/, "");
35638
36622
  const cancelled = workerRegistry.cancel(/^\d+$/.test(ref) ? Number(ref) : ref);
35639
36623
  if (!cancelled) return `no running subagent #${ref} (see /subagents list)`;
35640
- return `cancelling subagent #${cancelled.index} (${oneLine(cancelled.task, 60)}); the turn continues`;
36624
+ return `cancelling subagent #${cancelled.index} (${oneLine2(cancelled.task, 60)}); the turn continues`;
35641
36625
  }
35642
36626
  function formatElapsed2(ms) {
35643
36627
  const total = Math.max(0, Math.floor(ms / 1e3));
@@ -35645,7 +36629,7 @@ function formatElapsed2(ms) {
35645
36629
  const sec = total % 60;
35646
36630
  return m > 0 ? `${m}m${String(sec).padStart(2, "0")}s` : `${sec}s`;
35647
36631
  }
35648
- function oneLine(text3, max) {
36632
+ function oneLine2(text3, max) {
35649
36633
  const single = text3.replace(/\s+/g, " ").trim();
35650
36634
  return single.length > max ? `${single.slice(0, max - 1)}\u2026` : single;
35651
36635
  }
@@ -36044,7 +37028,7 @@ auto (default) lets the coordinator assess substantial tasks and delegate indepe
36044
37028
  const context = noContext ? [] : await buildJevContext(question.prompt, ctx.messagesRef.current, process.cwd());
36045
37029
  const receipt = formatJevContextReceipt(context, showContext);
36046
37030
  setResult(eventKey, { kind: "info", key: eventKey, text: `Jev is evaluating \xB7 ${receipt}`, pending: true });
36047
- await new Promise((resolve21) => setTimeout(resolve21, 0));
37031
+ await new Promise((resolve22) => setTimeout(resolve22, 0));
36048
37032
  const answer = await askJev(apiKey, question, { context });
36049
37033
  const presentation = presentJevAnswer(question, answer);
36050
37034
  setResult(eventKey, {
@@ -36542,7 +37526,7 @@ ${lines.join("\n")}` }]);
36542
37526
  void (async () => {
36543
37527
  try {
36544
37528
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
36545
- const file = await loadSession(join56(sessionsDir3(), `${currentId}.json`));
37529
+ const file = await loadSession(join58(sessionsDir3(), `${currentId}.json`));
36546
37530
  const cps = file.checkpoints ?? [];
36547
37531
  if (cps.length === 0) {
36548
37532
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "no checkpoints in this session" }]);
@@ -36587,7 +37571,7 @@ ${lines.join("\n")}` }]);
36587
37571
  try {
36588
37572
  ctx.ensureSessionId();
36589
37573
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
36590
- const filePath = join56(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
37574
+ const filePath = join58(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
36591
37575
  await addCheckpoint(filePath, cp);
36592
37576
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: `checkpoint saved: "${label}"` }]);
36593
37577
  } catch (e) {
@@ -37087,12 +38071,12 @@ also read (legacy): ${legacyGlobalSettingsPath()}, ${legacyProjectSettingsPath(c
37087
38071
 
37088
38072
  // src/init/context-generator.ts
37089
38073
  import { existsSync as existsSync17, statSync as statSync6 } from "fs";
37090
- import { join as join57 } from "path";
38074
+ import { join as join59 } from "path";
37091
38075
  function detectFlavor(cwd) {
37092
38076
  for (const [flavor, signatures] of Object.entries(FLAVOR_SIGNATURES)) {
37093
38077
  if (flavor === "generic") continue;
37094
38078
  for (const sig of signatures) {
37095
- const path = join57(cwd, sig);
38079
+ const path = join59(cwd, sig);
37096
38080
  if (sig.includes("*")) {
37097
38081
  try {
37098
38082
  const parts = sig.split("*");
@@ -37113,14 +38097,14 @@ function detectFlavor(cwd) {
37113
38097
  }
37114
38098
  function findFile(cwd, candidates) {
37115
38099
  for (const c of candidates) {
37116
- if (existsSync17(join57(cwd, c))) return c;
38100
+ if (existsSync17(join59(cwd, c))) return c;
37117
38101
  }
37118
38102
  return null;
37119
38103
  }
37120
38104
  function findSourceRoots(cwd) {
37121
38105
  const roots = [];
37122
38106
  for (const r of SOURCE_ROOT_CANDIDATES) {
37123
- const p = join57(cwd, r);
38107
+ const p = join59(cwd, r);
37124
38108
  try {
37125
38109
  const s = statSync6(p);
37126
38110
  if (s.isDirectory()) roots.push(r);
@@ -37131,9 +38115,9 @@ function findSourceRoots(cwd) {
37131
38115
  }
37132
38116
  function findCiConfig(cwd) {
37133
38117
  for (const c of CI_PATHS) {
37134
- if (existsSync17(join57(cwd, c))) {
38118
+ if (existsSync17(join59(cwd, c))) {
37135
38119
  try {
37136
- const s = statSync6(join57(cwd, c));
38120
+ const s = statSync6(join59(cwd, c));
37137
38121
  return s.isDirectory() ? c : c;
37138
38122
  } catch {
37139
38123
  }
@@ -37270,7 +38254,7 @@ function analyzeProject(cwd) {
37270
38254
  ciConfig: findCiConfig(cwd),
37271
38255
  readme: findFile(cwd, ["README.md", "README.rst", "README.txt", "Readme.md"]),
37272
38256
  sourceRoots: findSourceRoots(cwd),
37273
- hasGit: existsSync17(join57(cwd, ".git"))
38257
+ hasGit: existsSync17(join59(cwd, ".git"))
37274
38258
  };
37275
38259
  }
37276
38260
  function bashDiscoveryCommands(profile) {
@@ -37445,7 +38429,7 @@ function buildInitTasks(targetFilename) {
37445
38429
  }
37446
38430
  function buildInitPrompt(cwd) {
37447
38431
  const existingName = ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].find(
37448
- (n) => existsSync17(join57(cwd, n))
38432
+ (n) => existsSync17(join59(cwd, n))
37449
38433
  );
37450
38434
  const isRefresh = existingName !== void 0;
37451
38435
  const targetFilename = existingName ?? "AGENTS.md";
@@ -37533,7 +38517,7 @@ var init_context_generator = __esm({
37533
38517
 
37534
38518
  // src/init/run-init.ts
37535
38519
  import { existsSync as existsSync18 } from "fs";
37536
- import { join as join58 } from "path";
38520
+ import { join as join60 } from "path";
37537
38521
  async function runInit(deps) {
37538
38522
  const {
37539
38523
  cfg,
@@ -37629,7 +38613,7 @@ async function runInit(deps) {
37629
38613
  lspManagerRef.current.notifyChange(path, content);
37630
38614
  } else {
37631
38615
  void import("fs/promises").then(
37632
- ({ readFile: readFile40 }) => readFile40(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
38616
+ ({ readFile: readFile42 }) => readFile42(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
37633
38617
  })
37634
38618
  );
37635
38619
  }
@@ -37730,7 +38714,7 @@ async function runInit(deps) {
37730
38714
  }
37731
38715
  }
37732
38716
  } }));
37733
- if (existsSync18(join58(cwd, targetFilename))) {
38717
+ if (existsSync18(join60(cwd, targetFilename))) {
37734
38718
  if (cacheStableRef.current) {
37735
38719
  messagesRef.current[1] = {
37736
38720
  role: "system",
@@ -37824,11 +38808,11 @@ var init_run_init = __esm({
37824
38808
  });
37825
38809
 
37826
38810
  // src/skills/discovery.ts
37827
- import { readdir as readdir9, stat as stat13, readFile as readFile39 } from "fs/promises";
37828
- import { join as join59, extname as extname2 } from "path";
38811
+ import { readdir as readdir9, stat as stat14, readFile as readFile41 } from "fs/promises";
38812
+ import { join as join61, extname as extname2 } from "path";
37829
38813
  async function dirExists(path) {
37830
38814
  try {
37831
- const s = await stat13(path);
38815
+ const s = await stat14(path);
37832
38816
  return s.isDirectory();
37833
38817
  } catch {
37834
38818
  return false;
@@ -37836,7 +38820,7 @@ async function dirExists(path) {
37836
38820
  }
37837
38821
  async function fileExists(path) {
37838
38822
  try {
37839
- const s = await stat13(path);
38823
+ const s = await stat14(path);
37840
38824
  return s.isFile();
37841
38825
  } catch {
37842
38826
  return false;
@@ -37849,20 +38833,20 @@ async function scanSkillDir(dirPath, source) {
37849
38833
  for (const entry of entries) {
37850
38834
  if (!entry.isFile()) continue;
37851
38835
  if (!SKILL_EXTENSIONS.has(extname2(entry.name))) continue;
37852
- files.push({ filePath: join59(dirPath, entry.name), source });
38836
+ files.push({ filePath: join61(dirPath, entry.name), source });
37853
38837
  }
37854
38838
  return files;
37855
38839
  }
37856
38840
  async function discoverSkills(cwd) {
37857
- const agentsSkills = await scanSkillDir(join59(cwd, ".agents", "skills"), "agents");
37858
- const agentsMd = await fileExists(join59(cwd, "AGENTS.md")) ? [{ filePath: join59(cwd, "AGENTS.md"), source: "agents-md" }] : [];
37859
- const githubSkills = await scanSkillDir(join59(cwd, ".github", "skills"), "github");
37860
- const kimiflareSkills = await scanSkillDir(join59(cwd, ".kimiflare", "skills"), "kimiflare");
38841
+ const agentsSkills = await scanSkillDir(join61(cwd, ".agents", "skills"), "agents");
38842
+ const agentsMd = await fileExists(join61(cwd, "AGENTS.md")) ? [{ filePath: join61(cwd, "AGENTS.md"), source: "agents-md" }] : [];
38843
+ const githubSkills = await scanSkillDir(join61(cwd, ".github", "skills"), "github");
38844
+ const kimiflareSkills = await scanSkillDir(join61(cwd, ".kimiflare", "skills"), "kimiflare");
37861
38845
  const ordered = [...agentsSkills, ...agentsMd, ...githubSkills, ...kimiflareSkills];
37862
38846
  return ordered;
37863
38847
  }
37864
38848
  async function readSkillFile(filePath) {
37865
- const bytes = await readFile39(filePath);
38849
+ const bytes = await readFile41(filePath);
37866
38850
  return { bytes, text: bytes.toString("utf-8") };
37867
38851
  }
37868
38852
  var SKILL_EXTENSIONS;
@@ -38069,7 +39053,7 @@ var init_skills = __esm({
38069
39053
 
38070
39054
  // src/ui/run-startup-tasks.ts
38071
39055
  import { existsSync as existsSync19 } from "fs";
38072
- import { join as join60 } from "path";
39056
+ import { join as join62 } from "path";
38073
39057
  function runStartupTasks(deps) {
38074
39058
  const {
38075
39059
  cfg,
@@ -38092,7 +39076,7 @@ function runStartupTasks(deps) {
38092
39076
  }
38093
39077
  });
38094
39078
  if (cfg.memoryEnabled) {
38095
- const dbPath = cfg.memoryDbPath ?? join60(process.cwd(), ".kimiflare", "memory.db");
39079
+ const dbPath = cfg.memoryDbPath ?? join62(process.cwd(), ".kimiflare", "memory.db");
38096
39080
  const manager = new MemoryManager({
38097
39081
  dbPath,
38098
39082
  ...auth,
@@ -38125,7 +39109,7 @@ function runStartupTasks(deps) {
38125
39109
  });
38126
39110
  const cwd = process.cwd();
38127
39111
  sessionStartRecallRef.current = manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
38128
- if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync19(join60(cwd, name)))) {
39112
+ if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync19(join62(cwd, name)))) {
38129
39113
  const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
38130
39114
  const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
38131
39115
  if (driftCount >= 5) {
@@ -38136,7 +39120,7 @@ function runStartupTasks(deps) {
38136
39120
  memoryManagerRef.current?.close();
38137
39121
  memoryManagerRef.current = null;
38138
39122
  }
38139
- const skillDbPath = cfg.memoryDbPath ?? join60(process.cwd(), ".kimiflare", "memory.db");
39123
+ const skillDbPath = cfg.memoryDbPath ?? join62(process.cwd(), ".kimiflare", "memory.db");
38140
39124
  const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
38141
39125
  initSkillsSchema(skillDb);
38142
39126
  void indexSkills({
@@ -38812,8 +39796,8 @@ var init_turn_nudges = __esm({
38812
39796
  });
38813
39797
 
38814
39798
  // src/commands/save.ts
38815
- import { mkdir as mkdir21, writeFile as writeFile18, unlink as unlink6 } from "fs/promises";
38816
- import { dirname as dirname27 } from "path";
39799
+ import { mkdir as mkdir22, writeFile as writeFile19, unlink as unlink6 } from "fs/promises";
39800
+ import { dirname as dirname28 } from "path";
38817
39801
  async function saveCustomCommand(opts2) {
38818
39802
  const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
38819
39803
  const filepath = `${dir}/${opts2.name}.md`;
@@ -38824,8 +39808,8 @@ async function saveCustomCommand(opts2) {
38824
39808
  if (opts2.effort) data.effort = opts2.effort;
38825
39809
  const frontmatter = serializeFrontmatter(data);
38826
39810
  const content = frontmatter + opts2.template;
38827
- await mkdir21(dirname27(filepath), { recursive: true });
38828
- await writeFile18(filepath, content, "utf8");
39811
+ await mkdir22(dirname28(filepath), { recursive: true });
39812
+ await writeFile19(filepath, content, "utf8");
38829
39813
  return { filepath };
38830
39814
  }
38831
39815
  async function deleteCustomCommand(cmd) {
@@ -38924,7 +39908,7 @@ __export(app_exports, {
38924
39908
  import { useState as useState6, useRef as useRef4, useEffect as useEffect2, useCallback as useCallback4, useMemo } from "react";
38925
39909
  import { useApp, render } from "ink";
38926
39910
  import { existsSync as existsSync20 } from "fs";
38927
- import { join as join61 } from "path";
39911
+ import { join as join63 } from "path";
38928
39912
  import { jsx } from "react/jsx-runtime";
38929
39913
  function App({
38930
39914
  initialCfg,
@@ -39178,7 +40162,7 @@ function App({
39178
40162
  void (async () => {
39179
40163
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39180
40164
  try {
39181
- await doResumeSession(join61(sessionsDir3(), `${initialResumeSessionId}.json`));
40165
+ await doResumeSession(join63(sessionsDir3(), `${initialResumeSessionId}.json`));
39182
40166
  if (initialResumeReason === "crash") {
39183
40167
  setEvents((e) => [
39184
40168
  ...e,
@@ -39411,7 +40395,7 @@ function App({
39411
40395
  saveCheckpoint: async (checkpoint) => {
39412
40396
  ensureSessionId();
39413
40397
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39414
- await addCheckpoint(join61(sessionsDir3(), `${sessionIdRef.current}.json`), checkpoint);
40398
+ await addCheckpoint(join63(sessionsDir3(), `${sessionIdRef.current}.json`), checkpoint);
39415
40399
  },
39416
40400
  summarize: () => generateContinuationSummary({
39417
40401
  messages: messagesRef.current,
@@ -39930,27 +40914,20 @@ ${result2.output ?? "(output not captured)"}`,
39930
40914
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: nudge }]);
39931
40915
  }
39932
40916
  const classification = classifyIntent(trimmed);
39933
- if (hooksManagerRef.current.hasEnabledHooks("UserPromptSubmit")) {
39934
- const promptOutcome = await hooksManagerRef.current.fire(
39935
- "UserPromptSubmit",
39936
- {
39937
- event: "UserPromptSubmit",
39938
- session_id: sessionIdRef.current,
39939
- cwd: process.cwd(),
39940
- prompt: opts2?.userPromptForHooks ?? display,
39941
- tier: classification.tier
39942
- },
39943
- null
39944
- );
39945
- if (promptOutcome.vetoed) {
39946
- const reason = promptOutcome.vetoReason || "UserPromptSubmit hook blocked the prompt";
39947
- setEvents((e) => [
39948
- ...e,
39949
- { kind: "info", key: mkKey(), text: `hook blocked the prompt: ${reason}` }
39950
- ]);
39951
- endTurn();
39952
- return;
39953
- }
40917
+ const promptGate = await submitUserPrompt({
40918
+ hooks: hooksManagerRef.current,
40919
+ sessionId: ensureSessionId(trimmed),
40920
+ cwd: process.cwd(),
40921
+ prompt: opts2?.userPromptForHooks ?? display,
40922
+ tier: classification.tier,
40923
+ resumed: messagesRef.current.some((m) => m.role === "assistant"),
40924
+ model: overrideModel ?? cfg.model,
40925
+ auth: llmAuthFromConfig(cfg)
40926
+ });
40927
+ if (promptGate.blocked) {
40928
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `hook blocked the prompt: ${promptGate.reason}` }]);
40929
+ endTurn();
40930
+ return;
39954
40931
  }
39955
40932
  messagesRef.current.push({ role: "user", content });
39956
40933
  const traceUserSource = denyFeedbackRef.current ? "deny_feedback" : "typed";
@@ -39970,7 +40947,7 @@ ${result2.output ?? "(output not captured)"}`,
39970
40947
  turnCounterRef.current += 1;
39971
40948
  const nudges = turnNudges({
39972
40949
  turn: turnCounterRef.current,
39973
- hasContextFile: ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync20(join61(process.cwd(), name))),
40950
+ hasContextFile: ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync20(join63(process.cwd(), name))),
39974
40951
  contextStale: kimiMdStale,
39975
40952
  mode: modeRef.current,
39976
40953
  freshAfterTurns: cfg?.autoFreshSuggestionTurns ?? DEFAULT_AUTO_FRESH_SUGGESTION_TURNS,
@@ -40147,7 +41124,7 @@ ${result2.output ?? "(output not captured)"}`,
40147
41124
  lspManagerRef.current.notifyChange(path, content2);
40148
41125
  } else {
40149
41126
  void import("fs/promises").then(
40150
- ({ readFile: readFile40 }) => readFile40(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
41127
+ ({ readFile: readFile42 }) => readFile42(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
40151
41128
  })
40152
41129
  );
40153
41130
  }
@@ -40655,7 +41632,7 @@ ${result2.output ?? "(output not captured)"}`,
40655
41632
  lsp: {
40656
41633
  servers: cfg?.lspServers ?? {},
40657
41634
  scope: lspScope,
40658
- hasProjectDir: existsSync20(join61(process.cwd(), ".kimiflare"))
41635
+ hasProjectDir: existsSync20(join63(process.cwd(), ".kimiflare"))
40659
41636
  }
40660
41637
  });
40661
41638
  });
@@ -40747,6 +41724,7 @@ var init_app = __esm({
40747
41724
  init_run_init();
40748
41725
  init_connect();
40749
41726
  init_session_lifecycle();
41727
+ init_prompt_submit();
40750
41728
  init_run_startup_tasks();
40751
41729
  init_manager_init();
40752
41730
  init_run_compact();
@@ -40786,6 +41764,7 @@ init_sessions();
40786
41764
  init_context_budget();
40787
41765
  init_subagent_stats();
40788
41766
  init_session_lifecycle();
41767
+ init_prompt_submit();
40789
41768
  init_attachments();
40790
41769
  init_glob();
40791
41770
  init_permissions_evaluator();
@@ -40830,8 +41809,8 @@ async function resolveFiles(filePatterns, cwd) {
40830
41809
  const resolved = /* @__PURE__ */ new Set();
40831
41810
  for (const pattern of filePatterns) {
40832
41811
  try {
40833
- const stat14 = await import("fs/promises").then((m) => m.stat(resolve7(cwd, pattern)));
40834
- if (stat14.isFile()) {
41812
+ const stat15 = await import("fs/promises").then((m) => m.stat(resolve7(cwd, pattern)));
41813
+ if (stat15.isFile()) {
40835
41814
  resolved.add(resolve7(cwd, pattern));
40836
41815
  continue;
40837
41816
  }
@@ -40959,6 +41938,33 @@ async function runPrintMode(opts2) {
40959
41938
  });
40960
41939
  const usageWrites = [];
40961
41940
  const auth = llmAuthFromConfig(opts2);
41941
+ const gate = await submitUserPrompt({
41942
+ hooks,
41943
+ sessionId: sessionFile.id,
41944
+ cwd,
41945
+ prompt: opts2.prompt,
41946
+ tier: policy.classification.tier,
41947
+ resumed: messages.some((m) => m.role === "assistant"),
41948
+ model: opts2.model,
41949
+ auth
41950
+ });
41951
+ if (gate.blocked) {
41952
+ const msg = `prompt blocked by a UserPromptSubmit hook: ${gate.reason}`;
41953
+ if (format === "text") {
41954
+ process.stderr.write(`\x1B[31mautopilot: ${msg}\x1B[0m
41955
+ `);
41956
+ } else if (format === "json") {
41957
+ jsonOutput.durationMs = Date.now() - startMs;
41958
+ process.stdout.write(JSON.stringify({ ...jsonOutput, error: msg, blocked: { reason: gate.reason } }, null, 2) + "\n");
41959
+ } else {
41960
+ emitStreamJson("error", { message: msg, code: PROMPT_BLOCKED_EXIT_CODE, reason: "prompt_blocked" });
41961
+ }
41962
+ process.exitCode = PROMPT_BLOCKED_EXIT_CODE;
41963
+ await endSessions("exit");
41964
+ trace.emit("session_end", { reason: "exit" }, null);
41965
+ await trace.flush();
41966
+ return;
41967
+ }
40962
41968
  const callbacks = {
40963
41969
  onUsageFinal: (u, meta) => {
40964
41970
  const lookup = opts2.openrouterApiKey && !auth.customEndpoint && meta?.generationId ? { apiKey: opts2.openrouterApiKey, meta } : void 0;
@@ -41204,7 +42210,7 @@ function registerEvalCommand(program2) {
41204
42210
  // src/index.tsx
41205
42211
  var resumeCommand = null;
41206
42212
  var program = new Command();
41207
- program.name("autopilot").description("Terminal coding agent. Bring your own provider: OpenRouter, OpenAI, DeepSeek, Kimi, Z.ai, Ollama, any OpenAI-compatible endpoint.").version(getAppVersion()).option("-p, --print <prompt>", "one-shot mode: send prompt, stream reply to stdout, exit").option("-m, --model <id>", "model id for the active provider (defaults to its configured model)").addOption(new Option("--cloud", "(retired) KimiFlare Cloud").hideHelp()).option("--dangerously-allow-all", "auto-approve every permission prompt (print mode only)").addOption(new Option("--worker-profile <profile>", "internal worker permission profile").hideHelp()).option("--reasoning", "include reasoning in stdout (print mode only)").option("--thinking", "alias for --reasoning").option("--continue-on-limit", "reset tool-call counter and continue when the 200-call limit is hit (print mode only)").option("--max-total-tool-iterations <n>", "hard ceiling on tool iterations per turn across --continue-on-limit resets (default 1000)", (v) => parseInt(v, 10)).option("--max-input-tokens <n>", "cumulative prompt token budget; exits 42 when exhausted (print mode only)", (v) => parseInt(v, 10)).option("--emit-events", "emit Camouflage NDJSON events to stdout; requires -p (for initial prompt)").option("--multi-turn", "with --emit-events: keep reading stdin for UserInputSubmitted follow-ups after the initial turn").option("--mode <mode>", "run mode: interactive (default), print, rpc").option("-c, --continue", "continue the most recent session in the current working directory").option("-S, --session <id>", "resume a specific session by id").option("-f, --file <path>", "attach file(s) to the prompt; repeatable, supports globs (print mode only)", (v, prev) => (prev ?? []).concat(v)).option("--format <mode>", "output format for print mode: text (default), json, stream-json").option("--dir <path>", "run in the specified directory instead of the current one (print mode only)").option("--title <title>", "override the auto-generated session title (print mode only)").option("--attach <url>", "attach to a running autopilot serve instance (print mode only)");
42213
+ program.name("autopilot").description("Terminal coding agent. Bring your own provider: OpenRouter, OpenAI, DeepSeek, Kimi, Z.ai, Ollama, any OpenAI-compatible endpoint.").version(getAppVersion()).option("-p, --print <prompt>", "one-shot mode: send prompt, stream reply to stdout, exit").option("-m, --model <id>", "model id for the active provider (defaults to its configured model)").addOption(new Option("--cloud", "(retired) KimiFlare Cloud").hideHelp()).option("--dangerously-allow-all", "auto-approve every permission prompt (print mode only)").addOption(new Option("--worker-profile <profile>", "internal worker permission profile").hideHelp()).option("--reasoning", "include reasoning in stdout (print mode only)").option("--thinking", "alias for --reasoning").option("--continue-on-limit", "reset tool-call counter and continue when the 200-call limit is hit (print mode only)").option("--max-total-tool-iterations <n>", "hard ceiling on tool iterations per turn across --continue-on-limit resets (default 1000)", (v) => parseInt(v, 10)).option("--max-input-tokens <n>", "cumulative prompt token budget; exits 42 when exhausted (print mode only)", (v) => parseInt(v, 10)).option("--emit-events", "emit Camouflage NDJSON events to stdout; requires -p (for initial prompt)").option("--multi-turn", "with --emit-events: keep reading stdin for UserInputSubmitted follow-ups after the initial turn").option("--mode <mode>", "run mode: interactive (default), print, rpc").option("-c, --continue", "continue the most recent session in the current working directory").option("-S, --session <id>", "resume a specific session by id").option("-f, --file <path>", "attach file(s) to the prompt; repeatable, supports globs (print mode only)", (v, prev) => (prev ?? []).concat(v)).option("--format <mode>", "output format for print mode: text (default), json, stream-json").option("--dir <path>", "run in the specified directory instead of the current one (print mode only)").option("--title <title>", "override the auto-generated session title (print mode only)").option("--attach <url>", "attach to a running autopilot serve instance (print mode only)").option("--on <server>", "run the task on a paired server (autopilot remote add) instead of this machine").option("--local", "run on this machine (the default)").option("--repo <owner/name>", "with --on: GitHub repository (default: this checkout's origin)").option("--detach", "with --on: start the task and return without streaming its output").argument("[task...]", "with --on: the task to run on the server");
41208
42214
  program.command("cost").description("Show cost attribution by task type (requires costAttribution enabled)").option("-w, --week", "last 7 days (default)").option("-m, --month", "last 30 days").option("-d, --day", "today only").option("-s, --session <id>", "single session detail").option("-c, --category <name>", "filter by category").option("--json", "machine-readable output").option("--reclassify", "re-run classification on all sessions").option("--local-only", "skip OpenRouter reconciliation (no network)").option("--verify", "check recorded tokens and cost against OpenRouter's generation records (latest session, or -s <id>)").action(async (cmdOpts) => {
41209
42215
  const cfg = await loadConfig();
41210
42216
  if (cmdOpts.verify) {
@@ -41235,10 +42241,51 @@ program.command("cost").description("Show cost attribution by task type (require
41235
42241
  const { runCostCommand: runCostCommand2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
41236
42242
  await runCostCommand2({ ...cmdOpts, config: cfg });
41237
42243
  });
41238
- program.command("remote").description("Retired: remote sessions on Cloudflare").allowUnknownOption().argument("[args...]").action(() => {
41239
- console.error('autopilot remote was retired. For unattended work use local subagents (/subagents) or a headless run: autopilot -p "\u2026".');
41240
- process.exit(2);
41241
- });
42244
+ async function runRemoteCommand(fn) {
42245
+ const controller = new AbortController();
42246
+ const onInterrupt = () => controller.abort();
42247
+ process.once("SIGINT", onInterrupt);
42248
+ try {
42249
+ const code = await fn(controller.signal);
42250
+ if (typeof code === "number") process.exitCode = code;
42251
+ } catch (error) {
42252
+ if (controller.signal.aborted) return;
42253
+ const { describeError: describeError2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42254
+ console.error(`autopilot: ${describeError2(error)}`);
42255
+ process.exitCode = 1;
42256
+ } finally {
42257
+ process.off("SIGINT", onInterrupt);
42258
+ }
42259
+ }
42260
+ var remoteCommand = program.command("remote").description("Pair this machine with Autopilot servers that run tasks for you");
42261
+ remoteCommand.command("add").description("Pair with a server: approve the code it shows in your browser (no keys copied)").argument("<url>", "server URL, e.g. https://acme.example.com").option("-n, --name <name>", "short name for --on (default: the host's first label)").option("--no-open", "print the approval link instead of opening a browser").action((url, cmdOpts) => runRemoteCommand(async (signal) => {
42262
+ const { remoteAdd: remoteAdd2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42263
+ await remoteAdd2({ url, open: cmdOpts.open, signal, ...cmdOpts.name ? { name: cmdOpts.name } : {} });
42264
+ }));
42265
+ remoteCommand.command("list").description("List paired servers").action(() => runRemoteCommand(async () => {
42266
+ const { remoteList: remoteList2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42267
+ await remoteList2();
42268
+ }));
42269
+ remoteCommand.command("remove").description("Forget a paired server").argument("<name>").action((name) => runRemoteCommand(async () => {
42270
+ const { remoteRemove: remoteRemove2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42271
+ return await remoteRemove2(name) ? 0 : 1;
42272
+ }));
42273
+ var runsCommand = program.command("runs").description("List tasks running or finished on your servers").option("--on <server>", "only this server").option("--json", "machine-readable output").action((cmdOpts) => runRemoteCommand(async () => {
42274
+ const { listRuns: listRuns2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42275
+ await listRuns2({ ...cmdOpts.on ? { remote: cmdOpts.on } : {}, json: !!cmdOpts.json });
42276
+ }));
42277
+ runsCommand.command("watch").description("Show a run's output and follow it until it ends (Ctrl-C detaches)").argument("<run>", "run ID or prefix").option("--on <server>", "server the run is on").action((id, cmdOpts) => runRemoteCommand(async (signal) => {
42278
+ const { watchRun: watchRun2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42279
+ return watchRun2({ id, signal, ...cmdOpts.on ? { remote: cmdOpts.on } : {} });
42280
+ }));
42281
+ runsCommand.command("cancel").description("Stop a running task").argument("<run>", "run ID or prefix").option("--on <server>", "server the run is on").action((id, cmdOpts) => runRemoteCommand(async () => {
42282
+ const { cancelRun: cancelRun2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42283
+ await cancelRun2({ id, ...cmdOpts.on ? { remote: cmdOpts.on } : {} });
42284
+ }));
42285
+ program.command("pull").description("Check out a run's branch in this repository").argument("<run>", "run ID or prefix").option("--on <server>", "server the run is on").action((id, cmdOpts) => runRemoteCommand(async () => {
42286
+ const { pullRun: pullRun2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42287
+ await pullRun2({ id, cwd: process.cwd(), ...cmdOpts.on ? { remote: cmdOpts.on } : {} });
42288
+ }));
41242
42289
  registerEvalCommand(program);
41243
42290
  var asterCommand = program.command("aster").description("Manage the Aster mobile API");
41244
42291
  asterCommand.command("setup").description("Configure Aster for one or more local Git workspaces").requiredOption("-w, --workspace <path>", "Local Git repository root (repeatable)", (value, previous = []) => [...previous, value]).option("--json", "Print the selected workspace IDs as JSON").action(async (options) => {
@@ -41620,11 +42667,16 @@ Open: ${step.url}`);
41620
42667
  })
41621
42668
  );
41622
42669
  program.command("serve").description("Start a headless HTTP server for API access and CI integration").option("--port <n>", "port to listen on", (v) => parseInt(v, 10), 4096).option("--hostname <host>", "hostname to listen on", "127.0.0.1").action(async (cmdOpts) => {
41623
- const cfg = await loadConfig();
41624
- if (!cfg) {
42670
+ const { resolveServeConfig: resolveServeConfig2 } = await Promise.resolve().then(() => (init_serve_config(), serve_config_exports));
42671
+ const resolved = resolveServeConfig2(await loadConfig());
42672
+ if (!resolved) {
41625
42673
  console.error(`autopilot serve: ${await noCredentialsMessage()}`);
41626
42674
  process.exit(2);
41627
42675
  }
42676
+ if (resolved.asterOnly) {
42677
+ console.error("autopilot serve: no provider key on this host; serving the Aster API, whose cells reach the model through Hotcell's gateway.");
42678
+ }
42679
+ const cfg = resolved.config;
41628
42680
  await initOtel();
41629
42681
  await ensureModelCatalog(cfg);
41630
42682
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
@@ -41639,7 +42691,9 @@ program.command("serve").description("Start a headless HTTP server for API acces
41639
42691
  process.exit(1);
41640
42692
  }
41641
42693
  });
41642
- program.action(async () => {
42694
+ var positionalTask = [];
42695
+ program.action(async (task = []) => {
42696
+ positionalTask = task;
41643
42697
  await main();
41644
42698
  const { runShutdownTasks: runShutdownTasks2 } = await Promise.resolve().then(() => (init_exit(), exit_exports));
41645
42699
  await runShutdownTasks2();
@@ -41654,10 +42708,43 @@ async function initOtel() {
41654
42708
  onShutdown2("otel-flush", shutdownOtelSink2, SHUTDOWN_PHASE2.flush);
41655
42709
  }
41656
42710
  }
42711
+ async function runRemoteTask() {
42712
+ if (opts.on === void 0) {
42713
+ console.error(`autopilot: unknown command "${positionalTask[0]}". To run a task here use -p "\u2026"; on a server, --on <server> "\u2026".`);
42714
+ process.exit(2);
42715
+ }
42716
+ if (opts.local) {
42717
+ console.error("autopilot: --on and --local can't be combined.");
42718
+ process.exit(2);
42719
+ }
42720
+ const task = (positionalTask.join(" ") || opts.print || "").trim();
42721
+ if (!task) {
42722
+ console.error(`autopilot: --on needs a task, e.g. autopilot --on ${opts.on} "fix the flaky checkout test"`);
42723
+ process.exit(2);
42724
+ }
42725
+ const cfg = await loadConfig().catch(() => null);
42726
+ await runRemoteCommand(async (signal) => {
42727
+ const { runOnRemote: runOnRemote2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42728
+ return runOnRemote2({
42729
+ remote: opts.on,
42730
+ task,
42731
+ cwd: opts.dir ?? process.cwd(),
42732
+ signal,
42733
+ detach: !!opts.detach,
42734
+ ...opts.model ? { model: opts.model } : {},
42735
+ ...cfg?.model ? { preferredModel: cfg.model } : {},
42736
+ ...opts.repo ? { repository: opts.repo } : {}
42737
+ });
42738
+ });
42739
+ }
41657
42740
  async function main() {
41658
42741
  await initOtel();
41659
42742
  const { installExitHandlers: installExitHandlers2 } = await Promise.resolve().then(() => (init_exit(), exit_exports));
41660
42743
  installExitHandlers2({ crashes: true });
42744
+ if (opts.on !== void 0 || positionalTask.length > 0) {
42745
+ await runRemoteTask();
42746
+ return;
42747
+ }
41661
42748
  const globalCfg = await loadConfig();
41662
42749
  const updateResult = await checkForUpdate();
41663
42750
  let cfg = globalCfg;
@@ -41836,12 +42923,12 @@ function requireModel(model) {
41836
42923
  process.exit(2);
41837
42924
  }
41838
42925
  async function noCredentialsMessage() {
41839
- const { readFile: readFile40 } = await import("fs/promises");
42926
+ const { readFile: readFile42 } = await import("fs/promises");
41840
42927
  const { configPath: configPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
41841
42928
  const { resolveActiveProvider: resolveActiveProvider2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
41842
42929
  let persisted = {};
41843
42930
  try {
41844
- persisted = JSON.parse(await readFile40(configPath2(), "utf8"));
42931
+ persisted = JSON.parse(await readFile42(configPath2(), "utf8"));
41845
42932
  } catch {
41846
42933
  }
41847
42934
  const { id, preset } = resolveActiveProvider2(persisted);
@@ -41869,11 +42956,11 @@ async function promptHidden(question) {
41869
42956
  }
41870
42957
  });
41871
42958
  const rl = createInterface4({ input: process.stdin, output, terminal: true });
41872
- return new Promise((resolve21) => {
42959
+ return new Promise((resolve22) => {
41873
42960
  rl.question(question, (answer) => {
41874
42961
  rl.close();
41875
42962
  process.stdout.write("\n");
41876
- resolve21(answer);
42963
+ resolve22(answer);
41877
42964
  });
41878
42965
  muted = true;
41879
42966
  });