autopilot-ai 2.1.0 → 2.2.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,661 @@ 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
+ const renderer = new EventRenderer(io);
23173
+ let outcome;
23174
+ try {
23175
+ if (replayUntil === void 0 || replayUntil > cursor) {
23176
+ for await (const event of client.events(conversationId, cursor, controller.signal)) {
23177
+ renderer.render(event);
23178
+ if (RUN_EVENT_TYPES.has(event.type)) outcome = event.type;
23179
+ if (replayUntil === void 0 ? RUN_EVENT_TYPES.has(event.type) : event.id >= replayUntil) break;
23180
+ }
23181
+ }
23182
+ if (replayUntil !== void 0 && !controller.signal.aborted) outcome ??= "completed";
23183
+ } finally {
23184
+ signal?.removeEventListener("abort", stop);
23185
+ controller.abort();
23186
+ }
23187
+ renderer.finish();
23188
+ const shortId = conversationId.slice(0, 8);
23189
+ if (!outcome) {
23190
+ io.err(`
23191
+ Detached from ${shortId}; it keeps running on ${remote.name}. Follow it with: autopilot runs watch ${shortId}
23192
+ `);
23193
+ return 0;
23194
+ }
23195
+ const final = await client.getConversation(conversationId);
23196
+ if (outcome === "completed") {
23197
+ io.err(`
23198
+ \u2713 ${shortId} completed${final.pullRequestURL ? `: ${final.pullRequestURL}` : " (no changes, so no pull request)"}
23199
+ `);
23200
+ if (final.pullRequestURL) io.err(` Check it out locally: autopilot pull ${shortId}
23201
+ `);
23202
+ return 0;
23203
+ }
23204
+ io.err(`
23205
+ \u2717 ${shortId} ${outcome}
23206
+ `);
23207
+ return 1;
23208
+ }
23209
+ async function findRun(id, remoteName) {
23210
+ const needle = id.trim().toLowerCase();
23211
+ if (!/^[0-9a-f-]{4,36}$/.test(needle)) throw new Error("Run IDs are the hex IDs `autopilot runs` shows");
23212
+ const remotes = remoteName ? [await resolveRemote(remoteName)] : await loadRemotes();
23213
+ const matches = [];
23214
+ for (const remote of remotes) {
23215
+ const runs = await new RemoteClient(remote.url, remote.token).listConversations(200).catch(() => []);
23216
+ for (const run of runs) if (run.conversationId.startsWith(needle)) matches.push({ remote, run });
23217
+ }
23218
+ if (matches.length === 0) throw new Error(`No run ${id} on ${remoteName ?? "any paired server"}`);
23219
+ if (matches.length > 1) throw new Error(`${id} matches several runs; use more characters`);
23220
+ return matches[0];
23221
+ }
23222
+ function pickModel(models, requested, preferred) {
23223
+ if (requested) {
23224
+ if (!models.includes(requested)) throw new Error(`The server doesn't offer ${requested}`);
23225
+ return requested;
23226
+ }
23227
+ if (preferred && models.includes(preferred)) return preferred;
23228
+ const first = models[0];
23229
+ if (!first) throw new Error("The server offers no models");
23230
+ return first;
23231
+ }
23232
+ function clientName() {
23233
+ const host = hostname().replace(/\.local$/, "").replace(/[^\p{L}\p{N} ._'()@-]/gu, "-").slice(0, 40) || "laptop";
23234
+ return `${host} (autopilot)`;
23235
+ }
23236
+ function oneLine(text3, max) {
23237
+ const flat = text3.replace(/\s+/g, " ").trim();
23238
+ return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
23239
+ }
23240
+ function describeError(error) {
23241
+ if (error instanceof RemoteApiError) return `${error.message} (${error.code})`;
23242
+ return error instanceof Error ? error.message : String(error);
23243
+ }
23244
+ var execFileAsync7, RUN_EVENT_TYPES, processIO, EventRenderer;
23245
+ var init_cli2 = __esm({
23246
+ "src/remote/cli.ts"() {
23247
+ "use strict";
23248
+ init_open_browser();
23249
+ init_client3();
23250
+ init_config4();
23251
+ init_git();
23252
+ execFileAsync7 = promisify9(execFile10);
23253
+ RUN_EVENT_TYPES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "interrupted"]);
23254
+ processIO = {
23255
+ out: (text3) => process.stdout.write(text3),
23256
+ err: (text3) => process.stderr.write(text3)
23257
+ };
23258
+ EventRenderer = class {
23259
+ constructor(io) {
23260
+ this.io = io;
23261
+ }
23262
+ io;
23263
+ midLine = false;
23264
+ render(event) {
23265
+ const data = event.data;
23266
+ if (event.type === "assistant.delta") {
23267
+ const text3 = typeof data.text === "string" ? data.text : typeof data.delta === "string" ? data.delta : "";
23268
+ if (!text3) return;
23269
+ this.io.out(text3);
23270
+ this.midLine = !text3.endsWith("\n");
23271
+ return;
23272
+ }
23273
+ if (event.type === "tool.activity" && data.activity === "started") {
23274
+ const input = typeof data.input === "string" ? ` ${oneLine(data.input, 120)}` : "";
23275
+ this.line(` \u203A ${String(data.tool ?? "tool")}${input}`);
23276
+ return;
23277
+ }
23278
+ if (event.type === "tool.activity" && data.activity === "failed") {
23279
+ this.line(` \u2717 ${String(data.tool ?? "tool")} failed`);
23280
+ return;
23281
+ }
23282
+ if (event.type === "checkpoint.created" && typeof data.message === "string") {
23283
+ this.line(` \u25CF ${oneLine(data.message, 100)}`);
23284
+ }
23285
+ }
23286
+ finish() {
23287
+ if (this.midLine) this.io.out("\n");
23288
+ this.midLine = false;
23289
+ }
23290
+ line(text3) {
23291
+ this.finish();
23292
+ this.io.out(`${text3}
23293
+ `);
23294
+ }
23295
+ };
23296
+ }
23297
+ });
23298
+
22380
23299
  // src/server/aster-workspaces.ts
22381
23300
  var aster_workspaces_exports = {};
22382
23301
  __export(aster_workspaces_exports, {
@@ -22387,23 +23306,23 @@ __export(aster_workspaces_exports, {
22387
23306
  resolveAsterServerConfigPath: () => resolveAsterServerConfigPath
22388
23307
  });
22389
23308
  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";
23309
+ import { readFile as readFile32, realpath as realpath5, stat as stat11 } from "fs/promises";
23310
+ import { homedir as homedir28 } from "os";
23311
+ import { isAbsolute as isAbsolute5, join as join46, relative as relative8, resolve as resolve14, sep as sep4 } from "path";
22393
23312
  function localAsterConfigPath(env = process.env) {
22394
- const configHome2 = env.XDG_CONFIG_HOME?.trim() || join44(homedir27(), ".config");
22395
- return resolve13(configHome2, "autopilot", "aster.json");
23313
+ const configHome2 = env.XDG_CONFIG_HOME?.trim() || join46(homedir28(), ".config");
23314
+ return resolve14(configHome2, "autopilot", "aster.json");
22396
23315
  }
22397
23316
  function resolveAsterServerConfigPath(env = process.env) {
22398
23317
  const configuredPath = env.AUTOPILOT_ASTER_CONFIG?.trim();
22399
- if (configuredPath) return resolve13(configuredPath);
23318
+ if (configuredPath) return resolve14(configuredPath);
22400
23319
  const userConfigPath = localAsterConfigPath(env);
22401
23320
  return existsSync11(userConfigPath) ? userConfigPath : SYSTEM_ASTER_CONFIG_PATH;
22402
23321
  }
22403
23322
  async function loadAsterServerConfig(configPath2 = resolveAsterServerConfigPath()) {
22404
23323
  let raw;
22405
23324
  try {
22406
- raw = JSON.parse(await readFile30(configPath2, "utf8"));
23325
+ raw = JSON.parse(await readFile32(configPath2, "utf8"));
22407
23326
  } catch {
22408
23327
  throw new AsterConfigError();
22409
23328
  }
@@ -22440,7 +23359,7 @@ async function loadAsterServerConfig(configPath2 = resolveAsterServerConfigPath(
22440
23359
  let rootPath;
22441
23360
  try {
22442
23361
  rootPath = await realpath5(workspace.rootPath);
22443
- if (!(await stat10(rootPath)).isDirectory()) throw new Error("not a directory");
23362
+ if (!(await stat11(rootPath)).isDirectory()) throw new Error("not a directory");
22444
23363
  } catch {
22445
23364
  throw new AsterConfigError();
22446
23365
  }
@@ -22480,10 +23399,10 @@ __export(aster_setup_exports, {
22480
23399
  setupLocalAsterConfig: () => setupLocalAsterConfig
22481
23400
  });
22482
23401
  import { execFileSync as execFileSync3 } from "child_process";
22483
- import { randomUUID as randomUUID8 } from "crypto";
23402
+ import { randomUUID as randomUUID9 } from "crypto";
22484
23403
  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";
23404
+ import { chmod as chmod5, link, mkdir as mkdir19, realpath as realpath6, rm as rm7, stat as stat12, writeFile as writeFile17 } from "fs/promises";
23405
+ import { basename as basename9, dirname as dirname22, resolve as resolve15 } from "path";
22487
23406
  async function setupLocalAsterConfig(options) {
22488
23407
  if (options.workspacePaths.length === 0 || options.workspacePaths.length > 100) {
22489
23408
  throw new Error("provide between 1 and 100 Git workspace roots");
@@ -22495,7 +23414,7 @@ async function setupLocalAsterConfig(options) {
22495
23414
  const requestedWorkspaces = await createWorkspaceEntries(options.workspacePaths);
22496
23415
  const env = options.env ?? process.env;
22497
23416
  const requestedPath = options.configPath?.trim() || env.AUTOPILOT_ASTER_CONFIG?.trim() || localAsterConfigPath(env);
22498
- const path = resolve14(requestedPath);
23417
+ const path = resolve15(requestedPath);
22499
23418
  if (existsSync12(path)) {
22500
23419
  const config3 = await loadAsterServerConfig(path);
22501
23420
  const selectedWorkspaces = requestedWorkspaces.map((requested) => {
@@ -22511,21 +23430,21 @@ async function setupLocalAsterConfig(options) {
22511
23430
  models: models.length > 0 ? models : "openrouter",
22512
23431
  workspaces: requestedWorkspaces
22513
23432
  };
22514
- const directory = dirname21(path);
22515
- await mkdir18(directory, { recursive: true, mode: 448 });
22516
- await chmod4(directory, 448).catch(() => {
23433
+ const directory = dirname22(path);
23434
+ await mkdir19(directory, { recursive: true, mode: 448 });
23435
+ await chmod5(directory, 448).catch(() => {
22517
23436
  });
22518
- const temporaryPath = `${path}.tmp-${randomUUID8()}`;
23437
+ const temporaryPath = `${path}.tmp-${randomUUID9()}`;
22519
23438
  try {
22520
- await writeFile16(temporaryPath, `${JSON.stringify(config2, null, 2)}
23439
+ await writeFile17(temporaryPath, `${JSON.stringify(config2, null, 2)}
22521
23440
  `, { encoding: "utf8", mode: 384, flag: "wx" });
22522
- await chmod4(temporaryPath, 384);
23441
+ await chmod5(temporaryPath, 384);
22523
23442
  const validated = await loadAsterServerConfig(temporaryPath);
22524
23443
  await link(temporaryPath, path);
22525
- await rm6(temporaryPath);
23444
+ await rm7(temporaryPath);
22526
23445
  return { path, config: validated, selectedWorkspaces: validated.workspaces };
22527
23446
  } catch (error) {
22528
- await rm6(temporaryPath, { force: true }).catch(() => {
23447
+ await rm7(temporaryPath, { force: true }).catch(() => {
22529
23448
  });
22530
23449
  if (isAlreadyExists(error)) {
22531
23450
  return setupLocalAsterConfig(options);
@@ -22537,11 +23456,11 @@ async function createWorkspaceEntries(paths) {
22537
23456
  const usedIds = /* @__PURE__ */ new Set();
22538
23457
  const workspaces = [];
22539
23458
  for (const input of paths) {
22540
- const requested = resolve14(input);
23459
+ const requested = resolve15(input);
22541
23460
  let root;
22542
23461
  try {
22543
23462
  root = await realpath6(requested);
22544
- if (!(await stat11(root)).isDirectory()) throw new Error("not a directory");
23463
+ if (!(await stat12(root)).isDirectory()) throw new Error("not a directory");
22545
23464
  const output = execFileSync3("git", ["-C", root, "rev-parse", "--show-toplevel"], {
22546
23465
  encoding: "utf8",
22547
23466
  stdio: ["ignore", "pipe", "ignore"]
@@ -22588,13 +23507,13 @@ __export(aster_store_exports, {
22588
23507
  normalizeUserCode: () => normalizeUserCode
22589
23508
  });
22590
23509
  import Database4 from "better-sqlite3";
22591
- import { createHash as createHash7, randomBytes as randomBytes3, randomInt, randomUUID as randomUUID9 } from "crypto";
23510
+ import { createHash as createHash7, randomBytes as randomBytes3, randomInt, randomUUID as randomUUID10 } from "crypto";
22592
23511
  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";
23512
+ import { homedir as homedir29 } from "os";
23513
+ import { dirname as dirname23, join as join47 } from "path";
22595
23514
  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");
23515
+ const root = process.env.XDG_STATE_HOME || join47(homedir29(), ".local", "state");
23516
+ return process.env.AUTOPILOT_ASTER_DB || join47(root, "autopilot", "aster.db");
22598
23517
  }
22599
23518
  function hashToken(token) {
22600
23519
  return createHash7("sha256").update(token).digest("hex");
@@ -22681,9 +23600,9 @@ var init_aster_store = __esm({
22681
23600
  AsterStore = class {
22682
23601
  db;
22683
23602
  constructor(dbPath = defaultAsterDbPath()) {
22684
- mkdirSync10(dirname22(dbPath), { recursive: true, mode: 448 });
23603
+ mkdirSync10(dirname23(dbPath), { recursive: true, mode: 448 });
22685
23604
  try {
22686
- chmodSync2(dirname22(dbPath), 448);
23605
+ chmodSync2(dirname23(dbPath), 448);
22687
23606
  } catch {
22688
23607
  }
22689
23608
  this.db = new Database4(dbPath);
@@ -22898,7 +23817,7 @@ var init_aster_store = __esm({
22898
23817
  const scopes = [...new Set(input.scopes)];
22899
23818
  if (scopes.length === 0 || scopes.some((scope) => !VALID_SCOPES.has(scope))) throw new Error("credential scopes are invalid");
22900
23819
  if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= Date.now()) throw new Error("credential expiry must be in the future");
22901
- const id = randomUUID9();
23820
+ const id = randomUUID10();
22902
23821
  const token = `aster_${randomBytes3(32).toString("base64url")}`;
22903
23822
  const now2 = Date.now();
22904
23823
  this.db.prepare(`INSERT INTO aster_credentials
@@ -22952,7 +23871,7 @@ var init_aster_store = __esm({
22952
23871
  }
22953
23872
  /** The owner's decision on a user code. An approval fixes the credential the client will receive. */
22954
23873
  resolveDeviceAuthorization(userCode, decision, grant, now2 = Date.now()) {
22955
- const resolve21 = this.db.transaction(() => {
23874
+ const resolve22 = this.db.transaction(() => {
22956
23875
  const row = this.db.prepare("SELECT * FROM aster_device_authorizations WHERE user_code = ?").get(normalizeUserCode(userCode));
22957
23876
  if (!row) return "not_found";
22958
23877
  if (row.status !== "pending") return "already_resolved";
@@ -22965,7 +23884,7 @@ var init_aster_store = __esm({
22965
23884
  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
23885
  return "approved";
22967
23886
  });
22968
- return resolve21.immediate();
23887
+ return resolve22.immediate();
22969
23888
  }
22970
23889
  /** The client's poll. An approved code is exchanged for its credential exactly once. */
22971
23890
  redeemDeviceAuthorization(deviceCode, now2 = Date.now()) {
@@ -23212,7 +24131,7 @@ var init_aster_store = __esm({
23212
24131
  }));
23213
24132
  }
23214
24133
  createApproval(input) {
23215
- const id = randomUUID9();
24134
+ const id = randomUUID10();
23216
24135
  const now2 = Date.now();
23217
24136
  this.db.prepare(`INSERT INTO aster_approvals
23218
24137
  (id, conversation_id, run_id, tool_name, explanation, arguments_json, status, expires_at, created_at)
@@ -23315,9 +24234,9 @@ async function handleAsterPairing(req, res, url, store) {
23315
24234
  const method = req.method ?? "GET";
23316
24235
  if (path === `${AUTH_PREFIX}/device` && method === "POST") {
23317
24236
  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 });
24237
+ const clientName2 = typeof body.clientName === "string" ? body.clientName.trim() : "Autopilot CLI";
24238
+ if (!CLIENT_NAME_RE.test(clientName2)) return sendError(res, 400, "invalid_client_name", "clientName must be 1-60 letters, digits, spaces, or ._'()@-");
24239
+ const started2 = store.createDeviceAuthorization({ clientName: clientName2, ttlMs: DEVICE_CODE_TTL_MS, maxPending: MAX_PENDING_DEVICE_CODES });
23321
24240
  if (!started2) return sendError(res, 429, "too_many_pending", "Too many pairings are waiting for approval; try again later");
23322
24241
  const verificationUri = `${publicBaseUrl(req)}${AUTH_PREFIX}/approve`;
23323
24242
  return json(res, 200, {
@@ -23464,9 +24383,9 @@ function notOwnerBody(code) {
23464
24383
  <p>You can also approve from the server directly:</p>
23465
24384
  <pre>autopilot aster pair approve ${escapeHtml2(code ?? "<code>")}</pre>`;
23466
24385
  }
23467
- function approvalForm(userCode, clientName) {
24386
+ function approvalForm(userCode, clientName2) {
23468
24387
  return `
23469
- <p><strong>${escapeHtml2(clientName)}</strong> wants to use this server.</p>
24388
+ <p><strong>${escapeHtml2(clientName2)}</strong> wants to use this server.</p>
23470
24389
  <p class="code">${escapeHtml2(userCode)}</p>
23471
24390
  <p class="muted">Approve only if this matches the code shown on your device.</p>
23472
24391
  <form method="post" action="${AUTH_PREFIX}/approve">
@@ -23587,7 +24506,7 @@ __export(keys_exports, {
23587
24506
  saveProviderKey: () => saveProviderKey,
23588
24507
  switchProvider: () => switchProvider
23589
24508
  });
23590
- import { readFile as readFile31 } from "fs/promises";
24509
+ import { readFile as readFile33 } from "fs/promises";
23591
24510
  function looksLikeProviderKey(id, key) {
23592
24511
  if (id === "openrouter") return looksLikeOpenRouterKey(key);
23593
24512
  return key.length >= 8 && !/\s/.test(key);
@@ -23670,7 +24589,7 @@ async function checkProviderKey(id, key, baseUrl, fetchImpl2 = fetch) {
23670
24589
  }
23671
24590
  async function persistedProviders() {
23672
24591
  try {
23673
- const raw = JSON.parse(await readFile31(configPath(), "utf8"));
24592
+ const raw = JSON.parse(await readFile33(configPath(), "utf8"));
23674
24593
  return raw.providers ?? {};
23675
24594
  } catch {
23676
24595
  return {};
@@ -23948,7 +24867,7 @@ __export(github_auth_exports, {
23948
24867
  authGitHubForTui: () => authGitHubForTui
23949
24868
  });
23950
24869
  function sleep4(ms) {
23951
- return new Promise((resolve21) => setTimeout(resolve21, ms));
24870
+ return new Promise((resolve22) => setTimeout(resolve22, ms));
23952
24871
  }
23953
24872
  async function* authGitHubForTui() {
23954
24873
  yield { message: "Starting GitHub OAuth device flow..." };
@@ -24955,13 +25874,13 @@ var init_openapi = __esm({
24955
25874
  });
24956
25875
 
24957
25876
  // 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";
25877
+ import { execFile as execFile11 } from "child_process";
25878
+ import { access as access2, chmod as chmod6, mkdir as mkdir20, realpath as realpath7 } from "fs/promises";
25879
+ import { homedir as homedir30 } from "os";
25880
+ import { isAbsolute as isAbsolute6, join as join48, parse, relative as relative9, resolve as resolve16, sep as sep5 } from "path";
24962
25881
  function defaultWorktreesDir() {
24963
- const dataHome = process.env.XDG_DATA_HOME || join46(homedir29(), ".local", "share");
24964
- return join46(dataHome, "autopilot", "worktrees");
25882
+ const dataHome = process.env.XDG_DATA_HOME || join48(homedir30(), ".local", "share");
25883
+ return join48(dataHome, "autopilot", "worktrees");
24965
25884
  }
24966
25885
  function isOutside(path) {
24967
25886
  return path === ".." || path.startsWith(`..${sep5}`) || isAbsolute6(path);
@@ -24972,7 +25891,7 @@ function isWithin3(parent, candidate) {
24972
25891
  }
24973
25892
  function runGit(args, cwd) {
24974
25893
  return new Promise((resolveOutput, reject) => {
24975
- execFile9("git", args, { cwd, encoding: "utf8", timeout: 3e4, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
25894
+ execFile11("git", args, { cwd, encoding: "utf8", timeout: 3e4, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
24976
25895
  if (error) {
24977
25896
  const detail = stderr.trim();
24978
25897
  reject(new Error(`git ${args[0]} failed${detail ? `: ${detail}` : ""}`, { cause: error }));
@@ -25002,17 +25921,17 @@ var init_worktrees = __esm({
25002
25921
  if (isOutside(sourceRelativePath)) {
25003
25922
  throw new Error("sourceCwd must be inside the Git repository");
25004
25923
  }
25005
- const configuredRoot = resolve15(this.rootDir);
25924
+ const configuredRoot = resolve16(this.rootDir);
25006
25925
  if (configuredRoot === parse(configuredRoot).root || isWithin3(repository, configuredRoot)) {
25007
25926
  throw new Error("worktree root must be outside the source repository and not the filesystem root");
25008
25927
  }
25009
- await mkdir19(configuredRoot, { recursive: true, mode: 448 });
25928
+ await mkdir20(configuredRoot, { recursive: true, mode: 448 });
25010
25929
  const worktreesRoot = await realpath7(configuredRoot);
25011
25930
  if (isWithin3(repository, worktreesRoot)) {
25012
25931
  throw new Error("worktree root must be outside the source repository");
25013
25932
  }
25014
- await chmod5(worktreesRoot, 448);
25015
- const worktreePath = join46(worktreesRoot, runId);
25933
+ await chmod6(worktreesRoot, 448);
25934
+ const worktreePath = join48(worktreesRoot, runId);
25016
25935
  try {
25017
25936
  await access2(worktreePath);
25018
25937
  throw new Error(`worktree path already exists for run ${runId}`);
@@ -25026,7 +25945,7 @@ var init_worktrees = __esm({
25026
25945
  repositoryRoot: repository,
25027
25946
  sourceCwd: source,
25028
25947
  worktreePath,
25029
- cwd: resolve15(worktreePath, sourceRelativePath),
25948
+ cwd: resolve16(worktreePath, sourceRelativePath),
25030
25949
  branch
25031
25950
  };
25032
25951
  }
@@ -25108,10 +26027,10 @@ var init_wake_scheduler = __esm({
25108
26027
  scheduleNextTick() {
25109
26028
  if (!this.running || this.disposed) return;
25110
26029
  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()));
26030
+ const delay2 = nextAt === null ? IDLE_RECHECK_MS : Math.max(0, Math.min(MAX_NODE_TIMEOUT_MS, nextAt - Date.now()));
25112
26031
  this.timer = setTimeout(() => {
25113
26032
  void this.tick();
25114
- }, delay);
26033
+ }, delay2);
25115
26034
  this.timer.unref();
25116
26035
  }
25117
26036
  async processTimer(timer2, now2) {
@@ -25473,9 +26392,9 @@ var init_aster_tools = __esm({
25473
26392
 
25474
26393
  // src/server/aster-memory.ts
25475
26394
  import Database5 from "better-sqlite3";
25476
- import { randomUUID as randomUUID10 } from "crypto";
26395
+ import { randomUUID as randomUUID11 } from "crypto";
25477
26396
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync11 } from "fs";
25478
- import { dirname as dirname23 } from "path";
26397
+ import { dirname as dirname24 } from "path";
25479
26398
  function rowToItem(row) {
25480
26399
  return {
25481
26400
  id: row.id,
@@ -25528,7 +26447,7 @@ var init_aster_memory = __esm({
25528
26447
  AsterMemory = class {
25529
26448
  constructor(dbPath = defaultAsterDbPath(), options = {}) {
25530
26449
  this.options = options;
25531
- mkdirSync11(dirname23(dbPath), { recursive: true, mode: 448 });
26450
+ mkdirSync11(dirname24(dbPath), { recursive: true, mode: 448 });
25532
26451
  this.db = new Database5(dbPath);
25533
26452
  this.db.pragma("journal_mode = WAL");
25534
26453
  this.db.pragma("busy_timeout = 5000");
@@ -25592,7 +26511,7 @@ var init_aster_memory = __esm({
25592
26511
  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
26512
  return { item: this.get(existing.id), change: "updated" };
25594
26513
  }
25595
- const id = randomUUID10();
26514
+ const id = randomUUID11();
25596
26515
  this.db.prepare(`INSERT INTO aster_memories (id, kind, key, title, content, pinned, source_conversation_id, created_at, updated_at)
25597
26516
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.kind, input.key ?? null, title, content, input.pinned ? 1 : 0, input.sourceConversationId ?? null, now2, now2);
25598
26517
  return { item: this.get(id), change: "added" };
@@ -25780,14 +26699,14 @@ function parseReceivePackCommands(data) {
25780
26699
  function isSafeBranch(branch) {
25781
26700
  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
26701
  }
25783
- function upstreamHeaders(req, token, git4) {
26702
+ function upstreamHeaders(req, token, git5) {
25784
26703
  const headers = {};
25785
26704
  for (const name of FORWARDED_REQUEST_HEADERS) {
25786
26705
  const value = req.headers[name];
25787
26706
  if (typeof value === "string") headers[name] = value;
25788
26707
  }
25789
26708
  headers["user-agent"] ??= "autopilot-aster";
25790
- headers.authorization = git4 ? "Basic " + Buffer.from(`x-access-token:${token}`).toString("base64") : `Bearer ${token}`;
26709
+ headers.authorization = git5 ? "Basic " + Buffer.from(`x-access-token:${token}`).toString("base64") : `Bearer ${token}`;
25791
26710
  return headers;
25792
26711
  }
25793
26712
  function pipeResponse(upstreamRes, res) {
@@ -25837,9 +26756,9 @@ var init_aster_github_proxy = __esm({
25837
26756
  gitUpstream;
25838
26757
  async listen(port, host) {
25839
26758
  this.server = createServer2((req, res) => this.handle(req, res));
25840
- await new Promise((resolve21, reject) => {
26759
+ await new Promise((resolve22, reject) => {
25841
26760
  this.server.once("error", reject);
25842
- this.server.listen(port, host, () => resolve21());
26761
+ this.server.listen(port, host, () => resolve22());
25843
26762
  });
25844
26763
  const address = this.server.address();
25845
26764
  return typeof address === "object" && address ? address.port : port;
@@ -25850,8 +26769,8 @@ var init_aster_github_proxy = __esm({
25850
26769
  }
25851
26770
  handle(req, res, pathOverride) {
25852
26771
  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");
26772
+ const git5 = url.pathname.match(GIT_PATH_RE);
26773
+ if (!git5) return deny(res, 404, "Only Git Smart HTTP is available through this proxy");
25855
26774
  const grantToken = extractToken(req);
25856
26775
  if (!grantToken) {
25857
26776
  res.writeHead(401, { "www-authenticate": 'Basic realm="aster-github"', "content-type": "text/plain" });
@@ -25860,7 +26779,7 @@ var init_aster_github_proxy = __esm({
25860
26779
  }
25861
26780
  const grant = this.options.resolveGrant(grantToken);
25862
26781
  if (!grant) return deny(res, 403, "This repository grant is invalid or was revoked");
25863
- const [, owner, name, service] = git4;
26782
+ const [, owner, name, service] = git5;
25864
26783
  if (`${owner}/${name}`.toLowerCase() !== grant.repository.toLowerCase()) {
25865
26784
  return deny(res, 403, `This conversation can only access ${grant.repository}`);
25866
26785
  }
@@ -26021,7 +26940,7 @@ var init_aster_push = __esm({
26021
26940
  };
26022
26941
  if (this.options.deliver) return this.options.deliver({ host, token, headers, payload });
26023
26942
  const session = this.session(host);
26024
- return await new Promise((resolve21, reject) => {
26943
+ return await new Promise((resolve22, reject) => {
26025
26944
  const stream = session.request({ ":method": "POST", ":path": `/3/device/${token}`, ...headers, "content-type": "application/json" });
26026
26945
  let status = 0;
26027
26946
  stream.setTimeout(15e3, () => stream.close());
@@ -26029,7 +26948,7 @@ var init_aster_push = __esm({
26029
26948
  status = Number(responseHeaders[":status"]) || 0;
26030
26949
  });
26031
26950
  stream.on("error", reject);
26032
- stream.on("close", () => resolve21(status));
26951
+ stream.on("close", () => resolve22(status));
26033
26952
  stream.resume();
26034
26953
  stream.end(payload);
26035
26954
  });
@@ -26060,9 +26979,9 @@ var init_aster_push = __esm({
26060
26979
  });
26061
26980
 
26062
26981
  // src/server/aster-api.ts
26063
- import { randomUUID as randomUUID11 } from "crypto";
26982
+ import { randomUUID as randomUUID12 } from "crypto";
26064
26983
  import { unlink as unlink3 } from "fs/promises";
26065
- import { resolve as resolve16 } from "path";
26984
+ import { resolve as resolve17 } from "path";
26066
26985
  function parseCursor(header, query) {
26067
26986
  const value = (Array.isArray(header) ? header[0] : header) ?? query ?? "0";
26068
26987
  if (!/^\d{1,16}$/.test(value)) return void 0;
@@ -26164,7 +27083,7 @@ function json2(res, status, data) {
26164
27083
  res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
26165
27084
  res.end(JSON.stringify(data));
26166
27085
  }
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;
27086
+ 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
27087
  var init_aster_api = __esm({
26169
27088
  "src/server/aster-api.ts"() {
26170
27089
  "use strict";
@@ -26190,7 +27109,7 @@ var init_aster_api = __esm({
26190
27109
  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
27110
  ALLOWED_ASTER_TOOLS = ["read", "write", "edit"];
26192
27111
  BASE_COMMIT_RE = /^[0-9a-f]{40}$/;
26193
- MAX_PATCH_CHARS = 3 * 1024 * 1024;
27112
+ MAX_PATCH_CHARS2 = 3 * 1024 * 1024;
26194
27113
  MAX_LISTED_CONVERSATIONS = 200;
26195
27114
  AsterApi = class {
26196
27115
  store;
@@ -26310,7 +27229,7 @@ var init_aster_api = __esm({
26310
27229
  if (body.baseCommit !== void 0 && (typeof body.baseCommit !== "string" || !BASE_COMMIT_RE.test(body.baseCommit))) {
26311
27230
  return sendError2(res, 400, "invalid_base_commit", "baseCommit must be a full 40-character commit SHA");
26312
27231
  }
26313
- if (body.patch !== void 0 && (typeof body.patch !== "string" || body.patch.length > MAX_PATCH_CHARS)) {
27232
+ if (body.patch !== void 0 && (typeof body.patch !== "string" || body.patch.length > MAX_PATCH_CHARS2)) {
26314
27233
  return sendError2(res, 400, "invalid_patch", "patch must be a git diff of at most 3 MiB");
26315
27234
  }
26316
27235
  if (typeof body.patch === "string" && containsLikelyProviderSecret(body.patch)) {
@@ -26675,8 +27594,8 @@ var init_aster_api = __esm({
26675
27594
  };
26676
27595
  }
26677
27596
  async createConversation(res, store, principal, workspace, model, idempotencyKey, kind, repository, changes) {
26678
- const candidateId = randomUUID11();
26679
- const candidateSessionId = randomUUID11();
27597
+ const candidateId = randomUUID12();
27598
+ const candidateSessionId = randomUUID12();
26680
27599
  const reservation = store.reserveCellConversation({
26681
27600
  conversationId: candidateId,
26682
27601
  credentialId: principal.credentialId,
@@ -26817,12 +27736,12 @@ var init_aster_api = __esm({
26817
27736
  sendError2(res, 409, "conversation_cell_unavailable", "Conversation cell is not available");
26818
27737
  return;
26819
27738
  }
26820
- const runId = randomUUID11();
27739
+ const runId = randomUUID12();
26821
27740
  if (!idempotent && !store.beginTurn(conversation.id, runId)) {
26822
27741
  sendError2(res, 409, "conversation_busy", "A turn is already active for this conversation");
26823
27742
  return;
26824
27743
  }
26825
- const sessionPath = resolve16(sessionsDir(), conversation.sessionId + ".json");
27744
+ const sessionPath = resolve17(sessionsDir(), conversation.sessionId + ".json");
26826
27745
  let sessionFile;
26827
27746
  try {
26828
27747
  sessionFile = await loadSession(sessionPath);
@@ -27132,7 +28051,7 @@ var init_aster_api = __esm({
27132
28051
  }
27133
28052
  try {
27134
28053
  if (mapping.cellId) await this.runtime.destroyCell(mapping.cellId);
27135
- await unlink3(resolve16(sessionsDir(), mapping.sessionId + ".json")).catch(() => {
28054
+ await unlink3(resolve17(sessionsDir(), mapping.sessionId + ".json")).catch(() => {
27136
28055
  });
27137
28056
  store.setCellStatus(conversation.id, "destroyed");
27138
28057
  store.setConversationStatus(conversation.id, "destroyed");
@@ -27179,7 +28098,7 @@ var init_aster_api = __esm({
27179
28098
  return void 0;
27180
28099
  }
27181
28100
  try {
27182
- const sessionPath = resolve16(sessionsDir(), conversation.sessionId + ".json");
28101
+ const sessionPath = resolve17(sessionsDir(), conversation.sessionId + ".json");
27183
28102
  const sessionFile = await loadSession(sessionPath);
27184
28103
  sessionFile.model = model;
27185
28104
  await saveSession(sessionFile);
@@ -27318,10 +28237,10 @@ data: ${JSON.stringify(event.data)}
27318
28237
  });
27319
28238
 
27320
28239
  // 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";
28240
+ import { execFile as execFile12 } from "child_process";
28241
+ import { readFile as readFile34 } from "fs/promises";
28242
+ import { promisify as promisify10 } from "util";
28243
+ import { randomUUID as randomUUID13 } from "crypto";
27325
28244
  async function writeCellFileChunked(cell, path, content) {
27326
28245
  if (Buffer.byteLength(content, "utf8") <= CELL_FILE_WRITE_CHUNK_CHARS) {
27327
28246
  await cell.writeFile(path, content, { mode: "0600" });
@@ -27349,14 +28268,14 @@ async function writeCellFileChunked(cell, path, content) {
27349
28268
  async function readBundledCellSdkBundle() {
27350
28269
  for (const url of [new URL("./sdk/index.js", import.meta.url), new URL("../sdk/index.js", import.meta.url)]) {
27351
28270
  try {
27352
- return await readFile32(url, "utf8");
28271
+ return await readFile34(url, "utf8");
27353
28272
  } catch {
27354
28273
  }
27355
28274
  }
27356
28275
  throw new Error("cell_runtime_bundle_unavailable");
27357
28276
  }
27358
28277
  async function defaultArchiveWorkspace(workspaceRoot) {
27359
- const { stdout } = await execFileAsync6("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
28278
+ const { stdout } = await execFileAsync8("git", ["-C", workspaceRoot, "archive", "--format=tar", "HEAD"], {
27360
28279
  encoding: "buffer",
27361
28280
  maxBuffer: 512 * 1024 * 1024
27362
28281
  });
@@ -27402,15 +28321,15 @@ function boundedToolText(text3, limit) {
27402
28321
  ${redacted.slice(-half)}`;
27403
28322
  }
27404
28323
  function sleep5(ms) {
27405
- return new Promise((resolve21) => setTimeout(resolve21, ms));
28324
+ return new Promise((resolve22) => setTimeout(resolve22, ms));
27406
28325
  }
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;
28326
+ 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
28327
  var init_aster_cell_runtime = __esm({
27409
28328
  "src/server/aster-cell-runtime.ts"() {
27410
28329
  "use strict";
27411
28330
  init_version();
27412
28331
  init_aster_tools();
27413
- execFileAsync6 = promisify8(execFile10);
28332
+ execFileAsync8 = promisify10(execFile12);
27414
28333
  CELL_WORKSPACE = "/workspace";
27415
28334
  CELL_STATE_DIR = "/workspace/.aster";
27416
28335
  CHECKPOINT_MESSAGE_FILE = `${CELL_STATE_DIR}/checkpoint-message`;
@@ -27519,7 +28438,7 @@ var init_aster_cell_runtime = __esm({
27519
28438
  await this.installRuntime(cell);
27520
28439
  await this.ensureBridge(cell);
27521
28440
  await this.control(cell, "POST", "/rpc", {
27522
- id: randomUUID12(),
28441
+ id: randomUUID13(),
27523
28442
  type: "new_session",
27524
28443
  sessionId: input.sessionId,
27525
28444
  config: { model: input.model, cwd: CELL_WORKSPACE }
@@ -27538,7 +28457,7 @@ var init_aster_cell_runtime = __esm({
27538
28457
  /** Switch the cell session's model between turns. The bridge persists it for session restore. */
27539
28458
  async setModel(cellId, model) {
27540
28459
  await this.ensureReady(cellId);
27541
- await this.controlById(cellId, "POST", "/rpc", { id: randomUUID12(), type: "set_model", modelId: model });
28460
+ await this.controlById(cellId, "POST", "/rpc", { id: randomUUID13(), type: "set_model", modelId: model });
27542
28461
  }
27543
28462
  /** Reinstall the runtime if the cell's image lost it and restart the bridge if it isn't running. */
27544
28463
  async ensureReady(cellId) {
@@ -27636,7 +28555,7 @@ var init_aster_cell_runtime = __esm({
27636
28555
  const active = this.activeRuns.get(runId);
27637
28556
  if (!active) return;
27638
28557
  active.stop();
27639
- void this.controlById(active.cellId, "POST", "/rpc", { id: randomUUID12(), type: "abort" }).catch(() => {
28558
+ void this.controlById(active.cellId, "POST", "/rpc", { id: randomUUID13(), type: "abort" }).catch(() => {
27640
28559
  });
27641
28560
  }
27642
28561
  async runTurn(turn, options = {}) {
@@ -27736,7 +28655,7 @@ ${turn.userText}` : turn.userText;
27736
28655
  args: event.args ?? {}
27737
28656
  });
27738
28657
  await this.controlById(cellId, "POST", "/rpc", {
27739
- id: randomUUID12(),
28658
+ id: randomUUID13(),
27740
28659
  type: "resolve_permission",
27741
28660
  requestId: event.requestId,
27742
28661
  decision
@@ -27917,9 +28836,9 @@ ${turn.userText}` : turn.userText;
27917
28836
  if (!isSafeArtifactPath(path)) throw new Error("invalid_artifact_path");
27918
28837
  const cell = await this.provider.getCell(cellId);
27919
28838
  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");
28839
+ const stat15 = await cell.exec(`[ -f ${file} ] && [ ! -L ${file} ] && stat -c %s -- ${file}`);
28840
+ if (stat15.exitCode !== 0) throw new Error("artifact_not_found");
28841
+ if (Number(stat15.stdout.trim()) > MAX_ARTIFACT_BYTES) throw new Error("artifact_too_large");
27923
28842
  const encoded = await cell.exec(`base64 -w0 -- ${file}`);
27924
28843
  if (encoded.exitCode !== 0) throw new Error("artifact_not_found");
27925
28844
  return { data: Buffer.from(encoded.stdout.trim(), "base64"), mimeType: artifactMimeType(path), name: path.split("/").pop() ?? path };
@@ -27998,7 +28917,7 @@ ${text3}` : text3, { mode: "0600" });
27998
28917
  }
27999
28918
  }
28000
28919
  async control(cell, method, path, body) {
28001
- const requestId = randomUUID12();
28920
+ const requestId = randomUUID13();
28002
28921
  await cell.mkdir(CELL_CONTROL_DIR, { parents: true });
28003
28922
  await writeCellFileChunked(cell, `${CELL_CONTROL_DIR}/${requestId}.json`, JSON.stringify({ method, path, body }));
28004
28923
  const result2 = await cell.exec(`${CONTROL_RUNNER} ${requestId}`);
@@ -28214,11 +29133,11 @@ var init_hotcell_provider = __esm({
28214
29133
  });
28215
29134
 
28216
29135
  // src/server/routes.ts
28217
- import { randomUUID as randomUUID13 } from "crypto";
28218
- import { hostname } from "os";
29136
+ import { randomUUID as randomUUID14 } from "crypto";
29137
+ import { hostname as hostname2 } from "os";
28219
29138
  import { URL as URL2 } from "url";
28220
29139
  import { unlink as unlink4 } from "fs/promises";
28221
- import { resolve as resolve17 } from "path";
29140
+ import { resolve as resolve18 } from "path";
28222
29141
  function createHotcellAsterRuntime() {
28223
29142
  try {
28224
29143
  const idlePauseMs = Number(process.env.AUTOPILOT_ASTER_IDLE_PAUSE_MS ?? 15 * 60 * 1e3);
@@ -28282,6 +29201,9 @@ function json3(res, status, data) {
28282
29201
  function badRequest(res, message2) {
28283
29202
  json3(res, 400, { error: message2 });
28284
29203
  }
29204
+ function promptBlocked(res, reason) {
29205
+ json3(res, 403, { error: "prompt_blocked", reason, message: `prompt blocked by a UserPromptSubmit hook: ${reason}` });
29206
+ }
28285
29207
  function notFound(res, message2) {
28286
29208
  json3(res, 404, { error: message2 });
28287
29209
  }
@@ -28301,9 +29223,9 @@ async function resolveFiles2(filePatterns, cwd) {
28301
29223
  const resolved = /* @__PURE__ */ new Set();
28302
29224
  for (const pattern of filePatterns) {
28303
29225
  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));
29226
+ const stat15 = await import("fs/promises").then((m) => m.stat(resolve18(cwd, pattern)));
29227
+ if (stat15.isFile()) {
29228
+ resolved.add(resolve18(cwd, pattern));
28307
29229
  continue;
28308
29230
  }
28309
29231
  } catch {
@@ -28353,7 +29275,7 @@ function setupRoutes(config2, options = {}) {
28353
29275
  if (!run.sessionId) throw new Error("Cannot restore run without a session id: " + run.id);
28354
29276
  const existing = activeRuns.get(run.id) ?? activeSessions.get(run.sessionId);
28355
29277
  if (existing) return existing;
28356
- const sessionFile = await loadSession(resolve17(sessionsDir(), run.sessionId + ".json"));
29278
+ const sessionFile = await loadSession(resolve18(sessionsDir(), run.sessionId + ".json"));
28357
29279
  const allowedTools = new Set(run.allowedTools);
28358
29280
  allowedTools.add("wait_for");
28359
29281
  const tools = ALL_TOOLS.filter((tool) => allowedTools.has(tool.name));
@@ -28405,7 +29327,7 @@ function setupRoutes(config2, options = {}) {
28405
29327
  }
28406
29328
  });
28407
29329
  const notificationSettings = notificationSettingsFromConfig(config2);
28408
- const stopNotifications = notificationSettings ? startNotificationDispatcher(() => new RunStore(), notificationSettings, { host: hostname() }) : () => {
29330
+ const stopNotifications = notificationSettings ? startNotificationDispatcher(() => new RunStore(), notificationSettings, { host: hostname2() }) : () => {
28409
29331
  };
28410
29332
  const imapSettings = notificationSettings?.decisionsReplyTo ? imapSettingsFromEnv() : null;
28411
29333
  const stopDecisions = imapSettings && notificationSettings ? startDecisionPoller(imapSettings, async (mail) => {
@@ -28420,7 +29342,7 @@ function setupRoutes(config2, options = {}) {
28420
29342
  },
28421
29343
  modelExists: (id) => listModels().some((m) => m.id === id),
28422
29344
  confirm: async (to, subject, text3) => {
28423
- await notificationSettings.transport.send({ to: [to], from: notificationSettings.from, subject, text: text3, idempotencyKey: `confirm_${randomUUID13()}` });
29345
+ await notificationSettings.transport.send({ to: [to], from: notificationSettings.from, subject, text: text3, idempotencyKey: `confirm_${randomUUID14()}` });
28424
29346
  }
28425
29347
  }, { allowedSenders: notificationSettings.to, requireAuthentication: process.env.AUTOPILOT_DECISIONS_REQUIRE_AUTH !== "0" });
28426
29348
  } finally {
@@ -28556,7 +29478,7 @@ function setupRoutes(config2, options = {}) {
28556
29478
  const task = typeof body.task === "string" ? body.task.trim() : "";
28557
29479
  let cwd;
28558
29480
  try {
28559
- cwd = typeof body.cwd === "string" ? resolve17(body.cwd) : process.cwd();
29481
+ cwd = typeof body.cwd === "string" ? resolve18(body.cwd) : process.cwd();
28560
29482
  } catch {
28561
29483
  badRequest(res, "cwd must be a valid path");
28562
29484
  return;
@@ -28620,7 +29542,7 @@ function setupRoutes(config2, options = {}) {
28620
29542
  const executor = new ToolExecutor(tools);
28621
29543
  const { makeSessionId: makeSessionId2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
28622
29544
  const sessionId = makeSessionId2(task);
28623
- const runId = randomUUID13();
29545
+ const runId = randomUUID14();
28624
29546
  const worktreeManager = new RunWorktreeManager();
28625
29547
  let worktree;
28626
29548
  if (useWorktree) {
@@ -28669,7 +29591,7 @@ function setupRoutes(config2, options = {}) {
28669
29591
  store.close();
28670
29592
  }
28671
29593
  } catch (error) {
28672
- await unlink4(resolve17(sessionsDir(), `${sessionId}.json`)).catch(() => {
29594
+ await unlink4(resolve18(sessionsDir(), `${sessionId}.json`)).catch(() => {
28673
29595
  });
28674
29596
  if (worktree) {
28675
29597
  await worktreeManager.discard(worktree).catch((cleanupError) => {
@@ -28812,7 +29734,7 @@ function setupRoutes(config2, options = {}) {
28812
29734
  return;
28813
29735
  }
28814
29736
  try {
28815
- const file = await loadSession(resolve17(sessionsDir(), `${sessionId}.json`));
29737
+ const file = await loadSession(resolve18(sessionsDir(), `${sessionId}.json`));
28816
29738
  json3(res, 200, {
28817
29739
  id: file.id,
28818
29740
  cwd: file.cwd,
@@ -28832,7 +29754,7 @@ function setupRoutes(config2, options = {}) {
28832
29754
  activeSessions.delete(sessionId);
28833
29755
  try {
28834
29756
  const { unlink: unlink7 } = await import("fs/promises");
28835
- await unlink7(resolve17(sessionsDir(), `${sessionId}.json`));
29757
+ await unlink7(resolve18(sessionsDir(), `${sessionId}.json`));
28836
29758
  } catch {
28837
29759
  }
28838
29760
  json3(res, 200, { deleted: sessionId });
@@ -28862,6 +29784,12 @@ function setupRoutes(config2, options = {}) {
28862
29784
  title
28863
29785
  };
28864
29786
  const hooks = new HooksManager(cwd);
29787
+ const gate = await submitUserPrompt({ hooks, sessionId, cwd, prompt, resumed: false, model, auth: llmAuthFromConfig(config2) });
29788
+ if (gate.blocked) {
29789
+ await endSession(sessionId, "exit");
29790
+ promptBlocked(res, gate.reason);
29791
+ return;
29792
+ }
28865
29793
  const executor = new ToolExecutor(ALL_TOOLS, { hooks });
28866
29794
  const messages = [
28867
29795
  {
@@ -28914,6 +29842,19 @@ function setupRoutes(config2, options = {}) {
28914
29842
  badRequest(res, "prompt is required");
28915
29843
  return;
28916
29844
  }
29845
+ const gate = await submitUserPrompt({
29846
+ ...active.hooks ? { hooks: active.hooks } : {},
29847
+ sessionId,
29848
+ cwd: active.sessionFile.cwd,
29849
+ prompt,
29850
+ resumed: active.messages.some((m) => m.role === "assistant"),
29851
+ model: active.sessionFile.model,
29852
+ auth: llmAuthFromConfig(config2)
29853
+ });
29854
+ if (gate.blocked) {
29855
+ promptBlocked(res, gate.reason);
29856
+ return;
29857
+ }
28917
29858
  const resolvedFiles = await resolveFiles2(files, active.sessionFile.cwd);
28918
29859
  const { content: userContent2 } = await buildAttachmentMessage(prompt, resolvedFiles, active.sessionFile.cwd, imageOptions(config2));
28919
29860
  active.messages.push({ role: "user", content: userContent2 });
@@ -29237,6 +30178,7 @@ var init_routes = __esm({
29237
30178
  "use strict";
29238
30179
  init_session_core();
29239
30180
  init_session_lifecycle();
30181
+ init_prompt_submit();
29240
30182
  init_manager2();
29241
30183
  init_exit();
29242
30184
  init_registry();
@@ -29328,7 +30270,7 @@ async function startServer(opts2) {
29328
30270
  server.keepAliveTimeout = 5e3;
29329
30271
  const unregisterShutdown = onShutdown("server", async () => {
29330
30272
  const ended = cleanup();
29331
- const closed = new Promise((resolve21) => server.close(() => resolve21()));
30273
+ const closed = new Promise((resolve22) => server.close(() => resolve22()));
29332
30274
  server.closeAllConnections();
29333
30275
  await Promise.all([ended, closed]);
29334
30276
  });
@@ -29337,13 +30279,13 @@ async function startServer(opts2) {
29337
30279
  void cleanup();
29338
30280
  });
29339
30281
  installExitHandlers({ signals: ["SIGINT", "SIGTERM"], exitCode: () => 0, crashes: true });
29340
- return new Promise((resolve21, reject) => {
30282
+ return new Promise((resolve22, reject) => {
29341
30283
  server.listen(opts2.port, opts2.hostname, () => {
29342
30284
  logger.info("server: listening", { hostname: opts2.hostname, port: opts2.port });
29343
30285
  console.log(`kimiflare serve: http://${opts2.hostname}:${opts2.port}`);
29344
30286
  console.log(` API docs: http://${opts2.hostname}:${opts2.port}/doc`);
29345
30287
  console.log(` SSE stream: http://${opts2.hostname}:${opts2.port}/event`);
29346
- resolve21(server);
30288
+ resolve22(server);
29347
30289
  });
29348
30290
  server.on("error", reject);
29349
30291
  });
@@ -29677,8 +30619,8 @@ async function runEmitMode(opts2) {
29677
30619
  async function nextFollowUp() {
29678
30620
  if (followUpQueue.length > 0) return followUpQueue.shift();
29679
30621
  if (stdinClosed) return null;
29680
- return new Promise((resolve21) => {
29681
- followUpResolver = resolve21;
30622
+ return new Promise((resolve22) => {
30623
+ followUpResolver = resolve22;
29682
30624
  });
29683
30625
  }
29684
30626
  const cwd = process.cwd();
@@ -29708,6 +30650,25 @@ async function runEmitMode(opts2) {
29708
30650
  process.on("SIGINT", sigintHandler);
29709
30651
  async function runTurn(text3) {
29710
30652
  emit("UserMessageCreated", { text: text3 });
30653
+ const gate = await submitUserPrompt({
30654
+ hooks,
30655
+ sessionId,
30656
+ cwd,
30657
+ prompt: text3,
30658
+ resumed: messages.some((m) => m.role === "assistant"),
30659
+ model: opts2.model,
30660
+ auth: llmAuthFromConfig(opts2)
30661
+ });
30662
+ if (gate.blocked) {
30663
+ emit("RuntimeError", {
30664
+ message: `prompt blocked by a UserPromptSubmit hook: ${gate.reason}`,
30665
+ source: "hooks",
30666
+ kind: "generic",
30667
+ severity: "error"
30668
+ });
30669
+ if (!opts2.multiTurn) exitCode = PROMPT_BLOCKED_EXIT_CODE;
30670
+ return;
30671
+ }
29711
30672
  messages.push({ role: "user", content: text3 });
29712
30673
  try {
29713
30674
  const policy = await resolveTurnPolicy({ cfg: opts2, prompt: text3, tools: ALL_TOOLS, signal: controller.signal });
@@ -29830,8 +30791,8 @@ async function runEmitMode(opts2) {
29830
30791
  return fromHook;
29831
30792
  }
29832
30793
  if (opts2.multiTurn) {
29833
- const choice = await new Promise((resolve21) => {
29834
- pendingPermissions.set(reqId, resolve21);
30794
+ const choice = await new Promise((resolve22) => {
30795
+ pendingPermissions.set(reqId, resolve22);
29835
30796
  });
29836
30797
  if (choice === "allow" || choice === "allow_session") {
29837
30798
  emit("PermissionGranted", { request_id: reqId });
@@ -29931,6 +30892,7 @@ var init_emit_mode = __esm({
29931
30892
  init_executor();
29932
30893
  init_errors();
29933
30894
  init_session_lifecycle();
30895
+ init_prompt_submit();
29934
30896
  init_sessions();
29935
30897
  }
29936
30898
  });
@@ -29958,7 +30920,7 @@ async function runAttachMode(opts2) {
29958
30920
  if (!response.ok) {
29959
30921
  const text3 = await response.text().catch(() => "unknown error");
29960
30922
  console.error(`kimiflare attach: ${response.status} ${text3}`);
29961
- process.exit(1);
30923
+ process.exit(response.status === 403 && text3.includes('"prompt_blocked"') ? PROMPT_BLOCKED_EXIT_CODE : 1);
29962
30924
  }
29963
30925
  const result2 = await response.json();
29964
30926
  const sessionId = result2.sessionId;
@@ -29971,7 +30933,7 @@ async function runAttachMode(opts2) {
29971
30933
  async function streamSse(url, sessionId, format) {
29972
30934
  const eventSource = new EventSource(`${url}/event`);
29973
30935
  let done = false;
29974
- return new Promise((resolve21, reject) => {
30936
+ return new Promise((resolve22, reject) => {
29975
30937
  eventSource.onmessage = (event) => {
29976
30938
  try {
29977
30939
  const data = JSON.parse(event.data);
@@ -30018,7 +30980,7 @@ async function streamSse(url, sessionId, format) {
30018
30980
  done = true;
30019
30981
  eventSource.close();
30020
30982
  if (format === "text") process.stdout.write("\n");
30021
- resolve21(void 0);
30983
+ resolve22(void 0);
30022
30984
  break;
30023
30985
  case "error":
30024
30986
  done = true;
@@ -30031,7 +30993,7 @@ async function streamSse(url, sessionId, format) {
30031
30993
  process.stdout.write(JSON.stringify({ event: "error", message: data.message }) + "\n");
30032
30994
  }
30033
30995
  process.exitCode = 1;
30034
- resolve21(void 0);
30996
+ resolve22(void 0);
30035
30997
  break;
30036
30998
  }
30037
30999
  } catch {
@@ -30057,7 +31019,7 @@ async function streamSseJson(url, sessionId) {
30057
31019
  const toolResults = [];
30058
31020
  let usage;
30059
31021
  const eventSource = new EventSource(`${url}/event`);
30060
- return new Promise((resolve21, reject) => {
31022
+ return new Promise((resolve22, reject) => {
30061
31023
  eventSource.onmessage = (event) => {
30062
31024
  try {
30063
31025
  const data = JSON.parse(event.data);
@@ -30094,7 +31056,7 @@ async function streamSseJson(url, sessionId) {
30094
31056
  2
30095
31057
  ) + "\n"
30096
31058
  );
30097
- resolve21(void 0);
31059
+ resolve22(void 0);
30098
31060
  break;
30099
31061
  case "error":
30100
31062
  eventSource.close();
@@ -30102,7 +31064,7 @@ async function streamSseJson(url, sessionId) {
30102
31064
  JSON.stringify({ error: data.message, sessionId }, null, 2) + "\n"
30103
31065
  );
30104
31066
  process.exitCode = 1;
30105
- resolve21(void 0);
31067
+ resolve22(void 0);
30106
31068
  break;
30107
31069
  }
30108
31070
  } catch {
@@ -30122,6 +31084,7 @@ var EventSource;
30122
31084
  var init_attach_mode = __esm({
30123
31085
  "src/attach-mode.ts"() {
30124
31086
  "use strict";
31087
+ init_prompt_submit();
30125
31088
  EventSource = class {
30126
31089
  url;
30127
31090
  controller;
@@ -30279,7 +31242,7 @@ __export(openrouter_oauth_exports, {
30279
31242
  });
30280
31243
  import { createHash as createHash8, randomBytes as randomBytes4 } from "crypto";
30281
31244
  import { createServer as createServer4 } from "http";
30282
- import { hostname as hostname2 } from "os";
31245
+ import { hostname as hostname3 } from "os";
30283
31246
  function createPkce() {
30284
31247
  const verifier = randomBytes4(32).toString("base64url");
30285
31248
  return { verifier, challenge: pkceChallenge(verifier) };
@@ -30291,7 +31254,7 @@ function openRouterAuthPageUrl() {
30291
31254
  return (process.env.OPENROUTER_AUTH_URL?.trim() || "https://openrouter.ai/auth").replace(/\/+$/, "");
30292
31255
  }
30293
31256
  function defaultKeyLabel() {
30294
- return `autopilot (${hostname2()})`.slice(0, 60);
31257
+ return `autopilot (${hostname3()})`.slice(0, 60);
30295
31258
  }
30296
31259
  function buildAuthUrl(opts2) {
30297
31260
  const params = new URLSearchParams();
@@ -30368,9 +31331,9 @@ async function startLoopbackListener(opts2 = {}) {
30368
31331
  rejectCode(e);
30369
31332
  finish();
30370
31333
  }
30371
- await new Promise((resolve21, reject) => {
31334
+ await new Promise((resolve22, reject) => {
30372
31335
  server.once("error", reject);
30373
- server.listen(0, "127.0.0.1", () => resolve21());
31336
+ server.listen(0, "127.0.0.1", () => resolve22());
30374
31337
  });
30375
31338
  const address = server.address();
30376
31339
  const port = typeof address === "object" && address ? address.port : 0;
@@ -30517,11 +31480,11 @@ async function chatgptSignIn(onUrl, signal) {
30517
31480
  }
30518
31481
  function untilAborted(p, signal) {
30519
31482
  if (!signal) return p;
30520
- return new Promise((resolve21, reject) => {
31483
+ return new Promise((resolve22, reject) => {
30521
31484
  const abort = () => reject(new DOMException("sign-in cancelled", "AbortError"));
30522
31485
  if (signal.aborted) return abort();
30523
31486
  signal.addEventListener("abort", abort, { once: true });
30524
- p.then(resolve21, reject).finally(() => signal.removeEventListener("abort", abort));
31487
+ p.then(resolve22, reject).finally(() => signal.removeEventListener("abort", abort));
30525
31488
  });
30526
31489
  }
30527
31490
  async function openRouterSignIn(io) {
@@ -30767,20 +31730,20 @@ var init_help_catalog = __esm({
30767
31730
 
30768
31731
  // src/ui/bang-command.ts
30769
31732
  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";
31733
+ import { mkdtemp as mkdtemp4, readFile as readFile36, rm as rm8 } from "fs/promises";
31734
+ import { tmpdir as tmpdir6, platform as platform5 } from "os";
31735
+ import { join as join49 } from "path";
30773
31736
  function registerTerminalHandoff(h) {
30774
31737
  terminalHandoff = h;
30775
31738
  }
30776
31739
  function runCaptured(inv, cwd) {
30777
- return new Promise((resolve21) => {
31740
+ return new Promise((resolve22) => {
30778
31741
  const child = spawn8(inv.file, inv.args, { cwd, stdio: ["ignore", "pipe", "pipe"], env: inv.env ?? process.env });
30779
31742
  let output = "";
30780
31743
  child.stdout?.on("data", (c) => output += c.toString("utf8"));
30781
31744
  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 }));
31745
+ child.once("error", (e) => resolve22({ code: 127, signal: null, output: `failed to start: ${e.message}` }));
31746
+ child.once("exit", (code, signal) => resolve22({ code, signal, output }));
30784
31747
  });
30785
31748
  }
30786
31749
  function isTerminalHandedOff() {
@@ -30848,10 +31811,10 @@ function swallow() {
30848
31811
  });
30849
31812
  }
30850
31813
  function spawnInherit(inv, cwd) {
30851
- return new Promise((resolve21, reject) => {
31814
+ return new Promise((resolve22, reject) => {
30852
31815
  const child = spawn8(inv.file, inv.args, { cwd, stdio: "inherit", env: inv.env ?? process.env });
30853
31816
  child.once("error", reject);
30854
- child.once("exit", (code, signal) => resolve21({ code, signal }));
31817
+ child.once("exit", (code, signal) => resolve22({ code, signal }));
30855
31818
  });
30856
31819
  }
30857
31820
  async function runBangCommand(command, cwd) {
@@ -30869,8 +31832,8 @@ async function runBangCommand(command, cwd) {
30869
31832
  const origErr = stderr.write;
30870
31833
  const readable = stdin.listeners("readable");
30871
31834
  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;
31835
+ const logDir2 = isWindows ? null : await mkdtemp4(join49(tmpdir6(), "autopilot-bang-"));
31836
+ const logPath = logDir2 ? join49(logDir2, "out.log") : null;
30874
31837
  const viaScript = logPath ? scriptInvocation(command, shell, logPath) : null;
30875
31838
  origOut.call(stdout, `
30876
31839
  $ ${command}
@@ -30911,9 +31874,9 @@ $ ${command}
30911
31874
  }
30912
31875
  let output = spawnError ? `failed to start: ${spawnError}` : null;
30913
31876
  if (captured && logPath) {
30914
- output = cleanTerminalOutput(await readFile34(logPath, "utf8").catch(() => ""));
31877
+ output = cleanTerminalOutput(await readFile36(logPath, "utf8").catch(() => ""));
30915
31878
  }
30916
- if (logDir2) await rm7(logDir2, { recursive: true, force: true }).catch(() => {
31879
+ if (logDir2) await rm8(logDir2, { recursive: true, force: true }).catch(() => {
30917
31880
  });
30918
31881
  return { command, exitCode: result2.code, signal: result2.signal, output };
30919
31882
  }
@@ -31150,8 +32113,8 @@ function activityFromSubagent(worker) {
31150
32113
  };
31151
32114
  }
31152
32115
  function activityTaskPreview(task) {
31153
- const oneLine2 = task.replace(/\s+/g, " ").trim();
31154
- return oneLine2.length > 72 ? `${oneLine2.slice(0, 71)}\u2026` : oneLine2;
32116
+ const oneLine3 = task.replace(/\s+/g, " ").trim();
32117
+ return oneLine3.length > 72 ? `${oneLine3.slice(0, 71)}\u2026` : oneLine3;
31155
32118
  }
31156
32119
  function diffActivityItems(previous, current, snapshotSent) {
31157
32120
  if (!snapshotSent) return [{ type: "ActivitySnapshot", payload: { items: [...current] } }];
@@ -31188,14 +32151,14 @@ __export(camouflage_launch_exports, {
31188
32151
  runWithCrashRestarts: () => runWithCrashRestarts
31189
32152
  });
31190
32153
  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";
32154
+ import { homedir as homedir31 } from "os";
32155
+ import { delimiter, dirname as dirname25, isAbsolute as isAbsolute8, join as join50, resolve as resolve19, sep as sep7 } from "path";
31193
32156
  import { fileURLToPath as fileURLToPath5 } from "url";
31194
32157
  function defaultPackageDir() {
31195
32158
  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;
32159
+ let dir = dirname25(fileURLToPath5(import.meta.resolve("camouflage-tui")));
32160
+ while (!existsSync14(join50(dir, "package.json")) && dirname25(dir) !== dir) dir = dirname25(dir);
32161
+ return existsSync14(join50(dir, "package.json")) ? dir : null;
31199
32162
  } catch {
31200
32163
  return null;
31201
32164
  }
@@ -31209,11 +32172,11 @@ function defaultIsExecutable(path) {
31209
32172
  }
31210
32173
  }
31211
32174
  function findOnPath(bin, env, platform7, isExecutable) {
31212
- if (isAbsolute8(bin) || bin.includes("/") || bin.includes(sep7)) return isExecutable(resolve18(bin));
32175
+ if (isAbsolute8(bin) || bin.includes("/") || bin.includes(sep7)) return isExecutable(resolve19(bin));
31213
32176
  const exts = platform7 === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
31214
32177
  for (const dir of (env.PATH ?? "").split(delimiter)) {
31215
32178
  if (!dir) continue;
31216
- for (const ext of exts) if (isExecutable(join48(dir, bin + ext))) return true;
32179
+ for (const ext of exts) if (isExecutable(join50(dir, bin + ext))) return true;
31217
32180
  }
31218
32181
  return false;
31219
32182
  }
@@ -31222,21 +32185,21 @@ function diagnoseRenderer(deps = {}) {
31222
32185
  const platform7 = deps.platform ?? process.platform;
31223
32186
  const arch = deps.arch ?? process.arch;
31224
32187
  const isExecutable = deps.isExecutable ?? defaultIsExecutable;
31225
- const readFile40 = deps.readFile ?? ((p) => readFileSync9(p, "utf8"));
32188
+ const readFile42 = deps.readFile ?? ((p) => readFileSync9(p, "utf8"));
31226
32189
  const packageDir = (deps.packageDir ?? defaultPackageDir)();
31227
32190
  let packageVersion = null;
31228
32191
  let packageBin = null;
31229
32192
  if (packageDir) {
31230
32193
  try {
31231
- packageVersion = JSON.parse(readFile40(join48(packageDir, "package.json"))).version ?? null;
32194
+ packageVersion = JSON.parse(readFile42(join50(packageDir, "package.json"))).version ?? null;
31232
32195
  } catch {
31233
32196
  }
31234
- packageBin = join48(packageDir, "bin", platform7 === "win32" ? "camouflage-tui.exe" : "camouflage-tui");
32197
+ packageBin = join50(packageDir, "bin", platform7 === "win32" ? "camouflage-tui.exe" : "camouflage-tui");
31235
32198
  }
31236
- const installErrorPath = join48(deps.home ?? homedir30(), ".camouflage", "install-error.txt");
32199
+ const installErrorPath = join50(deps.home ?? homedir31(), ".camouflage", "install-error.txt");
31237
32200
  let installError = null;
31238
32201
  try {
31239
- installError = readFile40(installErrorPath).trim() || null;
32202
+ installError = readFile42(installErrorPath).trim() || null;
31240
32203
  } catch {
31241
32204
  }
31242
32205
  const envBin = env.CAMOUFLAGE_BIN || null;
@@ -31283,7 +32246,7 @@ function formatUnavailable(d, reason) {
31283
32246
  if (d.envBin) lines.push(` - point CAMOUFLAGE_BIN at an existing camouflage-tui binary, or unset it to use the bundled one`);
31284
32247
  if (d.packageDir) {
31285
32248
  lines.push(
31286
- ` - download the renderer again: node "${join48(d.packageDir, "scripts", "install.js")}"`,
32249
+ ` - download the renderer again: node "${join50(d.packageDir, "scripts", "install.js")}"`,
31287
32250
  ` (or run \`npm rebuild camouflage-tui\` where autopilot-ai is installed; \`npm rebuild -g camouflage-tui\` for a global install)`
31288
32251
  );
31289
32252
  }
@@ -31332,7 +32295,7 @@ async function runWithCrashRestarts(run, opts2 = {}) {
31332
32295
  function crashGiveUpMessage(outcome, windowMs = 6e4) {
31333
32296
  const lines = [
31334
32297
  `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`
32298
+ `Crash reports: ${join50(homedir31(), ".camouflage")} (crash-*.txt). Please attach one to an issue: https://github.com/sinameraji/autopilot/issues`
31336
32299
  ];
31337
32300
  if (outcome.sessionId) {
31338
32301
  lines.push(
@@ -31414,11 +32377,11 @@ __export(interactive_resume_exports, {
31414
32377
  resumeHint: () => resumeHint
31415
32378
  });
31416
32379
  import { existsSync as existsSync15 } from "fs";
31417
- import { join as join49 } from "path";
32380
+ import { join as join51 } from "path";
31418
32381
  async function resolveInteractiveResume(req, cwd = process.cwd()) {
31419
32382
  if (req.sessionId !== void 0) {
31420
32383
  const id = req.sessionId.trim().replace(/\.json$/, "");
31421
- if (!SESSION_ID.test(id) || !existsSync15(join49(sessionsDir(), `${id}.json`))) {
32384
+ if (!SESSION_ID.test(id) || !existsSync15(join51(sessionsDir(), `${id}.json`))) {
31422
32385
  return { kind: "error", message: `no saved conversation with id "${req.sessionId}". Run \`autopilot resume\` to pick one.` };
31423
32386
  }
31424
32387
  return { kind: "session", sessionId: id };
@@ -31431,7 +32394,7 @@ async function resolveInteractiveResume(req, cwd = process.cwd()) {
31431
32394
  return { kind: "picker" };
31432
32395
  }
31433
32396
  function resumeHint(sessionId) {
31434
- if (!sessionId || !SESSION_ID.test(sessionId) || !existsSync15(join49(sessionsDir(), `${sessionId}.json`))) return null;
32397
+ if (!sessionId || !SESSION_ID.test(sessionId) || !existsSync15(join51(sessionsDir(), `${sessionId}.json`))) return null;
31435
32398
  return `Resume this conversation with: autopilot resume ${sessionId}
31436
32399
  `;
31437
32400
  }
@@ -31471,8 +32434,8 @@ __export(camouflage_view_exports, {
31471
32434
  import { execFileSync as execFileSync4, spawn as spawn9 } from "child_process";
31472
32435
  import { readdir as readdir7 } from "fs/promises";
31473
32436
  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";
32437
+ import { homedir as homedir32 } from "os";
32438
+ import { dirname as dirname26, join as join52, resolve as resolve20 } from "path";
31476
32439
  import { fileURLToPath as fileURLToPath6 } from "url";
31477
32440
  function noticeSeverity(e) {
31478
32441
  return e.kind === "info" && e.tone === "warn" ? "warn" : "info";
@@ -31493,10 +32456,10 @@ function rendererInfo(env = process.env) {
31493
32456
  let packageVersion = null;
31494
32457
  let packageBin = "camouflage-tui";
31495
32458
  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");
32459
+ let dir = dirname26(fileURLToPath6(import.meta.resolve("camouflage-tui")));
32460
+ while (!existsSync16(join52(dir, "package.json")) && dirname26(dir) !== dir) dir = dirname26(dir);
32461
+ packageVersion = JSON.parse(readFileSync10(join52(dir, "package.json"), "utf8")).version ?? null;
32462
+ const local = join52(dir, "bin", process.platform === "win32" ? "camouflage-tui.exe" : "camouflage-tui");
31500
32463
  packageBin = local;
31501
32464
  } catch {
31502
32465
  }
@@ -31624,7 +32587,7 @@ async function runCamouflageView(opts2) {
31624
32587
  if (process.stdin.isTTY) process.stdin.setRawMode?.(false);
31625
32588
  process.stderr.write(
31626
32589
  `
31627
- The Camouflage renderer stopped unexpectedly (${signal ?? `exit code ${code}`}). Its crash report is in ${join50(homedir31(), ".camouflage")} (crash-*.txt).
32590
+ The Camouflage renderer stopped unexpectedly (${signal ?? `exit code ${code}`}). Its crash report is in ${join52(homedir32(), ".camouflage")} (crash-*.txt).
31628
32591
  ` + (view.busy ? "Finishing the current turn first\u2026\n" : "")
31629
32592
  );
31630
32593
  await view.settle();
@@ -31635,7 +32598,7 @@ The Camouflage renderer stopped unexpectedly (${signal ?? `exit code ${code}`}).
31635
32598
  let endBeforeApp = () => {
31636
32599
  };
31637
32600
  let quitBeforeApp = false;
31638
- const beforeApp = new Promise((resolve21) => endBeforeApp = resolve21);
32601
+ const beforeApp = new Promise((resolve22) => endBeforeApp = resolve22);
31639
32602
  let setup = null;
31640
32603
  try {
31641
32604
  let cfg = opts2.cfg;
@@ -31680,14 +32643,14 @@ The Camouflage renderer stopped unexpectedly (${signal ?? `exit code ${code}`}).
31680
32643
  return "done";
31681
32644
  }
31682
32645
  function runShell2(command) {
31683
- return new Promise((resolve21) => {
32646
+ return new Promise((resolve22) => {
31684
32647
  const { shell, args } = getShellCommand();
31685
32648
  const child = spawn9(shell, [...args, command], { env: process.env });
31686
32649
  let out = "";
31687
32650
  child.stdout?.on("data", (c) => out += c.toString("utf8"));
31688
32651
  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 }));
32652
+ child.on("close", (code) => resolve22({ ok: code === 0, output: out || (code === 0 ? "" : `Exit code: ${code}`) }));
32653
+ child.on("error", (err) => resolve22({ ok: false, output: err.message }));
31691
32654
  });
31692
32655
  }
31693
32656
  function syncSlashCommands(custom, lastKey, send) {
@@ -31770,8 +32733,8 @@ function truncateText(s, max) {
31770
32733
  async function listMentionDir(cwd, query) {
31771
32734
  const exact = query === "~" || query === "." || query === ".." || query.endsWith("/..");
31772
32735
  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 || ".");
32736
+ const expanded = dir.startsWith("~") ? join52(homedir32(), dir.slice(1)) : dir;
32737
+ const target = resolve20(cwd, expanded || ".");
31775
32738
  try {
31776
32739
  const items = await readdir7(target, { withFileTypes: true });
31777
32740
  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 +32873,7 @@ function shortModel(id) {
31910
32873
  return id.includes("/") ? id.slice(id.indexOf("/") + 1) : id;
31911
32874
  }
31912
32875
  function shortPath(p) {
31913
- const home = homedir31();
32876
+ const home = homedir32();
31914
32877
  return p === home ? "~" : p.startsWith(home + "/") ? `~${p.slice(home.length)}` : p;
31915
32878
  }
31916
32879
  function gitBranch(cwd) {
@@ -32032,15 +32995,15 @@ var init_camouflage_view = __esm({
32032
32995
  /** Resolves once the App is idle (no turn running). */
32033
32996
  settle(maxWaitMs = 12e4) {
32034
32997
  if (!this.busy) return Promise.resolve();
32035
- return new Promise((resolve21) => {
32036
- this.idleWaiters.push(resolve21);
32037
- setTimeout(resolve21, maxWaitMs).unref();
32998
+ return new Promise((resolve22) => {
32999
+ this.idleWaiters.push(resolve22);
33000
+ setTimeout(resolve22, maxWaitMs).unref();
32038
33001
  });
32039
33002
  }
32040
33003
  sync(s) {
32041
33004
  this.busy = s.turnRunning;
32042
33005
  this.sessionId = s.sessionId;
32043
- if (!s.turnRunning) for (const resolve21 of this.idleWaiters.splice(0)) resolve21();
33006
+ if (!s.turnRunning) for (const resolve22 of this.idleWaiters.splice(0)) resolve22();
32044
33007
  if (this.disposed) {
32045
33008
  if (s.permission) this.actions?.decidePermission("deny");
32046
33009
  return;
@@ -32337,7 +33300,7 @@ var init_camouflage_view = __esm({
32337
33300
  */
32338
33301
  async denyFeedback(given, escaped) {
32339
33302
  if (given?.trim()) return given.trim();
32340
- await new Promise((resolve21) => setTimeout(resolve21, 50));
33303
+ await new Promise((resolve22) => setTimeout(resolve22, 50));
32341
33304
  if (escaped() || this.disposed) return "";
32342
33305
  const r = await this.sdk.form(this.cam, {
32343
33306
  id: `deny-${Date.now()}`,
@@ -33447,9 +34410,9 @@ function usePermissionController(getMode, onPlanModeBlocked) {
33447
34410
  const askPermission = useCallback(
33448
34411
  (req, askOpts) => resolvePermission(req, getModeRef.current(), askOpts, {
33449
34412
  onPlanModeBlocked: (toolName) => onPlanModeBlockedRef.current(toolName),
33450
- prompt: () => new Promise((resolve21) => {
33451
- resolveRef.current = resolve21;
33452
- setPending({ tool: req.tool, args: req.args, resolve: resolve21 });
34413
+ prompt: () => new Promise((resolve22) => {
34414
+ resolveRef.current = resolve22;
34415
+ setPending({ tool: req.tool, args: req.args, resolve: resolve22 });
33453
34416
  })
33454
34417
  }),
33455
34418
  []
@@ -33830,14 +34793,14 @@ var init_frontmatter = __esm({
33830
34793
 
33831
34794
  // src/commands/loader.ts
33832
34795
  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";
34796
+ import { homedir as homedir33 } from "os";
34797
+ import { join as join53, relative as relative11, sep as sep8 } from "path";
33835
34798
  function projectCommandsDir(cwd = process.cwd()) {
33836
- return join51(cwd, ".kimiflare", "commands");
34799
+ return join53(cwd, ".kimiflare", "commands");
33837
34800
  }
33838
34801
  function globalCommandsDir() {
33839
- const xdg = process.env.XDG_CONFIG_HOME || join51(homedir32(), ".config");
33840
- return join51(xdg, "kimiflare", "commands");
34802
+ const xdg = process.env.XDG_CONFIG_HOME || join53(homedir33(), ".config");
34803
+ return join53(xdg, "kimiflare", "commands");
33841
34804
  }
33842
34805
  async function loadCustomCommands(cwd = process.cwd()) {
33843
34806
  const warnings = [];
@@ -33991,7 +34954,7 @@ var init_loader = __esm({
33991
34954
  import { exec } from "child_process";
33992
34955
  import { open as open3, realpath as realpath10 } from "fs/promises";
33993
34956
  import { isAbsolute as isAbsolute9, relative as relative12, resolve as resolvePathJoin, basename as pathBasename } from "path";
33994
- import { promisify as promisify9 } from "util";
34957
+ import { promisify as promisify11 } from "util";
33995
34958
  function tokenizeArgs(s) {
33996
34959
  return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
33997
34960
  const token = match[0];
@@ -34144,7 +35107,7 @@ var init_renderer2 = __esm({
34144
35107
  "src/commands/renderer.ts"() {
34145
35108
  "use strict";
34146
35109
  init_paths();
34147
- execAsync = promisify9(exec);
35110
+ execAsync = promisify11(exec);
34148
35111
  ARG_TOKEN_RE = /(?:"[^"]*"|'[^']*'|[^\s"']+)/g;
34149
35112
  POSITIONAL_RE = /\$(\d+)/g;
34150
35113
  HAS_POSITIONAL = /\$\d+/;
@@ -34453,6 +35416,9 @@ Rules:
34453
35416
 
34454
35417
  // src/ui/use-session-manager.ts
34455
35418
  import React, { useCallback as useCallback2, useRef as useRef2, useState as useState4 } from "react";
35419
+ function sessionNameText(messages, firstPrompt) {
35420
+ return firstPrompt?.trim() || extractFirstUserText(messages);
35421
+ }
34456
35422
  function extractFirstUserText(messages) {
34457
35423
  const firstUser = messages.find((m) => m.role === "user");
34458
35424
  if (!firstUser) return "session";
@@ -34485,9 +35451,9 @@ function useSessionManager(deps) {
34485
35451
  const [checkpointList, setCheckpointList] = useState4([]);
34486
35452
  const depsRef = useRef2(deps);
34487
35453
  depsRef.current = deps;
34488
- const ensureSessionId = useCallback2(() => {
35454
+ const ensureSessionId = useCallback2((firstPrompt) => {
34489
35455
  if (sessionIdRef.current) return sessionIdRef.current;
34490
- const text3 = extractFirstUserText(depsRef.current.messagesRef.current);
35456
+ const text3 = sessionNameText(depsRef.current.messagesRef.current, firstPrompt);
34491
35457
  sessionIdRef.current = makeSessionId(text3);
34492
35458
  setLogSessionId(sessionIdRef.current);
34493
35459
  return sessionIdRef.current;
@@ -34986,8 +35952,8 @@ var init_terminal_qr = __esm({
34986
35952
 
34987
35953
  // src/agent/jev-context.ts
34988
35954
  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";
35955
+ import { readFile as readFile37 } from "fs/promises";
35956
+ import { basename as basename11, join as join54, resolve as resolve21 } from "path";
34991
35957
  function messageText(message2) {
34992
35958
  if (typeof message2.content === "string") return message2.content.trim();
34993
35959
  if (!Array.isArray(message2.content)) return "";
@@ -35004,12 +35970,12 @@ function projectRoot(cwd) {
35004
35970
  timeout: 1e3
35005
35971
  }).trim();
35006
35972
  } catch {
35007
- return resolve20(cwd);
35973
+ return resolve21(cwd);
35008
35974
  }
35009
35975
  }
35010
35976
  async function readPackageMetadata(root) {
35011
35977
  try {
35012
- const parsed = JSON.parse(await readFile35(join52(root, "package.json"), "utf8"));
35978
+ const parsed = JSON.parse(await readFile37(join54(root, "package.json"), "utf8"));
35013
35979
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
35014
35980
  const pkg = parsed;
35015
35981
  const metadata = {};
@@ -35026,7 +35992,7 @@ async function readPackageMetadata(root) {
35026
35992
  async function readLicenseEvidence(root) {
35027
35993
  for (const name of ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "COPYING"]) {
35028
35994
  try {
35029
- const content = (await readFile35(join52(root, name), "utf8")).trim();
35995
+ const content = (await readFile37(join54(root, name), "utf8")).trim();
35030
35996
  if (content) return { source: `project license (${name})`, content: content.slice(0, 700) };
35031
35997
  } catch {
35032
35998
  }
@@ -35059,7 +36025,7 @@ async function buildJevContext(question, messages, cwd = process.cwd()) {
35059
36025
  conversationRemaining -= source.length + content.length + 2;
35060
36026
  }
35061
36027
  }
35062
- const currentDirectory = basename11(resolve20(cwd));
36028
+ const currentDirectory = basename11(resolve21(cwd));
35063
36029
  const escapedDirectory = currentDirectory.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
35064
36030
  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
36031
  if (CURRENT_PROJECT_REFERENCE.test(question) || namesCurrentDirectory) {
@@ -35282,8 +36248,8 @@ var init_frontmatter2 = __esm({
35282
36248
  });
35283
36249
 
35284
36250
  // 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";
36251
+ import { readFile as readFile38, readdir as readdir8, stat as stat13 } from "fs/promises";
36252
+ import { join as join55, extname } from "path";
35287
36253
  function normalizeManifest(raw, filePath) {
35288
36254
  const name = typeof raw.name === "string" ? raw.name : "";
35289
36255
  const description = typeof raw.description === "string" ? raw.description : "";
@@ -35297,7 +36263,7 @@ function normalizeManifest(raw, filePath) {
35297
36263
  return { name, description, match, scope, priority, enabled: enabled2 };
35298
36264
  }
35299
36265
  async function loadSkillFile(filePath) {
35300
- const raw = await readFile36(filePath, "utf-8");
36266
+ const raw = await readFile38(filePath, "utf-8");
35301
36267
  const parsed = parseFrontmatter2(raw);
35302
36268
  const manifest = normalizeManifest(parsed.data, filePath);
35303
36269
  const body = parsed.content.trim();
@@ -35319,8 +36285,8 @@ async function loadSkillsFromDir(dirPath) {
35319
36285
  const entries = await readdir8(dirPath);
35320
36286
  const files = [];
35321
36287
  for (const entry of entries) {
35322
- const full = join53(dirPath, entry);
35323
- const s = await stat12(full);
36288
+ const full = join55(dirPath, entry);
36289
+ const s = await stat13(full);
35324
36290
  if (s.isFile() && extname(entry) === ".md") {
35325
36291
  files.push(full);
35326
36292
  }
@@ -35347,12 +36313,12 @@ var init_loader2 = __esm({
35347
36313
  });
35348
36314
 
35349
36315
  // 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";
36316
+ import { mkdir as mkdir21, writeFile as writeFile18, unlink as unlink5, readFile as readFile39 } from "fs/promises";
36317
+ import { join as join56 } from "path";
35352
36318
  function getSkillDirs(cwd) {
35353
36319
  return {
35354
- projectDir: join54(cwd, ".kimiflare", "skills"),
35355
- globalDir: join54(process.env.HOME ?? "", ".config", "kimiflare", "skills")
36320
+ projectDir: join56(cwd, ".kimiflare", "skills"),
36321
+ globalDir: join56(process.env.HOME ?? "", ".config", "kimiflare", "skills")
35356
36322
  };
35357
36323
  }
35358
36324
  async function listAllSkills(cwd) {
@@ -35366,7 +36332,7 @@ async function listAllSkills(cwd) {
35366
36332
  async function createSkill(opts2) {
35367
36333
  const dirs = getSkillDirs(opts2.cwd);
35368
36334
  const dir = opts2.scope === "project" ? dirs.projectDir : dirs.globalDir;
35369
- const filepath = join54(dir, `${opts2.name}.md`);
36335
+ const filepath = join56(dir, `${opts2.name}.md`);
35370
36336
  const frontmatter = {
35371
36337
  name: opts2.name,
35372
36338
  enabled: true,
@@ -35387,8 +36353,8 @@ ${yaml}
35387
36353
 
35388
36354
  Add your instructions here.
35389
36355
  `;
35390
- await mkdir20(dir, { recursive: true });
35391
- await writeFile17(filepath, content, "utf8");
36356
+ await mkdir21(dir, { recursive: true });
36357
+ await writeFile18(filepath, content, "utf8");
35392
36358
  return { filepath };
35393
36359
  }
35394
36360
  async function deleteSkill(name, cwd) {
@@ -35402,7 +36368,7 @@ async function setSkillEnabled(name, enabled2, cwd) {
35402
36368
  const all = await listAllSkills(cwd);
35403
36369
  const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
35404
36370
  if (!skill) throw new Error(`skill "${name}" not found`);
35405
- const raw = await readFile37(skill.filePath, "utf-8");
36371
+ const raw = await readFile39(skill.filePath, "utf-8");
35406
36372
  const parsed = parseFrontmatter2(raw);
35407
36373
  parsed.data.enabled = enabled2;
35408
36374
  const yaml = Object.entries(parsed.data).map(([k, v]) => {
@@ -35414,7 +36380,7 @@ ${v.map((item) => ` - ${item}`).join("\n")}`;
35414
36380
  ${yaml}
35415
36381
  ---
35416
36382
  ${parsed.content}`;
35417
- await writeFile17(skill.filePath, content, "utf8");
36383
+ await writeFile18(skill.filePath, content, "utf8");
35418
36384
  return { filepath: skill.filePath };
35419
36385
  }
35420
36386
  async function findSkillFile(name, cwd) {
@@ -35495,15 +36461,15 @@ var tui_report_exports = {};
35495
36461
  __export(tui_report_exports, {
35496
36462
  getCategoryReportText: () => getCategoryReportText
35497
36463
  });
35498
- import { readFile as readFile38 } from "fs/promises";
35499
- import { join as join55 } from "path";
35500
- import { homedir as homedir33 } from "os";
36464
+ import { readFile as readFile40 } from "fs/promises";
36465
+ import { join as join57 } from "path";
36466
+ import { homedir as homedir34 } from "os";
35501
36467
  function usageDir3() {
35502
- const xdg = process.env.XDG_DATA_HOME || join55(homedir33(), ".local", "share");
35503
- return join55(xdg, "kimiflare");
36468
+ const xdg = process.env.XDG_DATA_HOME || join57(homedir34(), ".local", "share");
36469
+ return join57(xdg, "kimiflare");
35504
36470
  }
35505
36471
  function usagePath4() {
35506
- return join55(usageDir3(), "usage.json");
36472
+ return join57(usageDir3(), "usage.json");
35507
36473
  }
35508
36474
  function today3() {
35509
36475
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -35515,7 +36481,7 @@ function daysAgo2(n) {
35515
36481
  }
35516
36482
  async function loadLog3() {
35517
36483
  try {
35518
- const raw = await readFile38(usagePath4(), "utf8");
36484
+ const raw = await readFile40(usagePath4(), "utf8");
35519
36485
  return JSON.parse(raw);
35520
36486
  } catch {
35521
36487
  return { version: 1, days: [], sessions: [] };
@@ -35572,7 +36538,7 @@ var init_tui_report = __esm({
35572
36538
  });
35573
36539
 
35574
36540
  // src/ui/slash-commands.ts
35575
- import { join as join56 } from "path";
36541
+ import { join as join58 } from "path";
35576
36542
  function executeFreshStart(ctx, planText, overrideMode, opts2 = {}) {
35577
36543
  const oldSessionId = ctx.sessionIdRef.current;
35578
36544
  if (ctx.cacheStableRef.current && ctx.messagesRef.current.length >= 2) {
@@ -35624,7 +36590,7 @@ function formatRunningSubagents(now2 = Date.now()) {
35624
36590
  if (workers.length === 0) return "no subagents running";
35625
36591
  return [
35626
36592
  `${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)}`),
36593
+ ...workers.map((w) => ` #${w.index} ${formatElapsed2(now2 - w.startedAt)} ${w.status === "cancelling" ? "(cancelling) " : ""}${oneLine2(w.task, 90)}`),
35628
36594
  "cancel one with /subagents cancel <n>, or all with /subagents cancel all \u2014 the turn keeps going"
35629
36595
  ].join("\n");
35630
36596
  }
@@ -35637,7 +36603,7 @@ function cancelSubagents(target) {
35637
36603
  const ref = target.replace(/^#/, "");
35638
36604
  const cancelled = workerRegistry.cancel(/^\d+$/.test(ref) ? Number(ref) : ref);
35639
36605
  if (!cancelled) return `no running subagent #${ref} (see /subagents list)`;
35640
- return `cancelling subagent #${cancelled.index} (${oneLine(cancelled.task, 60)}); the turn continues`;
36606
+ return `cancelling subagent #${cancelled.index} (${oneLine2(cancelled.task, 60)}); the turn continues`;
35641
36607
  }
35642
36608
  function formatElapsed2(ms) {
35643
36609
  const total = Math.max(0, Math.floor(ms / 1e3));
@@ -35645,7 +36611,7 @@ function formatElapsed2(ms) {
35645
36611
  const sec = total % 60;
35646
36612
  return m > 0 ? `${m}m${String(sec).padStart(2, "0")}s` : `${sec}s`;
35647
36613
  }
35648
- function oneLine(text3, max) {
36614
+ function oneLine2(text3, max) {
35649
36615
  const single = text3.replace(/\s+/g, " ").trim();
35650
36616
  return single.length > max ? `${single.slice(0, max - 1)}\u2026` : single;
35651
36617
  }
@@ -36044,7 +37010,7 @@ auto (default) lets the coordinator assess substantial tasks and delegate indepe
36044
37010
  const context = noContext ? [] : await buildJevContext(question.prompt, ctx.messagesRef.current, process.cwd());
36045
37011
  const receipt = formatJevContextReceipt(context, showContext);
36046
37012
  setResult(eventKey, { kind: "info", key: eventKey, text: `Jev is evaluating \xB7 ${receipt}`, pending: true });
36047
- await new Promise((resolve21) => setTimeout(resolve21, 0));
37013
+ await new Promise((resolve22) => setTimeout(resolve22, 0));
36048
37014
  const answer = await askJev(apiKey, question, { context });
36049
37015
  const presentation = presentJevAnswer(question, answer);
36050
37016
  setResult(eventKey, {
@@ -36542,7 +37508,7 @@ ${lines.join("\n")}` }]);
36542
37508
  void (async () => {
36543
37509
  try {
36544
37510
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
36545
- const file = await loadSession(join56(sessionsDir3(), `${currentId}.json`));
37511
+ const file = await loadSession(join58(sessionsDir3(), `${currentId}.json`));
36546
37512
  const cps = file.checkpoints ?? [];
36547
37513
  if (cps.length === 0) {
36548
37514
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: "no checkpoints in this session" }]);
@@ -36587,7 +37553,7 @@ ${lines.join("\n")}` }]);
36587
37553
  try {
36588
37554
  ctx.ensureSessionId();
36589
37555
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
36590
- const filePath = join56(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
37556
+ const filePath = join58(sessionsDir3(), `${ctx.sessionIdRef.current}.json`);
36591
37557
  await addCheckpoint(filePath, cp);
36592
37558
  setEvents((e) => [...e, { kind: "info", key: mkKey2(), text: `checkpoint saved: "${label}"` }]);
36593
37559
  } catch (e) {
@@ -37087,12 +38053,12 @@ also read (legacy): ${legacyGlobalSettingsPath()}, ${legacyProjectSettingsPath(c
37087
38053
 
37088
38054
  // src/init/context-generator.ts
37089
38055
  import { existsSync as existsSync17, statSync as statSync6 } from "fs";
37090
- import { join as join57 } from "path";
38056
+ import { join as join59 } from "path";
37091
38057
  function detectFlavor(cwd) {
37092
38058
  for (const [flavor, signatures] of Object.entries(FLAVOR_SIGNATURES)) {
37093
38059
  if (flavor === "generic") continue;
37094
38060
  for (const sig of signatures) {
37095
- const path = join57(cwd, sig);
38061
+ const path = join59(cwd, sig);
37096
38062
  if (sig.includes("*")) {
37097
38063
  try {
37098
38064
  const parts = sig.split("*");
@@ -37113,14 +38079,14 @@ function detectFlavor(cwd) {
37113
38079
  }
37114
38080
  function findFile(cwd, candidates) {
37115
38081
  for (const c of candidates) {
37116
- if (existsSync17(join57(cwd, c))) return c;
38082
+ if (existsSync17(join59(cwd, c))) return c;
37117
38083
  }
37118
38084
  return null;
37119
38085
  }
37120
38086
  function findSourceRoots(cwd) {
37121
38087
  const roots = [];
37122
38088
  for (const r of SOURCE_ROOT_CANDIDATES) {
37123
- const p = join57(cwd, r);
38089
+ const p = join59(cwd, r);
37124
38090
  try {
37125
38091
  const s = statSync6(p);
37126
38092
  if (s.isDirectory()) roots.push(r);
@@ -37131,9 +38097,9 @@ function findSourceRoots(cwd) {
37131
38097
  }
37132
38098
  function findCiConfig(cwd) {
37133
38099
  for (const c of CI_PATHS) {
37134
- if (existsSync17(join57(cwd, c))) {
38100
+ if (existsSync17(join59(cwd, c))) {
37135
38101
  try {
37136
- const s = statSync6(join57(cwd, c));
38102
+ const s = statSync6(join59(cwd, c));
37137
38103
  return s.isDirectory() ? c : c;
37138
38104
  } catch {
37139
38105
  }
@@ -37270,7 +38236,7 @@ function analyzeProject(cwd) {
37270
38236
  ciConfig: findCiConfig(cwd),
37271
38237
  readme: findFile(cwd, ["README.md", "README.rst", "README.txt", "Readme.md"]),
37272
38238
  sourceRoots: findSourceRoots(cwd),
37273
- hasGit: existsSync17(join57(cwd, ".git"))
38239
+ hasGit: existsSync17(join59(cwd, ".git"))
37274
38240
  };
37275
38241
  }
37276
38242
  function bashDiscoveryCommands(profile) {
@@ -37445,7 +38411,7 @@ function buildInitTasks(targetFilename) {
37445
38411
  }
37446
38412
  function buildInitPrompt(cwd) {
37447
38413
  const existingName = ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].find(
37448
- (n) => existsSync17(join57(cwd, n))
38414
+ (n) => existsSync17(join59(cwd, n))
37449
38415
  );
37450
38416
  const isRefresh = existingName !== void 0;
37451
38417
  const targetFilename = existingName ?? "AGENTS.md";
@@ -37533,7 +38499,7 @@ var init_context_generator = __esm({
37533
38499
 
37534
38500
  // src/init/run-init.ts
37535
38501
  import { existsSync as existsSync18 } from "fs";
37536
- import { join as join58 } from "path";
38502
+ import { join as join60 } from "path";
37537
38503
  async function runInit(deps) {
37538
38504
  const {
37539
38505
  cfg,
@@ -37629,7 +38595,7 @@ async function runInit(deps) {
37629
38595
  lspManagerRef.current.notifyChange(path, content);
37630
38596
  } else {
37631
38597
  void import("fs/promises").then(
37632
- ({ readFile: readFile40 }) => readFile40(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
38598
+ ({ readFile: readFile42 }) => readFile42(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
37633
38599
  })
37634
38600
  );
37635
38601
  }
@@ -37730,7 +38696,7 @@ async function runInit(deps) {
37730
38696
  }
37731
38697
  }
37732
38698
  } }));
37733
- if (existsSync18(join58(cwd, targetFilename))) {
38699
+ if (existsSync18(join60(cwd, targetFilename))) {
37734
38700
  if (cacheStableRef.current) {
37735
38701
  messagesRef.current[1] = {
37736
38702
  role: "system",
@@ -37824,11 +38790,11 @@ var init_run_init = __esm({
37824
38790
  });
37825
38791
 
37826
38792
  // 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";
38793
+ import { readdir as readdir9, stat as stat14, readFile as readFile41 } from "fs/promises";
38794
+ import { join as join61, extname as extname2 } from "path";
37829
38795
  async function dirExists(path) {
37830
38796
  try {
37831
- const s = await stat13(path);
38797
+ const s = await stat14(path);
37832
38798
  return s.isDirectory();
37833
38799
  } catch {
37834
38800
  return false;
@@ -37836,7 +38802,7 @@ async function dirExists(path) {
37836
38802
  }
37837
38803
  async function fileExists(path) {
37838
38804
  try {
37839
- const s = await stat13(path);
38805
+ const s = await stat14(path);
37840
38806
  return s.isFile();
37841
38807
  } catch {
37842
38808
  return false;
@@ -37849,20 +38815,20 @@ async function scanSkillDir(dirPath, source) {
37849
38815
  for (const entry of entries) {
37850
38816
  if (!entry.isFile()) continue;
37851
38817
  if (!SKILL_EXTENSIONS.has(extname2(entry.name))) continue;
37852
- files.push({ filePath: join59(dirPath, entry.name), source });
38818
+ files.push({ filePath: join61(dirPath, entry.name), source });
37853
38819
  }
37854
38820
  return files;
37855
38821
  }
37856
38822
  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");
38823
+ const agentsSkills = await scanSkillDir(join61(cwd, ".agents", "skills"), "agents");
38824
+ const agentsMd = await fileExists(join61(cwd, "AGENTS.md")) ? [{ filePath: join61(cwd, "AGENTS.md"), source: "agents-md" }] : [];
38825
+ const githubSkills = await scanSkillDir(join61(cwd, ".github", "skills"), "github");
38826
+ const kimiflareSkills = await scanSkillDir(join61(cwd, ".kimiflare", "skills"), "kimiflare");
37861
38827
  const ordered = [...agentsSkills, ...agentsMd, ...githubSkills, ...kimiflareSkills];
37862
38828
  return ordered;
37863
38829
  }
37864
38830
  async function readSkillFile(filePath) {
37865
- const bytes = await readFile39(filePath);
38831
+ const bytes = await readFile41(filePath);
37866
38832
  return { bytes, text: bytes.toString("utf-8") };
37867
38833
  }
37868
38834
  var SKILL_EXTENSIONS;
@@ -38069,7 +39035,7 @@ var init_skills = __esm({
38069
39035
 
38070
39036
  // src/ui/run-startup-tasks.ts
38071
39037
  import { existsSync as existsSync19 } from "fs";
38072
- import { join as join60 } from "path";
39038
+ import { join as join62 } from "path";
38073
39039
  function runStartupTasks(deps) {
38074
39040
  const {
38075
39041
  cfg,
@@ -38092,7 +39058,7 @@ function runStartupTasks(deps) {
38092
39058
  }
38093
39059
  });
38094
39060
  if (cfg.memoryEnabled) {
38095
- const dbPath = cfg.memoryDbPath ?? join60(process.cwd(), ".kimiflare", "memory.db");
39061
+ const dbPath = cfg.memoryDbPath ?? join62(process.cwd(), ".kimiflare", "memory.db");
38096
39062
  const manager = new MemoryManager({
38097
39063
  dbPath,
38098
39064
  ...auth,
@@ -38125,7 +39091,7 @@ function runStartupTasks(deps) {
38125
39091
  });
38126
39092
  const cwd = process.cwd();
38127
39093
  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)))) {
39094
+ if (["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync19(join62(cwd, name)))) {
38129
39095
  const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
38130
39096
  const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
38131
39097
  if (driftCount >= 5) {
@@ -38136,7 +39102,7 @@ function runStartupTasks(deps) {
38136
39102
  memoryManagerRef.current?.close();
38137
39103
  memoryManagerRef.current = null;
38138
39104
  }
38139
- const skillDbPath = cfg.memoryDbPath ?? join60(process.cwd(), ".kimiflare", "memory.db");
39105
+ const skillDbPath = cfg.memoryDbPath ?? join62(process.cwd(), ".kimiflare", "memory.db");
38140
39106
  const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
38141
39107
  initSkillsSchema(skillDb);
38142
39108
  void indexSkills({
@@ -38812,8 +39778,8 @@ var init_turn_nudges = __esm({
38812
39778
  });
38813
39779
 
38814
39780
  // 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";
39781
+ import { mkdir as mkdir22, writeFile as writeFile19, unlink as unlink6 } from "fs/promises";
39782
+ import { dirname as dirname28 } from "path";
38817
39783
  async function saveCustomCommand(opts2) {
38818
39784
  const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
38819
39785
  const filepath = `${dir}/${opts2.name}.md`;
@@ -38824,8 +39790,8 @@ async function saveCustomCommand(opts2) {
38824
39790
  if (opts2.effort) data.effort = opts2.effort;
38825
39791
  const frontmatter = serializeFrontmatter(data);
38826
39792
  const content = frontmatter + opts2.template;
38827
- await mkdir21(dirname27(filepath), { recursive: true });
38828
- await writeFile18(filepath, content, "utf8");
39793
+ await mkdir22(dirname28(filepath), { recursive: true });
39794
+ await writeFile19(filepath, content, "utf8");
38829
39795
  return { filepath };
38830
39796
  }
38831
39797
  async function deleteCustomCommand(cmd) {
@@ -38924,7 +39890,7 @@ __export(app_exports, {
38924
39890
  import { useState as useState6, useRef as useRef4, useEffect as useEffect2, useCallback as useCallback4, useMemo } from "react";
38925
39891
  import { useApp, render } from "ink";
38926
39892
  import { existsSync as existsSync20 } from "fs";
38927
- import { join as join61 } from "path";
39893
+ import { join as join63 } from "path";
38928
39894
  import { jsx } from "react/jsx-runtime";
38929
39895
  function App({
38930
39896
  initialCfg,
@@ -39178,7 +40144,7 @@ function App({
39178
40144
  void (async () => {
39179
40145
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39180
40146
  try {
39181
- await doResumeSession(join61(sessionsDir3(), `${initialResumeSessionId}.json`));
40147
+ await doResumeSession(join63(sessionsDir3(), `${initialResumeSessionId}.json`));
39182
40148
  if (initialResumeReason === "crash") {
39183
40149
  setEvents((e) => [
39184
40150
  ...e,
@@ -39411,7 +40377,7 @@ function App({
39411
40377
  saveCheckpoint: async (checkpoint) => {
39412
40378
  ensureSessionId();
39413
40379
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
39414
- await addCheckpoint(join61(sessionsDir3(), `${sessionIdRef.current}.json`), checkpoint);
40380
+ await addCheckpoint(join63(sessionsDir3(), `${sessionIdRef.current}.json`), checkpoint);
39415
40381
  },
39416
40382
  summarize: () => generateContinuationSummary({
39417
40383
  messages: messagesRef.current,
@@ -39930,27 +40896,20 @@ ${result2.output ?? "(output not captured)"}`,
39930
40896
  setEvents((e) => [...e, { kind: "info", key: mkKey(), text: nudge }]);
39931
40897
  }
39932
40898
  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
- }
40899
+ const promptGate = await submitUserPrompt({
40900
+ hooks: hooksManagerRef.current,
40901
+ sessionId: ensureSessionId(trimmed),
40902
+ cwd: process.cwd(),
40903
+ prompt: opts2?.userPromptForHooks ?? display,
40904
+ tier: classification.tier,
40905
+ resumed: messagesRef.current.some((m) => m.role === "assistant"),
40906
+ model: overrideModel ?? cfg.model,
40907
+ auth: llmAuthFromConfig(cfg)
40908
+ });
40909
+ if (promptGate.blocked) {
40910
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `hook blocked the prompt: ${promptGate.reason}` }]);
40911
+ endTurn();
40912
+ return;
39954
40913
  }
39955
40914
  messagesRef.current.push({ role: "user", content });
39956
40915
  const traceUserSource = denyFeedbackRef.current ? "deny_feedback" : "typed";
@@ -39970,7 +40929,7 @@ ${result2.output ?? "(output not captured)"}`,
39970
40929
  turnCounterRef.current += 1;
39971
40930
  const nudges = turnNudges({
39972
40931
  turn: turnCounterRef.current,
39973
- hasContextFile: ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync20(join61(process.cwd(), name))),
40932
+ hasContextFile: ["AGENTS.md", "KIMI.md", "KIMIFLARE.md", "AGENT.md"].some((name) => existsSync20(join63(process.cwd(), name))),
39974
40933
  contextStale: kimiMdStale,
39975
40934
  mode: modeRef.current,
39976
40935
  freshAfterTurns: cfg?.autoFreshSuggestionTurns ?? DEFAULT_AUTO_FRESH_SUGGESTION_TURNS,
@@ -40147,7 +41106,7 @@ ${result2.output ?? "(output not captured)"}`,
40147
41106
  lspManagerRef.current.notifyChange(path, content2);
40148
41107
  } else {
40149
41108
  void import("fs/promises").then(
40150
- ({ readFile: readFile40 }) => readFile40(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
41109
+ ({ readFile: readFile42 }) => readFile42(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
40151
41110
  })
40152
41111
  );
40153
41112
  }
@@ -40655,7 +41614,7 @@ ${result2.output ?? "(output not captured)"}`,
40655
41614
  lsp: {
40656
41615
  servers: cfg?.lspServers ?? {},
40657
41616
  scope: lspScope,
40658
- hasProjectDir: existsSync20(join61(process.cwd(), ".kimiflare"))
41617
+ hasProjectDir: existsSync20(join63(process.cwd(), ".kimiflare"))
40659
41618
  }
40660
41619
  });
40661
41620
  });
@@ -40747,6 +41706,7 @@ var init_app = __esm({
40747
41706
  init_run_init();
40748
41707
  init_connect();
40749
41708
  init_session_lifecycle();
41709
+ init_prompt_submit();
40750
41710
  init_run_startup_tasks();
40751
41711
  init_manager_init();
40752
41712
  init_run_compact();
@@ -40786,6 +41746,7 @@ init_sessions();
40786
41746
  init_context_budget();
40787
41747
  init_subagent_stats();
40788
41748
  init_session_lifecycle();
41749
+ init_prompt_submit();
40789
41750
  init_attachments();
40790
41751
  init_glob();
40791
41752
  init_permissions_evaluator();
@@ -40830,8 +41791,8 @@ async function resolveFiles(filePatterns, cwd) {
40830
41791
  const resolved = /* @__PURE__ */ new Set();
40831
41792
  for (const pattern of filePatterns) {
40832
41793
  try {
40833
- const stat14 = await import("fs/promises").then((m) => m.stat(resolve7(cwd, pattern)));
40834
- if (stat14.isFile()) {
41794
+ const stat15 = await import("fs/promises").then((m) => m.stat(resolve7(cwd, pattern)));
41795
+ if (stat15.isFile()) {
40835
41796
  resolved.add(resolve7(cwd, pattern));
40836
41797
  continue;
40837
41798
  }
@@ -40959,6 +41920,33 @@ async function runPrintMode(opts2) {
40959
41920
  });
40960
41921
  const usageWrites = [];
40961
41922
  const auth = llmAuthFromConfig(opts2);
41923
+ const gate = await submitUserPrompt({
41924
+ hooks,
41925
+ sessionId: sessionFile.id,
41926
+ cwd,
41927
+ prompt: opts2.prompt,
41928
+ tier: policy.classification.tier,
41929
+ resumed: messages.some((m) => m.role === "assistant"),
41930
+ model: opts2.model,
41931
+ auth
41932
+ });
41933
+ if (gate.blocked) {
41934
+ const msg = `prompt blocked by a UserPromptSubmit hook: ${gate.reason}`;
41935
+ if (format === "text") {
41936
+ process.stderr.write(`\x1B[31mautopilot: ${msg}\x1B[0m
41937
+ `);
41938
+ } else if (format === "json") {
41939
+ jsonOutput.durationMs = Date.now() - startMs;
41940
+ process.stdout.write(JSON.stringify({ ...jsonOutput, error: msg, blocked: { reason: gate.reason } }, null, 2) + "\n");
41941
+ } else {
41942
+ emitStreamJson("error", { message: msg, code: PROMPT_BLOCKED_EXIT_CODE, reason: "prompt_blocked" });
41943
+ }
41944
+ process.exitCode = PROMPT_BLOCKED_EXIT_CODE;
41945
+ await endSessions("exit");
41946
+ trace.emit("session_end", { reason: "exit" }, null);
41947
+ await trace.flush();
41948
+ return;
41949
+ }
40962
41950
  const callbacks = {
40963
41951
  onUsageFinal: (u, meta) => {
40964
41952
  const lookup = opts2.openrouterApiKey && !auth.customEndpoint && meta?.generationId ? { apiKey: opts2.openrouterApiKey, meta } : void 0;
@@ -41204,7 +42192,7 @@ function registerEvalCommand(program2) {
41204
42192
  // src/index.tsx
41205
42193
  var resumeCommand = null;
41206
42194
  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)");
42195
+ 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
42196
  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
42197
  const cfg = await loadConfig();
41210
42198
  if (cmdOpts.verify) {
@@ -41235,10 +42223,51 @@ program.command("cost").description("Show cost attribution by task type (require
41235
42223
  const { runCostCommand: runCostCommand2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
41236
42224
  await runCostCommand2({ ...cmdOpts, config: cfg });
41237
42225
  });
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
- });
42226
+ async function runRemoteCommand(fn) {
42227
+ const controller = new AbortController();
42228
+ const onInterrupt = () => controller.abort();
42229
+ process.once("SIGINT", onInterrupt);
42230
+ try {
42231
+ const code = await fn(controller.signal);
42232
+ if (typeof code === "number") process.exitCode = code;
42233
+ } catch (error) {
42234
+ if (controller.signal.aborted) return;
42235
+ const { describeError: describeError2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42236
+ console.error(`autopilot: ${describeError2(error)}`);
42237
+ process.exitCode = 1;
42238
+ } finally {
42239
+ process.off("SIGINT", onInterrupt);
42240
+ }
42241
+ }
42242
+ var remoteCommand = program.command("remote").description("Pair this machine with Autopilot servers that run tasks for you");
42243
+ 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) => {
42244
+ const { remoteAdd: remoteAdd2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42245
+ await remoteAdd2({ url, open: cmdOpts.open, signal, ...cmdOpts.name ? { name: cmdOpts.name } : {} });
42246
+ }));
42247
+ remoteCommand.command("list").description("List paired servers").action(() => runRemoteCommand(async () => {
42248
+ const { remoteList: remoteList2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42249
+ await remoteList2();
42250
+ }));
42251
+ remoteCommand.command("remove").description("Forget a paired server").argument("<name>").action((name) => runRemoteCommand(async () => {
42252
+ const { remoteRemove: remoteRemove2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42253
+ return await remoteRemove2(name) ? 0 : 1;
42254
+ }));
42255
+ 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 () => {
42256
+ const { listRuns: listRuns2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42257
+ await listRuns2({ ...cmdOpts.on ? { remote: cmdOpts.on } : {}, json: !!cmdOpts.json });
42258
+ }));
42259
+ 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) => {
42260
+ const { watchRun: watchRun2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42261
+ return watchRun2({ id, signal, ...cmdOpts.on ? { remote: cmdOpts.on } : {} });
42262
+ }));
42263
+ 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 () => {
42264
+ const { cancelRun: cancelRun2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42265
+ await cancelRun2({ id, ...cmdOpts.on ? { remote: cmdOpts.on } : {} });
42266
+ }));
42267
+ 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 () => {
42268
+ const { pullRun: pullRun2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42269
+ await pullRun2({ id, cwd: process.cwd(), ...cmdOpts.on ? { remote: cmdOpts.on } : {} });
42270
+ }));
41242
42271
  registerEvalCommand(program);
41243
42272
  var asterCommand = program.command("aster").description("Manage the Aster mobile API");
41244
42273
  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) => {
@@ -41639,7 +42668,9 @@ program.command("serve").description("Start a headless HTTP server for API acces
41639
42668
  process.exit(1);
41640
42669
  }
41641
42670
  });
41642
- program.action(async () => {
42671
+ var positionalTask = [];
42672
+ program.action(async (task = []) => {
42673
+ positionalTask = task;
41643
42674
  await main();
41644
42675
  const { runShutdownTasks: runShutdownTasks2 } = await Promise.resolve().then(() => (init_exit(), exit_exports));
41645
42676
  await runShutdownTasks2();
@@ -41654,10 +42685,43 @@ async function initOtel() {
41654
42685
  onShutdown2("otel-flush", shutdownOtelSink2, SHUTDOWN_PHASE2.flush);
41655
42686
  }
41656
42687
  }
42688
+ async function runRemoteTask() {
42689
+ if (opts.on === void 0) {
42690
+ console.error(`autopilot: unknown command "${positionalTask[0]}". To run a task here use -p "\u2026"; on a server, --on <server> "\u2026".`);
42691
+ process.exit(2);
42692
+ }
42693
+ if (opts.local) {
42694
+ console.error("autopilot: --on and --local can't be combined.");
42695
+ process.exit(2);
42696
+ }
42697
+ const task = (positionalTask.join(" ") || opts.print || "").trim();
42698
+ if (!task) {
42699
+ console.error(`autopilot: --on needs a task, e.g. autopilot --on ${opts.on} "fix the flaky checkout test"`);
42700
+ process.exit(2);
42701
+ }
42702
+ const cfg = await loadConfig().catch(() => null);
42703
+ await runRemoteCommand(async (signal) => {
42704
+ const { runOnRemote: runOnRemote2 } = await Promise.resolve().then(() => (init_cli2(), cli_exports2));
42705
+ return runOnRemote2({
42706
+ remote: opts.on,
42707
+ task,
42708
+ cwd: opts.dir ?? process.cwd(),
42709
+ signal,
42710
+ detach: !!opts.detach,
42711
+ ...opts.model ? { model: opts.model } : {},
42712
+ ...cfg?.model ? { preferredModel: cfg.model } : {},
42713
+ ...opts.repo ? { repository: opts.repo } : {}
42714
+ });
42715
+ });
42716
+ }
41657
42717
  async function main() {
41658
42718
  await initOtel();
41659
42719
  const { installExitHandlers: installExitHandlers2 } = await Promise.resolve().then(() => (init_exit(), exit_exports));
41660
42720
  installExitHandlers2({ crashes: true });
42721
+ if (opts.on !== void 0 || positionalTask.length > 0) {
42722
+ await runRemoteTask();
42723
+ return;
42724
+ }
41661
42725
  const globalCfg = await loadConfig();
41662
42726
  const updateResult = await checkForUpdate();
41663
42727
  let cfg = globalCfg;
@@ -41836,12 +42900,12 @@ function requireModel(model) {
41836
42900
  process.exit(2);
41837
42901
  }
41838
42902
  async function noCredentialsMessage() {
41839
- const { readFile: readFile40 } = await import("fs/promises");
42903
+ const { readFile: readFile42 } = await import("fs/promises");
41840
42904
  const { configPath: configPath2 } = await Promise.resolve().then(() => (init_config(), config_exports));
41841
42905
  const { resolveActiveProvider: resolveActiveProvider2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
41842
42906
  let persisted = {};
41843
42907
  try {
41844
- persisted = JSON.parse(await readFile40(configPath2(), "utf8"));
42908
+ persisted = JSON.parse(await readFile42(configPath2(), "utf8"));
41845
42909
  } catch {
41846
42910
  }
41847
42911
  const { id, preset } = resolveActiveProvider2(persisted);
@@ -41869,11 +42933,11 @@ async function promptHidden(question) {
41869
42933
  }
41870
42934
  });
41871
42935
  const rl = createInterface4({ input: process.stdin, output, terminal: true });
41872
- return new Promise((resolve21) => {
42936
+ return new Promise((resolve22) => {
41873
42937
  rl.question(question, (answer) => {
41874
42938
  rl.close();
41875
42939
  process.stdout.write("\n");
41876
- resolve21(answer);
42940
+ resolve22(answer);
41877
42941
  });
41878
42942
  muted = true;
41879
42943
  });