kimiflare 0.50.1 → 0.51.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
@@ -2467,6 +2467,7 @@ Use console.log() to return results. Only console.log output will be sent back t
2467
2467
  let cumulativePromptTokens = 0;
2468
2468
  let iter = 0;
2469
2469
  let budgetExhausted = false;
2470
+ let loopExhausted = false;
2470
2471
  while (true) {
2471
2472
  if (budgetExhausted) {
2472
2473
  opts2.messages.push({
@@ -2474,6 +2475,12 @@ Use console.log() to return results. Only console.log output will be sent back t
2474
2475
  content: "You have reached the cumulative input token budget for this session. Please synthesize your findings and provide a final summary of what was accomplished."
2475
2476
  });
2476
2477
  }
2478
+ if (loopExhausted) {
2479
+ opts2.messages.push({
2480
+ role: "system",
2481
+ content: "You have repeatedly called the same tools with identical arguments and are stuck in a loop. Please synthesize what you know from the conversation history and provide a final answer."
2482
+ });
2483
+ }
2477
2484
  if (iter >= max) {
2478
2485
  if (opts2.callbacks.onToolLimitReached) {
2479
2486
  const decision = await opts2.callbacks.onToolLimitReached();
@@ -2651,6 +2658,7 @@ Use console.log() to return results. Only console.log output will be sent back t
2651
2658
  logger.info("turn:complete", { sessionId: opts2.sessionId, durationMs: Math.round(performance.now() - turnStart) });
2652
2659
  return;
2653
2660
  }
2661
+ let blockedCount = 0;
2654
2662
  for (const tc of toolCalls) {
2655
2663
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
2656
2664
  const loopSignature = `${tc.function.name}:${stableStringify(tc.function.arguments)}`;
@@ -2673,6 +2681,7 @@ Use console.log() to return results. Only console.log output will be sent back t
2673
2681
  opts2.callbacks.onToolResult?.(loopResult);
2674
2682
  recentToolCalls.push(loopSignature);
2675
2683
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
2684
+ blockedCount++;
2676
2685
  continue;
2677
2686
  }
2678
2687
  if (tc.function.name === "web_fetch") {
@@ -2700,6 +2709,7 @@ Use console.log() to return results. Only console.log output will be sent back t
2700
2709
  opts2.callbacks.onToolResult?.(budgetResult);
2701
2710
  recentToolCalls.push(loopSignature);
2702
2711
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
2712
+ blockedCount++;
2703
2713
  continue;
2704
2714
  }
2705
2715
  if (domainCount >= WEB_FETCH_DOMAIN_THRESHOLD) {
@@ -2720,6 +2730,7 @@ Use console.log() to return results. Only console.log output will be sent back t
2720
2730
  opts2.callbacks.onToolResult?.(loopResult);
2721
2731
  recentToolCalls.push(loopSignature);
2722
2732
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
2733
+ blockedCount++;
2723
2734
  continue;
2724
2735
  }
2725
2736
  webFetchHistory.push({ url, domain });
@@ -2855,6 +2866,9 @@ ${sandboxResult.output}` : `${warningPrefix}${sandboxResult.output}`;
2855
2866
  if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
2856
2867
  }
2857
2868
  }
2869
+ if (blockedCount === toolCalls.length && toolCalls.length > 0) {
2870
+ loopExhausted = true;
2871
+ }
2858
2872
  if (opts2.sessionId) {
2859
2873
  const current = driftAccumulator.get(opts2.sessionId) ?? 0;
2860
2874
  if (current > 0) {
@@ -2883,6 +2897,9 @@ ${sandboxResult.output}` : `${warningPrefix}${sandboxResult.output}`;
2883
2897
  if (budgetExhausted) {
2884
2898
  throw new BudgetExhaustedError();
2885
2899
  }
2900
+ if (loopExhausted) {
2901
+ throw new AgentLoopError();
2902
+ }
2886
2903
  }
2887
2904
  }
2888
2905
  function validateToolArguments(raw) {
@@ -2894,7 +2911,7 @@ function validateToolArguments(raw) {
2894
2911
  return "{}";
2895
2912
  }
2896
2913
  }
2897
- var BudgetExhaustedError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
2914
+ var BudgetExhaustedError, AgentLoopError, codeModeApiCache, driftAccumulator, DRIFT_THRESHOLD, MAX_PROMPT_TOKENS, MAX_TOOL_CONTENT_CHARS;
2898
2915
  var init_loop = __esm({
2899
2916
  "src/agent/loop.ts"() {
2900
2917
  "use strict";
@@ -2913,6 +2930,12 @@ var init_loop = __esm({
2913
2930
  this.name = "BudgetExhaustedError";
2914
2931
  }
2915
2932
  };
2933
+ AgentLoopError = class extends Error {
2934
+ constructor(message2 = "Agent got stuck repeating the same tool calls") {
2935
+ super(message2);
2936
+ this.name = "AgentLoopError";
2937
+ }
2938
+ };
2916
2939
  codeModeApiCache = /* @__PURE__ */ new Map();
2917
2940
  driftAccumulator = /* @__PURE__ */ new Map();
2918
2941
  DRIFT_THRESHOLD = 5;
@@ -2953,88 +2976,47 @@ function isBlockedInPlanMode(toolName) {
2953
2976
  if (toolName === "browser_fetch") return true;
2954
2977
  return false;
2955
2978
  }
2956
- function tokenizeCommand(command) {
2957
- const tokens = [];
2958
- let current = "";
2959
- let inQuote = null;
2960
- for (const ch of command) {
2961
- if (inQuote) {
2962
- if (ch === inQuote) {
2963
- inQuote = null;
2964
- } else {
2965
- current += ch;
2966
- }
2979
+ function getTokens(s) {
2980
+ const toks = [];
2981
+ let cur = "";
2982
+ let q = null;
2983
+ for (const ch of s) {
2984
+ if (q) {
2985
+ if (ch === q) q = null;
2986
+ else cur += ch;
2967
2987
  } else if (ch === '"' || ch === "'") {
2968
- inQuote = ch;
2988
+ q = ch;
2969
2989
  } else if (/\s/.test(ch)) {
2970
- if (current) {
2971
- tokens.push(current);
2972
- current = "";
2990
+ if (cur) {
2991
+ toks.push(cur);
2992
+ cur = "";
2973
2993
  }
2974
2994
  } else {
2975
- current += ch;
2995
+ cur += ch;
2976
2996
  }
2977
2997
  }
2978
- if (current) tokens.push(current);
2979
- return tokens;
2998
+ if (cur) toks.push(cur);
2999
+ return toks;
2980
3000
  }
2981
- function splitByOperators(command, operators) {
2982
- const segments = [];
2983
- let current = "";
2984
- let inQuote = null;
2985
- for (let i = 0; i < command.length; i++) {
2986
- const ch = command[i];
2987
- if (inQuote) {
2988
- if (ch === inQuote) {
2989
- inQuote = null;
2990
- }
2991
- current += ch;
2992
- continue;
2993
- }
2994
- if (ch === '"' || ch === "'") {
2995
- inQuote = ch;
2996
- current += ch;
2997
- continue;
2998
- }
2999
- let matchedOp = false;
3000
- for (const op of operators) {
3001
- if (command.slice(i, i + op.length) === op) {
3002
- segments.push(current.trim());
3003
- current = "";
3004
- i += op.length - 1;
3005
- matchedOp = true;
3006
- break;
3007
- }
3008
- }
3009
- if (matchedOp) continue;
3010
- current += ch;
3011
- }
3012
- if (current.trim()) segments.push(current.trim());
3013
- return segments;
3014
- }
3015
- function isReadOnlyBashSegment(command) {
3016
- const trimmed = command.trim();
3017
- if (!trimmed) return false;
3018
- const tokens = tokenizeCommand(trimmed);
3019
- if (tokens.length === 0) return false;
3020
- const cmd = tokens[0];
3021
- const args = tokens.slice(1);
3001
+ function isReadOnlySegment(seg) {
3002
+ const toks = getTokens(seg.trim());
3003
+ if (toks.length === 0) return false;
3004
+ const [cmd, sub, ...rest] = toks;
3022
3005
  if (cmd === "git") {
3023
- const sub = args[0] ?? "";
3024
- const allowed = GIT_READONLY_SUBCOMMANDS[sub];
3006
+ const allowed = GIT_READONLY_SUBCOMMANDS[sub ?? ""];
3025
3007
  if (allowed === void 0) return false;
3026
3008
  if (allowed === true) return true;
3027
3009
  switch (sub) {
3028
3010
  case "branch":
3029
- return !args.some((a) => /^-[dDmMcC]/.test(a));
3011
+ return !rest.some((a) => /^-[dDmMcC]/.test(a));
3030
3012
  case "stash":
3031
- return args[1] === "list";
3013
+ return rest[0] === "list";
3032
3014
  case "remote":
3033
- return args[1] === "-v" || args[1] === "--verbose" || args.length === 1;
3015
+ return rest[0] === "-v" || rest[0] === "--verbose" || rest.length === 0;
3034
3016
  case "tag":
3035
- return args[1] === "-l" || args[1] === "--list" || args.length === 1;
3017
+ return rest[0] === "-l" || rest[0] === "--list" || rest.length === 0;
3036
3018
  case "config":
3037
- return args[1] === "--list" || args[1]?.startsWith("--get") === true || args.length === 1;
3019
+ return rest[0] === "--list" || rest[0]?.startsWith("--get") === true || rest.length === 0;
3038
3020
  default:
3039
3021
  return false;
3040
3022
  }
@@ -3045,10 +3027,32 @@ function isReadOnlyBash(command) {
3045
3027
  const trimmed = command.trim();
3046
3028
  if (!trimmed) return false;
3047
3029
  if (DANGEROUS_PATTERNS.test(trimmed)) return false;
3048
- const segments = splitByOperators(trimmed, ["|", "&&"]);
3049
- if (segments.length === 0) return false;
3050
- for (const segment of segments) {
3051
- if (!isReadOnlyBashSegment(segment.trim())) return false;
3030
+ const segs = [];
3031
+ let cur = "";
3032
+ let q = null;
3033
+ for (let i = 0; i < trimmed.length; i++) {
3034
+ const ch = trimmed[i];
3035
+ if (q) {
3036
+ if (ch === q) q = null;
3037
+ cur += ch;
3038
+ } else if (ch === '"' || ch === "'") {
3039
+ q = ch;
3040
+ cur += ch;
3041
+ } else if (trimmed.slice(i, i + 2) === "&&") {
3042
+ segs.push(cur);
3043
+ cur = "";
3044
+ i++;
3045
+ } else if (ch === "|") {
3046
+ segs.push(cur);
3047
+ cur = "";
3048
+ } else {
3049
+ cur += ch;
3050
+ }
3051
+ }
3052
+ if (cur.trim()) segs.push(cur);
3053
+ if (segs.length === 0) return false;
3054
+ for (const seg of segs) {
3055
+ if (!isReadOnlySegment(seg.trim())) return false;
3052
3056
  }
3053
3057
  return true;
3054
3058
  }
@@ -6078,13 +6082,18 @@ __export(auth_exports, {
6078
6082
  registerDevice: () => registerDevice,
6079
6083
  saveCloudCredentials: () => saveCloudCredentials
6080
6084
  });
6081
- import { readFile as readFile11, writeFile as writeFile8 } from "fs/promises";
6082
- import { homedir as homedir9 } from "os";
6085
+ import { readFile as readFile11, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
6086
+ import { createHash } from "crypto";
6087
+ import { homedir as homedir9, hostname, userInfo } from "os";
6083
6088
  import { join as join15 } from "path";
6084
6089
  function cloudCredPath() {
6085
6090
  const xdg = process.env.XDG_CONFIG_HOME || join15(homedir9(), ".config");
6086
6091
  return join15(xdg, "kimiflare", "cloud.json");
6087
6092
  }
6093
+ function deviceIdPath() {
6094
+ const xdg = process.env.XDG_DATA_HOME || join15(homedir9(), ".local", "share");
6095
+ return join15(xdg, "kimiflare", "device_id");
6096
+ }
6088
6097
  function generateCode() {
6089
6098
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
6090
6099
  let out = "";
@@ -6093,16 +6102,51 @@ function generateCode() {
6093
6102
  }
6094
6103
  return out;
6095
6104
  }
6096
- function generateDeviceId() {
6105
+ function deriveStableDeviceId() {
6106
+ const seed = `${hostname()}:${userInfo().username}:${homedir9()}`;
6107
+ const hash = createHash("sha256").update(seed).digest();
6108
+ return hash.subarray(0, 16).toString("hex");
6109
+ }
6110
+ function generateRandomDeviceId() {
6097
6111
  const arr = new Uint8Array(16);
6098
6112
  crypto.getRandomValues(arr);
6099
6113
  return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
6100
6114
  }
6101
- function generateDeviceCodes() {
6115
+ async function getOrCreateDeviceId() {
6116
+ try {
6117
+ const raw = await readFile11(deviceIdPath(), "utf8");
6118
+ const id = raw.trim();
6119
+ if (/^[0-9a-f]{32}$/i.test(id)) return id;
6120
+ } catch {
6121
+ }
6122
+ try {
6123
+ const raw = await readFile11(cloudCredPath(), "utf8");
6124
+ const parsed = JSON.parse(raw);
6125
+ if (parsed.deviceId && /^[0-9a-f]{32}$/i.test(parsed.deviceId)) {
6126
+ await persistDeviceId(parsed.deviceId);
6127
+ return parsed.deviceId;
6128
+ }
6129
+ } catch {
6130
+ }
6131
+ let deviceId;
6132
+ try {
6133
+ deviceId = deriveStableDeviceId();
6134
+ } catch {
6135
+ deviceId = generateRandomDeviceId();
6136
+ }
6137
+ await persistDeviceId(deviceId);
6138
+ return deviceId;
6139
+ }
6140
+ async function persistDeviceId(deviceId) {
6141
+ const p = deviceIdPath();
6142
+ await mkdir8(join15(p, ".."), { recursive: true });
6143
+ await writeFile8(p, deviceId, { mode: 384 });
6144
+ }
6145
+ async function generateDeviceCodes() {
6102
6146
  const deviceCode = `device-${generateCode()}-${Date.now()}`;
6103
6147
  const userCode = `${generateCode()}-${generateCode()}`;
6104
6148
  const authUrl = `${CLOUD_API_URL}/auth?code=${encodeURIComponent(userCode)}`;
6105
- const deviceId = generateDeviceId();
6149
+ const deviceId = await getOrCreateDeviceId();
6106
6150
  return { deviceCode, userCode, authUrl, deviceId };
6107
6151
  }
6108
6152
  async function registerDevice(codes) {
@@ -6157,6 +6201,10 @@ async function loadCloudCredentials() {
6157
6201
  const raw = await readFile11(cloudCredPath(), "utf8");
6158
6202
  const parsed = JSON.parse(raw);
6159
6203
  if (parsed.expiresAt && parsed.expiresAt > Date.now() / 1e3 && parsed.accessToken) {
6204
+ if (parsed.deviceId) {
6205
+ await persistDeviceId(parsed.deviceId).catch(() => {
6206
+ });
6207
+ }
6160
6208
  return parsed;
6161
6209
  }
6162
6210
  } catch {
@@ -6166,6 +6214,10 @@ async function loadCloudCredentials() {
6166
6214
  async function saveCloudCredentials(creds) {
6167
6215
  const p = cloudCredPath();
6168
6216
  await writeFile8(p, JSON.stringify(creds, null, 2), "utf8");
6217
+ if (creds.deviceId) {
6218
+ await persistDeviceId(creds.deviceId).catch(() => {
6219
+ });
6220
+ }
6169
6221
  }
6170
6222
  async function clearCloudCredentials() {
6171
6223
  try {
@@ -6173,9 +6225,14 @@ async function clearCloudCredentials() {
6173
6225
  await unlink5(cloudCredPath());
6174
6226
  } catch {
6175
6227
  }
6228
+ try {
6229
+ const { unlink: unlink5 } = await import("fs/promises");
6230
+ await unlink5(deviceIdPath());
6231
+ } catch {
6232
+ }
6176
6233
  }
6177
6234
  async function authenticateDevice(onStatus) {
6178
- const codes = generateDeviceCodes();
6235
+ const codes = await generateDeviceCodes();
6179
6236
  await registerDevice(codes);
6180
6237
  onStatus({ url: codes.authUrl, userCode: codes.userCode, polling: false });
6181
6238
  const startTime = Date.now();
@@ -8309,7 +8366,7 @@ __export(sessions_exports, {
8309
8366
  saveSession: () => saveSession,
8310
8367
  sessionsDir: () => sessionsDir2
8311
8368
  });
8312
- import { readFile as readFile12, writeFile as writeFile9, mkdir as mkdir8, readdir as readdir3, stat as stat4 } from "fs/promises";
8369
+ import { readFile as readFile12, writeFile as writeFile9, mkdir as mkdir9, readdir as readdir3, stat as stat4 } from "fs/promises";
8313
8370
  import { homedir as homedir10 } from "os";
8314
8371
  import { join as join17 } from "path";
8315
8372
  function sessionsDir2() {
@@ -8333,7 +8390,7 @@ function makeSessionId(firstPrompt) {
8333
8390
  }
8334
8391
  async function saveSession(file) {
8335
8392
  const dir = sessionsDir2();
8336
- await mkdir8(dir, { recursive: true });
8393
+ await mkdir9(dir, { recursive: true });
8337
8394
  const path = join17(dir, `${file.id}.json`);
8338
8395
  await writeFile9(path, JSON.stringify(file, null, 2), "utf8");
8339
8396
  return path;
@@ -8443,7 +8500,7 @@ var init_pricing = __esm({
8443
8500
  });
8444
8501
 
8445
8502
  // src/usage-tracker.ts
8446
- import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
8503
+ import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir10 } from "fs/promises";
8447
8504
  import { homedir as homedir11 } from "os";
8448
8505
  import { join as join18 } from "path";
8449
8506
  function usageDir2() {
@@ -8473,7 +8530,7 @@ async function loadLog2() {
8473
8530
  return { version: LOG_VERSION2, days: [], sessions: [] };
8474
8531
  }
8475
8532
  async function saveLog(log2) {
8476
- await mkdir9(usageDir2(), { recursive: true });
8533
+ await mkdir10(usageDir2(), { recursive: true });
8477
8534
  await writeFile10(usagePath2(), JSON.stringify(log2, null, 2), "utf8");
8478
8535
  }
8479
8536
  async function loadHistory() {
@@ -8502,7 +8559,7 @@ async function upsertHistoryDay(day) {
8502
8559
  entries.push(day);
8503
8560
  }
8504
8561
  const lines = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
8505
- await mkdir9(usageDir2(), { recursive: true });
8562
+ await mkdir10(usageDir2(), { recursive: true });
8506
8563
  await writeFile10(historyPath(), lines, "utf8");
8507
8564
  }
8508
8565
  function getOrCreateDay(log2, date) {
@@ -8973,7 +9030,7 @@ var init_session = __esm({
8973
9030
  if (err.name === "AbortError") {
8974
9031
  this.emit({ type: "session.end", reason: "aborted" });
8975
9032
  this.emit({ type: "status", status: "idle" });
8976
- } else if (err instanceof BudgetExhaustedError) {
9033
+ } else if (err instanceof BudgetExhaustedError || err instanceof AgentLoopError) {
8977
9034
  this.emit({ type: "session.end", reason: "error", error: err.message });
8978
9035
  this.emit({ type: "status", status: "error" });
8979
9036
  throw err;
@@ -12027,7 +12084,7 @@ function Onboarding({ onDone, onCancel }) {
12027
12084
  );
12028
12085
  const startCloudAuth = useCallback(async () => {
12029
12086
  try {
12030
- const codes = generateDeviceCodes();
12087
+ const codes = await generateDeviceCodes();
12031
12088
  await registerDevice(codes);
12032
12089
  setCloudAuth({ phase: "ready", codes });
12033
12090
  setStep("cloudAuth");
@@ -12892,7 +12949,7 @@ var init_skills = __esm({
12892
12949
  });
12893
12950
 
12894
12951
  // src/skills/manager.ts
12895
- import { mkdir as mkdir10, writeFile as writeFile11, unlink as unlink2, readFile as readFile15 } from "fs/promises";
12952
+ import { mkdir as mkdir11, writeFile as writeFile11, unlink as unlink2, readFile as readFile15 } from "fs/promises";
12896
12953
  import { join as join21 } from "path";
12897
12954
  import matter2 from "gray-matter";
12898
12955
  function getSkillDirs(cwd) {
@@ -12933,7 +12990,7 @@ ${yaml}
12933
12990
 
12934
12991
  Add your instructions here.
12935
12992
  `;
12936
- await mkdir10(dir, { recursive: true });
12993
+ await mkdir11(dir, { recursive: true });
12937
12994
  await writeFile11(filepath, content, "utf8");
12938
12995
  return { filepath };
12939
12996
  }
@@ -13015,7 +13072,7 @@ var init_image = __esm({
13015
13072
  });
13016
13073
 
13017
13074
  // src/util/state.ts
13018
- import { readFile as readFile17, writeFile as writeFile12, mkdir as mkdir11 } from "fs/promises";
13075
+ import { readFile as readFile17, writeFile as writeFile12, mkdir as mkdir12 } from "fs/promises";
13019
13076
  import { homedir as homedir13 } from "os";
13020
13077
  import { join as join22 } from "path";
13021
13078
  function statePath() {
@@ -13032,7 +13089,7 @@ async function readState() {
13032
13089
  }
13033
13090
  async function writeState(state) {
13034
13091
  const path = statePath();
13035
- await mkdir11(join22(path, ".."), { recursive: true });
13092
+ await mkdir12(join22(path, ".."), { recursive: true });
13036
13093
  await writeFile12(path, JSON.stringify(state, null, 2) + "\n", "utf8");
13037
13094
  }
13038
13095
  async function markCreatorMessageSeen(version) {
@@ -13490,7 +13547,7 @@ var init_builtins = __esm({
13490
13547
  });
13491
13548
 
13492
13549
  // src/commands/save.ts
13493
- import { mkdir as mkdir12, writeFile as writeFile13, unlink as unlink3 } from "fs/promises";
13550
+ import { mkdir as mkdir13, writeFile as writeFile13, unlink as unlink3 } from "fs/promises";
13494
13551
  import { dirname as dirname10 } from "path";
13495
13552
  async function saveCustomCommand(opts2) {
13496
13553
  const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
@@ -13502,7 +13559,7 @@ async function saveCustomCommand(opts2) {
13502
13559
  if (opts2.effort) data.effort = opts2.effort;
13503
13560
  const frontmatter = serializeFrontmatter(data);
13504
13561
  const content = frontmatter + opts2.template;
13505
- await mkdir12(dirname10(filepath), { recursive: true });
13562
+ await mkdir13(dirname10(filepath), { recursive: true });
13506
13563
  await writeFile13(filepath, content, "utf8");
13507
13564
  return { filepath };
13508
13565
  }
@@ -18044,6 +18101,11 @@ ${wcagWarnings.join("\n")}` }
18044
18101
  ...es,
18045
18102
  { kind: "cloud_quota_exhausted", key: mkKey(), used, limit, expiresAt }
18046
18103
  ]);
18104
+ } else if (e instanceof AgentLoopError) {
18105
+ setEvents((es) => [
18106
+ ...es,
18107
+ { kind: "error", key: mkKey(), text: "The agent got stuck repeating the same actions. Here's what we know so far." }
18108
+ ]);
18047
18109
  } else {
18048
18110
  const displayText = e instanceof KimiApiError ? humanizeCloudflareError(e) : `init failed: ${e.message}`;
18049
18111
  setEvents((es) => [
@@ -20434,6 +20496,11 @@ async function runPrintMode(opts2) {
20434
20496
  process.exitCode = 42;
20435
20497
  return;
20436
20498
  }
20499
+ if (err instanceof AgentLoopError) {
20500
+ process.stderr.write("\n\x1B[33m[Agent loop detected \u2014 exiting with code 43]\x1B[0m\n");
20501
+ process.exitCode = 43;
20502
+ return;
20503
+ }
20437
20504
  if (err instanceof KimiApiError) {
20438
20505
  process.stderr.write(`
20439
20506
  \x1B[31mError: ${humanizeCloudflareError(err)}\x1B[0m