kimiflare 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -10
- package/dist/index.js +1073 -149
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
package/dist/index.js
CHANGED
|
@@ -16,12 +16,25 @@ function configPath() {
|
|
|
16
16
|
const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
17
17
|
return join(xdg, "kimiflare", "config.json");
|
|
18
18
|
}
|
|
19
|
+
function readReasoningEffortEnv() {
|
|
20
|
+
const raw = process.env.KIMI_REASONING_EFFORT?.toLowerCase();
|
|
21
|
+
if (raw === "low" || raw === "medium" || raw === "high") return raw;
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
19
24
|
async function loadConfig() {
|
|
20
25
|
const envAccount = process.env.CLOUDFLARE_ACCOUNT_ID ?? process.env.CF_ACCOUNT_ID;
|
|
21
26
|
const envToken = process.env.CLOUDFLARE_API_TOKEN ?? process.env.CF_API_TOKEN;
|
|
22
27
|
const envModel = process.env.KIMI_MODEL ?? DEFAULT_MODEL;
|
|
28
|
+
const envEffort = readReasoningEffortEnv();
|
|
29
|
+
const envTheme = process.env.KIMI_THEME;
|
|
23
30
|
if (envAccount && envToken) {
|
|
24
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
accountId: envAccount,
|
|
33
|
+
apiToken: envToken,
|
|
34
|
+
model: envModel,
|
|
35
|
+
theme: envTheme,
|
|
36
|
+
reasoningEffort: envEffort
|
|
37
|
+
};
|
|
25
38
|
}
|
|
26
39
|
try {
|
|
27
40
|
const raw = await readFile(configPath(), "utf8");
|
|
@@ -30,7 +43,9 @@ async function loadConfig() {
|
|
|
30
43
|
return {
|
|
31
44
|
accountId: envAccount ?? parsed.accountId,
|
|
32
45
|
apiToken: envToken ?? parsed.apiToken,
|
|
33
|
-
model: envModel ?? parsed.model ?? DEFAULT_MODEL
|
|
46
|
+
model: envModel ?? parsed.model ?? DEFAULT_MODEL,
|
|
47
|
+
theme: envTheme ?? parsed.theme,
|
|
48
|
+
reasoningEffort: envEffort ?? parsed.reasoningEffort
|
|
34
49
|
};
|
|
35
50
|
}
|
|
36
51
|
} catch {
|
|
@@ -44,11 +59,12 @@ async function saveConfig(cfg) {
|
|
|
44
59
|
await chmod(p, 384);
|
|
45
60
|
return p;
|
|
46
61
|
}
|
|
47
|
-
var DEFAULT_MODEL;
|
|
62
|
+
var DEFAULT_MODEL, DEFAULT_REASONING_EFFORT;
|
|
48
63
|
var init_config = __esm({
|
|
49
64
|
"src/config.ts"() {
|
|
50
65
|
"use strict";
|
|
51
66
|
DEFAULT_MODEL = "@cf/moonshotai/kimi-k2.6";
|
|
67
|
+
DEFAULT_REASONING_EFFORT = "medium";
|
|
52
68
|
}
|
|
53
69
|
});
|
|
54
70
|
|
|
@@ -122,6 +138,9 @@ async function* runKimi(opts2) {
|
|
|
122
138
|
temperature: opts2.temperature ?? 0.2,
|
|
123
139
|
max_completion_tokens: opts2.maxCompletionTokens ?? 16384
|
|
124
140
|
};
|
|
141
|
+
if (opts2.reasoningEffort) {
|
|
142
|
+
body.reasoning_effort = opts2.reasoningEffort;
|
|
143
|
+
}
|
|
125
144
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
126
145
|
const res = await fetch(url, {
|
|
127
146
|
method: "POST",
|
|
@@ -289,7 +308,8 @@ async function runAgentTurn(opts2) {
|
|
|
289
308
|
tools: toolDefs,
|
|
290
309
|
signal: opts2.signal,
|
|
291
310
|
temperature: opts2.temperature,
|
|
292
|
-
maxCompletionTokens: opts2.maxCompletionTokens
|
|
311
|
+
maxCompletionTokens: opts2.maxCompletionTokens,
|
|
312
|
+
reasoningEffort: opts2.reasoningEffort
|
|
293
313
|
});
|
|
294
314
|
for await (const ev of events) {
|
|
295
315
|
switch (ev.type) {
|
|
@@ -338,7 +358,7 @@ async function runAgentTurn(opts2) {
|
|
|
338
358
|
const result = await opts2.executor.run(
|
|
339
359
|
{ id: tc.id, name: tc.function.name, arguments: tc.function.arguments },
|
|
340
360
|
opts2.callbacks.askPermission,
|
|
341
|
-
{ cwd: opts2.cwd, signal: opts2.signal }
|
|
361
|
+
{ cwd: opts2.cwd, signal: opts2.signal, onTasks: opts2.callbacks.onTasks }
|
|
342
362
|
);
|
|
343
363
|
opts2.messages.push({
|
|
344
364
|
role: "tool",
|
|
@@ -359,6 +379,32 @@ var init_loop = __esm({
|
|
|
359
379
|
}
|
|
360
380
|
});
|
|
361
381
|
|
|
382
|
+
// src/mode.ts
|
|
383
|
+
function nextMode(m) {
|
|
384
|
+
const i = MODES.indexOf(m);
|
|
385
|
+
return MODES[(i + 1) % MODES.length];
|
|
386
|
+
}
|
|
387
|
+
function isBlockedInPlanMode(toolName) {
|
|
388
|
+
return MUTATING_TOOLS.has(toolName);
|
|
389
|
+
}
|
|
390
|
+
function systemPromptForMode(m) {
|
|
391
|
+
if (m === "plan") {
|
|
392
|
+
return "\n\nPLAN MODE is active. The user wants you to investigate and produce a plan WITHOUT making any changes. Do not call write, edit, or bash. Only use read/glob/grep/web-fetch. At the end, present a concise plan (bullets, files to change, approach). The user will review and then exit plan mode to execute.";
|
|
393
|
+
}
|
|
394
|
+
if (m === "auto") {
|
|
395
|
+
return "\n\nAUTO MODE is active. The user has opted into autonomous execution \u2014 every tool call will be auto-approved. Work efficiently, but do not take irreversible destructive actions (rm -rf, git push --force, dropping tables, etc.) without pausing to describe them in chat first. Prefer smaller reversible steps.";
|
|
396
|
+
}
|
|
397
|
+
return "";
|
|
398
|
+
}
|
|
399
|
+
var MODES, MUTATING_TOOLS;
|
|
400
|
+
var init_mode = __esm({
|
|
401
|
+
"src/mode.ts"() {
|
|
402
|
+
"use strict";
|
|
403
|
+
MODES = ["edit", "plan", "auto"];
|
|
404
|
+
MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "bash"]);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
362
408
|
// src/agent/system-prompt.ts
|
|
363
409
|
import { platform, release, homedir as homedir2 } from "os";
|
|
364
410
|
import { basename } from "path";
|
|
@@ -386,15 +432,17 @@ How to work:
|
|
|
386
432
|
- Prefer calling tools over guessing. Read files before editing them. Use \`glob\` and \`grep\` to explore code before assuming structure.
|
|
387
433
|
- Before any mutating tool call (write, edit, bash), state in one short sentence what you're about to do, then call the tool. The user will be asked to approve each mutating call.
|
|
388
434
|
- When the user asks for a change, make the change. Do not paste code in chat that you could apply with \`edit\` or \`write\`.
|
|
435
|
+
- For multi-step work, call \`tasks_set\` at the start with a short task list (one task "in_progress", the rest "pending"), then call it again after each step completes (flip that one to "completed" and the next to "in_progress"). Skip it for trivial single-step requests.
|
|
389
436
|
- Keep responses terse. The user sees tool calls and their results inline \u2014 do not re-summarize them unless asked.
|
|
390
437
|
- If a tool returns an error, read it carefully and adjust; do not retry the same call blindly.
|
|
391
438
|
- You have a 262k-token context window. Read as much of a file as needed rather than guessing.
|
|
392
439
|
- If a request is ambiguous, ask one focused question instead of making large assumptions.
|
|
393
|
-
- When you finish a task, stop. Do not add a closing summary
|
|
440
|
+
- When you finish a task, stop. Do not add a closing summary.${opts2.mode ? systemPromptForMode(opts2.mode) : ""}`;
|
|
394
441
|
}
|
|
395
442
|
var init_system_prompt = __esm({
|
|
396
443
|
"src/agent/system-prompt.ts"() {
|
|
397
444
|
"use strict";
|
|
445
|
+
init_mode();
|
|
398
446
|
}
|
|
399
447
|
});
|
|
400
448
|
|
|
@@ -812,6 +860,88 @@ ${bounded}`, MAX_OUTPUT);
|
|
|
812
860
|
}
|
|
813
861
|
});
|
|
814
862
|
|
|
863
|
+
// src/tasks-state.ts
|
|
864
|
+
function isValidStatus(s) {
|
|
865
|
+
return s === "pending" || s === "in_progress" || s === "completed";
|
|
866
|
+
}
|
|
867
|
+
function validateTasks(input) {
|
|
868
|
+
if (!Array.isArray(input)) throw new Error("tasks must be an array");
|
|
869
|
+
return input.map((t, i) => {
|
|
870
|
+
if (!t || typeof t !== "object") throw new Error(`tasks[${i}] must be an object`);
|
|
871
|
+
const rec = t;
|
|
872
|
+
const id = typeof rec.id === "string" && rec.id.length > 0 ? rec.id : String(i + 1);
|
|
873
|
+
const title = typeof rec.title === "string" ? rec.title.trim() : "";
|
|
874
|
+
if (!title) throw new Error(`tasks[${i}].title is required`);
|
|
875
|
+
const status = isValidStatus(rec.status) ? rec.status : "pending";
|
|
876
|
+
return { id, title, status };
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
var init_tasks_state = __esm({
|
|
880
|
+
"src/tasks-state.ts"() {
|
|
881
|
+
"use strict";
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
// src/tools/tasks.ts
|
|
886
|
+
var tasksSetTool;
|
|
887
|
+
var init_tasks = __esm({
|
|
888
|
+
"src/tools/tasks.ts"() {
|
|
889
|
+
"use strict";
|
|
890
|
+
init_tasks_state();
|
|
891
|
+
tasksSetTool = {
|
|
892
|
+
name: "tasks_set",
|
|
893
|
+
description: [
|
|
894
|
+
"Set the visible task list shown to the user during this turn.",
|
|
895
|
+
"Call this when the user has given you a multi-step job, or whenever progress changes:",
|
|
896
|
+
"at the start (all tasks pending, with exactly one in_progress), and after each step completes",
|
|
897
|
+
"(flip that task to completed and the next to in_progress).",
|
|
898
|
+
"Pass the ENTIRE task list each call \u2014 this replaces the panel.",
|
|
899
|
+
"Keep tasks short (one imperative clause). Only one should be in_progress at a time.",
|
|
900
|
+
"For quick single-step requests, don't use this tool at all."
|
|
901
|
+
].join(" "),
|
|
902
|
+
parameters: {
|
|
903
|
+
type: "object",
|
|
904
|
+
properties: {
|
|
905
|
+
tasks: {
|
|
906
|
+
type: "array",
|
|
907
|
+
description: "The full, ordered list of tasks. Pass every call.",
|
|
908
|
+
items: {
|
|
909
|
+
type: "object",
|
|
910
|
+
properties: {
|
|
911
|
+
id: { type: "string", description: "Stable short id (e.g. '1', '2')." },
|
|
912
|
+
title: { type: "string", description: "Short imperative task title." },
|
|
913
|
+
status: {
|
|
914
|
+
type: "string",
|
|
915
|
+
enum: ["pending", "in_progress", "completed"],
|
|
916
|
+
description: "Only one task should be 'in_progress' at a time."
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
required: ["id", "title", "status"]
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
required: ["tasks"]
|
|
924
|
+
},
|
|
925
|
+
needsPermission: false,
|
|
926
|
+
render: (args) => ({
|
|
927
|
+
title: `tasks (${args.tasks.length} items)`,
|
|
928
|
+
body: args.tasks.map((t) => `${t.status === "completed" ? "\u2713" : t.status === "in_progress" ? "\u25B8" : "\xB7"} ${t.title}`).join("\n")
|
|
929
|
+
}),
|
|
930
|
+
run: async (args, ctx) => {
|
|
931
|
+
let tasks;
|
|
932
|
+
try {
|
|
933
|
+
tasks = validateTasks(args.tasks);
|
|
934
|
+
} catch (e) {
|
|
935
|
+
return `Error: ${e.message}`;
|
|
936
|
+
}
|
|
937
|
+
ctx.onTasks?.(tasks);
|
|
938
|
+
const summary = `${tasks.length} tasks set \u2014 ${tasks.filter((t) => t.status === "completed").length} done, ${tasks.filter((t) => t.status === "in_progress").length} active, ${tasks.filter((t) => t.status === "pending").length} pending`;
|
|
939
|
+
return summary;
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
|
|
815
945
|
// src/tools/executor.ts
|
|
816
946
|
function truncateForError(s) {
|
|
817
947
|
return s.length <= 200 ? s : `${s.slice(0, 200)}... [${s.length - 200} more chars]`;
|
|
@@ -827,6 +957,7 @@ var init_executor = __esm({
|
|
|
827
957
|
init_glob();
|
|
828
958
|
init_grep();
|
|
829
959
|
init_web_fetch();
|
|
960
|
+
init_tasks();
|
|
830
961
|
ALL_TOOLS = [
|
|
831
962
|
readTool,
|
|
832
963
|
writeTool,
|
|
@@ -834,7 +965,8 @@ var init_executor = __esm({
|
|
|
834
965
|
bashTool,
|
|
835
966
|
globTool,
|
|
836
967
|
grepTool,
|
|
837
|
-
webFetchTool
|
|
968
|
+
webFetchTool,
|
|
969
|
+
tasksSetTool
|
|
838
970
|
];
|
|
839
971
|
ToolExecutor = class {
|
|
840
972
|
sessionAllowed = /* @__PURE__ */ new Set();
|
|
@@ -845,6 +977,9 @@ var init_executor = __esm({
|
|
|
845
977
|
list() {
|
|
846
978
|
return [...this.tools.values()];
|
|
847
979
|
}
|
|
980
|
+
clearSessionPermissions() {
|
|
981
|
+
this.sessionAllowed.clear();
|
|
982
|
+
}
|
|
848
983
|
async run(call, askPermission, ctx) {
|
|
849
984
|
const tool = this.tools.get(call.name);
|
|
850
985
|
if (!tool) {
|
|
@@ -904,6 +1039,85 @@ var init_executor = __esm({
|
|
|
904
1039
|
}
|
|
905
1040
|
});
|
|
906
1041
|
|
|
1042
|
+
// src/agent/compact.ts
|
|
1043
|
+
function indexOfNthUserFromEnd(messages, n) {
|
|
1044
|
+
let seen = 0;
|
|
1045
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1046
|
+
if (messages[i].role === "user") {
|
|
1047
|
+
seen++;
|
|
1048
|
+
if (seen === n) return i;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return -1;
|
|
1052
|
+
}
|
|
1053
|
+
async function compactMessages(opts2) {
|
|
1054
|
+
const keep = opts2.keepLastTurns ?? 4;
|
|
1055
|
+
const messages = opts2.messages;
|
|
1056
|
+
const systemMsg = messages.find((m) => m.role === "system");
|
|
1057
|
+
if (!systemMsg) throw new Error("compact: no system message found");
|
|
1058
|
+
const cutoffUserIdx = indexOfNthUserFromEnd(messages, keep);
|
|
1059
|
+
const firstKeepIdx = cutoffUserIdx >= 0 ? cutoffUserIdx : messages.length;
|
|
1060
|
+
const toSummarize = messages.slice(1, firstKeepIdx);
|
|
1061
|
+
const toKeep = messages.slice(firstKeepIdx);
|
|
1062
|
+
if (toSummarize.length === 0) {
|
|
1063
|
+
return { summary: "", newMessages: messages, replacedCount: 0 };
|
|
1064
|
+
}
|
|
1065
|
+
const transcript = toSummarize.map((m) => {
|
|
1066
|
+
if (m.role === "tool") {
|
|
1067
|
+
const snippet = (m.content ?? "").slice(0, 500);
|
|
1068
|
+
return `[tool ${m.name ?? ""}] ${snippet}`;
|
|
1069
|
+
}
|
|
1070
|
+
if (m.role === "assistant") {
|
|
1071
|
+
const calls = m.tool_calls ? ` (tool_calls: ${m.tool_calls.map((c) => c.function.name).join(", ")})` : "";
|
|
1072
|
+
return `[assistant]${calls} ${m.content ?? ""}`;
|
|
1073
|
+
}
|
|
1074
|
+
return `[${m.role}] ${m.content ?? ""}`;
|
|
1075
|
+
}).join("\n");
|
|
1076
|
+
let summary = "";
|
|
1077
|
+
const events = runKimi({
|
|
1078
|
+
accountId: opts2.accountId,
|
|
1079
|
+
apiToken: opts2.apiToken,
|
|
1080
|
+
model: opts2.model,
|
|
1081
|
+
messages: [
|
|
1082
|
+
{ role: "system", content: SUMMARY_SYSTEM },
|
|
1083
|
+
{ role: "user", content: `Summarize this session so it can be replaced by your summary:
|
|
1084
|
+
|
|
1085
|
+
${transcript}` }
|
|
1086
|
+
],
|
|
1087
|
+
signal: opts2.signal,
|
|
1088
|
+
temperature: 0.1,
|
|
1089
|
+
reasoningEffort: "low"
|
|
1090
|
+
});
|
|
1091
|
+
for await (const ev of events) {
|
|
1092
|
+
if (ev.type === "text") summary += ev.delta;
|
|
1093
|
+
}
|
|
1094
|
+
const summaryMsg = {
|
|
1095
|
+
role: "user",
|
|
1096
|
+
content: `[compacted summary of earlier turns]
|
|
1097
|
+
${summary.trim()}`
|
|
1098
|
+
};
|
|
1099
|
+
return {
|
|
1100
|
+
summary: summary.trim(),
|
|
1101
|
+
newMessages: [systemMsg, summaryMsg, ...toKeep],
|
|
1102
|
+
replacedCount: toSummarize.length
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
var SUMMARY_SYSTEM;
|
|
1106
|
+
var init_compact = __esm({
|
|
1107
|
+
"src/agent/compact.ts"() {
|
|
1108
|
+
"use strict";
|
|
1109
|
+
init_client();
|
|
1110
|
+
SUMMARY_SYSTEM = `You are summarizing a terminal coding session so it can fit back into a short context window. Produce a dense summary that captures:
|
|
1111
|
+
- The user's goal(s) and what they've asked for.
|
|
1112
|
+
- Files read or modified, with paths.
|
|
1113
|
+
- Tools run (bash commands, edits) and the outcome of each.
|
|
1114
|
+
- Decisions made and open questions.
|
|
1115
|
+
- Any constraints or preferences the user has stated.
|
|
1116
|
+
|
|
1117
|
+
Do not include speculation. Do not include chat-style pleasantries. Use short bullet form. Aim for ~400-800 tokens.`;
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
|
|
907
1121
|
// src/ui/diff-view.tsx
|
|
908
1122
|
import { Box, Text } from "ink";
|
|
909
1123
|
import { createTwoFilesPatch } from "diff";
|
|
@@ -943,9 +1157,12 @@ var init_diff_view = __esm({
|
|
|
943
1157
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
944
1158
|
import Spinner from "ink-spinner";
|
|
945
1159
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
946
|
-
function ToolView({ evt }) {
|
|
1160
|
+
function ToolView({ evt, verbose }) {
|
|
947
1161
|
const statusIcon = evt.status === "running" ? /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) : evt.status === "error" ? /* @__PURE__ */ jsx2(Text2, { color: "red", children: "\u2717" }) : /* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713" });
|
|
948
1162
|
const title = evt.render?.title ?? `${evt.name}(${compactArgs(evt.args)})`;
|
|
1163
|
+
const expand = Boolean(evt.expanded || verbose);
|
|
1164
|
+
const lines = evt.result ? evt.result.split("\n") : [];
|
|
1165
|
+
const showLimit = verbose ? 200 : 20;
|
|
949
1166
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginLeft: 2, children: [
|
|
950
1167
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
951
1168
|
statusIcon,
|
|
@@ -953,15 +1170,15 @@ function ToolView({ evt }) {
|
|
|
953
1170
|
/* @__PURE__ */ jsx2(Text2, { color: "magenta", children: title })
|
|
954
1171
|
] }),
|
|
955
1172
|
evt.render?.diff ? /* @__PURE__ */ jsx2(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx2(DiffView, { ...evt.render.diff }) }) : null,
|
|
956
|
-
evt.result &&
|
|
957
|
-
|
|
958
|
-
|
|
1173
|
+
evt.result && expand ? /* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, flexDirection: "column", children: [
|
|
1174
|
+
lines.slice(0, showLimit).map((l, i) => /* @__PURE__ */ jsx2(Text2, { color: "gray", children: l }, i)),
|
|
1175
|
+
lines.length > showLimit && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
959
1176
|
"... (",
|
|
960
|
-
|
|
1177
|
+
lines.length - showLimit,
|
|
961
1178
|
" more lines)"
|
|
962
1179
|
] })
|
|
963
1180
|
] }) : null,
|
|
964
|
-
evt.result && !
|
|
1181
|
+
evt.result && !expand && evt.status !== "running" ? /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
965
1182
|
" ",
|
|
966
1183
|
firstLine(evt.result)
|
|
967
1184
|
] }) : null
|
|
@@ -985,32 +1202,32 @@ var init_tool_view = __esm({
|
|
|
985
1202
|
// src/ui/chat.tsx
|
|
986
1203
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
987
1204
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
988
|
-
function ChatView({ events, showReasoning }) {
|
|
989
|
-
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: events.map((e) => /* @__PURE__ */ jsx3(EventView, { evt: e, showReasoning }, e.key)) });
|
|
1205
|
+
function ChatView({ events, showReasoning, theme, verbose }) {
|
|
1206
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: events.map((e) => /* @__PURE__ */ jsx3(EventView, { evt: e, showReasoning, theme, verbose }, e.key)) });
|
|
990
1207
|
}
|
|
991
|
-
function EventView({ evt, showReasoning }) {
|
|
1208
|
+
function EventView({ evt, showReasoning, theme, verbose }) {
|
|
992
1209
|
if (evt.kind === "user") {
|
|
993
1210
|
return /* @__PURE__ */ jsxs3(Box3, { marginY: 0, children: [
|
|
994
|
-
/* @__PURE__ */ jsx3(Text3, { color:
|
|
1211
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.user, children: "\u203A " }),
|
|
995
1212
|
/* @__PURE__ */ jsx3(Text3, { children: evt.text })
|
|
996
1213
|
] });
|
|
997
1214
|
}
|
|
998
1215
|
if (evt.kind === "assistant") {
|
|
999
1216
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginY: 0, children: [
|
|
1000
|
-
showReasoning && evt.reasoning ? /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color:
|
|
1217
|
+
showReasoning && evt.reasoning ? /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: theme.reasoning.color, dimColor: theme.reasoning.dim, children: [
|
|
1001
1218
|
"\u2727 thinking: ",
|
|
1002
1219
|
evt.reasoning.length > 400 ? evt.reasoning.slice(0, 400) + "\u2026" : evt.reasoning
|
|
1003
1220
|
] }) }) : null,
|
|
1004
|
-
evt.text ? /* @__PURE__ */ jsx3(Text3, { children: evt.text }) : null
|
|
1221
|
+
evt.text ? theme.assistant ? /* @__PURE__ */ jsx3(Text3, { color: theme.assistant, children: evt.text }) : /* @__PURE__ */ jsx3(Text3, { children: evt.text }) : null
|
|
1005
1222
|
] });
|
|
1006
1223
|
}
|
|
1007
1224
|
if (evt.kind === "tool") {
|
|
1008
|
-
return /* @__PURE__ */ jsx3(ToolView, { evt });
|
|
1225
|
+
return /* @__PURE__ */ jsx3(ToolView, { evt, verbose });
|
|
1009
1226
|
}
|
|
1010
1227
|
if (evt.kind === "info") {
|
|
1011
|
-
return /* @__PURE__ */ jsx3(Text3, { color:
|
|
1228
|
+
return /* @__PURE__ */ jsx3(Text3, { color: theme.info.color, dimColor: theme.info.dim, children: evt.text });
|
|
1012
1229
|
}
|
|
1013
|
-
return /* @__PURE__ */ jsxs3(Text3, { color:
|
|
1230
|
+
return /* @__PURE__ */ jsxs3(Text3, { color: theme.error, children: [
|
|
1014
1231
|
"! ",
|
|
1015
1232
|
evt.text
|
|
1016
1233
|
] });
|
|
@@ -1024,22 +1241,38 @@ var init_chat = __esm({
|
|
|
1024
1241
|
|
|
1025
1242
|
// src/ui/status.tsx
|
|
1026
1243
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
1027
|
-
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1028
|
-
function StatusBar({ model, usage, thinking, hint }) {
|
|
1029
|
-
const parts = [`model: ${shortModel(model)}`];
|
|
1244
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1245
|
+
function StatusBar({ model, usage, thinking, hint, theme, mode, effort, contextLimit }) {
|
|
1246
|
+
const parts = [`model: ${shortModel(model)}`, `effort: ${effort}`];
|
|
1030
1247
|
if (usage) {
|
|
1031
1248
|
const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
1032
1249
|
const uncachedIn = usage.prompt_tokens - cached;
|
|
1033
1250
|
const cost = uncachedIn * PRICE_IN_PER_M / 1e6 + cached * PRICE_IN_CACHED_PER_M / 1e6 + usage.completion_tokens * PRICE_OUT_PER_M / 1e6;
|
|
1251
|
+
const pct = Math.round(usage.prompt_tokens / contextLimit * 100);
|
|
1034
1252
|
parts.push(
|
|
1035
1253
|
`in: ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`,
|
|
1036
1254
|
`out: ${usage.completion_tokens}`,
|
|
1255
|
+
`ctx: ${pct}%`,
|
|
1037
1256
|
`$${cost.toFixed(5)}`
|
|
1038
1257
|
);
|
|
1039
1258
|
}
|
|
1040
1259
|
if (thinking) parts.push("thinking\u2026");
|
|
1041
1260
|
if (hint) parts.push(hint);
|
|
1042
|
-
|
|
1261
|
+
const modeColor = mode === "plan" ? theme.modeBadge.plan : mode === "auto" ? theme.modeBadge.auto : theme.modeBadge.edit;
|
|
1262
|
+
const warn = usage && usage.prompt_tokens / contextLimit >= 0.8;
|
|
1263
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
1264
|
+
/* @__PURE__ */ jsxs4(Text4, { color: modeColor, bold: true, children: [
|
|
1265
|
+
"[",
|
|
1266
|
+
mode,
|
|
1267
|
+
"]"
|
|
1268
|
+
] }),
|
|
1269
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1270
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.info.color, dimColor: theme.info.dim, children: parts.join(" \xB7 ") }),
|
|
1271
|
+
warn ? /* @__PURE__ */ jsxs4(Text4, { color: theme.warn, bold: true, children: [
|
|
1272
|
+
" \xB7 ",
|
|
1273
|
+
"/compact recommended"
|
|
1274
|
+
] }) : null
|
|
1275
|
+
] });
|
|
1043
1276
|
}
|
|
1044
1277
|
function shortModel(m) {
|
|
1045
1278
|
const last = m.split("/").at(-1) ?? m;
|
|
@@ -1058,25 +1291,25 @@ var init_status = __esm({
|
|
|
1058
1291
|
// src/ui/permission.tsx
|
|
1059
1292
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
1060
1293
|
import SelectInput from "ink-select-input";
|
|
1061
|
-
import { jsx as jsx5, jsxs as
|
|
1062
|
-
function PermissionModal({ tool, args, onDecide }) {
|
|
1294
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1295
|
+
function PermissionModal({ tool, args, onDecide, theme }) {
|
|
1063
1296
|
const render2 = tool.render?.(args);
|
|
1064
1297
|
const items = [
|
|
1065
1298
|
{ label: "Allow once", value: "allow" },
|
|
1066
1299
|
{ label: "Allow for this session", value: "allow_session" },
|
|
1067
1300
|
{ label: "Deny", value: "deny" }
|
|
1068
1301
|
];
|
|
1069
|
-
return /* @__PURE__ */
|
|
1070
|
-
/* @__PURE__ */ jsx5(Text5, { color:
|
|
1071
|
-
/* @__PURE__ */
|
|
1302
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: theme.permission, paddingX: 1, children: [
|
|
1303
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.permission, bold: true, children: "Permission requested" }),
|
|
1304
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1072
1305
|
"tool: ",
|
|
1073
|
-
/* @__PURE__ */ jsx5(Text5, { color:
|
|
1306
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.tool, children: tool.name })
|
|
1074
1307
|
] }),
|
|
1075
|
-
render2?.title ? /* @__PURE__ */
|
|
1308
|
+
render2?.title ? /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1076
1309
|
"action: ",
|
|
1077
1310
|
render2.title
|
|
1078
1311
|
] }) : null,
|
|
1079
|
-
render2?.diff ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx5(DiffView, { ...render2.diff }) }) : /* @__PURE__ */
|
|
1312
|
+
render2?.diff ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx5(DiffView, { ...render2.diff }) }) : /* @__PURE__ */ jsxs5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children: [
|
|
1080
1313
|
"args: ",
|
|
1081
1314
|
JSON.stringify(args)
|
|
1082
1315
|
] }),
|
|
@@ -1090,6 +1323,150 @@ var init_permission = __esm({
|
|
|
1090
1323
|
}
|
|
1091
1324
|
});
|
|
1092
1325
|
|
|
1326
|
+
// src/ui/resume-picker.tsx
|
|
1327
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
1328
|
+
import SelectInput2 from "ink-select-input";
|
|
1329
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1330
|
+
function ResumePicker({ sessions, onPick, theme }) {
|
|
1331
|
+
if (sessions.length === 0) {
|
|
1332
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
|
|
1333
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: "Resume a session" }),
|
|
1334
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.info.color, dimColor: theme.info.dim, children: "No saved sessions yet. Press Enter to dismiss." }),
|
|
1335
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(
|
|
1336
|
+
SelectInput2,
|
|
1337
|
+
{
|
|
1338
|
+
items: [{ label: "(back)", value: "__cancel__" }],
|
|
1339
|
+
onSelect: () => onPick(null)
|
|
1340
|
+
}
|
|
1341
|
+
) })
|
|
1342
|
+
] });
|
|
1343
|
+
}
|
|
1344
|
+
const items = sessions.map((s) => ({
|
|
1345
|
+
label: `${formatDate(s.updatedAt)} \xB7 ${s.messageCount} msgs \xB7 ${s.firstPrompt}`,
|
|
1346
|
+
value: s.id
|
|
1347
|
+
}));
|
|
1348
|
+
items.push({ label: "(cancel)", value: "__cancel__" });
|
|
1349
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
|
|
1350
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: "Resume a session" }),
|
|
1351
|
+
/* @__PURE__ */ jsx6(Text6, { color: theme.info.color, dimColor: theme.info.dim, children: "Arrow keys to select, Enter to confirm." }),
|
|
1352
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(
|
|
1353
|
+
SelectInput2,
|
|
1354
|
+
{
|
|
1355
|
+
items,
|
|
1356
|
+
onSelect: (item) => {
|
|
1357
|
+
if (item.value === "__cancel__") return onPick(null);
|
|
1358
|
+
const picked = sessions.find((s) => s.id === item.value) ?? null;
|
|
1359
|
+
onPick(picked);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
) })
|
|
1363
|
+
] });
|
|
1364
|
+
}
|
|
1365
|
+
function formatDate(iso) {
|
|
1366
|
+
try {
|
|
1367
|
+
const d = new Date(iso);
|
|
1368
|
+
return d.toLocaleString(void 0, {
|
|
1369
|
+
month: "short",
|
|
1370
|
+
day: "numeric",
|
|
1371
|
+
hour: "2-digit",
|
|
1372
|
+
minute: "2-digit"
|
|
1373
|
+
});
|
|
1374
|
+
} catch {
|
|
1375
|
+
return iso;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
var init_resume_picker = __esm({
|
|
1379
|
+
"src/ui/resume-picker.tsx"() {
|
|
1380
|
+
"use strict";
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
// src/ui/task-list.tsx
|
|
1385
|
+
import { useEffect, useState } from "react";
|
|
1386
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
1387
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1388
|
+
function TaskList({ tasks, theme, startedAt, tokensDelta }) {
|
|
1389
|
+
const [now, setNow] = useState(Date.now());
|
|
1390
|
+
useEffect(() => {
|
|
1391
|
+
if (startedAt === null) return;
|
|
1392
|
+
const allDone2 = tasks.length > 0 && tasks.every((t) => t.status === "completed");
|
|
1393
|
+
if (allDone2) return;
|
|
1394
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
1395
|
+
return () => clearInterval(id);
|
|
1396
|
+
}, [startedAt, tasks]);
|
|
1397
|
+
if (tasks.length === 0) return null;
|
|
1398
|
+
const active = tasks.find((t) => t.status === "in_progress");
|
|
1399
|
+
const done = tasks.filter((t) => t.status === "completed").length;
|
|
1400
|
+
const total = tasks.length;
|
|
1401
|
+
const allDone = done === total;
|
|
1402
|
+
const header = active ? active.title : allDone ? `Done (${total} tasks)` : `Tasks (${done}/${total})`;
|
|
1403
|
+
const elapsed = startedAt ? formatElapsed(now - startedAt) : null;
|
|
1404
|
+
const headerStats = [elapsed, tokensDelta > 0 ? `\u2191 ${formatTokens(tokensDelta)} tokens` : null].filter(Boolean).join(" \xB7 ");
|
|
1405
|
+
const visibleTasks = tasks.slice(0, MAX_VISIBLE);
|
|
1406
|
+
const hiddenPending = Math.max(0, tasks.length - visibleTasks.length);
|
|
1407
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
1408
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1409
|
+
/* @__PURE__ */ jsxs7(Text7, { color: allDone ? "green" : theme.accent, bold: true, children: [
|
|
1410
|
+
allDone ? "\u2713" : "\u25B8",
|
|
1411
|
+
" ",
|
|
1412
|
+
header
|
|
1413
|
+
] }),
|
|
1414
|
+
headerStats && /* @__PURE__ */ jsxs7(Text7, { color: theme.info.color, dimColor: theme.info.dim, children: [
|
|
1415
|
+
" ",
|
|
1416
|
+
"(",
|
|
1417
|
+
headerStats,
|
|
1418
|
+
")"
|
|
1419
|
+
] })
|
|
1420
|
+
] }),
|
|
1421
|
+
visibleTasks.map((t) => /* @__PURE__ */ jsx7(TaskRow, { task: t, theme }, t.id)),
|
|
1422
|
+
hiddenPending > 0 && /* @__PURE__ */ jsxs7(Text7, { color: theme.info.color, dimColor: theme.info.dim, children: [
|
|
1423
|
+
" ",
|
|
1424
|
+
"\u2026 +",
|
|
1425
|
+
hiddenPending,
|
|
1426
|
+
" more"
|
|
1427
|
+
] })
|
|
1428
|
+
] });
|
|
1429
|
+
}
|
|
1430
|
+
function TaskRow({ task, theme }) {
|
|
1431
|
+
if (task.status === "completed") {
|
|
1432
|
+
return /* @__PURE__ */ jsxs7(Text7, { color: theme.info.color, dimColor: theme.info.dim, children: [
|
|
1433
|
+
" ",
|
|
1434
|
+
"\u2713 ",
|
|
1435
|
+
/* @__PURE__ */ jsx7(Text7, { strikethrough: true, children: task.title })
|
|
1436
|
+
] });
|
|
1437
|
+
}
|
|
1438
|
+
if (task.status === "in_progress") {
|
|
1439
|
+
return /* @__PURE__ */ jsxs7(Text7, { color: theme.accent, bold: true, children: [
|
|
1440
|
+
" ",
|
|
1441
|
+
"\u25A0 ",
|
|
1442
|
+
task.title
|
|
1443
|
+
] });
|
|
1444
|
+
}
|
|
1445
|
+
return /* @__PURE__ */ jsxs7(Text7, { color: theme.info.color, dimColor: theme.info.dim, children: [
|
|
1446
|
+
" ",
|
|
1447
|
+
"\u2610 ",
|
|
1448
|
+
task.title
|
|
1449
|
+
] });
|
|
1450
|
+
}
|
|
1451
|
+
function formatElapsed(ms) {
|
|
1452
|
+
const total = Math.floor(ms / 1e3);
|
|
1453
|
+
const m = Math.floor(total / 60);
|
|
1454
|
+
const s = total % 60;
|
|
1455
|
+
if (m === 0) return `${s}s`;
|
|
1456
|
+
return `${m}m ${s}s`;
|
|
1457
|
+
}
|
|
1458
|
+
function formatTokens(n) {
|
|
1459
|
+
if (n < 1e3) return String(n);
|
|
1460
|
+
return `${(n / 1e3).toFixed(1)}k`;
|
|
1461
|
+
}
|
|
1462
|
+
var MAX_VISIBLE;
|
|
1463
|
+
var init_task_list = __esm({
|
|
1464
|
+
"src/ui/task-list.tsx"() {
|
|
1465
|
+
"use strict";
|
|
1466
|
+
MAX_VISIBLE = 6;
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1093
1470
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
1094
1471
|
function assembleStyles() {
|
|
1095
1472
|
const codes = /* @__PURE__ */ new Map();
|
|
@@ -1611,9 +1988,20 @@ var init_source = __esm({
|
|
|
1611
1988
|
});
|
|
1612
1989
|
|
|
1613
1990
|
// src/ui/text-input.tsx
|
|
1614
|
-
import { useState, useEffect } from "react";
|
|
1615
|
-
import { Text as
|
|
1616
|
-
import { jsx as
|
|
1991
|
+
import { useState as useState2, useEffect as useEffect2, useRef } from "react";
|
|
1992
|
+
import { Text as Text8, useInput } from "ink";
|
|
1993
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1994
|
+
function shouldTreatAsPaste(input) {
|
|
1995
|
+
if (input.length >= PASTE_CHAR_THRESHOLD) return true;
|
|
1996
|
+
const newlines = (input.match(/\n/g) ?? []).length;
|
|
1997
|
+
return newlines >= PASTE_NEWLINE_THRESHOLD;
|
|
1998
|
+
}
|
|
1999
|
+
function newPasteId() {
|
|
2000
|
+
return Math.random().toString(36).slice(2, 7);
|
|
2001
|
+
}
|
|
2002
|
+
function countLines(s) {
|
|
2003
|
+
return s.split("\n").length;
|
|
2004
|
+
}
|
|
1617
2005
|
function findWordBoundaryForward(text, pos) {
|
|
1618
2006
|
while (pos < text.length && /\w/.test(text[pos])) pos++;
|
|
1619
2007
|
while (pos < text.length && !/\w/.test(text[pos])) pos++;
|
|
@@ -1632,10 +2020,12 @@ function CustomTextInput({
|
|
|
1632
2020
|
onHistoryDown,
|
|
1633
2021
|
onClearQueueItem,
|
|
1634
2022
|
focus = true,
|
|
1635
|
-
mask
|
|
2023
|
+
mask,
|
|
2024
|
+
enablePaste = false
|
|
1636
2025
|
}) {
|
|
1637
|
-
const [cursorOffset, setCursorOffset] =
|
|
1638
|
-
|
|
2026
|
+
const [cursorOffset, setCursorOffset] = useState2(value.length);
|
|
2027
|
+
const pastesRef = useRef(/* @__PURE__ */ new Map());
|
|
2028
|
+
useEffect2(() => {
|
|
1639
2029
|
if (!focus) return;
|
|
1640
2030
|
setCursorOffset((prev) => prev > value.length ? value.length : prev);
|
|
1641
2031
|
}, [value, focus]);
|
|
@@ -1644,9 +2034,21 @@ function CustomTextInput({
|
|
|
1644
2034
|
if (!focus) return;
|
|
1645
2035
|
if (key.ctrl && input === "c") return;
|
|
1646
2036
|
if (key.ctrl && input === "r") return;
|
|
2037
|
+
if (key.ctrl && input === "o") return;
|
|
1647
2038
|
if (key.tab) return;
|
|
1648
2039
|
if (key.return) {
|
|
1649
|
-
|
|
2040
|
+
let full = value;
|
|
2041
|
+
let hasPastes = false;
|
|
2042
|
+
if (enablePaste && pastesRef.current.size > 0) {
|
|
2043
|
+
for (const [placeholder, fullText] of pastesRef.current) {
|
|
2044
|
+
if (full.includes(placeholder)) {
|
|
2045
|
+
full = full.split(placeholder).join(fullText);
|
|
2046
|
+
hasPastes = true;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
onSubmit(full, hasPastes ? value : void 0);
|
|
2051
|
+
pastesRef.current.clear();
|
|
1650
2052
|
setCursorOffset(0);
|
|
1651
2053
|
return;
|
|
1652
2054
|
}
|
|
@@ -1673,13 +2075,27 @@ function CustomTextInput({
|
|
|
1673
2075
|
} else {
|
|
1674
2076
|
nextCursor = cursorOffset + 1;
|
|
1675
2077
|
}
|
|
2078
|
+
} else if (key.meta && input === "b") {
|
|
2079
|
+
nextCursor = findWordBoundaryBackward(value, cursorOffset);
|
|
2080
|
+
} else if (key.meta && input === "f") {
|
|
2081
|
+
nextCursor = findWordBoundaryForward(value, cursorOffset);
|
|
2082
|
+
} else if (key.meta && input === "d") {
|
|
2083
|
+
didDelete = true;
|
|
2084
|
+
const boundary = findWordBoundaryForward(value, cursorOffset);
|
|
2085
|
+
nextValue = value.slice(0, cursorOffset) + value.slice(boundary);
|
|
1676
2086
|
} else if (key.home || key.ctrl && input === "a") {
|
|
1677
2087
|
nextCursor = 0;
|
|
1678
2088
|
} else if (key.end || key.ctrl && input === "e") {
|
|
1679
2089
|
nextCursor = value.length;
|
|
1680
2090
|
} else if (key.backspace) {
|
|
1681
2091
|
didDelete = true;
|
|
1682
|
-
|
|
2092
|
+
const tokenBoundary = enablePaste ? findPasteTokenEndingAt(value, cursorOffset, pastesRef.current) : -1;
|
|
2093
|
+
if (tokenBoundary >= 0) {
|
|
2094
|
+
const token = value.slice(tokenBoundary, cursorOffset);
|
|
2095
|
+
pastesRef.current.delete(token);
|
|
2096
|
+
nextValue = value.slice(0, tokenBoundary) + value.slice(cursorOffset);
|
|
2097
|
+
nextCursor = tokenBoundary;
|
|
2098
|
+
} else if (key.meta || key.ctrl && input === "w") {
|
|
1683
2099
|
const boundary = findWordBoundaryBackward(value, cursorOffset);
|
|
1684
2100
|
nextValue = value.slice(0, boundary) + value.slice(cursorOffset);
|
|
1685
2101
|
nextCursor = boundary;
|
|
@@ -1714,8 +2130,16 @@ function CustomTextInput({
|
|
|
1714
2130
|
didDelete = true;
|
|
1715
2131
|
nextValue = value.slice(0, cursorOffset);
|
|
1716
2132
|
} else if (input.length > 0 && !key.ctrl && !key.meta) {
|
|
1717
|
-
|
|
1718
|
-
|
|
2133
|
+
let toInsert = input;
|
|
2134
|
+
if (enablePaste && shouldTreatAsPaste(input)) {
|
|
2135
|
+
const lines = countLines(input);
|
|
2136
|
+
const id = newPasteId();
|
|
2137
|
+
const placeholder = `[pasted ${lines} line${lines === 1 ? "" : "s"} #${id}]`;
|
|
2138
|
+
pastesRef.current.set(placeholder, input);
|
|
2139
|
+
toInsert = placeholder;
|
|
2140
|
+
}
|
|
2141
|
+
nextValue = value.slice(0, cursorOffset) + toInsert + value.slice(cursorOffset);
|
|
2142
|
+
nextCursor = cursorOffset + toInsert.length;
|
|
1719
2143
|
}
|
|
1720
2144
|
if (nextCursor < 0) nextCursor = 0;
|
|
1721
2145
|
if (nextCursor > nextValue.length) nextCursor = nextValue.length;
|
|
@@ -1743,12 +2167,24 @@ function CustomTextInput({
|
|
|
1743
2167
|
} else if (cursorOffset === displayValue.length) {
|
|
1744
2168
|
renderedValue += source_default.inverse(" ");
|
|
1745
2169
|
}
|
|
1746
|
-
return /* @__PURE__ */
|
|
2170
|
+
return /* @__PURE__ */ jsx8(Text8, { children: renderedValue });
|
|
2171
|
+
}
|
|
2172
|
+
function findPasteTokenEndingAt(value, pos, pastes) {
|
|
2173
|
+
if (pos <= 0 || value[pos - 1] !== "]") return -1;
|
|
2174
|
+
for (const placeholder of pastes.keys()) {
|
|
2175
|
+
if (placeholder.length > pos) continue;
|
|
2176
|
+
const start = pos - placeholder.length;
|
|
2177
|
+
if (value.slice(start, pos) === placeholder) return start;
|
|
2178
|
+
}
|
|
2179
|
+
return -1;
|
|
1747
2180
|
}
|
|
2181
|
+
var PASTE_CHAR_THRESHOLD, PASTE_NEWLINE_THRESHOLD;
|
|
1748
2182
|
var init_text_input = __esm({
|
|
1749
2183
|
"src/ui/text-input.tsx"() {
|
|
1750
2184
|
"use strict";
|
|
1751
2185
|
init_source();
|
|
2186
|
+
PASTE_CHAR_THRESHOLD = 200;
|
|
2187
|
+
PASTE_NEWLINE_THRESHOLD = 3;
|
|
1752
2188
|
}
|
|
1753
2189
|
});
|
|
1754
2190
|
|
|
@@ -1792,12 +2228,12 @@ async function writeCache(entry) {
|
|
|
1792
2228
|
}
|
|
1793
2229
|
async function fetchLatestVersion() {
|
|
1794
2230
|
try {
|
|
1795
|
-
const res = await fetch(
|
|
1796
|
-
headers: { "User-Agent": "kimiflare-update-checker" }
|
|
2231
|
+
const res = await fetch(NPM_REGISTRY, {
|
|
2232
|
+
headers: { "User-Agent": "kimiflare-update-checker", Accept: "application/json" }
|
|
1797
2233
|
});
|
|
1798
2234
|
if (!res.ok) return null;
|
|
1799
2235
|
const data = await res.json();
|
|
1800
|
-
return data.
|
|
2236
|
+
return data.version ?? null;
|
|
1801
2237
|
} catch {
|
|
1802
2238
|
return null;
|
|
1803
2239
|
}
|
|
@@ -1840,25 +2276,25 @@ async function isGitRepo() {
|
|
|
1840
2276
|
return false;
|
|
1841
2277
|
}
|
|
1842
2278
|
}
|
|
1843
|
-
var CACHE_TTL_MS,
|
|
2279
|
+
var CACHE_TTL_MS, NPM_REGISTRY;
|
|
1844
2280
|
var init_update_check = __esm({
|
|
1845
2281
|
"src/util/update-check.ts"() {
|
|
1846
2282
|
"use strict";
|
|
1847
2283
|
CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
1848
|
-
|
|
2284
|
+
NPM_REGISTRY = "https://registry.npmjs.org/kimiflare/latest";
|
|
1849
2285
|
}
|
|
1850
2286
|
});
|
|
1851
2287
|
|
|
1852
2288
|
// src/ui/onboarding.tsx
|
|
1853
|
-
import { useState as
|
|
1854
|
-
import { Box as
|
|
1855
|
-
import { Fragment, jsx as
|
|
2289
|
+
import { useState as useState3 } from "react";
|
|
2290
|
+
import { Box as Box8, Text as Text9 } from "ink";
|
|
2291
|
+
import { Fragment, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1856
2292
|
function Onboarding({ onDone }) {
|
|
1857
|
-
const [step, setStep] =
|
|
1858
|
-
const [accountId, setAccountId] =
|
|
1859
|
-
const [apiToken, setApiToken] =
|
|
1860
|
-
const [model, setModel] =
|
|
1861
|
-
const [savedPath, setSavedPath] =
|
|
2293
|
+
const [step, setStep] = useState3("accountId");
|
|
2294
|
+
const [accountId, setAccountId] = useState3("");
|
|
2295
|
+
const [apiToken, setApiToken] = useState3("");
|
|
2296
|
+
const [model, setModel] = useState3(DEFAULT_MODEL);
|
|
2297
|
+
const [savedPath, setSavedPath] = useState3(null);
|
|
1862
2298
|
const handleAccountIdSubmit = (value) => {
|
|
1863
2299
|
const trimmed = value.trim();
|
|
1864
2300
|
if (!trimmed) return;
|
|
@@ -1886,15 +2322,15 @@ function Onboarding({ onDone }) {
|
|
|
1886
2322
|
setSavedPath(`error: ${e.message}`);
|
|
1887
2323
|
}
|
|
1888
2324
|
};
|
|
1889
|
-
return /* @__PURE__ */
|
|
1890
|
-
/* @__PURE__ */
|
|
1891
|
-
/* @__PURE__ */
|
|
1892
|
-
/* @__PURE__ */
|
|
1893
|
-
step === "accountId" && /* @__PURE__ */
|
|
1894
|
-
/* @__PURE__ */
|
|
1895
|
-
/* @__PURE__ */
|
|
1896
|
-
/* @__PURE__ */
|
|
1897
|
-
/* @__PURE__ */
|
|
2325
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2326
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: "Welcome to kimiflare!" }),
|
|
2327
|
+
/* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: "Terminal coding agent powered by Kimi-K2.6 on Cloudflare Workers AI." }),
|
|
2328
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2329
|
+
step === "accountId" && /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
2330
|
+
/* @__PURE__ */ jsx9(Text9, { children: "Enter your Cloudflare Account ID:" }),
|
|
2331
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2332
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "\u203A " }),
|
|
2333
|
+
/* @__PURE__ */ jsx9(
|
|
1898
2334
|
CustomTextInput,
|
|
1899
2335
|
{
|
|
1900
2336
|
value: accountId,
|
|
@@ -1904,12 +2340,12 @@ function Onboarding({ onDone }) {
|
|
|
1904
2340
|
)
|
|
1905
2341
|
] })
|
|
1906
2342
|
] }),
|
|
1907
|
-
step === "apiToken" && /* @__PURE__ */
|
|
1908
|
-
/* @__PURE__ */
|
|
1909
|
-
/* @__PURE__ */
|
|
1910
|
-
/* @__PURE__ */
|
|
1911
|
-
/* @__PURE__ */
|
|
1912
|
-
/* @__PURE__ */
|
|
2343
|
+
step === "apiToken" && /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
2344
|
+
/* @__PURE__ */ jsx9(Text9, { children: "Enter your Cloudflare API Token:" }),
|
|
2345
|
+
/* @__PURE__ */ jsx9(Text9, { color: "gray", dimColor: true, children: "Create one at https://dash.cloudflare.com/profile/api-tokens" }),
|
|
2346
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2347
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "\u203A " }),
|
|
2348
|
+
/* @__PURE__ */ jsx9(
|
|
1913
2349
|
CustomTextInput,
|
|
1914
2350
|
{
|
|
1915
2351
|
value: apiToken,
|
|
@@ -1920,15 +2356,15 @@ function Onboarding({ onDone }) {
|
|
|
1920
2356
|
)
|
|
1921
2357
|
] })
|
|
1922
2358
|
] }),
|
|
1923
|
-
step === "model" && /* @__PURE__ */
|
|
1924
|
-
/* @__PURE__ */
|
|
1925
|
-
/* @__PURE__ */
|
|
2359
|
+
step === "model" && /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
2360
|
+
/* @__PURE__ */ jsx9(Text9, { children: "Model ID (press Enter for default):" }),
|
|
2361
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "gray", dimColor: true, children: [
|
|
1926
2362
|
"default: ",
|
|
1927
2363
|
DEFAULT_MODEL
|
|
1928
2364
|
] }),
|
|
1929
|
-
/* @__PURE__ */
|
|
1930
|
-
/* @__PURE__ */
|
|
1931
|
-
/* @__PURE__ */
|
|
2365
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2366
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "\u203A " }),
|
|
2367
|
+
/* @__PURE__ */ jsx9(
|
|
1932
2368
|
CustomTextInput,
|
|
1933
2369
|
{
|
|
1934
2370
|
value: model,
|
|
@@ -1938,26 +2374,26 @@ function Onboarding({ onDone }) {
|
|
|
1938
2374
|
)
|
|
1939
2375
|
] })
|
|
1940
2376
|
] }),
|
|
1941
|
-
step === "confirm" && /* @__PURE__ */
|
|
1942
|
-
/* @__PURE__ */
|
|
1943
|
-
/* @__PURE__ */
|
|
1944
|
-
/* @__PURE__ */
|
|
2377
|
+
step === "confirm" && /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
2378
|
+
/* @__PURE__ */ jsx9(Text9, { children: "Ready to save configuration:" }),
|
|
2379
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, children: [
|
|
2380
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1945
2381
|
"Account ID: ",
|
|
1946
2382
|
accountId
|
|
1947
2383
|
] }),
|
|
1948
|
-
/* @__PURE__ */
|
|
2384
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1949
2385
|
"API Token: ",
|
|
1950
2386
|
"\u2022".repeat(apiToken.length)
|
|
1951
2387
|
] }),
|
|
1952
|
-
/* @__PURE__ */
|
|
2388
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "gray", children: [
|
|
1953
2389
|
"Model: ",
|
|
1954
2390
|
model
|
|
1955
2391
|
] })
|
|
1956
2392
|
] }),
|
|
1957
|
-
/* @__PURE__ */
|
|
1958
|
-
/* @__PURE__ */
|
|
1959
|
-
/* @__PURE__ */
|
|
1960
|
-
/* @__PURE__ */
|
|
2393
|
+
/* @__PURE__ */ jsx9(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { children: "Press Enter to confirm, or Ctrl+C to cancel" }) }),
|
|
2394
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2395
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "\u203A " }),
|
|
2396
|
+
/* @__PURE__ */ jsx9(
|
|
1961
2397
|
CustomTextInput,
|
|
1962
2398
|
{
|
|
1963
2399
|
value: "",
|
|
@@ -1968,7 +2404,7 @@ function Onboarding({ onDone }) {
|
|
|
1968
2404
|
)
|
|
1969
2405
|
] })
|
|
1970
2406
|
] }),
|
|
1971
|
-
savedPath && /* @__PURE__ */
|
|
2407
|
+
savedPath && /* @__PURE__ */ jsxs8(Text9, { color: "green", children: [
|
|
1972
2408
|
"Config saved to ",
|
|
1973
2409
|
savedPath
|
|
1974
2410
|
] })
|
|
@@ -1983,51 +2419,266 @@ var init_onboarding = __esm({
|
|
|
1983
2419
|
}
|
|
1984
2420
|
});
|
|
1985
2421
|
|
|
2422
|
+
// src/ui/theme.ts
|
|
2423
|
+
function resolveTheme(name) {
|
|
2424
|
+
if (!name) return THEMES[DEFAULT_THEME_NAME];
|
|
2425
|
+
return THEMES[name] ?? THEMES[DEFAULT_THEME_NAME];
|
|
2426
|
+
}
|
|
2427
|
+
function themeNames() {
|
|
2428
|
+
return Object.keys(THEMES);
|
|
2429
|
+
}
|
|
2430
|
+
var dark, light, highContrast, THEMES, DEFAULT_THEME_NAME;
|
|
2431
|
+
var init_theme = __esm({
|
|
2432
|
+
"src/ui/theme.ts"() {
|
|
2433
|
+
"use strict";
|
|
2434
|
+
dark = {
|
|
2435
|
+
name: "dark",
|
|
2436
|
+
label: "dark (default \u2014 for dark terminals)",
|
|
2437
|
+
user: "cyan",
|
|
2438
|
+
assistant: void 0,
|
|
2439
|
+
reasoning: { color: "gray", dim: true },
|
|
2440
|
+
info: { color: "gray", dim: true },
|
|
2441
|
+
error: "red",
|
|
2442
|
+
warn: "yellow",
|
|
2443
|
+
tool: "magenta",
|
|
2444
|
+
spinner: "yellow",
|
|
2445
|
+
permission: "yellow",
|
|
2446
|
+
queue: { color: "gray", dim: true },
|
|
2447
|
+
accent: "cyan",
|
|
2448
|
+
modeBadge: { plan: "blue", auto: "green", edit: "cyan" }
|
|
2449
|
+
};
|
|
2450
|
+
light = {
|
|
2451
|
+
name: "light",
|
|
2452
|
+
label: "light (for bright terminal backgrounds)",
|
|
2453
|
+
user: "blue",
|
|
2454
|
+
assistant: void 0,
|
|
2455
|
+
reasoning: { color: "blackBright", dim: false },
|
|
2456
|
+
info: { color: "blackBright", dim: false },
|
|
2457
|
+
error: "red",
|
|
2458
|
+
warn: "magenta",
|
|
2459
|
+
tool: "magenta",
|
|
2460
|
+
spinner: "blue",
|
|
2461
|
+
permission: "magenta",
|
|
2462
|
+
queue: { color: "blackBright", dim: false },
|
|
2463
|
+
accent: "blue",
|
|
2464
|
+
modeBadge: { plan: "blue", auto: "green", edit: "magenta" }
|
|
2465
|
+
};
|
|
2466
|
+
highContrast = {
|
|
2467
|
+
name: "high-contrast",
|
|
2468
|
+
label: "high-contrast (bold, bright colors for low-vision)",
|
|
2469
|
+
user: "cyanBright",
|
|
2470
|
+
assistant: "whiteBright",
|
|
2471
|
+
reasoning: { color: "whiteBright", dim: false },
|
|
2472
|
+
info: { color: "whiteBright", dim: false },
|
|
2473
|
+
error: "redBright",
|
|
2474
|
+
warn: "yellowBright",
|
|
2475
|
+
tool: "magentaBright",
|
|
2476
|
+
spinner: "yellowBright",
|
|
2477
|
+
permission: "yellowBright",
|
|
2478
|
+
queue: { color: "whiteBright", dim: false },
|
|
2479
|
+
accent: "cyanBright",
|
|
2480
|
+
modeBadge: { plan: "blueBright", auto: "greenBright", edit: "cyanBright" }
|
|
2481
|
+
};
|
|
2482
|
+
THEMES = {
|
|
2483
|
+
dark,
|
|
2484
|
+
light,
|
|
2485
|
+
"high-contrast": highContrast
|
|
2486
|
+
};
|
|
2487
|
+
DEFAULT_THEME_NAME = "dark";
|
|
2488
|
+
}
|
|
2489
|
+
});
|
|
2490
|
+
|
|
2491
|
+
// src/sessions.ts
|
|
2492
|
+
import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir4, readdir, stat as stat2 } from "fs/promises";
|
|
2493
|
+
import { homedir as homedir5 } from "os";
|
|
2494
|
+
import { join as join3 } from "path";
|
|
2495
|
+
function sessionsDir() {
|
|
2496
|
+
const xdg = process.env.XDG_DATA_HOME || join3(homedir5(), ".local", "share");
|
|
2497
|
+
return join3(xdg, "kimiflare", "sessions");
|
|
2498
|
+
}
|
|
2499
|
+
function sanitize(text) {
|
|
2500
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
2501
|
+
}
|
|
2502
|
+
function makeSessionId(firstPrompt) {
|
|
2503
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2504
|
+
const slug = sanitize(firstPrompt) || "session";
|
|
2505
|
+
return `${ts}_${slug}`;
|
|
2506
|
+
}
|
|
2507
|
+
async function saveSession(file) {
|
|
2508
|
+
const dir = sessionsDir();
|
|
2509
|
+
await mkdir4(dir, { recursive: true });
|
|
2510
|
+
const path = join3(dir, `${file.id}.json`);
|
|
2511
|
+
await writeFile5(path, JSON.stringify(file, null, 2), "utf8");
|
|
2512
|
+
return path;
|
|
2513
|
+
}
|
|
2514
|
+
async function listSessions(limit = 30) {
|
|
2515
|
+
const dir = sessionsDir();
|
|
2516
|
+
let entries;
|
|
2517
|
+
try {
|
|
2518
|
+
entries = await readdir(dir);
|
|
2519
|
+
} catch {
|
|
2520
|
+
return [];
|
|
2521
|
+
}
|
|
2522
|
+
const summaries = [];
|
|
2523
|
+
for (const name of entries) {
|
|
2524
|
+
if (!name.endsWith(".json")) continue;
|
|
2525
|
+
const path = join3(dir, name);
|
|
2526
|
+
try {
|
|
2527
|
+
const [s, raw] = await Promise.all([stat2(path), readFile7(path, "utf8")]);
|
|
2528
|
+
const parsed = JSON.parse(raw);
|
|
2529
|
+
const firstUser = parsed.messages.find((m) => m.role === "user");
|
|
2530
|
+
const firstPrompt = typeof firstUser?.content === "string" ? firstUser.content : "(no prompt)";
|
|
2531
|
+
summaries.push({
|
|
2532
|
+
id: parsed.id,
|
|
2533
|
+
filePath: path,
|
|
2534
|
+
cwd: parsed.cwd,
|
|
2535
|
+
firstPrompt: firstPrompt.slice(0, 80),
|
|
2536
|
+
messageCount: parsed.messages.filter((m) => m.role !== "system").length,
|
|
2537
|
+
updatedAt: parsed.updatedAt ?? s.mtime.toISOString()
|
|
2538
|
+
});
|
|
2539
|
+
} catch {
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
summaries.sort((a, b) => b.updatedAt < a.updatedAt ? -1 : 1);
|
|
2543
|
+
return summaries.slice(0, limit);
|
|
2544
|
+
}
|
|
2545
|
+
async function loadSession(filePath) {
|
|
2546
|
+
const raw = await readFile7(filePath, "utf8");
|
|
2547
|
+
return JSON.parse(raw);
|
|
2548
|
+
}
|
|
2549
|
+
var init_sessions = __esm({
|
|
2550
|
+
"src/sessions.ts"() {
|
|
2551
|
+
"use strict";
|
|
2552
|
+
}
|
|
2553
|
+
});
|
|
2554
|
+
|
|
1986
2555
|
// src/app.tsx
|
|
1987
2556
|
var app_exports = {};
|
|
1988
2557
|
__export(app_exports, {
|
|
1989
2558
|
renderApp: () => renderApp
|
|
1990
2559
|
});
|
|
1991
|
-
import { useState as
|
|
1992
|
-
import { Box as
|
|
2560
|
+
import { useState as useState4, useRef as useRef2, useEffect as useEffect3, useCallback } from "react";
|
|
2561
|
+
import { Box as Box9, Text as Text10, useApp, useInput as useInput2, render } from "ink";
|
|
1993
2562
|
import Spinner2 from "ink-spinner";
|
|
1994
2563
|
import { unlink } from "fs/promises";
|
|
1995
|
-
import { jsx as
|
|
2564
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1996
2565
|
function App({ initialCfg }) {
|
|
1997
2566
|
const { exit } = useApp();
|
|
1998
|
-
const [cfg, setCfg] =
|
|
1999
|
-
const [events, setEvents] =
|
|
2000
|
-
{ kind: "info", key: mkKey(), text: "kimiflare \xB7 /help for commands \xB7 ctrl-c to exit" }
|
|
2567
|
+
const [cfg, setCfg] = useState4(initialCfg);
|
|
2568
|
+
const [events, setEvents] = useState4([
|
|
2569
|
+
{ kind: "info", key: mkKey(), text: "kimiflare \xB7 /help for commands \xB7 ctrl-c to exit \xB7 shift+tab to cycle modes" }
|
|
2001
2570
|
]);
|
|
2002
|
-
const [input, setInput] =
|
|
2003
|
-
const [busy, setBusy] =
|
|
2004
|
-
const [usage, setUsage] =
|
|
2005
|
-
const [showReasoning, setShowReasoning] =
|
|
2006
|
-
const [perm, setPerm] =
|
|
2007
|
-
const [queue, setQueue] =
|
|
2008
|
-
const [history, setHistory] =
|
|
2009
|
-
const [historyIndex, setHistoryIndex] =
|
|
2010
|
-
const [draftInput, setDraftInput] =
|
|
2011
|
-
const [updateInfo, setUpdateInfo] =
|
|
2012
|
-
const
|
|
2571
|
+
const [input, setInput] = useState4("");
|
|
2572
|
+
const [busy, setBusy] = useState4(false);
|
|
2573
|
+
const [usage, setUsage] = useState4(null);
|
|
2574
|
+
const [showReasoning, setShowReasoning] = useState4(false);
|
|
2575
|
+
const [perm, setPerm] = useState4(null);
|
|
2576
|
+
const [queue, setQueue] = useState4([]);
|
|
2577
|
+
const [history, setHistory] = useState4([]);
|
|
2578
|
+
const [historyIndex, setHistoryIndex] = useState4(-1);
|
|
2579
|
+
const [draftInput, setDraftInput] = useState4("");
|
|
2580
|
+
const [updateInfo, setUpdateInfo] = useState4(null);
|
|
2581
|
+
const [mode, setMode] = useState4("edit");
|
|
2582
|
+
const [effort, setEffort] = useState4(
|
|
2583
|
+
initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
|
|
2584
|
+
);
|
|
2585
|
+
const [theme, setTheme] = useState4(resolveTheme(initialCfg?.theme));
|
|
2586
|
+
const [resumeSessions, setResumeSessions] = useState4(null);
|
|
2587
|
+
const [tasks, setTasks] = useState4([]);
|
|
2588
|
+
const [tasksStartedAt, setTasksStartedAt] = useState4(null);
|
|
2589
|
+
const [tasksStartTokens, setTasksStartTokens] = useState4(0);
|
|
2590
|
+
const [verbose, setVerbose] = useState4(false);
|
|
2591
|
+
const messagesRef = useRef2([
|
|
2013
2592
|
{
|
|
2014
2593
|
role: "system",
|
|
2015
|
-
content: buildSystemPrompt({
|
|
2594
|
+
content: buildSystemPrompt({
|
|
2595
|
+
cwd: process.cwd(),
|
|
2596
|
+
tools: ALL_TOOLS,
|
|
2597
|
+
model: cfg?.model ?? DEFAULT_MODEL,
|
|
2598
|
+
mode: "edit"
|
|
2599
|
+
})
|
|
2016
2600
|
}
|
|
2017
2601
|
]);
|
|
2018
|
-
const executorRef =
|
|
2019
|
-
const activeAsstIdRef =
|
|
2020
|
-
const activeControllerRef =
|
|
2602
|
+
const executorRef = useRef2(new ToolExecutor(ALL_TOOLS));
|
|
2603
|
+
const activeAsstIdRef = useRef2(null);
|
|
2604
|
+
const activeControllerRef = useRef2(null);
|
|
2605
|
+
const sessionIdRef = useRef2(null);
|
|
2606
|
+
const modeRef = useRef2(mode);
|
|
2607
|
+
const effortRef = useRef2(effort);
|
|
2608
|
+
useEffect3(() => {
|
|
2609
|
+
modeRef.current = mode;
|
|
2610
|
+
messagesRef.current[0] = {
|
|
2611
|
+
role: "system",
|
|
2612
|
+
content: buildSystemPrompt({
|
|
2613
|
+
cwd: process.cwd(),
|
|
2614
|
+
tools: ALL_TOOLS,
|
|
2615
|
+
model: cfg?.model ?? DEFAULT_MODEL,
|
|
2616
|
+
mode
|
|
2617
|
+
})
|
|
2618
|
+
};
|
|
2619
|
+
if (mode === "plan") {
|
|
2620
|
+
executorRef.current.clearSessionPermissions();
|
|
2621
|
+
}
|
|
2622
|
+
}, [mode, cfg?.model]);
|
|
2623
|
+
useEffect3(() => {
|
|
2624
|
+
effortRef.current = effort;
|
|
2625
|
+
}, [effort]);
|
|
2626
|
+
const saveSessionSafe = useCallback(async () => {
|
|
2627
|
+
if (!cfg) return;
|
|
2628
|
+
if (!sessionIdRef.current) {
|
|
2629
|
+
const firstUser = messagesRef.current.find((m) => m.role === "user");
|
|
2630
|
+
const firstText = typeof firstUser?.content === "string" ? firstUser.content : "session";
|
|
2631
|
+
sessionIdRef.current = makeSessionId(firstText);
|
|
2632
|
+
}
|
|
2633
|
+
try {
|
|
2634
|
+
await saveSession({
|
|
2635
|
+
id: sessionIdRef.current,
|
|
2636
|
+
cwd: process.cwd(),
|
|
2637
|
+
model: cfg.model,
|
|
2638
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2639
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2640
|
+
messages: messagesRef.current
|
|
2641
|
+
});
|
|
2642
|
+
} catch {
|
|
2643
|
+
}
|
|
2644
|
+
}, [cfg]);
|
|
2021
2645
|
useInput2((inputChar, key) => {
|
|
2022
2646
|
if (key.ctrl && inputChar === "c") {
|
|
2023
2647
|
if (busy && activeControllerRef.current) {
|
|
2024
2648
|
activeControllerRef.current.abort();
|
|
2649
|
+
setQueue([]);
|
|
2025
2650
|
setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "(interrupted)" }]);
|
|
2026
2651
|
} else {
|
|
2027
2652
|
exit();
|
|
2028
2653
|
}
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
if (key.ctrl && inputChar === "r") {
|
|
2657
|
+
setShowReasoning((s) => !s);
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
if (key.shift && key.tab) {
|
|
2661
|
+
setMode((m) => {
|
|
2662
|
+
const nm = nextMode(m);
|
|
2663
|
+
setEvents((e) => [
|
|
2664
|
+
...e,
|
|
2665
|
+
{ kind: "info", key: mkKey(), text: `mode: ${nm}` }
|
|
2666
|
+
]);
|
|
2667
|
+
return nm;
|
|
2668
|
+
});
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
if (key.ctrl && inputChar === "o") {
|
|
2672
|
+
setVerbose((v) => {
|
|
2673
|
+
const next = !v;
|
|
2674
|
+
setEvents((e) => [
|
|
2675
|
+
...e,
|
|
2676
|
+
{ kind: "info", key: mkKey(), text: `output: ${next ? "verbose (full tool results)" : "compact"}` }
|
|
2677
|
+
]);
|
|
2678
|
+
return next;
|
|
2679
|
+
});
|
|
2680
|
+
return;
|
|
2029
2681
|
}
|
|
2030
|
-
if (key.ctrl && inputChar === "r") setShowReasoning((s) => !s);
|
|
2031
2682
|
});
|
|
2032
2683
|
const updateAssistant = useCallback(
|
|
2033
2684
|
(id, patch) => {
|
|
@@ -2049,17 +2700,102 @@ function App({ initialCfg }) {
|
|
|
2049
2700
|
},
|
|
2050
2701
|
[]
|
|
2051
2702
|
);
|
|
2703
|
+
const runCompact = useCallback(async () => {
|
|
2704
|
+
if (!cfg) return;
|
|
2705
|
+
if (busy) {
|
|
2706
|
+
setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "can't compact while model is running" }]);
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
setBusy(true);
|
|
2710
|
+
setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "compacting conversation\u2026" }]);
|
|
2711
|
+
const controller = new AbortController();
|
|
2712
|
+
activeControllerRef.current = controller;
|
|
2713
|
+
try {
|
|
2714
|
+
const result = await compactMessages({
|
|
2715
|
+
accountId: cfg.accountId,
|
|
2716
|
+
apiToken: cfg.apiToken,
|
|
2717
|
+
model: cfg.model,
|
|
2718
|
+
messages: messagesRef.current,
|
|
2719
|
+
signal: controller.signal
|
|
2720
|
+
});
|
|
2721
|
+
if (result.replacedCount === 0) {
|
|
2722
|
+
setEvents((e) => [
|
|
2723
|
+
...e,
|
|
2724
|
+
{ kind: "info", key: mkKey(), text: "nothing to compact yet" }
|
|
2725
|
+
]);
|
|
2726
|
+
} else {
|
|
2727
|
+
messagesRef.current = result.newMessages;
|
|
2728
|
+
setEvents((e) => [
|
|
2729
|
+
...e,
|
|
2730
|
+
{
|
|
2731
|
+
kind: "info",
|
|
2732
|
+
key: mkKey(),
|
|
2733
|
+
text: `compacted ${result.replacedCount} messages into a summary`
|
|
2734
|
+
}
|
|
2735
|
+
]);
|
|
2736
|
+
await saveSessionSafe();
|
|
2737
|
+
}
|
|
2738
|
+
} catch (e) {
|
|
2739
|
+
if (e.name !== "AbortError") {
|
|
2740
|
+
setEvents((es) => [
|
|
2741
|
+
...es,
|
|
2742
|
+
{ kind: "error", key: mkKey(), text: `compact failed: ${e.message}` }
|
|
2743
|
+
]);
|
|
2744
|
+
}
|
|
2745
|
+
} finally {
|
|
2746
|
+
setBusy(false);
|
|
2747
|
+
activeControllerRef.current = null;
|
|
2748
|
+
}
|
|
2749
|
+
}, [cfg, busy, saveSessionSafe]);
|
|
2750
|
+
const openResumePicker = useCallback(async () => {
|
|
2751
|
+
const sessions = await listSessions(30);
|
|
2752
|
+
setResumeSessions(sessions);
|
|
2753
|
+
}, []);
|
|
2754
|
+
const handleResumePick = useCallback(
|
|
2755
|
+
async (picked) => {
|
|
2756
|
+
setResumeSessions(null);
|
|
2757
|
+
if (!picked) return;
|
|
2758
|
+
try {
|
|
2759
|
+
const file = await loadSession(picked.filePath);
|
|
2760
|
+
messagesRef.current = file.messages;
|
|
2761
|
+
sessionIdRef.current = file.id;
|
|
2762
|
+
setEvents([
|
|
2763
|
+
{
|
|
2764
|
+
kind: "info",
|
|
2765
|
+
key: mkKey(),
|
|
2766
|
+
text: `resumed session ${picked.id} (${picked.messageCount} msgs)`
|
|
2767
|
+
}
|
|
2768
|
+
]);
|
|
2769
|
+
const userMsgs = file.messages.filter((m) => m.role === "user" && typeof m.content === "string").map((m) => m.content);
|
|
2770
|
+
if (userMsgs.length > 0) setHistory(userMsgs);
|
|
2771
|
+
setUsage(null);
|
|
2772
|
+
} catch (e) {
|
|
2773
|
+
setEvents((es) => [
|
|
2774
|
+
...es,
|
|
2775
|
+
{ kind: "error", key: mkKey(), text: `failed to load session: ${e.message}` }
|
|
2776
|
+
]);
|
|
2777
|
+
}
|
|
2778
|
+
},
|
|
2779
|
+
[]
|
|
2780
|
+
);
|
|
2052
2781
|
const handleSlash = useCallback(
|
|
2053
2782
|
(cmd) => {
|
|
2054
|
-
const
|
|
2783
|
+
const raw = cmd.trim();
|
|
2784
|
+
const [head, ...rest] = raw.split(/\s+/);
|
|
2785
|
+
const c = (head ?? "").toLowerCase();
|
|
2786
|
+
const arg = rest.join(" ").trim().toLowerCase();
|
|
2055
2787
|
if (c === "/exit" || c === "/quit") {
|
|
2056
2788
|
exit();
|
|
2057
2789
|
return true;
|
|
2058
2790
|
}
|
|
2059
2791
|
if (c === "/clear") {
|
|
2060
2792
|
messagesRef.current = [messagesRef.current[0]];
|
|
2793
|
+
sessionIdRef.current = null;
|
|
2061
2794
|
setEvents([{ kind: "info", key: mkKey(), text: "conversation cleared" }]);
|
|
2062
2795
|
setUsage(null);
|
|
2796
|
+
setTasks([]);
|
|
2797
|
+
setTasksStartedAt(null);
|
|
2798
|
+
setTasksStartTokens(0);
|
|
2063
2799
|
return true;
|
|
2064
2800
|
}
|
|
2065
2801
|
if (c === "/reasoning") {
|
|
@@ -2091,6 +2827,114 @@ function App({ initialCfg }) {
|
|
|
2091
2827
|
]);
|
|
2092
2828
|
return true;
|
|
2093
2829
|
}
|
|
2830
|
+
if (c === "/thinking" || c === "/effort") {
|
|
2831
|
+
if (!arg) {
|
|
2832
|
+
setEvents((e) => [
|
|
2833
|
+
...e,
|
|
2834
|
+
{
|
|
2835
|
+
kind: "info",
|
|
2836
|
+
key: mkKey(),
|
|
2837
|
+
text: `current: ${effort} \xB7 ${EFFORT_DESCRIPTIONS[effort]}
|
|
2838
|
+
use: /thinking low | medium | high`
|
|
2839
|
+
}
|
|
2840
|
+
]);
|
|
2841
|
+
return true;
|
|
2842
|
+
}
|
|
2843
|
+
if (arg === "low" || arg === "medium" || arg === "high") {
|
|
2844
|
+
setEffort(arg);
|
|
2845
|
+
if (cfg) void saveConfig({ ...cfg, reasoningEffort: arg }).catch(() => {
|
|
2846
|
+
});
|
|
2847
|
+
setEvents((e) => [
|
|
2848
|
+
...e,
|
|
2849
|
+
{
|
|
2850
|
+
kind: "info",
|
|
2851
|
+
key: mkKey(),
|
|
2852
|
+
text: `thinking: ${arg} \xB7 ${EFFORT_DESCRIPTIONS[arg]}`
|
|
2853
|
+
}
|
|
2854
|
+
]);
|
|
2855
|
+
return true;
|
|
2856
|
+
}
|
|
2857
|
+
setEvents((e) => [
|
|
2858
|
+
...e,
|
|
2859
|
+
{ kind: "info", key: mkKey(), text: "usage: /thinking low | medium | high" }
|
|
2860
|
+
]);
|
|
2861
|
+
return true;
|
|
2862
|
+
}
|
|
2863
|
+
if (c === "/theme") {
|
|
2864
|
+
if (!arg) {
|
|
2865
|
+
setEvents((e) => [
|
|
2866
|
+
...e,
|
|
2867
|
+
{
|
|
2868
|
+
kind: "info",
|
|
2869
|
+
key: mkKey(),
|
|
2870
|
+
text: `current: ${theme.name} \xB7 available: ${themeNames().join(", ")}`
|
|
2871
|
+
}
|
|
2872
|
+
]);
|
|
2873
|
+
return true;
|
|
2874
|
+
}
|
|
2875
|
+
const next = resolveTheme(arg);
|
|
2876
|
+
if (next.name !== arg) {
|
|
2877
|
+
setEvents((e) => [
|
|
2878
|
+
...e,
|
|
2879
|
+
{ kind: "info", key: mkKey(), text: `unknown theme "${arg}" \u2014 available: ${themeNames().join(", ")}` }
|
|
2880
|
+
]);
|
|
2881
|
+
return true;
|
|
2882
|
+
}
|
|
2883
|
+
setTheme(next);
|
|
2884
|
+
setCfg((c2) => c2 ? { ...c2, theme: next.name } : c2);
|
|
2885
|
+
if (cfg) void saveConfig({ ...cfg, theme: next.name }).catch(() => {
|
|
2886
|
+
});
|
|
2887
|
+
setEvents((e) => [
|
|
2888
|
+
...e,
|
|
2889
|
+
{ kind: "info", key: mkKey(), text: `theme: ${next.label}` }
|
|
2890
|
+
]);
|
|
2891
|
+
return true;
|
|
2892
|
+
}
|
|
2893
|
+
if (c === "/mode") {
|
|
2894
|
+
if (!arg) {
|
|
2895
|
+
setEvents((e) => [
|
|
2896
|
+
...e,
|
|
2897
|
+
{ kind: "info", key: mkKey(), text: `current mode: ${mode} \xB7 use /mode edit|plan|auto or shift+tab` }
|
|
2898
|
+
]);
|
|
2899
|
+
return true;
|
|
2900
|
+
}
|
|
2901
|
+
if (arg === "edit" || arg === "plan" || arg === "auto") {
|
|
2902
|
+
setMode(arg);
|
|
2903
|
+
setEvents((e) => [
|
|
2904
|
+
...e,
|
|
2905
|
+
{ kind: "info", key: mkKey(), text: `mode: ${arg}` }
|
|
2906
|
+
]);
|
|
2907
|
+
return true;
|
|
2908
|
+
}
|
|
2909
|
+
setEvents((e) => [
|
|
2910
|
+
...e,
|
|
2911
|
+
{ kind: "info", key: mkKey(), text: "usage: /mode edit|plan|auto" }
|
|
2912
|
+
]);
|
|
2913
|
+
return true;
|
|
2914
|
+
}
|
|
2915
|
+
if (c === "/plan") {
|
|
2916
|
+
setMode("plan");
|
|
2917
|
+
setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "mode: plan" }]);
|
|
2918
|
+
return true;
|
|
2919
|
+
}
|
|
2920
|
+
if (c === "/auto") {
|
|
2921
|
+
setMode("auto");
|
|
2922
|
+
setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "mode: auto" }]);
|
|
2923
|
+
return true;
|
|
2924
|
+
}
|
|
2925
|
+
if (c === "/edit") {
|
|
2926
|
+
setMode("edit");
|
|
2927
|
+
setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "mode: edit" }]);
|
|
2928
|
+
return true;
|
|
2929
|
+
}
|
|
2930
|
+
if (c === "/resume") {
|
|
2931
|
+
void openResumePicker();
|
|
2932
|
+
return true;
|
|
2933
|
+
}
|
|
2934
|
+
if (c === "/compact") {
|
|
2935
|
+
void runCompact();
|
|
2936
|
+
return true;
|
|
2937
|
+
}
|
|
2094
2938
|
if (c === "/update") {
|
|
2095
2939
|
if (updateInfo?.hasUpdate) {
|
|
2096
2940
|
setEvents((e) => [
|
|
@@ -2146,22 +2990,23 @@ function App({ initialCfg }) {
|
|
|
2146
2990
|
{
|
|
2147
2991
|
kind: "info",
|
|
2148
2992
|
key: mkKey(),
|
|
2149
|
-
text: "commands
|
|
2993
|
+
text: "commands:\n /mode edit|plan|auto switch mode (or shift+tab to cycle)\n /plan /auto /edit shortcuts for /mode\n /thinking low|med|high set reasoning effort (quality vs speed)\n /theme NAME dark, light, high-contrast\n /resume pick a past conversation\n /compact summarize old turns to free context\n /reasoning toggle show/hide model reasoning\n /clear clear current conversation\n /cost /model /update /logout /help /exit\nkeys: ctrl-c interrupt/exit \xB7 ctrl-r toggle reasoning \xB7 ctrl-o toggle verbose output \xB7 shift+tab cycle mode \xB7 \u2191/\u2193 history"
|
|
2150
2994
|
}
|
|
2151
2995
|
]);
|
|
2152
2996
|
return true;
|
|
2153
2997
|
}
|
|
2154
2998
|
return false;
|
|
2155
2999
|
},
|
|
2156
|
-
[cfg, exit, usage, updateInfo]
|
|
3000
|
+
[cfg, exit, usage, updateInfo, effort, theme, mode, openResumePicker, runCompact]
|
|
2157
3001
|
);
|
|
2158
3002
|
const processMessage = useCallback(
|
|
2159
|
-
async (text) => {
|
|
3003
|
+
async (text, displayText) => {
|
|
2160
3004
|
if (!cfg) return;
|
|
2161
3005
|
const trimmed = text.trim();
|
|
2162
3006
|
if (!trimmed) return;
|
|
2163
3007
|
if (trimmed.startsWith("/") && handleSlash(trimmed)) return;
|
|
2164
|
-
|
|
3008
|
+
const display = displayText?.trim() || trimmed;
|
|
3009
|
+
setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display }]);
|
|
2165
3010
|
messagesRef.current.push({ role: "user", content: trimmed });
|
|
2166
3011
|
setBusy(true);
|
|
2167
3012
|
const controller = new AbortController();
|
|
@@ -2176,6 +3021,7 @@ function App({ initialCfg }) {
|
|
|
2176
3021
|
executor: executorRef.current,
|
|
2177
3022
|
cwd: process.cwd(),
|
|
2178
3023
|
signal: controller.signal,
|
|
3024
|
+
reasoningEffort: effortRef.current,
|
|
2179
3025
|
callbacks: {
|
|
2180
3026
|
onAssistantStart: () => {
|
|
2181
3027
|
const id = nextAssistantId++;
|
|
@@ -2226,11 +3072,41 @@ function App({ initialCfg }) {
|
|
|
2226
3072
|
});
|
|
2227
3073
|
},
|
|
2228
3074
|
onUsage: (u) => setUsage(u),
|
|
3075
|
+
onTasks: (nextTasks) => {
|
|
3076
|
+
setTasks((prev) => {
|
|
3077
|
+
if (prev.length === 0 && nextTasks.length > 0) {
|
|
3078
|
+
setTasksStartedAt(Date.now());
|
|
3079
|
+
setTasksStartTokens(usage?.prompt_tokens ?? 0);
|
|
3080
|
+
}
|
|
3081
|
+
if (nextTasks.length === 0) {
|
|
3082
|
+
setTasksStartedAt(null);
|
|
3083
|
+
setTasksStartTokens(0);
|
|
3084
|
+
}
|
|
3085
|
+
return nextTasks;
|
|
3086
|
+
});
|
|
3087
|
+
},
|
|
2229
3088
|
askPermission: (req) => new Promise((resolve2) => {
|
|
3089
|
+
if (modeRef.current === "auto") {
|
|
3090
|
+
resolve2("allow");
|
|
3091
|
+
return;
|
|
3092
|
+
}
|
|
3093
|
+
if (modeRef.current === "plan" && isBlockedInPlanMode(req.tool.name)) {
|
|
3094
|
+
setEvents((e) => [
|
|
3095
|
+
...e,
|
|
3096
|
+
{
|
|
3097
|
+
kind: "info",
|
|
3098
|
+
key: mkKey(),
|
|
3099
|
+
text: `plan mode blocked ${req.tool.name}; exit plan mode to execute`
|
|
3100
|
+
}
|
|
3101
|
+
]);
|
|
3102
|
+
resolve2("deny");
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
2230
3105
|
setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
|
|
2231
3106
|
})
|
|
2232
3107
|
}
|
|
2233
3108
|
});
|
|
3109
|
+
await saveSessionSafe();
|
|
2234
3110
|
} catch (e) {
|
|
2235
3111
|
if (e.name === "AbortError") {
|
|
2236
3112
|
setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
|
|
@@ -2246,36 +3122,36 @@ function App({ initialCfg }) {
|
|
|
2246
3122
|
activeControllerRef.current = null;
|
|
2247
3123
|
}
|
|
2248
3124
|
},
|
|
2249
|
-
[cfg, handleSlash, updateAssistant, updateTool]
|
|
3125
|
+
[cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe]
|
|
2250
3126
|
);
|
|
2251
|
-
|
|
3127
|
+
useEffect3(() => {
|
|
2252
3128
|
if (!busy && queue.length > 0) {
|
|
2253
3129
|
const next = queue[0];
|
|
2254
3130
|
setQueue((q) => q.slice(1));
|
|
2255
|
-
processMessage(next);
|
|
3131
|
+
processMessage(next.full, next.display);
|
|
2256
3132
|
}
|
|
2257
3133
|
}, [busy, queue, processMessage]);
|
|
2258
3134
|
const submit = useCallback(
|
|
2259
|
-
(
|
|
2260
|
-
const
|
|
2261
|
-
if (!
|
|
3135
|
+
(full, display) => {
|
|
3136
|
+
const trimmedFull = full.trim();
|
|
3137
|
+
if (!trimmedFull) return;
|
|
3138
|
+
const trimmedDisplay = (display ?? full).trim() || trimmedFull;
|
|
3139
|
+
const historyEntry = trimmedDisplay;
|
|
2262
3140
|
if (busy) {
|
|
2263
|
-
setQueue((q) => [...q,
|
|
2264
|
-
setHistory((h) => h.length > 0 && h[h.length - 1] ===
|
|
3141
|
+
setQueue((q) => [...q, { full: trimmedFull, display: trimmedDisplay }]);
|
|
3142
|
+
setHistory((h) => h.length > 0 && h[h.length - 1] === historyEntry ? h : [...h, historyEntry]);
|
|
2265
3143
|
setInput("");
|
|
2266
3144
|
setHistoryIndex(-1);
|
|
2267
3145
|
return;
|
|
2268
3146
|
}
|
|
2269
|
-
setHistory((h) => h.length > 0 && h[h.length - 1] ===
|
|
3147
|
+
setHistory((h) => h.length > 0 && h[h.length - 1] === historyEntry ? h : [...h, historyEntry]);
|
|
2270
3148
|
setInput("");
|
|
2271
3149
|
setHistoryIndex(-1);
|
|
2272
|
-
processMessage(
|
|
3150
|
+
processMessage(trimmedFull, trimmedDisplay !== trimmedFull ? trimmedDisplay : void 0);
|
|
2273
3151
|
},
|
|
2274
3152
|
[busy, processMessage]
|
|
2275
3153
|
);
|
|
2276
|
-
|
|
2277
|
-
}, [events]);
|
|
2278
|
-
useEffect2(() => {
|
|
3154
|
+
useEffect3(() => {
|
|
2279
3155
|
checkForUpdate().then((result) => {
|
|
2280
3156
|
if (result.hasUpdate) {
|
|
2281
3157
|
setUpdateInfo(result);
|
|
@@ -2290,8 +3166,24 @@ function App({ initialCfg }) {
|
|
|
2290
3166
|
}
|
|
2291
3167
|
});
|
|
2292
3168
|
}, []);
|
|
3169
|
+
useEffect3(() => {
|
|
3170
|
+
if (usage && usage.prompt_tokens / CONTEXT_LIMIT >= AUTO_COMPACT_SUGGEST_PCT) {
|
|
3171
|
+
setEvents((e) => {
|
|
3172
|
+
const last = e[e.length - 1];
|
|
3173
|
+
if (last?.kind === "info" && last.text.startsWith("context ")) return e;
|
|
3174
|
+
return [
|
|
3175
|
+
...e,
|
|
3176
|
+
{
|
|
3177
|
+
kind: "info",
|
|
3178
|
+
key: mkKey(),
|
|
3179
|
+
text: `context ${Math.round(usage.prompt_tokens / CONTEXT_LIMIT * 100)}% full \u2014 run /compact to summarize older turns`
|
|
3180
|
+
}
|
|
3181
|
+
];
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
}, [usage]);
|
|
2293
3185
|
if (!cfg) {
|
|
2294
|
-
return /* @__PURE__ */
|
|
3186
|
+
return /* @__PURE__ */ jsx10(
|
|
2295
3187
|
Onboarding,
|
|
2296
3188
|
{
|
|
2297
3189
|
onDone: (newCfg) => {
|
|
@@ -2304,43 +3196,62 @@ function App({ initialCfg }) {
|
|
|
2304
3196
|
}
|
|
2305
3197
|
);
|
|
2306
3198
|
}
|
|
2307
|
-
|
|
2308
|
-
/* @__PURE__ */
|
|
2309
|
-
|
|
3199
|
+
if (resumeSessions !== null) {
|
|
3200
|
+
return /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx10(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
|
|
3201
|
+
}
|
|
3202
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3203
|
+
/* @__PURE__ */ jsx10(ChatView, { events, showReasoning, theme, verbose }),
|
|
3204
|
+
perm ? /* @__PURE__ */ jsx10(
|
|
2310
3205
|
PermissionModal,
|
|
2311
3206
|
{
|
|
2312
3207
|
tool: perm.tool,
|
|
2313
3208
|
args: perm.args,
|
|
3209
|
+
theme,
|
|
2314
3210
|
onDecide: (d) => {
|
|
2315
3211
|
perm.resolve(d);
|
|
2316
3212
|
setPerm(null);
|
|
2317
3213
|
}
|
|
2318
3214
|
}
|
|
2319
|
-
) : /* @__PURE__ */
|
|
2320
|
-
|
|
3215
|
+
) : /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, children: [
|
|
3216
|
+
tasks.length > 0 && /* @__PURE__ */ jsx10(
|
|
3217
|
+
TaskList,
|
|
3218
|
+
{
|
|
3219
|
+
tasks,
|
|
3220
|
+
theme,
|
|
3221
|
+
startedAt: tasksStartedAt,
|
|
3222
|
+
tokensDelta: Math.max(0, (usage?.prompt_tokens ?? 0) - tasksStartTokens)
|
|
3223
|
+
}
|
|
3224
|
+
),
|
|
3225
|
+
queue.length > 0 && /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", marginBottom: 1, children: queue.map((q, i) => /* @__PURE__ */ jsxs9(Text10, { color: theme.queue.color, dimColor: theme.queue.dim, children: [
|
|
2321
3226
|
"\u23F3 ",
|
|
2322
|
-
q
|
|
3227
|
+
q.display
|
|
2323
3228
|
] }, `queue_${i}`)) }),
|
|
2324
|
-
/* @__PURE__ */
|
|
3229
|
+
/* @__PURE__ */ jsx10(
|
|
2325
3230
|
StatusBar,
|
|
2326
3231
|
{
|
|
2327
3232
|
model: cfg.model,
|
|
2328
3233
|
usage,
|
|
2329
3234
|
thinking: busy,
|
|
2330
|
-
hint: busy ? "ctrl-c to interrupt" : "enter to send \xB7 /help"
|
|
3235
|
+
hint: busy ? "ctrl-c to interrupt \xB7 type to queue next" : "enter to send \xB7 /help \xB7 shift+tab cycles mode",
|
|
3236
|
+
theme,
|
|
3237
|
+
mode,
|
|
3238
|
+
effort,
|
|
3239
|
+
contextLimit: CONTEXT_LIMIT
|
|
2331
3240
|
}
|
|
2332
3241
|
),
|
|
2333
|
-
|
|
2334
|
-
/* @__PURE__ */
|
|
2335
|
-
/* @__PURE__ */
|
|
2336
|
-
] })
|
|
2337
|
-
|
|
2338
|
-
/* @__PURE__ */
|
|
3242
|
+
busy && /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3243
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.spinner, children: /* @__PURE__ */ jsx10(Spinner2, { type: "dots" }) }),
|
|
3244
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.info.color, dimColor: theme.info.dim, children: " working\u2026" })
|
|
3245
|
+
] }),
|
|
3246
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3247
|
+
/* @__PURE__ */ jsx10(Text10, { color: theme.user, children: busy ? "\u23F3 " : "\u203A " }),
|
|
3248
|
+
/* @__PURE__ */ jsx10(
|
|
2339
3249
|
CustomTextInput,
|
|
2340
3250
|
{
|
|
2341
3251
|
value: input,
|
|
2342
3252
|
onChange: setInput,
|
|
2343
3253
|
onSubmit: submit,
|
|
3254
|
+
enablePaste: true,
|
|
2344
3255
|
onHistoryUp: () => {
|
|
2345
3256
|
if (history.length === 0) return;
|
|
2346
3257
|
if (historyIndex === -1) {
|
|
@@ -2367,7 +3278,7 @@ function App({ initialCfg }) {
|
|
|
2367
3278
|
},
|
|
2368
3279
|
onClearQueueItem: (text) => {
|
|
2369
3280
|
setQueue((q) => {
|
|
2370
|
-
const idx = q.
|
|
3281
|
+
const idx = q.findIndex((item) => item.display === text);
|
|
2371
3282
|
if (idx >= 0) {
|
|
2372
3283
|
const next = [...q];
|
|
2373
3284
|
next.splice(idx, 1);
|
|
@@ -2378,31 +3289,44 @@ function App({ initialCfg }) {
|
|
|
2378
3289
|
}
|
|
2379
3290
|
}
|
|
2380
3291
|
)
|
|
2381
|
-
] })
|
|
3292
|
+
] })
|
|
2382
3293
|
] })
|
|
2383
3294
|
] });
|
|
2384
3295
|
}
|
|
2385
3296
|
async function renderApp(cfg) {
|
|
2386
|
-
const instance = render(/* @__PURE__ */
|
|
3297
|
+
const instance = render(/* @__PURE__ */ jsx10(App, { initialCfg: cfg }));
|
|
2387
3298
|
await instance.waitUntilExit();
|
|
2388
3299
|
}
|
|
2389
|
-
var nextAssistantId, nextKey, mkKey;
|
|
3300
|
+
var CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, nextAssistantId, nextKey, mkKey, EFFORT_DESCRIPTIONS;
|
|
2390
3301
|
var init_app = __esm({
|
|
2391
3302
|
"src/app.tsx"() {
|
|
2392
3303
|
"use strict";
|
|
2393
3304
|
init_loop();
|
|
2394
3305
|
init_system_prompt();
|
|
3306
|
+
init_compact();
|
|
2395
3307
|
init_executor();
|
|
2396
3308
|
init_chat();
|
|
2397
3309
|
init_status();
|
|
2398
3310
|
init_permission();
|
|
3311
|
+
init_resume_picker();
|
|
3312
|
+
init_task_list();
|
|
2399
3313
|
init_text_input();
|
|
2400
3314
|
init_update_check();
|
|
2401
3315
|
init_onboarding();
|
|
2402
3316
|
init_config();
|
|
3317
|
+
init_theme();
|
|
3318
|
+
init_mode();
|
|
3319
|
+
init_sessions();
|
|
3320
|
+
CONTEXT_LIMIT = 262e3;
|
|
3321
|
+
AUTO_COMPACT_SUGGEST_PCT = 0.8;
|
|
2403
3322
|
nextAssistantId = 1;
|
|
2404
3323
|
nextKey = 1;
|
|
2405
3324
|
mkKey = () => `evt_${nextKey++}`;
|
|
3325
|
+
EFFORT_DESCRIPTIONS = {
|
|
3326
|
+
low: "low \u2014 fastest; lightest reasoning. Best for simple Q&A, small edits, quick coordination.",
|
|
3327
|
+
medium: "medium \u2014 balanced (default). Solid quality on most edits, fast on trivial prompts.",
|
|
3328
|
+
high: "high \u2014 deepest reasoning; slowest. Best for complex debugging, architecture, multi-file refactors."
|
|
3329
|
+
};
|
|
2406
3330
|
}
|
|
2407
3331
|
});
|
|
2408
3332
|
|