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 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. It's Claude Code, but the model is Moonshot's 1T-parameter open-source Kimi running directly on your Cloudflare account — no middleman, no AI Gateway, no OpenAI SDK. You bring the token, your traffic goes straight to Cloudflare.
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
- No AI Gateway, no proxy, no OpenAI SDK. Direct `fetch` to Workers AI, OpenAI-compatible `messages` + `tools` payload, SSE stream with reasoning + content + tool-call deltas accumulated by index.
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__ */ jsx5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children: leftParts.join(" \xB7 ") })
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
- rightParts.length > 0 && /* @__PURE__ */ jsxs5(Box5, { children: [
1558
- /* @__PURE__ */ jsx5(Text5, { color: theme.info.color, dimColor: theme.info.dim, children: rightParts.join(" \xB7 ") }),
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] = useState(Date.now());
1679
- useEffect(() => {
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 ? formatElapsed(now - startedAt) : null;
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
- "\u25A0 ",
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 formatElapsed(ms) {
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 useState2, useEffect as useEffect2, useRef } from "react";
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] = useState2(value.length);
2337
+ const [cursorOffset, setCursorOffset] = useState3(value.length);
2312
2338
  const pastesRef = useRef(/* @__PURE__ */ new Map());
2313
- useEffect2(() => {
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 = 3;
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 useState3 } from "react";
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] = useState3("accountId");
2594
- const [accountId, setAccountId] = useState3("");
2595
- const [apiToken, setApiToken] = useState3("");
2596
- const [model, setModel] = useState3(DEFAULT_MODEL);
2597
- const [savedPath, setSavedPath] = useState3(null);
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 useState4, useRef as useRef2, useEffect as useEffect3, useCallback } from "react";
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] = useState4(initialCfg);
2929
- const [events, setEvents] = useState4([]);
2930
- const [input, setInput] = useState4("");
2931
- const [busy, setBusy] = useState4(false);
2932
- const [usage, setUsage] = useState4(null);
2933
- const [showReasoning, setShowReasoning] = useState4(false);
2934
- const [perm, setPerm] = useState4(null);
2935
- const [queue, setQueue] = useState4([]);
2936
- const [history, setHistory] = useState4([]);
2937
- const [historyIndex, setHistoryIndex] = useState4(-1);
2938
- const [draftInput, setDraftInput] = useState4("");
2939
- const [mode, setMode] = useState4("edit");
2940
- const [effort, setEffort] = useState4(
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] = useState4(resolveTheme(initialCfg?.theme));
2944
- const [resumeSessions, setResumeSessions] = useState4(null);
2945
- const [tasks, setTasks] = useState4([]);
2946
- const [tasksStartedAt, setTasksStartedAt] = useState4(null);
2947
- const [tasksStartTokens, setTasksStartTokens] = useState4(0);
2948
- const [verbose, setVerbose] = useState4(false);
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
- useEffect3(() => {
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
- useEffect3(() => {
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
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "compacting conversation\u2026" }]);
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([{ kind: "info", key: mkKey(), text: "conversation cleared" }]);
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
- useEffect3(() => {
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
- useEffect3(() => {
3696
+ useEffect4(() => {
3697
+ if (compactSuggestedRef.current) return;
3636
3698
  if (usage && usage.prompt_tokens / CONTEXT_LIMIT >= AUTO_COMPACT_SUGGEST_PCT) {
3637
- setEvents((e) => {
3638
- const last = e[e.length - 1];
3639
- if (last?.kind === "info" && last.text.startsWith("context ")) return e;
3640
- return [
3641
- ...e,
3642
- {
3643
- kind: "info",
3644
- key: mkKey(),
3645
- text: `context ${Math.round(usage.prompt_tokens / CONTEXT_LIMIT * 100)}% full \u2014 run /compact to summarize older turns`
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,