kimiflare 0.50.0 → 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
  }
@@ -3349,8 +3353,8 @@ var init_write = __esm({
3349
3353
  },
3350
3354
  needsPermission: true,
3351
3355
  render: (args) => ({
3352
- title: `write ${collapsePath(args.path, process.cwd())} (${args.content.length} chars)`,
3353
- diff: { path: args.path, before: "", after: args.content }
3356
+ title: `write ${collapsePath(String(args.path ?? ""), process.cwd())} (${String(args.content ?? "").length} chars)`,
3357
+ diff: { path: String(args.path ?? ""), before: "", after: String(args.content ?? "") }
3354
3358
  }),
3355
3359
  async run(args, ctx) {
3356
3360
  const abs = resolvePath(ctx.cwd, args.path);
@@ -3402,8 +3406,8 @@ var init_edit = __esm({
3402
3406
  },
3403
3407
  needsPermission: true,
3404
3408
  render: (args) => ({
3405
- title: `edit ${collapsePath(args.path, process.cwd())}${args.replace_all ? " (replace_all)" : ""}`,
3406
- diff: { path: args.path, before: args.old_string, after: args.new_string }
3409
+ title: `edit ${collapsePath(String(args.path ?? ""), process.cwd())}${args.replace_all ? " (replace_all)" : ""}`,
3410
+ diff: { path: String(args.path ?? ""), before: String(args.old_string ?? ""), after: String(args.new_string ?? "") }
3407
3411
  }),
3408
3412
  async run(args, ctx) {
3409
3413
  const abs = resolvePath(ctx.cwd, args.path);
@@ -3551,7 +3555,7 @@ var init_bash = __esm({
3551
3555
  additionalProperties: false
3552
3556
  },
3553
3557
  needsPermission: true,
3554
- render: (args) => ({ title: formatBashTitle(args.command) }),
3558
+ render: (args) => ({ title: formatBashTitle(String(args.command ?? "")) }),
3555
3559
  run: (args, ctx) => runBash(args, ctx)
3556
3560
  };
3557
3561
  }
@@ -3577,7 +3581,7 @@ var init_glob = __esm({
3577
3581
  additionalProperties: false
3578
3582
  },
3579
3583
  needsPermission: false,
3580
- render: (args) => ({ title: `glob ${args.pattern}${args.path ? ` in ${collapsePath(args.path, process.cwd())}` : ""}` }),
3584
+ render: (args) => ({ title: `glob ${args.pattern ?? ""}${args.path ? ` in ${collapsePath(String(args.path), process.cwd())}` : ""}` }),
3581
3585
  async run(args, ctx) {
3582
3586
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
3583
3587
  const entries = await fg(args.pattern, {
@@ -3695,7 +3699,7 @@ var init_grep = __esm({
3695
3699
  additionalProperties: false
3696
3700
  },
3697
3701
  needsPermission: false,
3698
- render: (args) => ({ title: `grep ${args.pattern}${args.glob ? ` (${args.glob})` : ""}` }),
3702
+ render: (args) => ({ title: `grep ${args.pattern ?? ""}${args.glob ? ` (${args.glob})` : ""}` }),
3699
3703
  async run(args, ctx) {
3700
3704
  const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
3701
3705
  const mode = args.output_mode ?? "content";
@@ -3727,7 +3731,7 @@ var init_web_fetch = __esm({
3727
3731
  additionalProperties: false
3728
3732
  },
3729
3733
  needsPermission: false,
3730
- render: (args) => ({ title: `GET ${args.url}` }),
3734
+ render: (args) => ({ title: `GET ${args.url ?? ""}` }),
3731
3735
  async run(args) {
3732
3736
  const controller = new AbortController();
3733
3737
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
@@ -3862,7 +3866,7 @@ var init_web_search = __esm({
3862
3866
  additionalProperties: false
3863
3867
  },
3864
3868
  needsPermission: false,
3865
- render: (args) => ({ title: `search web: ${args.query}` }),
3869
+ render: (args) => ({ title: `search web: ${args.query ?? ""}` }),
3866
3870
  async run(args) {
3867
3871
  const count = Math.min(Math.max(args.count ?? DEFAULT_RESULTS, 1), MAX_RESULTS);
3868
3872
  try {
@@ -3948,7 +3952,7 @@ var init_github = __esm({
3948
3952
  additionalProperties: false
3949
3953
  },
3950
3954
  needsPermission: false,
3951
- render: (args) => ({ title: `GitHub PR ${args.owner}/${args.repo}#${args.number}` }),
3955
+ render: (args) => ({ title: `GitHub PR ${args.owner ?? ""}/${args.repo ?? ""}#${args.number ?? ""}` }),
3952
3956
  async run(args, ctx) {
3953
3957
  const token = getToken(ctx);
3954
3958
  const pr = await githubFetch(`/repos/${args.owner}/${args.repo}/pulls/${args.number}`, token);
@@ -3990,7 +3994,7 @@ var init_github = __esm({
3990
3994
  additionalProperties: false
3991
3995
  },
3992
3996
  needsPermission: false,
3993
- render: (args) => ({ title: `GitHub issue ${args.owner}/${args.repo}#${args.number}` }),
3997
+ render: (args) => ({ title: `GitHub issue ${args.owner ?? ""}/${args.repo ?? ""}#${args.number ?? ""}` }),
3994
3998
  async run(args, ctx) {
3995
3999
  const token = getToken(ctx);
3996
4000
  const issue = await githubFetch(`/repos/${args.owner}/${args.repo}/issues/${args.number}`, token);
@@ -4037,7 +4041,7 @@ var init_github = __esm({
4037
4041
  },
4038
4042
  needsPermission: false,
4039
4043
  render: (args) => ({
4040
- title: `GitHub code ${args.owner}/${args.repo}/${args.path}${args.ref ? `@${args.ref}` : ""}`
4044
+ title: `GitHub code ${args.owner ?? ""}/${args.repo ?? ""}/${args.path ?? ""}${args.ref ? `@${args.ref}` : ""}`
4041
4045
  }),
4042
4046
  async run(args, ctx) {
4043
4047
  const token = getToken(ctx);
@@ -4115,7 +4119,7 @@ var init_browser = __esm({
4115
4119
  },
4116
4120
  needsPermission: false,
4117
4121
  render: (args) => ({
4118
- title: `browser ${args.url}${args.screenshot ? " (screenshot)" : ""}`
4122
+ title: `browser ${args.url ?? ""}${args.screenshot ? " (screenshot)" : ""}`
4119
4123
  }),
4120
4124
  async run(args, ctx) {
4121
4125
  let playwright;
@@ -4241,10 +4245,13 @@ var init_tasks = __esm({
4241
4245
  required: ["tasks"]
4242
4246
  },
4243
4247
  needsPermission: false,
4244
- render: (args) => ({
4245
- title: `tasks (${args.tasks.length} items)`,
4246
- body: args.tasks.map((t) => `${t.status === "completed" ? "\u2713" : t.status === "in_progress" ? "\u25B8" : "\xB7"} ${t.title}`).join("\n")
4247
- }),
4248
+ render: (args) => {
4249
+ const tasks = Array.isArray(args.tasks) ? args.tasks : [];
4250
+ return {
4251
+ title: `tasks (${tasks.length} items)`,
4252
+ body: tasks.map((t) => `${t.status === "completed" ? "\u2713" : t.status === "in_progress" ? "\u25B8" : "\xB7"} ${t.title}`).join("\n")
4253
+ };
4254
+ },
4248
4255
  run: async (args, ctx) => {
4249
4256
  let tasks;
4250
4257
  try {
@@ -4295,7 +4302,7 @@ var init_memory = __esm({
4295
4302
  needsPermission: false,
4296
4303
  render: (args) => ({
4297
4304
  title: "memory_remember",
4298
- body: `[${args.category}] ${args.content} (importance: ${args.importance})`
4305
+ body: `[${args.category ?? "unknown"}] ${args.content ?? ""} (importance: ${args.importance ?? 1})`
4299
4306
  }),
4300
4307
  run: async (args, ctx) => {
4301
4308
  if (!isMemoryCtx(ctx) || !ctx.memoryManager) {
@@ -4350,7 +4357,7 @@ var init_memory = __esm({
4350
4357
  needsPermission: false,
4351
4358
  render: (args) => ({
4352
4359
  title: "memory_recall",
4353
- body: `Query: "${args.query}"`
4360
+ body: `Query: "${args.query ?? ""}"`
4354
4361
  }),
4355
4362
  run: async (args, ctx) => {
4356
4363
  if (!isMemoryCtx(ctx) || !ctx.memoryManager) {
@@ -4395,7 +4402,7 @@ var init_memory = __esm({
4395
4402
  needsPermission: false,
4396
4403
  render: (args) => ({
4397
4404
  title: "memory_forget",
4398
- body: `Forgetting memory ${args.memory_id}`
4405
+ body: `Forgetting memory ${args.memory_id ?? ""}`
4399
4406
  }),
4400
4407
  run: async (args, ctx) => {
4401
4408
  if (!isMemoryCtx(ctx) || !ctx.memoryManager) {
@@ -4955,7 +4962,7 @@ function makeExpandArtifactTool(store) {
4955
4962
  additionalProperties: false
4956
4963
  },
4957
4964
  needsPermission: false,
4958
- render: (args) => ({ title: `expand ${args.artifact_id}` }),
4965
+ render: (args) => ({ title: `expand ${args.artifact_id ?? ""}` }),
4959
4966
  run: async (args) => {
4960
4967
  const raw = store.retrieve(args.artifact_id);
4961
4968
  if (!raw) {
@@ -6075,13 +6082,18 @@ __export(auth_exports, {
6075
6082
  registerDevice: () => registerDevice,
6076
6083
  saveCloudCredentials: () => saveCloudCredentials
6077
6084
  });
6078
- import { readFile as readFile11, writeFile as writeFile8 } from "fs/promises";
6079
- 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";
6080
6088
  import { join as join15 } from "path";
6081
6089
  function cloudCredPath() {
6082
6090
  const xdg = process.env.XDG_CONFIG_HOME || join15(homedir9(), ".config");
6083
6091
  return join15(xdg, "kimiflare", "cloud.json");
6084
6092
  }
6093
+ function deviceIdPath() {
6094
+ const xdg = process.env.XDG_DATA_HOME || join15(homedir9(), ".local", "share");
6095
+ return join15(xdg, "kimiflare", "device_id");
6096
+ }
6085
6097
  function generateCode() {
6086
6098
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
6087
6099
  let out = "";
@@ -6090,16 +6102,51 @@ function generateCode() {
6090
6102
  }
6091
6103
  return out;
6092
6104
  }
6093
- 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() {
6094
6111
  const arr = new Uint8Array(16);
6095
6112
  crypto.getRandomValues(arr);
6096
6113
  return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
6097
6114
  }
6098
- 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() {
6099
6146
  const deviceCode = `device-${generateCode()}-${Date.now()}`;
6100
6147
  const userCode = `${generateCode()}-${generateCode()}`;
6101
6148
  const authUrl = `${CLOUD_API_URL}/auth?code=${encodeURIComponent(userCode)}`;
6102
- const deviceId = generateDeviceId();
6149
+ const deviceId = await getOrCreateDeviceId();
6103
6150
  return { deviceCode, userCode, authUrl, deviceId };
6104
6151
  }
6105
6152
  async function registerDevice(codes) {
@@ -6154,6 +6201,10 @@ async function loadCloudCredentials() {
6154
6201
  const raw = await readFile11(cloudCredPath(), "utf8");
6155
6202
  const parsed = JSON.parse(raw);
6156
6203
  if (parsed.expiresAt && parsed.expiresAt > Date.now() / 1e3 && parsed.accessToken) {
6204
+ if (parsed.deviceId) {
6205
+ await persistDeviceId(parsed.deviceId).catch(() => {
6206
+ });
6207
+ }
6157
6208
  return parsed;
6158
6209
  }
6159
6210
  } catch {
@@ -6163,6 +6214,10 @@ async function loadCloudCredentials() {
6163
6214
  async function saveCloudCredentials(creds) {
6164
6215
  const p = cloudCredPath();
6165
6216
  await writeFile8(p, JSON.stringify(creds, null, 2), "utf8");
6217
+ if (creds.deviceId) {
6218
+ await persistDeviceId(creds.deviceId).catch(() => {
6219
+ });
6220
+ }
6166
6221
  }
6167
6222
  async function clearCloudCredentials() {
6168
6223
  try {
@@ -6170,9 +6225,14 @@ async function clearCloudCredentials() {
6170
6225
  await unlink5(cloudCredPath());
6171
6226
  } catch {
6172
6227
  }
6228
+ try {
6229
+ const { unlink: unlink5 } = await import("fs/promises");
6230
+ await unlink5(deviceIdPath());
6231
+ } catch {
6232
+ }
6173
6233
  }
6174
6234
  async function authenticateDevice(onStatus) {
6175
- const codes = generateDeviceCodes();
6235
+ const codes = await generateDeviceCodes();
6176
6236
  await registerDevice(codes);
6177
6237
  onStatus({ url: codes.authUrl, userCode: codes.userCode, polling: false });
6178
6238
  const startTime = Date.now();
@@ -8306,7 +8366,7 @@ __export(sessions_exports, {
8306
8366
  saveSession: () => saveSession,
8307
8367
  sessionsDir: () => sessionsDir2
8308
8368
  });
8309
- 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";
8310
8370
  import { homedir as homedir10 } from "os";
8311
8371
  import { join as join17 } from "path";
8312
8372
  function sessionsDir2() {
@@ -8330,7 +8390,7 @@ function makeSessionId(firstPrompt) {
8330
8390
  }
8331
8391
  async function saveSession(file) {
8332
8392
  const dir = sessionsDir2();
8333
- await mkdir8(dir, { recursive: true });
8393
+ await mkdir9(dir, { recursive: true });
8334
8394
  const path = join17(dir, `${file.id}.json`);
8335
8395
  await writeFile9(path, JSON.stringify(file, null, 2), "utf8");
8336
8396
  return path;
@@ -8440,7 +8500,7 @@ var init_pricing = __esm({
8440
8500
  });
8441
8501
 
8442
8502
  // src/usage-tracker.ts
8443
- 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";
8444
8504
  import { homedir as homedir11 } from "os";
8445
8505
  import { join as join18 } from "path";
8446
8506
  function usageDir2() {
@@ -8470,7 +8530,7 @@ async function loadLog2() {
8470
8530
  return { version: LOG_VERSION2, days: [], sessions: [] };
8471
8531
  }
8472
8532
  async function saveLog(log2) {
8473
- await mkdir9(usageDir2(), { recursive: true });
8533
+ await mkdir10(usageDir2(), { recursive: true });
8474
8534
  await writeFile10(usagePath2(), JSON.stringify(log2, null, 2), "utf8");
8475
8535
  }
8476
8536
  async function loadHistory() {
@@ -8499,7 +8559,7 @@ async function upsertHistoryDay(day) {
8499
8559
  entries.push(day);
8500
8560
  }
8501
8561
  const lines = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
8502
- await mkdir9(usageDir2(), { recursive: true });
8562
+ await mkdir10(usageDir2(), { recursive: true });
8503
8563
  await writeFile10(historyPath(), lines, "utf8");
8504
8564
  }
8505
8565
  function getOrCreateDay(log2, date) {
@@ -8970,7 +9030,7 @@ var init_session = __esm({
8970
9030
  if (err.name === "AbortError") {
8971
9031
  this.emit({ type: "session.end", reason: "aborted" });
8972
9032
  this.emit({ type: "status", status: "idle" });
8973
- } else if (err instanceof BudgetExhaustedError) {
9033
+ } else if (err instanceof BudgetExhaustedError || err instanceof AgentLoopError) {
8974
9034
  this.emit({ type: "session.end", reason: "error", error: err.message });
8975
9035
  this.emit({ type: "status", status: "error" });
8976
9036
  throw err;
@@ -9730,7 +9790,9 @@ import { createTwoFilesPatch } from "diff";
9730
9790
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
9731
9791
  function DiffView({ path, before, after, maxLines = 40 }) {
9732
9792
  const theme = useTheme();
9733
- const patch = createTwoFilesPatch(path, path, before, after, "", "", { context: 2 });
9793
+ const safeBefore = before ?? "";
9794
+ const safeAfter = after ?? "";
9795
+ const patch = createTwoFilesPatch(path, path, safeBefore, safeAfter, "", "", { context: 2 });
9734
9796
  const raw = patch.split("\n").slice(4);
9735
9797
  const lines = raw.filter((l) => {
9736
9798
  if (l.startsWith("--- ") || l.startsWith("+++ ")) return false;
@@ -10745,7 +10807,11 @@ import SelectInput from "ink-select-input";
10745
10807
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
10746
10808
  function PermissionModal({ tool, args, onDecide }) {
10747
10809
  const theme = useTheme();
10748
- const render2 = tool.render?.(args);
10810
+ let render2;
10811
+ try {
10812
+ render2 = tool.render?.(args);
10813
+ } catch {
10814
+ }
10749
10815
  const items = [
10750
10816
  { label: "Allow once", value: "allow" },
10751
10817
  { label: "Allow for this session", value: "allow_session" },
@@ -12018,7 +12084,7 @@ function Onboarding({ onDone, onCancel }) {
12018
12084
  );
12019
12085
  const startCloudAuth = useCallback(async () => {
12020
12086
  try {
12021
- const codes = generateDeviceCodes();
12087
+ const codes = await generateDeviceCodes();
12022
12088
  await registerDevice(codes);
12023
12089
  setCloudAuth({ phase: "ready", codes });
12024
12090
  setStep("cloudAuth");
@@ -12883,7 +12949,7 @@ var init_skills = __esm({
12883
12949
  });
12884
12950
 
12885
12951
  // src/skills/manager.ts
12886
- 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";
12887
12953
  import { join as join21 } from "path";
12888
12954
  import matter2 from "gray-matter";
12889
12955
  function getSkillDirs(cwd) {
@@ -12924,7 +12990,7 @@ ${yaml}
12924
12990
 
12925
12991
  Add your instructions here.
12926
12992
  `;
12927
- await mkdir10(dir, { recursive: true });
12993
+ await mkdir11(dir, { recursive: true });
12928
12994
  await writeFile11(filepath, content, "utf8");
12929
12995
  return { filepath };
12930
12996
  }
@@ -13006,7 +13072,7 @@ var init_image = __esm({
13006
13072
  });
13007
13073
 
13008
13074
  // src/util/state.ts
13009
- 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";
13010
13076
  import { homedir as homedir13 } from "os";
13011
13077
  import { join as join22 } from "path";
13012
13078
  function statePath() {
@@ -13023,7 +13089,7 @@ async function readState() {
13023
13089
  }
13024
13090
  async function writeState(state) {
13025
13091
  const path = statePath();
13026
- await mkdir11(join22(path, ".."), { recursive: true });
13092
+ await mkdir12(join22(path, ".."), { recursive: true });
13027
13093
  await writeFile12(path, JSON.stringify(state, null, 2) + "\n", "utf8");
13028
13094
  }
13029
13095
  async function markCreatorMessageSeen(version) {
@@ -13481,7 +13547,7 @@ var init_builtins = __esm({
13481
13547
  });
13482
13548
 
13483
13549
  // src/commands/save.ts
13484
- 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";
13485
13551
  import { dirname as dirname10 } from "path";
13486
13552
  async function saveCustomCommand(opts2) {
13487
13553
  const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
@@ -13493,7 +13559,7 @@ async function saveCustomCommand(opts2) {
13493
13559
  if (opts2.effort) data.effort = opts2.effort;
13494
13560
  const frontmatter = serializeFrontmatter(data);
13495
13561
  const content = frontmatter + opts2.template;
13496
- await mkdir12(dirname10(filepath), { recursive: true });
13562
+ await mkdir13(dirname10(filepath), { recursive: true });
13497
13563
  await writeFile13(filepath, content, "utf8");
13498
13564
  return { filepath };
13499
13565
  }
@@ -18035,6 +18101,11 @@ ${wcagWarnings.join("\n")}` }
18035
18101
  ...es,
18036
18102
  { kind: "cloud_quota_exhausted", key: mkKey(), used, limit, expiresAt }
18037
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
+ ]);
18038
18109
  } else {
18039
18110
  const displayText = e instanceof KimiApiError ? humanizeCloudflareError(e) : `init failed: ${e.message}`;
18040
18111
  setEvents((es) => [
@@ -20425,6 +20496,11 @@ async function runPrintMode(opts2) {
20425
20496
  process.exitCode = 42;
20426
20497
  return;
20427
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
+ }
20428
20504
  if (err instanceof KimiApiError) {
20429
20505
  process.stderr.write(`
20430
20506
  \x1B[31mError: ${humanizeCloudflareError(err)}\x1B[0m