kimiflare 0.6.0 → 0.7.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 +2 -2
- package/dist/index.js +133 -73
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# kimiflare
|
|
2
2
|
|
|
3
|
-
A terminal coding agent powered by **[Kimi-K2.6](https://developers.cloudflare.com/workers-ai/models/kimi-k2.6/)** on Cloudflare Workers AI.
|
|
3
|
+
A terminal coding agent powered by **[Kimi-K2.6](https://developers.cloudflare.com/workers-ai/models/kimi-k2.6/)** on Cloudflare Workers AI. Moonshot's 1T-parameter open-source model runs directly on your Cloudflare account. You bring the token, your traffic goes straight to Cloudflare.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
$ kimiflare
|
|
@@ -180,7 +180,7 @@ All tool calls show inline; mutating ones require per-call approval the first ti
|
|
|
180
180
|
@cf/moonshotai/kimi-k2.6
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
Direct `fetch` to Workers AI, OpenAI-compatible `messages` + `tools` payload, SSE stream with reasoning + content + tool-call deltas accumulated by index.
|
|
184
184
|
|
|
185
185
|
## Development
|
|
186
186
|
|
package/dist/index.js
CHANGED
|
@@ -1465,6 +1465,7 @@ var init_markdown = __esm({
|
|
|
1465
1465
|
|
|
1466
1466
|
// src/ui/chat.tsx
|
|
1467
1467
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
1468
|
+
import Spinner2 from "ink-spinner";
|
|
1468
1469
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1469
1470
|
function ChatView({ events, showReasoning, theme, verbose }) {
|
|
1470
1471
|
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: events.map((e, i) => {
|
|
@@ -1498,7 +1499,8 @@ function EventView({
|
|
|
1498
1499
|
" ",
|
|
1499
1500
|
evt.reasoning.length > 400 ? evt.reasoning.slice(0, 400) + "\u2026" : evt.reasoning
|
|
1500
1501
|
] }) }) : null,
|
|
1501
|
-
evt.text ? /* @__PURE__ */ jsx4(MD, { text: evt.text, theme }) : null
|
|
1502
|
+
evt.text ? /* @__PURE__ */ jsx4(MD, { text: evt.text, theme }) : null,
|
|
1503
|
+
evt.streaming && /* @__PURE__ */ jsx4(Text4, { color: theme.spinner, children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) })
|
|
1502
1504
|
] });
|
|
1503
1505
|
}
|
|
1504
1506
|
if (evt.kind === "tool") {
|
|
@@ -1524,26 +1526,21 @@ var init_chat = __esm({
|
|
|
1524
1526
|
});
|
|
1525
1527
|
|
|
1526
1528
|
// src/ui/status.tsx
|
|
1529
|
+
import { useEffect, useState } from "react";
|
|
1527
1530
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
1531
|
+
import Spinner3 from "ink-spinner";
|
|
1528
1532
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1529
|
-
function StatusBar({ model, usage, thinking, theme, mode, effort, contextLimit }) {
|
|
1533
|
+
function StatusBar({ model, usage, thinking, turnStartedAt, theme, mode, effort, contextLimit }) {
|
|
1534
|
+
const [now, setNow] = useState(Date.now());
|
|
1530
1535
|
const modeColor = mode === "plan" ? theme.modeBadge.plan : mode === "auto" ? theme.modeBadge.auto : theme.modeBadge.edit;
|
|
1531
1536
|
const warn = usage && usage.prompt_tokens / contextLimit >= 0.8;
|
|
1537
|
+
useEffect(() => {
|
|
1538
|
+
if (!thinking || turnStartedAt === null) return;
|
|
1539
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
1540
|
+
return () => clearInterval(id);
|
|
1541
|
+
}, [thinking, turnStartedAt]);
|
|
1542
|
+
const elapsed = turnStartedAt !== null ? formatElapsed(now - turnStartedAt) : null;
|
|
1532
1543
|
const leftParts = [`${shortModel(model)}`, effort];
|
|
1533
|
-
if (thinking) leftParts.push("thinking\u2026");
|
|
1534
|
-
const rightParts = [];
|
|
1535
|
-
if (usage) {
|
|
1536
|
-
const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
1537
|
-
const uncachedIn = usage.prompt_tokens - cached;
|
|
1538
|
-
const cost = uncachedIn * PRICE_IN_PER_M / 1e6 + cached * PRICE_IN_CACHED_PER_M / 1e6 + usage.completion_tokens * PRICE_OUT_PER_M / 1e6;
|
|
1539
|
-
const pct = Math.round(usage.prompt_tokens / contextLimit * 100);
|
|
1540
|
-
rightParts.push(
|
|
1541
|
-
`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`,
|
|
1542
|
-
`out ${usage.completion_tokens}`,
|
|
1543
|
-
`ctx ${pct}%`,
|
|
1544
|
-
`${cost.toFixed(5)}`
|
|
1545
|
-
);
|
|
1546
|
-
}
|
|
1547
1544
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1548
1545
|
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1549
1546
|
/* @__PURE__ */ jsxs5(Text5, { color: modeColor, bold: true, children: [
|
|
@@ -1552,10 +1549,18 @@ function StatusBar({ model, usage, thinking, theme, mode, effort, contextLimit }
|
|
|
1552
1549
|
"]"
|
|
1553
1550
|
] }),
|
|
1554
1551
|
/* @__PURE__ */ jsx5(Text5, { children: " " }),
|
|
1555
|
-
/* @__PURE__ */
|
|
1552
|
+
thinking ? /* @__PURE__ */ jsxs5(Text5, { color: theme.spinner, children: [
|
|
1553
|
+
/* @__PURE__ */ jsx5(Spinner3, { type: "dots" }),
|
|
1554
|
+
" ",
|
|
1555
|
+
"thinking",
|
|
1556
|
+
elapsed ? ` \xB7 ${elapsed}` : ""
|
|
1557
|
+
] }) : /* @__PURE__ */ jsxs5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children: [
|
|
1558
|
+
leftParts.join(" \xB7 "),
|
|
1559
|
+
" \xB7 ready"
|
|
1560
|
+
] })
|
|
1556
1561
|
] }),
|
|
1557
|
-
|
|
1558
|
-
/* @__PURE__ */ jsx5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children:
|
|
1562
|
+
usage && /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1563
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children: buildRightParts(usage, contextLimit).join(" \xB7 ") }),
|
|
1559
1564
|
warn ? /* @__PURE__ */ jsxs5(Text5, { color: theme.warn, bold: true, children: [
|
|
1560
1565
|
" \xB7 ",
|
|
1561
1566
|
"/compact recommended"
|
|
@@ -1563,10 +1568,29 @@ function StatusBar({ model, usage, thinking, theme, mode, effort, contextLimit }
|
|
|
1563
1568
|
] })
|
|
1564
1569
|
] });
|
|
1565
1570
|
}
|
|
1571
|
+
function buildRightParts(usage, contextLimit) {
|
|
1572
|
+
const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
1573
|
+
const uncachedIn = usage.prompt_tokens - cached;
|
|
1574
|
+
const cost = uncachedIn * PRICE_IN_PER_M / 1e6 + cached * PRICE_IN_CACHED_PER_M / 1e6 + usage.completion_tokens * PRICE_OUT_PER_M / 1e6;
|
|
1575
|
+
const pct = Math.round(usage.prompt_tokens / contextLimit * 100);
|
|
1576
|
+
return [
|
|
1577
|
+
`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`,
|
|
1578
|
+
`out ${usage.completion_tokens}`,
|
|
1579
|
+
`ctx ${pct}%`,
|
|
1580
|
+
`${cost.toFixed(5)}`
|
|
1581
|
+
];
|
|
1582
|
+
}
|
|
1566
1583
|
function shortModel(m) {
|
|
1567
1584
|
const last = m.split("/").at(-1) ?? m;
|
|
1568
1585
|
return last;
|
|
1569
1586
|
}
|
|
1587
|
+
function formatElapsed(ms) {
|
|
1588
|
+
const total = Math.floor(ms / 1e3);
|
|
1589
|
+
const m = Math.floor(total / 60);
|
|
1590
|
+
const s = total % 60;
|
|
1591
|
+
if (m === 0) return `${s}s`;
|
|
1592
|
+
return `${m}m ${s}s`;
|
|
1593
|
+
}
|
|
1570
1594
|
var PRICE_IN_PER_M, PRICE_IN_CACHED_PER_M, PRICE_OUT_PER_M;
|
|
1571
1595
|
var init_status = __esm({
|
|
1572
1596
|
"src/ui/status.tsx"() {
|
|
@@ -1671,12 +1695,13 @@ var init_resume_picker = __esm({
|
|
|
1671
1695
|
});
|
|
1672
1696
|
|
|
1673
1697
|
// src/ui/task-list.tsx
|
|
1674
|
-
import { useEffect, useState } from "react";
|
|
1698
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
1675
1699
|
import { Box as Box8, Text as Text8 } from "ink";
|
|
1700
|
+
import Spinner4 from "ink-spinner";
|
|
1676
1701
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1677
1702
|
function TaskList({ tasks, theme, startedAt, tokensDelta }) {
|
|
1678
|
-
const [now, setNow] =
|
|
1679
|
-
|
|
1703
|
+
const [now, setNow] = useState2(Date.now());
|
|
1704
|
+
useEffect2(() => {
|
|
1680
1705
|
if (startedAt === null) return;
|
|
1681
1706
|
const allDone2 = tasks.length > 0 && tasks.every((t) => t.status === "completed");
|
|
1682
1707
|
if (allDone2) return;
|
|
@@ -1689,7 +1714,7 @@ function TaskList({ tasks, theme, startedAt, tokensDelta }) {
|
|
|
1689
1714
|
const total = tasks.length;
|
|
1690
1715
|
const allDone = done === total;
|
|
1691
1716
|
const header = active ? active.title : allDone ? `${total} tasks done` : `${done}/${total}`;
|
|
1692
|
-
const elapsed = startedAt ?
|
|
1717
|
+
const elapsed = startedAt ? formatElapsed2(now - startedAt) : null;
|
|
1693
1718
|
const headerStats = [elapsed, tokensDelta > 0 ? `\u2191 ${formatTokens(tokensDelta)} tokens` : null].filter(Boolean).join(" \xB7 ");
|
|
1694
1719
|
const visibleTasks = tasks.slice(0, MAX_VISIBLE);
|
|
1695
1720
|
const hiddenPending = Math.max(0, tasks.length - visibleTasks.length);
|
|
@@ -1723,7 +1748,8 @@ function TaskRow({ task, theme }) {
|
|
|
1723
1748
|
if (task.status === "in_progress") {
|
|
1724
1749
|
return /* @__PURE__ */ jsxs8(Text8, { color: theme.accent, bold: true, children: [
|
|
1725
1750
|
" ",
|
|
1726
|
-
"
|
|
1751
|
+
/* @__PURE__ */ jsx8(Spinner4, { type: "dots" }),
|
|
1752
|
+
" ",
|
|
1727
1753
|
task.title
|
|
1728
1754
|
] });
|
|
1729
1755
|
}
|
|
@@ -1733,7 +1759,7 @@ function TaskRow({ task, theme }) {
|
|
|
1733
1759
|
task.title
|
|
1734
1760
|
] });
|
|
1735
1761
|
}
|
|
1736
|
-
function
|
|
1762
|
+
function formatElapsed2(ms) {
|
|
1737
1763
|
const total = Math.floor(ms / 1e3);
|
|
1738
1764
|
const m = Math.floor(total / 60);
|
|
1739
1765
|
const s = total % 60;
|
|
@@ -2273,7 +2299,7 @@ var init_source = __esm({
|
|
|
2273
2299
|
});
|
|
2274
2300
|
|
|
2275
2301
|
// src/ui/text-input.tsx
|
|
2276
|
-
import { useState as
|
|
2302
|
+
import { useState as useState3, useEffect as useEffect3, useRef } from "react";
|
|
2277
2303
|
import { Text as Text9, useInput } from "ink";
|
|
2278
2304
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2279
2305
|
function shouldTreatAsPaste(input) {
|
|
@@ -2308,9 +2334,9 @@ function CustomTextInput({
|
|
|
2308
2334
|
mask,
|
|
2309
2335
|
enablePaste = false
|
|
2310
2336
|
}) {
|
|
2311
|
-
const [cursorOffset, setCursorOffset] =
|
|
2337
|
+
const [cursorOffset, setCursorOffset] = useState3(value.length);
|
|
2312
2338
|
const pastesRef = useRef(/* @__PURE__ */ new Map());
|
|
2313
|
-
|
|
2339
|
+
useEffect3(() => {
|
|
2314
2340
|
if (!focus) return;
|
|
2315
2341
|
setCursorOffset((prev) => prev > value.length ? value.length : prev);
|
|
2316
2342
|
}, [value, focus]);
|
|
@@ -2469,7 +2495,7 @@ var init_text_input = __esm({
|
|
|
2469
2495
|
"use strict";
|
|
2470
2496
|
init_source();
|
|
2471
2497
|
PASTE_CHAR_THRESHOLD = 200;
|
|
2472
|
-
PASTE_NEWLINE_THRESHOLD =
|
|
2498
|
+
PASTE_NEWLINE_THRESHOLD = 1;
|
|
2473
2499
|
}
|
|
2474
2500
|
});
|
|
2475
2501
|
|
|
@@ -2586,15 +2612,15 @@ var init_update_check = __esm({
|
|
|
2586
2612
|
});
|
|
2587
2613
|
|
|
2588
2614
|
// src/ui/onboarding.tsx
|
|
2589
|
-
import { useState as
|
|
2615
|
+
import { useState as useState4 } from "react";
|
|
2590
2616
|
import { Box as Box9, Text as Text10 } from "ink";
|
|
2591
2617
|
import { Fragment, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2592
2618
|
function Onboarding({ onDone }) {
|
|
2593
|
-
const [step, setStep] =
|
|
2594
|
-
const [accountId, setAccountId] =
|
|
2595
|
-
const [apiToken, setApiToken] =
|
|
2596
|
-
const [model, setModel] =
|
|
2597
|
-
const [savedPath, setSavedPath] =
|
|
2619
|
+
const [step, setStep] = useState4("accountId");
|
|
2620
|
+
const [accountId, setAccountId] = useState4("");
|
|
2621
|
+
const [apiToken, setApiToken] = useState4("");
|
|
2622
|
+
const [model, setModel] = useState4(DEFAULT_MODEL);
|
|
2623
|
+
const [savedPath, setSavedPath] = useState4(null);
|
|
2598
2624
|
const stepIndex = STEPS.indexOf(step) + 1;
|
|
2599
2625
|
const handleAccountIdSubmit = (value) => {
|
|
2600
2626
|
const trimmed = value.trim();
|
|
@@ -2917,7 +2943,7 @@ var app_exports = {};
|
|
|
2917
2943
|
__export(app_exports, {
|
|
2918
2944
|
renderApp: () => renderApp
|
|
2919
2945
|
});
|
|
2920
|
-
import { useState as
|
|
2946
|
+
import { useState as useState5, useRef as useRef2, useEffect as useEffect4, useCallback } from "react";
|
|
2921
2947
|
import { Box as Box11, Text as Text12, useApp, useInput as useInput2, render } from "ink";
|
|
2922
2948
|
import { existsSync } from "fs";
|
|
2923
2949
|
import { join as join5 } from "path";
|
|
@@ -2925,27 +2951,28 @@ import { unlink } from "fs/promises";
|
|
|
2925
2951
|
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2926
2952
|
function App({ initialCfg }) {
|
|
2927
2953
|
const { exit } = useApp();
|
|
2928
|
-
const [cfg, setCfg] =
|
|
2929
|
-
const [events, setEvents] =
|
|
2930
|
-
const [input, setInput] =
|
|
2931
|
-
const [busy, setBusy] =
|
|
2932
|
-
const [usage, setUsage] =
|
|
2933
|
-
const [showReasoning, setShowReasoning] =
|
|
2934
|
-
const [perm, setPerm] =
|
|
2935
|
-
const [queue, setQueue] =
|
|
2936
|
-
const [history, setHistory] =
|
|
2937
|
-
const [historyIndex, setHistoryIndex] =
|
|
2938
|
-
const [draftInput, setDraftInput] =
|
|
2939
|
-
const [mode, setMode] =
|
|
2940
|
-
const [effort, setEffort] =
|
|
2954
|
+
const [cfg, setCfg] = useState5(initialCfg);
|
|
2955
|
+
const [events, setEvents] = useState5([]);
|
|
2956
|
+
const [input, setInput] = useState5("");
|
|
2957
|
+
const [busy, setBusy] = useState5(false);
|
|
2958
|
+
const [usage, setUsage] = useState5(null);
|
|
2959
|
+
const [showReasoning, setShowReasoning] = useState5(false);
|
|
2960
|
+
const [perm, setPerm] = useState5(null);
|
|
2961
|
+
const [queue, setQueue] = useState5([]);
|
|
2962
|
+
const [history, setHistory] = useState5([]);
|
|
2963
|
+
const [historyIndex, setHistoryIndex] = useState5(-1);
|
|
2964
|
+
const [draftInput, setDraftInput] = useState5("");
|
|
2965
|
+
const [mode, setMode] = useState5("edit");
|
|
2966
|
+
const [effort, setEffort] = useState5(
|
|
2941
2967
|
initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
|
|
2942
2968
|
);
|
|
2943
|
-
const [theme, setTheme] =
|
|
2944
|
-
const [resumeSessions, setResumeSessions] =
|
|
2945
|
-
const [tasks, setTasks] =
|
|
2946
|
-
const [tasksStartedAt, setTasksStartedAt] =
|
|
2947
|
-
const [tasksStartTokens, setTasksStartTokens] =
|
|
2948
|
-
const [
|
|
2969
|
+
const [theme, setTheme] = useState5(resolveTheme(initialCfg?.theme));
|
|
2970
|
+
const [resumeSessions, setResumeSessions] = useState5(null);
|
|
2971
|
+
const [tasks, setTasks] = useState5([]);
|
|
2972
|
+
const [tasksStartedAt, setTasksStartedAt] = useState5(null);
|
|
2973
|
+
const [tasksStartTokens, setTasksStartTokens] = useState5(0);
|
|
2974
|
+
const [turnStartedAt, setTurnStartedAt] = useState5(null);
|
|
2975
|
+
const [verbose, setVerbose] = useState5(false);
|
|
2949
2976
|
const messagesRef = useRef2([
|
|
2950
2977
|
{
|
|
2951
2978
|
role: "system",
|
|
@@ -2965,7 +2992,35 @@ function App({ initialCfg }) {
|
|
|
2965
2992
|
const effortRef = useRef2(effort);
|
|
2966
2993
|
const tasksRef = useRef2([]);
|
|
2967
2994
|
const usageRef = useRef2(null);
|
|
2968
|
-
|
|
2995
|
+
const updateCheckedRef = useRef2(false);
|
|
2996
|
+
const compactSuggestedRef = useRef2(false);
|
|
2997
|
+
useEffect4(() => {
|
|
2998
|
+
if (!cfg || updateCheckedRef.current) return;
|
|
2999
|
+
updateCheckedRef.current = true;
|
|
3000
|
+
void checkForUpdate().then((result) => {
|
|
3001
|
+
if (result.hasUpdate) {
|
|
3002
|
+
setEvents((e) => [
|
|
3003
|
+
...e,
|
|
3004
|
+
{
|
|
3005
|
+
kind: "info",
|
|
3006
|
+
key: mkKey(),
|
|
3007
|
+
text: `update available: ${result.localVersion} \u2192 ${result.latestVersion}`
|
|
3008
|
+
}
|
|
3009
|
+
]);
|
|
3010
|
+
void isGitRepo().then((git) => {
|
|
3011
|
+
setEvents((e) => [
|
|
3012
|
+
...e,
|
|
3013
|
+
{
|
|
3014
|
+
kind: "info",
|
|
3015
|
+
key: mkKey(),
|
|
3016
|
+
text: git ? "run: git pull && npm install && npm run build then restart kimiflare" : "run: npm update -g kimiflare then restart"
|
|
3017
|
+
}
|
|
3018
|
+
]);
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
});
|
|
3022
|
+
}, [cfg]);
|
|
3023
|
+
useEffect4(() => {
|
|
2969
3024
|
modeRef.current = mode;
|
|
2970
3025
|
messagesRef.current[0] = {
|
|
2971
3026
|
role: "system",
|
|
@@ -2980,7 +3035,7 @@ function App({ initialCfg }) {
|
|
|
2980
3035
|
executorRef.current.clearSessionPermissions();
|
|
2981
3036
|
}
|
|
2982
3037
|
}, [mode, cfg?.model]);
|
|
2983
|
-
|
|
3038
|
+
useEffect4(() => {
|
|
2984
3039
|
effortRef.current = effort;
|
|
2985
3040
|
}, [effort]);
|
|
2986
3041
|
const saveSessionSafe = useCallback(async () => {
|
|
@@ -3053,7 +3108,7 @@ function App({ initialCfg }) {
|
|
|
3053
3108
|
return;
|
|
3054
3109
|
}
|
|
3055
3110
|
setBusy(true);
|
|
3056
|
-
|
|
3111
|
+
setTurnStartedAt(Date.now());
|
|
3057
3112
|
const controller = new AbortController();
|
|
3058
3113
|
activeControllerRef.current = controller;
|
|
3059
3114
|
try {
|
|
@@ -3090,6 +3145,7 @@ function App({ initialCfg }) {
|
|
|
3090
3145
|
}
|
|
3091
3146
|
} finally {
|
|
3092
3147
|
setBusy(false);
|
|
3148
|
+
setTurnStartedAt(null);
|
|
3093
3149
|
activeControllerRef.current = null;
|
|
3094
3150
|
}
|
|
3095
3151
|
}, [cfg, busy, saveSessionSafe]);
|
|
@@ -3135,6 +3191,7 @@ function App({ initialCfg }) {
|
|
|
3135
3191
|
setEvents((e) => [...e, { kind: "user", key: mkKey(), text: "/init" }]);
|
|
3136
3192
|
messagesRef.current.push({ role: "user", content: prompt });
|
|
3137
3193
|
setBusy(true);
|
|
3194
|
+
setTurnStartedAt(Date.now());
|
|
3138
3195
|
const controller = new AbortController();
|
|
3139
3196
|
activeControllerRef.current = controller;
|
|
3140
3197
|
try {
|
|
@@ -3229,6 +3286,7 @@ function App({ initialCfg }) {
|
|
|
3229
3286
|
}
|
|
3230
3287
|
} finally {
|
|
3231
3288
|
setBusy(false);
|
|
3289
|
+
setTurnStartedAt(null);
|
|
3232
3290
|
activeAsstIdRef.current = null;
|
|
3233
3291
|
activeControllerRef.current = null;
|
|
3234
3292
|
}
|
|
@@ -3273,11 +3331,12 @@ function App({ initialCfg }) {
|
|
|
3273
3331
|
if (c === "/clear") {
|
|
3274
3332
|
messagesRef.current = [messagesRef.current[0]];
|
|
3275
3333
|
sessionIdRef.current = null;
|
|
3276
|
-
setEvents([
|
|
3334
|
+
setEvents([]);
|
|
3277
3335
|
setUsage(null);
|
|
3278
3336
|
setTasks([]);
|
|
3279
3337
|
setTasksStartedAt(null);
|
|
3280
3338
|
setTasksStartTokens(0);
|
|
3339
|
+
compactSuggestedRef.current = false;
|
|
3281
3340
|
return true;
|
|
3282
3341
|
}
|
|
3283
3342
|
if (c === "/reasoning") {
|
|
@@ -3486,6 +3545,7 @@ use: /thinking low | medium | high`
|
|
|
3486
3545
|
setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display }]);
|
|
3487
3546
|
messagesRef.current.push({ role: "user", content: trimmed });
|
|
3488
3547
|
setBusy(true);
|
|
3548
|
+
setTurnStartedAt(Date.now());
|
|
3489
3549
|
const controller = new AbortController();
|
|
3490
3550
|
activeControllerRef.current = controller;
|
|
3491
3551
|
try {
|
|
@@ -3599,13 +3659,14 @@ use: /thinking low | medium | high`
|
|
|
3599
3659
|
}
|
|
3600
3660
|
} finally {
|
|
3601
3661
|
setBusy(false);
|
|
3662
|
+
setTurnStartedAt(null);
|
|
3602
3663
|
activeAsstIdRef.current = null;
|
|
3603
3664
|
activeControllerRef.current = null;
|
|
3604
3665
|
}
|
|
3605
3666
|
},
|
|
3606
3667
|
[cfg, handleSlash, updateAssistant, updateTool, saveSessionSafe]
|
|
3607
3668
|
);
|
|
3608
|
-
|
|
3669
|
+
useEffect4(() => {
|
|
3609
3670
|
if (!busy && queue.length > 0) {
|
|
3610
3671
|
const next = queue[0];
|
|
3611
3672
|
setQueue((q) => q.slice(1));
|
|
@@ -3632,20 +3693,18 @@ use: /thinking low | medium | high`
|
|
|
3632
3693
|
},
|
|
3633
3694
|
[busy, processMessage]
|
|
3634
3695
|
);
|
|
3635
|
-
|
|
3696
|
+
useEffect4(() => {
|
|
3697
|
+
if (compactSuggestedRef.current) return;
|
|
3636
3698
|
if (usage && usage.prompt_tokens / CONTEXT_LIMIT >= AUTO_COMPACT_SUGGEST_PCT) {
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
}
|
|
3647
|
-
];
|
|
3648
|
-
});
|
|
3699
|
+
compactSuggestedRef.current = true;
|
|
3700
|
+
setEvents((e) => [
|
|
3701
|
+
...e,
|
|
3702
|
+
{
|
|
3703
|
+
kind: "info",
|
|
3704
|
+
key: mkKey(),
|
|
3705
|
+
text: `context ${Math.round(usage.prompt_tokens / CONTEXT_LIMIT * 100)}% full \u2014 run /compact to summarize older turns`
|
|
3706
|
+
}
|
|
3707
|
+
]);
|
|
3649
3708
|
}
|
|
3650
3709
|
}, [usage]);
|
|
3651
3710
|
if (!cfg) {
|
|
@@ -3699,6 +3758,7 @@ use: /thinking low | medium | high`
|
|
|
3699
3758
|
model: cfg.model,
|
|
3700
3759
|
usage,
|
|
3701
3760
|
thinking: busy,
|
|
3761
|
+
turnStartedAt,
|
|
3702
3762
|
theme,
|
|
3703
3763
|
mode,
|
|
3704
3764
|
effort,
|