research-copilot 0.2.0 → 0.2.2

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.
Files changed (67) hide show
  1. package/app/out/main/index.mjs +3222 -412
  2. package/app/out/preload/index.js +26 -0
  3. package/app/out/renderer/assets/{MilkdownMarkdownEditor-Czh2N6UQ.js → MilkdownMarkdownEditor-jaF-aGPn.js} +50 -50
  4. package/app/out/renderer/assets/{arc-BWoErJNa.js → arc-C1kBmvvR.js} +1 -1
  5. package/app/out/renderer/assets/{blockDiagram-c4efeb88-Bod-vAlS.js → blockDiagram-c4efeb88-Do93X2rs.js} +8 -8
  6. package/app/out/renderer/assets/{c4Diagram-c83219d4-CTVUA_li.js → c4Diagram-c83219d4-DgxxcZWC.js} +3 -3
  7. package/app/out/renderer/assets/{channel-CxGr5Q5E.js → channel-Co_M0Svj.js} +1 -1
  8. package/app/out/renderer/assets/{classDiagram-beda092f-DABwUrsU.js → classDiagram-beda092f-CQlHgE6H.js} +6 -6
  9. package/app/out/renderer/assets/{classDiagram-v2-2358418a-CFt8hqf5.js → classDiagram-v2-2358418a-CkGG3aI2.js} +10 -10
  10. package/app/out/renderer/assets/{clone-BL91dKYn.js → clone-C18Y6dgC.js} +1 -1
  11. package/app/out/renderer/assets/{createText-1719965b-DGkv4rEO.js → createText-1719965b-DGRc6nys.js} +2 -2
  12. package/app/out/renderer/assets/{edges-96097737-Gf41lQOd.js → edges-96097737-BXvJ4fAK.js} +3 -3
  13. package/app/out/renderer/assets/{erDiagram-0228fc6a-Dj75BiRy.js → erDiagram-0228fc6a-CXjPp0pt.js} +5 -5
  14. package/app/out/renderer/assets/{flowDb-c6c81e3f-C_xVBMxS.js → flowDb-c6c81e3f-CNhpbtw_.js} +1 -1
  15. package/app/out/renderer/assets/{flowDiagram-50d868cf-B-lLn2XC.js → flowDiagram-50d868cf-KZ_BUCPA.js} +12 -12
  16. package/app/out/renderer/assets/{flowDiagram-v2-4f6560a1-BFnLU3PE.js → flowDiagram-v2-4f6560a1-IMv50KZP.js} +12 -12
  17. package/app/out/renderer/assets/{flowchart-elk-definition-6af322e1-DmjfyXbt.js → flowchart-elk-definition-6af322e1-BFwFiPvq.js} +6 -6
  18. package/app/out/renderer/assets/{ganttDiagram-a2739b55-BTPRekAy.js → ganttDiagram-a2739b55-D0-ehN-T.js} +3 -3
  19. package/app/out/renderer/assets/{gitGraphDiagram-82fe8481-1riYxgGS.js → gitGraphDiagram-82fe8481-DUyIR0Dv.js} +2 -2
  20. package/app/out/renderer/assets/{graph-CvDtMlX-.js → graph-DnTq2_3F.js} +1 -1
  21. package/app/out/renderer/assets/{index-5325376f-BGaoNMNN.js → index-5325376f-CBwuFbRF.js} +6 -6
  22. package/app/out/renderer/assets/{index-8tvmsRje.js → index-7hDGClrI.js} +3 -3
  23. package/app/out/renderer/assets/{index-CUPy7R5v.js → index-BB-a1ajC.js} +2122 -471
  24. package/app/out/renderer/assets/{index-Bii7x9Rr.js → index-BHcU72Rm.js} +3 -3
  25. package/app/out/renderer/assets/{index-qS7qbXvX.js → index-BQ7qz1CD.js} +3 -3
  26. package/app/out/renderer/assets/{index-DrvR7Peq.js → index-BVYoMX5H.js} +3 -3
  27. package/app/out/renderer/assets/{index-CXN1f9OT.js → index-BpKrXGYD.js} +3 -3
  28. package/app/out/renderer/assets/{index-0kPJXDfu.js → index-C1oXjI4L.js} +3 -3
  29. package/app/out/renderer/assets/{index-BK5rYWMs.js → index-CKXwBmK7.js} +5 -5
  30. package/app/out/renderer/assets/{index-B9lieynj.js → index-COZSDrEw.js} +6 -6
  31. package/app/out/renderer/assets/{index-Ctwkk-AW.css → index-CT1HtzVp.css} +165 -14
  32. package/app/out/renderer/assets/{index-zr8uxb8p.js → index-CjffvluT.js} +6 -6
  33. package/app/out/renderer/assets/{index-CnL9yPzK.js → index-D6jljsup.js} +3 -3
  34. package/app/out/renderer/assets/{index-BxOmAXUZ.js → index-D6r8msaQ.js} +3 -3
  35. package/app/out/renderer/assets/{index-D2fFfHUR.js → index-DWU4ia28.js} +6 -6
  36. package/app/out/renderer/assets/{index-BVNrdWzl.js → index-DZbrRR7w.js} +6 -6
  37. package/app/out/renderer/assets/{index-BCOrnr8q.js → index-Diy30-34.js} +4 -4
  38. package/app/out/renderer/assets/{index-NHbUPOmb.js → index-DuhageEr.js} +3 -3
  39. package/app/out/renderer/assets/{index-BnRwUKpv.js → index-ESFHcvWy.js} +3 -3
  40. package/app/out/renderer/assets/{index-B4djqBxS.js → index-JT8OCsRP.js} +1 -1
  41. package/app/out/renderer/assets/{index-cAZJ88Np.js → index-bMe3RSkw.js} +6 -6
  42. package/app/out/renderer/assets/{index-3LdRym1K.js → index-gH-w4EHk.js} +3 -3
  43. package/app/out/renderer/assets/{index-O3gvL3-Z.js → index-h_fNksib.js} +3 -3
  44. package/app/out/renderer/assets/{index-y5XZ-0EB.js → index-u0FZRZON.js} +4 -4
  45. package/app/out/renderer/assets/{index-BgSz3yUy.js → index-yanwpi6t.js} +6 -6
  46. package/app/out/renderer/assets/{infoDiagram-8eee0895-Cq8aXV8u.js → infoDiagram-8eee0895-Qra4japr.js} +2 -2
  47. package/app/out/renderer/assets/{journeyDiagram-c64418c1-D4ewDrYD.js → journeyDiagram-c64418c1-BTN9SgOL.js} +4 -4
  48. package/app/out/renderer/assets/{layout-CZmLZO9t.js → layout-DGrHHJdN.js} +2 -2
  49. package/app/out/renderer/assets/{line-D7kWOiRx.js → line-DXtxdS2B.js} +1 -1
  50. package/app/out/renderer/assets/{linear-B055Dz0c.js → linear-CexrSQK6.js} +1 -1
  51. package/app/out/renderer/assets/{mindmap-definition-8da855dc-D6EW4QCj.js → mindmap-definition-8da855dc-pvG2hzEB.js} +3 -3
  52. package/app/out/renderer/assets/{pieDiagram-a8764435-BX_Dz4T9.js → pieDiagram-a8764435-D_neFVMq.js} +3 -3
  53. package/app/out/renderer/assets/{quadrantDiagram-1e28029f-BsI6xGsm.js → quadrantDiagram-1e28029f-C47W3UMp.js} +3 -3
  54. package/app/out/renderer/assets/{requirementDiagram-08caed73-c2d8T0BS.js → requirementDiagram-08caed73-DW4Bo_fu.js} +5 -5
  55. package/app/out/renderer/assets/{sankeyDiagram-a04cb91d-CkDhRKRC.js → sankeyDiagram-a04cb91d-D_3PD7JI.js} +2 -2
  56. package/app/out/renderer/assets/{sequenceDiagram-c5b8d532-DS0RKYnD.js → sequenceDiagram-c5b8d532-BW6nGtuQ.js} +3 -3
  57. package/app/out/renderer/assets/{stateDiagram-1ecb1508-BjTK27QX.js → stateDiagram-1ecb1508-CDgBJ3-T.js} +6 -6
  58. package/app/out/renderer/assets/{stateDiagram-v2-c2b004d7-D1wWbeR3.js → stateDiagram-v2-c2b004d7-CBw5TtXo.js} +10 -10
  59. package/app/out/renderer/assets/{styles-b4e223ce-DXUfbXTM.js → styles-b4e223ce-DeeiEsuW.js} +1 -1
  60. package/app/out/renderer/assets/{styles-ca3715f6-CE_JRTmB.js → styles-ca3715f6-CMpiebrG.js} +1 -1
  61. package/app/out/renderer/assets/{styles-d45a18b0-CdtAXXSE.js → styles-d45a18b0-CZe9hU7H.js} +4 -4
  62. package/app/out/renderer/assets/{svgDrawCommon-b86b1483-dCxPWgBl.js → svgDrawCommon-b86b1483-CmJZfZzJ.js} +1 -1
  63. package/app/out/renderer/assets/{timeline-definition-faaaa080-B7ZP3Dqw.js → timeline-definition-faaaa080-Beo2kiiz.js} +3 -3
  64. package/app/out/renderer/assets/{xychartDiagram-f5964ef8-CXagmo1Q.js → xychartDiagram-f5964ef8-DYmo7moz.js} +5 -5
  65. package/app/out/renderer/index.html +2 -2
  66. package/app/package.json +1 -1
  67. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./MilkdownMarkdownEditor-Czh2N6UQ.js","./MilkdownMarkdownEditor-tTNRIB2K.css"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./MilkdownMarkdownEditor-jaF-aGPn.js","./MilkdownMarkdownEditor-tTNRIB2K.css"])))=>i.map(i=>d[i]);
2
2
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
3
3
  function getDefaultExportFromCjs(x) {
4
4
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -12641,6 +12641,27 @@ const ChevronRight = createLucideIcon("ChevronRight", [
12641
12641
  * See the LICENSE file in the root directory of this source tree.
12642
12642
  */
12643
12643
  const ChevronUp = createLucideIcon("ChevronUp", [["path", { d: "m18 15-6-6-6 6", key: "153udz" }]]);
12644
+ /**
12645
+ * @license lucide-react v0.469.0 - ISC
12646
+ *
12647
+ * This source code is licensed under the ISC license.
12648
+ * See the LICENSE file in the root directory of this source tree.
12649
+ */
12650
+ const CircleAlert = createLucideIcon("CircleAlert", [
12651
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
12652
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "12", key: "1pkeuh" }],
12653
+ ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }]
12654
+ ]);
12655
+ /**
12656
+ * @license lucide-react v0.469.0 - ISC
12657
+ *
12658
+ * This source code is licensed under the ISC license.
12659
+ * See the LICENSE file in the root directory of this source tree.
12660
+ */
12661
+ const CircleCheck = createLucideIcon("CircleCheck", [
12662
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
12663
+ ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
12664
+ ]);
12644
12665
  /**
12645
12666
  * @license lucide-react v0.469.0 - ISC
12646
12667
  *
@@ -12681,23 +12702,6 @@ const Database = createLucideIcon("Database", [
12681
12702
  ["path", { d: "M3 5V19A9 3 0 0 0 21 19V5", key: "1wlel7" }],
12682
12703
  ["path", { d: "M3 12A9 3 0 0 0 21 12", key: "mv7ke4" }]
12683
12704
  ]);
12684
- /**
12685
- * @license lucide-react v0.469.0 - ISC
12686
- *
12687
- * This source code is licensed under the ISC license.
12688
- * See the LICENSE file in the root directory of this source tree.
12689
- */
12690
- const Eraser = createLucideIcon("Eraser", [
12691
- [
12692
- "path",
12693
- {
12694
- d: "m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21",
12695
- key: "182aya"
12696
- }
12697
- ],
12698
- ["path", { d: "M22 21H7", key: "t4ddhn" }],
12699
- ["path", { d: "m5 11 9 9", key: "1mo9qw" }]
12700
- ]);
12701
12705
  /**
12702
12706
  * @license lucide-react v0.469.0 - ISC
12703
12707
  *
@@ -12883,6 +12887,28 @@ const GitBranch = createLucideIcon("GitBranch", [
12883
12887
  ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
12884
12888
  ["path", { d: "M18 9a9 9 0 0 1-9 9", key: "n2h4wq" }]
12885
12889
  ]);
12890
+ /**
12891
+ * @license lucide-react v0.469.0 - ISC
12892
+ *
12893
+ * This source code is licensed under the ISC license.
12894
+ * See the LICENSE file in the root directory of this source tree.
12895
+ */
12896
+ const Globe = createLucideIcon("Globe", [
12897
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
12898
+ ["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20", key: "13o1zl" }],
12899
+ ["path", { d: "M2 12h20", key: "9i4pu4" }]
12900
+ ]);
12901
+ /**
12902
+ * @license lucide-react v0.469.0 - ISC
12903
+ *
12904
+ * This source code is licensed under the ISC license.
12905
+ * See the LICENSE file in the root directory of this source tree.
12906
+ */
12907
+ const Info$1 = createLucideIcon("Info", [
12908
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
12909
+ ["path", { d: "M12 16v-4", key: "1dtifu" }],
12910
+ ["path", { d: "M12 8h.01", key: "e9boi3" }]
12911
+ ]);
12886
12912
  /**
12887
12913
  * @license lucide-react v0.469.0 - ISC
12888
12914
  *
@@ -12920,6 +12946,28 @@ const Lightbulb = createLucideIcon("Lightbulb", [
12920
12946
  const LoaderCircle = createLucideIcon("LoaderCircle", [
12921
12947
  ["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]
12922
12948
  ]);
12949
+ /**
12950
+ * @license lucide-react v0.469.0 - ISC
12951
+ *
12952
+ * This source code is licensed under the ISC license.
12953
+ * See the LICENSE file in the root directory of this source tree.
12954
+ */
12955
+ const LogIn = createLucideIcon("LogIn", [
12956
+ ["path", { d: "M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4", key: "u53s6r" }],
12957
+ ["polyline", { points: "10 17 15 12 10 7", key: "1ail0h" }],
12958
+ ["line", { x1: "15", x2: "3", y1: "12", y2: "12", key: "v6grx8" }]
12959
+ ]);
12960
+ /**
12961
+ * @license lucide-react v0.469.0 - ISC
12962
+ *
12963
+ * This source code is licensed under the ISC license.
12964
+ * See the LICENSE file in the root directory of this source tree.
12965
+ */
12966
+ const LogOut = createLucideIcon("LogOut", [
12967
+ ["path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4", key: "1uf3rs" }],
12968
+ ["polyline", { points: "16 17 21 12 16 7", key: "1gabdz" }],
12969
+ ["line", { x1: "21", x2: "9", y1: "12", y2: "12", key: "1uyos4" }]
12970
+ ]);
12923
12971
  /**
12924
12972
  * @license lucide-react v0.469.0 - ISC
12925
12973
  *
@@ -12929,6 +12977,17 @@ const LoaderCircle = createLucideIcon("LoaderCircle", [
12929
12977
  const MessageSquare = createLucideIcon("MessageSquare", [
12930
12978
  ["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }]
12931
12979
  ]);
12980
+ /**
12981
+ * @license lucide-react v0.469.0 - ISC
12982
+ *
12983
+ * This source code is licensed under the ISC license.
12984
+ * See the LICENSE file in the root directory of this source tree.
12985
+ */
12986
+ const Monitor = createLucideIcon("Monitor", [
12987
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
12988
+ ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
12989
+ ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
12990
+ ]);
12932
12991
  /**
12933
12992
  * @license lucide-react v0.469.0 - ISC
12934
12993
  *
@@ -13182,6 +13241,21 @@ const Upload = createLucideIcon("Upload", [
13182
13241
  ["polyline", { points: "17 8 12 3 7 8", key: "t8dd8p" }],
13183
13242
  ["line", { x1: "12", x2: "12", y1: "3", y2: "15", key: "widbto" }]
13184
13243
  ]);
13244
+ /**
13245
+ * @license lucide-react v0.469.0 - ISC
13246
+ *
13247
+ * This source code is licensed under the ISC license.
13248
+ * See the LICENSE file in the root directory of this source tree.
13249
+ */
13250
+ const Wrench = createLucideIcon("Wrench", [
13251
+ [
13252
+ "path",
13253
+ {
13254
+ d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z",
13255
+ key: "cbrjhi"
13256
+ }
13257
+ ]
13258
+ ]);
13185
13259
  /**
13186
13260
  * @license lucide-react v0.469.0 - ISC
13187
13261
  *
@@ -13207,7 +13281,7 @@ const Zap = createLucideIcon("Zap", [
13207
13281
  }
13208
13282
  ]
13209
13283
  ]);
13210
- const api$a = window.api;
13284
+ const api$b = window.api;
13211
13285
  const KEY_FIELDS = [
13212
13286
  {
13213
13287
  name: "ANTHROPIC_API_KEY",
@@ -13248,21 +13322,40 @@ function ApiKeySetup({ onComplete }) {
13248
13322
  const [visible, setVisible] = reactExports.useState({});
13249
13323
  const [saving, setSaving] = reactExports.useState(false);
13250
13324
  const [error, setError] = reactExports.useState(null);
13325
+ const [codexStatus, setCodexStatus] = reactExports.useState(null);
13326
+ const [codexLoggingIn, setCodexLoggingIn] = reactExports.useState(false);
13251
13327
  reactExports.useEffect(() => {
13252
- api$a.getApiKeyStatus().then((s15) => setStatus(s15));
13328
+ api$b.getApiKeyStatus().then((s15) => setStatus(s15));
13329
+ api$b.getOpenAICodexStatus?.().then((s15) => setCodexStatus(s15)).catch(() => {
13330
+ });
13253
13331
  }, []);
13254
- const hasAnyLlmKey = status.ANTHROPIC_API_KEY || status.OPENAI_API_KEY || !!(values.ANTHROPIC_API_KEY || "").trim() || !!(values.OPENAI_API_KEY || "").trim();
13332
+ const handleCodexLogin = async () => {
13333
+ setCodexLoggingIn(true);
13334
+ try {
13335
+ const result = await api$b.openaiCodexLogin?.();
13336
+ if (result?.success) {
13337
+ setCodexStatus({ isLoggedIn: true });
13338
+ } else {
13339
+ setError(result?.error || "ChatGPT sign-in failed");
13340
+ }
13341
+ } catch (err) {
13342
+ setError(err.message || "ChatGPT sign-in failed");
13343
+ } finally {
13344
+ setCodexLoggingIn(false);
13345
+ }
13346
+ };
13347
+ const hasAnyLlmKey = status.ANTHROPIC_API_KEY || status.OPENAI_API_KEY || codexStatus?.isLoggedIn || !!(values.ANTHROPIC_API_KEY || "").trim() || !!(values.OPENAI_API_KEY || "").trim();
13255
13348
  const handleSave = async () => {
13256
13349
  const entries = Object.entries(values).filter(([, v3]) => v3.trim());
13257
- if (entries.length === 0 && !status.ANTHROPIC_API_KEY && !status.OPENAI_API_KEY) {
13258
- setError("Please enter at least one API key (Anthropic or OpenAI) to continue.");
13350
+ if (entries.length === 0 && !status.ANTHROPIC_API_KEY && !status.OPENAI_API_KEY && !codexStatus?.isLoggedIn) {
13351
+ setError("Please enter at least one API key or sign in with ChatGPT to continue.");
13259
13352
  return;
13260
13353
  }
13261
13354
  setSaving(true);
13262
13355
  setError(null);
13263
13356
  try {
13264
13357
  for (const [key, val] of entries) {
13265
- await api$a.saveApiKey(key, val);
13358
+ await api$b.saveApiKey(key, val);
13266
13359
  }
13267
13360
  onComplete();
13268
13361
  } catch (err) {
@@ -13278,11 +13371,7 @@ function ApiKeySetup({ onComplete }) {
13278
13371
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative mx-auto mb-6 w-fit", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
13279
13372
  "div",
13280
13373
  {
13281
- className: "w-14 h-14 rounded-2xl flex items-center justify-center",
13282
- style: {
13283
- background: "linear-gradient(135deg, var(--color-accent) 0%, var(--color-accent-2) 100%)",
13284
- boxShadow: "0 8px 32px var(--color-accent-2-muted)"
13285
- },
13374
+ className: "w-14 h-14 rounded-2xl flex items-center justify-center t-gradient-accent t-gradient-accent-shadow-lg",
13286
13375
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "text-white", size: 22 })
13287
13376
  }
13288
13377
  ) }),
@@ -13333,7 +13422,8 @@ function ApiKeySetup({ onComplete }) {
13333
13422
  className: "w-full text-xs px-2.5 py-1.5 rounded-md border t-border t-bg-base t-text font-mono pr-8\n focus:outline-none focus:ring-1 focus:ring-[var(--color-accent)]",
13334
13423
  placeholder: alreadySet ? "•••••••• (already set — leave blank to keep)" : field.placeholder,
13335
13424
  value: values[field.name] || "",
13336
- onChange: (e) => setValues((prev) => ({ ...prev, [field.name]: e.target.value }))
13425
+ onChange: (e) => setValues((prev) => ({ ...prev, [field.name]: e.target.value })),
13426
+ "aria-label": `${field.label} API key`
13337
13427
  }
13338
13428
  ),
13339
13429
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -13350,10 +13440,33 @@ function ApiKeySetup({ onComplete }) {
13350
13440
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mt-1", children: field.hint })
13351
13441
  ] }, field.name);
13352
13442
  }) }),
13353
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mb-1", children: "* You need at least one of Anthropic or OpenAI. Both is fine too." }),
13354
- error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 mb-3", children: error }),
13443
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border t-border t-bg-surface/50 p-3", children: [
13444
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between mb-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-xs font-medium t-text flex items-center gap-1.5", children: [
13445
+ "ChatGPT Subscription",
13446
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted", children: "(alternative to OpenAI API key)" }),
13447
+ codexStatus?.isLoggedIn && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-0.5 text-[10px] text-green-500", children: [
13448
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 10 }),
13449
+ " signed in"
13450
+ ] })
13451
+ ] }) }),
13452
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
13453
+ "button",
13454
+ {
13455
+ onClick: handleCodexLogin,
13456
+ disabled: codexLoggingIn || codexStatus?.isLoggedIn,
13457
+ className: "flex items-center gap-1.5 px-3 py-1.5 rounded-md border t-border text-xs t-text-secondary hover:t-text t-bg-hover disabled:opacity-50",
13458
+ children: [
13459
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LogIn, { size: 12 }),
13460
+ codexLoggingIn ? "Signing in..." : codexStatus?.isLoggedIn ? "Already signed in" : "Sign in with ChatGPT"
13461
+ ]
13462
+ }
13463
+ ),
13464
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mt-1", children: "Use your ChatGPT Plus/Pro subscription instead of an API key. No per-token billing." })
13465
+ ] }),
13466
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] t-text-muted mb-1", children: "* You need at least one of Anthropic or OpenAI (API key or ChatGPT subscription)." }),
13467
+ error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 mb-3", role: "alert", children: error }),
13355
13468
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
13356
- (status.ANTHROPIC_API_KEY || status.OPENAI_API_KEY) && /* @__PURE__ */ jsxRuntimeExports.jsx(
13469
+ (status.ANTHROPIC_API_KEY || status.OPENAI_API_KEY || codexStatus?.isLoggedIn) && /* @__PURE__ */ jsxRuntimeExports.jsx(
13357
13470
  "button",
13358
13471
  {
13359
13472
  onClick: onComplete,
@@ -13367,11 +13480,7 @@ function ApiKeySetup({ onComplete }) {
13367
13480
  {
13368
13481
  onClick: handleSave,
13369
13482
  disabled: saving,
13370
- className: "px-5 py-2 rounded-lg text-white text-sm font-medium hover:opacity-90 transition-all duration-200 disabled:opacity-50",
13371
- style: {
13372
- background: "linear-gradient(135deg, var(--color-accent) 0%, var(--color-accent-2) 100%)",
13373
- boxShadow: "0 4px 16px var(--color-accent-2-muted)"
13374
- },
13483
+ className: "px-5 py-2 rounded-lg text-white text-sm font-medium hover:opacity-90 transition-all duration-200 disabled:opacity-50 t-gradient-accent t-gradient-accent-shadow",
13375
13484
  children: saving ? "Saving..." : hasAnyLlmKey ? "Save & Continue" : "Save & Continue"
13376
13485
  }
13377
13486
  )
@@ -13418,11 +13527,44 @@ const createImpl = (createState2) => {
13418
13527
  return useBoundStore;
13419
13528
  };
13420
13529
  const create$1 = (createState2) => createState2 ? createImpl(createState2) : createImpl;
13530
+ const REASONING_MODELS = [
13531
+ "openai:gpt-5.4",
13532
+ "openai:gpt-5.4-mini",
13533
+ "openai:gpt-5.4-nano",
13534
+ "openai-codex:gpt-5.4",
13535
+ "openai-codex:gpt-5.4-mini",
13536
+ "openai-codex:gpt-5.4-nano",
13537
+ "anthropic:claude-opus-4-6"
13538
+ ];
13539
+ const SUPPORTED_MODELS = [
13540
+ // OpenAI (API key)
13541
+ { id: "openai:gpt-5.4", label: "GPT-5.4", provider: "OpenAI" },
13542
+ { id: "openai:gpt-5.4-mini", label: "GPT-5.4 Mini", provider: "OpenAI" },
13543
+ { id: "openai:gpt-5.4-nano", label: "GPT-5.4 Nano", provider: "OpenAI" },
13544
+ { id: "openai:gpt-4o", label: "GPT-4o", provider: "OpenAI" },
13545
+ // ChatGPT Subscription (OAuth)
13546
+ { id: "openai-codex:gpt-5.4", label: "GPT-5.4", provider: "ChatGPT Subscription" },
13547
+ { id: "openai-codex:gpt-5.4-mini", label: "GPT-5.4 Mini", provider: "ChatGPT Subscription" },
13548
+ { id: "openai-codex:gpt-5.4-pro", label: "GPT-5.4 Pro", provider: "ChatGPT Subscription" },
13549
+ // Anthropic (API key)
13550
+ { id: "anthropic:claude-opus-4-6", label: "Claude Opus 4.6", provider: "Anthropic" },
13551
+ { id: "anthropic:claude-opus-4-5-20251101", label: "Claude Opus 4.5", provider: "Anthropic" },
13552
+ { id: "anthropic:claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "Anthropic" },
13553
+ { id: "anthropic:claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", provider: "Anthropic" }
13554
+ ];
13555
+ const DEFAULT_MODEL = "openai:gpt-5.4";
13556
+ function parseModelKey(key) {
13557
+ const i = key.indexOf(":");
13558
+ if (i > 0) return { provider: key.slice(0, i), modelId: key.slice(i + 1) };
13559
+ if (key.startsWith("claude-")) return { provider: "anthropic", modelId: key };
13560
+ if (key.startsWith("gemini-")) return { provider: "google", modelId: key };
13561
+ return { provider: "openai", modelId: key };
13562
+ }
13421
13563
  const useUIStore = create$1((set) => ({
13422
13564
  theme: "light",
13423
13565
  leftTab: "files",
13424
13566
  centerView: "chat",
13425
- selectedModel: "gpt-5.4",
13567
+ selectedModel: DEFAULT_MODEL,
13426
13568
  isIdle: true,
13427
13569
  rightSidebarCollapsed: false,
13428
13570
  leftSidebarCollapsed: false,
@@ -13528,16 +13670,321 @@ async function hydratePreferences() {
13528
13670
  const prefs = await api2?.loadPreferences?.();
13529
13671
  if (!prefs) return;
13530
13672
  const updates = {};
13531
- if (prefs.selectedModel) updates.selectedModel = prefs.selectedModel;
13673
+ if (prefs.selectedModel) {
13674
+ const m = prefs.selectedModel;
13675
+ if (!m.includes(":")) {
13676
+ const { provider, modelId } = parseModelKey(m);
13677
+ updates.selectedModel = `${provider}:${modelId}`;
13678
+ } else {
13679
+ updates.selectedModel = m;
13680
+ }
13681
+ }
13532
13682
  if (prefs.reasoningEffort) updates.reasoningEffort = prefs.reasoningEffort;
13533
13683
  if (prefs.theme) updates.theme = prefs.theme;
13534
13684
  if (Object.keys(updates).length > 0) useUIStore.setState(updates);
13535
13685
  }
13536
13686
  const uiStore = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
13537
13687
  __proto__: null,
13688
+ REASONING_MODELS,
13689
+ SUPPORTED_MODELS,
13538
13690
  hydratePreferences,
13539
13691
  useUIStore
13540
13692
  }, Symbol.toStringTag, { value: "Module" }));
13693
+ const scriptRel = function detectScriptRel() {
13694
+ const relList = typeof document !== "undefined" && document.createElement("link").relList;
13695
+ return relList && relList.supports && relList.supports("modulepreload") ? "modulepreload" : "preload";
13696
+ }();
13697
+ const assetsURL = function(dep, importerUrl) {
13698
+ return new URL(dep, importerUrl).href;
13699
+ };
13700
+ const seen = {};
13701
+ const __vitePreload = function preload2(baseModule, deps, importerUrl) {
13702
+ let promise = Promise.resolve();
13703
+ if (deps && deps.length > 0) {
13704
+ const links = document.getElementsByTagName("link");
13705
+ const cspNonceMeta = document.querySelector(
13706
+ "meta[property=csp-nonce]"
13707
+ );
13708
+ const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
13709
+ promise = Promise.allSettled(
13710
+ deps.map((dep) => {
13711
+ dep = assetsURL(dep, importerUrl);
13712
+ if (dep in seen) return;
13713
+ seen[dep] = true;
13714
+ const isCss = dep.endsWith(".css");
13715
+ const cssSelector = isCss ? '[rel="stylesheet"]' : "";
13716
+ const isBaseRelative = !!importerUrl;
13717
+ if (isBaseRelative) {
13718
+ for (let i = links.length - 1; i >= 0; i--) {
13719
+ const link22 = links[i];
13720
+ if (link22.href === dep && (!isCss || link22.rel === "stylesheet")) {
13721
+ return;
13722
+ }
13723
+ }
13724
+ } else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
13725
+ return;
13726
+ }
13727
+ const link2 = document.createElement("link");
13728
+ link2.rel = isCss ? "stylesheet" : scriptRel;
13729
+ if (!isCss) {
13730
+ link2.as = "script";
13731
+ }
13732
+ link2.crossOrigin = "";
13733
+ link2.href = dep;
13734
+ if (cspNonce) {
13735
+ link2.setAttribute("nonce", cspNonce);
13736
+ }
13737
+ document.head.appendChild(link2);
13738
+ if (isCss) {
13739
+ return new Promise((res, rej) => {
13740
+ link2.addEventListener("load", res);
13741
+ link2.addEventListener(
13742
+ "error",
13743
+ () => rej(new Error(`Unable to preload CSS for ${dep}`))
13744
+ );
13745
+ });
13746
+ }
13747
+ })
13748
+ );
13749
+ }
13750
+ function handlePreloadError(err) {
13751
+ const e = new Event("vite:preloadError", {
13752
+ cancelable: true
13753
+ });
13754
+ e.payload = err;
13755
+ window.dispatchEvent(e);
13756
+ if (!e.defaultPrevented) {
13757
+ throw err;
13758
+ }
13759
+ }
13760
+ return promise.then((res) => {
13761
+ for (const item of res || []) {
13762
+ if (item.status !== "rejected") continue;
13763
+ handlePreloadError(item.reason);
13764
+ }
13765
+ return baseModule().catch(handlePreloadError);
13766
+ });
13767
+ };
13768
+ function extractProgress(tool, data) {
13769
+ if (!data?.partialResult) return void 0;
13770
+ const text2 = data.partialResult?.content?.[0]?.text;
13771
+ if (typeof text2 === "string" && text2.length > 0) {
13772
+ const lines = text2.split("\n").filter(Boolean);
13773
+ const maxLines = tool === "bash" ? 5 : 3;
13774
+ return lines.slice(-maxLines).join("\n");
13775
+ }
13776
+ return void 0;
13777
+ }
13778
+ const useToolEventsStore = create$1((set, get) => ({
13779
+ currentRunEvents: [],
13780
+ onToolCall: (event) => {
13781
+ const toolCallId = event.toolCallId || `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
13782
+ const newEvent = {
13783
+ id: toolCallId,
13784
+ toolCallId,
13785
+ tool: event.tool,
13786
+ status: "running",
13787
+ summary: event.summary,
13788
+ detail: event.detail,
13789
+ startedAt: Date.now()
13790
+ };
13791
+ set((state) => ({
13792
+ currentRunEvents: [...state.currentRunEvents, newEvent]
13793
+ }));
13794
+ },
13795
+ onToolResult: (event) => {
13796
+ set((state) => {
13797
+ const events = [...state.currentRunEvents];
13798
+ const idx = event.toolCallId ? events.findLastIndex((e) => e.toolCallId === event.toolCallId && e.status === "running") : events.findLastIndex((e) => e.tool === event.tool && e.status === "running");
13799
+ if (idx !== -1) {
13800
+ events[idx] = {
13801
+ ...events[idx],
13802
+ status: event.success !== false ? "success" : "error",
13803
+ resultSummary: event.summary,
13804
+ resultDetail: event.resultDetail,
13805
+ durationMs: event.durationMs,
13806
+ completedAt: Date.now()
13807
+ };
13808
+ }
13809
+ return { currentRunEvents: events };
13810
+ });
13811
+ },
13812
+ onToolProgress: (event) => {
13813
+ if (event.phase === "end") return;
13814
+ set((state) => {
13815
+ const events = [...state.currentRunEvents];
13816
+ const idx = events.findLastIndex((e) => e.toolCallId === event.toolCallId);
13817
+ if (idx !== -1) {
13818
+ const progress = extractProgress(event.tool, event.data) ?? events[idx].progress;
13819
+ events[idx] = { ...events[idx], progress };
13820
+ }
13821
+ return { currentRunEvents: events };
13822
+ });
13823
+ },
13824
+ snapshot: () => [...get().currentRunEvents],
13825
+ clearRun: () => set({ currentRunEvents: [] })
13826
+ }));
13827
+ const PAGE_SIZE = 20;
13828
+ const api$a = window.api;
13829
+ let _sessionId = "";
13830
+ const useChatStore = create$1((set, get) => ({
13831
+ messages: [],
13832
+ streamingText: "",
13833
+ isStreaming: false,
13834
+ savedMessageIds: /* @__PURE__ */ new Set(),
13835
+ turnToolEvents: /* @__PURE__ */ new Map(),
13836
+ draftText: "",
13837
+ setDraftText: (text2) => set({ draftText: text2 }),
13838
+ hasMore: false,
13839
+ isLoadingHistory: false,
13840
+ _offset: 0,
13841
+ scrollToMessageId: null,
13842
+ send: async (text2, images) => {
13843
+ const userMsg = {
13844
+ id: crypto.randomUUID(),
13845
+ role: "user",
13846
+ content: text2,
13847
+ images: images?.map((i) => `data:${i.mimeType};base64,${i.base64}`),
13848
+ timestamp: Date.now()
13849
+ };
13850
+ set((s15) => ({
13851
+ messages: [...s15.messages, userMsg],
13852
+ streamingText: "",
13853
+ isStreaming: true
13854
+ }));
13855
+ if (_sessionId) {
13856
+ api$a.saveMessage(_sessionId, userMsg).catch(() => {
13857
+ });
13858
+ }
13859
+ const { useUsageStore: useUsageStore2 } = await __vitePreload(async () => {
13860
+ const { useUsageStore: useUsageStore3 } = await Promise.resolve().then(() => usageStore);
13861
+ return { useUsageStore: useUsageStore3 };
13862
+ }, true ? void 0 : void 0, import.meta.url);
13863
+ useUsageStore2.getState().resetRun();
13864
+ try {
13865
+ const { useUIStore: useUIStore2 } = await __vitePreload(async () => {
13866
+ const { useUIStore: useUIStore3 } = await Promise.resolve().then(() => uiStore);
13867
+ return { useUIStore: useUIStore3 };
13868
+ }, true ? void 0 : void 0, import.meta.url);
13869
+ const model = useUIStore2.getState().selectedModel;
13870
+ await api$a.sendMessage(text2, void 0, model, images);
13871
+ } catch {
13872
+ }
13873
+ },
13874
+ stop: async () => {
13875
+ await api$a.stopAgent();
13876
+ },
13877
+ appendChunk: (chunk) => {
13878
+ set((s15) => ({ streamingText: s15.streamingText + chunk }));
13879
+ },
13880
+ finalize: (result) => {
13881
+ const content2 = result.response || result.error || "No response";
13882
+ const assistantMsg = {
13883
+ id: crypto.randomUUID(),
13884
+ role: "assistant",
13885
+ content: content2,
13886
+ images: result.images?.map((i) => `data:${i.mimeType};base64,${i.base64}`),
13887
+ timestamp: Date.now()
13888
+ };
13889
+ const toolEventsSnapshot = useToolEventsStore.getState().snapshot();
13890
+ useToolEventsStore.getState().clearRun();
13891
+ set((s15) => {
13892
+ const nextToolEvents = new Map(s15.turnToolEvents);
13893
+ if (toolEventsSnapshot.length > 0) {
13894
+ nextToolEvents.set(assistantMsg.id, toolEventsSnapshot);
13895
+ }
13896
+ return {
13897
+ messages: [...s15.messages, assistantMsg],
13898
+ streamingText: "",
13899
+ isStreaming: false,
13900
+ turnToolEvents: nextToolEvents
13901
+ };
13902
+ });
13903
+ if (_sessionId) {
13904
+ api$a.saveMessage(_sessionId, assistantMsg).catch(() => {
13905
+ });
13906
+ }
13907
+ },
13908
+ clear: () => {
13909
+ _sessionId = "";
13910
+ return set({
13911
+ messages: [],
13912
+ streamingText: "",
13913
+ isStreaming: false,
13914
+ savedMessageIds: /* @__PURE__ */ new Set(),
13915
+ turnToolEvents: /* @__PURE__ */ new Map(),
13916
+ draftText: "",
13917
+ hasMore: false,
13918
+ isLoadingHistory: false,
13919
+ _offset: 0,
13920
+ scrollToMessageId: null
13921
+ });
13922
+ },
13923
+ markSaved: (messageId) => {
13924
+ set((s15) => {
13925
+ const next = new Set(s15.savedMessageIds);
13926
+ next.add(messageId);
13927
+ return { savedMessageIds: next };
13928
+ });
13929
+ if (_sessionId) {
13930
+ api$a.markMessageSaved(_sessionId, messageId).catch(() => {
13931
+ });
13932
+ }
13933
+ },
13934
+ insertContextReset: () => {
13935
+ const divider = {
13936
+ id: `ctx-reset-${Date.now()}`,
13937
+ role: "system",
13938
+ content: "AI context has been reset — chat history is preserved.",
13939
+ timestamp: Date.now()
13940
+ };
13941
+ set((s15) => ({ messages: [...s15.messages, divider] }));
13942
+ },
13943
+ loadInitial: async (sessionId) => {
13944
+ _sessionId = sessionId;
13945
+ try {
13946
+ const [count, messages, savedIds] = await Promise.all([
13947
+ api$a.getMessageCount(sessionId),
13948
+ api$a.loadMessages(sessionId, 0, PAGE_SIZE),
13949
+ api$a.loadSavedMessageIds(sessionId)
13950
+ ]);
13951
+ set({
13952
+ messages,
13953
+ hasMore: count > PAGE_SIZE,
13954
+ _offset: PAGE_SIZE,
13955
+ savedMessageIds: new Set(savedIds || [])
13956
+ });
13957
+ } catch {
13958
+ }
13959
+ },
13960
+ loadHistory: async () => {
13961
+ const { hasMore, isLoadingHistory, _offset } = get();
13962
+ if (!hasMore || isLoadingHistory || !_sessionId) return;
13963
+ set({ isLoadingHistory: true });
13964
+ try {
13965
+ const [count, older] = await Promise.all([
13966
+ api$a.getMessageCount(_sessionId),
13967
+ api$a.loadMessages(_sessionId, _offset, PAGE_SIZE)
13968
+ ]);
13969
+ if (older.length > 0) {
13970
+ set((s15) => ({
13971
+ messages: [...older, ...s15.messages],
13972
+ _offset: s15._offset + older.length,
13973
+ hasMore: s15._offset + older.length < count,
13974
+ isLoadingHistory: false
13975
+ }));
13976
+ } else {
13977
+ set({ hasMore: false, isLoadingHistory: false });
13978
+ }
13979
+ } catch {
13980
+ set({ isLoadingHistory: false });
13981
+ }
13982
+ },
13983
+ requestScrollTo: (messageId) => {
13984
+ set({ scrollToMessageId: messageId });
13985
+ setTimeout(() => set({ scrollToMessageId: null }), 100);
13986
+ }
13987
+ }));
13541
13988
  function ok$1() {
13542
13989
  }
13543
13990
  function unreachable() {
@@ -26140,219 +26587,6 @@ const useEntityStore = create$1((set, get) => ({
26140
26587
  await get().refreshAll();
26141
26588
  }
26142
26589
  }));
26143
- const scriptRel = function detectScriptRel() {
26144
- const relList = typeof document !== "undefined" && document.createElement("link").relList;
26145
- return relList && relList.supports && relList.supports("modulepreload") ? "modulepreload" : "preload";
26146
- }();
26147
- const assetsURL = function(dep, importerUrl) {
26148
- return new URL(dep, importerUrl).href;
26149
- };
26150
- const seen = {};
26151
- const __vitePreload = function preload2(baseModule, deps, importerUrl) {
26152
- let promise = Promise.resolve();
26153
- if (deps && deps.length > 0) {
26154
- const links = document.getElementsByTagName("link");
26155
- const cspNonceMeta = document.querySelector(
26156
- "meta[property=csp-nonce]"
26157
- );
26158
- const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
26159
- promise = Promise.allSettled(
26160
- deps.map((dep) => {
26161
- dep = assetsURL(dep, importerUrl);
26162
- if (dep in seen) return;
26163
- seen[dep] = true;
26164
- const isCss = dep.endsWith(".css");
26165
- const cssSelector = isCss ? '[rel="stylesheet"]' : "";
26166
- const isBaseRelative = !!importerUrl;
26167
- if (isBaseRelative) {
26168
- for (let i = links.length - 1; i >= 0; i--) {
26169
- const link22 = links[i];
26170
- if (link22.href === dep && (!isCss || link22.rel === "stylesheet")) {
26171
- return;
26172
- }
26173
- }
26174
- } else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
26175
- return;
26176
- }
26177
- const link2 = document.createElement("link");
26178
- link2.rel = isCss ? "stylesheet" : scriptRel;
26179
- if (!isCss) {
26180
- link2.as = "script";
26181
- }
26182
- link2.crossOrigin = "";
26183
- link2.href = dep;
26184
- if (cspNonce) {
26185
- link2.setAttribute("nonce", cspNonce);
26186
- }
26187
- document.head.appendChild(link2);
26188
- if (isCss) {
26189
- return new Promise((res, rej) => {
26190
- link2.addEventListener("load", res);
26191
- link2.addEventListener(
26192
- "error",
26193
- () => rej(new Error(`Unable to preload CSS for ${dep}`))
26194
- );
26195
- });
26196
- }
26197
- })
26198
- );
26199
- }
26200
- function handlePreloadError(err) {
26201
- const e = new Event("vite:preloadError", {
26202
- cancelable: true
26203
- });
26204
- e.payload = err;
26205
- window.dispatchEvent(e);
26206
- if (!e.defaultPrevented) {
26207
- throw err;
26208
- }
26209
- }
26210
- return promise.then((res) => {
26211
- for (const item of res || []) {
26212
- if (item.status !== "rejected") continue;
26213
- handlePreloadError(item.reason);
26214
- }
26215
- return baseModule().catch(handlePreloadError);
26216
- });
26217
- };
26218
- const PAGE_SIZE = 20;
26219
- const api$8 = window.api;
26220
- let _sessionId = "";
26221
- const useChatStore = create$1((set, get) => ({
26222
- messages: [],
26223
- streamingText: "",
26224
- isStreaming: false,
26225
- savedMessageIds: /* @__PURE__ */ new Set(),
26226
- hasMore: false,
26227
- isLoadingHistory: false,
26228
- _offset: 0,
26229
- scrollToMessageId: null,
26230
- send: async (text2, images) => {
26231
- const userMsg = {
26232
- id: crypto.randomUUID(),
26233
- role: "user",
26234
- content: text2,
26235
- images: images?.map((i) => `data:${i.mimeType};base64,${i.base64}`),
26236
- timestamp: Date.now()
26237
- };
26238
- set((s15) => ({
26239
- messages: [...s15.messages, userMsg],
26240
- streamingText: "",
26241
- isStreaming: true
26242
- }));
26243
- if (_sessionId) {
26244
- api$8.saveMessage(_sessionId, userMsg).catch(() => {
26245
- });
26246
- }
26247
- const { useUsageStore: useUsageStore2 } = await __vitePreload(async () => {
26248
- const { useUsageStore: useUsageStore3 } = await Promise.resolve().then(() => usageStore);
26249
- return { useUsageStore: useUsageStore3 };
26250
- }, true ? void 0 : void 0, import.meta.url);
26251
- useUsageStore2.getState().resetRun();
26252
- try {
26253
- const { useUIStore: useUIStore2 } = await __vitePreload(async () => {
26254
- const { useUIStore: useUIStore3 } = await Promise.resolve().then(() => uiStore);
26255
- return { useUIStore: useUIStore3 };
26256
- }, true ? void 0 : void 0, import.meta.url);
26257
- const model = useUIStore2.getState().selectedModel;
26258
- await api$8.sendMessage(text2, void 0, model, images);
26259
- } catch {
26260
- }
26261
- },
26262
- stop: async () => {
26263
- await api$8.stopAgent();
26264
- },
26265
- appendChunk: (chunk) => {
26266
- set((s15) => ({ streamingText: s15.streamingText + chunk }));
26267
- },
26268
- finalize: (result) => {
26269
- const content2 = result.response || result.error || "No response";
26270
- const assistantMsg = {
26271
- id: crypto.randomUUID(),
26272
- role: "assistant",
26273
- content: content2,
26274
- images: result.images?.map((i) => `data:${i.mimeType};base64,${i.base64}`),
26275
- timestamp: Date.now()
26276
- };
26277
- set((s15) => ({
26278
- messages: [...s15.messages, assistantMsg],
26279
- streamingText: "",
26280
- isStreaming: false
26281
- }));
26282
- if (_sessionId) {
26283
- api$8.saveMessage(_sessionId, assistantMsg).catch(() => {
26284
- });
26285
- }
26286
- },
26287
- clear: () => {
26288
- _sessionId = "";
26289
- return set({
26290
- messages: [],
26291
- streamingText: "",
26292
- isStreaming: false,
26293
- savedMessageIds: /* @__PURE__ */ new Set(),
26294
- hasMore: false,
26295
- isLoadingHistory: false,
26296
- _offset: 0,
26297
- scrollToMessageId: null
26298
- });
26299
- },
26300
- markSaved: (messageId) => {
26301
- set((s15) => {
26302
- const next = new Set(s15.savedMessageIds);
26303
- next.add(messageId);
26304
- return { savedMessageIds: next };
26305
- });
26306
- if (_sessionId) {
26307
- api$8.markMessageSaved(_sessionId, messageId).catch(() => {
26308
- });
26309
- }
26310
- },
26311
- loadInitial: async (sessionId) => {
26312
- _sessionId = sessionId;
26313
- try {
26314
- const [count, messages, savedIds] = await Promise.all([
26315
- api$8.getMessageCount(sessionId),
26316
- api$8.loadMessages(sessionId, 0, PAGE_SIZE),
26317
- api$8.loadSavedMessageIds(sessionId)
26318
- ]);
26319
- set({
26320
- messages,
26321
- hasMore: count > PAGE_SIZE,
26322
- _offset: PAGE_SIZE,
26323
- savedMessageIds: new Set(savedIds || [])
26324
- });
26325
- } catch {
26326
- }
26327
- },
26328
- loadHistory: async () => {
26329
- const { hasMore, isLoadingHistory, _offset } = get();
26330
- if (!hasMore || isLoadingHistory || !_sessionId) return;
26331
- set({ isLoadingHistory: true });
26332
- try {
26333
- const [count, older] = await Promise.all([
26334
- api$8.getMessageCount(_sessionId),
26335
- api$8.loadMessages(_sessionId, _offset, PAGE_SIZE)
26336
- ]);
26337
- if (older.length > 0) {
26338
- set((s15) => ({
26339
- messages: [...older, ...s15.messages],
26340
- _offset: s15._offset + older.length,
26341
- hasMore: s15._offset + older.length < count,
26342
- isLoadingHistory: false
26343
- }));
26344
- } else {
26345
- set({ hasMore: false, isLoadingHistory: false });
26346
- }
26347
- } catch {
26348
- set({ isLoadingHistory: false });
26349
- }
26350
- },
26351
- requestScrollTo: (messageId) => {
26352
- set({ scrollToMessageId: messageId });
26353
- setTimeout(() => set({ scrollToMessageId: null }), 100);
26354
- }
26355
- }));
26356
26590
  const useProgressStore = create$1((set) => ({
26357
26591
  items: [],
26358
26592
  upsertItem: (item) => set((state) => {
@@ -26377,16 +26611,19 @@ const useActivityStore = create$1((set) => ({
26377
26611
  push: (event) => set((state) => {
26378
26612
  if (event.tool && event.tool.startsWith("todo-")) return state;
26379
26613
  if (event.type === "tool-result") {
26380
- const idx = findLastIndex(state.events, (e) => e.type === "tool-call" && e.tool === event.tool);
26614
+ const idx = event.toolCallId ? findLastIndex(state.events, (e) => e.type === "tool-call" && e.toolCallId === event.toolCallId) : findLastIndex(state.events, (e) => e.type === "tool-call" && e.tool === event.tool);
26381
26615
  if (idx !== -1) {
26382
26616
  const updated = [...state.events];
26383
26617
  updated[idx] = {
26384
26618
  ...updated[idx],
26619
+ // preserve original detail (tool-call params)
26385
26620
  type: "tool-result",
26386
26621
  summary: event.summary,
26387
26622
  success: event.success,
26388
26623
  error: event.error,
26389
- timestamp: event.timestamp
26624
+ timestamp: event.timestamp,
26625
+ resultDetail: event.resultDetail,
26626
+ durationMs: event.durationMs
26390
26627
  };
26391
26628
  return { events: updated };
26392
26629
  }
@@ -26402,10 +26639,10 @@ function findLastIndex(arr, pred) {
26402
26639
  }
26403
26640
  return -1;
26404
26641
  }
26405
- const api$7 = window.api;
26642
+ const api$8 = window.api;
26406
26643
  async function loadFromFramework() {
26407
26644
  try {
26408
- const data = await api$7?.getUsageTotals?.();
26645
+ const data = await api$8?.getUsageTotals?.();
26409
26646
  return data ?? null;
26410
26647
  } catch (e) {
26411
26648
  console.warn("[usage-store] Failed to load persisted totals:", e);
@@ -26415,6 +26652,10 @@ async function loadFromFramework() {
26415
26652
  const useUsageStore = create$1((set, get) => {
26416
26653
  return {
26417
26654
  // Current run
26655
+ runPromptTokens: 0,
26656
+ runCompletionTokens: 0,
26657
+ runCachedTokens: 0,
26658
+ runCacheWriteTokens: 0,
26418
26659
  runTokens: 0,
26419
26660
  runCost: 0,
26420
26661
  runCacheHitRate: 0,
@@ -26426,37 +26667,54 @@ const useUsageStore = create$1((set, get) => {
26426
26667
  // All-time totals (from framework persistence)
26427
26668
  allTimeTokens: 0,
26428
26669
  allTimePromptTokens: 0,
26670
+ allTimeCompletionTokens: 0,
26429
26671
  allTimeCachedTokens: 0,
26672
+ allTimeCacheWriteTokens: 0,
26430
26673
  allTimeCost: 0,
26431
26674
  allTimeBillableCost: 0,
26432
26675
  allTimeCalls: 0,
26433
26676
  billingSource: "none",
26434
26677
  recordCall: (event) => set((state) => {
26435
- const newTokens = event.promptTokens + event.completionTokens;
26678
+ const callTokens = event.promptTokens + event.completionTokens + event.cachedTokens;
26679
+ const cacheWrite = event.cacheWriteTokens ?? 0;
26436
26680
  const billableCost = event.billableCost ?? event.cost;
26437
- const newState = {
26438
- runTokens: state.runTokens + newTokens,
26681
+ const newRunPrompt = state.runPromptTokens + event.promptTokens;
26682
+ const newRunCached = state.runCachedTokens + event.cachedTokens;
26683
+ const totalRunInput = newRunPrompt + newRunCached;
26684
+ const runCacheHitRate = totalRunInput > 0 ? newRunCached / totalRunInput : 0;
26685
+ return {
26686
+ runPromptTokens: newRunPrompt,
26687
+ runCompletionTokens: state.runCompletionTokens + event.completionTokens,
26688
+ runCachedTokens: newRunCached,
26689
+ runCacheWriteTokens: state.runCacheWriteTokens + cacheWrite,
26690
+ runTokens: state.runTokens + callTokens,
26439
26691
  runCost: state.runCost + event.cost,
26440
- runCacheHitRate: event.cacheHitRate,
26692
+ runCacheHitRate,
26441
26693
  runCallCount: state.runCallCount + 1,
26442
- // Also accumulate to session/all-time
26443
- sessionTokens: state.sessionTokens + newTokens,
26694
+ // Session
26695
+ sessionTokens: state.sessionTokens + callTokens,
26444
26696
  sessionCost: state.sessionCost + event.cost,
26445
26697
  sessionCalls: state.sessionCalls + 1,
26446
- allTimeTokens: state.allTimeTokens + newTokens,
26698
+ // All-time
26699
+ allTimeTokens: state.allTimeTokens + callTokens,
26447
26700
  allTimePromptTokens: state.allTimePromptTokens + event.promptTokens,
26701
+ allTimeCompletionTokens: state.allTimeCompletionTokens + event.completionTokens,
26448
26702
  allTimeCachedTokens: state.allTimeCachedTokens + event.cachedTokens,
26703
+ allTimeCacheWriteTokens: state.allTimeCacheWriteTokens + cacheWrite,
26449
26704
  allTimeCost: state.allTimeCost + event.cost,
26450
26705
  allTimeBillableCost: state.allTimeBillableCost + billableCost,
26451
26706
  allTimeCalls: state.allTimeCalls + 1,
26452
26707
  billingSource: event.billingSource ?? state.billingSource
26453
26708
  };
26454
- return newState;
26455
26709
  }),
26456
26710
  completeRun: (_summary) => {
26457
26711
  },
26458
26712
  // Reset run stats - called when a NEW run starts (not when old one ends)
26459
26713
  resetRun: () => set({
26714
+ runPromptTokens: 0,
26715
+ runCompletionTokens: 0,
26716
+ runCachedTokens: 0,
26717
+ runCacheWriteTokens: 0,
26460
26718
  runTokens: 0,
26461
26719
  runCost: 0,
26462
26720
  runCacheHitRate: 0,
@@ -26464,6 +26722,10 @@ const useUsageStore = create$1((set, get) => {
26464
26722
  }),
26465
26723
  // Reset session stats (but keep all-time)
26466
26724
  resetSession: () => set({
26725
+ runPromptTokens: 0,
26726
+ runCompletionTokens: 0,
26727
+ runCachedTokens: 0,
26728
+ runCacheWriteTokens: 0,
26467
26729
  runTokens: 0,
26468
26730
  runCost: 0,
26469
26731
  runCacheHitRate: 0,
@@ -26476,26 +26738,33 @@ const useUsageStore = create$1((set, get) => {
26476
26738
  loadPersisted: async () => {
26477
26739
  const persisted = await loadFromFramework();
26478
26740
  if (persisted?.totals) {
26741
+ const t = persisted.totals;
26479
26742
  set({
26480
- allTimeTokens: persisted.totals.tokens ?? 0,
26481
- allTimePromptTokens: persisted.totals.promptTokens ?? 0,
26482
- allTimeCachedTokens: persisted.totals.cachedTokens ?? 0,
26483
- allTimeCost: persisted.totals.cost ?? 0,
26484
- allTimeBillableCost: persisted.totals.cost ?? 0,
26485
- allTimeCalls: persisted.totals.calls ?? 0,
26743
+ allTimeTokens: t.tokens ?? 0,
26744
+ allTimePromptTokens: t.promptTokens ?? 0,
26745
+ allTimeCompletionTokens: t.completionTokens ?? 0,
26746
+ allTimeCachedTokens: t.cachedTokens ?? 0,
26747
+ allTimeCacheWriteTokens: t.cacheWriteTokens ?? 0,
26748
+ allTimeCost: t.cost ?? 0,
26749
+ allTimeBillableCost: t.cost ?? 0,
26750
+ allTimeCalls: t.calls ?? 0,
26486
26751
  billingSource: "api-key",
26487
26752
  // Also restore to session totals
26488
- sessionTokens: persisted.totals.tokens ?? 0,
26489
- sessionCost: persisted.totals.cost ?? 0,
26490
- sessionCalls: persisted.totals.calls ?? 0
26753
+ sessionTokens: t.tokens ?? 0,
26754
+ sessionCost: t.cost ?? 0,
26755
+ sessionCalls: t.calls ?? 0
26491
26756
  });
26492
26757
  }
26493
26758
  },
26494
26759
  // Reset all-time totals (user-initiated)
26495
26760
  resetAllTime: () => {
26496
- api$7?.resetUsageTotals?.().catch?.(() => {
26761
+ api$8?.resetUsageTotals?.().catch?.(() => {
26497
26762
  });
26498
26763
  set({
26764
+ runPromptTokens: 0,
26765
+ runCompletionTokens: 0,
26766
+ runCachedTokens: 0,
26767
+ runCacheWriteTokens: 0,
26499
26768
  runTokens: 0,
26500
26769
  runCost: 0,
26501
26770
  runCacheHitRate: 0,
@@ -26505,7 +26774,9 @@ const useUsageStore = create$1((set, get) => {
26505
26774
  sessionCalls: 0,
26506
26775
  allTimeTokens: 0,
26507
26776
  allTimePromptTokens: 0,
26777
+ allTimeCompletionTokens: 0,
26508
26778
  allTimeCachedTokens: 0,
26779
+ allTimeCacheWriteTokens: 0,
26509
26780
  allTimeCost: 0,
26510
26781
  allTimeBillableCost: 0,
26511
26782
  billingSource: "none",
@@ -26518,13 +26789,13 @@ const usageStore = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
26518
26789
  __proto__: null,
26519
26790
  useUsageStore
26520
26791
  }, Symbol.toStringTag, { value: "Module" }));
26521
- const api$6 = window.api;
26792
+ const api$7 = window.api;
26522
26793
  const useSessionStore = create$1((set) => ({
26523
26794
  sessionId: "",
26524
26795
  projectPath: "",
26525
26796
  hasProject: false,
26526
26797
  init: async () => {
26527
- const session = await api$6.getCurrentSession();
26798
+ const session = await api$7.getCurrentSession();
26528
26799
  set({
26529
26800
  sessionId: session.sessionId,
26530
26801
  projectPath: session.projectPath,
@@ -26535,7 +26806,7 @@ const useSessionStore = create$1((set) => ({
26535
26806
  }
26536
26807
  },
26537
26808
  pickFolder: async () => {
26538
- const result = await api$6.pickFolder();
26809
+ const result = await api$7.pickFolder();
26539
26810
  if (result) {
26540
26811
  useChatStore.getState().clear();
26541
26812
  useProgressStore.getState().clear();
@@ -26556,7 +26827,7 @@ const useSessionStore = create$1((set) => ({
26556
26827
  return false;
26557
26828
  },
26558
26829
  closeProject: async () => {
26559
- await api$6.closeProject();
26830
+ await api$7.closeProject();
26560
26831
  useChatStore.getState().clear();
26561
26832
  useProgressStore.getState().clear();
26562
26833
  useActivityStore.getState().clear();
@@ -26566,7 +26837,7 @@ const useSessionStore = create$1((set) => ({
26566
26837
  set({ sessionId: "", projectPath: "", hasProject: false });
26567
26838
  }
26568
26839
  }));
26569
- const api$5 = window.api;
26840
+ const api$6 = window.api;
26570
26841
  const ROW_HEIGHT = 28;
26571
26842
  const OVERSCAN_ROWS = 10;
26572
26843
  const MAX_DROP_FILE_SIZE = 100 * 1024 * 1024;
@@ -26665,7 +26936,7 @@ function WorkspaceTree() {
26665
26936
  const parentKey = toKey(relativePath);
26666
26937
  setParentLoading(parentKey, true);
26667
26938
  try {
26668
- const children = await api$5.listTree({
26939
+ const children = await api$6.listTree({
26669
26940
  relativePath,
26670
26941
  showIgnored,
26671
26942
  limit: 2e3
@@ -26690,8 +26961,8 @@ function WorkspaceTree() {
26690
26961
  void Promise.all(dirs.map((dir) => loadChildren(dir)));
26691
26962
  }, 500);
26692
26963
  };
26693
- const unsubFileCreated = api$5.onFileCreated(scheduleRefresh);
26694
- const unsubAgentDone = api$5.onAgentDone(scheduleRefresh);
26964
+ const unsubFileCreated = api$6.onFileCreated(scheduleRefresh);
26965
+ const unsubAgentDone = api$6.onAgentDone(scheduleRefresh);
26695
26966
  return () => {
26696
26967
  if (debounceTimer) clearTimeout(debounceTimer);
26697
26968
  unsubFileCreated();
@@ -26731,7 +27002,7 @@ function WorkspaceTree() {
26731
27002
  }
26732
27003
  setSearching(true);
26733
27004
  try {
26734
- const results = await api$5.searchTree(query.trim(), { showIgnored, maxResults: 4e3 });
27005
+ const results = await api$6.searchTree(query.trim(), { showIgnored, maxResults: 4e3 });
26735
27006
  setSearchResults(results);
26736
27007
  } finally {
26737
27008
  setSearching(false);
@@ -26775,7 +27046,7 @@ function WorkspaceTree() {
26775
27046
  if (node2.type !== "file") return;
26776
27047
  const ext = (node2.name.split(".").pop() || "").toLowerCase();
26777
27048
  if (!TEXT_EXTENSIONS.has(ext)) {
26778
- api$5.openFile(node2.path);
27049
+ api$6.openFile(node2.path);
26779
27050
  return;
26780
27051
  }
26781
27052
  const normalizedNodePath = normalizePath(node2.path);
@@ -26800,14 +27071,14 @@ function WorkspaceTree() {
26800
27071
  });
26801
27072
  }, [data, openPreview]);
26802
27073
  const createArtifact = reactExports.useCallback(async (node2) => {
26803
- await api$5.createArtifactFromFile(node2.path);
27074
+ await api$6.createArtifactFromFile(node2.path);
26804
27075
  await refreshEntities();
26805
27076
  }, [refreshEntities]);
26806
27077
  const handleTrashClick = reactExports.useCallback(async (node2) => {
26807
27078
  if (confirmTrashPath === node2.relativePath) {
26808
27079
  if (confirmTimerRef.current) clearTimeout(confirmTimerRef.current);
26809
27080
  setConfirmTrashPath(null);
26810
- const result = await api$5.trashFile(node2.path);
27081
+ const result = await api$6.trashFile(node2.path);
26811
27082
  if (result.success) {
26812
27083
  const parentRelPath = node2.relativePath.includes("/") ? node2.relativePath.slice(0, node2.relativePath.lastIndexOf("/")) : "";
26813
27084
  await loadChildren(parentRelPath);
@@ -26856,7 +27127,7 @@ function WorkspaceTree() {
26856
27127
  return;
26857
27128
  }
26858
27129
  const relPath = creating.parentDir ? `${creating.parentDir}/${createValue.trim()}` : createValue.trim();
26859
- const result = creating.type === "file" ? await api$5.createFile(relPath) : await api$5.createDir(relPath);
27130
+ const result = creating.type === "file" ? await api$6.createFile(relPath) : await api$6.createDir(relPath);
26860
27131
  if (result.success) {
26861
27132
  await loadChildren(creating.parentDir);
26862
27133
  }
@@ -26883,7 +27154,7 @@ function WorkspaceTree() {
26883
27154
  const parentDir = renaming.includes("/") ? renaming.slice(0, renaming.lastIndexOf("/")) : "";
26884
27155
  const newRelPath = parentDir ? `${parentDir}/${renameValue.trim()}` : renameValue.trim();
26885
27156
  if (newRelPath !== renaming) {
26886
- await api$5.renameFile(renaming, newRelPath);
27157
+ await api$6.renameFile(renaming, newRelPath);
26887
27158
  await loadChildren(parentDir);
26888
27159
  }
26889
27160
  setRenaming(null);
@@ -26925,7 +27196,7 @@ function WorkspaceTree() {
26925
27196
  const base64 = btoa(
26926
27197
  new Uint8Array(buffer).reduce((data2, byte) => data2 + String.fromCharCode(byte), "")
26927
27198
  );
26928
- await api$5.dropToDir(file.name, base64, targetRelPath);
27199
+ await api$6.dropToDir(file.name, base64, targetRelPath);
26929
27200
  }
26930
27201
  await loadChildren(targetRelPath);
26931
27202
  }, [getDropDir, loadChildren]);
@@ -26951,7 +27222,7 @@ function WorkspaceTree() {
26951
27222
  const base64 = btoa(
26952
27223
  new Uint8Array(buffer).reduce((data2, byte) => data2 + String.fromCharCode(byte), "")
26953
27224
  );
26954
- await api$5.dropToDir(file.name, base64, "");
27225
+ await api$6.dropToDir(file.name, base64, "");
26955
27226
  }
26956
27227
  await loadChildren("");
26957
27228
  }, [loadChildren]);
@@ -27013,7 +27284,7 @@ function WorkspaceTree() {
27013
27284
  "div",
27014
27285
  {
27015
27286
  className: "flex items-center gap-1 rounded px-1.5 h-7 text-xs bg-[var(--color-accent)]/10",
27016
- style: { paddingLeft: `${depth * 14 + 6}px` },
27287
+ style: { paddingLeft: `${depth * 1.1 + 0.4}em` },
27017
27288
  children: [
27018
27289
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3 shrink-0" }),
27019
27290
  creating.type === "directory" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Folder, { size: 12, className: "shrink-0 t-text-warning" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(File, { size: 12, className: "shrink-0 t-text-muted" }),
@@ -27040,7 +27311,7 @@ function WorkspaceTree() {
27040
27311
  "div",
27041
27312
  {
27042
27313
  className: "flex items-center gap-1 text-xs t-text-muted h-7",
27043
- style: { paddingLeft: `${row.depth * 14 + 26}px` },
27314
+ style: { paddingLeft: `${row.depth * 1.1 + 2}em` },
27044
27315
  children: [
27045
27316
  /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 11, className: "animate-spin" }),
27046
27317
  "Loading..."
@@ -27173,6 +27444,7 @@ function WorkspaceTree() {
27173
27444
  value: query,
27174
27445
  onChange: (e) => setQuery(e.target.value),
27175
27446
  placeholder: "Filter files...",
27447
+ "aria-label": "Filter files",
27176
27448
  className: "w-full bg-transparent text-xs outline-none t-focus-ring t-text"
27177
27449
  }
27178
27450
  )
@@ -27686,6 +27958,7 @@ function SkillsContent() {
27686
27958
  onClick: () => fileInputRef.current?.click(),
27687
27959
  className: "no-drag flex items-center gap-1 px-1.5 py-0.5 text-[10px] t-text-muted hover:t-text-accent-soft transition-colors rounded t-bg-hover",
27688
27960
  title: "Upload skill (.zip)",
27961
+ "aria-label": "Install skill from zip file",
27689
27962
  children: [
27690
27963
  /* @__PURE__ */ jsxRuntimeExports.jsx(CloudUpload, { size: 11 }),
27691
27964
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Install" })
@@ -27713,6 +27986,7 @@ function SkillsContent() {
27713
27986
  value: searchQuery,
27714
27987
  onChange: (e) => setSearchQuery(e.target.value),
27715
27988
  placeholder: "Search skills or tags...",
27989
+ "aria-label": "Search skills",
27716
27990
  className: "w-full pl-6 pr-2 py-1 text-[11px] rounded border t-border t-bg-surface t-text focus:outline-none focus:border-[var(--color-accent-soft)]"
27717
27991
  }
27718
27992
  )
@@ -27800,13 +28074,30 @@ function EntityTabs() {
27800
28074
  return null;
27801
28075
  }
27802
28076
  };
28077
+ const handleTabKeyDown = (e) => {
28078
+ const tabKeys = tabs.map((t) => t.key);
28079
+ const idx = tabKeys.indexOf(leftTab);
28080
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
28081
+ e.preventDefault();
28082
+ const next = tabKeys[(idx + 1) % tabKeys.length];
28083
+ setLeftTab(next);
28084
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
28085
+ e.preventDefault();
28086
+ const prev = tabKeys[(idx - 1 + tabKeys.length) % tabKeys.length];
28087
+ setLeftTab(prev);
28088
+ }
28089
+ };
27803
28090
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex flex-col min-h-0", children: [
27804
28091
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b t-border px-1", role: "tablist", "aria-label": "Content categories", children: tabs.map(({ key, label, icon: Icon2 }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
27805
28092
  "button",
27806
28093
  {
27807
28094
  role: "tab",
28095
+ id: `tab-${key}`,
27808
28096
  "aria-selected": leftTab === key,
28097
+ "aria-controls": `tabpanel-${key}`,
28098
+ tabIndex: leftTab === key ? 0 : -1,
27809
28099
  onClick: () => setLeftTab(key),
28100
+ onKeyDown: handleTabKeyDown,
27810
28101
  className: `no-drag flex items-center gap-1 px-2 py-2 text-[11px] font-medium border-b-2 transition-colors ${leftTab === key ? "border-[var(--color-accent-soft)] t-text-accent" : "border-transparent t-text-muted hover:t-text-secondary"}`,
27811
28102
  title: label,
27812
28103
  children: [
@@ -27816,7 +28107,16 @@ function EntityTabs() {
27816
28107
  },
27817
28108
  key
27818
28109
  )) }),
27819
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-hidden flex flex-col", children: renderContent() })
28110
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28111
+ "div",
28112
+ {
28113
+ role: "tabpanel",
28114
+ id: `tabpanel-${leftTab}`,
28115
+ "aria-labelledby": `tab-${leftTab}`,
28116
+ className: "flex-1 min-h-0 overflow-hidden flex flex-col",
28117
+ children: renderContent()
28118
+ }
28119
+ )
27820
28120
  ] });
27821
28121
  }
27822
28122
  function QuickAction({
@@ -28001,6 +28301,166 @@ function LiteratureSidebar() {
28001
28301
  ] })
28002
28302
  ] });
28003
28303
  }
28304
+ const useComputeStore = create$1((set) => ({
28305
+ runs: /* @__PURE__ */ new Map(),
28306
+ environment: null,
28307
+ updateRun: (runId, data) => set((state) => {
28308
+ const next = new Map(state.runs);
28309
+ const existing = next.get(runId);
28310
+ if (existing) {
28311
+ next.set(runId, { ...existing, ...data });
28312
+ } else {
28313
+ next.set(runId, {
28314
+ runId,
28315
+ status: "running",
28316
+ currentPhase: "full",
28317
+ command: "",
28318
+ sandbox: "process",
28319
+ weight: "heavy",
28320
+ elapsedSeconds: 0,
28321
+ outputBytes: 0,
28322
+ outputLines: 0,
28323
+ stalled: false,
28324
+ outputTail: "",
28325
+ ...data
28326
+ });
28327
+ }
28328
+ return { runs: next };
28329
+ }),
28330
+ removeRun: (runId) => set((state) => {
28331
+ const next = new Map(state.runs);
28332
+ next.delete(runId);
28333
+ return { runs: next };
28334
+ }),
28335
+ setEnvironment: (env2) => set({ environment: env2 }),
28336
+ reset: () => set({ runs: /* @__PURE__ */ new Map(), environment: null })
28337
+ }));
28338
+ function useActiveRuns() {
28339
+ const runs = useComputeStore((s15) => s15.runs);
28340
+ return Array.from(runs.values()).filter(
28341
+ (r) => r.status === "running" || r.status === "stalled"
28342
+ );
28343
+ }
28344
+ function useRecentRuns() {
28345
+ const runs = useComputeStore((s15) => s15.runs);
28346
+ return Array.from(runs.values()).filter((r) => r.status !== "running" && r.status !== "stalled").sort((a, b2) => {
28347
+ const aTime = a.startedAt ? new Date(a.startedAt).getTime() : 0;
28348
+ const bTime = b2.startedAt ? new Date(b2.startedAt).getTime() : 0;
28349
+ return bTime - aTime;
28350
+ }).slice(0, 20);
28351
+ }
28352
+ function useActiveRunCount() {
28353
+ const runs = useComputeStore((s15) => s15.runs);
28354
+ let count = 0;
28355
+ for (const r of runs.values()) {
28356
+ if (r.status === "running" || r.status === "stalled") count++;
28357
+ }
28358
+ return count;
28359
+ }
28360
+ function ResourceBar({ label, percent }) {
28361
+ const clamped = Math.min(100, Math.max(0, percent));
28362
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
28363
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted w-11 text-right shrink-0", children: label }),
28364
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 h-1 rounded-full t-bg-elevated overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
28365
+ "div",
28366
+ {
28367
+ className: `h-full rounded-full transition-all duration-700 ${clamped > 85 ? "" : "t-gradient-accent-h"}`,
28368
+ style: {
28369
+ width: `${clamped}%`,
28370
+ ...clamped > 85 ? { background: "var(--color-text-muted)", opacity: 0.6 } : { opacity: 0.5 }
28371
+ }
28372
+ }
28373
+ ) }),
28374
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] t-text-muted tabular-nums w-7 shrink-0", children: [
28375
+ clamped,
28376
+ "%"
28377
+ ] })
28378
+ ] });
28379
+ }
28380
+ function SandboxExplanation({ environment }) {
28381
+ const [expanded, setExpanded] = reactExports.useState(false);
28382
+ const isDocker = environment.sandbox === "docker";
28383
+ const isMac = environment.os === "darwin";
28384
+ const hasMLX = environment.mlxAvailable;
28385
+ let explanation;
28386
+ if (isDocker && isMac) {
28387
+ explanation = "Docker available. Note: Docker on macOS lacks GPU passthrough. Switch to Process sandbox for MLX/Metal GPU access if needed.";
28388
+ } else if (isDocker) {
28389
+ explanation = "Docker available. Runs execute in isolated containers with resource limits.";
28390
+ } else if (isMac && hasMLX) {
28391
+ explanation = "Process sandbox uses Python virtual environments with process-group isolation. Direct MLX/Metal GPU access for ML training.";
28392
+ } else if (isMac) {
28393
+ explanation = "Process sandbox uses Python virtual environments. Install Docker for stronger container-based isolation.";
28394
+ } else {
28395
+ explanation = "Process sandbox uses Python virtual environments with process-group isolation.";
28396
+ }
28397
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 pt-2 border-t t-border-subtle", children: [
28398
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
28399
+ "button",
28400
+ {
28401
+ onClick: () => setExpanded(!expanded),
28402
+ className: "flex items-center gap-1 text-[10px] t-text-muted hover:t-text-secondary transition-colors w-full text-left",
28403
+ children: [
28404
+ expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 10 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 10 }),
28405
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
28406
+ "Sandbox: ",
28407
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-secondary", children: isDocker ? "Docker" : "Process (venv)" })
28408
+ ] })
28409
+ ]
28410
+ }
28411
+ ),
28412
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-1.5 ml-3.5 text-[10px] t-text-muted leading-relaxed", children: explanation })
28413
+ ] });
28414
+ }
28415
+ function ComputeSidebar() {
28416
+ const environment = useComputeStore((s15) => s15.environment);
28417
+ const activeCount = useActiveRunCount();
28418
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex flex-col min-h-0", children: [
28419
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-2 pt-2 pb-2", children: [
28420
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] t-text-accent-soft uppercase tracking-wider px-3 pb-1.5 font-medium", children: "Compute Target" }),
28421
+ environment ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2.5 rounded-lg t-bg-surface border t-border-subtle", children: [
28422
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-2", children: [
28423
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-1.5 h-1.5 rounded-full shrink-0 ${activeCount > 0 ? "bg-[var(--color-accent)]" : "bg-[var(--color-text-muted)]"}`, style: { opacity: activeCount > 0 ? 1 : 0.5 } }),
28424
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs t-text font-medium", children: "Local Machine" })
28425
+ ] }),
28426
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] t-text-muted leading-relaxed space-y-0.5", children: [
28427
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
28428
+ environment.gpu || `${environment.os} ${environment.arch}`,
28429
+ " · ",
28430
+ Math.round(environment.totalMemoryMb / 1024),
28431
+ " GB"
28432
+ ] }),
28433
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 flex-wrap", children: [
28434
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Python" }),
28435
+ environment.mlxAvailable && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1 py-px rounded text-[9px] bg-[var(--color-accent-soft)]/10 t-text-accent", children: "MLX" }),
28436
+ environment.sandbox === "docker" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1 py-px rounded text-[9px] t-bg-elevated t-text-muted", children: "Docker" })
28437
+ ] })
28438
+ ] }),
28439
+ (environment.freeMemoryMb !== void 0 || environment.freeDiskMb !== void 0) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2.5 space-y-1.5", children: environment.freeMemoryMb !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
28440
+ ResourceBar,
28441
+ {
28442
+ label: "Memory",
28443
+ percent: Math.round((1 - environment.freeMemoryMb / environment.totalMemoryMb) * 100)
28444
+ }
28445
+ ) }),
28446
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SandboxExplanation, { environment }),
28447
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 pt-2 border-t t-border-subtle text-[10px] t-text-muted", children: activeCount > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
28448
+ activeCount,
28449
+ " running"
28450
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Ready" }) })
28451
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-4 rounded-lg t-bg-surface border t-border-subtle", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 t-text-muted", children: [
28452
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Monitor, { size: 14, className: "opacity-40" }),
28453
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px]", children: "Detecting environment..." })
28454
+ ] }) }),
28455
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1.5 px-3 py-2 rounded-lg border border-dashed t-border-subtle cursor-default", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted opacity-60", children: "+ Add remote target" }) })
28456
+ ] }),
28457
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-3 border-t t-border" }),
28458
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto px-3 py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 text-[10px] t-text-muted leading-relaxed", children: [
28459
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Info$1, { size: 12, className: "shrink-0 mt-0.5 opacity-40" }),
28460
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Ask the agent to run scripts, train models, or process data. Code executes in a sandboxed environment with progress tracking and failure analysis." })
28461
+ ] }) })
28462
+ ] });
28463
+ }
28004
28464
  function UserProfile() {
28005
28465
  const projectPath = useSessionStore((s15) => s15.projectPath);
28006
28466
  const pickFolder = useSessionStore((s15) => s15.pickFolder);
@@ -28024,25 +28484,6 @@ function UserProfile() {
28024
28484
  }
28025
28485
  );
28026
28486
  }
28027
- const REASONING_MODELS = [
28028
- "gpt-5.4",
28029
- "gpt-5.4-mini",
28030
- "gpt-5.4-nano",
28031
- "claude-opus-4-6"
28032
- ];
28033
- const SUPPORTED_MODELS = [
28034
- // GPT
28035
- { id: "gpt-5.4", label: "GPT-5.4", provider: "OpenAI" },
28036
- { id: "gpt-5.4-mini", label: "GPT-5.4 Mini", provider: "OpenAI" },
28037
- { id: "gpt-5.4-nano", label: "GPT-5.4 Nano", provider: "OpenAI" },
28038
- { id: "gpt-4o", label: "GPT-4o", provider: "OpenAI" },
28039
- // Anthropic Claude 4.6
28040
- { id: "claude-opus-4-6", label: "Claude Opus 4.6", provider: "Anthropic" },
28041
- // Anthropic Claude 4.5
28042
- { id: "claude-opus-4-5-20251101", label: "Claude Opus 4.5", provider: "Anthropic" },
28043
- { id: "claude-sonnet-4-5-20250929", label: "Claude Sonnet 4.5", provider: "Anthropic" },
28044
- { id: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5", provider: "Anthropic" }
28045
- ];
28046
28487
  const providers = [...new Set(SUPPORTED_MODELS.map((m) => m.provider))];
28047
28488
  const groupedModels = {};
28048
28489
  for (const p of providers) {
@@ -28051,6 +28492,8 @@ for (const p of providers) {
28051
28492
  function ModelSelector$1({ selectedModel, onSelectModel }) {
28052
28493
  const [open, setOpen] = reactExports.useState(false);
28053
28494
  const [anthropicStatus, setAnthropicStatus] = reactExports.useState(null);
28495
+ const [codexStatus, setCodexStatus] = reactExports.useState(null);
28496
+ const [codexLoggingIn, setCodexLoggingIn] = reactExports.useState(false);
28054
28497
  const [showAnthropicDialog, setShowAnthropicDialog] = reactExports.useState(false);
28055
28498
  const [showOpenAIDialog, setShowOpenAIDialog] = reactExports.useState(false);
28056
28499
  const ref = reactExports.useRef(null);
@@ -28064,6 +28507,14 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28064
28507
  setAnthropicStatus(null);
28065
28508
  }
28066
28509
  }, [api2]);
28510
+ const refreshCodexStatus = reactExports.useCallback(async () => {
28511
+ try {
28512
+ const status = await api2?.getOpenAICodexStatus?.();
28513
+ setCodexStatus(status ?? null);
28514
+ } catch {
28515
+ setCodexStatus(null);
28516
+ }
28517
+ }, [api2]);
28067
28518
  reactExports.useEffect(() => {
28068
28519
  if (!open) return;
28069
28520
  const handler = (e) => {
@@ -28084,13 +28535,47 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28084
28535
  }, [open]);
28085
28536
  reactExports.useEffect(() => {
28086
28537
  refreshAnthropicStatus();
28538
+ refreshCodexStatus();
28087
28539
  const unsub = api2?.onAnthropicAuthStatus?.((status) => setAnthropicStatus(status));
28088
28540
  return () => {
28089
28541
  if (typeof unsub === "function") unsub();
28090
28542
  };
28091
- }, [api2, refreshAnthropicStatus]);
28543
+ }, [api2, refreshAnthropicStatus, refreshCodexStatus]);
28544
+ const handleCodexLogin = async () => {
28545
+ setCodexLoggingIn(true);
28546
+ try {
28547
+ const result = await api2?.openaiCodexLogin?.();
28548
+ if (result?.success) {
28549
+ await refreshCodexStatus();
28550
+ } else {
28551
+ console.error("[ModelSelector] Codex login failed:", result?.error);
28552
+ }
28553
+ } catch (err) {
28554
+ console.error("[ModelSelector] Codex login error:", err);
28555
+ } finally {
28556
+ setCodexLoggingIn(false);
28557
+ }
28558
+ };
28559
+ const handleCodexLogout = async () => {
28560
+ await api2?.openaiCodexLogout?.();
28561
+ await refreshCodexStatus();
28562
+ };
28092
28563
  const handleModelSelect = async (model) => {
28093
- if (model.provider === "OpenAI") {
28564
+ const { provider } = parseModelKey(model.id);
28565
+ if (provider === "openai-codex") {
28566
+ if (!codexStatus?.isLoggedIn) {
28567
+ await handleCodexLogin();
28568
+ const status = await api2?.getOpenAICodexStatus?.();
28569
+ if (!status?.isLoggedIn) {
28570
+ setOpen(false);
28571
+ return;
28572
+ }
28573
+ }
28574
+ onSelectModel(model.id);
28575
+ setOpen(false);
28576
+ return;
28577
+ }
28578
+ if (provider === "openai") {
28094
28579
  const status = await api2?.getOpenAIAuthStatus?.();
28095
28580
  if (!status?.hasApiKey) {
28096
28581
  setShowOpenAIDialog(true);
@@ -28098,7 +28583,7 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28098
28583
  return;
28099
28584
  }
28100
28585
  }
28101
- if (model.provider === "Anthropic") {
28586
+ if (provider === "anthropic") {
28102
28587
  const status = await api2?.getAnthropicAuthStatus?.();
28103
28588
  if (!status?.hasApiKeyFallback) {
28104
28589
  setShowAnthropicDialog(true);
@@ -28110,18 +28595,27 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28110
28595
  setOpen(false);
28111
28596
  refreshAnthropicStatus();
28112
28597
  };
28113
- const authBadge = current?.provider === "Anthropic" ? anthropicStatus?.authMode === "api-key" ? "api" : "auth" : null;
28598
+ const { provider: currentProvider } = current ? parseModelKey(current.id) : { provider: "" };
28599
+ const authBadge = currentProvider === "anthropic" ? anthropicStatus?.authMode === "api-key" ? "api" : "auth" : currentProvider === "openai-codex" ? "sub" : currentProvider === "openai" ? "api" : null;
28114
28600
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref, className: "relative", children: [
28115
28601
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
28116
28602
  "button",
28117
28603
  {
28118
28604
  onClick: () => setOpen(!open),
28119
- className: "no-drag flex items-center gap-1.5 px-2 py-1.5 rounded-lg t-text-secondary text-xs font-medium t-bg-hover transition-colors",
28120
- title: "Select model",
28605
+ className: "no-drag group relative flex items-center gap-1.5 px-2 py-1.5 rounded-lg t-text-secondary text-xs font-medium t-bg-hover transition-colors",
28121
28606
  "aria-label": "Select model",
28122
28607
  "aria-haspopup": "listbox",
28123
28608
  "aria-expanded": open,
28124
28609
  children: [
28610
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28611
+ "span",
28612
+ {
28613
+ role: "tooltip",
28614
+ className: "pointer-events-none absolute left-1/2 -translate-x-1/2 top-full mt-1 px-2 py-0.5 rounded text-[10px] t-bg-elevated t-text-secondary border t-border shadow-lg whitespace-nowrap opacity-0 group-hover:opacity-100 z-50",
28615
+ style: { transition: "opacity 0.15s ease", transitionDelay: "0.2s" },
28616
+ children: "Select model"
28617
+ }
28618
+ ),
28125
28619
  /* @__PURE__ */ jsxRuntimeExports.jsx(Cpu, { size: 14 }),
28126
28620
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate max-w-[100px]", children: current?.label || selectedModel }),
28127
28621
  authBadge && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] px-1 rounded border t-border t-text-muted uppercase", children: authBadge }),
@@ -28131,7 +28625,39 @@ function ModelSelector$1({ selectedModel, onSelectModel }) {
28131
28625
  ),
28132
28626
  open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-full left-0 mt-1 w-64 max-h-80 overflow-y-auto rounded-xl border t-border t-bg-surface shadow-xl z-50", role: "listbox", "aria-label": "Available models", children: providers.map((provider) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
28133
28627
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider t-text-muted sticky top-0 t-bg-surface", children: provider }),
28134
- (provider === "OpenAI" || provider === "Anthropic") && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-1 text-[11px] t-text-muted", children: "API Key Only" }),
28628
+ provider === "ChatGPT Subscription" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 pb-1 flex items-center justify-between", children: [
28629
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] t-text-muted", children: codexStatus?.isLoggedIn ? "Signed in" : "OAuth required" }),
28630
+ codexStatus?.isLoggedIn ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
28631
+ "button",
28632
+ {
28633
+ onClick: (e) => {
28634
+ e.stopPropagation();
28635
+ handleCodexLogout();
28636
+ },
28637
+ className: "text-[10px] t-text-muted hover:t-text flex items-center gap-0.5",
28638
+ children: [
28639
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LogOut, { size: 10 }),
28640
+ " Sign out"
28641
+ ]
28642
+ }
28643
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
28644
+ "button",
28645
+ {
28646
+ onClick: (e) => {
28647
+ e.stopPropagation();
28648
+ handleCodexLogin();
28649
+ },
28650
+ disabled: codexLoggingIn,
28651
+ className: "text-[10px] t-text-accent flex items-center gap-0.5 hover:opacity-80 disabled:opacity-50",
28652
+ children: [
28653
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LogIn, { size: 10 }),
28654
+ " ",
28655
+ codexLoggingIn ? "Signing in..." : "Sign in"
28656
+ ]
28657
+ }
28658
+ )
28659
+ ] }),
28660
+ (provider === "OpenAI" || provider === "Anthropic") && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 pb-1 text-[11px] t-text-muted", children: "API Key" }),
28135
28661
  groupedModels[provider].map((model) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
28136
28662
  "button",
28137
28663
  {
@@ -28185,7 +28711,39 @@ function ApiKeyDialog({
28185
28711
  const [saving, setSaving] = reactExports.useState(false);
28186
28712
  const [error, setError] = reactExports.useState(null);
28187
28713
  const [showKey, setShowKey] = reactExports.useState(false);
28714
+ const dialogRef = reactExports.useRef(null);
28188
28715
  const api2 = window.api;
28716
+ reactExports.useEffect(() => {
28717
+ const dialog = dialogRef.current;
28718
+ if (!dialog) return;
28719
+ const focusable = dialog.querySelectorAll(
28720
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
28721
+ );
28722
+ if (focusable.length === 0) return;
28723
+ const first = focusable[0];
28724
+ const last = focusable[focusable.length - 1];
28725
+ first.focus();
28726
+ const handler = (e) => {
28727
+ if (e.key === "Escape") {
28728
+ onClose();
28729
+ return;
28730
+ }
28731
+ if (e.key !== "Tab") return;
28732
+ if (e.shiftKey) {
28733
+ if (document.activeElement === first) {
28734
+ e.preventDefault();
28735
+ last.focus();
28736
+ }
28737
+ } else {
28738
+ if (document.activeElement === last) {
28739
+ e.preventDefault();
28740
+ first.focus();
28741
+ }
28742
+ }
28743
+ };
28744
+ dialog.addEventListener("keydown", handler);
28745
+ return () => dialog.removeEventListener("keydown", handler);
28746
+ }, []);
28189
28747
  const handleSave = async () => {
28190
28748
  const trimmed = value.trim();
28191
28749
  if (!trimmed) {
@@ -28203,7 +28761,7 @@ function ApiKeyDialog({
28203
28761
  setSaving(false);
28204
28762
  }
28205
28763
  };
28206
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[90] flex items-center justify-center bg-black/40 p-4", role: "dialog", "aria-modal": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full max-w-md rounded-xl border t-border t-bg-surface shadow-2xl p-4 space-y-3", children: [
28764
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-[90] flex items-center justify-center bg-black/40 p-4", role: "dialog", "aria-modal": "true", "aria-label": `${provider} API Key Required`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: dialogRef, className: "w-full max-w-md rounded-xl border t-border t-bg-surface shadow-2xl p-4 space-y-3", children: [
28207
28765
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
28208
28766
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm font-semibold t-text", children: [
28209
28767
  provider,
@@ -28229,6 +28787,7 @@ function ApiKeyDialog({
28229
28787
  onKeyDown: (e) => {
28230
28788
  if (e.key === "Enter") handleSave();
28231
28789
  },
28790
+ "aria-label": `${provider} API key`,
28232
28791
  autoFocus: true
28233
28792
  }
28234
28793
  ),
@@ -28248,7 +28807,7 @@ function ApiKeyDialog({
28248
28807
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "px-1 rounded t-bg-surface", children: "~/.research-copilot/config.json" }),
28249
28808
  ". No restart needed."
28250
28809
  ] }),
28251
- error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400", children: error }),
28810
+ error && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400", role: "alert", children: error }),
28252
28811
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-end gap-2", children: [
28253
28812
  /* @__PURE__ */ jsxRuntimeExports.jsx(
28254
28813
  "button",
@@ -28295,13 +28854,27 @@ const COLORS = {
28295
28854
  };
28296
28855
  function ReasoningToggle$1({ selectedModel, reasoningEffort, onChangeEffort }) {
28297
28856
  if (!REASONING_MODELS.includes(selectedModel)) return null;
28298
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
28857
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
28299
28858
  "button",
28300
28859
  {
28301
28860
  onClick: () => onChangeEffort(CYCLE[reasoningEffort]),
28302
- className: "no-drag p-1.5 rounded-lg t-bg-hover transition-colors",
28303
- title: `Reasoning: ${reasoningEffort}`,
28304
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Lightbulb, { size: 16, className: COLORS[reasoningEffort] })
28861
+ className: "no-drag group relative p-2.5 rounded-lg t-bg-hover transition-colors",
28862
+ "aria-label": `Reasoning effort: ${reasoningEffort}`,
28863
+ children: [
28864
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Lightbulb, { size: 16, className: COLORS[reasoningEffort] }),
28865
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
28866
+ "span",
28867
+ {
28868
+ role: "tooltip",
28869
+ className: "pointer-events-none absolute left-1/2 -translate-x-1/2 top-full mt-1 px-2 py-0.5 rounded text-[10px] t-bg-elevated t-text-secondary border t-border shadow-lg whitespace-nowrap opacity-0 group-hover:opacity-100 z-50",
28870
+ style: { transition: "opacity 0.15s ease", transitionDelay: "0.2s" },
28871
+ children: [
28872
+ "Reasoning: ",
28873
+ reasoningEffort
28874
+ ]
28875
+ }
28876
+ )
28877
+ ]
28305
28878
  }
28306
28879
  );
28307
28880
  }
@@ -28318,45 +28891,87 @@ function ReasoningToggle() {
28318
28891
  }
28319
28892
  );
28320
28893
  }
28894
+ function ToolbarButton({ onClick, tooltip, children }) {
28895
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
28896
+ "button",
28897
+ {
28898
+ onClick,
28899
+ "aria-label": tooltip,
28900
+ className: "no-drag group relative p-2.5 rounded-lg t-text-muted t-bg-hover transition-colors",
28901
+ children: [
28902
+ children,
28903
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
28904
+ "span",
28905
+ {
28906
+ role: "tooltip",
28907
+ className: "pointer-events-none absolute left-1/2 -translate-x-1/2 top-full mt-1 px-2 py-0.5 rounded text-[10px] t-bg-elevated t-text-secondary border t-border shadow-lg whitespace-nowrap opacity-0 group-hover:opacity-100 z-50",
28908
+ style: { transition: "opacity 0.15s ease", transitionDelay: "0.2s" },
28909
+ children: tooltip
28910
+ }
28911
+ )
28912
+ ]
28913
+ }
28914
+ );
28915
+ }
28321
28916
  function LeftSidebar() {
28322
28917
  const theme = useUIStore((s15) => s15.theme);
28323
28918
  const toggleTheme = useUIStore((s15) => s15.toggleTheme);
28324
28919
  const centerView = useUIStore((s15) => s15.centerView);
28920
+ const noContextShownRef = reactExports.useRef(false);
28921
+ const handleResetContext = reactExports.useCallback(async () => {
28922
+ const messages = useChatStore.getState().messages;
28923
+ const hasContext = messages.some((m) => m.role === "user" || m.role === "assistant");
28924
+ if (!hasContext) {
28925
+ if (!noContextShownRef.current) {
28926
+ noContextShownRef.current = true;
28927
+ useChatStore.getState().insertContextReset();
28928
+ useChatStore.setState((s15) => {
28929
+ const msgs = [...s15.messages];
28930
+ const last = msgs[msgs.length - 1];
28931
+ if (last?.role === "system") {
28932
+ msgs[msgs.length - 1] = { ...last, content: "No context to reset yet." };
28933
+ }
28934
+ return { messages: msgs };
28935
+ });
28936
+ }
28937
+ return;
28938
+ }
28939
+ await window.api.clearSessionMemory();
28940
+ useChatStore.getState().insertContextReset();
28941
+ noContextShownRef.current = false;
28942
+ }, []);
28325
28943
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { className: "w-80 flex flex-col border-r t-border t-bg-base pt-10", children: [
28326
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 pb-3 flex items-center justify-between", children: [
28944
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("nav", { "aria-label": "Sidebar tools", className: "px-4 pb-3 flex items-center justify-between", children: [
28327
28945
  /* @__PURE__ */ jsxRuntimeExports.jsx(ModelSelector, {}),
28328
28946
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
28329
28947
  /* @__PURE__ */ jsxRuntimeExports.jsx(ReasoningToggle, {}),
28330
28948
  /* @__PURE__ */ jsxRuntimeExports.jsx(
28331
- "button",
28949
+ ToolbarButton,
28332
28950
  {
28333
- onClick: () => window.api.clearSessionMemory(),
28334
- className: "no-drag p-1.5 rounded-lg t-text-muted t-bg-hover transition-colors",
28335
- title: "Clear session memory",
28336
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Eraser, { size: 16 })
28951
+ onClick: handleResetContext,
28952
+ tooltip: "Reset AI context",
28953
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 16 })
28337
28954
  }
28338
28955
  ),
28339
28956
  /* @__PURE__ */ jsxRuntimeExports.jsx(
28340
- "button",
28957
+ ToolbarButton,
28341
28958
  {
28342
28959
  onClick: toggleTheme,
28343
- className: "no-drag p-1.5 rounded-lg t-text-muted t-bg-hover transition-colors",
28344
- title: `Switch to ${theme === "dark" ? "light" : "dark"} mode`,
28960
+ tooltip: `${theme === "dark" ? "Light" : "Dark"} mode`,
28345
28961
  children: theme === "dark" ? /* @__PURE__ */ jsxRuntimeExports.jsx(Sun, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Moon, { size: 16 })
28346
28962
  }
28347
28963
  ),
28348
28964
  /* @__PURE__ */ jsxRuntimeExports.jsx(
28349
- "button",
28965
+ ToolbarButton,
28350
28966
  {
28351
28967
  onClick: () => useUIStore.getState().toggleTerminal(),
28352
- className: "no-drag p-1.5 rounded-lg t-text-muted t-bg-hover transition-colors",
28353
- title: "Toggle terminal (⌘`)",
28968
+ tooltip: "Terminal ⌘`",
28354
28969
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 16 })
28355
28970
  }
28356
28971
  )
28357
28972
  ] })
28358
28973
  ] }),
28359
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: centerView === "literature" ? /* @__PURE__ */ jsxRuntimeExports.jsx(LiteratureSidebar, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(EntityTabs, {}) }),
28974
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: centerView === "literature" ? /* @__PURE__ */ jsxRuntimeExports.jsx(LiteratureSidebar, {}) : centerView === "compute" && window.api?.isComputeEnabled?.() ? /* @__PURE__ */ jsxRuntimeExports.jsx(ComputeSidebar, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(EntityTabs, {}) }),
28360
28975
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t t-border p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserProfile, {}) })
28361
28976
  ] });
28362
28977
  }
@@ -28367,17 +28982,11 @@ const suggestions = [
28367
28982
  { icon: Sparkles, label: "Brainstorm ideas", prompt: "Help me brainstorm ideas for " }
28368
28983
  ];
28369
28984
  function HeroIdle() {
28985
+ const setDraftText = useChatStore((s15) => s15.setDraftText);
28370
28986
  const handleSuggestion = (prompt) => {
28987
+ setDraftText(prompt);
28371
28988
  const input = document.querySelector("[data-chat-input]");
28372
- if (input) {
28373
- const nativeSetter = Object.getOwnPropertyDescriptor(
28374
- window.HTMLTextAreaElement.prototype,
28375
- "value"
28376
- )?.set;
28377
- nativeSetter?.call(input, prompt);
28378
- input.dispatchEvent(new Event("input", { bubbles: true }));
28379
- input.focus();
28380
- }
28989
+ input?.focus();
28381
28990
  };
28382
28991
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-10 w-full max-w-lg px-8", children: [
28383
28992
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center space-y-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -28402,7 +29011,631 @@ function HeroIdle() {
28402
29011
  )) })
28403
29012
  ] });
28404
29013
  }
28405
- const api$4 = window.api;
29014
+ function getFileName(path2) {
29015
+ if (!path2) return "";
29016
+ const parts = path2.replace(/\\/g, "/").split("/");
29017
+ return parts[parts.length - 1] || path2;
29018
+ }
29019
+ function truncStr(s15, max) {
29020
+ if (!s15) return "";
29021
+ return s15.length > max ? s15.slice(0, max - 3) + "..." : s15;
29022
+ }
29023
+ function safeRecord(obj) {
29024
+ return obj && typeof obj === "object" ? obj : {};
29025
+ }
29026
+ function extractResultText(result) {
29027
+ const r = safeRecord(result);
29028
+ const content2 = r.content;
29029
+ return content2?.[0]?.text || "";
29030
+ }
29031
+ function lastNLines$1(text2, n) {
29032
+ const lines = text2.split("\n").filter(Boolean);
29033
+ return lines.slice(-n).join("\n");
29034
+ }
29035
+ const configs = [
29036
+ // ── File tools ────────────────────────
29037
+ {
29038
+ name: "read",
29039
+ displayName: "Read",
29040
+ icon: "FileText",
29041
+ category: "file",
29042
+ formatCallSummary: (a) => {
29043
+ const path2 = a.path || "";
29044
+ const offset = a.offset;
29045
+ const limit = a.limit;
29046
+ const suffix2 = offset ? ` · lines ${offset}-${offset + (limit || 2e3)}` : "";
29047
+ return `${getFileName(path2)}${suffix2}`;
29048
+ },
29049
+ formatCallDetail: (a) => ({ path: a.path, offset: a.offset, limit: a.limit }),
29050
+ formatResultSummary: (result) => {
29051
+ const text2 = extractResultText(result);
29052
+ const lineCount = text2 ? text2.split("\n").length : 0;
29053
+ return lineCount ? `${lineCount} lines` : "Read completed";
29054
+ },
29055
+ formatResultDetail: (result) => {
29056
+ const text2 = extractResultText(result);
29057
+ return { lineCount: text2 ? text2.split("\n").length : 0 };
29058
+ }
29059
+ },
29060
+ {
29061
+ name: "write",
29062
+ displayName: "Write",
29063
+ icon: "FileText",
29064
+ category: "file",
29065
+ formatCallSummary: (a) => getFileName(a.path || ""),
29066
+ formatCallDetail: (a) => ({ path: a.path }),
29067
+ formatResultSummary: (_2, a) => `Written: ${getFileName(a?.path || "")}`,
29068
+ formatResultDetail: (_2, a) => ({ path: a?.path })
29069
+ },
29070
+ {
29071
+ name: "edit",
29072
+ displayName: "Edit",
29073
+ icon: "FileText",
29074
+ category: "file",
29075
+ formatCallSummary: (a) => getFileName(a.path || ""),
29076
+ formatCallDetail: (a) => ({ path: a.path }),
29077
+ formatResultSummary: (_2, a) => `Edited: ${getFileName(a?.path || "")}`,
29078
+ formatResultDetail: (_2, a) => ({ path: a?.path })
29079
+ },
29080
+ // ── Code tools ────────────────────────
29081
+ {
29082
+ name: "bash",
29083
+ displayName: "Bash",
29084
+ icon: "Terminal",
29085
+ category: "code",
29086
+ formatCallSummary: (a) => {
29087
+ const cmd = a.command || "";
29088
+ return cmd.length > 60 ? `$ ${cmd.slice(0, 57)}...` : `$ ${cmd}`;
29089
+ },
29090
+ formatCallDetail: (a) => ({ command: truncStr(a.command, 200) }),
29091
+ formatResultSummary: () => "Command completed",
29092
+ formatResultDetail: (result) => {
29093
+ const text2 = extractResultText(result);
29094
+ const lines = text2.split("\n").filter(Boolean);
29095
+ return { outputLines: lines.length, outputPreview: truncStr(lastNLines$1(text2, 3), 200) };
29096
+ },
29097
+ formatProgress: (partial) => {
29098
+ const text2 = partial?.content?.[0]?.text;
29099
+ if (typeof text2 === "string" && text2.length > 0) {
29100
+ return lastNLines$1(text2, 5);
29101
+ }
29102
+ return void 0;
29103
+ }
29104
+ },
29105
+ // ── Search tools ────────────────────────
29106
+ {
29107
+ name: "grep",
29108
+ displayName: "Search",
29109
+ icon: "Search",
29110
+ category: "search",
29111
+ formatCallSummary: (a) => `"${truncStr(a.pattern, 30)}"${a.path ? ` in ${a.path}` : ""}`,
29112
+ formatCallDetail: (a) => ({ pattern: a.pattern, path: a.path, glob: a.glob }),
29113
+ formatResultSummary: (result) => {
29114
+ const text2 = extractResultText(result);
29115
+ const count = text2.split("\n").filter(Boolean).length;
29116
+ return `${count} results`;
29117
+ },
29118
+ formatResultDetail: (result) => {
29119
+ const text2 = extractResultText(result);
29120
+ return { matchCount: text2.split("\n").filter(Boolean).length };
29121
+ }
29122
+ },
29123
+ {
29124
+ name: "glob",
29125
+ displayName: "Find Files",
29126
+ icon: "Search",
29127
+ category: "search",
29128
+ formatCallSummary: (a) => a.pattern || "",
29129
+ formatCallDetail: (a) => ({ pattern: a.pattern, path: a.path }),
29130
+ formatResultSummary: (result) => {
29131
+ const text2 = extractResultText(result);
29132
+ const count = text2.split("\n").filter(Boolean).length;
29133
+ return `${count} files`;
29134
+ },
29135
+ formatResultDetail: (result) => {
29136
+ const text2 = extractResultText(result);
29137
+ return { fileCount: text2.split("\n").filter(Boolean).length };
29138
+ }
29139
+ },
29140
+ {
29141
+ name: "find",
29142
+ displayName: "Find",
29143
+ icon: "Search",
29144
+ category: "search",
29145
+ formatCallSummary: (a) => truncStr(a.pattern || a.path, 40),
29146
+ formatCallDetail: (a) => ({ pattern: a.pattern, path: a.path }),
29147
+ formatResultSummary: () => "Find completed",
29148
+ formatResultDetail: () => ({})
29149
+ },
29150
+ {
29151
+ name: "ls",
29152
+ displayName: "List",
29153
+ icon: "FileText",
29154
+ category: "file",
29155
+ formatCallSummary: (a) => a.path || ".",
29156
+ formatCallDetail: (a) => ({ path: a.path }),
29157
+ formatResultSummary: () => "Listed",
29158
+ formatResultDetail: () => ({})
29159
+ },
29160
+ // ── Web tools ────────────────────────
29161
+ {
29162
+ name: "fetch",
29163
+ displayName: "Fetch",
29164
+ icon: "Globe",
29165
+ category: "web",
29166
+ formatCallSummary: (a) => truncStr(a.url, 50),
29167
+ formatCallDetail: (a) => ({ url: a.url }),
29168
+ formatResultSummary: (result) => {
29169
+ const text2 = extractResultText(result);
29170
+ const kb = (text2.length / 1024).toFixed(1);
29171
+ return `${kb}KB received`;
29172
+ },
29173
+ formatResultDetail: (result) => {
29174
+ const text2 = extractResultText(result);
29175
+ return { sizeKB: parseFloat((text2.length / 1024).toFixed(1)) };
29176
+ }
29177
+ },
29178
+ {
29179
+ name: "web_fetch",
29180
+ displayName: "Web Fetch",
29181
+ icon: "Globe",
29182
+ category: "web",
29183
+ formatCallSummary: (a) => truncStr(a.url, 50),
29184
+ formatCallDetail: (a) => ({ url: a.url }),
29185
+ formatResultSummary: (result) => {
29186
+ const r = safeRecord(result);
29187
+ const data = safeRecord(r.data);
29188
+ const charCount = data.charCount;
29189
+ if (charCount) return `${(charCount / 1024).toFixed(1)}KB received`;
29190
+ return "Fetch completed";
29191
+ },
29192
+ formatResultDetail: (result) => {
29193
+ const r = safeRecord(result);
29194
+ const data = safeRecord(r.data);
29195
+ return { charCount: data.charCount, url: data.url };
29196
+ }
29197
+ },
29198
+ {
29199
+ name: "web_search",
29200
+ displayName: "Web Search",
29201
+ icon: "Globe",
29202
+ category: "web",
29203
+ formatCallSummary: (a) => truncStr(a.query, 50),
29204
+ formatCallDetail: (a) => ({ query: a.query }),
29205
+ formatResultSummary: () => "Search completed",
29206
+ formatResultDetail: () => ({})
29207
+ },
29208
+ // ── Research tools ────────────────────────
29209
+ {
29210
+ name: "literature-search",
29211
+ displayName: "Literature Search",
29212
+ icon: "BookOpen",
29213
+ category: "research",
29214
+ formatCallSummary: (a) => truncStr(a.query, 40),
29215
+ formatCallDetail: (a) => ({ query: a.query, maxResults: a.max_results }),
29216
+ formatResultSummary: (result) => {
29217
+ const r = safeRecord(result);
29218
+ const data = safeRecord(r.data);
29219
+ const totalFound = data.totalPapersFound ?? 0;
29220
+ const saved = data.papersAutoSaved ?? 0;
29221
+ const coverage = data.coverage;
29222
+ if (totalFound > 0) {
29223
+ let s15 = `Found ${totalFound} papers`;
29224
+ if (coverage?.score != null) s15 += ` (${Math.round(coverage.score * 100)}%)`;
29225
+ if (saved > 0) s15 += `, saved ${saved}`;
29226
+ return s15;
29227
+ }
29228
+ const local = data.localPapersUsed ?? 0;
29229
+ const external = data.externalPapersUsed ?? 0;
29230
+ return `Found ${local + external} papers`;
29231
+ },
29232
+ formatResultDetail: (result) => {
29233
+ const r = safeRecord(result);
29234
+ const data = safeRecord(r.data);
29235
+ return {
29236
+ papersFound: data.totalPapersFound ?? 0,
29237
+ papersSaved: data.papersAutoSaved ?? 0,
29238
+ coverage: data.coverage?.score
29239
+ };
29240
+ }
29241
+ },
29242
+ {
29243
+ name: "lit-subtopic",
29244
+ displayName: "Sub-topic Search",
29245
+ icon: "BookOpen",
29246
+ category: "research",
29247
+ formatCallSummary: (a) => a._summary || "Searching sub-topic",
29248
+ formatCallDetail: (a) => ({ summary: a._summary }),
29249
+ formatResultSummary: (result) => safeRecord(result).data || "Search completed",
29250
+ formatResultDetail: () => ({})
29251
+ },
29252
+ {
29253
+ name: "lit-enrich",
29254
+ displayName: "Enrich Papers",
29255
+ icon: "BookOpen",
29256
+ category: "research",
29257
+ formatCallSummary: (a) => a._summary || "Enriching paper metadata",
29258
+ formatCallDetail: (a) => ({ summary: a._summary }),
29259
+ formatResultSummary: (result) => safeRecord(result).data || "Enriched metadata",
29260
+ formatResultDetail: () => ({})
29261
+ },
29262
+ {
29263
+ name: "lit-autosave",
29264
+ displayName: "Save Papers",
29265
+ icon: "BookOpen",
29266
+ category: "research",
29267
+ formatCallSummary: (a) => a._summary || "Saving papers",
29268
+ formatCallDetail: (a) => ({ summary: a._summary }),
29269
+ formatResultSummary: (result) => safeRecord(result).data || "Saved papers",
29270
+ formatResultDetail: () => ({})
29271
+ },
29272
+ {
29273
+ name: "data_analyze",
29274
+ displayName: "Data Analysis",
29275
+ icon: "Database",
29276
+ category: "research",
29277
+ formatCallSummary: (a) => getFileName(a.file_path || "") || "data",
29278
+ formatCallDetail: (a) => ({ file_path: a.file_path }),
29279
+ formatResultSummary: () => "Analysis completed",
29280
+ formatResultDetail: () => ({})
29281
+ },
29282
+ // ── Artifact tools ────────────────────────
29283
+ {
29284
+ name: "artifact-create",
29285
+ displayName: "Create Artifact",
29286
+ icon: "Sparkles",
29287
+ category: "memory",
29288
+ formatCallSummary: (a) => {
29289
+ const type = (a.type || "artifact").toLowerCase();
29290
+ const title = truncStr(a.title, 35);
29291
+ return `${type}: ${title}`;
29292
+ },
29293
+ formatCallDetail: (a) => ({ type: a.type, title: a.title }),
29294
+ formatResultSummary: (result) => {
29295
+ const data = safeRecord(safeRecord(result).data);
29296
+ const type = data.type || "artifact";
29297
+ const title = truncStr(data.title, 30);
29298
+ return title ? `Created ${type}: ${title}` : `Created ${type}`;
29299
+ },
29300
+ formatResultDetail: (result) => {
29301
+ const data = safeRecord(safeRecord(result).data);
29302
+ return { type: data.type, title: data.title };
29303
+ }
29304
+ },
29305
+ {
29306
+ name: "artifact-update",
29307
+ displayName: "Update Artifact",
29308
+ icon: "Sparkles",
29309
+ category: "memory",
29310
+ formatCallSummary: (a) => truncStr(a.id, 30),
29311
+ formatCallDetail: (a) => ({ id: a.id }),
29312
+ formatResultSummary: () => "Updated",
29313
+ formatResultDetail: () => ({})
29314
+ },
29315
+ {
29316
+ name: "artifact-search",
29317
+ displayName: "Search Artifacts",
29318
+ icon: "Search",
29319
+ category: "memory",
29320
+ formatCallSummary: (a) => truncStr(a.query, 40),
29321
+ formatCallDetail: (a) => ({ query: a.query, types: a.types }),
29322
+ formatResultSummary: () => "Search completed",
29323
+ formatResultDetail: () => ({})
29324
+ },
29325
+ // ── System tools ────────────────────────
29326
+ {
29327
+ name: "convert_document",
29328
+ displayName: "Convert Document",
29329
+ icon: "FileText",
29330
+ category: "system",
29331
+ formatCallSummary: (a) => getFileName(a.source || ""),
29332
+ formatCallDetail: (a) => ({ source: a.source }),
29333
+ formatResultSummary: (result, a) => {
29334
+ const data = safeRecord(safeRecord(result).data);
29335
+ const skill = data.converterSkill;
29336
+ const sourceName = getFileName(a?.source || "");
29337
+ return skill ? `Converted ${sourceName} via ${skill}` : `Converted ${sourceName}`;
29338
+ },
29339
+ formatResultDetail: (result) => {
29340
+ const data = safeRecord(safeRecord(result).data);
29341
+ return { converterSkill: data.converterSkill, outputFile: data.outputFile };
29342
+ }
29343
+ },
29344
+ {
29345
+ name: "load_skill",
29346
+ displayName: "Load Skill",
29347
+ icon: "Sparkles",
29348
+ category: "system",
29349
+ formatCallSummary: (a) => a.name || "skill",
29350
+ formatCallDetail: (a) => ({ name: a.name }),
29351
+ formatResultSummary: (_2, a) => `Loaded: ${a?.name || "skill"}`,
29352
+ formatResultDetail: () => ({})
29353
+ }
29354
+ ];
29355
+ const registry = /* @__PURE__ */ new Map();
29356
+ for (const config of configs) {
29357
+ registry.set(config.name, config);
29358
+ }
29359
+ function getToolDisplayName(toolName) {
29360
+ return registry.get(toolName)?.displayName || toolName;
29361
+ }
29362
+ function getToolIcon(toolName) {
29363
+ return registry.get(toolName)?.icon || "Wrench";
29364
+ }
29365
+ const ICON_MAP = {
29366
+ FileText,
29367
+ Terminal,
29368
+ Search,
29369
+ Globe,
29370
+ Wrench,
29371
+ BookOpen,
29372
+ Database,
29373
+ Sparkles
29374
+ };
29375
+ function getToolMeta(tool) {
29376
+ return {
29377
+ name: getToolDisplayName(tool),
29378
+ Icon: ICON_MAP[getToolIcon(tool)] || Wrench
29379
+ };
29380
+ }
29381
+ function StatusIcon({ status, size = 13 }) {
29382
+ switch (status) {
29383
+ case "running":
29384
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size, className: "animate-spin t-text-accent-soft" });
29385
+ case "success":
29386
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size, className: "t-text-success" });
29387
+ case "error":
29388
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size, className: "t-text-error" });
29389
+ }
29390
+ }
29391
+ function formatDuration$1(ms2) {
29392
+ if (ms2 < 1e3) return `${ms2}ms`;
29393
+ if (ms2 < 6e4) return `${(ms2 / 1e3).toFixed(1)}s`;
29394
+ return `${(ms2 / 6e4).toFixed(1)}m`;
29395
+ }
29396
+ function ElapsedTimer({ startedAt }) {
29397
+ const [elapsed, setElapsed] = reactExports.useState(Date.now() - startedAt);
29398
+ reactExports.useEffect(() => {
29399
+ const interval = setInterval(() => setElapsed(Date.now() - startedAt), 500);
29400
+ return () => clearInterval(interval);
29401
+ }, [startedAt]);
29402
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted tabular-nums", children: formatDuration$1(elapsed) });
29403
+ }
29404
+ const CompactToolLine = React$2.memo(function CompactToolLine2({ event }) {
29405
+ const [expanded, setExpanded] = reactExports.useState(false);
29406
+ const { name: name2, Icon: Icon2 } = getToolMeta(event.tool);
29407
+ const displaySummary = event.resultSummary || event.summary;
29408
+ const detailItems = buildDetailItems(event.tool, event.detail, event.resultDetail);
29409
+ const hasExpandable = detailItems.length > 0;
29410
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
29411
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
29412
+ "button",
29413
+ {
29414
+ onClick: () => hasExpandable && setExpanded(!expanded),
29415
+ className: `w-full flex items-center gap-1.5 px-2 py-[3px] text-[11px] rounded transition-colors ${hasExpandable ? "hover:t-bg-hover cursor-pointer" : "cursor-default"}`,
29416
+ children: [
29417
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIcon, { status: event.status, size: 11 }),
29418
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { size: 10, className: "t-text-muted shrink-0" }),
29419
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium t-text-secondary shrink-0", children: name2 }),
29420
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted select-none", children: "·" }),
29421
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted truncate flex-1 text-left", children: displaySummary }),
29422
+ event.durationMs != null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] t-text-muted tabular-nums shrink-0", children: formatDuration$1(event.durationMs) }),
29423
+ hasExpandable && /* @__PURE__ */ jsxRuntimeExports.jsx(
29424
+ "span",
29425
+ {
29426
+ className: "t-text-muted shrink-0 transition-transform duration-150",
29427
+ style: { transform: expanded ? "rotate(90deg)" : "rotate(0deg)" },
29428
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 9 })
29429
+ }
29430
+ )
29431
+ ]
29432
+ }
29433
+ ),
29434
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ml-5 mr-2 mb-1 mt-0.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandedDetail, { event }) })
29435
+ ] });
29436
+ });
29437
+ const RunningToolCard = React$2.memo(function RunningToolCard2({ event }) {
29438
+ const [expanded, setExpanded] = reactExports.useState(false);
29439
+ const { name: name2, Icon: Icon2 } = getToolMeta(event.tool);
29440
+ const displaySummary = event.summary;
29441
+ const detailItems = buildDetailItems(event.tool, event.detail, event.resultDetail);
29442
+ const hasExpandable = detailItems.length > 0 || event.progress;
29443
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border t-border t-bg-surface shadow-sm overflow-hidden", children: [
29444
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
29445
+ "button",
29446
+ {
29447
+ onClick: () => hasExpandable && setExpanded(!expanded),
29448
+ className: `w-full flex items-center gap-2 px-3 py-2 text-xs transition-colors ${hasExpandable ? "hover:t-bg-hover cursor-pointer" : "cursor-default"}`,
29449
+ children: [
29450
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusIcon, { status: event.status }),
29451
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { size: 12, className: "t-text-accent-soft shrink-0" }),
29452
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium t-text shrink-0", children: name2 }),
29453
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted select-none", children: "·" }),
29454
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-secondary truncate flex-1 text-left", children: displaySummary }),
29455
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ElapsedTimer, { startedAt: event.startedAt }),
29456
+ hasExpandable && /* @__PURE__ */ jsxRuntimeExports.jsx(
29457
+ "span",
29458
+ {
29459
+ className: "t-text-muted shrink-0 transition-transform duration-150",
29460
+ style: { transform: expanded ? "rotate(90deg)" : "rotate(0deg)" },
29461
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 10 })
29462
+ }
29463
+ )
29464
+ ]
29465
+ }
29466
+ ),
29467
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t t-border px-3 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandedDetail, { event }) }),
29468
+ !expanded && event.progress && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t t-border px-3 py-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[10px] font-mono t-text-muted overflow-hidden max-h-14 leading-relaxed whitespace-pre-wrap", children: event.progress }) })
29469
+ ] });
29470
+ });
29471
+ const ToolUseCard = React$2.memo(function ToolUseCard2({ event, compact }) {
29472
+ if (compact || event.status !== "running") {
29473
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(CompactToolLine, { event });
29474
+ }
29475
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(RunningToolCard, { event });
29476
+ });
29477
+ function ExpandedDetail({ event }) {
29478
+ const items = buildDetailItems(event.tool, event.detail, event.resultDetail);
29479
+ const hasProgress = !!event.progress;
29480
+ if (items.length === 0 && !hasProgress) return null;
29481
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5", children: [
29482
+ items.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1", children: items.map((item, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(DetailItem, { ...item }, i)) }),
29483
+ hasProgress && /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "p-2 rounded t-bg-base text-[10px] font-mono t-text-secondary overflow-x-auto max-h-28 overflow-y-auto leading-relaxed whitespace-pre-wrap border t-border-subtle", children: event.progress })
29484
+ ] });
29485
+ }
29486
+ function DetailItem({ type, label, value }) {
29487
+ switch (type) {
29488
+ case "path":
29489
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-[11px]", children: [
29490
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { size: 10, className: "t-text-muted shrink-0" }),
29491
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono t-text-secondary truncate", title: value, children: value })
29492
+ ] });
29493
+ case "code":
29494
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "p-2 rounded t-bg-base text-[10px] font-mono t-text-secondary overflow-x-auto leading-relaxed whitespace-pre-wrap border t-border-subtle", children: value });
29495
+ case "stat":
29496
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-[10px]", children: [
29497
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: label }),
29498
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium t-text-secondary tabular-nums", children: value })
29499
+ ] });
29500
+ case "kv":
29501
+ default:
29502
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-baseline gap-2 text-[11px]", children: [
29503
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted shrink-0 text-[10px]", children: label }),
29504
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-secondary font-mono truncate", title: value, children: value })
29505
+ ] });
29506
+ }
29507
+ }
29508
+ function buildDetailItems(tool, detail, resultDetail) {
29509
+ const items = [];
29510
+ if (!detail && !resultDetail) return items;
29511
+ switch (tool) {
29512
+ case "read": {
29513
+ const path2 = detail?.path;
29514
+ if (path2) items.push({ type: "path", value: path2 });
29515
+ const lineCount = resultDetail?.lineCount;
29516
+ if (lineCount) items.push({ type: "stat", label: "Lines read", value: String(lineCount) });
29517
+ break;
29518
+ }
29519
+ case "write":
29520
+ case "edit": {
29521
+ const path2 = detail?.path || resultDetail?.path;
29522
+ if (path2) items.push({ type: "path", value: path2 });
29523
+ break;
29524
+ }
29525
+ case "bash": {
29526
+ const cmd = detail?.command;
29527
+ if (cmd) items.push({ type: "code", value: `$ ${cmd}` });
29528
+ const preview = resultDetail?.outputPreview;
29529
+ if (preview) items.push({ type: "code", value: preview });
29530
+ const lines = resultDetail?.outputLines;
29531
+ if (lines && !preview) items.push({ type: "stat", label: "Output", value: `${lines} lines` });
29532
+ break;
29533
+ }
29534
+ case "grep": {
29535
+ const pattern = detail?.pattern;
29536
+ const path2 = detail?.path;
29537
+ if (pattern) items.push({ type: "kv", label: "Pattern", value: pattern });
29538
+ if (path2) items.push({ type: "kv", label: "Path", value: path2 });
29539
+ const count = resultDetail?.matchCount;
29540
+ if (count != null) items.push({ type: "stat", label: "Matches", value: String(count) });
29541
+ break;
29542
+ }
29543
+ case "glob": {
29544
+ const pattern = detail?.pattern;
29545
+ if (pattern) items.push({ type: "kv", label: "Pattern", value: pattern });
29546
+ const count = resultDetail?.fileCount;
29547
+ if (count != null) items.push({ type: "stat", label: "Files found", value: String(count) });
29548
+ break;
29549
+ }
29550
+ case "fetch":
29551
+ case "web_fetch": {
29552
+ const url = detail?.url;
29553
+ if (url) items.push({ type: "kv", label: "URL", value: url });
29554
+ const size = resultDetail?.sizeKB || resultDetail?.charCount;
29555
+ if (size) items.push({ type: "stat", label: "Received", value: size > 1024 ? `${(size / 1024).toFixed(1)}MB` : `${size}KB` });
29556
+ break;
29557
+ }
29558
+ case "web_search": {
29559
+ const query = detail?.query;
29560
+ if (query) items.push({ type: "kv", label: "Query", value: query });
29561
+ break;
29562
+ }
29563
+ case "literature-search": {
29564
+ const query = detail?.query;
29565
+ if (query) items.push({ type: "kv", label: "Query", value: query });
29566
+ const found = resultDetail?.papersFound;
29567
+ const saved = resultDetail?.papersSaved;
29568
+ const coverage = resultDetail?.coverage;
29569
+ if (found != null) items.push({ type: "stat", label: "Papers found", value: String(found) });
29570
+ if (saved != null && saved > 0) items.push({ type: "stat", label: "Saved", value: String(saved) });
29571
+ if (coverage != null) items.push({ type: "stat", label: "Coverage", value: `${Math.round(coverage * 100)}%` });
29572
+ break;
29573
+ }
29574
+ case "artifact-create":
29575
+ case "artifact-update": {
29576
+ const type = detail?.type || resultDetail?.type;
29577
+ const title = detail?.title || resultDetail?.title;
29578
+ if (type) items.push({ type: "kv", label: "Type", value: type });
29579
+ if (title) items.push({ type: "kv", label: "Title", value: title });
29580
+ break;
29581
+ }
29582
+ case "artifact-search": {
29583
+ const query = detail?.query;
29584
+ if (query) items.push({ type: "kv", label: "Query", value: query });
29585
+ break;
29586
+ }
29587
+ default: {
29588
+ const skipKeys = /* @__PURE__ */ new Set(["success", "path"]);
29589
+ const source = { ...detail, ...resultDetail };
29590
+ for (const [key, val] of Object.entries(source)) {
29591
+ if (val == null || skipKeys.has(key)) continue;
29592
+ const strVal = typeof val === "string" ? val : JSON.stringify(val);
29593
+ if (strVal.length === 0 || strVal === "true" || strVal === "false") continue;
29594
+ items.push({ type: "kv", label: key, value: strVal });
29595
+ }
29596
+ break;
29597
+ }
29598
+ }
29599
+ return items;
29600
+ }
29601
+ const VISIBLE_COMPLETED = 3;
29602
+ function ToolUseStream({ events: propEvents }) {
29603
+ const liveEvents = useToolEventsStore((s15) => s15.currentRunEvents);
29604
+ const events = propEvents ?? liveEvents;
29605
+ const [showAll, setShowAll] = reactExports.useState(false);
29606
+ const { running, completed } = reactExports.useMemo(() => {
29607
+ const running2 = [];
29608
+ const completed2 = [];
29609
+ for (const e of events) {
29610
+ if (e.status === "running") running2.push(e);
29611
+ else completed2.push(e);
29612
+ }
29613
+ return { running: running2, completed: completed2 };
29614
+ }, [events]);
29615
+ if (events.length === 0) return null;
29616
+ const hiddenCount = showAll ? 0 : Math.max(0, completed.length - VISIBLE_COMPLETED);
29617
+ const visibleCompleted = showAll ? completed : completed.slice(-VISIBLE_COMPLETED);
29618
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "my-2 max-w-[80%]", children: [
29619
+ hiddenCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
29620
+ "button",
29621
+ {
29622
+ onClick: () => setShowAll(true),
29623
+ className: "flex items-center gap-1.5 px-2 py-1 mb-1 text-[10px] t-text-muted hover:t-text-accent-soft transition-colors rounded",
29624
+ children: [
29625
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 10 }),
29626
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
29627
+ hiddenCount,
29628
+ " more tool ",
29629
+ hiddenCount === 1 ? "call" : "calls"
29630
+ ] })
29631
+ ]
29632
+ }
29633
+ ),
29634
+ visibleCompleted.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-0", children: visibleCompleted.map((event) => /* @__PURE__ */ jsxRuntimeExports.jsx(ToolUseCard, { event, compact: true }, event.id)) }),
29635
+ running.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 mt-1", children: running.map((event) => /* @__PURE__ */ jsxRuntimeExports.jsx(ToolUseCard, { event }, event.id)) })
29636
+ ] });
29637
+ }
29638
+ const api$5 = window.api;
28406
29639
  const remarkPlugins$1 = [remarkGfm];
28407
29640
  function SelectionBookmark() {
28408
29641
  const refreshAll = useEntityStore((s15) => s15.refreshAll);
@@ -28463,7 +29696,7 @@ function SelectionBookmark() {
28463
29696
  try {
28464
29697
  const first = selectedText.split(/[.!?\n]/)[0].trim();
28465
29698
  const title = first.length > 60 ? first.slice(0, 57) + "…" : first || "Untitled selection";
28466
- const created = await api$4.artifactCreate({
29699
+ const created = await api$5.artifactCreate({
28467
29700
  type: "note",
28468
29701
  title,
28469
29702
  content: selectedText,
@@ -28495,7 +29728,7 @@ function SelectionBookmark() {
28495
29728
  {
28496
29729
  ref: tooltipRef,
28497
29730
  onMouseDown: handleSave,
28498
- className: "fixed z-[100] flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium shadow-lg border t-border transition-colors",
29731
+ className: "fixed z-50 flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium shadow-lg border t-border transition-colors",
28499
29732
  style: {
28500
29733
  left: pos.x,
28501
29734
  top: pos.y,
@@ -28528,7 +29761,7 @@ const MessageBubble = React$2.memo(function MessageBubble2({ msg, isSaved }) {
28528
29761
  try {
28529
29762
  const first = msg.content.replace(/^#+\s*/, "").split(/[.!?\n]/)[0].trim();
28530
29763
  const title = first.length > 60 ? first.slice(0, 57) + "…" : first || "Untitled note";
28531
- const created = await api$4.artifactCreate({
29764
+ const created = await api$5.artifactCreate({
28532
29765
  type: "note",
28533
29766
  title,
28534
29767
  content: msg.content,
@@ -28562,6 +29795,7 @@ const MessageBubble = React$2.memo(function MessageBubble2({ msg, isSaved }) {
28562
29795
  {
28563
29796
  src,
28564
29797
  alt: "",
29798
+ loading: "lazy",
28565
29799
  className: `rounded-lg border t-border cursor-pointer hover:opacity-90 transition-opacity ${isUser ? "max-h-48" : "max-h-80"}`,
28566
29800
  onClick: () => window.open(src, "_blank")
28567
29801
  },
@@ -28583,37 +29817,12 @@ const MessageBubble = React$2.memo(function MessageBubble2({ msg, isSaved }) {
28583
29817
  }
28584
29818
  ) });
28585
29819
  });
28586
- function ThinkingDots() {
28587
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex gap-1 items-center", children: [
28588
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 h-1.5 rounded-full t-bg-accent-soft animate-bounce", style: { animationDelay: "0ms" } }),
28589
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 h-1.5 rounded-full t-bg-accent-soft animate-bounce", style: { animationDelay: "150ms" } }),
28590
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-1.5 h-1.5 rounded-full t-bg-accent-soft animate-bounce", style: { animationDelay: "300ms" } })
28591
- ] });
28592
- }
28593
- function ThinkingBubble() {
28594
- const events = useActivityStore((s15) => s15.events);
28595
- const activity = reactExports.useMemo(() => {
28596
- const latest = [...events].reverse().find((e) => e.type === "tool-call") || events[events.length - 1];
28597
- return latest?.summary || "";
28598
- }, [events]);
28599
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
28600
- /* @__PURE__ */ jsxRuntimeExports.jsx(
28601
- "div",
28602
- {
28603
- className: "rounded-2xl px-4 py-3 text-sm t-text-secondary shrink-0",
28604
- style: { background: "var(--color-bubble-assistant)" },
28605
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
28606
- /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingDots, {}),
28607
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "Thinking" })
28608
- ] })
28609
- }
28610
- ),
28611
- activity && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex items-center gap-2 min-w-0", children: [
28612
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 h-px border-t t-border" }),
28613
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs t-text-muted whitespace-nowrap truncate max-w-[60%] animate-pulse", children: activity }),
28614
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 h-px border-t t-border" })
28615
- ] })
28616
- ] });
29820
+ function ThinkingIndicator() {
29821
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1.5 mt-3 ml-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex gap-[3px] items-center", "aria-label": "Thinking", children: [
29822
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-[5px] h-[5px] rounded-full t-bg-accent-soft animate-bounce animation-delay-0" }),
29823
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-[5px] h-[5px] rounded-full t-bg-accent-soft animate-bounce animation-delay-150" }),
29824
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-[5px] h-[5px] rounded-full t-bg-accent-soft animate-bounce animation-delay-300" })
29825
+ ] }) });
28617
29826
  }
28618
29827
  function StreamingBubble() {
28619
29828
  const text2 = useChatStore((s15) => s15.streamingText);
@@ -28635,10 +29844,13 @@ function ChatMessages() {
28635
29844
  const isStreaming = useChatStore((s15) => s15.isStreaming);
28636
29845
  const streamingText = useChatStore((s15) => s15.streamingText);
28637
29846
  const savedMessageIds = useChatStore((s15) => s15.savedMessageIds);
29847
+ const turnToolEvents = useChatStore((s15) => s15.turnToolEvents);
28638
29848
  const hasMore = useChatStore((s15) => s15.hasMore);
28639
29849
  const isLoadingHistory = useChatStore((s15) => s15.isLoadingHistory);
28640
29850
  const loadHistory = useChatStore((s15) => s15.loadHistory);
28641
29851
  const scrollToMessageId = useChatStore((s15) => s15.scrollToMessageId);
29852
+ const toolEventsCount = useToolEventsStore((s15) => s15.currentRunEvents.length);
29853
+ const hasRunningTools = useToolEventsStore((s15) => s15.currentRunEvents.some((e) => e.status === "running"));
28642
29854
  const bottomRef = reactExports.useRef(null);
28643
29855
  const scrollContainerRef = reactExports.useRef(null);
28644
29856
  const [autoScroll, setAutoScroll] = reactExports.useState(true);
@@ -28671,7 +29883,7 @@ function ChatMessages() {
28671
29883
  bottomRef.current?.scrollIntoView({ behavior: "smooth" });
28672
29884
  }
28673
29885
  }
28674
- }, [messages, streamingText, autoScroll]);
29886
+ }, [messages, streamingText, toolEventsCount, autoScroll]);
28675
29887
  reactExports.useEffect(() => {
28676
29888
  if (!scrollToMessageId || !scrollContainerRef.current) return;
28677
29889
  const el2 = scrollContainerRef.current.querySelector(`[data-msg-id="${scrollToMessageId}"]`);
@@ -28695,24 +29907,42 @@ function ChatMessages() {
28695
29907
  children: [
28696
29908
  isLoadingHistory && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 16, className: "animate-spin t-text-muted" }) }),
28697
29909
  messages.map((msg, i) => {
29910
+ if (msg.role === "system") {
29911
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 my-5 px-2", children: [
29912
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 h-px t-bg-elevated" }),
29913
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] t-text-muted shrink-0", children: msg.content }),
29914
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 h-px t-bg-elevated" })
29915
+ ] }, msg.id);
29916
+ }
28698
29917
  const prev = messages[i - 1];
28699
29918
  const gap = prev && prev.role !== msg.role ? "mt-5" : "mt-3";
28700
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: i === 0 ? "" : gap, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
28701
- MessageBubble,
28702
- {
28703
- msg,
28704
- isSaved: savedMessageIds.has(msg.id)
28705
- }
28706
- ) }, msg.id);
29919
+ const toolEvents = msg.role === "assistant" ? turnToolEvents.get(msg.id) : void 0;
29920
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: i === 0 ? "" : gap, children: [
29921
+ toolEvents && toolEvents.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(ToolUseStream, { events: toolEvents }),
29922
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29923
+ MessageBubble,
29924
+ {
29925
+ msg,
29926
+ isSaved: savedMessageIds.has(msg.id)
29927
+ }
29928
+ )
29929
+ ] }, msg.id);
28707
29930
  }),
28708
- isStreaming && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-5", children: streamingText ? /* @__PURE__ */ jsxRuntimeExports.jsx(StreamingBubble, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingBubble, {}) }),
29931
+ isStreaming && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-5", children: [
29932
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ToolUseStream, {}),
29933
+ streamingText ? /* @__PURE__ */ jsxRuntimeExports.jsx(StreamingBubble, {}) : !hasRunningTools ? (
29934
+ /* Show thinking dots only when no tools are running —
29935
+ running tool cards already have spinners as progress indicator */
29936
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ThinkingIndicator, {})
29937
+ ) : null
29938
+ ] }),
28709
29939
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: bottomRef })
28710
29940
  ]
28711
29941
  }
28712
29942
  )
28713
29943
  ] });
28714
29944
  }
28715
- const api$3 = window.api;
29945
+ const api$4 = window.api;
28716
29946
  const typeIcons$1 = {
28717
29947
  note: /* @__PURE__ */ jsxRuntimeExports.jsx(StickyNote, { size: 13, className: "t-text-warning" }),
28718
29948
  paper: /* @__PURE__ */ jsxRuntimeExports.jsx(BookOpen, { size: 13, className: "t-text-info" }),
@@ -28761,7 +29991,7 @@ function MentionPopover({ query, onSelect, onClose }) {
28761
29991
  setLoading(true);
28762
29992
  clearTimeout(debounceRef.current);
28763
29993
  debounceRef.current = setTimeout(() => {
28764
- api$3.getCandidates(query).then((result) => {
29994
+ api$4.getCandidates(query).then((result) => {
28765
29995
  if (stale) return;
28766
29996
  setCandidates(result || []);
28767
29997
  setSelectedIdx(0);
@@ -28807,8 +30037,8 @@ function MentionPopover({ query, onSelect, onClose }) {
28807
30037
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
28808
30038
  "div",
28809
30039
  {
28810
- className: "absolute z-50 w-[32rem] max-h-80 overflow-y-auto rounded-xl border t-border t-bg-surface shadow-xl",
28811
- style: { bottom: "100%", left: 48, marginBottom: 8 },
30040
+ className: "absolute z-50 w-[32rem] max-w-[calc(100vw-2rem)] max-h-80 overflow-y-auto rounded-xl border t-border t-bg-surface shadow-xl",
30041
+ style: { bottom: "100%", left: 0, marginBottom: 8 },
28812
30042
  children: [
28813
30043
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-1.5 border-b t-border flex items-center gap-2 text-xs t-text-secondary", children: [
28814
30044
  /* @__PURE__ */ jsxRuntimeExports.jsx(AtSign, { size: 11 }),
@@ -28901,7 +30131,7 @@ function CommandPopover({ query, commands, onSelect, onClose }) {
28901
30131
  "div",
28902
30132
  {
28903
30133
  className: "absolute z-50 w-72 rounded-xl border t-border t-bg-surface shadow-xl",
28904
- style: { bottom: "100%", left: 48, marginBottom: 8 },
30134
+ style: { bottom: "100%", left: 0, marginBottom: 8 },
28905
30135
  children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-3 text-xs t-text-muted", children: [
28906
30136
  'No matching commands for "/',
28907
30137
  query,
@@ -28914,7 +30144,7 @@ function CommandPopover({ query, commands, onSelect, onClose }) {
28914
30144
  "div",
28915
30145
  {
28916
30146
  className: "absolute z-50 w-80 max-h-64 overflow-y-auto rounded-xl border t-border t-bg-surface shadow-xl",
28917
- style: { bottom: "100%", left: 48, marginBottom: 8 },
30147
+ style: { bottom: "100%", left: 0, marginBottom: 8 },
28918
30148
  children: [
28919
30149
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-2 border-b t-border flex items-center gap-2 text-xs t-text-secondary", children: [
28920
30150
  /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { size: 12 }),
@@ -28954,7 +30184,7 @@ const SLASH_COMMANDS = [
28954
30184
  { name: "/delete", description: "Delete an entity", args: "<id>" },
28955
30185
  { name: "/help", description: "Show available commands" }
28956
30186
  ];
28957
- const api$2 = window.api;
30187
+ const api$3 = window.api;
28958
30188
  const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
28959
30189
  const MAX_IMAGES = 5;
28960
30190
  const ACCEPTED_DOC_EXTENSIONS = [".pdf", ".csv", ".md", ".txt", ".json", ".xml", ".html", ".docx"];
@@ -28993,7 +30223,8 @@ function parseFlagArgs(raw) {
28993
30223
  return { cleaned: cleaned.trim(), flags };
28994
30224
  }
28995
30225
  function ChatInput() {
28996
- const [text2, setText] = reactExports.useState("");
30226
+ const text2 = useChatStore((s15) => s15.draftText);
30227
+ const setText = useChatStore((s15) => s15.setDraftText);
28997
30228
  const [showMention, setShowMention] = reactExports.useState(false);
28998
30229
  const [mentionQuery, setMentionQuery] = reactExports.useState("");
28999
30230
  const [showCommand, setShowCommand] = reactExports.useState(false);
@@ -29010,7 +30241,6 @@ function ChatInput() {
29010
30241
  const hasProject = useSessionStore((s15) => s15.hasProject);
29011
30242
  const refreshEntities = useEntityStore((s15) => s15.refreshAll);
29012
30243
  reactExports.useEffect(() => {
29013
- setText("");
29014
30244
  setShowMention(false);
29015
30245
  setShowCommand(false);
29016
30246
  setPendingImages([]);
@@ -29075,7 +30305,7 @@ function ChatInput() {
29075
30305
  const dataUrl = reader.result;
29076
30306
  const base64 = dataUrl.split(",")[1];
29077
30307
  try {
29078
- const result = await api$2.convertFileToText(file.name, base64);
30308
+ const result = await api$3.convertFileToText(file.name, base64);
29079
30309
  if (result?.success && result.content) {
29080
30310
  setPendingFiles((p) => p.map(
29081
30311
  (f) => f.name === file.name ? { ...f, content: result.content, loading: false } : f
@@ -29192,19 +30422,19 @@ ${fileBlocks}`;
29192
30422
  try {
29193
30423
  switch (cmd) {
29194
30424
  case "/notes": {
29195
- const notes = await api$2.listNotes();
30425
+ const notes = await api$3.listNotes();
29196
30426
  result = notes?.length ? `**Notes (${notes.length}):**
29197
30427
  ` + notes.map((n) => `- ${n.title} \`${n.id.slice(0, 8)}\``).join("\n") : "No notes yet.";
29198
30428
  break;
29199
30429
  }
29200
30430
  case "/papers": {
29201
- const papers = await api$2.listLiterature();
30431
+ const papers = await api$3.listLiterature();
29202
30432
  result = papers?.length ? `**Papers (${papers.length}):**
29203
30433
  ` + papers.map((p) => `- ${p.title} \`${p.citeKey}\``).join("\n") : "No papers yet.";
29204
30434
  break;
29205
30435
  }
29206
30436
  case "/data": {
29207
- const data = await api$2.listData();
30437
+ const data = await api$3.listData();
29208
30438
  result = data?.length ? `**Data (${data.length}):**
29209
30439
  ` + data.map((d) => `- ${d.name} \`${d.id.slice(0, 8)}\``).join("\n") : "No data attachments yet.";
29210
30440
  break;
@@ -29214,7 +30444,7 @@ ${fileBlocks}`;
29214
30444
  result = "Usage: `/search <query>`";
29215
30445
  break;
29216
30446
  }
29217
- const results = await api$2.search(rest);
30447
+ const results = await api$3.search(rest);
29218
30448
  result = results?.length ? `**Search results for "${rest}":**
29219
30449
  ` + results.map((r) => `- [${r.type}] ${r.title} \`${r.id.slice(0, 8)}\``).join("\n") : `No results for "${rest}".`;
29220
30450
  break;
@@ -29224,7 +30454,7 @@ ${fileBlocks}`;
29224
30454
  result = "Usage: `/note <title>`";
29225
30455
  break;
29226
30456
  }
29227
- const r = await api$2.artifactCreate({ type: "note", title: rest, content: "" });
30457
+ const r = await api$3.artifactCreate({ type: "note", title: rest, content: "" });
29228
30458
  result = r?.success ? `Note saved: **${rest}**` : `Failed: ${r?.error || "unknown error"}`;
29229
30459
  refreshEntities();
29230
30460
  break;
@@ -29247,7 +30477,7 @@ ${fileBlocks}`;
29247
30477
  const bibtex = flags.bibtex || `@article{${citeKey},
29248
30478
  title = {${cleaned}}
29249
30479
  }`;
29250
- const r = await api$2.artifactCreate({
30480
+ const r = await api$3.artifactCreate({
29251
30481
  type: "paper",
29252
30482
  title: cleaned,
29253
30483
  authors,
@@ -29274,7 +30504,7 @@ ${fileBlocks}`;
29274
30504
  result = "Usage: `/save-data <name> --path <file>`";
29275
30505
  break;
29276
30506
  }
29277
- const r = await api$2.artifactCreate({
30507
+ const r = await api$3.artifactCreate({
29278
30508
  type: "data",
29279
30509
  title: cleaned,
29280
30510
  filePath: flags.path,
@@ -29285,7 +30515,7 @@ ${fileBlocks}`;
29285
30515
  break;
29286
30516
  }
29287
30517
  case "/summary": {
29288
- const summaryResult = await api$2.sessionSummaryGet();
30518
+ const summaryResult = await api$3.sessionSummaryGet();
29289
30519
  if (!summaryResult?.success || !summaryResult?.summary) {
29290
30520
  result = "No session summary available yet.";
29291
30521
  break;
@@ -29312,7 +30542,7 @@ ${s15.openQuestions.map((q2) => `- ${q2}`).join("\n")}`;
29312
30542
  result = "Usage: `/delete <id>`";
29313
30543
  break;
29314
30544
  }
29315
- const r = await api$2.deleteEntity(rest);
30545
+ const r = await api$3.deleteEntity(rest);
29316
30546
  result = r?.success ? `Deleted \`${rest}\`` : `Failed: ${r?.error || "not found"}`;
29317
30547
  refreshEntities();
29318
30548
  break;
@@ -29500,6 +30730,7 @@ ${s15.openQuestions.map((q2) => `- ${q2}`).join("\n")}`;
29500
30730
  onKeyDown: handleKeyDown,
29501
30731
  onPaste: handlePaste,
29502
30732
  placeholder: "Ask anything, attach files, or type /commands...",
30733
+ "aria-label": "Chat message input",
29503
30734
  rows: 1,
29504
30735
  className: "flex-1 bg-transparent text-sm t-text placeholder:t-text-muted resize-none outline-none"
29505
30736
  }
@@ -29691,7 +30922,7 @@ function PaperRow({
29691
30922
  ] })
29692
30923
  ] });
29693
30924
  }
29694
- function CoverageBar({ papers }) {
30925
+ function CoverageBar$1({ papers }) {
29695
30926
  const topicCounts = reactExports.useMemo(() => {
29696
30927
  const counts = /* @__PURE__ */ new Map();
29697
30928
  for (const p of papers) {
@@ -29723,16 +30954,15 @@ function CoverageBar({ papers }) {
29723
30954
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 max-w-48", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1.5 rounded-full t-bg-elevated overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
29724
30955
  "div",
29725
30956
  {
29726
- className: "h-full rounded-full",
30957
+ className: "h-full rounded-full t-gradient-accent-h",
29727
30958
  style: {
29728
- width: `${Math.min(100, highRelevance / Math.max(papers.length, 1) * 100)}%`,
29729
- background: "linear-gradient(90deg, var(--color-accent), var(--color-accent-2))"
30959
+ width: `${Math.min(100, highRelevance / Math.max(papers.length, 1) * 100)}%`
29730
30960
  }
29731
30961
  }
29732
30962
  ) }) })
29733
30963
  ] });
29734
30964
  }
29735
- function FilterBar() {
30965
+ function FilterBar$1() {
29736
30966
  const filter = useUIStore((s15) => s15.literatureFilter);
29737
30967
  const setFilter = useUIStore((s15) => s15.setLiteratureFilter);
29738
30968
  const [showFilters, setShowFilters] = reactExports.useState(false);
@@ -29868,7 +31098,7 @@ function LiteratureView() {
29868
31098
  }, [papers, filter]);
29869
31099
  const hasTopics = papers.some((p) => p.subTopic);
29870
31100
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-h-0", children: [
29871
- /* @__PURE__ */ jsxRuntimeExports.jsx(FilterBar, {}),
31101
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FilterBar$1, {}),
29872
31102
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex min-h-0", children: [
29873
31103
  hasTopics && /* @__PURE__ */ jsxRuntimeExports.jsx(
29874
31104
  TopicTree,
@@ -29943,18 +31173,360 @@ function LiteratureView() {
29943
31173
  )) })
29944
31174
  ] })
29945
31175
  ] }),
29946
- /* @__PURE__ */ jsxRuntimeExports.jsx(CoverageBar, { papers: filtered })
31176
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CoverageBar$1, { papers: filtered })
29947
31177
  ] });
29948
31178
  }
31179
+ function formatDuration(seconds) {
31180
+ if (!seconds || seconds < 0) return "--";
31181
+ if (seconds < 60) return `${Math.round(seconds)}s`;
31182
+ if (seconds < 3600) {
31183
+ const m2 = Math.floor(seconds / 60);
31184
+ const s15 = Math.round(seconds % 60);
31185
+ return s15 > 0 ? `${m2}m ${s15}s` : `${m2}m`;
31186
+ }
31187
+ const h2 = Math.floor(seconds / 3600);
31188
+ const m = Math.floor(seconds % 3600 / 60);
31189
+ return `${h2}h ${m}m`;
31190
+ }
31191
+ function formatBytes(bytes) {
31192
+ if (!bytes || bytes < 0) return "--";
31193
+ if (bytes < 1024) return `${bytes} B`;
31194
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
31195
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
31196
+ }
31197
+ function timeAgo(iso) {
31198
+ const diff = Date.now() - new Date(iso).getTime();
31199
+ if (diff < 6e4) return "just now";
31200
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
31201
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
31202
+ return `${Math.floor(diff / 864e5)}d ago`;
31203
+ }
31204
+ const STATUS_LABELS = {
31205
+ running: "Running",
31206
+ stalled: "Stalled",
31207
+ completed: "Completed",
31208
+ failed: "Failed",
31209
+ timed_out: "Timed out",
31210
+ cancelled: "Cancelled"
31211
+ };
31212
+ function StatusDot({ status }) {
31213
+ const isActive = status === "running";
31214
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `inline-block w-1.5 h-1.5 rounded-full shrink-0 ${isActive ? "bg-[var(--color-accent)]" : "t-bg-elevated"}`, style: isActive ? {} : { opacity: 0.8 } });
31215
+ }
31216
+ function ProgressBar({ percentage }) {
31217
+ const value = Math.min(100, Math.max(0, percentage || 0));
31218
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1 rounded-full t-bg-elevated overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
31219
+ "div",
31220
+ {
31221
+ className: "h-full rounded-full transition-all duration-700 t-gradient-accent-h",
31222
+ style: { width: `${value}%` }
31223
+ }
31224
+ ) });
31225
+ }
31226
+ function RunRow({
31227
+ run,
31228
+ expanded,
31229
+ onToggle
31230
+ }) {
31231
+ const isActive = run.status === "running" || run.status === "stalled";
31232
+ const hasProgress = run.progress?.percentage !== void 0;
31233
+ const setCenterView = useUIStore((s15) => s15.setCenterView);
31234
+ const sendToChat = (text2) => {
31235
+ setCenterView("chat");
31236
+ setTimeout(() => {
31237
+ const inputEl = document.querySelector("[data-chat-input]");
31238
+ if (inputEl) {
31239
+ inputEl.value = text2;
31240
+ inputEl.focus();
31241
+ inputEl.dispatchEvent(new Event("input", { bubbles: true }));
31242
+ }
31243
+ }, 100);
31244
+ };
31245
+ const hasMetrics = run.progress?.metrics && Object.keys(run.progress.metrics).length > 0;
31246
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-b t-border last:border-b-0", children: [
31247
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
31248
+ "div",
31249
+ {
31250
+ className: "flex items-center gap-3 px-3 py-2 hover:bg-[var(--color-accent-soft)]/5 transition-colors cursor-pointer",
31251
+ onClick: onToggle,
31252
+ children: [
31253
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "shrink-0 t-text-muted", children: expanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14 }) }),
31254
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatusDot, { status: run.status }),
31255
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
31256
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[13px] t-text font-medium truncate leading-tight font-mono", children: run.command }),
31257
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] t-text-muted truncate mt-0.5", children: [
31258
+ run.runId,
31259
+ run.status !== "running" && run.status !== "stalled" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
31260
+ " · ",
31261
+ STATUS_LABELS[run.status] ?? run.status
31262
+ ] }),
31263
+ run.stalled && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
31264
+ " · ",
31265
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-accent-soft", children: "stalled — no output for a while" })
31266
+ ] }),
31267
+ run.failure && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
31268
+ " · ",
31269
+ run.failure.code
31270
+ ] }),
31271
+ run.parentRunId && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: " · retry" })
31272
+ ] })
31273
+ ] }),
31274
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[11px] t-text-muted tabular-nums w-14 text-right", children: formatDuration(run.elapsedSeconds) }),
31275
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[11px] t-text-muted w-14 text-right", children: isActive ? run.currentPhase : run.startedAt ? timeAgo(run.startedAt) : "--" })
31276
+ ]
31277
+ }
31278
+ ),
31279
+ isActive && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-10 pb-1.5", children: [
31280
+ hasProgress ? /* @__PURE__ */ jsxRuntimeExports.jsx(ProgressBar, { percentage: run.progress.percentage }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1 rounded-full t-bg-elevated overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full w-1/3 rounded-full animate-pulse t-gradient-accent-h opacity-30" }) }),
31281
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mt-1 text-[10px] t-text-muted", children: [
31282
+ hasProgress && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31283
+ run.progress.percentage,
31284
+ "%"
31285
+ ] }),
31286
+ run.progress?.currentStep !== void 0 && run.progress?.totalSteps !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31287
+ "Step ",
31288
+ run.progress.currentStep,
31289
+ "/",
31290
+ run.progress.totalSteps
31291
+ ] }),
31292
+ run.progress?.phase && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: run.progress.phase }),
31293
+ run.progress?.etaSeconds !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31294
+ "ETA ",
31295
+ formatDuration(run.progress.etaSeconds)
31296
+ ] }),
31297
+ run.startedAt && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31298
+ "started ",
31299
+ timeAgo(run.startedAt)
31300
+ ] }),
31301
+ run.outputLines > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31302
+ run.outputLines,
31303
+ " lines"
31304
+ ] })
31305
+ ] }),
31306
+ hasMetrics && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-1.5 mt-1.5", children: Object.entries(run.progress.metrics).map(([key, val]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-secondary font-mono", children: [
31307
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: key }),
31308
+ " ",
31309
+ typeof val === "number" ? val < 0.01 ? val.toExponential(2) : val.toFixed(4) : val
31310
+ ] }, key)) })
31311
+ ] }),
31312
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-10 pb-3 space-y-2", children: [
31313
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-1.5", children: [
31314
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: run.sandbox }),
31315
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: run.weight }),
31316
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: [
31317
+ formatBytes(run.outputBytes),
31318
+ " · ",
31319
+ run.outputLines,
31320
+ " lines"
31321
+ ] }),
31322
+ run.exitCode !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: [
31323
+ "exit ",
31324
+ run.exitCode
31325
+ ] }),
31326
+ run.startedAt && !isActive && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-muted", children: [
31327
+ "started ",
31328
+ timeAgo(run.startedAt)
31329
+ ] })
31330
+ ] }),
31331
+ !isActive && hasMetrics && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-1.5", children: Object.entries(run.progress.metrics).map(([key, val]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 text-[10px] rounded t-bg-elevated t-text-secondary font-mono", children: [
31332
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-muted", children: key }),
31333
+ " ",
31334
+ typeof val === "number" ? val < 0.01 ? val.toExponential(2) : val.toFixed(4) : val
31335
+ ] }, key)) }),
31336
+ run.failure && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs t-text-secondary leading-relaxed", children: [
31337
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "font-medium", children: [
31338
+ run.failure.code,
31339
+ ": ",
31340
+ run.failure.message
31341
+ ] }),
31342
+ run.failure.suggestions.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("ul", { className: "mt-1 space-y-0.5", children: run.failure.suggestions.map((s15, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { className: "flex items-start gap-1.5 t-text-muted", children: [
31343
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 mt-1.5 w-1 h-1 rounded-full t-bg-elevated" }),
31344
+ s15
31345
+ ] }, i)) })
31346
+ ] }),
31347
+ run.parentRunId && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 text-[10px] t-text-muted", children: [
31348
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 10 }),
31349
+ "Retry of ",
31350
+ run.parentRunId
31351
+ ] }),
31352
+ run.outputTail && /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "p-2 rounded t-bg-elevated text-[10px] t-text-secondary font-mono overflow-x-auto max-h-40 overflow-y-auto whitespace-pre-wrap leading-relaxed", children: run.outputTail.slice(-2048) }),
31353
+ run.failure?.retryable && /* @__PURE__ */ jsxRuntimeExports.jsx(
31354
+ "button",
31355
+ {
31356
+ onClick: (e) => {
31357
+ e.stopPropagation();
31358
+ sendToChat(`Compute run ${run.runId} failed with ${run.failure.code}. Please review the error and fix the code, then retry.`);
31359
+ },
31360
+ className: "text-[10px] t-text-accent hover:underline",
31361
+ children: "Fix & retry in chat"
31362
+ }
31363
+ )
31364
+ ] })
31365
+ ] });
31366
+ }
31367
+ function FilterBar({
31368
+ search: search2,
31369
+ onSearchChange
31370
+ }) {
31371
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-2 border-b t-border", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [
31372
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { size: 13, className: "absolute left-2.5 top-1/2 -translate-y-1/2 t-text-muted" }),
31373
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
31374
+ "input",
31375
+ {
31376
+ type: "text",
31377
+ value: search2,
31378
+ onChange: (e) => onSearchChange(e.target.value),
31379
+ placeholder: "Search runs by command or ID...",
31380
+ className: "w-full pl-8 pr-3 py-1.5 text-xs rounded-lg border t-border t-bg-surface t-text focus:outline-none focus:border-[var(--color-accent-soft)]"
31381
+ }
31382
+ ),
31383
+ search2 && /* @__PURE__ */ jsxRuntimeExports.jsx(
31384
+ "button",
31385
+ {
31386
+ onClick: () => onSearchChange(""),
31387
+ className: "absolute right-2 top-1/2 -translate-y-1/2 t-text-muted hover:t-text",
31388
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 12 })
31389
+ }
31390
+ )
31391
+ ] }) });
31392
+ }
31393
+ function CoverageBar({ runs }) {
31394
+ const completed = runs.filter((r) => r.status === "completed").length;
31395
+ const failed = runs.filter((r) => ["failed", "timed_out"].includes(r.status)).length;
31396
+ const total = runs.length;
31397
+ const successRate = total > 0 ? completed / total : 0;
31398
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 px-4 py-2 border-t t-border t-bg-surface text-[11px] t-text-muted", children: [
31399
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31400
+ total,
31401
+ " runs"
31402
+ ] }),
31403
+ completed > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31404
+ completed,
31405
+ " completed"
31406
+ ] }),
31407
+ failed > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31408
+ failed,
31409
+ " failed"
31410
+ ] }),
31411
+ total > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
31412
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 max-w-48", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-1.5 rounded-full t-bg-elevated overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
31413
+ "div",
31414
+ {
31415
+ className: "h-full rounded-full t-gradient-accent-h",
31416
+ style: { width: `${successRate * 100}%` }
31417
+ }
31418
+ ) }) }),
31419
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
31420
+ Math.round(successRate * 100),
31421
+ "% success"
31422
+ ] })
31423
+ ] })
31424
+ ] });
31425
+ }
31426
+ function EmptyState() {
31427
+ const environment = useComputeStore((s15) => s15.environment);
31428
+ const setCenterView = useUIStore((s15) => s15.setCenterView);
31429
+ const goToChat = (text2) => {
31430
+ setCenterView("chat");
31431
+ setTimeout(() => {
31432
+ const inputEl = document.querySelector("[data-chat-input]");
31433
+ if (inputEl) {
31434
+ inputEl.value = text2;
31435
+ inputEl.focus();
31436
+ inputEl.dispatchEvent(new Event("input", { bubbles: true }));
31437
+ }
31438
+ }, 100);
31439
+ };
31440
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full text-center px-8", children: [
31441
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Cpu, { size: 32, className: "t-text-muted mb-3 opacity-30" }),
31442
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm t-text font-medium mb-1", children: "Your compute environment is ready" }),
31443
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs t-text-muted max-w-sm leading-relaxed mb-4", children: "Ask the agent to run scripts, train models, or process data. Code executes in a sandboxed environment with progress tracking and failure analysis." }),
31444
+ environment && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2.5 rounded-lg t-bg-elevated text-[11px] t-text-secondary mb-5", children: [
31445
+ environment.gpu || `${environment.os} ${environment.arch}`,
31446
+ " · ",
31447
+ Math.round(environment.totalMemoryMb / 1024),
31448
+ " GB",
31449
+ environment.mlxAvailable && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
31450
+ " · ",
31451
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-accent", children: "MLX" })
31452
+ ] }),
31453
+ environment.sandbox === "docker" && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: " · Docker" })
31454
+ ] }),
31455
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1.5 w-full max-w-sm", children: [
31456
+ "Train a model on my dataset",
31457
+ "Run my analysis script",
31458
+ "Process and clean this data"
31459
+ ].map((prompt) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
31460
+ "button",
31461
+ {
31462
+ onClick: () => goToChat(prompt),
31463
+ className: "w-full flex items-center justify-between px-3 py-2 rounded-lg border t-border-subtle hover:bg-[var(--color-accent-soft)]/5 transition-colors text-left",
31464
+ children: [
31465
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs t-text-secondary", children: [
31466
+ '"',
31467
+ prompt,
31468
+ '"'
31469
+ ] }),
31470
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 12, className: "t-text-muted" })
31471
+ ]
31472
+ },
31473
+ prompt
31474
+ )) })
31475
+ ] });
31476
+ }
31477
+ function ComputeView() {
31478
+ const activeRuns = useActiveRuns();
31479
+ const recentRuns = useRecentRuns();
31480
+ const [expandedId, setExpandedId] = reactExports.useState(null);
31481
+ const [search2, setSearch] = reactExports.useState("");
31482
+ const allRuns = reactExports.useMemo(() => [...activeRuns, ...recentRuns], [activeRuns, recentRuns]);
31483
+ const filtered = reactExports.useMemo(() => {
31484
+ if (!search2.trim()) return allRuns;
31485
+ const q2 = search2.toLowerCase();
31486
+ return allRuns.filter(
31487
+ (r) => r.command.toLowerCase().includes(q2) || r.runId.toLowerCase().includes(q2)
31488
+ );
31489
+ }, [allRuns, search2]);
31490
+ const isEmpty = allRuns.length === 0;
31491
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-h-0", children: [
31492
+ !isEmpty && /* @__PURE__ */ jsxRuntimeExports.jsx(FilterBar, { search: search2, onSearchChange: setSearch }),
31493
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto", children: isEmpty ? /* @__PURE__ */ jsxRuntimeExports.jsx(EmptyState, {}) : filtered.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full text-center px-8", children: [
31494
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Cpu, { size: 32, className: "t-text-muted mb-3 opacity-40" }),
31495
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm t-text-muted", children: "No runs match your search." })
31496
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
31497
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 px-3 py-1.5 border-b t-border t-bg-surface", children: [
31498
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5" }),
31499
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-1.5" }),
31500
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 text-[10px] uppercase tracking-wider font-medium t-text-muted", children: "Command" }),
31501
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-14 text-right text-[10px] uppercase tracking-wider font-medium t-text-muted", children: "Duration" }),
31502
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-14 text-right text-[10px] uppercase tracking-wider font-medium t-text-muted", children: "Phase" })
31503
+ ] }),
31504
+ filtered.map((run) => /* @__PURE__ */ jsxRuntimeExports.jsx(
31505
+ RunRow,
31506
+ {
31507
+ run,
31508
+ expanded: expandedId === run.runId,
31509
+ onToggle: () => setExpandedId(expandedId === run.runId ? null : run.runId)
31510
+ },
31511
+ run.runId
31512
+ ))
31513
+ ] }) }),
31514
+ !isEmpty && /* @__PURE__ */ jsxRuntimeExports.jsx(CoverageBar, { runs: allRuns })
31515
+ ] });
31516
+ }
31517
+ const api$2 = window.api;
31518
+ const computeEnabled = api$2?.isComputeEnabled?.() ?? false;
29949
31519
  const viewTabs = [
29950
31520
  { key: "chat", label: "Chat", icon: MessageSquare, shortcut: "⌘1" },
29951
- { key: "literature", label: "Literature", icon: BookOpen, shortcut: "⌘2" }
31521
+ { key: "literature", label: "Literature", icon: BookOpen, shortcut: "⌘2" },
31522
+ ...computeEnabled ? [{ key: "compute", label: "Compute", icon: Cpu, shortcut: "⌘3" }] : []
29952
31523
  ];
29953
31524
  function ViewSwitcher() {
29954
31525
  const centerView = useUIStore((s15) => s15.centerView);
29955
31526
  const setCenterView = useUIStore((s15) => s15.setCenterView);
29956
31527
  const paperCount = useEntityStore((s15) => s15.papers.length);
29957
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-0.5 px-4 pt-10 pb-1", children: viewTabs.map(({ key, label, icon: Icon2, shortcut }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
31528
+ const activeComputeRuns = useActiveRunCount();
31529
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("nav", { "aria-label": "View switcher", className: "flex items-center gap-0.5 px-4 pt-10 pb-1", children: viewTabs.map(({ key, label, icon: Icon2, shortcut }) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
29958
31530
  "button",
29959
31531
  {
29960
31532
  onClick: () => setCenterView(key),
@@ -29963,7 +31535,9 @@ function ViewSwitcher() {
29963
31535
  children: [
29964
31536
  /* @__PURE__ */ jsxRuntimeExports.jsx(Icon2, { size: 13 }),
29965
31537
  label,
29966
- key === "literature" && paperCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-0.5 px-1 py-px text-[9px] rounded-full t-bg-elevated t-text-muted tabular-nums", children: paperCount })
31538
+ key === "literature" && paperCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-0.5 px-1 py-px text-[9px] rounded-full t-bg-elevated t-text-muted tabular-nums", children: paperCount }),
31539
+ key === "compute" && activeComputeRuns > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-0.5 px-1 py-px text-[9px] rounded-full bg-[var(--color-accent-soft)]/10 t-text-accent tabular-nums", children: activeComputeRuns }),
31540
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] t-text-muted opacity-40 ml-0.5", children: shortcut })
29967
31541
  ]
29968
31542
  },
29969
31543
  key
@@ -29975,12 +31549,18 @@ function CenterPanel() {
29975
31549
  const messages = useChatStore((s15) => s15.messages);
29976
31550
  const showHero = isIdle && messages.length === 0;
29977
31551
  if (centerView === "literature") {
29978
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { className: "flex-1 flex flex-col min-w-0", children: [
31552
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { id: "main-content", className: "flex-1 flex flex-col min-w-0", children: [
29979
31553
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewSwitcher, {}),
29980
31554
  /* @__PURE__ */ jsxRuntimeExports.jsx(LiteratureView, {})
29981
31555
  ] });
29982
31556
  }
29983
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { className: "flex-1 flex flex-col min-w-0", children: [
31557
+ if (centerView === "compute") {
31558
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { id: "main-content", className: "flex-1 flex flex-col min-w-0", children: [
31559
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ViewSwitcher, {}),
31560
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ComputeView, {})
31561
+ ] });
31562
+ }
31563
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { id: "main-content", className: "flex-1 flex flex-col min-w-0", children: [
29984
31564
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewSwitcher, {}),
29985
31565
  showHero ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HeroIdle, {}) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 px-6 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-auto h-full", style: { maxWidth: "48rem" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChatMessages, {}) }) }),
29986
31566
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-6 pb-5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-auto", style: { maxWidth: "48rem" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChatInput, {}) }) })
@@ -29988,7 +31568,7 @@ function CenterPanel() {
29988
31568
  }
29989
31569
  const remarkPlugins = [remarkGfm];
29990
31570
  const LazyMilkdownMarkdownEditor = reactExports.lazy(async () => {
29991
- const mod = await __vitePreload(() => import("./MilkdownMarkdownEditor-Czh2N6UQ.js").then((n) => n.bL), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
31571
+ const mod = await __vitePreload(() => import("./MilkdownMarkdownEditor-jaF-aGPn.js").then((n) => n.bL), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
29992
31572
  return { default: mod.MilkdownMarkdownEditor };
29993
31573
  });
29994
31574
  const typeIcons = {
@@ -30585,6 +32165,9 @@ function StatusBar() {
30585
32165
  const events = useActivityStore((s15) => s15.events);
30586
32166
  const activeSkills = useActivityStore((s15) => s15.activeSkills);
30587
32167
  const runTokens = useUsageStore((s15) => s15.runTokens);
32168
+ const runPromptTokens = useUsageStore((s15) => s15.runPromptTokens);
32169
+ const runCompletionTokens = useUsageStore((s15) => s15.runCompletionTokens);
32170
+ const runCachedTokens = useUsageStore((s15) => s15.runCachedTokens);
30588
32171
  const runCost = useUsageStore((s15) => s15.runCost);
30589
32172
  const runCacheHitRate = useUsageStore((s15) => s15.runCacheHitRate);
30590
32173
  const allTimeTokens = useUsageStore((s15) => s15.allTimeTokens);
@@ -30610,7 +32193,7 @@ function StatusBar() {
30610
32193
  const hasSkills = activeSkills.length > 0;
30611
32194
  const hasRunUsage = runTokens > 0;
30612
32195
  const hasProjectUsage = allTimeTokens > 0;
30613
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-7 flex items-center px-4 gap-5 border-t t-border t-bg-surface text-[11px] t-text-muted select-none shrink-0", children: [
32196
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-7 flex items-center px-4 gap-5 border-t t-border t-bg-surface text-[11px] t-text-secondary select-none shrink-0", children: [
30614
32197
  hasSkills && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 overflow-hidden", children: activeSkills.map((name2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 px-1.5 py-0.5 rounded t-bg-accent/10 t-text-accent whitespace-nowrap", children: [
30615
32198
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px]", children: "⚡" }),
30616
32199
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: name2 })
@@ -30626,12 +32209,12 @@ function StatusBar() {
30626
32209
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
30627
32210
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 whitespace-nowrap", children: [
30628
32211
  hasRunUsage && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
30629
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
32212
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { title: `In: ${formatTokens(runPromptTokens)} · Cache: ${formatTokens(runCachedTokens)} · Out: ${formatTokens(runCompletionTokens)}`, children: [
30630
32213
  formatTokens(runTokens),
30631
32214
  " tokens"
30632
32215
  ] }),
30633
32216
  runCost > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "t-text-success", children: formatCost(runCost) }),
30634
- runCacheHitRate > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "t-text-accent-soft", children: [
32217
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: runCacheHitRate > 0.5 ? "t-text-accent" : "t-text-accent-soft", children: [
30635
32218
  Math.round(runCacheHitRate * 100),
30636
32219
  "% cache"
30637
32220
  ] })
@@ -39234,22 +40817,17 @@ function TerminalPanel() {
39234
40817
  };
39235
40818
  reactExports.useEffect(() => {
39236
40819
  if (!termRef.current) return;
39237
- const isDark = theme === "dark";
40820
+ const cs2 = getComputedStyle(document.documentElement);
39238
40821
  const term = new Dl({
39239
40822
  fontSize: 13,
39240
40823
  fontFamily: 'Menlo, Monaco, "Courier New", monospace',
39241
40824
  cursorBlink: true,
39242
40825
  allowProposedApi: true,
39243
- theme: isDark ? {
39244
- background: "#1a1a1a",
39245
- foreground: "#e0e0e0",
39246
- cursor: "#e0e0e0",
39247
- selectionBackground: "#3a3a5a"
39248
- } : {
39249
- background: "#fafafa",
39250
- foreground: "#1a1a1a",
39251
- cursor: "#1a1a1a",
39252
- selectionBackground: "#c0d0e0"
40826
+ theme: {
40827
+ background: cs2.getPropertyValue("--color-bg-base").trim(),
40828
+ foreground: cs2.getPropertyValue("--color-text").trim(),
40829
+ cursor: cs2.getPropertyValue("--color-text").trim(),
40830
+ selectionBackground: cs2.getPropertyValue("--color-bg-elevated").trim()
39253
40831
  }
39254
40832
  });
39255
40833
  const fit = new o();
@@ -39306,17 +40884,12 @@ function TerminalPanel() {
39306
40884
  reactExports.useEffect(() => {
39307
40885
  const term = xtermRef.current;
39308
40886
  if (!term) return;
39309
- const isDark = theme === "dark";
39310
- term.options.theme = isDark ? {
39311
- background: "#1a1a1a",
39312
- foreground: "#e0e0e0",
39313
- cursor: "#e0e0e0",
39314
- selectionBackground: "#3a3a5a"
39315
- } : {
39316
- background: "#fafafa",
39317
- foreground: "#1a1a1a",
39318
- cursor: "#1a1a1a",
39319
- selectionBackground: "#c0d0e0"
40887
+ const cs2 = getComputedStyle(document.documentElement);
40888
+ term.options.theme = {
40889
+ background: cs2.getPropertyValue("--color-bg-base").trim(),
40890
+ foreground: cs2.getPropertyValue("--color-text").trim(),
40891
+ cursor: cs2.getPropertyValue("--color-text").trim(),
40892
+ selectionBackground: cs2.getPropertyValue("--color-bg-elevated").trim()
39320
40893
  };
39321
40894
  }, [theme]);
39322
40895
  reactExports.useEffect(() => {
@@ -39343,8 +40916,9 @@ function TerminalPanel() {
39343
40916
  "button",
39344
40917
  {
39345
40918
  onClick: handleRestart,
39346
- className: "p-0.5 rounded t-text-muted hover:t-text-secondary t-bg-hover",
40919
+ className: "p-1 rounded t-text-muted hover:t-text-secondary t-bg-hover",
39347
40920
  title: "Restart terminal",
40921
+ "aria-label": "Restart terminal",
39348
40922
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 12 })
39349
40923
  }
39350
40924
  ),
@@ -39352,8 +40926,9 @@ function TerminalPanel() {
39352
40926
  "button",
39353
40927
  {
39354
40928
  onClick: handleClose,
39355
- className: "p-0.5 rounded t-text-muted hover:t-text-secondary t-bg-hover",
40929
+ className: "p-1 rounded t-text-muted hover:t-text-secondary t-bg-hover",
39356
40930
  title: "Close terminal",
40931
+ "aria-label": "Close terminal",
39357
40932
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { size: 12 })
39358
40933
  }
39359
40934
  )
@@ -39390,6 +40965,53 @@ class ErrorBoundary extends reactExports.Component {
39390
40965
  ] }) });
39391
40966
  }
39392
40967
  }
40968
+ function lastNLines(text2, n) {
40969
+ const lines = text2.split("\n").filter(Boolean);
40970
+ return lines.slice(-n).join("\n");
40971
+ }
40972
+ function extractPartialOutput(tool, data) {
40973
+ if (!data?.partialResult) return void 0;
40974
+ const pr = data.partialResult;
40975
+ const text2 = pr?.content?.[0]?.text;
40976
+ if (typeof text2 === "string" && text2.length > 0) {
40977
+ const maxLines = tool === "bash" ? 5 : 3;
40978
+ return lastNLines(text2, maxLines);
40979
+ }
40980
+ return void 0;
40981
+ }
40982
+ const useToolProgressStore = create$1((set) => ({
40983
+ inFlight: /* @__PURE__ */ new Map(),
40984
+ reportProgress: (event) => {
40985
+ set((state) => {
40986
+ const next = new Map(state.inFlight);
40987
+ const phase = event.phase;
40988
+ if (phase === "start") {
40989
+ next.set(event.toolCallId, {
40990
+ tool: event.tool,
40991
+ toolCallId: event.toolCallId,
40992
+ phase,
40993
+ startedAt: event.timestamp,
40994
+ updatedAt: event.timestamp
40995
+ });
40996
+ } else if (phase === "update") {
40997
+ const existing = next.get(event.toolCallId);
40998
+ const partialOutput = extractPartialOutput(event.tool, event.data);
40999
+ next.set(event.toolCallId, {
41000
+ tool: event.tool,
41001
+ toolCallId: event.toolCallId,
41002
+ phase,
41003
+ partialOutput: partialOutput ?? existing?.partialOutput,
41004
+ startedAt: existing?.startedAt ?? event.timestamp,
41005
+ updatedAt: event.timestamp
41006
+ });
41007
+ } else if (phase === "end") {
41008
+ next.delete(event.toolCallId);
41009
+ }
41010
+ return { inFlight: next };
41011
+ });
41012
+ },
41013
+ clearAll: () => set({ inFlight: /* @__PURE__ */ new Map() })
41014
+ }));
39393
41015
  const api = window.api;
39394
41016
  function FolderGate({ onOpenSettings }) {
39395
41017
  const pickFolder = useSessionStore((s15) => s15.pickFolder);
@@ -39407,19 +41029,14 @@ function FolderGate({ onOpenSettings }) {
39407
41029
  /* @__PURE__ */ jsxRuntimeExports.jsx(
39408
41030
  "div",
39409
41031
  {
39410
- className: "w-14 h-14 rounded-2xl flex items-center justify-center",
39411
- style: {
39412
- background: "linear-gradient(135deg, var(--color-accent) 0%, var(--color-accent-2) 100%)",
39413
- boxShadow: "0 8px 32px var(--color-accent-2-muted)"
39414
- },
41032
+ className: "w-14 h-14 rounded-2xl flex items-center justify-center t-gradient-accent t-gradient-accent-shadow-lg",
39415
41033
  children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-white text-xl font-bold tracking-tight", children: "P" })
39416
41034
  }
39417
41035
  ),
39418
41036
  /* @__PURE__ */ jsxRuntimeExports.jsx(
39419
41037
  "div",
39420
41038
  {
39421
- className: "absolute -inset-2 rounded-3xl opacity-15 blur-xl -z-10",
39422
- style: { background: "linear-gradient(135deg, var(--color-accent), var(--color-accent-2))" }
41039
+ className: "absolute -inset-2 rounded-3xl opacity-15 blur-xl -z-10 t-gradient-accent"
39423
41040
  }
39424
41041
  )
39425
41042
  ] }),
@@ -39440,11 +41057,7 @@ function FolderGate({ onOpenSettings }) {
39440
41057
  "button",
39441
41058
  {
39442
41059
  onClick: handlePick,
39443
- className: "inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-white text-sm font-medium\n hover:opacity-90 transition-all duration-200",
39444
- style: {
39445
- background: "linear-gradient(135deg, var(--color-accent) 0%, var(--color-accent-2) 100%)",
39446
- boxShadow: "0 4px 16px var(--color-accent-2-muted)"
39447
- },
41060
+ className: "inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-white text-sm font-medium\n hover:opacity-90 transition-all duration-200 t-gradient-accent t-gradient-accent-shadow",
39448
41061
  children: [
39449
41062
  /* @__PURE__ */ jsxRuntimeExports.jsx(FolderOpen, { size: 16 }),
39450
41063
  "Open Project Folder"
@@ -39521,6 +41134,16 @@ function App() {
39521
41134
  });
39522
41135
  }
39523
41136
  }
41137
+ if (snapshot && snapshot.toolEvents?.length > 0) {
41138
+ const toolStore = useToolEventsStore.getState();
41139
+ for (const evt of snapshot.toolEvents) {
41140
+ if (evt.type === "tool-call") {
41141
+ toolStore.onToolCall(evt);
41142
+ } else if (evt.type === "tool-result") {
41143
+ toolStore.onToolResult(evt);
41144
+ }
41145
+ }
41146
+ }
39524
41147
  });
39525
41148
  api.getCurrentSession().then((session) => {
39526
41149
  useChatStore.getState().loadInitial(session.sessionId);
@@ -39536,14 +41159,26 @@ function App() {
39536
41159
  });
39537
41160
  const unsubActivityClear = api.onActivityClear(() => {
39538
41161
  useActivityStore.getState().clear();
41162
+ useToolProgressStore.getState().clearAll();
41163
+ useToolEventsStore.getState().clearRun();
39539
41164
  useUsageStore.getState().resetRun();
39540
41165
  });
39541
41166
  const unsubActivity = api.onActivity((event) => {
39542
- useActivityStore.getState().push({
41167
+ const enrichedEvent = {
39543
41168
  id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
39544
41169
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
39545
41170
  ...event
39546
- });
41171
+ };
41172
+ useActivityStore.getState().push(enrichedEvent);
41173
+ if (event.type === "tool-call") {
41174
+ useToolEventsStore.getState().onToolCall(enrichedEvent);
41175
+ } else if (event.type === "tool-result") {
41176
+ useToolEventsStore.getState().onToolResult(enrichedEvent);
41177
+ }
41178
+ });
41179
+ const unsubToolProgress = api.onToolProgress((event) => {
41180
+ useToolProgressStore.getState().reportProgress(event);
41181
+ useToolEventsStore.getState().onToolProgress(event);
39547
41182
  });
39548
41183
  const unsubSkillLoaded = api.onSkillLoaded((skillName) => {
39549
41184
  useActivityStore.getState().addSkill(skillName);
@@ -39581,6 +41216,19 @@ function App() {
39581
41216
  const unsub6 = api.onEntityCreated(() => {
39582
41217
  refreshEntities();
39583
41218
  });
41219
+ if (api?.isComputeEnabled?.()) {
41220
+ api.probeComputeEnvironment?.().catch(() => {
41221
+ });
41222
+ }
41223
+ const unsubComputeUpdate = api.onComputeRunUpdate((event) => {
41224
+ useComputeStore.getState().updateRun(event.runId, event);
41225
+ });
41226
+ const unsubComputeComplete = api.onComputeRunComplete((event) => {
41227
+ useComputeStore.getState().updateRun(event.runId, event);
41228
+ });
41229
+ const unsubComputeEnv = api.onComputeEnvironment((event) => {
41230
+ useComputeStore.getState().setEnvironment(event);
41231
+ });
39584
41232
  return () => {
39585
41233
  unsub1();
39586
41234
  unsub2();
@@ -39590,8 +41238,12 @@ function App() {
39590
41238
  unsub6();
39591
41239
  unsubActivity();
39592
41240
  unsubActivityClear();
41241
+ unsubToolProgress();
39593
41242
  unsubSkillLoaded();
39594
41243
  unsubUsage();
41244
+ unsubComputeUpdate();
41245
+ unsubComputeComplete();
41246
+ unsubComputeEnv();
39595
41247
  };
39596
41248
  }, [hasProject]);
39597
41249
  reactExports.useEffect(() => {
@@ -39603,12 +41255,6 @@ function App() {
39603
41255
  reactExports.useEffect(() => {
39604
41256
  const handler = (e) => {
39605
41257
  if (previewEditorFocused) return;
39606
- if ((e.metaKey || e.ctrlKey) && e.key === "n") {
39607
- e.preventDefault();
39608
- useChatStore.getState().clear();
39609
- useUIStore.getState().setIdle(true);
39610
- useUIStore.getState().closePreview();
39611
- }
39612
41258
  if ((e.metaKey || e.ctrlKey) && e.key === "1") {
39613
41259
  e.preventDefault();
39614
41260
  useUIStore.getState().setCenterView("chat");
@@ -39617,6 +41263,10 @@ function App() {
39617
41263
  e.preventDefault();
39618
41264
  useUIStore.getState().setCenterView("literature");
39619
41265
  }
41266
+ if ((e.metaKey || e.ctrlKey) && e.key === "3" && api?.isComputeEnabled?.()) {
41267
+ e.preventDefault();
41268
+ useUIStore.getState().setCenterView("compute");
41269
+ }
39620
41270
  if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === "K") {
39621
41271
  e.preventDefault();
39622
41272
  if (useChatStore.getState().isStreaming) {
@@ -39648,6 +41298,7 @@ function App() {
39648
41298
  return /* @__PURE__ */ jsxRuntimeExports.jsx(FolderGate, { onOpenSettings: () => setShowApiKeySetup(true) });
39649
41299
  }
39650
41300
  return /* @__PURE__ */ jsxRuntimeExports.jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-screen w-screen t-bg-base t-text", children: [
41301
+ /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "#main-content", className: "skip-link", children: "Skip to content" }),
39651
41302
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "drag-region fixed top-0 left-0 right-0 h-8 z-50" }),
39652
41303
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 min-h-0", children: [
39653
41304
  !leftCollapsed && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: previewEntity ? "hidden" : "contents", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LeftSidebar, {}) }),