clixad 0.0.1-beta.44 → 0.0.1-beta.46
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.
|
@@ -30,22 +30,53 @@ function parseDirectLines(body) {
|
|
|
30
30
|
seen.add(e.format);
|
|
31
31
|
continue;
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
let clickUrl;
|
|
37
|
-
try {
|
|
38
|
-
const url = new URL(e.click_url);
|
|
39
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") continue;
|
|
40
|
-
clickUrl = url.toString();
|
|
41
|
-
} catch {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
out.push({ format: e.format, text, serveToken: e.serve_token, clickUrl });
|
|
33
|
+
const line = readServedLine(e);
|
|
34
|
+
if (!line) continue;
|
|
35
|
+
out.push(line);
|
|
45
36
|
seen.add(e.format);
|
|
46
37
|
}
|
|
47
38
|
return out;
|
|
48
39
|
}
|
|
40
|
+
function parseFallbackLines(body) {
|
|
41
|
+
const raw = body?.fallbacks;
|
|
42
|
+
if (!Array.isArray(raw)) return [];
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const item of raw) {
|
|
45
|
+
if (!item || typeof item !== "object") continue;
|
|
46
|
+
const line = readServedLine(item);
|
|
47
|
+
if (!line || out.some((o) => o.format === line.format)) continue;
|
|
48
|
+
out.push(line);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function readServedLine(e) {
|
|
53
|
+
if (!isAdFormat(e.format)) return null;
|
|
54
|
+
if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") return null;
|
|
55
|
+
const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
|
|
56
|
+
if (!text || !e.serve_token || e.serve_token.length > 2e3) return null;
|
|
57
|
+
let clickUrl;
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(e.click_url);
|
|
60
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
61
|
+
clickUrl = url.toString();
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return { format: e.format, text, serveToken: e.serve_token, clickUrl };
|
|
66
|
+
}
|
|
67
|
+
var MAX_LINK_TEXT = 40;
|
|
68
|
+
function shortLinkText(clickUrl) {
|
|
69
|
+
let url;
|
|
70
|
+
try {
|
|
71
|
+
url = new URL(clickUrl);
|
|
72
|
+
} catch {
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
if (url.protocol !== "https:" || url.search || url.hash || url.username || url.password) return void 0;
|
|
76
|
+
const text = `${url.host}${url.pathname === "/" ? "" : url.pathname}`;
|
|
77
|
+
if (text.length > MAX_LINK_TEXT || !/^[\x21-\x7e]+$/.test(text)) return void 0;
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
49
80
|
|
|
50
81
|
// src/edge.ts
|
|
51
82
|
function detectEdgeBlock(status, headers, body) {
|
|
@@ -607,16 +638,16 @@ async function httpFailure(res, what) {
|
|
|
607
638
|
return detail ? `${what} failed: ${res.status} \u2014 ${detail}` : `${what} failed: ${res.status}`;
|
|
608
639
|
}
|
|
609
640
|
function sleep(ms, signal) {
|
|
610
|
-
return new Promise((
|
|
641
|
+
return new Promise((resolve5) => {
|
|
611
642
|
if (!signal) {
|
|
612
|
-
setTimeout(
|
|
643
|
+
setTimeout(resolve5, ms);
|
|
613
644
|
return;
|
|
614
645
|
}
|
|
615
|
-
if (signal.aborted) return
|
|
646
|
+
if (signal.aborted) return resolve5();
|
|
616
647
|
const done = () => {
|
|
617
648
|
clearTimeout(timer);
|
|
618
649
|
signal.removeEventListener("abort", done);
|
|
619
|
-
|
|
650
|
+
resolve5();
|
|
620
651
|
};
|
|
621
652
|
const timer = setTimeout(done, ms);
|
|
622
653
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -1107,7 +1138,9 @@ var GatewayClient = class {
|
|
|
1107
1138
|
});
|
|
1108
1139
|
if (res.status === 404) return { supported: false, lines: [] };
|
|
1109
1140
|
if (!res.ok) return { supported: true, lines: [], failed: true };
|
|
1110
|
-
|
|
1141
|
+
const body = await res.json();
|
|
1142
|
+
const fallbacks = parseFallbackLines(body);
|
|
1143
|
+
return { supported: true, lines: parseDirectLines(body), ...fallbacks.length > 0 ? { fallbacks } : {} };
|
|
1111
1144
|
} catch {
|
|
1112
1145
|
return { supported: true, lines: [], failed: true };
|
|
1113
1146
|
}
|
|
@@ -1133,6 +1166,27 @@ var GatewayClient = class {
|
|
|
1133
1166
|
return 0;
|
|
1134
1167
|
}
|
|
1135
1168
|
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Report the fallback lines a turn drew, in one call, after the turn is
|
|
1171
|
+
* over. They are counted and earn nothing, because nobody pays for them being
|
|
1172
|
+
* shown, so this answers how many were counted. Never throws.
|
|
1173
|
+
*/
|
|
1174
|
+
async reportAffiliateImpressions(impressions, signal) {
|
|
1175
|
+
if (impressions.length === 0) return 0;
|
|
1176
|
+
try {
|
|
1177
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/affiliate/impression`, {
|
|
1178
|
+
method: "POST",
|
|
1179
|
+
headers: this.headers(),
|
|
1180
|
+
...signal ? { signal } : {},
|
|
1181
|
+
body: JSON.stringify({ impressions: impressions.map((i) => ({ format: i.format, serve_token: i.serveToken })) })
|
|
1182
|
+
});
|
|
1183
|
+
if (!res.ok) return 0;
|
|
1184
|
+
const body = await res.json();
|
|
1185
|
+
return typeof body.counted === "number" ? body.counted : 0;
|
|
1186
|
+
} catch {
|
|
1187
|
+
return 0;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1136
1190
|
/**
|
|
1137
1191
|
* Search the web, through the gateway.
|
|
1138
1192
|
*
|
|
@@ -1939,16 +1993,16 @@ async function devLogin(client, config, email, invite) {
|
|
|
1939
1993
|
};
|
|
1940
1994
|
}
|
|
1941
1995
|
function sleep2(ms, signal) {
|
|
1942
|
-
return new Promise((
|
|
1996
|
+
return new Promise((resolve5) => {
|
|
1943
1997
|
if (!signal) {
|
|
1944
|
-
setTimeout(
|
|
1998
|
+
setTimeout(resolve5, ms);
|
|
1945
1999
|
return;
|
|
1946
2000
|
}
|
|
1947
|
-
if (signal.aborted) return
|
|
2001
|
+
if (signal.aborted) return resolve5();
|
|
1948
2002
|
const done = () => {
|
|
1949
2003
|
clearTimeout(timer);
|
|
1950
2004
|
signal.removeEventListener("abort", done);
|
|
1951
|
-
|
|
2005
|
+
resolve5();
|
|
1952
2006
|
};
|
|
1953
2007
|
const timer = setTimeout(done, ms);
|
|
1954
2008
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -3084,7 +3138,7 @@ var StdioTransport = class {
|
|
|
3084
3138
|
if (this.exited) return Promise.reject(new McpError(this.exited));
|
|
3085
3139
|
const id = ++this.seq;
|
|
3086
3140
|
const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
3087
|
-
return new Promise((
|
|
3141
|
+
return new Promise((resolve5, reject) => {
|
|
3088
3142
|
const settle = (fn) => {
|
|
3089
3143
|
clearTimeout(timer);
|
|
3090
3144
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -3098,7 +3152,7 @@ var StdioTransport = class {
|
|
|
3098
3152
|
const onAbort = () => settle(() => reject(new McpError("cancelled")));
|
|
3099
3153
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3100
3154
|
this.pending.set(id, {
|
|
3101
|
-
resolve: (v) => settle(() =>
|
|
3155
|
+
resolve: (v) => settle(() => resolve5(v)),
|
|
3102
3156
|
reject: (e) => settle(() => reject(e))
|
|
3103
3157
|
});
|
|
3104
3158
|
this.child.stdin?.write(payload, (err) => {
|
|
@@ -5972,12 +6026,20 @@ import {
|
|
|
5972
6026
|
writeFileSync as writeFileSync3
|
|
5973
6027
|
} from "node:fs";
|
|
5974
6028
|
import { homedir as homedir5 } from "node:os";
|
|
5975
|
-
import { join as join5 } from "node:path";
|
|
6029
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
5976
6030
|
var ROOT = join5(homedir5(), ".clixad");
|
|
5977
6031
|
var SESSION_DIR = join5(ROOT, "sessions");
|
|
5978
6032
|
var HISTORY_FILE = join5(ROOT, "history");
|
|
5979
6033
|
var MAX_HISTORY = 200;
|
|
5980
|
-
var MAX_SESSIONS =
|
|
6034
|
+
var MAX_SESSIONS = 200;
|
|
6035
|
+
var SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
6036
|
+
function isSessionId(id) {
|
|
6037
|
+
return SESSION_ID.test(id);
|
|
6038
|
+
}
|
|
6039
|
+
function sameDirectory(a, b) {
|
|
6040
|
+
const norm = (p) => process.platform === "win32" ? resolve4(p).toLowerCase() : resolve4(p);
|
|
6041
|
+
return norm(a) === norm(b);
|
|
6042
|
+
}
|
|
5981
6043
|
function newSessionId() {
|
|
5982
6044
|
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "");
|
|
5983
6045
|
return `${now}-${Math.random().toString(16).slice(2, 6)}`;
|
|
@@ -5992,12 +6054,25 @@ function saveSession(session) {
|
|
|
5992
6054
|
}
|
|
5993
6055
|
}
|
|
5994
6056
|
function loadSession(id) {
|
|
6057
|
+
if (!isSessionId(id)) return void 0;
|
|
5995
6058
|
try {
|
|
5996
|
-
|
|
6059
|
+
const s = JSON.parse(readFileSync5(join5(SESSION_DIR, `${id}.json`), "utf8"));
|
|
6060
|
+
return s && Array.isArray(s.messages) && typeof s.id === "string" ? s : void 0;
|
|
5997
6061
|
} catch {
|
|
5998
6062
|
return void 0;
|
|
5999
6063
|
}
|
|
6000
6064
|
}
|
|
6065
|
+
function deleteSession(id) {
|
|
6066
|
+
if (!isSessionId(id)) return false;
|
|
6067
|
+
const path = join5(SESSION_DIR, `${id}.json`);
|
|
6068
|
+
try {
|
|
6069
|
+
if (!existsSync4(path)) return false;
|
|
6070
|
+
rmSync(path);
|
|
6071
|
+
return true;
|
|
6072
|
+
} catch {
|
|
6073
|
+
return false;
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6001
6076
|
function listSessions(cwd, limit = 20) {
|
|
6002
6077
|
if (!existsSync4(SESSION_DIR)) return [];
|
|
6003
6078
|
const metas = [];
|
|
@@ -6005,16 +6080,15 @@ function listSessions(cwd, limit = 20) {
|
|
|
6005
6080
|
if (!name.endsWith(".json")) continue;
|
|
6006
6081
|
const s = loadSession(name.slice(0, -5));
|
|
6007
6082
|
if (!s) continue;
|
|
6008
|
-
if (cwd && s.cwd
|
|
6009
|
-
const { messages
|
|
6010
|
-
metas.push(meta);
|
|
6083
|
+
if (cwd && !sameDirectory(s.cwd ?? "", cwd)) continue;
|
|
6084
|
+
const { messages, ...meta } = s;
|
|
6085
|
+
metas.push({ ...meta, title: meta.title ?? "", updated: meta.updated ?? "", messageCount: messages.length });
|
|
6011
6086
|
}
|
|
6012
6087
|
metas.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
6013
6088
|
return metas.slice(0, limit);
|
|
6014
6089
|
}
|
|
6015
6090
|
function hasSessionIn(cwd) {
|
|
6016
6091
|
if (!existsSync4(SESSION_DIR)) return false;
|
|
6017
|
-
const needle = `"cwd":${JSON.stringify(cwd)}`;
|
|
6018
6092
|
try {
|
|
6019
6093
|
for (const name of readdirSync3(SESSION_DIR)) {
|
|
6020
6094
|
if (!name.endsWith(".json")) continue;
|
|
@@ -6027,8 +6101,14 @@ function hasSessionIn(cwd) {
|
|
|
6027
6101
|
} finally {
|
|
6028
6102
|
closeSync2(fd);
|
|
6029
6103
|
}
|
|
6030
|
-
|
|
6031
|
-
|
|
6104
|
+
const field = /"cwd":("(?:[^"\\]|\\.)*")/.exec(head)?.[1];
|
|
6105
|
+
let stored;
|
|
6106
|
+
try {
|
|
6107
|
+
stored = field ? JSON.parse(field) : loadSession(name.slice(0, -5))?.cwd;
|
|
6108
|
+
} catch {
|
|
6109
|
+
stored = void 0;
|
|
6110
|
+
}
|
|
6111
|
+
if (stored && sameDirectory(stored, cwd)) return true;
|
|
6032
6112
|
}
|
|
6033
6113
|
} catch {
|
|
6034
6114
|
}
|
|
@@ -6073,6 +6153,7 @@ var CONTEXT_TOKENS = {
|
|
|
6073
6153
|
"claude-haiku-5.5": 24e3,
|
|
6074
6154
|
"deepseek-v4-flash": 1048576,
|
|
6075
6155
|
"gemini-3.5-flash-lite": 1048576,
|
|
6156
|
+
"glm-5.3-flash": 1048576,
|
|
6076
6157
|
"kimi-k2.5": 262144,
|
|
6077
6158
|
"gemini-3.8-flash": 1048576,
|
|
6078
6159
|
"kimi-k2.7-code": 262144,
|
|
@@ -6160,14 +6241,14 @@ function runKimiCode(config, task, opts = {}) {
|
|
|
6160
6241
|
OPENAI_BASE_URL: `${config.gatewayUrl.replace(/\/+$/, "")}/v1`,
|
|
6161
6242
|
OPENAI_API_KEY: config.token
|
|
6162
6243
|
};
|
|
6163
|
-
return new Promise((
|
|
6244
|
+
return new Promise((resolve5) => {
|
|
6164
6245
|
const child = process.platform === "win32" ? spawn5([bin, ...args].map(quoteWin).join(" "), { stdio: "inherit", env, shell: true }) : spawn5(bin, args, { stdio: "inherit", env });
|
|
6165
6246
|
child.on("error", (err) => {
|
|
6166
6247
|
if (err.code === "ENOENT") console.log("\n" + INSTALL_HELP);
|
|
6167
6248
|
else console.error(`failed to launch Kimi CLI: ${err.message}`);
|
|
6168
|
-
|
|
6249
|
+
resolve5(127);
|
|
6169
6250
|
});
|
|
6170
|
-
child.on("exit", (code) =>
|
|
6251
|
+
child.on("exit", (code) => resolve5(code ?? 0));
|
|
6171
6252
|
});
|
|
6172
6253
|
}
|
|
6173
6254
|
function quoteWin(s) {
|
|
@@ -6427,6 +6508,8 @@ function cutPoint(messages, keepTurns = 2) {
|
|
|
6427
6508
|
}
|
|
6428
6509
|
return 0;
|
|
6429
6510
|
}
|
|
6511
|
+
var SUMMARY_LEAD = "Summary of the earlier conversation:\n\n";
|
|
6512
|
+
var SUMMARY_ACK = "Understood \u2014 continuing from that summary.";
|
|
6430
6513
|
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.";
|
|
6431
6514
|
async function compact(client, model, messages, opts = {}) {
|
|
6432
6515
|
const cut2 = cutPoint(messages, opts.keepTurns ?? 2);
|
|
@@ -6442,10 +6525,8 @@ async function compact(client, model, messages, opts = {}) {
|
|
|
6442
6525
|
const summary = messageText(turn.message).trim() || "(no summary returned)";
|
|
6443
6526
|
const compacted = [
|
|
6444
6527
|
messages[0],
|
|
6445
|
-
{ role: "user", content: `
|
|
6446
|
-
|
|
6447
|
-
${summary}` },
|
|
6448
|
-
{ role: "assistant", content: "Understood \u2014 continuing from that summary." },
|
|
6528
|
+
{ role: "user", content: `${SUMMARY_LEAD}${summary}` },
|
|
6529
|
+
{ role: "assistant", content: SUMMARY_ACK },
|
|
6449
6530
|
...messages.slice(cut2)
|
|
6450
6531
|
];
|
|
6451
6532
|
return {
|
|
@@ -6546,6 +6627,7 @@ export {
|
|
|
6546
6627
|
AD_FORMATS,
|
|
6547
6628
|
EXTRA_AD_FORMATS,
|
|
6548
6629
|
DIRECT_ASK_TIMEOUT_MS,
|
|
6630
|
+
shortLinkText,
|
|
6549
6631
|
accessColumn,
|
|
6550
6632
|
premiumLocked,
|
|
6551
6633
|
premiumUnlockHint,
|
|
@@ -6561,6 +6643,7 @@ export {
|
|
|
6561
6643
|
referLines,
|
|
6562
6644
|
redeemMessage,
|
|
6563
6645
|
ledgerReasonLabel,
|
|
6646
|
+
messageText,
|
|
6564
6647
|
PaywallError,
|
|
6565
6648
|
PremiumLockedError,
|
|
6566
6649
|
LimitRefusalError,
|
|
@@ -6603,6 +6686,7 @@ export {
|
|
|
6603
6686
|
NO_SEARCH_INSTRUCTION,
|
|
6604
6687
|
runAgent,
|
|
6605
6688
|
interruptedConversation,
|
|
6689
|
+
INTERRUPTED_CALL,
|
|
6606
6690
|
collectContext,
|
|
6607
6691
|
INIT_PROMPT,
|
|
6608
6692
|
INIT_HINT,
|
|
@@ -6615,9 +6699,12 @@ export {
|
|
|
6615
6699
|
switchMode,
|
|
6616
6700
|
createPermit,
|
|
6617
6701
|
denyAll,
|
|
6702
|
+
MAX_SESSIONS,
|
|
6703
|
+
sameDirectory,
|
|
6618
6704
|
newSessionId,
|
|
6619
6705
|
saveSession,
|
|
6620
6706
|
loadSession,
|
|
6707
|
+
deleteSession,
|
|
6621
6708
|
listSessions,
|
|
6622
6709
|
hasSessionIn,
|
|
6623
6710
|
latestSession,
|
|
@@ -6642,6 +6729,8 @@ export {
|
|
|
6642
6729
|
estimateTokens,
|
|
6643
6730
|
conversationTokens,
|
|
6644
6731
|
shouldCompact,
|
|
6732
|
+
SUMMARY_LEAD,
|
|
6733
|
+
SUMMARY_ACK,
|
|
6645
6734
|
compact,
|
|
6646
6735
|
VERSION,
|
|
6647
6736
|
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-5GX5GUR4.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-2AEK7HJV.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,
|
|
@@ -52,6 +57,7 @@ import {
|
|
|
52
57
|
loadHistory,
|
|
53
58
|
loadMcpConfig,
|
|
54
59
|
loadSession,
|
|
60
|
+
messageText,
|
|
55
61
|
modeInstruction,
|
|
56
62
|
newSessionId,
|
|
57
63
|
nextMode,
|
|
@@ -83,6 +89,7 @@ import {
|
|
|
83
89
|
savedEffort,
|
|
84
90
|
shells,
|
|
85
91
|
shortCredits,
|
|
92
|
+
shortLinkText,
|
|
86
93
|
shortfallHeadline,
|
|
87
94
|
shouldCompact,
|
|
88
95
|
shouldSuggestInit,
|
|
@@ -98,7 +105,7 @@ import {
|
|
|
98
105
|
visibleLength,
|
|
99
106
|
webSearchCostLine,
|
|
100
107
|
welcomeBanner
|
|
101
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-5GX5GUR4.mjs";
|
|
102
109
|
|
|
103
110
|
// src/tui/index.tsx
|
|
104
111
|
import "react";
|
|
@@ -111,6 +118,7 @@ import { Box as Box2, Static, Text as Text2, useApp, useInput, usePaste, useStdo
|
|
|
111
118
|
// src/mentions.ts
|
|
112
119
|
var TRAILING = /[),;:]+$/;
|
|
113
120
|
var MAX_MENTION_CHARS = 12e4;
|
|
121
|
+
var MENTIONS_HEADING = "\n\n# Files the user referenced\n";
|
|
114
122
|
function findMentions(text) {
|
|
115
123
|
const out = [];
|
|
116
124
|
for (const match of text.matchAll(/(?:^|\s)@([^\s]+)/g)) {
|
|
@@ -145,10 +153,7 @@ ${clipped}`);
|
|
|
145
153
|
}
|
|
146
154
|
if (blocks.length === 0) return { text, attached: [], images: [] };
|
|
147
155
|
return {
|
|
148
|
-
text: `${text}
|
|
149
|
-
|
|
150
|
-
# Files the user referenced
|
|
151
|
-
Their contents are below, already read for you \u2014 do not call read_file on them again.
|
|
156
|
+
text: `${text}${MENTIONS_HEADING}Their contents are below, already read for you \u2014 do not call read_file on them again.
|
|
152
157
|
|
|
153
158
|
` + blocks.join("\n\n"),
|
|
154
159
|
attached,
|
|
@@ -272,21 +277,28 @@ var SIDEBAR_MIN_COLS = 50 + SIDEBAR_CARD_COLS;
|
|
|
272
277
|
function adFitsFormat(format, cols) {
|
|
273
278
|
return format === "sidebar_card" ? cols >= SIDEBAR_MIN_COLS : adFitsWidth(cols);
|
|
274
279
|
}
|
|
275
|
-
|
|
280
|
+
var LINK_SEPARATOR = " \xB7 ";
|
|
281
|
+
function sidebarCard(line2, cols, linkText) {
|
|
276
282
|
if (!adFitsFormat("sidebar_card", cols)) return null;
|
|
277
283
|
const text = line2.trim();
|
|
278
284
|
if (!text) return null;
|
|
279
285
|
const body = visibleLength(text) <= SIDEBAR_BODY_COLS ? text : `${sliceColumns(text, SIDEBAR_BODY_COLS - 1).trimEnd()}\u2026`;
|
|
280
|
-
|
|
286
|
+
const label = `${SPONSOR_SIGIL} ${AD_LABEL}`;
|
|
287
|
+
const link = linkText && visibleLength(label) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText) <= SIDEBAR_BODY_COLS ? linkText : null;
|
|
288
|
+
return { rule: SIDEBAR_RULE, label, link, body };
|
|
281
289
|
}
|
|
282
|
-
function sponsorRow(line2, cols, origin) {
|
|
290
|
+
function sponsorRow(line2, cols, origin, linkText) {
|
|
283
291
|
const marker = origin === "house" || cols >= AD_FULL_PREFIX_MIN_COLS ? HOUSE_MARKER_ROW : AD_MARKER_ROW;
|
|
284
292
|
const label = origin === "network" ? LABEL_ROW : null;
|
|
285
293
|
if (origin === "network" && !adFitsWidth(cols)) return null;
|
|
286
294
|
const room = cols - visibleLength(marker) - (label ? visibleLength(label) : 0);
|
|
287
295
|
if (room < MIN_BODY_COLS) return null;
|
|
296
|
+
if (origin === "network" && linkText) {
|
|
297
|
+
const whole = visibleLength(line2) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText);
|
|
298
|
+
if (whole <= room) return { marker, label, body: line2, link: linkText, origin };
|
|
299
|
+
}
|
|
288
300
|
const body = visibleLength(line2) <= room ? line2 : `${sliceColumns(line2, room - 1).trimEnd()}\u2026`;
|
|
289
|
-
return { marker, label, body, origin };
|
|
301
|
+
return { marker, label, body, link: null, origin };
|
|
290
302
|
}
|
|
291
303
|
function adColorEnabled({ isTTY, env }) {
|
|
292
304
|
if (!colorEnabled({ isTTY, env })) return false;
|
|
@@ -421,8 +433,8 @@ var COMMANDS = [
|
|
|
421
433
|
{ name: "clear", desc: "clear the screen and start a new session" },
|
|
422
434
|
// Deliberately *no* `args` on /resume, for /login's reason: an arg hint makes
|
|
423
435
|
// enter complete the command instead of running it, and the picker is the
|
|
424
|
-
// point. `/resume <id
|
|
425
|
-
{ name: "resume", desc: "pick up an earlier session
|
|
436
|
+
// point. `/resume <id>`, `/resume all` and `/resume delete <id>` work typed in full.
|
|
437
|
+
{ name: "resume", desc: "pick up or delete an earlier session (all: every directory)" },
|
|
426
438
|
{ name: "init", desc: "write a CLIXAD.md for this project" },
|
|
427
439
|
// Deliberately *no* `args` on either, for /login's reason: an arg hint makes
|
|
428
440
|
// enter complete the command instead of running it, and listing is what both
|
|
@@ -1462,9 +1474,9 @@ function presentResult(input, width, tint = diffTint()) {
|
|
|
1462
1474
|
const [notes = "", ...rest] = result.split("\n\n");
|
|
1463
1475
|
const out = nonEmpty(rest.join("\n\n")).filter((l) => l !== "(nothing new since the last read)");
|
|
1464
1476
|
const { body, more } = rows(out);
|
|
1465
|
-
const
|
|
1477
|
+
const failed2 = /\] (exited with code (?!0\b)\d+|failed|killed)/.test(notes);
|
|
1466
1478
|
return {
|
|
1467
|
-
status:
|
|
1479
|
+
status: failed2 ? "error" : "ok",
|
|
1468
1480
|
line: fit(nonEmpty(notes)[0] ?? "read"),
|
|
1469
1481
|
body,
|
|
1470
1482
|
more,
|
|
@@ -1604,6 +1616,69 @@ function askWords(req) {
|
|
|
1604
1616
|
}
|
|
1605
1617
|
}
|
|
1606
1618
|
|
|
1619
|
+
// src/tui/replay.ts
|
|
1620
|
+
function shellNote(command2, output) {
|
|
1621
|
+
return `I ran \`${command2}\` in the workspace myself. Its output:
|
|
1622
|
+
|
|
1623
|
+
${output}`;
|
|
1624
|
+
}
|
|
1625
|
+
var SHELL_NOTE = /^I ran `([^\n]*)` in the workspace myself\. Its output:\n\n([\s\S]*)$/;
|
|
1626
|
+
var IMAGE_NOTE = /^Here (?:is the image|are the images) you read \(/;
|
|
1627
|
+
function failed(result) {
|
|
1628
|
+
return result.startsWith("error: ") || result === INTERRUPTED_CALL;
|
|
1629
|
+
}
|
|
1630
|
+
function parseArgs(raw) {
|
|
1631
|
+
if (!raw) return {};
|
|
1632
|
+
try {
|
|
1633
|
+
const parsed = JSON.parse(raw);
|
|
1634
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1635
|
+
} catch {
|
|
1636
|
+
return {};
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function replayItems(messages) {
|
|
1640
|
+
const answers = /* @__PURE__ */ new Map();
|
|
1641
|
+
for (const m of messages) {
|
|
1642
|
+
if (m.role === "tool" && m.tool_call_id) answers.set(m.tool_call_id, messageText(m));
|
|
1643
|
+
}
|
|
1644
|
+
const out = [];
|
|
1645
|
+
for (const m of messages) {
|
|
1646
|
+
if (m.role === "user") {
|
|
1647
|
+
const text = messageText(m);
|
|
1648
|
+
if (text.startsWith(SUMMARY_LEAD)) {
|
|
1649
|
+
out.push({ kind: "notice", text: "earlier messages were summarised to save context; the summary is what the model has" });
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
if (IMAGE_NOTE.test(text)) continue;
|
|
1653
|
+
const shell = SHELL_NOTE.exec(text);
|
|
1654
|
+
if (shell) {
|
|
1655
|
+
out.push({ kind: "user", text: `!${shell[1]}` });
|
|
1656
|
+
out.push({ kind: "tool", name: "!", args: { command: shell[1] }, result: shell[2] ?? "", ok: true });
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
const cut = text.indexOf(MENTIONS_HEADING);
|
|
1660
|
+
const typed = (cut >= 0 ? text.slice(0, cut) : text).trim();
|
|
1661
|
+
if (typed) out.push({ kind: "user", text: typed });
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
if (m.role === "assistant") {
|
|
1665
|
+
const text = messageText(m).trim();
|
|
1666
|
+
if (text && text !== SUMMARY_ACK) out.push({ kind: "assistant", text });
|
|
1667
|
+
for (const call of m.tool_calls ?? []) {
|
|
1668
|
+
const result = answers.get(call.id) ?? INTERRUPTED_CALL;
|
|
1669
|
+
out.push({
|
|
1670
|
+
kind: "tool",
|
|
1671
|
+
name: call.function.name,
|
|
1672
|
+
args: parseArgs(call.function.arguments),
|
|
1673
|
+
result,
|
|
1674
|
+
ok: !failed(result)
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return out;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1607
1682
|
// src/tui/app.tsx
|
|
1608
1683
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1609
1684
|
var WORKING = "working\u2026";
|
|
@@ -1715,6 +1790,7 @@ var INIT_HINT_AFTER_TURNS = 3;
|
|
|
1715
1790
|
var freshStream = () => ({ text: "", md: {}, committed: false, streamed: false });
|
|
1716
1791
|
var EARN_ROW = "\0earn";
|
|
1717
1792
|
var BUY_ROW = "\0buy";
|
|
1793
|
+
var PICKER_DELETE_CONFIRM = "Press d again to delete it for good \xB7 any other key keeps it";
|
|
1718
1794
|
function App({
|
|
1719
1795
|
client,
|
|
1720
1796
|
config,
|
|
@@ -1805,17 +1881,15 @@ function App({
|
|
|
1805
1881
|
const [plan, setPlan] = useState(null);
|
|
1806
1882
|
const [picker, setPicker] = useState(null);
|
|
1807
1883
|
const [pickerSel, setPickerSel] = useState(0);
|
|
1884
|
+
const [pickerArmed, setPickerArmed] = useState(null);
|
|
1885
|
+
const [pickerNote, setPickerNote] = useState(null);
|
|
1808
1886
|
const [buy, setBuy] = useState(null);
|
|
1809
1887
|
const [tick, setTick] = useState(0);
|
|
1810
1888
|
const [startedAt, setStartedAt] = useState(0);
|
|
1811
1889
|
const [quitHint, setQuitHint] = useState(false);
|
|
1812
1890
|
const [busyLabel, setBusyLabel] = useState(WORKING);
|
|
1813
|
-
const [sponsor, setSponsor] = useState(
|
|
1814
|
-
|
|
1815
|
-
);
|
|
1816
|
-
const [adSlots, setAdSlots] = useState(
|
|
1817
|
-
{}
|
|
1818
|
-
);
|
|
1891
|
+
const [sponsor, setSponsor] = useState(null);
|
|
1892
|
+
const [adSlots, setAdSlots] = useState({});
|
|
1819
1893
|
const messagesRef = useRef(messages);
|
|
1820
1894
|
messagesRef.current = messages;
|
|
1821
1895
|
const busyRef = useRef(false);
|
|
@@ -1940,6 +2014,7 @@ function App({
|
|
|
1940
2014
|
const sponsorCallRef = useRef(0);
|
|
1941
2015
|
const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
|
|
1942
2016
|
const pendingDirectRef = useRef(/* @__PURE__ */ new Map());
|
|
2017
|
+
const pendingFallbackRef = useRef(/* @__PURE__ */ new Map());
|
|
1943
2018
|
const directSupportedRef = useRef(true);
|
|
1944
2019
|
const knownBookedRef = useRef(/* @__PURE__ */ new Set());
|
|
1945
2020
|
const drawnRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -2113,9 +2188,6 @@ function App({
|
|
|
2113
2188
|
if (freeLine) {
|
|
2114
2189
|
push({ kind: "notice", tone: (wallet?.balance ?? 0) <= 0 ? "warn" : "good", text: ` ${freeLine}` });
|
|
2115
2190
|
}
|
|
2116
|
-
if (session?.messages.length) {
|
|
2117
|
-
push({ kind: "notice", text: ` resumed session ${session.id} (${session.messages.length} messages)` });
|
|
2118
|
-
}
|
|
2119
2191
|
if (initHint?.returning) {
|
|
2120
2192
|
initHintedRef.current = true;
|
|
2121
2193
|
push({ kind: "notice", text: ` ${INIT_HINT}` });
|
|
@@ -2160,14 +2232,14 @@ function App({
|
|
|
2160
2232
|
if (cancelled) return void hub.close();
|
|
2161
2233
|
mcpRef.current = hub;
|
|
2162
2234
|
const ready = hub.status().filter((s) => s.state === "ready");
|
|
2163
|
-
const
|
|
2235
|
+
const failed2 = hub.status().filter((s) => s.state === "failed");
|
|
2164
2236
|
if (ready.length) {
|
|
2165
2237
|
push({
|
|
2166
2238
|
kind: "notice",
|
|
2167
2239
|
text: ` mcp: ${ready.map((s) => `${s.name} (${s.toolCount} tools)`).join(", ")}`
|
|
2168
2240
|
});
|
|
2169
2241
|
}
|
|
2170
|
-
for (const server of
|
|
2242
|
+
for (const server of failed2) {
|
|
2171
2243
|
push({ kind: "notice", tone: "warn", text: ` mcp: ${server.name} did not start \u2014 ${server.error}` });
|
|
2172
2244
|
}
|
|
2173
2245
|
});
|
|
@@ -2210,9 +2282,13 @@ function App({
|
|
|
2210
2282
|
} else {
|
|
2211
2283
|
setSponsor(null);
|
|
2212
2284
|
}
|
|
2213
|
-
const fetchNetwork = (format) => {
|
|
2285
|
+
const fetchNetwork = (format, fallback) => {
|
|
2214
2286
|
void remoteSponsorRef.current[format].fetch().then((line2) => {
|
|
2215
|
-
if (
|
|
2287
|
+
if (call !== sponsorCallRef.current) return;
|
|
2288
|
+
if (!line2) {
|
|
2289
|
+
if (fallback) paintFallback(fallback);
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2216
2292
|
if (!adFitsFormat(format, colsRef.current)) return;
|
|
2217
2293
|
if (format !== "top_bar" && modalOpenRef.current) return;
|
|
2218
2294
|
if (format === "top_bar") {
|
|
@@ -2244,10 +2320,28 @@ function App({
|
|
|
2244
2320
|
}
|
|
2245
2321
|
pendingDirectRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
|
|
2246
2322
|
};
|
|
2323
|
+
const paintFallback = (line2) => {
|
|
2324
|
+
const { format } = line2;
|
|
2325
|
+
if (call !== sponsorCallRef.current) return;
|
|
2326
|
+
if (!adFitsFormat(format, colsRef.current)) return;
|
|
2327
|
+
if (format !== "top_bar" && modalOpenRef.current) return;
|
|
2328
|
+
const drawnId = `fallback:${call}:${format}`;
|
|
2329
|
+
const link = line2.clickUrl;
|
|
2330
|
+
const linkText = shortLinkText(link);
|
|
2331
|
+
const extra = linkText ? { link, linkText } : { link };
|
|
2332
|
+
if (format === "top_bar") {
|
|
2333
|
+
if (!sponsorRow(line2.text, colsRef.current, "network", linkText)) return;
|
|
2334
|
+
setSponsor({ text: line2.text, origin: "network", impressionId: drawnId, ...extra });
|
|
2335
|
+
} else {
|
|
2336
|
+
setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, ...extra } }));
|
|
2337
|
+
}
|
|
2338
|
+
pendingFallbackRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
|
|
2339
|
+
};
|
|
2247
2340
|
const fitting = AD_FORMATS.filter((format) => adFitsFormat(format, colsRef.current));
|
|
2248
2341
|
void (async () => {
|
|
2249
2342
|
let direct = [];
|
|
2250
|
-
let
|
|
2343
|
+
let fallbacks = [];
|
|
2344
|
+
let failed2 = false;
|
|
2251
2345
|
if (directSupportedRef.current && fitting.length > 0) {
|
|
2252
2346
|
const ask2 = sponsorDirect ?? ((formats, cols2, signal) => client.directSponsor(formats, cols2, signal));
|
|
2253
2347
|
let answer = { supported: true, lines: [] };
|
|
@@ -2257,9 +2351,10 @@ function App({
|
|
|
2257
2351
|
answer = { supported: true, lines: [], failed: true };
|
|
2258
2352
|
}
|
|
2259
2353
|
if (!answer.supported) directSupportedRef.current = false;
|
|
2260
|
-
|
|
2354
|
+
failed2 = answer.failed === true;
|
|
2261
2355
|
direct = answer.lines;
|
|
2262
|
-
|
|
2356
|
+
fallbacks = answer.fallbacks ?? [];
|
|
2357
|
+
if (!failed2) knownBookedRef.current = new Set(direct.map((line2) => line2.format));
|
|
2263
2358
|
}
|
|
2264
2359
|
if (call !== sponsorCallRef.current) return;
|
|
2265
2360
|
for (const format of ["top_bar", ...EXTRA_AD_FORMATS]) {
|
|
@@ -2267,8 +2362,8 @@ function App({
|
|
|
2267
2362
|
const booked = direct.find((line2) => line2.format === format);
|
|
2268
2363
|
if (booked && "hold" in booked) continue;
|
|
2269
2364
|
if (booked) paintDirect(booked);
|
|
2270
|
-
else if (
|
|
2271
|
-
else fetchNetwork(format);
|
|
2365
|
+
else if (failed2 && knownBookedRef.current.has(format)) continue;
|
|
2366
|
+
else fetchNetwork(format, fallbacks.find((line2) => line2.format === format));
|
|
2272
2367
|
}
|
|
2273
2368
|
})();
|
|
2274
2369
|
},
|
|
@@ -2277,12 +2372,16 @@ function App({
|
|
|
2277
2372
|
const reportSponsor = useCallback(() => {
|
|
2278
2373
|
const pending = [...pendingImpressionRef.current.values()];
|
|
2279
2374
|
const pendingDirect = [...pendingDirectRef.current.entries()];
|
|
2375
|
+
const pendingFallback = [...pendingFallbackRef.current.entries()];
|
|
2280
2376
|
const drawn = drawnRef.current;
|
|
2281
2377
|
pendingImpressionRef.current = /* @__PURE__ */ new Map();
|
|
2282
2378
|
pendingDirectRef.current = /* @__PURE__ */ new Map();
|
|
2379
|
+
pendingFallbackRef.current = /* @__PURE__ */ new Map();
|
|
2283
2380
|
drawnRef.current = /* @__PURE__ */ new Set();
|
|
2284
2381
|
const directReports = pendingDirect.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
|
|
2285
2382
|
if (directReports.length > 0) void client.reportDirectImpressions(directReports);
|
|
2383
|
+
const fallbackReports = pendingFallback.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
|
|
2384
|
+
if (fallbackReports.length > 0) void client.reportAffiliateImpressions(fallbackReports);
|
|
2286
2385
|
const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
|
|
2287
2386
|
if (reports.length === 0) return;
|
|
2288
2387
|
void (async () => {
|
|
@@ -2364,6 +2463,26 @@ function App({
|
|
|
2364
2463
|
},
|
|
2365
2464
|
[cols, keepOutput, push]
|
|
2366
2465
|
);
|
|
2466
|
+
const replayConversation = useCallback(
|
|
2467
|
+
(stored) => {
|
|
2468
|
+
const title = stored.title ? ` \xB7 ${stored.title}` : "";
|
|
2469
|
+
push({
|
|
2470
|
+
kind: "notice",
|
|
2471
|
+
tone: "good",
|
|
2472
|
+
text: ` resumed session ${stored.id} (${stored.messages.length} messages)${title}`
|
|
2473
|
+
});
|
|
2474
|
+
for (const item of replayItems(stored.messages)) {
|
|
2475
|
+
if (item.kind === "tool") commitTool(item.name, item.args, { result: item.result, ok: item.ok });
|
|
2476
|
+
else if (item.kind === "notice") push({ kind: "notice", text: ` ${item.text}` });
|
|
2477
|
+
else push(item);
|
|
2478
|
+
}
|
|
2479
|
+
push({ kind: "notice", text: " the agent has all of this conversation; carry on below" });
|
|
2480
|
+
},
|
|
2481
|
+
[commitTool, push]
|
|
2482
|
+
);
|
|
2483
|
+
useEffect(() => {
|
|
2484
|
+
if (session?.messages.length) replayConversation(session);
|
|
2485
|
+
}, []);
|
|
2367
2486
|
const handleEvent = useCallback(
|
|
2368
2487
|
(event) => {
|
|
2369
2488
|
if (event.type === "delta") {
|
|
@@ -3349,11 +3468,23 @@ ${preferencesText}` : ""),
|
|
|
3349
3468
|
// earlier conversation was to quit the one you were in.
|
|
3350
3469
|
case "resume":
|
|
3351
3470
|
case "sessions": {
|
|
3352
|
-
const
|
|
3353
|
-
if (
|
|
3354
|
-
|
|
3471
|
+
const [verb, target] = arg.split(/\s+/);
|
|
3472
|
+
if (verb === "delete" || verb === "rm") {
|
|
3473
|
+
if (!target) {
|
|
3474
|
+
push({ kind: "notice", tone: "warn", text: " /resume delete <id>, or press d twice on a row in /resume" });
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
if (target === sessionRef.current.id) {
|
|
3478
|
+
push({ kind: "notice", tone: "warn", text: ` ${target} is the conversation you are in. /clear starts a new one first.` });
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
push(
|
|
3482
|
+
deleteSession(target) ? { kind: "notice", tone: "good", text: ` deleted session ${target}` } : { kind: "notice", tone: "error", text: ` no such session: ${target}` }
|
|
3483
|
+
);
|
|
3355
3484
|
return;
|
|
3356
3485
|
}
|
|
3486
|
+
const everywhere = verb === "all";
|
|
3487
|
+
const metas = listSessions(everywhere ? void 0 : root, MAX_SESSIONS);
|
|
3357
3488
|
const load = (id) => {
|
|
3358
3489
|
const found = loadSession(id);
|
|
3359
3490
|
if (!found) {
|
|
@@ -3362,26 +3493,42 @@ ${preferencesText}` : ""),
|
|
|
3362
3493
|
}
|
|
3363
3494
|
setMessages(found.messages);
|
|
3364
3495
|
sessionRef.current = { id: found.id, started: found.started, title: found.title };
|
|
3496
|
+
todosRef.current = new TodoList();
|
|
3497
|
+
lastTurnRef.current = null;
|
|
3498
|
+
replayConversation(found);
|
|
3499
|
+
};
|
|
3500
|
+
if (arg && !everywhere) return load(arg);
|
|
3501
|
+
if (!metas.length) {
|
|
3502
|
+
const elsewhere = everywhere ? 0 : listSessions(void 0, MAX_SESSIONS).length;
|
|
3365
3503
|
push({
|
|
3366
3504
|
kind: "notice",
|
|
3367
|
-
tone: "
|
|
3368
|
-
text: `
|
|
3369
|
-
${found.title}`
|
|
3505
|
+
tone: "warn",
|
|
3506
|
+
text: elsewhere ? ` no saved sessions in this directory yet. ${elsewhere} in other directories: /resume all lists them.` : " no saved sessions yet"
|
|
3370
3507
|
});
|
|
3371
|
-
|
|
3372
|
-
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3373
3510
|
const items = metas.map((m) => ({
|
|
3374
3511
|
value: m.id,
|
|
3375
3512
|
label: m.updated.slice(0, 16).replace("T", " "),
|
|
3376
|
-
hint: m.title.slice(0, 60)
|
|
3513
|
+
hint: `${m.title.slice(0, 60)} \xB7 ${m.messageCount} messages${everywhere ? ` \xB7 ${m.cwd}` : ""}`,
|
|
3377
3514
|
current: m.id === sessionRef.current.id
|
|
3378
3515
|
}));
|
|
3379
3516
|
setPickerSel(0);
|
|
3517
|
+
setPickerArmed(null);
|
|
3518
|
+
setPickerNote(null);
|
|
3380
3519
|
setPicker({
|
|
3381
|
-
title: "Resume a session",
|
|
3382
|
-
subtitle: "Replaces the current conversation; nothing on screen is lost.",
|
|
3520
|
+
title: everywhere ? "Resume a session from any directory" : "Resume a session",
|
|
3521
|
+
subtitle: "Replaces the current conversation; nothing on screen is lost. d twice deletes one.",
|
|
3383
3522
|
items,
|
|
3384
|
-
onPick: (choice) => load(choice.value)
|
|
3523
|
+
onPick: (choice) => load(choice.value),
|
|
3524
|
+
onDelete: (choice) => {
|
|
3525
|
+
if (choice.value === sessionRef.current.id) {
|
|
3526
|
+
return "That is the conversation you are in. /clear starts a new one first.";
|
|
3527
|
+
}
|
|
3528
|
+
if (!deleteSession(choice.value)) return "It could not be deleted.";
|
|
3529
|
+
push({ kind: "notice", tone: "good", text: ` deleted session ${choice.value}` });
|
|
3530
|
+
return void 0;
|
|
3531
|
+
}
|
|
3385
3532
|
});
|
|
3386
3533
|
return;
|
|
3387
3534
|
}
|
|
@@ -3480,7 +3627,7 @@ ${preferencesText}` : ""),
|
|
|
3480
3627
|
push({ kind: "notice", tone: "warn", text: ` unknown command: /${cmd} \u2014 try /help` });
|
|
3481
3628
|
}
|
|
3482
3629
|
},
|
|
3483
|
-
[client, cols, config, cycleMode, exit, model, modelTaskHints, premium, push, root, runAdWall, runBusy, runBuy, runLogin, runTurn, setPremium, writeToStdout]
|
|
3630
|
+
[client, cols, config, cycleMode, exit, model, modelTaskHints, premium, push, replayConversation, root, runAdWall, runBusy, runBuy, runLogin, runTurn, setPremium, writeToStdout]
|
|
3484
3631
|
);
|
|
3485
3632
|
const runShell = useCallback(
|
|
3486
3633
|
async (command2) => {
|
|
@@ -3507,9 +3654,7 @@ ${preferencesText}` : ""),
|
|
|
3507
3654
|
if (output) {
|
|
3508
3655
|
setMessages((prev) => [
|
|
3509
3656
|
...prev,
|
|
3510
|
-
{ role: "user", content:
|
|
3511
|
-
|
|
3512
|
-
${output}` }
|
|
3657
|
+
{ role: "user", content: shellNote(command2, output) }
|
|
3513
3658
|
]);
|
|
3514
3659
|
}
|
|
3515
3660
|
},
|
|
@@ -3718,6 +3863,22 @@ ${output}` }
|
|
|
3718
3863
|
}
|
|
3719
3864
|
if (picker) {
|
|
3720
3865
|
const n = picker.items.length;
|
|
3866
|
+
if (picker.onDelete && (ch === "d" || ch === "D") && !key.ctrl && !key.meta) {
|
|
3867
|
+
const item = picker.items[Math.min(pickerSel, n - 1)];
|
|
3868
|
+
if (!item) return;
|
|
3869
|
+
setPickerNote(null);
|
|
3870
|
+
if (pickerArmed !== item.value) return setPickerArmed(item.value);
|
|
3871
|
+
setPickerArmed(null);
|
|
3872
|
+
const refused = picker.onDelete(item);
|
|
3873
|
+
if (refused) return setPickerNote(refused);
|
|
3874
|
+
const rest = picker.items.filter((i) => i !== item);
|
|
3875
|
+
if (!rest.length) return setPicker(null);
|
|
3876
|
+
setPicker({ ...picker, items: rest });
|
|
3877
|
+
setPickerSel((sel2) => Math.min(sel2, rest.length - 1));
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
if (pickerArmed !== null) setPickerArmed(null);
|
|
3881
|
+
if (pickerNote !== null) setPickerNote(null);
|
|
3721
3882
|
if (key.escape) {
|
|
3722
3883
|
const cancel = picker.onCancel;
|
|
3723
3884
|
setPicker(null);
|
|
@@ -3862,13 +4023,13 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
3862
4023
|
});
|
|
3863
4024
|
const elapsed = busy && startedAt ? Math.floor((Date.now() - startedAt) / 1e3) : 0;
|
|
3864
4025
|
const spinner = spinnerRef.current[Math.floor(tick / SPINNER_TICKS_PER_FRAME) % spinnerRef.current.length];
|
|
3865
|
-
const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin) : null;
|
|
4026
|
+
const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin, sponsor.linkText) : null;
|
|
3866
4027
|
const adColor = adColorEnabled({ isTTY: stdout.isTTY, env: process.env });
|
|
3867
4028
|
const linkable = hyperlinksEnabled({ isTTY: stdout.isTTY, env: process.env });
|
|
3868
4029
|
const modalOpen = Boolean(picker || ask || plan || buy);
|
|
3869
4030
|
modalOpenRef.current = modalOpen;
|
|
3870
|
-
const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols) : null;
|
|
3871
|
-
const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network") : null;
|
|
4031
|
+
const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols, adSlots.sidebar_card.linkText) : null;
|
|
4032
|
+
const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network", adSlots.status_bar.linkText) : null;
|
|
3872
4033
|
const viewport = Math.max(1, rows - 1);
|
|
3873
4034
|
const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
|
|
3874
4035
|
const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
|
|
@@ -3932,16 +4093,17 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
3932
4093
|
};
|
|
3933
4094
|
});
|
|
3934
4095
|
const pickerTitle = picker ? fitRow(picker.title, inner) : "";
|
|
3935
|
-
const pickerSubtitle = picker ? fitRow(picker.subtitle, inner) : "";
|
|
4096
|
+
const pickerSubtitle = picker ? fitRow(pickerArmed !== null ? PICKER_DELETE_CONFIRM : pickerNote ?? picker.subtitle, inner) : "";
|
|
4097
|
+
const pickerDelete = picker?.onDelete ? " \xB7 d delete" : "";
|
|
3936
4098
|
const pickerHint = picker ? pickHint(
|
|
3937
4099
|
picker.items.length > pickerItems.length ? [
|
|
3938
|
-
`\u2191\u2193 choose (${pickerFrom + 1}-${pickerFrom + pickerItems.length} of ${picker.items.length}) \xB7 1-${pickerItems.length} jump \xB7 \u23CE confirm \xB7 esc cancel`,
|
|
3939
|
-
`\u2191\u2193 ${pickerFrom + 1}-${pickerFrom + pickerItems.length}/${picker.items.length} \xB7 \u23CE confirm \xB7 esc cancel`,
|
|
3940
|
-
"\u2191\u2193 \xB7 \u23CE \xB7 esc"
|
|
4100
|
+
`\u2191\u2193 choose (${pickerFrom + 1}-${pickerFrom + pickerItems.length} of ${picker.items.length}) \xB7 1-${pickerItems.length} jump \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
|
|
4101
|
+
`\u2191\u2193 ${pickerFrom + 1}-${pickerFrom + pickerItems.length}/${picker.items.length} \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
|
|
4102
|
+
picker.onDelete ? "\u2191\u2193 \xB7 \u23CE \xB7 d \xB7 esc" : "\u2191\u2193 \xB7 \u23CE \xB7 esc"
|
|
3941
4103
|
] : [
|
|
3942
|
-
`\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump straight to a row \xB7 \u23CE confirm \xB7 esc cancel`,
|
|
3943
|
-
`\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump \xB7 \u23CE confirm \xB7 esc cancel`,
|
|
3944
|
-
"\u2191\u2193 \xB7 \u23CE \xB7 esc"
|
|
4104
|
+
`\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump straight to a row \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
|
|
4105
|
+
`\u2191\u2193 choose \xB7 1-${Math.min(9, pickerItems.length)} jump \xB7 \u23CE confirm${pickerDelete} \xB7 esc cancel`,
|
|
4106
|
+
picker.onDelete ? "\u2191\u2193 \xB7 \u23CE \xB7 d \xB7 esc" : "\u2191\u2193 \xB7 \u23CE \xB7 esc"
|
|
3945
4107
|
],
|
|
3946
4108
|
inner
|
|
3947
4109
|
) : "";
|
|
@@ -4146,7 +4308,11 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
4146
4308
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: SIDEBAR_CARD_COLS, children: [
|
|
4147
4309
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
4148
4310
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
|
|
4149
|
-
/* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label })
|
|
4311
|
+
/* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label }),
|
|
4312
|
+
card.link ? /* @__PURE__ */ jsxs2(Text2, { ...adColor ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
|
|
4313
|
+
LINK_SEPARATOR,
|
|
4314
|
+
/* @__PURE__ */ jsx2(Text2, { underline: adColor, children: linkable && adSlots.sidebar_card?.link ? osc8(card.link, adSlots.sidebar_card.link) : card.link })
|
|
4315
|
+
] }) : null
|
|
4150
4316
|
] }),
|
|
4151
4317
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
4152
4318
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
|
|
@@ -4235,7 +4401,13 @@ function SponsorRowView({ row, adColor, link }) {
|
|
|
4235
4401
|
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
4236
4402
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: row.marker }),
|
|
4237
4403
|
row.label ? /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: row.label }) : null,
|
|
4238
|
-
/* @__PURE__ */
|
|
4404
|
+
/* @__PURE__ */ jsxs2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
|
|
4405
|
+
link && row.origin === "network" ? osc8(row.body, link) : row.body,
|
|
4406
|
+
row.link ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
4407
|
+
LINK_SEPARATOR,
|
|
4408
|
+
/* @__PURE__ */ jsx2(Text2, { underline: adColor, children: link ? osc8(row.link, link) : row.link })
|
|
4409
|
+
] }) : null
|
|
4410
|
+
] })
|
|
4239
4411
|
] });
|
|
4240
4412
|
}
|
|
4241
4413
|
function sleep(ms, signal) {
|