clixad 0.0.1-beta.42 → 0.0.1-beta.44

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.
@@ -2319,6 +2319,94 @@ function diffStat(lines) {
2319
2319
  }
2320
2320
  return { added, removed };
2321
2321
  }
2322
+ var MAX_VIEW_ROWS = 2e3;
2323
+ function diffView(path, before, after, opts = {}) {
2324
+ const context = opts.context ?? 3;
2325
+ if (opts.created) {
2326
+ const lines2 = splitLines(after);
2327
+ const rows2 = lines2.slice(0, MAX_VIEW_ROWS).map((text, i) => ({ kind: "add", line: i + 1, text }));
2328
+ if (lines2.length > MAX_VIEW_ROWS) rows2.push({ kind: "gap", count: lines2.length - MAX_VIEW_ROWS });
2329
+ return { kind: "diff", path, created: true, added: after === "" ? 0 : lines2.length, removed: 0, rows: rows2 };
2330
+ }
2331
+ const lines = diffLines(splitLines(before), splitLines(after));
2332
+ const { added, removed } = diffStat(lines);
2333
+ const keep = new Array(lines.length).fill(false);
2334
+ lines.forEach((l, i) => {
2335
+ if (l.kind === "ctx") return;
2336
+ for (let k = Math.max(0, i - context); k <= Math.min(lines.length - 1, i + context); k++) keep[k] = true;
2337
+ });
2338
+ const rows = [];
2339
+ let skipped = 0;
2340
+ for (let i = 0; i < lines.length; i++) {
2341
+ if (!keep[i]) {
2342
+ skipped++;
2343
+ continue;
2344
+ }
2345
+ if (skipped > 0 && rows.length > 0) rows.push({ kind: "gap", count: skipped });
2346
+ skipped = 0;
2347
+ if (rows.length >= MAX_VIEW_ROWS) {
2348
+ rows.push({ kind: "gap", count: lines.length - i });
2349
+ break;
2350
+ }
2351
+ rows.push({ ...lines[i] });
2352
+ }
2353
+ return { kind: "diff", path, created: false, added, removed, rows };
2354
+ }
2355
+ function diffTint(env = process.env) {
2356
+ if (env.NO_COLOR) return "none";
2357
+ if (env.COLORTERM === "truecolor" || env.COLORTERM === "24bit") return "truecolor";
2358
+ if (/256/.test(env.TERM ?? "")) return "256";
2359
+ if (env.TERM === "dumb") return "none";
2360
+ return "basic";
2361
+ }
2362
+ var TINTS = {
2363
+ truecolor: {
2364
+ add: "\x1B[48;2;28;62;38m",
2365
+ del: "\x1B[48;2;78;32;36m",
2366
+ addSign: "\x1B[38;2;126;231;135m",
2367
+ delSign: "\x1B[38;2;255;123;114m",
2368
+ off: R
2369
+ },
2370
+ "256": { add: "\x1B[48;5;22m", del: "\x1B[48;5;52m", addSign: "\x1B[92m", delSign: "\x1B[91m", off: R },
2371
+ basic: { add: GREEN, del: RED, addSign: "", delSign: "", off: R },
2372
+ none: { add: "", del: "", addSign: "", delSign: "", off: "" }
2373
+ };
2374
+ function renderDiffRows(view, width, opts) {
2375
+ const tint = TINTS[opts.tint ?? "basic"];
2376
+ const max = opts.maxRows ?? Number.POSITIVE_INFINITY;
2377
+ const numbers = view.rows.filter((r) => r.kind !== "gap");
2378
+ const gutter = Math.max(3, ...numbers.map((r) => String(r.line).length));
2379
+ const fit = (text) => {
2380
+ const cut2 = opts.measure(text) > width ? opts.cut(text, width) : text;
2381
+ return cut2 + " ".repeat(Math.max(0, width - opts.measure(cut2)));
2382
+ };
2383
+ const rows = [];
2384
+ let more = 0;
2385
+ for (const row of view.rows) {
2386
+ if (rows.length >= max) {
2387
+ if (row.kind !== "gap") more++;
2388
+ continue;
2389
+ }
2390
+ if (row.kind === "gap") {
2391
+ const dim = tint === TINTS.none ? "" : DIM;
2392
+ rows.push(`${dim}${opts.cut(`${" ".repeat(gutter)} \u22EF ${row.count} unchanged line${row.count === 1 ? "" : "s"}`, width)}${tint.off}`);
2393
+ continue;
2394
+ }
2395
+ const text = row.text.replace(/\t/g, " ");
2396
+ const no = String(row.line).padStart(gutter);
2397
+ if (row.kind === "ctx") {
2398
+ rows.push(`${tint === TINTS.none ? "" : DIM}${opts.cut(`${no} ${text}`, width)}${tint.off}`);
2399
+ continue;
2400
+ }
2401
+ const sign = row.kind === "add" ? "+" : "-";
2402
+ const bg2 = row.kind === "add" ? tint.add : tint.del;
2403
+ const signColor = row.kind === "add" ? tint.addSign : tint.delSign;
2404
+ const body = fit(`${no} ${sign} ${text}`);
2405
+ const at = gutter + 1;
2406
+ rows.push(`${bg2}${body.slice(0, at)}${signColor}${sign}${signColor ? "\x1B[39m" : ""}${body.slice(at + 1)}${tint.off}`);
2407
+ }
2408
+ return { rows, more };
2409
+ }
2322
2410
  function renderDiff(oldText, newText, opts = {}) {
2323
2411
  const context = opts.context ?? 3;
2324
2412
  const maxLines = opts.maxLines ?? 40;
@@ -3618,13 +3706,16 @@ ${body2}` + (pdf.text.length > MAX_FILE_BYTES ? "\n\u2026 [truncated]" : "");
3618
3706
  const abs = safeResolve(ctx.root, requireString(args.path, "write_file", "path"));
3619
3707
  const content = requireString(args.content, "write_file", "content");
3620
3708
  const name = rel(ctx.root, abs);
3621
- const before = existsSync2(abs) ? fsCall(`cannot write ${name}`, () => readFileSync3(abs, "utf8")) : "";
3709
+ const existed = existsSync2(abs);
3710
+ const before = existed ? fsCall(`cannot write ${name}`, () => readFileSync3(abs, "utf8")) : "";
3622
3711
  const preview = renderDiff(before, content) || "(no change)";
3712
+ const view = diffView(name, before, content, { created: !existed });
3623
3713
  await gate(ctx, {
3624
3714
  tool: "write_file",
3625
3715
  signature: `write:${name}`,
3626
3716
  summary: `${before ? "overwrite" : "create"} ${name}`,
3627
3717
  preview,
3718
+ view,
3628
3719
  path: name,
3629
3720
  root: ctx.root
3630
3721
  });
@@ -3632,6 +3723,7 @@ ${body2}` + (pdf.text.length > MAX_FILE_BYTES ? "\n\u2026 [truncated]" : "");
3632
3723
  mkdirSync2(dirname2(abs), { recursive: true });
3633
3724
  writeFileSync2(abs, content, "utf8");
3634
3725
  });
3726
+ ctx.present?.(view);
3635
3727
  return `wrote ${content.length} bytes to ${name}`;
3636
3728
  },
3637
3729
  /**
@@ -3661,15 +3753,18 @@ ${body2}` + (pdf.text.length > MAX_FILE_BYTES ? "\n\u2026 [truncated]" : "");
3661
3753
  }
3662
3754
  const after = before.split(oldString).join(newString);
3663
3755
  const preview = renderDiff(before, after) || "(no change)";
3756
+ const view = diffView(name, before, after);
3664
3757
  await gate(ctx, {
3665
3758
  tool: "edit_file",
3666
3759
  signature: `write:${name}`,
3667
3760
  summary: `edit ${name}`,
3668
3761
  preview,
3762
+ view,
3669
3763
  path: name,
3670
3764
  root: ctx.root
3671
3765
  });
3672
3766
  fsCall(`cannot edit ${name}`, () => writeFileSync2(abs, after, "utf8"));
3767
+ ctx.present?.(view);
3673
3768
  return `edited ${name} (${count > 1 ? `${count} occurrences` : "1 occurrence"})`;
3674
3769
  },
3675
3770
  /**
@@ -5316,6 +5411,9 @@ async function runAgent(client, task, opts) {
5316
5411
  mcp: opts.mcp,
5317
5412
  shells: opts.shells,
5318
5413
  attach: (image) => pendingImages.push(image),
5414
+ // A placeholder that says writes are shown at this depth; each call gets
5415
+ // its own (`executeToolCall`), which puts the view on its result event.
5416
+ ...depth === 0 ? { present: () => void 0 } : {},
5319
5417
  // The recursion guard, and the whole of it: a sub-agent runs at depth 1 and
5320
5418
  // is handed no way to start another.
5321
5419
  ...depth === 0 ? { spawn: runSubAgent } : {}
@@ -5388,79 +5486,92 @@ ${NO_SEARCH_INSTRUCTION}`,
5388
5486
  ...turn.spendable ? { spendable: turn.spendable } : {}
5389
5487
  });
5390
5488
  }
5391
- let retriedEmpty = false;
5392
- for (let step = 0; step < maxSteps; step++) {
5393
- if (opts.signal?.aborted) return stop("aborted");
5394
- let turn;
5395
- try {
5396
- turn = await client.chatStream(messages, opts.model, {
5397
- effort: opts.effort,
5398
- tools: opts.tools ? opts.tools() : TOOL_SCHEMA,
5399
- signal: opts.signal,
5400
- onDelta: (text) => emit({ type: "delta", text })
5401
- });
5402
- } catch (err) {
5403
- if (isAbort(err) || opts.signal?.aborted) return stop("aborted");
5404
- throw err;
5405
- }
5406
- creditsCharged += turn.creditsCharged;
5407
- creditsWaived += turn.creditsWaived ?? 0;
5408
- if (turn.freeCreditsLeft !== void 0) freeCreditsLeft = turn.freeCreditsLeft;
5409
- balance = turn.balance;
5410
- emitUsage(turn);
5411
- const content = messageText(turn.message);
5412
- const calls = turn.message.tool_calls ?? [];
5413
- if (calls.length === 0 && !content.trim() && !retriedEmpty) {
5414
- retriedEmpty = true;
5415
- continue;
5416
- }
5417
- retriedEmpty = false;
5418
- messages.push(turn.message);
5419
- if (content) lastText = content;
5420
- if (calls.length === 0) {
5421
- emit({ type: "final", content, creditsCharged, balance });
5422
- return {
5423
- content,
5424
- messages: withoutReasoning(messages),
5425
- creditsCharged,
5426
- balance,
5427
- creditsWaived,
5428
- freeCreditsLeft,
5429
- searchCredits,
5430
- steps: step + 1
5431
- };
5489
+ const base = messages.length;
5490
+ try {
5491
+ return await loop();
5492
+ } catch (err) {
5493
+ if (typeof err === "object" && err !== null) {
5494
+ if (messages.length > base) interrupted.set(err, closeOpenCalls(withoutReasoning(messages)));
5495
+ else interrupted.delete(err);
5432
5496
  }
5433
- if (content) emit({ type: "message", content });
5434
- pendingImages = [];
5435
- for (const [i, call] of calls.entries()) {
5436
- if (opts.signal?.aborted) {
5437
- for (const rest of calls.slice(i)) messages.push(toolMessage(rest, "cancelled by the user"));
5438
- return stop("aborted");
5497
+ throw err;
5498
+ }
5499
+ async function loop() {
5500
+ let retriedEmpty = false;
5501
+ for (let step = 0; step < maxSteps; step++) {
5502
+ if (opts.signal?.aborted) return stop("aborted");
5503
+ let turn;
5504
+ try {
5505
+ turn = await client.chatStream(messages, opts.model, {
5506
+ effort: opts.effort,
5507
+ tools: opts.tools ? opts.tools() : TOOL_SCHEMA,
5508
+ signal: opts.signal,
5509
+ onDelta: (text) => emit({ type: "delta", text })
5510
+ });
5511
+ } catch (err) {
5512
+ if (isAbort(err) || opts.signal?.aborted) return stop("aborted");
5513
+ throw err;
5439
5514
  }
5440
- const result = await executeToolCall(ctx, call, emit);
5441
- messages.push(toolMessage(call, result));
5442
- }
5443
- if (pendingImages.length) {
5444
- messages.push(imageMessage(pendingImages));
5445
- emit({
5446
- type: "attachment",
5447
- labels: pendingImages.map((image) => image.label)
5448
- });
5515
+ creditsCharged += turn.creditsCharged;
5516
+ creditsWaived += turn.creditsWaived ?? 0;
5517
+ if (turn.freeCreditsLeft !== void 0) freeCreditsLeft = turn.freeCreditsLeft;
5518
+ balance = turn.balance;
5519
+ emitUsage(turn);
5520
+ const content = messageText(turn.message);
5521
+ const calls = turn.message.tool_calls ?? [];
5522
+ if (calls.length === 0 && !content.trim() && !retriedEmpty) {
5523
+ retriedEmpty = true;
5524
+ continue;
5525
+ }
5526
+ retriedEmpty = false;
5527
+ messages.push(turn.message);
5528
+ if (content) lastText = content;
5529
+ if (calls.length === 0) {
5530
+ emit({ type: "final", content, creditsCharged, balance });
5531
+ return {
5532
+ content,
5533
+ messages: withoutReasoning(messages),
5534
+ creditsCharged,
5535
+ balance,
5536
+ creditsWaived,
5537
+ freeCreditsLeft,
5538
+ searchCredits,
5539
+ steps: step + 1
5540
+ };
5541
+ }
5542
+ if (content) emit({ type: "message", content });
5449
5543
  pendingImages = [];
5544
+ for (const [i, call] of calls.entries()) {
5545
+ if (opts.signal?.aborted) {
5546
+ for (const rest of calls.slice(i)) messages.push(toolMessage(rest, "cancelled by the user"));
5547
+ return stop("aborted");
5548
+ }
5549
+ const result = await executeToolCall(ctx, call, emit);
5550
+ messages.push(toolMessage(call, result));
5551
+ }
5552
+ if (pendingImages.length) {
5553
+ messages.push(imageMessage(pendingImages));
5554
+ emit({
5555
+ type: "attachment",
5556
+ labels: pendingImages.map((image) => image.label)
5557
+ });
5558
+ pendingImages = [];
5559
+ }
5560
+ opts.onCheckpoint?.(withoutReasoning(messages));
5450
5561
  }
5562
+ emit({ type: "max_steps" });
5563
+ return {
5564
+ content: lastText || "(stopped: reached max steps)",
5565
+ messages: withoutReasoning(messages),
5566
+ creditsCharged,
5567
+ balance,
5568
+ creditsWaived,
5569
+ freeCreditsLeft,
5570
+ searchCredits,
5571
+ steps: maxSteps,
5572
+ stopped: "max_steps"
5573
+ };
5451
5574
  }
5452
- emit({ type: "max_steps" });
5453
- return {
5454
- content: lastText || "(stopped: reached max steps)",
5455
- messages: withoutReasoning(messages),
5456
- creditsCharged,
5457
- balance,
5458
- creditsWaived,
5459
- freeCreditsLeft,
5460
- searchCredits,
5461
- steps: maxSteps,
5462
- stopped: "max_steps"
5463
- };
5464
5575
  function stop(reason) {
5465
5576
  emit({ type: reason });
5466
5577
  return {
@@ -5476,6 +5587,21 @@ ${NO_SEARCH_INSTRUCTION}`,
5476
5587
  };
5477
5588
  }
5478
5589
  }
5590
+ var interrupted = /* @__PURE__ */ new WeakMap();
5591
+ function interruptedConversation(err) {
5592
+ return typeof err === "object" && err !== null ? interrupted.get(err) : void 0;
5593
+ }
5594
+ var INTERRUPTED_CALL = "not completed: the turn stopped with an error before this call finished";
5595
+ function closeOpenCalls(messages) {
5596
+ const out = [...messages];
5597
+ const last = out.findLastIndex((m) => m.role === "assistant");
5598
+ const calls = last >= 0 ? out[last].tool_calls ?? [] : [];
5599
+ const answered = new Set(out.slice(last + 1).map((m) => m.tool_call_id));
5600
+ for (const call of calls) {
5601
+ if (!answered.has(call.id)) out.push(toolMessage(call, INTERRUPTED_CALL));
5602
+ }
5603
+ return out;
5604
+ }
5479
5605
  function toolMessage(call, content) {
5480
5606
  return { role: "tool", tool_call_id: call.id, name: call.function.name, content };
5481
5607
  }
@@ -5511,9 +5637,13 @@ async function executeToolCall(ctx, call, emit) {
5511
5637
  }
5512
5638
  emit({ type: "tool_call", id, name, args });
5513
5639
  const search = ctx.webSearch;
5640
+ let view;
5514
5641
  const scoped = {
5515
5642
  ...ctx,
5516
5643
  onOutput: (chunk) => emit({ type: "tool_output", id, chunk }),
5644
+ // Only where something draws it: a sub-agent's writes are not shown, and
5645
+ // `ctx.present` is how this run says whether anybody is watching.
5646
+ ...ctx.present ? { present: (v) => view = v } : {},
5517
5647
  // Reported against this call the moment it is charged, so the row says
5518
5648
  // what it cost and the balance on screen moves with it.
5519
5649
  ...search ? {
@@ -5533,7 +5663,7 @@ async function executeToolCall(ctx, call, emit) {
5533
5663
  };
5534
5664
  try {
5535
5665
  const result = await dispatch(scoped, name, args);
5536
- emit({ type: "tool_result", id, name, result, ok: true });
5666
+ emit({ type: "tool_result", id, name, result, ok: true, ...view ? { view } : {} });
5537
5667
  return result;
5538
5668
  } catch (err) {
5539
5669
  if (err instanceof ToolError) {
@@ -6457,6 +6587,8 @@ export {
6457
6587
  webSearchCostLine,
6458
6588
  freeHelpLine,
6459
6589
  freeAlternativeLine,
6590
+ diffTint,
6591
+ renderDiffRows,
6460
6592
  shells,
6461
6593
  TodoList,
6462
6594
  loadMcpConfig,
@@ -6470,6 +6602,7 @@ export {
6470
6602
  pickReviewModel,
6471
6603
  NO_SEARCH_INSTRUCTION,
6472
6604
  runAgent,
6605
+ interruptedConversation,
6473
6606
  collectContext,
6474
6607
  INIT_PROMPT,
6475
6608
  INIT_HINT,
package/dist/clixad.mjs CHANGED
@@ -53,7 +53,7 @@ import {
53
53
  takeConfigNotices,
54
54
  toolSchemaFor,
55
55
  usd
56
- } from "./chunk-ABDMJDYJ.mjs";
56
+ } from "./chunk-JV44GYWT.mjs";
57
57
 
58
58
  // src/tui/load.ts
59
59
  import * as nodeModule from "node:module";
@@ -132,7 +132,7 @@ async function withProductionReact(load) {
132
132
  }
133
133
  function loadTui() {
134
134
  boundInkCaches();
135
- return withProductionReact(() => import("./tui-377KNURT.mjs"));
135
+ return withProductionReact(() => import("./tui-KA2P7O7W.mjs"));
136
136
  }
137
137
 
138
138
  // src/title.ts