autopilot-ai 2.0.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/sdk/index.js CHANGED
@@ -2023,7 +2023,6 @@ async function loadConfig() {
2023
2023
  lspServers: persisted.lspServers,
2024
2024
  costAttribution: readBooleanEnv("KIMI_COST_ATTRIBUTION") ?? persisted.costAttribution ?? true,
2025
2025
  filePicker: readBooleanEnv("KIMIFLARE_FILE_PICKER") ?? persisted.filePicker ?? true,
2026
- theme: persisted.theme,
2027
2026
  shell: process.env.KIMIFLARE_SHELL ?? persisted.shell,
2028
2027
  remoteWorkerUrl: persisted.remoteWorkerUrl,
2029
2028
  remoteAuthSecret: persisted.remoteAuthSecret,
@@ -3176,6 +3175,146 @@ var init_redact = __esm({
3176
3175
  }
3177
3176
  });
3178
3177
 
3178
+ // src/util/line-diff.ts
3179
+ function splitLines(text3) {
3180
+ if (text3 === "") return [];
3181
+ const lines = text3.split("\n");
3182
+ if (lines[lines.length - 1] === "") lines.pop();
3183
+ return lines;
3184
+ }
3185
+ function myers(a, b) {
3186
+ const n = a.length;
3187
+ const m = b.length;
3188
+ const max = n + m;
3189
+ if (max === 0) return [];
3190
+ const offset = max;
3191
+ let v = new Int32Array(2 * max + 2);
3192
+ const trace = [];
3193
+ for (let d = 0; d <= max; d++) {
3194
+ if ((n + m) * d > MAX_MYERS_COST) return null;
3195
+ trace.push(v.slice());
3196
+ const next = v;
3197
+ for (let k = -d; k <= d; k += 2) {
3198
+ let x;
3199
+ if (k === -d || k !== d && next[offset + k - 1] < next[offset + k + 1]) x = next[offset + k + 1];
3200
+ else x = next[offset + k - 1] + 1;
3201
+ let y = x - k;
3202
+ while (x < n && y < m && a[x] === b[y]) {
3203
+ x++;
3204
+ y++;
3205
+ }
3206
+ next[offset + k] = x;
3207
+ if (x >= n && y >= m) {
3208
+ trace.push(next.slice());
3209
+ return backtrack(trace, a, b, offset);
3210
+ }
3211
+ }
3212
+ v = next;
3213
+ }
3214
+ return null;
3215
+ }
3216
+ function backtrack(trace, a, b, offset) {
3217
+ const ops = [];
3218
+ let x = a.length;
3219
+ let y = b.length;
3220
+ for (let d = trace.length - 2; d >= 0; d--) {
3221
+ const v = trace[d];
3222
+ const k = x - y;
3223
+ let prevK;
3224
+ if (k === -d || k !== d && v[offset + k - 1] < v[offset + k + 1]) prevK = k + 1;
3225
+ else prevK = k - 1;
3226
+ const prevX = d === 0 ? 0 : v[offset + prevK];
3227
+ const prevY = prevX - prevK;
3228
+ while (x > prevX && y > prevY) {
3229
+ ops.push({ kind: " ", text: a[x - 1] });
3230
+ x--;
3231
+ y--;
3232
+ }
3233
+ if (d === 0) break;
3234
+ if (x === prevX) ops.push({ kind: "+", text: b[y - 1] });
3235
+ else ops.push({ kind: "-", text: a[x - 1] });
3236
+ x = prevX;
3237
+ y = prevY;
3238
+ }
3239
+ while (x > 0 && y > 0) {
3240
+ ops.push({ kind: " ", text: a[x - 1] });
3241
+ x--;
3242
+ y--;
3243
+ }
3244
+ return ops.reverse();
3245
+ }
3246
+ function editScript(a, b) {
3247
+ let pre = 0;
3248
+ while (pre < a.length && pre < b.length && a[pre] === b[pre]) pre++;
3249
+ let suf = 0;
3250
+ while (suf < a.length - pre && suf < b.length - pre && a[a.length - 1 - suf] === b[b.length - 1 - suf]) suf++;
3251
+ const midA = a.slice(pre, a.length - suf);
3252
+ const midB = b.slice(pre, b.length - suf);
3253
+ const mid = myers(midA, midB) ?? [
3254
+ ...midA.map((text3) => ({ kind: "-", text: text3 })),
3255
+ ...midB.map((text3) => ({ kind: "+", text: text3 }))
3256
+ ];
3257
+ return [
3258
+ ...a.slice(0, pre).map((text3) => ({ kind: " ", text: text3 })),
3259
+ ...mid,
3260
+ ...a.slice(a.length - suf).map((text3) => ({ kind: " ", text: text3 }))
3261
+ ];
3262
+ }
3263
+ function structuredPatch(oldText, newText, context = DEFAULT_CONTEXT) {
3264
+ const ops = editScript(splitLines(oldText), splitLines(newText));
3265
+ const hunks = [];
3266
+ let oldNo = 1;
3267
+ let newNo = 1;
3268
+ const pos = ops.map((op) => {
3269
+ const at = { old: oldNo, new: newNo };
3270
+ if (op.kind !== "+") oldNo++;
3271
+ if (op.kind !== "-") newNo++;
3272
+ return at;
3273
+ });
3274
+ let i = 0;
3275
+ while (i < ops.length) {
3276
+ if (ops[i].kind === " ") {
3277
+ i++;
3278
+ continue;
3279
+ }
3280
+ const start = Math.max(0, i - context);
3281
+ let end = i;
3282
+ for (; ; ) {
3283
+ while (end < ops.length && ops[end].kind !== " ") end++;
3284
+ let gap = end;
3285
+ while (gap < ops.length && ops[gap].kind === " ") gap++;
3286
+ if (gap < ops.length && gap - end <= 2 * context) {
3287
+ end = gap;
3288
+ continue;
3289
+ }
3290
+ end = Math.min(ops.length, end + context);
3291
+ break;
3292
+ }
3293
+ const slice = ops.slice(start, end);
3294
+ const oldLines = slice.filter((o) => o.kind !== "+").length;
3295
+ const newLines = slice.filter((o) => o.kind !== "-").length;
3296
+ const first = pos[start];
3297
+ hunks.push({
3298
+ // jsdiff's convention: a hunk with no old (or new) lines starts one line earlier.
3299
+ oldStart: oldLines === 0 ? first.old - 1 : first.old,
3300
+ oldLines,
3301
+ newStart: newLines === 0 ? first.new - 1 : first.new,
3302
+ newLines,
3303
+ lines: slice.map((o) => o.kind + o.text)
3304
+ });
3305
+ i = end;
3306
+ }
3307
+ return hunks;
3308
+ }
3309
+ var DEFAULT_CONTEXT, MAX_MYERS_COST;
3310
+ var init_line_diff = __esm({
3311
+ "src/util/line-diff.ts"() {
3312
+ "use strict";
3313
+ DEFAULT_CONTEXT = 3;
3314
+ MAX_MYERS_COST = 4e6;
3315
+ }
3316
+ });
3317
+
3179
3318
  // src/hooks/claude-compat.ts
3180
3319
  function claudeToolName(tool, args) {
3181
3320
  if (tool === "edit" && Array.isArray(args.edits)) return "MultiEdit";
@@ -3260,9 +3399,35 @@ function claudeToolResponse(tool, args, cwd, result2) {
3260
3399
  }
3261
3400
  if ((tool === "write" || tool === "edit") && isStr(args.path) && !failed) {
3262
3401
  response.filePath = resolvePath(cwd, args.path);
3402
+ if (result2.fileChange) Object.assign(response, claudeFileChangeFields(tool, args, result2.fileChange));
3263
3403
  }
3264
3404
  return response;
3265
3405
  }
3406
+ function claudeFileChangeFields(tool, args, change) {
3407
+ const fields = {};
3408
+ if (tool === "write") {
3409
+ fields.type = change.existed ? "update" : "create";
3410
+ if (isStr(args.content)) fields.content = args.content;
3411
+ } else if (Array.isArray(args.edits)) {
3412
+ fields.edits = editPairs(args);
3413
+ } else {
3414
+ fields.oldString = args.old_string;
3415
+ fields.newString = args.new_string;
3416
+ fields.replaceAll = args.replace_all === true;
3417
+ }
3418
+ if (!change.existed) {
3419
+ fields.originalFile = null;
3420
+ fields.structuredPatch = [];
3421
+ } else {
3422
+ if (change.original !== null && Buffer.byteLength(change.original, "utf8") <= ORIGINAL_FILE_CAP_BYTES) {
3423
+ fields.originalFile = change.original;
3424
+ }
3425
+ if (change.original !== null && change.updated !== null) {
3426
+ fields.structuredPatch = structuredPatch(change.original, change.updated);
3427
+ }
3428
+ }
3429
+ return fields;
3430
+ }
3266
3431
  function claudeError(result2) {
3267
3432
  if (isNum(result2.exitCode) && result2.exitCode !== 0) {
3268
3433
  const output = [result2.stdout, result2.stderr].filter((s) => s && s.trim()).map((s) => s.trimEnd()).join("\n");
@@ -3295,12 +3460,13 @@ function claudeToolFields(payload) {
3295
3460
  }
3296
3461
  return { tool_name: toolName, ...fields };
3297
3462
  }
3298
- var CLAUDE_TOOL_NAMES, FILE_EDIT_NAMES, isStr, isNum;
3463
+ var CLAUDE_TOOL_NAMES, FILE_EDIT_NAMES, isStr, isNum, ORIGINAL_FILE_CAP_BYTES;
3299
3464
  var init_claude_compat = __esm({
3300
3465
  "src/hooks/claude-compat.ts"() {
3301
3466
  "use strict";
3302
3467
  init_edit();
3303
3468
  init_paths();
3469
+ init_line_diff();
3304
3470
  CLAUDE_TOOL_NAMES = {
3305
3471
  bash: "Bash",
3306
3472
  edit: "Edit",
@@ -3315,6 +3481,7 @@ var init_claude_compat = __esm({
3315
3481
  FILE_EDIT_NAMES = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit"]);
3316
3482
  isStr = (v) => typeof v === "string";
3317
3483
  isNum = (v) => typeof v === "number";
3484
+ ORIGINAL_FILE_CAP_BYTES = 256 * 1024;
3318
3485
  }
3319
3486
  });
3320
3487
 
@@ -3921,6 +4088,10 @@ function hookWirePayload(payload) {
3921
4088
  if (payload.session_id) Object.assign(wire, hookTranscriptFields(payload.session_id));
3922
4089
  if (isToolEventPayload(payload)) {
3923
4090
  Object.assign(wire, claudeToolFields(payload));
4091
+ if ((payload.event === "PostToolUse" || payload.event === "PostToolUseFailure") && payload.result.fileChange) {
4092
+ const { fileChange: _fileChange, ...result2 } = payload.result;
4093
+ wire.result = result2;
4094
+ }
3924
4095
  if (payload.tool_call_id) wire.tool_use_id = payload.tool_call_id;
3925
4096
  if (payload.event === "PermissionRequest") wire.permission_mode = claudePermissionMode(payload.mode);
3926
4097
  }
@@ -4743,7 +4914,7 @@ __export(openrouter_catalog_exports, {
4743
4914
  loadOpenRouterCatalog: () => loadOpenRouterCatalog,
4744
4915
  mapOpenRouterModel: () => mapOpenRouterModel
4745
4916
  });
4746
- import { readFile as readFile16, writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
4917
+ import { readFile as readFile17, writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
4747
4918
  import { homedir as homedir17 } from "os";
4748
4919
  import { join as join29, dirname as dirname14 } from "path";
4749
4920
  function catalogCachePath() {
@@ -4799,7 +4970,7 @@ async function fetchOpenRouterCatalog(fetchImpl2 = fetch) {
4799
4970
  }
4800
4971
  async function readCache2() {
4801
4972
  try {
4802
- const raw = await readFile16(catalogCachePath(), "utf8");
4973
+ const raw = await readFile17(catalogCachePath(), "utf8");
4803
4974
  const parsed = JSON.parse(raw);
4804
4975
  return parsed.version === CACHE_VERSION ? parsed : null;
4805
4976
  } catch {
@@ -4858,7 +5029,7 @@ __export(requesty_catalog_exports, {
4858
5029
  loadRequestyCatalog: () => loadRequestyCatalog,
4859
5030
  mapRequestyModel: () => mapRequestyModel
4860
5031
  });
4861
- import { readFile as readFile17, writeFile as writeFile10, mkdir as mkdir11 } from "fs/promises";
5032
+ import { readFile as readFile18, writeFile as writeFile10, mkdir as mkdir11 } from "fs/promises";
4862
5033
  import { homedir as homedir18 } from "os";
4863
5034
  import { join as join30, dirname as dirname15 } from "path";
4864
5035
  function catalogCachePath2() {
@@ -4919,7 +5090,7 @@ async function fetchRequestyCatalog(fetchImpl2 = fetch) {
4919
5090
  }
4920
5091
  async function readCache3() {
4921
5092
  try {
4922
- const raw = await readFile17(catalogCachePath2(), "utf8");
5093
+ const raw = await readFile18(catalogCachePath2(), "utf8");
4923
5094
  const parsed = JSON.parse(raw);
4924
5095
  if (parsed.version !== CACHE_VERSION2) return null;
4925
5096
  return {
@@ -4997,7 +5168,7 @@ __export(provider_catalog_exports, {
4997
5168
  mapModelsDevModel: () => mapModelsDevModel,
4998
5169
  mapModelsDevProvider: () => mapModelsDevProvider
4999
5170
  });
5000
- import { readFile as readFile18, writeFile as writeFile11, mkdir as mkdir12 } from "fs/promises";
5171
+ import { readFile as readFile19, writeFile as writeFile11, mkdir as mkdir12 } from "fs/promises";
5001
5172
  import { homedir as homedir19 } from "os";
5002
5173
  import { join as join31, dirname as dirname16 } from "path";
5003
5174
  function mapModelsDevModel(m) {
@@ -5037,7 +5208,7 @@ function cachePath() {
5037
5208
  }
5038
5209
  async function readCache4() {
5039
5210
  try {
5040
- const parsed = JSON.parse(await readFile18(cachePath(), "utf8"));
5211
+ const parsed = JSON.parse(await readFile19(cachePath(), "utf8"));
5041
5212
  return parsed.version === CACHE_VERSION3 ? parsed : null;
5042
5213
  } catch {
5043
5214
  return null;
@@ -5413,6 +5584,89 @@ async function endSessions(reason = "exit", timeoutMs = 3e3) {
5413
5584
  await Promise.all([...[...started.keys()].map((id) => endSession(id, reason, timeoutMs)), ...ending]);
5414
5585
  }
5415
5586
 
5587
+ // src/intent/classify.ts
5588
+ var INTENT_PATTERNS = {
5589
+ qa: /\b(what|how|why|explain|describe|what's|what is)\b/i,
5590
+ diagnose: /\b(broken|failing|error|bug|crash|why.*fail|not working)\b/i,
5591
+ verify: /\b(correct|right|verify|review|check|is this|does this)\b/i,
5592
+ polish: /\b(rename|refactor|extract|move|clean|lint|format)\b/i,
5593
+ small_edit: /\b(add|change|update|fix|remove|delete)\b.+\b(line|here|this|variable|function)\b/i,
5594
+ feature_bounded: /\b(add|implement|create|support)\b.+\b(flag|option|param|arg|field)\b/i,
5595
+ feature_exploratory: /\b(add|implement|migrate|integrate|build)\b.+\b(module|system|auth|oauth|framework|service)\b/i,
5596
+ explore: /\b(how.*work|architecture|structure|where.*used|find.*all|understand)\b/i,
5597
+ meta: /\b(plan|design|strategy|ontology|roadmap|approach)\b/i
5598
+ };
5599
+ var 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;
5600
+ function classifyIntent(prompt) {
5601
+ let intentScore = 0;
5602
+ let matchedIntent = "other";
5603
+ for (const [intent, pattern] of Object.entries(INTENT_PATTERNS)) {
5604
+ const matches = (prompt.match(pattern) || []).length;
5605
+ if (matches > intentScore) {
5606
+ intentScore = matches;
5607
+ matchedIntent = intent;
5608
+ }
5609
+ }
5610
+ const hasFileMentions = (prompt.match(/@\w+|\b[\w/-]+\.(ts|tsx|js|jsx|py|go|rs)\b/g) || []).length;
5611
+ const hasMutatingVerb = /\b(add|create|write|edit|delete|remove|rename|migrate|implement)\b/i.test(prompt);
5612
+ const isQuestion = prompt.trim().endsWith("?") || /\b(what|how|why|is|does|can)\b/i.test(prompt.split(" ")[0] || "");
5613
+ const hasHeavySignal = HEAVY_SIGNAL.test(prompt);
5614
+ const rawScore = Math.min(
5615
+ 1,
5616
+ intentScore * 0.25 + (hasFileMentions > 2 ? 0.3 : hasFileMentions * 0.1) + (hasMutatingVerb ? 0.25 : 0) + (isQuestion ? 0 : 0.1) + (hasHeavySignal ? 0.6 : 0)
5617
+ );
5618
+ const tier = rawScore < 0.3 ? "light" : rawScore < 0.65 ? "medium" : "heavy";
5619
+ return {
5620
+ intent: hasHeavySignal && matchedIntent === "other" ? "explore" : matchedIntent,
5621
+ rawScore,
5622
+ tier,
5623
+ confidence: 0.5 + (intentScore > 0 || hasHeavySignal ? 0.3 : 0) + (hasFileMentions > 0 ? 0.1 : 0)
5624
+ };
5625
+ }
5626
+
5627
+ // src/hooks/prompt-submit.ts
5628
+ init_llm_auth();
5629
+ var PROMPT_BLOCKED_DEFAULT_REASON = "UserPromptSubmit hook blocked the prompt";
5630
+ var PromptBlockedError = class extends Error {
5631
+ reason;
5632
+ code = "prompt_blocked";
5633
+ constructor(reason) {
5634
+ super(`prompt blocked by a UserPromptSubmit hook: ${reason}`);
5635
+ this.name = "PromptBlockedError";
5636
+ this.reason = reason;
5637
+ }
5638
+ };
5639
+ async function submitUserPrompt(info) {
5640
+ let provider;
5641
+ try {
5642
+ provider = info.auth ? providerPresetOf(info.auth).id : void 0;
5643
+ } catch {
5644
+ provider = void 0;
5645
+ }
5646
+ await noteSessionStart({
5647
+ ...info.hooks ? { hooks: info.hooks } : {},
5648
+ sessionId: info.sessionId,
5649
+ cwd: info.cwd,
5650
+ resumed: info.resumed,
5651
+ model: info.model,
5652
+ ...provider ? { provider } : {}
5653
+ });
5654
+ if (!info.hooks?.hasEnabledHooks("UserPromptSubmit")) return { blocked: false };
5655
+ const outcome = await info.hooks.fire(
5656
+ "UserPromptSubmit",
5657
+ {
5658
+ event: "UserPromptSubmit",
5659
+ session_id: info.sessionId,
5660
+ cwd: info.cwd,
5661
+ prompt: info.prompt,
5662
+ tier: info.tier ?? classifyIntent(info.prompt).tier
5663
+ },
5664
+ null
5665
+ );
5666
+ if (!outcome.vetoed) return { blocked: false };
5667
+ return { blocked: true, reason: outcome.vetoReason || PROMPT_BLOCKED_DEFAULT_REASON };
5668
+ }
5669
+
5416
5670
  // src/sdk/process-exit.ts
5417
5671
  init_otel_sink();
5418
5672
  init_logger();
@@ -9186,15 +9440,15 @@ async function gitNumstat(cwd, opts = {}) {
9186
9440
  if (opts.includeUntracked) {
9187
9441
  try {
9188
9442
  const { stdout } = await execFileAsync("git", ["ls-files", "--others", "--exclude-standard"], { cwd, timeout: 5e3 });
9189
- const { readFile: readFile20, stat: stat8 } = await import("fs/promises");
9443
+ const { readFile: readFile21, stat: stat9 } = await import("fs/promises");
9190
9444
  const { join: join36 } = await import("path");
9191
9445
  for (const file of stdout.split("\n").filter(Boolean)) {
9192
9446
  let lines = 0;
9193
9447
  try {
9194
9448
  const path = join36(cwd, file);
9195
- const size = (await stat8(path)).size;
9449
+ const size = (await stat9(path)).size;
9196
9450
  if (size > 0 && size <= 1024 * 1024) {
9197
- const text3 = await readFile20(path, "utf8");
9451
+ const text3 = await readFile21(path, "utf8");
9198
9452
  lines = text3.split("\n").length - (text3.endsWith("\n") ? 1 : 0);
9199
9453
  }
9200
9454
  } catch {
@@ -14613,8 +14867,8 @@ function waitForProcessStartTime(pid, jobId, exitPath) {
14613
14867
  function readProcessStartTime(pid) {
14614
14868
  if (process.platform === "linux") {
14615
14869
  try {
14616
- const stat8 = readFileSync6(`/proc/${pid}/stat`, "utf8");
14617
- const fieldsAfterCommand = stat8.slice(stat8.lastIndexOf(")") + 1).trim().split(/\s+/);
14870
+ const stat9 = readFileSync6(`/proc/${pid}/stat`, "utf8");
14871
+ const fieldsAfterCommand = stat9.slice(stat9.lastIndexOf(")") + 1).trim().split(/\s+/);
14618
14872
  return fieldsAfterCommand[19] ? `linux:${fieldsAfterCommand[19]}` : null;
14619
14873
  } catch {
14620
14874
  return null;
@@ -15485,7 +15739,8 @@ var waitForTool = {
15485
15739
  };
15486
15740
 
15487
15741
  // src/tools/executor.ts
15488
- import { realpath as realpath4 } from "fs/promises";
15742
+ init_paths();
15743
+ import { readFile as readFile15, realpath as realpath4, stat as stat6 } from "fs/promises";
15489
15744
  import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5, sep as sep3 } from "path";
15490
15745
  var ALL_TOOLS = [
15491
15746
  { ...readTool, isReadOnly: true },
@@ -15538,6 +15793,17 @@ function toPermissionResult(d) {
15538
15793
  return d;
15539
15794
  }
15540
15795
  var HOOK_RESULT_CONTENT_CAP_BYTES = 4 * 1024;
15796
+ var HOOK_FILE_SNAPSHOT_MAX_BYTES = 1024 * 1024;
15797
+ async function snapshotForHook(abs) {
15798
+ try {
15799
+ const st = await stat6(abs);
15800
+ if (!st.isFile()) return { existed: true, content: null };
15801
+ if (st.size > HOOK_FILE_SNAPSHOT_MAX_BYTES) return { existed: true, content: null };
15802
+ return { existed: true, content: await readFile15(abs, "utf8") };
15803
+ } catch {
15804
+ return { existed: false, content: null };
15805
+ }
15806
+ }
15541
15807
  function shellResultForHook(meta) {
15542
15808
  if (!meta || meta.exitCode === void 0) return {};
15543
15809
  return {
@@ -15556,6 +15822,15 @@ function capContentForHook(s) {
15556
15822
  return `${cut}
15557
15823
  [\u2026truncated for hook payload]`;
15558
15824
  }
15825
+ async function fileChangeAfter(tool, args, cwd, before) {
15826
+ let updated = null;
15827
+ if (tool === "write" && typeof args.content === "string") {
15828
+ updated = Buffer.byteLength(args.content, "utf8") <= HOOK_FILE_SNAPSHOT_MAX_BYTES ? args.content : null;
15829
+ } else {
15830
+ updated = (await snapshotForHook(resolvePath(cwd, args.path))).content;
15831
+ }
15832
+ return { existed: before.existed, original: before.content, updated };
15833
+ }
15559
15834
  var ToolExecutor = class {
15560
15835
  sessionAllowed = /* @__PURE__ */ new Set();
15561
15836
  tools;
@@ -15733,8 +16008,10 @@ var ToolExecutor = class {
15733
16008
  runStore.recordToolIntent(ctx.runId, call.id, call.name, args);
15734
16009
  intentRecorded = true;
15735
16010
  }
16011
+ const before = this.wantsFileChange(call.name, args) ? await snapshotForHook(resolvePath(ctx.cwd, args.path)) : void 0;
15736
16012
  const result2 = await tool.run(args, ctx);
15737
16013
  toolCompleted = true;
16014
+ const fileChange = before ? await fileChangeAfter(call.name, args, ctx.cwd, before) : void 0;
15738
16015
  const normalized = normalizeToolOutput(result2);
15739
16016
  tr?.raw(normalized);
15740
16017
  if (runStore && ctx.runId) {
@@ -15786,7 +16063,7 @@ var ToolExecutor = class {
15786
16063
  artifactId: reduced.artifactId,
15787
16064
  ...normalized.waitRequest ? { waitRequest: normalized.waitRequest } : {}
15788
16065
  };
15789
- this.firePostToolUse(call, args, success, ctx, normalized.meta);
16066
+ this.firePostToolUse(call, args, success, ctx, normalized.meta, fileChange);
15790
16067
  return success;
15791
16068
  } catch (e) {
15792
16069
  if (e instanceof RunToolBudgetExceededError) ctx.onRunBudgetExceeded?.(e.reason);
@@ -15857,7 +16134,7 @@ var ToolExecutor = class {
15857
16134
  * `content` field is capped via `capContentForHook` to keep large tool
15858
16135
  * outputs from blowing the OS env-var limits on `KIMIFLARE_HOOK_PAYLOAD`.
15859
16136
  */
15860
- firePostToolUse(call, args, result2, ctx, meta) {
16137
+ firePostToolUse(call, args, result2, ctx, meta, fileChange) {
15861
16138
  const hooks = this.hooks;
15862
16139
  if (!hooks) return;
15863
16140
  const post = hooks.hasEnabledHooks("PostToolUse");
@@ -15867,7 +16144,8 @@ var ToolExecutor = class {
15867
16144
  ok: result2.ok,
15868
16145
  content: capContentForHook(result2.content),
15869
16146
  errorCode: result2.errorCode,
15870
- ...shellResultForHook(meta)
16147
+ ...shellResultForHook(meta),
16148
+ ...fileChange ? { fileChange } : {}
15871
16149
  };
15872
16150
  const base = {
15873
16151
  session_id: ctx.sessionId ?? null,
@@ -15885,6 +16163,11 @@ var ToolExecutor = class {
15885
16163
  void hooks.fire("PostToolUseFailure", { event: "PostToolUseFailure", ...base, error: claudeError(resultFields) }, call.name, ctx.signal).catch(swallow);
15886
16164
  }
15887
16165
  }
16166
+ /** True when a PostToolUse hook would get this write / edit call's diff. */
16167
+ wantsFileChange(tool, args) {
16168
+ if (tool !== "write" && tool !== "edit" || typeof args.path !== "string" || !args.path) return false;
16169
+ return !!this.hooks && (this.hooks.hasEnabledHooks("PostToolUse") || this.hooks.hasEnabledHooks("PostToolUseFailure"));
16170
+ }
15888
16171
  permissionKey(tool, args) {
15889
16172
  if (tool.name === "bash" && typeof args.command === "string") {
15890
16173
  const firstToken = args.command.trim().split(/\s+/)[0] ?? "";
@@ -17603,7 +17886,7 @@ function makeLspTools(manager) {
17603
17886
  init_sessions();
17604
17887
 
17605
17888
  // src/usage-tracker.ts
17606
- import { readFile as readFile15, rename as rename3 } from "fs/promises";
17889
+ import { readFile as readFile16, rename as rename3 } from "fs/promises";
17607
17890
  import { homedir as homedir16 } from "os";
17608
17891
  import { join as join28 } from "path";
17609
17892
  import { EventEmitter as EventEmitter2 } from "events";
@@ -17635,7 +17918,7 @@ init_openrouter();
17635
17918
  init_storage_limits();
17636
17919
 
17637
17920
  // src/util/atomic-file.ts
17638
- import { mkdir as mkdir9, rename as rename2, rm as rm3, stat as stat6, writeFile as writeFile8 } from "fs/promises";
17921
+ import { mkdir as mkdir9, rename as rename2, rm as rm3, stat as stat7, writeFile as writeFile8 } from "fs/promises";
17639
17922
  import { dirname as dirname13 } from "path";
17640
17923
  import { randomUUID as randomUUID6 } from "crypto";
17641
17924
  async function writeFileAtomic(path, data) {
@@ -17662,7 +17945,7 @@ async function withFileLock2(lockPath, fn) {
17662
17945
  held = true;
17663
17946
  } catch (err) {
17664
17947
  if (err.code !== "EEXIST") break;
17665
- const age = await stat6(lockPath).then((s) => Date.now() - s.mtimeMs, () => 0);
17948
+ const age = await stat7(lockPath).then((s) => Date.now() - s.mtimeMs, () => 0);
17666
17949
  if (age > LOCK_STALE_MS) {
17667
17950
  await rm3(lockPath, { recursive: true, force: true }).catch(() => void 0);
17668
17951
  continue;
@@ -17712,7 +17995,7 @@ function cutoffDate(daysBack) {
17712
17995
  async function loadLog() {
17713
17996
  let raw;
17714
17997
  try {
17715
- raw = await readFile15(usagePath(), "utf8");
17998
+ raw = await readFile16(usagePath(), "utf8");
17716
17999
  } catch {
17717
18000
  return { version: LOG_VERSION2, days: [], sessions: [] };
17718
18001
  }
@@ -17738,7 +18021,7 @@ function withLock(fn) {
17738
18021
  }
17739
18022
  async function loadHistory() {
17740
18023
  try {
17741
- const raw = await readFile15(historyPath(), "utf8");
18024
+ const raw = await readFile16(historyPath(), "utf8");
17742
18025
  const lines = raw.split("\n").filter((l) => l.trim());
17743
18026
  const entries = [];
17744
18027
  for (const line of lines) {
@@ -17927,48 +18210,6 @@ init_llm_auth();
17927
18210
 
17928
18211
  // src/agent/session-core.ts
17929
18212
  init_llm_auth();
17930
-
17931
- // src/intent/classify.ts
17932
- var INTENT_PATTERNS = {
17933
- qa: /\b(what|how|why|explain|describe|what's|what is)\b/i,
17934
- diagnose: /\b(broken|failing|error|bug|crash|why.*fail|not working)\b/i,
17935
- verify: /\b(correct|right|verify|review|check|is this|does this)\b/i,
17936
- polish: /\b(rename|refactor|extract|move|clean|lint|format)\b/i,
17937
- small_edit: /\b(add|change|update|fix|remove|delete)\b.+\b(line|here|this|variable|function)\b/i,
17938
- feature_bounded: /\b(add|implement|create|support)\b.+\b(flag|option|param|arg|field)\b/i,
17939
- feature_exploratory: /\b(add|implement|migrate|integrate|build)\b.+\b(module|system|auth|oauth|framework|service)\b/i,
17940
- explore: /\b(how.*work|architecture|structure|where.*used|find.*all|understand)\b/i,
17941
- meta: /\b(plan|design|strategy|ontology|roadmap|approach)\b/i
17942
- };
17943
- var 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;
17944
- function classifyIntent(prompt) {
17945
- let intentScore = 0;
17946
- let matchedIntent = "other";
17947
- for (const [intent, pattern] of Object.entries(INTENT_PATTERNS)) {
17948
- const matches = (prompt.match(pattern) || []).length;
17949
- if (matches > intentScore) {
17950
- intentScore = matches;
17951
- matchedIntent = intent;
17952
- }
17953
- }
17954
- const hasFileMentions = (prompt.match(/@\w+|\b[\w/-]+\.(ts|tsx|js|jsx|py|go|rs)\b/g) || []).length;
17955
- const hasMutatingVerb = /\b(add|create|write|edit|delete|remove|rename|migrate|implement)\b/i.test(prompt);
17956
- const isQuestion = prompt.trim().endsWith("?") || /\b(what|how|why|is|does|can)\b/i.test(prompt.split(" ")[0] || "");
17957
- const hasHeavySignal = HEAVY_SIGNAL.test(prompt);
17958
- const rawScore = Math.min(
17959
- 1,
17960
- intentScore * 0.25 + (hasFileMentions > 2 ? 0.3 : hasFileMentions * 0.1) + (hasMutatingVerb ? 0.25 : 0) + (isQuestion ? 0 : 0.1) + (hasHeavySignal ? 0.6 : 0)
17961
- );
17962
- const tier = rawScore < 0.3 ? "light" : rawScore < 0.65 ? "medium" : "heavy";
17963
- return {
17964
- intent: hasHeavySignal && matchedIntent === "other" ? "explore" : matchedIntent,
17965
- rawScore,
17966
- tier,
17967
- confidence: 0.5 + (intentScore > 0 || hasHeavySignal ? 0.3 : 0) + (hasFileMentions > 0 ? 0.1 : 0)
17968
- };
17969
- }
17970
-
17971
- // src/agent/session-core.ts
17972
18213
  function lastUserText(messages) {
17973
18214
  for (let i = messages.length - 1; i >= 0; i--) {
17974
18215
  const m = messages[i];
@@ -18097,7 +18338,7 @@ init_redact();
18097
18338
 
18098
18339
  // src/trace/retention.ts
18099
18340
  init_storage_limits();
18100
- import { readdir as readdir5, rm as rm4, stat as stat7 } from "fs/promises";
18341
+ import { readdir as readdir5, rm as rm4, stat as stat8 } from "fs/promises";
18101
18342
  import { join as join32 } from "path";
18102
18343
 
18103
18344
  // src/trace/session-start.ts
@@ -18412,6 +18653,16 @@ var InternalSession = class {
18412
18653
  return;
18413
18654
  }
18414
18655
  const mode = options?.mode ?? this.currentMode;
18656
+ const gate = await submitUserPrompt({
18657
+ ...this.hooks ? { hooks: this.hooks } : {},
18658
+ sessionId: this.sessionId,
18659
+ cwd: this.cwd,
18660
+ prompt: text3,
18661
+ resumed: this.messages.some((m) => m.role === "assistant"),
18662
+ model: this.model,
18663
+ auth: llmAuthFromConfig(this.config)
18664
+ });
18665
+ if (gate.blocked) throw new PromptBlockedError(gate.reason);
18415
18666
  const messageId = `msg_${this.nextMessageId++}`;
18416
18667
  let userContent2 = text3;
18417
18668
  if (options?.images && options.images.length > 0) {
@@ -18757,7 +19008,7 @@ ${notes.join("\n")}` };
18757
19008
  this.lspManager?.notifyChange(path, content);
18758
19009
  } else {
18759
19010
  void import("fs/promises").then(
18760
- ({ readFile: readFile20 }) => readFile20(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
19011
+ ({ readFile: readFile21 }) => readFile21(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
18761
19012
  })
18762
19013
  ).catch(() => {
18763
19014
  });
@@ -18964,7 +19215,7 @@ async function startRpcServer(input = process.stdin, output = process.stdout, cr
18964
19215
  }
18965
19216
 
18966
19217
  // src/sdk/aster-cell.ts
18967
- import { appendFile as appendFile3, mkdir as mkdir13, readFile as readFile19, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
19218
+ import { appendFile as appendFile3, mkdir as mkdir13, readFile as readFile20, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
18968
19219
  import { createServer as createServer2 } from "http";
18969
19220
  import { PassThrough, Writable } from "stream";
18970
19221
  import { join as join35, resolve as resolvePath2 } from "path";
@@ -19163,13 +19414,13 @@ async function readCommand(req) {
19163
19414
  async function readState() {
19164
19415
  let state;
19165
19416
  try {
19166
- state = JSON.parse(await readFile19(TURN_STATE, "utf8"));
19417
+ state = JSON.parse(await readFile20(TURN_STATE, "utf8"));
19167
19418
  } catch {
19168
19419
  state = { sessionId: null, activeRunId: null, initialized: false, cursor: 0 };
19169
19420
  }
19170
19421
  let last;
19171
19422
  try {
19172
- const lines = (await readFile19(EVENT_LOG, "utf8")).trim().split("\n").filter(Boolean);
19423
+ const lines = (await readFile20(EVENT_LOG, "utf8")).trim().split("\n").filter(Boolean);
19173
19424
  last = lines.length ? JSON.parse(lines.at(-1)) : void 0;
19174
19425
  } catch {
19175
19426
  }
@@ -19197,7 +19448,7 @@ async function persistState(state) {
19197
19448
  async function readEvents(after) {
19198
19449
  let contents;
19199
19450
  try {
19200
- contents = await readFile19(EVENT_LOG, "utf8");
19451
+ contents = await readFile20(EVENT_LOG, "utf8");
19201
19452
  } catch {
19202
19453
  return [];
19203
19454
  }
@@ -19218,7 +19469,7 @@ async function executeAsterCellControl(requestId) {
19218
19469
  if (!CONTROL_UUID_RE.test(requestId)) throw new Error("invalid_control_request");
19219
19470
  const requestPath = resolvePath2(REQUEST_DIR, requestId + ".json");
19220
19471
  if (!requestPath.startsWith(REQUEST_DIR + "/")) throw new Error("invalid_control_request");
19221
- const request = JSON.parse(await readFile19(requestPath, "utf8"));
19472
+ const request = JSON.parse(await readFile20(requestPath, "utf8"));
19222
19473
  await unlink3(requestPath).catch(() => {
19223
19474
  });
19224
19475
  if (request.method === "GET" && request.path.startsWith("/events?after=")) {
@@ -19363,6 +19614,7 @@ var RunWakeScheduler = class {
19363
19614
  export {
19364
19615
  DEFAULT_MODEL,
19365
19616
  DEFAULT_REASONING_EFFORT,
19617
+ PromptBlockedError,
19366
19618
  RunStore,
19367
19619
  RunWakeScheduler,
19368
19620
  createAgentSession,