clixad 0.0.1-beta.43 → 0.0.1-beta.45
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.
|
@@ -607,16 +607,16 @@ async function httpFailure(res, what) {
|
|
|
607
607
|
return detail ? `${what} failed: ${res.status} \u2014 ${detail}` : `${what} failed: ${res.status}`;
|
|
608
608
|
}
|
|
609
609
|
function sleep(ms, signal) {
|
|
610
|
-
return new Promise((
|
|
610
|
+
return new Promise((resolve5) => {
|
|
611
611
|
if (!signal) {
|
|
612
|
-
setTimeout(
|
|
612
|
+
setTimeout(resolve5, ms);
|
|
613
613
|
return;
|
|
614
614
|
}
|
|
615
|
-
if (signal.aborted) return
|
|
615
|
+
if (signal.aborted) return resolve5();
|
|
616
616
|
const done = () => {
|
|
617
617
|
clearTimeout(timer);
|
|
618
618
|
signal.removeEventListener("abort", done);
|
|
619
|
-
|
|
619
|
+
resolve5();
|
|
620
620
|
};
|
|
621
621
|
const timer = setTimeout(done, ms);
|
|
622
622
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -1939,16 +1939,16 @@ async function devLogin(client, config, email, invite) {
|
|
|
1939
1939
|
};
|
|
1940
1940
|
}
|
|
1941
1941
|
function sleep2(ms, signal) {
|
|
1942
|
-
return new Promise((
|
|
1942
|
+
return new Promise((resolve5) => {
|
|
1943
1943
|
if (!signal) {
|
|
1944
|
-
setTimeout(
|
|
1944
|
+
setTimeout(resolve5, ms);
|
|
1945
1945
|
return;
|
|
1946
1946
|
}
|
|
1947
|
-
if (signal.aborted) return
|
|
1947
|
+
if (signal.aborted) return resolve5();
|
|
1948
1948
|
const done = () => {
|
|
1949
1949
|
clearTimeout(timer);
|
|
1950
1950
|
signal.removeEventListener("abort", done);
|
|
1951
|
-
|
|
1951
|
+
resolve5();
|
|
1952
1952
|
};
|
|
1953
1953
|
const timer = setTimeout(done, ms);
|
|
1954
1954
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -3084,7 +3084,7 @@ var StdioTransport = class {
|
|
|
3084
3084
|
if (this.exited) return Promise.reject(new McpError(this.exited));
|
|
3085
3085
|
const id = ++this.seq;
|
|
3086
3086
|
const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
3087
|
-
return new Promise((
|
|
3087
|
+
return new Promise((resolve5, reject) => {
|
|
3088
3088
|
const settle = (fn) => {
|
|
3089
3089
|
clearTimeout(timer);
|
|
3090
3090
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -3098,7 +3098,7 @@ var StdioTransport = class {
|
|
|
3098
3098
|
const onAbort = () => settle(() => reject(new McpError("cancelled")));
|
|
3099
3099
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3100
3100
|
this.pending.set(id, {
|
|
3101
|
-
resolve: (v) => settle(() =>
|
|
3101
|
+
resolve: (v) => settle(() => resolve5(v)),
|
|
3102
3102
|
reject: (e) => settle(() => reject(e))
|
|
3103
3103
|
});
|
|
3104
3104
|
this.child.stdin?.write(payload, (err) => {
|
|
@@ -5486,79 +5486,92 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
5486
5486
|
...turn.spendable ? { spendable: turn.spendable } : {}
|
|
5487
5487
|
});
|
|
5488
5488
|
}
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
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
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
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
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
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
|
}
|
|
@@ -5944,12 +5972,20 @@ import {
|
|
|
5944
5972
|
writeFileSync as writeFileSync3
|
|
5945
5973
|
} from "node:fs";
|
|
5946
5974
|
import { homedir as homedir5 } from "node:os";
|
|
5947
|
-
import { join as join5 } from "node:path";
|
|
5975
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
5948
5976
|
var ROOT = join5(homedir5(), ".clixad");
|
|
5949
5977
|
var SESSION_DIR = join5(ROOT, "sessions");
|
|
5950
5978
|
var HISTORY_FILE = join5(ROOT, "history");
|
|
5951
5979
|
var MAX_HISTORY = 200;
|
|
5952
|
-
var MAX_SESSIONS =
|
|
5980
|
+
var MAX_SESSIONS = 200;
|
|
5981
|
+
var SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
5982
|
+
function isSessionId(id) {
|
|
5983
|
+
return SESSION_ID.test(id);
|
|
5984
|
+
}
|
|
5985
|
+
function sameDirectory(a, b) {
|
|
5986
|
+
const norm = (p) => process.platform === "win32" ? resolve4(p).toLowerCase() : resolve4(p);
|
|
5987
|
+
return norm(a) === norm(b);
|
|
5988
|
+
}
|
|
5953
5989
|
function newSessionId() {
|
|
5954
5990
|
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "");
|
|
5955
5991
|
return `${now}-${Math.random().toString(16).slice(2, 6)}`;
|
|
@@ -5964,12 +6000,25 @@ function saveSession(session) {
|
|
|
5964
6000
|
}
|
|
5965
6001
|
}
|
|
5966
6002
|
function loadSession(id) {
|
|
6003
|
+
if (!isSessionId(id)) return void 0;
|
|
5967
6004
|
try {
|
|
5968
|
-
|
|
6005
|
+
const s = JSON.parse(readFileSync5(join5(SESSION_DIR, `${id}.json`), "utf8"));
|
|
6006
|
+
return s && Array.isArray(s.messages) && typeof s.id === "string" ? s : void 0;
|
|
5969
6007
|
} catch {
|
|
5970
6008
|
return void 0;
|
|
5971
6009
|
}
|
|
5972
6010
|
}
|
|
6011
|
+
function deleteSession(id) {
|
|
6012
|
+
if (!isSessionId(id)) return false;
|
|
6013
|
+
const path = join5(SESSION_DIR, `${id}.json`);
|
|
6014
|
+
try {
|
|
6015
|
+
if (!existsSync4(path)) return false;
|
|
6016
|
+
rmSync(path);
|
|
6017
|
+
return true;
|
|
6018
|
+
} catch {
|
|
6019
|
+
return false;
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
5973
6022
|
function listSessions(cwd, limit = 20) {
|
|
5974
6023
|
if (!existsSync4(SESSION_DIR)) return [];
|
|
5975
6024
|
const metas = [];
|
|
@@ -5977,16 +6026,15 @@ function listSessions(cwd, limit = 20) {
|
|
|
5977
6026
|
if (!name.endsWith(".json")) continue;
|
|
5978
6027
|
const s = loadSession(name.slice(0, -5));
|
|
5979
6028
|
if (!s) continue;
|
|
5980
|
-
if (cwd && s.cwd
|
|
5981
|
-
const { messages
|
|
5982
|
-
metas.push(meta);
|
|
6029
|
+
if (cwd && !sameDirectory(s.cwd ?? "", cwd)) continue;
|
|
6030
|
+
const { messages, ...meta } = s;
|
|
6031
|
+
metas.push({ ...meta, title: meta.title ?? "", updated: meta.updated ?? "", messageCount: messages.length });
|
|
5983
6032
|
}
|
|
5984
6033
|
metas.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
5985
6034
|
return metas.slice(0, limit);
|
|
5986
6035
|
}
|
|
5987
6036
|
function hasSessionIn(cwd) {
|
|
5988
6037
|
if (!existsSync4(SESSION_DIR)) return false;
|
|
5989
|
-
const needle = `"cwd":${JSON.stringify(cwd)}`;
|
|
5990
6038
|
try {
|
|
5991
6039
|
for (const name of readdirSync3(SESSION_DIR)) {
|
|
5992
6040
|
if (!name.endsWith(".json")) continue;
|
|
@@ -5999,8 +6047,14 @@ function hasSessionIn(cwd) {
|
|
|
5999
6047
|
} finally {
|
|
6000
6048
|
closeSync2(fd);
|
|
6001
6049
|
}
|
|
6002
|
-
|
|
6003
|
-
|
|
6050
|
+
const field = /"cwd":("(?:[^"\\]|\\.)*")/.exec(head)?.[1];
|
|
6051
|
+
let stored;
|
|
6052
|
+
try {
|
|
6053
|
+
stored = field ? JSON.parse(field) : loadSession(name.slice(0, -5))?.cwd;
|
|
6054
|
+
} catch {
|
|
6055
|
+
stored = void 0;
|
|
6056
|
+
}
|
|
6057
|
+
if (stored && sameDirectory(stored, cwd)) return true;
|
|
6004
6058
|
}
|
|
6005
6059
|
} catch {
|
|
6006
6060
|
}
|
|
@@ -6045,6 +6099,7 @@ var CONTEXT_TOKENS = {
|
|
|
6045
6099
|
"claude-haiku-5.5": 24e3,
|
|
6046
6100
|
"deepseek-v4-flash": 1048576,
|
|
6047
6101
|
"gemini-3.5-flash-lite": 1048576,
|
|
6102
|
+
"glm-5.3-flash": 1048576,
|
|
6048
6103
|
"kimi-k2.5": 262144,
|
|
6049
6104
|
"gemini-3.8-flash": 1048576,
|
|
6050
6105
|
"kimi-k2.7-code": 262144,
|
|
@@ -6132,14 +6187,14 @@ function runKimiCode(config, task, opts = {}) {
|
|
|
6132
6187
|
OPENAI_BASE_URL: `${config.gatewayUrl.replace(/\/+$/, "")}/v1`,
|
|
6133
6188
|
OPENAI_API_KEY: config.token
|
|
6134
6189
|
};
|
|
6135
|
-
return new Promise((
|
|
6190
|
+
return new Promise((resolve5) => {
|
|
6136
6191
|
const child = process.platform === "win32" ? spawn5([bin, ...args].map(quoteWin).join(" "), { stdio: "inherit", env, shell: true }) : spawn5(bin, args, { stdio: "inherit", env });
|
|
6137
6192
|
child.on("error", (err) => {
|
|
6138
6193
|
if (err.code === "ENOENT") console.log("\n" + INSTALL_HELP);
|
|
6139
6194
|
else console.error(`failed to launch Kimi CLI: ${err.message}`);
|
|
6140
|
-
|
|
6195
|
+
resolve5(127);
|
|
6141
6196
|
});
|
|
6142
|
-
child.on("exit", (code) =>
|
|
6197
|
+
child.on("exit", (code) => resolve5(code ?? 0));
|
|
6143
6198
|
});
|
|
6144
6199
|
}
|
|
6145
6200
|
function quoteWin(s) {
|
|
@@ -6399,6 +6454,8 @@ function cutPoint(messages, keepTurns = 2) {
|
|
|
6399
6454
|
}
|
|
6400
6455
|
return 0;
|
|
6401
6456
|
}
|
|
6457
|
+
var SUMMARY_LEAD = "Summary of the earlier conversation:\n\n";
|
|
6458
|
+
var SUMMARY_ACK = "Understood \u2014 continuing from that summary.";
|
|
6402
6459
|
var SUMMARY_PROMPT = "Summarise the conversation above for your future self, which will continue the work without seeing it. Cover: what the user asked for, what was already changed (files and the reasoning), what was learned about the codebase, and what is still open. Be specific about file paths and names. No preamble.";
|
|
6403
6460
|
async function compact(client, model, messages, opts = {}) {
|
|
6404
6461
|
const cut2 = cutPoint(messages, opts.keepTurns ?? 2);
|
|
@@ -6414,10 +6471,8 @@ async function compact(client, model, messages, opts = {}) {
|
|
|
6414
6471
|
const summary = messageText(turn.message).trim() || "(no summary returned)";
|
|
6415
6472
|
const compacted = [
|
|
6416
6473
|
messages[0],
|
|
6417
|
-
{ role: "user", content: `
|
|
6418
|
-
|
|
6419
|
-
${summary}` },
|
|
6420
|
-
{ role: "assistant", content: "Understood \u2014 continuing from that summary." },
|
|
6474
|
+
{ role: "user", content: `${SUMMARY_LEAD}${summary}` },
|
|
6475
|
+
{ role: "assistant", content: SUMMARY_ACK },
|
|
6421
6476
|
...messages.slice(cut2)
|
|
6422
6477
|
];
|
|
6423
6478
|
return {
|
|
@@ -6533,6 +6588,7 @@ export {
|
|
|
6533
6588
|
referLines,
|
|
6534
6589
|
redeemMessage,
|
|
6535
6590
|
ledgerReasonLabel,
|
|
6591
|
+
messageText,
|
|
6536
6592
|
PaywallError,
|
|
6537
6593
|
PremiumLockedError,
|
|
6538
6594
|
LimitRefusalError,
|
|
@@ -6574,6 +6630,8 @@ export {
|
|
|
6574
6630
|
pickReviewModel,
|
|
6575
6631
|
NO_SEARCH_INSTRUCTION,
|
|
6576
6632
|
runAgent,
|
|
6633
|
+
interruptedConversation,
|
|
6634
|
+
INTERRUPTED_CALL,
|
|
6577
6635
|
collectContext,
|
|
6578
6636
|
INIT_PROMPT,
|
|
6579
6637
|
INIT_HINT,
|
|
@@ -6586,9 +6644,12 @@ export {
|
|
|
6586
6644
|
switchMode,
|
|
6587
6645
|
createPermit,
|
|
6588
6646
|
denyAll,
|
|
6647
|
+
MAX_SESSIONS,
|
|
6648
|
+
sameDirectory,
|
|
6589
6649
|
newSessionId,
|
|
6590
6650
|
saveSession,
|
|
6591
6651
|
loadSession,
|
|
6652
|
+
deleteSession,
|
|
6592
6653
|
listSessions,
|
|
6593
6654
|
hasSessionIn,
|
|
6594
6655
|
latestSession,
|
|
@@ -6613,6 +6674,8 @@ export {
|
|
|
6613
6674
|
estimateTokens,
|
|
6614
6675
|
conversationTokens,
|
|
6615
6676
|
shouldCompact,
|
|
6677
|
+
SUMMARY_LEAD,
|
|
6678
|
+
SUMMARY_ACK,
|
|
6616
6679
|
compact,
|
|
6617
6680
|
VERSION,
|
|
6618
6681
|
VERSION_LABEL,
|
package/dist/clixad.mjs
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
rowTaskHint,
|
|
44
44
|
runAgent,
|
|
45
45
|
runKimiCode,
|
|
46
|
+
sameDirectory,
|
|
46
47
|
saveConfig,
|
|
47
48
|
savedEffort,
|
|
48
49
|
shells,
|
|
@@ -53,7 +54,7 @@ import {
|
|
|
53
54
|
takeConfigNotices,
|
|
54
55
|
toolSchemaFor,
|
|
55
56
|
usd
|
|
56
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-Y55DBUP6.mjs";
|
|
57
58
|
|
|
58
59
|
// src/tui/load.ts
|
|
59
60
|
import * as nodeModule from "node:module";
|
|
@@ -132,7 +133,7 @@ async function withProductionReact(load) {
|
|
|
132
133
|
}
|
|
133
134
|
function loadTui() {
|
|
134
135
|
boundInkCaches();
|
|
135
|
-
return withProductionReact(() => import("./tui-
|
|
136
|
+
return withProductionReact(() => import("./tui-KH7DNN65.mjs"));
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
// src/title.ts
|
|
@@ -615,13 +616,18 @@ async function codeCmd(client, config, rest) {
|
|
|
615
616
|
}
|
|
616
617
|
async function repl(client, config, opts = {}) {
|
|
617
618
|
if (opts.resume === "pick") {
|
|
618
|
-
const sessions = listSessions(void 0,
|
|
619
|
+
const sessions = listSessions(void 0, 20);
|
|
619
620
|
if (!sessions.length) return console.log(c.yellow("no saved sessions yet"));
|
|
620
|
-
|
|
621
|
+
const here = process.cwd();
|
|
622
|
+
console.log(c.bold("Sessions (newest first, * = this directory):"));
|
|
621
623
|
for (const s of sessions) {
|
|
622
|
-
|
|
624
|
+
const mark = sameDirectory(s.cwd, here) ? "*" : " ";
|
|
625
|
+
console.log(
|
|
626
|
+
`${mark} ${c.cyan(s.id)} ${s.updated.slice(0, 16).replace("T", " ")} ${String(s.messageCount).padStart(4)} msgs ${s.title}`
|
|
627
|
+
);
|
|
628
|
+
if (mark === " ") console.log(c.dim(` in ${s.cwd}`));
|
|
623
629
|
}
|
|
624
|
-
console.log(c.dim("\n resume one with: clixad --resume <id>"));
|
|
630
|
+
console.log(c.dim("\n resume one with: clixad --resume <id> \xB7 delete one in the REPL: /resume, then d twice"));
|
|
625
631
|
return;
|
|
626
632
|
}
|
|
627
633
|
if (!process.stdin.isTTY) {
|
|
@@ -639,9 +645,10 @@ async function repl(client, config, opts = {}) {
|
|
|
639
645
|
if (!config.token) return;
|
|
640
646
|
}
|
|
641
647
|
let session;
|
|
648
|
+
const notices = [...opts.notices ?? []];
|
|
642
649
|
if (opts.resume === "latest") {
|
|
643
650
|
session = latestSession(process.cwd());
|
|
644
|
-
if (!session)
|
|
651
|
+
if (!session) notices.push("no saved session in this directory, so this is a new one (/resume all lists every directory's)");
|
|
645
652
|
} else if (opts.resume) {
|
|
646
653
|
session = loadSession(opts.resume);
|
|
647
654
|
if (!session) return console.log(c.red(`no such session: ${opts.resume}`));
|
|
@@ -661,7 +668,7 @@ async function repl(client, config, opts = {}) {
|
|
|
661
668
|
newAccount,
|
|
662
669
|
preferences,
|
|
663
670
|
initialTask: opts.task,
|
|
664
|
-
...
|
|
671
|
+
...notices.length ? { notices } : {},
|
|
665
672
|
...initHint ? { initHint } : {}
|
|
666
673
|
});
|
|
667
674
|
}
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
EXTRA_AD_FORMATS,
|
|
7
7
|
INIT_HINT,
|
|
8
8
|
INIT_PROMPT,
|
|
9
|
+
INTERRUPTED_CALL,
|
|
9
10
|
LimitRefusalError,
|
|
11
|
+
MAX_SESSIONS,
|
|
10
12
|
MIN_BANNER_WIDTH,
|
|
11
13
|
MODES,
|
|
12
14
|
MODE_LABEL,
|
|
@@ -17,6 +19,8 @@ import {
|
|
|
17
19
|
PaywallError,
|
|
18
20
|
PremiumLockedError,
|
|
19
21
|
SPINNER_TICKS_PER_FRAME,
|
|
22
|
+
SUMMARY_ACK,
|
|
23
|
+
SUMMARY_LEAD,
|
|
20
24
|
TERMS_NOTICE,
|
|
21
25
|
TERMS_URL,
|
|
22
26
|
TOOLS,
|
|
@@ -35,6 +39,7 @@ import {
|
|
|
35
39
|
conversationTokens,
|
|
36
40
|
createPermit,
|
|
37
41
|
createState,
|
|
42
|
+
deleteSession,
|
|
38
43
|
diffTint,
|
|
39
44
|
effortLabel,
|
|
40
45
|
effortRaised,
|
|
@@ -45,12 +50,14 @@ import {
|
|
|
45
50
|
freeHelpLine,
|
|
46
51
|
freeTierNotice,
|
|
47
52
|
grantLabel,
|
|
53
|
+
interruptedConversation,
|
|
48
54
|
isNewerVersion,
|
|
49
55
|
listSessions,
|
|
50
56
|
listWorkspaceFiles,
|
|
51
57
|
loadHistory,
|
|
52
58
|
loadMcpConfig,
|
|
53
59
|
loadSession,
|
|
60
|
+
messageText,
|
|
54
61
|
modeInstruction,
|
|
55
62
|
newSessionId,
|
|
56
63
|
nextMode,
|
|
@@ -97,7 +104,7 @@ import {
|
|
|
97
104
|
visibleLength,
|
|
98
105
|
webSearchCostLine,
|
|
99
106
|
welcomeBanner
|
|
100
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-Y55DBUP6.mjs";
|
|
101
108
|
|
|
102
109
|
// src/tui/index.tsx
|
|
103
110
|
import "react";
|
|
@@ -110,6 +117,7 @@ import { Box as Box2, Static, Text as Text2, useApp, useInput, usePaste, useStdo
|
|
|
110
117
|
// src/mentions.ts
|
|
111
118
|
var TRAILING = /[),;:]+$/;
|
|
112
119
|
var MAX_MENTION_CHARS = 12e4;
|
|
120
|
+
var MENTIONS_HEADING = "\n\n# Files the user referenced\n";
|
|
113
121
|
function findMentions(text) {
|
|
114
122
|
const out = [];
|
|
115
123
|
for (const match of text.matchAll(/(?:^|\s)@([^\s]+)/g)) {
|
|
@@ -144,10 +152,7 @@ ${clipped}`);
|
|
|
144
152
|
}
|
|
145
153
|
if (blocks.length === 0) return { text, attached: [], images: [] };
|
|
146
154
|
return {
|
|
147
|
-
text: `${text}
|
|
148
|
-
|
|
149
|
-
# Files the user referenced
|
|
150
|
-
Their contents are below, already read for you \u2014 do not call read_file on them again.
|
|
155
|
+
text: `${text}${MENTIONS_HEADING}Their contents are below, already read for you \u2014 do not call read_file on them again.
|
|
151
156
|
|
|
152
157
|
` + blocks.join("\n\n"),
|
|
153
158
|
attached,
|
|
@@ -420,8 +425,8 @@ var COMMANDS = [
|
|
|
420
425
|
{ name: "clear", desc: "clear the screen and start a new session" },
|
|
421
426
|
// Deliberately *no* `args` on /resume, for /login's reason: an arg hint makes
|
|
422
427
|
// enter complete the command instead of running it, and the picker is the
|
|
423
|
-
// point. `/resume <id
|
|
424
|
-
{ name: "resume", desc: "pick up an earlier session
|
|
428
|
+
// point. `/resume <id>`, `/resume all` and `/resume delete <id>` work typed in full.
|
|
429
|
+
{ name: "resume", desc: "pick up or delete an earlier session (all: every directory)" },
|
|
425
430
|
{ name: "init", desc: "write a CLIXAD.md for this project" },
|
|
426
431
|
// Deliberately *no* `args` on either, for /login's reason: an arg hint makes
|
|
427
432
|
// enter complete the command instead of running it, and listing is what both
|
|
@@ -1461,9 +1466,9 @@ function presentResult(input, width, tint = diffTint()) {
|
|
|
1461
1466
|
const [notes = "", ...rest] = result.split("\n\n");
|
|
1462
1467
|
const out = nonEmpty(rest.join("\n\n")).filter((l) => l !== "(nothing new since the last read)");
|
|
1463
1468
|
const { body, more } = rows(out);
|
|
1464
|
-
const
|
|
1469
|
+
const failed2 = /\] (exited with code (?!0\b)\d+|failed|killed)/.test(notes);
|
|
1465
1470
|
return {
|
|
1466
|
-
status:
|
|
1471
|
+
status: failed2 ? "error" : "ok",
|
|
1467
1472
|
line: fit(nonEmpty(notes)[0] ?? "read"),
|
|
1468
1473
|
body,
|
|
1469
1474
|
more,
|
|
@@ -1603,6 +1608,69 @@ function askWords(req) {
|
|
|
1603
1608
|
}
|
|
1604
1609
|
}
|
|
1605
1610
|
|
|
1611
|
+
// src/tui/replay.ts
|
|
1612
|
+
function shellNote(command2, output) {
|
|
1613
|
+
return `I ran \`${command2}\` in the workspace myself. Its output:
|
|
1614
|
+
|
|
1615
|
+
${output}`;
|
|
1616
|
+
}
|
|
1617
|
+
var SHELL_NOTE = /^I ran `([^\n]*)` in the workspace myself\. Its output:\n\n([\s\S]*)$/;
|
|
1618
|
+
var IMAGE_NOTE = /^Here (?:is the image|are the images) you read \(/;
|
|
1619
|
+
function failed(result) {
|
|
1620
|
+
return result.startsWith("error: ") || result === INTERRUPTED_CALL;
|
|
1621
|
+
}
|
|
1622
|
+
function parseArgs(raw) {
|
|
1623
|
+
if (!raw) return {};
|
|
1624
|
+
try {
|
|
1625
|
+
const parsed = JSON.parse(raw);
|
|
1626
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1627
|
+
} catch {
|
|
1628
|
+
return {};
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function replayItems(messages) {
|
|
1632
|
+
const answers = /* @__PURE__ */ new Map();
|
|
1633
|
+
for (const m of messages) {
|
|
1634
|
+
if (m.role === "tool" && m.tool_call_id) answers.set(m.tool_call_id, messageText(m));
|
|
1635
|
+
}
|
|
1636
|
+
const out = [];
|
|
1637
|
+
for (const m of messages) {
|
|
1638
|
+
if (m.role === "user") {
|
|
1639
|
+
const text = messageText(m);
|
|
1640
|
+
if (text.startsWith(SUMMARY_LEAD)) {
|
|
1641
|
+
out.push({ kind: "notice", text: "earlier messages were summarised to save context; the summary is what the model has" });
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (IMAGE_NOTE.test(text)) continue;
|
|
1645
|
+
const shell = SHELL_NOTE.exec(text);
|
|
1646
|
+
if (shell) {
|
|
1647
|
+
out.push({ kind: "user", text: `!${shell[1]}` });
|
|
1648
|
+
out.push({ kind: "tool", name: "!", args: { command: shell[1] }, result: shell[2] ?? "", ok: true });
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
const cut = text.indexOf(MENTIONS_HEADING);
|
|
1652
|
+
const typed = (cut >= 0 ? text.slice(0, cut) : text).trim();
|
|
1653
|
+
if (typed) out.push({ kind: "user", text: typed });
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
if (m.role === "assistant") {
|
|
1657
|
+
const text = messageText(m).trim();
|
|
1658
|
+
if (text && text !== SUMMARY_ACK) out.push({ kind: "assistant", text });
|
|
1659
|
+
for (const call of m.tool_calls ?? []) {
|
|
1660
|
+
const result = answers.get(call.id) ?? INTERRUPTED_CALL;
|
|
1661
|
+
out.push({
|
|
1662
|
+
kind: "tool",
|
|
1663
|
+
name: call.function.name,
|
|
1664
|
+
args: parseArgs(call.function.arguments),
|
|
1665
|
+
result,
|
|
1666
|
+
ok: !failed(result)
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
return out;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1606
1674
|
// src/tui/app.tsx
|
|
1607
1675
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1608
1676
|
var WORKING = "working\u2026";
|
|
@@ -1615,6 +1683,7 @@ var SENDING_FEEDBACK = "sending\u2026";
|
|
|
1615
1683
|
var WAITING_FOR_PAYMENT = "waiting for the payment\u2026";
|
|
1616
1684
|
var FEEDBACK_TURN_INTERVAL = 50;
|
|
1617
1685
|
var NOT_SIGNED_IN = " Not signed in, or the stored token isn't valid for this gateway.\n Run /login to sign in.";
|
|
1686
|
+
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
1687
|
var LIVE_OUTPUT_LINES = 5;
|
|
1619
1688
|
var DELTA_FLUSH_MS = 50;
|
|
1620
1689
|
var PASTE_ENTER_MS = 30;
|
|
@@ -1713,6 +1782,7 @@ var INIT_HINT_AFTER_TURNS = 3;
|
|
|
1713
1782
|
var freshStream = () => ({ text: "", md: {}, committed: false, streamed: false });
|
|
1714
1783
|
var EARN_ROW = "\0earn";
|
|
1715
1784
|
var BUY_ROW = "\0buy";
|
|
1785
|
+
var PICKER_DELETE_CONFIRM = "Press d again to delete it for good \xB7 any other key keeps it";
|
|
1716
1786
|
function App({
|
|
1717
1787
|
client,
|
|
1718
1788
|
config,
|
|
@@ -1803,6 +1873,8 @@ function App({
|
|
|
1803
1873
|
const [plan, setPlan] = useState(null);
|
|
1804
1874
|
const [picker, setPicker] = useState(null);
|
|
1805
1875
|
const [pickerSel, setPickerSel] = useState(0);
|
|
1876
|
+
const [pickerArmed, setPickerArmed] = useState(null);
|
|
1877
|
+
const [pickerNote, setPickerNote] = useState(null);
|
|
1806
1878
|
const [buy, setBuy] = useState(null);
|
|
1807
1879
|
const [tick, setTick] = useState(0);
|
|
1808
1880
|
const [startedAt, setStartedAt] = useState(0);
|
|
@@ -2111,9 +2183,6 @@ function App({
|
|
|
2111
2183
|
if (freeLine) {
|
|
2112
2184
|
push({ kind: "notice", tone: (wallet?.balance ?? 0) <= 0 ? "warn" : "good", text: ` ${freeLine}` });
|
|
2113
2185
|
}
|
|
2114
|
-
if (session?.messages.length) {
|
|
2115
|
-
push({ kind: "notice", text: ` resumed session ${session.id} (${session.messages.length} messages)` });
|
|
2116
|
-
}
|
|
2117
2186
|
if (initHint?.returning) {
|
|
2118
2187
|
initHintedRef.current = true;
|
|
2119
2188
|
push({ kind: "notice", text: ` ${INIT_HINT}` });
|
|
@@ -2158,14 +2227,14 @@ function App({
|
|
|
2158
2227
|
if (cancelled) return void hub.close();
|
|
2159
2228
|
mcpRef.current = hub;
|
|
2160
2229
|
const ready = hub.status().filter((s) => s.state === "ready");
|
|
2161
|
-
const
|
|
2230
|
+
const failed2 = hub.status().filter((s) => s.state === "failed");
|
|
2162
2231
|
if (ready.length) {
|
|
2163
2232
|
push({
|
|
2164
2233
|
kind: "notice",
|
|
2165
2234
|
text: ` mcp: ${ready.map((s) => `${s.name} (${s.toolCount} tools)`).join(", ")}`
|
|
2166
2235
|
});
|
|
2167
2236
|
}
|
|
2168
|
-
for (const server of
|
|
2237
|
+
for (const server of failed2) {
|
|
2169
2238
|
push({ kind: "notice", tone: "warn", text: ` mcp: ${server.name} did not start \u2014 ${server.error}` });
|
|
2170
2239
|
}
|
|
2171
2240
|
});
|
|
@@ -2245,7 +2314,7 @@ function App({
|
|
|
2245
2314
|
const fitting = AD_FORMATS.filter((format) => adFitsFormat(format, colsRef.current));
|
|
2246
2315
|
void (async () => {
|
|
2247
2316
|
let direct = [];
|
|
2248
|
-
let
|
|
2317
|
+
let failed2 = false;
|
|
2249
2318
|
if (directSupportedRef.current && fitting.length > 0) {
|
|
2250
2319
|
const ask2 = sponsorDirect ?? ((formats, cols2, signal) => client.directSponsor(formats, cols2, signal));
|
|
2251
2320
|
let answer = { supported: true, lines: [] };
|
|
@@ -2255,9 +2324,9 @@ function App({
|
|
|
2255
2324
|
answer = { supported: true, lines: [], failed: true };
|
|
2256
2325
|
}
|
|
2257
2326
|
if (!answer.supported) directSupportedRef.current = false;
|
|
2258
|
-
|
|
2327
|
+
failed2 = answer.failed === true;
|
|
2259
2328
|
direct = answer.lines;
|
|
2260
|
-
if (!
|
|
2329
|
+
if (!failed2) knownBookedRef.current = new Set(direct.map((line2) => line2.format));
|
|
2261
2330
|
}
|
|
2262
2331
|
if (call !== sponsorCallRef.current) return;
|
|
2263
2332
|
for (const format of ["top_bar", ...EXTRA_AD_FORMATS]) {
|
|
@@ -2265,7 +2334,7 @@ function App({
|
|
|
2265
2334
|
const booked = direct.find((line2) => line2.format === format);
|
|
2266
2335
|
if (booked && "hold" in booked) continue;
|
|
2267
2336
|
if (booked) paintDirect(booked);
|
|
2268
|
-
else if (
|
|
2337
|
+
else if (failed2 && knownBookedRef.current.has(format)) continue;
|
|
2269
2338
|
else fetchNetwork(format);
|
|
2270
2339
|
}
|
|
2271
2340
|
})();
|
|
@@ -2362,6 +2431,26 @@ function App({
|
|
|
2362
2431
|
},
|
|
2363
2432
|
[cols, keepOutput, push]
|
|
2364
2433
|
);
|
|
2434
|
+
const replayConversation = useCallback(
|
|
2435
|
+
(stored) => {
|
|
2436
|
+
const title = stored.title ? ` \xB7 ${stored.title}` : "";
|
|
2437
|
+
push({
|
|
2438
|
+
kind: "notice",
|
|
2439
|
+
tone: "good",
|
|
2440
|
+
text: ` resumed session ${stored.id} (${stored.messages.length} messages)${title}`
|
|
2441
|
+
});
|
|
2442
|
+
for (const item of replayItems(stored.messages)) {
|
|
2443
|
+
if (item.kind === "tool") commitTool(item.name, item.args, { result: item.result, ok: item.ok });
|
|
2444
|
+
else if (item.kind === "notice") push({ kind: "notice", text: ` ${item.text}` });
|
|
2445
|
+
else push(item);
|
|
2446
|
+
}
|
|
2447
|
+
push({ kind: "notice", text: " the agent has all of this conversation; carry on below" });
|
|
2448
|
+
},
|
|
2449
|
+
[commitTool, push]
|
|
2450
|
+
);
|
|
2451
|
+
useEffect(() => {
|
|
2452
|
+
if (session?.messages.length) replayConversation(session);
|
|
2453
|
+
}, []);
|
|
2365
2454
|
const handleEvent = useCallback(
|
|
2366
2455
|
(event) => {
|
|
2367
2456
|
if (event.type === "delta") {
|
|
@@ -2481,6 +2570,18 @@ function App({
|
|
|
2481
2570
|
setLive({ text: "" });
|
|
2482
2571
|
nextSponsor(model2);
|
|
2483
2572
|
let history = messagesRef.current;
|
|
2573
|
+
const persist = (conversation) => {
|
|
2574
|
+
sessionRef.current.title ??= (task.split("\n")[0] || task).slice(0, 80);
|
|
2575
|
+
saveSession({
|
|
2576
|
+
id: sessionRef.current.id,
|
|
2577
|
+
started: sessionRef.current.started,
|
|
2578
|
+
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2579
|
+
cwd: root,
|
|
2580
|
+
model: model2,
|
|
2581
|
+
title: sessionRef.current.title,
|
|
2582
|
+
messages: conversation
|
|
2583
|
+
});
|
|
2584
|
+
};
|
|
2484
2585
|
try {
|
|
2485
2586
|
const window = contextWindow(model2);
|
|
2486
2587
|
if (shouldCompact([{ role: "system", content: contextRef.current.systemPrompt }, ...history], window)) {
|
|
@@ -2546,20 +2647,13 @@ ${preferencesText}` : ""),
|
|
|
2546
2647
|
webSearch: searchRef.current
|
|
2547
2648
|
}),
|
|
2548
2649
|
signal: ac.signal,
|
|
2549
|
-
onEvent: handleEvent
|
|
2650
|
+
onEvent: handleEvent,
|
|
2651
|
+
// Without the system prompt, which is rebuilt per run.
|
|
2652
|
+
onCheckpoint: (conversation) => persist(conversation.slice(1))
|
|
2550
2653
|
});
|
|
2551
2654
|
const next = result.messages.slice(1);
|
|
2552
2655
|
setMessages(next);
|
|
2553
|
-
|
|
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
|
-
});
|
|
2656
|
+
persist(next);
|
|
2563
2657
|
if (result.stopped === "aborted") {
|
|
2564
2658
|
if (streamRef.current.streamed) finishStream();
|
|
2565
2659
|
push({ kind: "notice", tone: "warn", text: " (stopped)" });
|
|
@@ -2647,6 +2741,11 @@ ${preferencesText}` : ""),
|
|
|
2647
2741
|
}
|
|
2648
2742
|
} catch (err) {
|
|
2649
2743
|
if (streamRef.current.streamed) finishStream();
|
|
2744
|
+
const kept = interruptedConversation(err)?.slice(1);
|
|
2745
|
+
if (kept) {
|
|
2746
|
+
setMessages(kept);
|
|
2747
|
+
persist(kept);
|
|
2748
|
+
}
|
|
2650
2749
|
if ((err instanceof LimitRefusalError || err instanceof PremiumLockedError) && err.spendable) {
|
|
2651
2750
|
balanceSeqRef.current++;
|
|
2652
2751
|
takeSpendable(err.spendable.figures, err.spendable.balance);
|
|
@@ -2706,6 +2805,9 @@ ${preferencesText}` : ""),
|
|
|
2706
2805
|
} else {
|
|
2707
2806
|
push({ kind: "notice", tone: "error", text: ` error: ${errorNotice(err.message, void 0, " ")}` });
|
|
2708
2807
|
}
|
|
2808
|
+
if (kept && !(err instanceof PaywallError) && !(err instanceof PremiumLockedError)) {
|
|
2809
|
+
push({ kind: "notice", text: KEPT_AFTER_ERROR });
|
|
2810
|
+
}
|
|
2709
2811
|
} finally {
|
|
2710
2812
|
abortRef.current = null;
|
|
2711
2813
|
dropDelta();
|
|
@@ -3334,11 +3436,23 @@ ${preferencesText}` : ""),
|
|
|
3334
3436
|
// earlier conversation was to quit the one you were in.
|
|
3335
3437
|
case "resume":
|
|
3336
3438
|
case "sessions": {
|
|
3337
|
-
const
|
|
3338
|
-
if (
|
|
3339
|
-
|
|
3439
|
+
const [verb, target] = arg.split(/\s+/);
|
|
3440
|
+
if (verb === "delete" || verb === "rm") {
|
|
3441
|
+
if (!target) {
|
|
3442
|
+
push({ kind: "notice", tone: "warn", text: " /resume delete <id>, or press d twice on a row in /resume" });
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
if (target === sessionRef.current.id) {
|
|
3446
|
+
push({ kind: "notice", tone: "warn", text: ` ${target} is the conversation you are in. /clear starts a new one first.` });
|
|
3447
|
+
return;
|
|
3448
|
+
}
|
|
3449
|
+
push(
|
|
3450
|
+
deleteSession(target) ? { kind: "notice", tone: "good", text: ` deleted session ${target}` } : { kind: "notice", tone: "error", text: ` no such session: ${target}` }
|
|
3451
|
+
);
|
|
3340
3452
|
return;
|
|
3341
3453
|
}
|
|
3454
|
+
const everywhere = verb === "all";
|
|
3455
|
+
const metas = listSessions(everywhere ? void 0 : root, MAX_SESSIONS);
|
|
3342
3456
|
const load = (id) => {
|
|
3343
3457
|
const found = loadSession(id);
|
|
3344
3458
|
if (!found) {
|
|
@@ -3347,26 +3461,42 @@ ${preferencesText}` : ""),
|
|
|
3347
3461
|
}
|
|
3348
3462
|
setMessages(found.messages);
|
|
3349
3463
|
sessionRef.current = { id: found.id, started: found.started, title: found.title };
|
|
3464
|
+
todosRef.current = new TodoList();
|
|
3465
|
+
lastTurnRef.current = null;
|
|
3466
|
+
replayConversation(found);
|
|
3467
|
+
};
|
|
3468
|
+
if (arg && !everywhere) return load(arg);
|
|
3469
|
+
if (!metas.length) {
|
|
3470
|
+
const elsewhere = everywhere ? 0 : listSessions(void 0, MAX_SESSIONS).length;
|
|
3350
3471
|
push({
|
|
3351
3472
|
kind: "notice",
|
|
3352
|
-
tone: "
|
|
3353
|
-
text: `
|
|
3354
|
-
${found.title}`
|
|
3473
|
+
tone: "warn",
|
|
3474
|
+
text: elsewhere ? ` no saved sessions in this directory yet. ${elsewhere} in other directories: /resume all lists them.` : " no saved sessions yet"
|
|
3355
3475
|
});
|
|
3356
|
-
|
|
3357
|
-
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3358
3478
|
const items = metas.map((m) => ({
|
|
3359
3479
|
value: m.id,
|
|
3360
3480
|
label: m.updated.slice(0, 16).replace("T", " "),
|
|
3361
|
-
hint: m.title.slice(0, 60)
|
|
3481
|
+
hint: `${m.title.slice(0, 60)} \xB7 ${m.messageCount} messages${everywhere ? ` \xB7 ${m.cwd}` : ""}`,
|
|
3362
3482
|
current: m.id === sessionRef.current.id
|
|
3363
3483
|
}));
|
|
3364
3484
|
setPickerSel(0);
|
|
3485
|
+
setPickerArmed(null);
|
|
3486
|
+
setPickerNote(null);
|
|
3365
3487
|
setPicker({
|
|
3366
|
-
title: "Resume a session",
|
|
3367
|
-
subtitle: "Replaces the current conversation; nothing on screen is lost.",
|
|
3488
|
+
title: everywhere ? "Resume a session from any directory" : "Resume a session",
|
|
3489
|
+
subtitle: "Replaces the current conversation; nothing on screen is lost. d twice deletes one.",
|
|
3368
3490
|
items,
|
|
3369
|
-
onPick: (choice) => load(choice.value)
|
|
3491
|
+
onPick: (choice) => load(choice.value),
|
|
3492
|
+
onDelete: (choice) => {
|
|
3493
|
+
if (choice.value === sessionRef.current.id) {
|
|
3494
|
+
return "That is the conversation you are in. /clear starts a new one first.";
|
|
3495
|
+
}
|
|
3496
|
+
if (!deleteSession(choice.value)) return "It could not be deleted.";
|
|
3497
|
+
push({ kind: "notice", tone: "good", text: ` deleted session ${choice.value}` });
|
|
3498
|
+
return void 0;
|
|
3499
|
+
}
|
|
3370
3500
|
});
|
|
3371
3501
|
return;
|
|
3372
3502
|
}
|
|
@@ -3465,7 +3595,7 @@ ${preferencesText}` : ""),
|
|
|
3465
3595
|
push({ kind: "notice", tone: "warn", text: ` unknown command: /${cmd} \u2014 try /help` });
|
|
3466
3596
|
}
|
|
3467
3597
|
},
|
|
3468
|
-
[client, cols, config, cycleMode, exit, model, modelTaskHints, premium, push, root, runAdWall, runBusy, runBuy, runLogin, runTurn, setPremium, writeToStdout]
|
|
3598
|
+
[client, cols, config, cycleMode, exit, model, modelTaskHints, premium, push, replayConversation, root, runAdWall, runBusy, runBuy, runLogin, runTurn, setPremium, writeToStdout]
|
|
3469
3599
|
);
|
|
3470
3600
|
const runShell = useCallback(
|
|
3471
3601
|
async (command2) => {
|
|
@@ -3492,9 +3622,7 @@ ${preferencesText}` : ""),
|
|
|
3492
3622
|
if (output) {
|
|
3493
3623
|
setMessages((prev) => [
|
|
3494
3624
|
...prev,
|
|
3495
|
-
{ role: "user", content:
|
|
3496
|
-
|
|
3497
|
-
${output}` }
|
|
3625
|
+
{ role: "user", content: shellNote(command2, output) }
|
|
3498
3626
|
]);
|
|
3499
3627
|
}
|
|
3500
3628
|
},
|
|
@@ -3703,6 +3831,22 @@ ${output}` }
|
|
|
3703
3831
|
}
|
|
3704
3832
|
if (picker) {
|
|
3705
3833
|
const n = picker.items.length;
|
|
3834
|
+
if (picker.onDelete && (ch === "d" || ch === "D") && !key.ctrl && !key.meta) {
|
|
3835
|
+
const item = picker.items[Math.min(pickerSel, n - 1)];
|
|
3836
|
+
if (!item) return;
|
|
3837
|
+
setPickerNote(null);
|
|
3838
|
+
if (pickerArmed !== item.value) return setPickerArmed(item.value);
|
|
3839
|
+
setPickerArmed(null);
|
|
3840
|
+
const refused = picker.onDelete(item);
|
|
3841
|
+
if (refused) return setPickerNote(refused);
|
|
3842
|
+
const rest = picker.items.filter((i) => i !== item);
|
|
3843
|
+
if (!rest.length) return setPicker(null);
|
|
3844
|
+
setPicker({ ...picker, items: rest });
|
|
3845
|
+
setPickerSel((sel2) => Math.min(sel2, rest.length - 1));
|
|
3846
|
+
return;
|
|
3847
|
+
}
|
|
3848
|
+
if (pickerArmed !== null) setPickerArmed(null);
|
|
3849
|
+
if (pickerNote !== null) setPickerNote(null);
|
|
3706
3850
|
if (key.escape) {
|
|
3707
3851
|
const cancel = picker.onCancel;
|
|
3708
3852
|
setPicker(null);
|
|
@@ -3917,16 +4061,17 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
3917
4061
|
};
|
|
3918
4062
|
});
|
|
3919
4063
|
const pickerTitle = picker ? fitRow(picker.title, inner) : "";
|
|
3920
|
-
const pickerSubtitle = picker ? fitRow(picker.subtitle, inner) : "";
|
|
4064
|
+
const pickerSubtitle = picker ? fitRow(pickerArmed !== null ? PICKER_DELETE_CONFIRM : pickerNote ?? picker.subtitle, inner) : "";
|
|
4065
|
+
const pickerDelete = picker?.onDelete ? " \xB7 d delete" : "";
|
|
3921
4066
|
const pickerHint = picker ? pickHint(
|
|
3922
4067
|
picker.items.length > pickerItems.length ? [
|
|
3923
|
-
`\u2191\u2193 choose (${pickerFrom + 1}-${pickerFrom + pickerItems.length} of ${picker.items.length}) \xB7 1-${pickerItems.length} jump \xB7 \u23CE confirm \xB7 esc cancel`,
|
|
3924
|
-
`\u2191\u2193 ${pickerFrom + 1}-${pickerFrom + pickerItems.length}/${picker.items.length} \xB7 \u23CE confirm \xB7 esc cancel`,
|
|
3925
|
-
"\u2191\u2193 \xB7 \u23CE \xB7 esc"
|
|
4068
|
+
`\u2191\u2193 choose (${pickerFrom + 1}-${pickerFrom + pickerItems.length} of ${picker.items.length}) \xB7 1-${pickerItems.length} jump \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
|
|
4069
|
+
`\u2191\u2193 ${pickerFrom + 1}-${pickerFrom + pickerItems.length}/${picker.items.length} \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
|
|
4070
|
+
picker.onDelete ? "\u2191\u2193 \xB7 \u23CE \xB7 d \xB7 esc" : "\u2191\u2193 \xB7 \u23CE \xB7 esc"
|
|
3926
4071
|
] : [
|
|
3927
|
-
`\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump straight to a row \xB7 \u23CE confirm \xB7 esc cancel`,
|
|
3928
|
-
`\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump \xB7 \u23CE confirm \xB7 esc cancel`,
|
|
3929
|
-
"\u2191\u2193 \xB7 \u23CE \xB7 esc"
|
|
4072
|
+
`\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump straight to a row \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
|
|
4073
|
+
`\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
|
|
4074
|
+
picker.onDelete ? "\u2191\u2193 \xB7 \u23CE \xB7 d \xB7 esc" : "\u2191\u2193 \xB7 \u23CE \xB7 esc"
|
|
3930
4075
|
],
|
|
3931
4076
|
inner
|
|
3932
4077
|
) : "";
|