clixad 0.0.1-beta.43 → 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.
@@ -5486,79 +5486,92 @@ ${NO_SEARCH_INSTRUCTION}`,
5486
5486
  ...turn.spendable ? { spendable: turn.spendable } : {}
5487
5487
  });
5488
5488
  }
5489
- let retriedEmpty = false;
5490
- for (let step = 0; step < maxSteps; step++) {
5491
- if (opts.signal?.aborted) return stop("aborted");
5492
- let turn;
5493
- try {
5494
- turn = await client.chatStream(messages, opts.model, {
5495
- effort: opts.effort,
5496
- tools: opts.tools ? opts.tools() : TOOL_SCHEMA,
5497
- signal: opts.signal,
5498
- onDelta: (text) => emit({ type: "delta", text })
5499
- });
5500
- } catch (err) {
5501
- if (isAbort(err) || opts.signal?.aborted) return stop("aborted");
5502
- throw err;
5503
- }
5504
- creditsCharged += turn.creditsCharged;
5505
- creditsWaived += turn.creditsWaived ?? 0;
5506
- if (turn.freeCreditsLeft !== void 0) freeCreditsLeft = turn.freeCreditsLeft;
5507
- balance = turn.balance;
5508
- emitUsage(turn);
5509
- const content = messageText(turn.message);
5510
- const calls = turn.message.tool_calls ?? [];
5511
- if (calls.length === 0 && !content.trim() && !retriedEmpty) {
5512
- retriedEmpty = true;
5513
- continue;
5514
- }
5515
- retriedEmpty = false;
5516
- messages.push(turn.message);
5517
- if (content) lastText = content;
5518
- if (calls.length === 0) {
5519
- emit({ type: "final", content, creditsCharged, balance });
5520
- return {
5521
- content,
5522
- messages: withoutReasoning(messages),
5523
- creditsCharged,
5524
- balance,
5525
- creditsWaived,
5526
- freeCreditsLeft,
5527
- searchCredits,
5528
- steps: step + 1
5529
- };
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);
5530
5496
  }
5531
- if (content) emit({ type: "message", content });
5532
- pendingImages = [];
5533
- for (const [i, call] of calls.entries()) {
5534
- if (opts.signal?.aborted) {
5535
- for (const rest of calls.slice(i)) messages.push(toolMessage(rest, "cancelled by the user"));
5536
- 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;
5537
5514
  }
5538
- const result = await executeToolCall(ctx, call, emit);
5539
- messages.push(toolMessage(call, result));
5540
- }
5541
- if (pendingImages.length) {
5542
- messages.push(imageMessage(pendingImages));
5543
- emit({
5544
- type: "attachment",
5545
- labels: pendingImages.map((image) => image.label)
5546
- });
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 });
5547
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));
5548
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
+ };
5549
5574
  }
5550
- emit({ type: "max_steps" });
5551
- return {
5552
- content: lastText || "(stopped: reached max steps)",
5553
- messages: withoutReasoning(messages),
5554
- creditsCharged,
5555
- balance,
5556
- creditsWaived,
5557
- freeCreditsLeft,
5558
- searchCredits,
5559
- steps: maxSteps,
5560
- stopped: "max_steps"
5561
- };
5562
5575
  function stop(reason) {
5563
5576
  emit({ type: reason });
5564
5577
  return {
@@ -5574,6 +5587,21 @@ ${NO_SEARCH_INSTRUCTION}`,
5574
5587
  };
5575
5588
  }
5576
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
+ }
5577
5605
  function toolMessage(call, content) {
5578
5606
  return { role: "tool", tool_call_id: call.id, name: call.function.name, content };
5579
5607
  }
@@ -6574,6 +6602,7 @@ export {
6574
6602
  pickReviewModel,
6575
6603
  NO_SEARCH_INSTRUCTION,
6576
6604
  runAgent,
6605
+ interruptedConversation,
6577
6606
  collectContext,
6578
6607
  INIT_PROMPT,
6579
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-T7SUUKJC.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-773NJ4FQ.mjs"));
135
+ return withProductionReact(() => import("./tui-KA2P7O7W.mjs"));
136
136
  }
137
137
 
138
138
  // src/title.ts
@@ -45,6 +45,7 @@ import {
45
45
  freeHelpLine,
46
46
  freeTierNotice,
47
47
  grantLabel,
48
+ interruptedConversation,
48
49
  isNewerVersion,
49
50
  listSessions,
50
51
  listWorkspaceFiles,
@@ -97,7 +98,7 @@ import {
97
98
  visibleLength,
98
99
  webSearchCostLine,
99
100
  welcomeBanner
100
- } from "./chunk-T7SUUKJC.mjs";
101
+ } from "./chunk-JV44GYWT.mjs";
101
102
 
102
103
  // src/tui/index.tsx
103
104
  import "react";
@@ -1615,6 +1616,7 @@ var SENDING_FEEDBACK = "sending\u2026";
1615
1616
  var WAITING_FOR_PAYMENT = "waiting for the payment\u2026";
1616
1617
  var FEEDBACK_TURN_INTERVAL = 50;
1617
1618
  var NOT_SIGNED_IN = " Not signed in, or the stored token isn't valid for this gateway.\n Run /login to sign in.";
1619
+ var KEPT_AFTER_ERROR = " The work done before this error is kept. Send a message to carry on from there,\n now or after a restart with clixad --continue.";
1618
1620
  var LIVE_OUTPUT_LINES = 5;
1619
1621
  var DELTA_FLUSH_MS = 50;
1620
1622
  var PASTE_ENTER_MS = 30;
@@ -2481,6 +2483,18 @@ function App({
2481
2483
  setLive({ text: "" });
2482
2484
  nextSponsor(model2);
2483
2485
  let history = messagesRef.current;
2486
+ const persist = (conversation) => {
2487
+ sessionRef.current.title ??= (task.split("\n")[0] || task).slice(0, 80);
2488
+ saveSession({
2489
+ id: sessionRef.current.id,
2490
+ started: sessionRef.current.started,
2491
+ updated: (/* @__PURE__ */ new Date()).toISOString(),
2492
+ cwd: root,
2493
+ model: model2,
2494
+ title: sessionRef.current.title,
2495
+ messages: conversation
2496
+ });
2497
+ };
2484
2498
  try {
2485
2499
  const window = contextWindow(model2);
2486
2500
  if (shouldCompact([{ role: "system", content: contextRef.current.systemPrompt }, ...history], window)) {
@@ -2546,20 +2560,13 @@ ${preferencesText}` : ""),
2546
2560
  webSearch: searchRef.current
2547
2561
  }),
2548
2562
  signal: ac.signal,
2549
- onEvent: handleEvent
2563
+ onEvent: handleEvent,
2564
+ // Without the system prompt, which is rebuilt per run.
2565
+ onCheckpoint: (conversation) => persist(conversation.slice(1))
2550
2566
  });
2551
2567
  const next = result.messages.slice(1);
2552
2568
  setMessages(next);
2553
- sessionRef.current.title ??= (task.split("\n")[0] || task).slice(0, 80);
2554
- saveSession({
2555
- id: sessionRef.current.id,
2556
- started: sessionRef.current.started,
2557
- updated: (/* @__PURE__ */ new Date()).toISOString(),
2558
- cwd: root,
2559
- model: model2,
2560
- title: sessionRef.current.title,
2561
- messages: next
2562
- });
2569
+ persist(next);
2563
2570
  if (result.stopped === "aborted") {
2564
2571
  if (streamRef.current.streamed) finishStream();
2565
2572
  push({ kind: "notice", tone: "warn", text: " (stopped)" });
@@ -2647,6 +2654,11 @@ ${preferencesText}` : ""),
2647
2654
  }
2648
2655
  } catch (err) {
2649
2656
  if (streamRef.current.streamed) finishStream();
2657
+ const kept = interruptedConversation(err)?.slice(1);
2658
+ if (kept) {
2659
+ setMessages(kept);
2660
+ persist(kept);
2661
+ }
2650
2662
  if ((err instanceof LimitRefusalError || err instanceof PremiumLockedError) && err.spendable) {
2651
2663
  balanceSeqRef.current++;
2652
2664
  takeSpendable(err.spendable.figures, err.spendable.balance);
@@ -2706,6 +2718,9 @@ ${preferencesText}` : ""),
2706
2718
  } else {
2707
2719
  push({ kind: "notice", tone: "error", text: ` error: ${errorNotice(err.message, void 0, " ")}` });
2708
2720
  }
2721
+ if (kept && !(err instanceof PaywallError) && !(err instanceof PremiumLockedError)) {
2722
+ push({ kind: "notice", text: KEPT_AFTER_ERROR });
2723
+ }
2709
2724
  } finally {
2710
2725
  abortRef.current = null;
2711
2726
  dropDelta();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clixad",
3
- "version": "0.0.1-beta.43",
3
+ "version": "0.0.1-beta.44",
4
4
  "description": "Free AI coding agent in your terminal, funded by rewarded ads.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",