@ottocode/web-sdk 0.1.274 → 0.1.276

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 (33) hide show
  1. package/dist/components/chat/ChatInputContainer.d.ts.map +1 -1
  2. package/dist/components/file-browser/FileViewerPanel.d.ts +2 -1
  3. package/dist/components/file-browser/FileViewerPanel.d.ts.map +1 -1
  4. package/dist/components/index.js +884 -292
  5. package/dist/components/index.js.map +18 -18
  6. package/dist/components/messages/MessagePartItem.d.ts.map +1 -1
  7. package/dist/components/messages/renderers/DiffView.d.ts.map +1 -1
  8. package/dist/components/ui/Toaster.d.ts.map +1 -1
  9. package/dist/components/workspace/ToolPreviewPanel.d.ts.map +1 -1
  10. package/dist/components/workspace/ViewerTabs.d.ts.map +1 -1
  11. package/dist/hooks/index.js +301 -60
  12. package/dist/hooks/index.js.map +8 -8
  13. package/dist/hooks/useProviderUsage.d.ts +1 -1
  14. package/dist/hooks/useProviderUsage.d.ts.map +1 -1
  15. package/dist/hooks/useQueueState.d.ts +9 -0
  16. package/dist/hooks/useQueueState.d.ts.map +1 -1
  17. package/dist/hooks/useSessionStream.d.ts.map +1 -1
  18. package/dist/index.js +885 -292
  19. package/dist/index.js.map +18 -18
  20. package/dist/lib/api-client/index.d.ts +6 -0
  21. package/dist/lib/api-client/index.d.ts.map +1 -1
  22. package/dist/lib/api-client/sessions.d.ts +6 -0
  23. package/dist/lib/api-client/sessions.d.ts.map +1 -1
  24. package/dist/lib/commands.d.ts.map +1 -1
  25. package/dist/lib/index.js +17 -1
  26. package/dist/lib/index.js.map +4 -4
  27. package/dist/stores/index.js +77 -16
  28. package/dist/stores/index.js.map +3 -3
  29. package/dist/stores/viewerTabsStore.d.ts +9 -0
  30. package/dist/stores/viewerTabsStore.d.ts.map +1 -1
  31. package/dist/types/api.d.ts +1 -1
  32. package/dist/types/api.d.ts.map +1 -1
  33. package/package.json +5 -3
package/dist/index.js CHANGED
@@ -480,6 +480,8 @@ function ToastItem({ toast: toast2 }) {
480
480
  };
481
481
  const style = typeStyles[toast2.type];
482
482
  const transitionClass = phase === "enter" ? "opacity-0 translate-y-2 scale-95" : phase === "exit" ? "opacity-0 translate-y-1 scale-95" : "opacity-100 translate-y-0 scale-100";
483
+ const rowAlignmentClass = toast2.type === "loading" ? "items-center" : "items-start";
484
+ const iconOffsetClass = toast2.type === "loading" ? "" : "mt-px";
483
485
  return /* @__PURE__ */ jsxs3("div", {
484
486
  className: `
485
487
  relative overflow-hidden
@@ -494,10 +496,10 @@ function ToastItem({ toast: toast2 }) {
494
496
  role: "alert",
495
497
  children: [
496
498
  /* @__PURE__ */ jsxs3("div", {
497
- className: "flex items-start gap-2.5 px-3 py-2.5",
499
+ className: `flex ${rowAlignmentClass} gap-2.5 px-3 py-2.5`,
498
500
  children: [
499
501
  /* @__PURE__ */ jsx7("span", {
500
- className: "mt-px shrink-0",
502
+ className: `${iconOffsetClass} shrink-0`,
501
503
  children: style.icon
502
504
  }),
503
505
  /* @__PURE__ */ jsx7("div", {
@@ -1191,6 +1193,21 @@ var sessionsMixin = {
1191
1193
  throw new Error(extractErrorMessage(response.error));
1192
1194
  return response.data;
1193
1195
  },
1196
+ async createHandoff(sessionId) {
1197
+ const response = await fetch(`${getBaseUrl()}/v1/sessions/${encodeURIComponent(sessionId)}/handoff`, { method: "POST" });
1198
+ const data = await response.json().catch(() => null);
1199
+ if (!response.ok)
1200
+ throw new Error(extractErrorMessage(data));
1201
+ if (!data?.session || !data?.sessionId) {
1202
+ throw new Error("No data returned from handoff");
1203
+ }
1204
+ return {
1205
+ session: convertSession(data.session),
1206
+ sessionId: String(data.sessionId),
1207
+ sourceSessionId: String(data.sourceSessionId),
1208
+ message: String(data.message ?? "")
1209
+ };
1210
+ },
1194
1211
  async abortSession(sessionId) {
1195
1212
  const response = await apiAbortSession({ path: { sessionId } });
1196
1213
  if (response.error)
@@ -1925,6 +1942,7 @@ class ApiClient {
1925
1942
  updateSession = sessionsMixin.updateSession;
1926
1943
  markSessionViewed = sessionsMixin.markSessionViewed;
1927
1944
  deleteSession = sessionsMixin.deleteSession;
1945
+ createHandoff = sessionsMixin.createHandoff;
1928
1946
  abortSession = sessionsMixin.abortSession;
1929
1947
  abortMessage = sessionsMixin.abortMessage;
1930
1948
  getQueueState = sessionsMixin.getQueueState;
@@ -2199,7 +2217,8 @@ import {
2199
2217
  Trash2,
2200
2218
  Share2,
2201
2219
  RefreshCw,
2202
- FileText as FileText2
2220
+ FileText as FileText2,
2221
+ ArrowRightLeft
2203
2222
  } from "lucide-react";
2204
2223
  var COMMANDS = [
2205
2224
  {
@@ -2268,6 +2287,12 @@ var COMMANDS = [
2268
2287
  description: "Generate AGENTS.md and .agents docs from the real repo structure",
2269
2288
  icon: FileText2
2270
2289
  },
2290
+ {
2291
+ id: "handoff",
2292
+ label: "/handoff",
2293
+ description: "Create a new session with current context",
2294
+ icon: ArrowRightLeft
2295
+ },
2271
2296
  {
2272
2297
  id: "branch",
2273
2298
  label: "/branch",
@@ -3840,14 +3865,26 @@ import { useQuery as useQuery4 } from "@tanstack/react-query";
3840
3865
  var defaultQueueState = {
3841
3866
  currentMessageId: null,
3842
3867
  queuedMessages: [],
3843
- queueLength: 0
3868
+ queueLength: 0,
3869
+ isRunning: false
3844
3870
  };
3871
+ function normalizeQueueState(state) {
3872
+ const isRunning = state.isRunning ?? Boolean(state.currentMessageId);
3873
+ const currentMessageId = isRunning ? state.currentMessageId : null;
3874
+ const hasActiveTurn = Boolean(currentMessageId);
3875
+ const queuedMessages = hasActiveTurn ? state.queuedMessages : [];
3876
+ return {
3877
+ currentMessageId,
3878
+ queuedMessages,
3879
+ queueLength: queuedMessages.length,
3880
+ isRunning: hasActiveTurn
3881
+ };
3882
+ }
3845
3883
  function optimisticallyQueueMessage(queryClient, sessionId, messageId) {
3846
3884
  queryClient.setQueryData(["queueState", sessionId], (current) => {
3847
3885
  if (!current)
3848
3886
  return current;
3849
- const isBusy = Boolean(current.currentMessageId) || current.queuedMessages.length > 0 || current.queueLength > 0;
3850
- if (!isBusy)
3887
+ if (!current.isRunning || !current.currentMessageId)
3851
3888
  return current;
3852
3889
  if (current.currentMessageId === messageId)
3853
3890
  return current;
@@ -3872,11 +3909,7 @@ function useQueueState(sessionId) {
3872
3909
  if (!sessionId)
3873
3910
  return defaultQueueState;
3874
3911
  const queueState = await apiClient.getQueueState(sessionId);
3875
- return {
3876
- currentMessageId: queueState.currentMessageId,
3877
- queuedMessages: queueState.queuedMessages,
3878
- queueLength: queueState.queuedMessages.length
3879
- };
3912
+ return normalizeQueueState(queueState);
3880
3913
  },
3881
3914
  enabled: !!sessionId,
3882
3915
  placeholderData: defaultQueueState,
@@ -5219,6 +5252,53 @@ function mergeChangedLines(existing, incoming) {
5219
5252
  return existing;
5220
5253
  return [...new Set([...existing, ...incoming])].sort((a, b) => a - b);
5221
5254
  }
5255
+ function countContentLines(content) {
5256
+ return content.length === 0 ? 1 : content.split(`
5257
+ `).length;
5258
+ }
5259
+ function annotationId(preview, targetPath) {
5260
+ return `${preview.toolName}:${preview.callId ?? `${normalizeViewerPath(targetPath)}:${preview.patch ?? preview.content ?? ""}`}`;
5261
+ }
5262
+ function buildAnnotation(preview, targetPath, existing) {
5263
+ if (preview.status === "error")
5264
+ return existing;
5265
+ const id = annotationId(preview, targetPath);
5266
+ if (preview.toolName === "write") {
5267
+ const content = preview.content;
5268
+ if (content === undefined)
5269
+ return existing;
5270
+ return {
5271
+ id,
5272
+ reason: "write",
5273
+ callId: preview.callId,
5274
+ status: preview.status,
5275
+ lineTones: Array.from({ length: countContentLines(content) }, (_, index) => [index + 1, "add"]),
5276
+ createdAt: existing?.createdAt ?? Date.now()
5277
+ };
5278
+ }
5279
+ const lineTones = preview.previewLineTones?.length ? preview.previewLineTones : preview.changedLines?.length ? preview.changedLines.map((line) => [line, "add"]) : existing?.lineTones;
5280
+ if (!lineTones?.length)
5281
+ return existing;
5282
+ return {
5283
+ id,
5284
+ reason: "apply_patch",
5285
+ callId: preview.callId,
5286
+ status: preview.status,
5287
+ lineTones,
5288
+ createdAt: existing?.createdAt ?? Date.now()
5289
+ };
5290
+ }
5291
+ function upsertAnnotation(annotations, annotation) {
5292
+ if (!annotation)
5293
+ return annotations;
5294
+ const existing = annotations ?? [];
5295
+ const index = existing.findIndex((item) => item.id === annotation.id);
5296
+ if (index === -1)
5297
+ return [...existing, annotation];
5298
+ const next = [...existing];
5299
+ next[index] = annotation;
5300
+ return next;
5301
+ }
5222
5302
  var useViewerTabsStore = create8((set) => ({
5223
5303
  tabs: [],
5224
5304
  activeTabId: null,
@@ -5265,7 +5345,11 @@ var useViewerTabsStore = create8((set) => ({
5265
5345
  id: targetId,
5266
5346
  type: "file",
5267
5347
  title: existingFile?.title ?? titleFromPath(targetPath),
5268
- path: targetPath
5348
+ path: targetPath,
5349
+ highlight: existingFile?.highlight,
5350
+ annotations: existingFile?.annotations,
5351
+ patchPreview: existingFile?.patchPreview,
5352
+ writePreview: existingFile?.writePreview
5269
5353
  }),
5270
5354
  activeTabId: targetId
5271
5355
  };
@@ -5286,6 +5370,7 @@ var useViewerTabsStore = create8((set) => ({
5286
5370
  title: existingFile?.title ?? titleFromPath(targetPath),
5287
5371
  path: targetPath,
5288
5372
  highlight,
5373
+ annotations: existingFile?.annotations,
5289
5374
  patchPreview: undefined,
5290
5375
  writePreview: undefined
5291
5376
  }),
@@ -5307,6 +5392,12 @@ var useViewerTabsStore = create8((set) => ({
5307
5392
  const samePatchCall = isSamePatchCall(existingPatchPreview, preview);
5308
5393
  const baseContent = preview.baseContent ?? (samePatchCall ? existingPatchPreview?.baseContent : existingPatchPreview?.resultContent ?? existingPatchPreview?.baseContent);
5309
5394
  const changedLines = samePatchCall ? preview.changedLines ?? existingPatchPreview?.changedLines : mergeChangedLines(existingPatchPreview?.changedLines, preview.changedLines);
5395
+ const annotationPreview = {
5396
+ ...preview,
5397
+ changedLines: preview.changedLines
5398
+ };
5399
+ const existingAnnotation = existingFile?.annotations?.find((annotation2) => annotation2.id === annotationId(annotationPreview, targetPath));
5400
+ const annotations = upsertAnnotation(existingFile?.annotations, buildAnnotation(annotationPreview, targetPath, existingAnnotation));
5310
5401
  return {
5311
5402
  tabs: upsertTab(tabs, {
5312
5403
  id: targetId,
@@ -5314,6 +5405,7 @@ var useViewerTabsStore = create8((set) => ({
5314
5405
  title: existingFile?.title ?? titleFromPath(targetPath),
5315
5406
  path: targetPath,
5316
5407
  highlight: undefined,
5408
+ annotations,
5317
5409
  writePreview: undefined,
5318
5410
  patchPreview: {
5319
5411
  path: targetPath,
@@ -5336,10 +5428,13 @@ var useViewerTabsStore = create8((set) => ({
5336
5428
  }
5337
5429
  if (existingFile) {
5338
5430
  const existingWritePreview = existingFile.writePreview;
5431
+ const existingAnnotation = existingFile.annotations?.find((annotation2) => annotation2.id === annotationId(preview, targetPath));
5432
+ const annotations = upsertAnnotation(existingFile.annotations, buildAnnotation(preview, targetPath, existingAnnotation));
5339
5433
  return {
5340
5434
  tabs: upsertTab(tabs, {
5341
5435
  ...existingFile,
5342
5436
  highlight: undefined,
5437
+ annotations,
5343
5438
  patchPreview: undefined,
5344
5439
  writePreview: {
5345
5440
  path: targetPath,
@@ -5354,25 +5449,24 @@ var useViewerTabsStore = create8((set) => ({
5354
5449
  };
5355
5450
  }
5356
5451
  const existingWrite = existing?.toolName === "write" ? existing : undefined;
5452
+ const annotation = buildAnnotation(preview, preview.path);
5357
5453
  return {
5358
5454
  tabs: upsertTab(tabs, {
5359
5455
  id,
5360
- type: "tool-preview",
5456
+ type: "file",
5361
5457
  title: titleFromPath(preview.path),
5362
5458
  path: preview.path,
5363
- toolName: preview.toolName,
5364
- callId: preview.callId,
5365
- content: preview.content ?? existingWrite?.content,
5366
- baseContent: undefined,
5367
- patch: undefined,
5368
- changedLines: undefined,
5369
- previewContent: undefined,
5370
- resultContent: undefined,
5371
- previewLineTones: undefined,
5372
- previewFirstLine: undefined,
5373
- previewLatestLine: undefined,
5374
- status: preview.status,
5375
- error: preview.error ?? existingWrite?.error
5459
+ highlight: undefined,
5460
+ annotations: annotation ? [annotation] : undefined,
5461
+ patchPreview: undefined,
5462
+ writePreview: {
5463
+ path: preview.path,
5464
+ toolName: "write",
5465
+ callId: preview.callId,
5466
+ content: preview.content ?? existingWrite?.content,
5467
+ status: preview.status,
5468
+ error: preview.error ?? existingWrite?.error
5469
+ }
5376
5470
  }),
5377
5471
  activeTabId: id
5378
5472
  };
@@ -7280,6 +7374,24 @@ ${content}` : content;
7280
7374
  handleSendMessage("/compact");
7281
7375
  } else if (commandId === "init") {
7282
7376
  handleSendMessage("/init");
7377
+ } else if (commandId === "handoff") {
7378
+ const toastId2 = toast.loading("Creating handoff...");
7379
+ try {
7380
+ const result = await apiClient.createHandoff(sessionId);
7381
+ queryClient.invalidateQueries({ queryKey: ["sessions"] });
7382
+ queryClient.invalidateQueries({
7383
+ queryKey: ["messages", sessionId]
7384
+ });
7385
+ queryClient.invalidateQueries({
7386
+ queryKey: ["messages", result.sessionId]
7387
+ });
7388
+ openPlatformSession(result.sessionId);
7389
+ toast.success("Handoff created");
7390
+ } catch (error) {
7391
+ toast.error(error instanceof Error ? error.message : "Failed to create handoff");
7392
+ } finally {
7393
+ useToastStore.getState().removeToast(toastId2);
7394
+ }
7283
7395
  } else if (commandId === "delete") {
7284
7396
  deleteSession.mutate(sessionId, {
7285
7397
  onSuccess: () => {
@@ -7928,6 +8040,11 @@ function parseDiff(patch) {
7928
8040
  let inHunk = false;
7929
8041
  let filePath = "";
7930
8042
  for (const line of lines) {
8043
+ const lineModePath = line.match(/^\*\*\* (?:Delete Lines in|Replace Lines in|Insert Before in|Insert After in): (.+)$/);
8044
+ if (lineModePath?.[1]) {
8045
+ filePath = lineModePath[1];
8046
+ inHunk = false;
8047
+ }
7931
8048
  if (line.startsWith("*** Update File:") || line.startsWith("*** Add File:")) {
7932
8049
  const match = line.match(/\*\*\* (?:Update|Add) File: (.+)/);
7933
8050
  if (match)
@@ -12913,101 +13030,83 @@ var MessagePartItem = memo10(function MessagePartItem2({
12913
13030
  } else if (data) {
12914
13031
  content = JSON.stringify(data, null, 2);
12915
13032
  }
12916
- return /* @__PURE__ */ jsxs50("div", {
13033
+ return /* @__PURE__ */ jsx59("div", {
12917
13034
  className: "relative group",
12918
- children: [
12919
- /* @__PURE__ */ jsx59("div", {
12920
- className: "absolute -top-1 right-0 opacity-0 group-hover:opacity-100 transition-opacity z-10",
12921
- children: /* @__PURE__ */ jsx59(CopyButton, {
12922
- text: content,
12923
- className: "bg-background/80 backdrop-blur-sm border border-border/50 shadow-sm",
12924
- size: "md"
12925
- })
12926
- }),
12927
- /* @__PURE__ */ jsx59("div", {
12928
- className: `${isCompactThread ? "text-[16.5px]" : "text-[17px]"} text-foreground leading-relaxed markdown-content max-w-full overflow-x-auto`,
12929
- children: /* @__PURE__ */ jsx59(ReactMarkdown, {
12930
- remarkPlugins: [remarkGfm],
12931
- components: {
12932
- a: ({
12933
- href,
12934
- children,
12935
- ...props
12936
- }) => /* @__PURE__ */ jsx59("a", {
12937
- href,
12938
- target: "_blank",
12939
- rel: "noopener noreferrer",
12940
- className: "text-primary underline decoration-primary/35 underline-offset-2 transition-colors hover:text-primary/90 hover:decoration-primary",
12941
- onClick: (e) => {
12942
- if (window.self !== window.top && href) {
12943
- e.preventDefault();
12944
- window.parent.postMessage({
12945
- type: "otto-open-url",
12946
- url: href
12947
- }, "*");
13035
+ children: /* @__PURE__ */ jsx59("div", {
13036
+ className: `${isCompactThread ? "text-[16.5px]" : "text-[17px]"} text-foreground leading-relaxed markdown-content max-w-full overflow-x-auto`,
13037
+ children: /* @__PURE__ */ jsx59(ReactMarkdown, {
13038
+ remarkPlugins: [remarkGfm],
13039
+ components: {
13040
+ a: ({
13041
+ href,
13042
+ children,
13043
+ ...props
13044
+ }) => /* @__PURE__ */ jsx59("a", {
13045
+ href,
13046
+ target: "_blank",
13047
+ rel: "noopener noreferrer",
13048
+ className: "text-primary underline decoration-primary/35 underline-offset-2 transition-colors hover:text-primary/90 hover:decoration-primary",
13049
+ onClick: (e) => {
13050
+ if (window.self !== window.top && href) {
13051
+ e.preventDefault();
13052
+ window.parent.postMessage({
13053
+ type: "otto-open-url",
13054
+ url: href
13055
+ }, "*");
13056
+ }
13057
+ },
13058
+ ...props,
13059
+ children
13060
+ }),
13061
+ pre: ({
13062
+ children,
13063
+ ...props
13064
+ }) => {
13065
+ const codeContent = (() => {
13066
+ if (!children)
13067
+ return "";
13068
+ const child = Array.isArray(children) ? children[0] : children;
13069
+ if (child && typeof child === "object" && "props" in child) {
13070
+ const codeProps = child.props;
13071
+ if (typeof codeProps.children === "string") {
13072
+ return codeProps.children;
12948
13073
  }
12949
- },
13074
+ }
13075
+ return "";
13076
+ })();
13077
+ return /* @__PURE__ */ jsxs50("div", {
13078
+ className: "relative group/code my-3",
13079
+ children: [
13080
+ /* @__PURE__ */ jsx59("div", {
13081
+ className: "absolute top-2 right-2 opacity-0 group-hover/code:opacity-100 transition-opacity z-10",
13082
+ children: /* @__PURE__ */ jsx59(CopyButton, {
13083
+ text: codeContent,
13084
+ className: "bg-background/80 backdrop-blur-sm border border-border/50 shadow-sm",
13085
+ size: "sm"
13086
+ })
13087
+ }),
13088
+ /* @__PURE__ */ jsx59("pre", {
13089
+ ...props,
13090
+ className: "overflow-x-auto",
13091
+ children
13092
+ })
13093
+ ]
13094
+ });
13095
+ },
13096
+ table: ({
13097
+ children,
13098
+ ...props
13099
+ }) => /* @__PURE__ */ jsx59("div", {
13100
+ className: "overflow-x-auto max-w-full min-w-0 my-3",
13101
+ children: /* @__PURE__ */ jsx59("table", {
12950
13102
  ...props,
12951
13103
  children
12952
- }),
12953
- pre: ({
12954
- children,
12955
- ...props
12956
- }) => {
12957
- const codeContent = (() => {
12958
- if (!children)
12959
- return "";
12960
- const child = Array.isArray(children) ? children[0] : children;
12961
- if (child && typeof child === "object" && "props" in child) {
12962
- const codeProps = child.props;
12963
- if (typeof codeProps.children === "string") {
12964
- return codeProps.children;
12965
- }
12966
- }
12967
- return "";
12968
- })();
12969
- return /* @__PURE__ */ jsxs50("div", {
12970
- className: "relative group/code my-3",
12971
- children: [
12972
- /* @__PURE__ */ jsx59("div", {
12973
- className: "absolute top-2 right-2 opacity-0 group-hover/code:opacity-100 transition-opacity z-10",
12974
- children: /* @__PURE__ */ jsx59(CopyButton, {
12975
- text: codeContent,
12976
- className: "bg-background/80 backdrop-blur-sm border border-border/50 shadow-sm",
12977
- size: "sm"
12978
- })
12979
- }),
12980
- /* @__PURE__ */ jsx59("pre", {
12981
- ...props,
12982
- className: "overflow-x-auto",
12983
- children
12984
- })
12985
- ]
12986
- });
12987
- },
12988
- table: ({
12989
- children,
12990
- ...props
12991
- }) => /* @__PURE__ */ jsx59("div", {
12992
- className: "overflow-x-auto max-w-full min-w-0 my-3",
12993
- children: /* @__PURE__ */ jsx59("table", {
12994
- ...props,
12995
- children
12996
- })
12997
13104
  })
12998
- },
12999
- children: content
13000
- })
13001
- }),
13002
- content.length > 500 && /* @__PURE__ */ jsx59("div", {
13003
- className: "absolute -bottom-1 right-0 opacity-0 group-hover:opacity-100 transition-opacity z-10",
13004
- children: /* @__PURE__ */ jsx59(CopyButton, {
13005
- text: content,
13006
- className: "bg-background/80 backdrop-blur-sm border border-border/50 shadow-sm",
13007
- size: "md"
13008
- })
13105
+ })
13106
+ },
13107
+ children: content
13009
13108
  })
13010
- ]
13109
+ })
13011
13110
  });
13012
13111
  }
13013
13112
  if (part.type === "error") {
@@ -14059,7 +14158,7 @@ function ActionToolBox({ part, showLine, compact }) {
14059
14158
  },
14060
14159
  children: /* @__PURE__ */ jsx61("pre", {
14061
14160
  ref: contentMeasureRef,
14062
- className: "px-1 pt-2.5 pb-1 text-[12px] leading-relaxed text-foreground/60 font-mono whitespace-pre-wrap break-all",
14161
+ className: "px-1 pt-2.5 pb-1 text-[12px] leading-relaxed text-foreground/60 font-mono whitespace-pre-wrap break-words",
14063
14162
  children: displayContent
14064
14163
  })
14065
14164
  })
@@ -14123,20 +14222,10 @@ function extractJsonStringField(raw, field) {
14123
14222
  let result = "";
14124
14223
  let i = start;
14125
14224
  while (i < raw.length) {
14126
- if (raw[i] === "\\" && i + 1 < raw.length) {
14127
- const next = raw[i + 1];
14128
- if (next === "n")
14129
- result += `
14130
- `;
14131
- else if (next === "t")
14132
- result += "\t";
14133
- else if (next === '"')
14134
- result += '"';
14135
- else if (next === "\\")
14136
- result += "\\";
14137
- else
14138
- result += next;
14139
- i += 2;
14225
+ const decoded = decodeJsonStringChar(raw, i);
14226
+ if (decoded) {
14227
+ result += decoded.value;
14228
+ i = decoded.nextIndex;
14140
14229
  } else if (raw[i] === '"') {
14141
14230
  break;
14142
14231
  } else {
@@ -14146,6 +14235,38 @@ function extractJsonStringField(raw, field) {
14146
14235
  }
14147
14236
  return result;
14148
14237
  }
14238
+ function decodeJsonStringChar(raw, index) {
14239
+ if (raw[index] !== "\\" || index + 1 >= raw.length)
14240
+ return null;
14241
+ const next = raw[index + 1];
14242
+ if (next === "n")
14243
+ return { value: `
14244
+ `, nextIndex: index + 2 };
14245
+ if (next === "t")
14246
+ return { value: "\t", nextIndex: index + 2 };
14247
+ if (next === "r")
14248
+ return { value: "\r", nextIndex: index + 2 };
14249
+ if (next === "b")
14250
+ return { value: "\b", nextIndex: index + 2 };
14251
+ if (next === "f")
14252
+ return { value: "\f", nextIndex: index + 2 };
14253
+ if (next === '"')
14254
+ return { value: '"', nextIndex: index + 2 };
14255
+ if (next === "\\")
14256
+ return { value: "\\", nextIndex: index + 2 };
14257
+ if (next === "/")
14258
+ return { value: "/", nextIndex: index + 2 };
14259
+ if (next === "u" && index + 5 < raw.length) {
14260
+ const hex = raw.slice(index + 2, index + 6);
14261
+ if (/^[0-9a-fA-F]{4}$/.test(hex)) {
14262
+ return {
14263
+ value: String.fromCharCode(Number.parseInt(hex, 16)),
14264
+ nextIndex: index + 6
14265
+ };
14266
+ }
14267
+ }
14268
+ return { value: next, nextIndex: index + 2 };
14269
+ }
14149
14270
  function extractJsonStringFieldPreview(raw, field) {
14150
14271
  const pattern = new RegExp(`"${field}"\\s*:\\s*"`);
14151
14272
  const m = pattern.exec(raw);
@@ -14155,8 +14276,25 @@ function extractJsonStringFieldPreview(raw, field) {
14155
14276
  if (raw.length - start <= LIVE_TOOL_CONTENT_PREVIEW_CHARS) {
14156
14277
  return extractJsonStringField(raw, field);
14157
14278
  }
14279
+ let result = "";
14280
+ let i = start;
14281
+ while (i < raw.length) {
14282
+ const decoded = decodeJsonStringChar(raw, i);
14283
+ if (decoded) {
14284
+ result += decoded.value;
14285
+ i = decoded.nextIndex;
14286
+ } else if (raw[i] === '"') {
14287
+ break;
14288
+ } else {
14289
+ result += raw[i];
14290
+ i += 1;
14291
+ }
14292
+ if (result.length > LIVE_TOOL_CONTENT_PREVIEW_CHARS) {
14293
+ result = result.slice(-LIVE_TOOL_CONTENT_PREVIEW_CHARS);
14294
+ }
14295
+ }
14158
14296
  return `… showing latest streamed content only …
14159
- ${raw.slice(-LIVE_TOOL_CONTENT_PREVIEW_CHARS)}`;
14297
+ ${result}`;
14160
14298
  }
14161
14299
  function getLiveToolContentPreview(toolName, content) {
14162
14300
  if (toolName !== "write" && toolName !== "apply_patch" && content.length <= LIVE_TOOL_CONTENT_PREVIEW_CHARS) {
@@ -15837,21 +15975,23 @@ var UsageModal = memo14(function UsageModal2() {
15837
15975
  // src/hooks/useProviderUsage.ts
15838
15976
  import { useEffect as useEffect23, useCallback as useCallback15, useRef as useRef15 } from "react";
15839
15977
  var POLL_INTERVAL = 60000;
15840
- var STALE_THRESHOLD = 30000;
15978
+ var STALE_THRESHOLD = 60000;
15841
15979
  var inflight = new Set;
15842
15980
  function useProviderUsage(provider, authType) {
15843
15981
  const setUsage = useUsageStore((s) => s.setUsage);
15844
15982
  const setLoading = useUsageStore((s) => s.setLoading);
15845
15983
  const setLastFetched = useUsageStore((s) => s.setLastFetched);
15984
+ const isModalOpen = useUsageStore((s) => s.isModalOpen);
15985
+ const modalProvider = useUsageStore((s) => s.modalProvider);
15846
15986
  const usage = useUsageStore((s) => provider ? s.usage[provider] : undefined);
15847
15987
  const isOAuthProvider = authType === "oauth" && (provider === "anthropic" || provider === "openai");
15848
- const fetchUsage = useCallback15(async () => {
15988
+ const fetchUsage = useCallback15(async (force = false) => {
15849
15989
  if (!provider || !isOAuthProvider)
15850
15990
  return;
15851
15991
  if (inflight.has(provider))
15852
15992
  return;
15853
15993
  const last = useUsageStore.getState().lastFetched[provider] ?? 0;
15854
- if (last && Date.now() - last < STALE_THRESHOLD)
15994
+ if (!force && last && Date.now() - last < STALE_THRESHOLD)
15855
15995
  return;
15856
15996
  inflight.add(provider);
15857
15997
  setLoading(provider, true);
@@ -15870,9 +16010,15 @@ function useProviderUsage(provider, authType) {
15870
16010
  if (!provider || !isOAuthProvider)
15871
16011
  return;
15872
16012
  fetchRef.current();
16013
+ }, [isOAuthProvider, provider]);
16014
+ useEffect23(() => {
16015
+ if (!provider || !isOAuthProvider || !isModalOpen || modalProvider !== provider) {
16016
+ return;
16017
+ }
16018
+ fetchRef.current(true);
15873
16019
  const interval = setInterval(() => fetchRef.current(), POLL_INTERVAL);
15874
16020
  return () => clearInterval(interval);
15875
- }, [isOAuthProvider, provider]);
16021
+ }, [isModalOpen, isOAuthProvider, modalProvider, provider]);
15876
16022
  return {
15877
16023
  usage,
15878
16024
  fetchUsage,
@@ -17163,22 +17309,30 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17163
17309
  return;
17164
17310
  let changeLines = 0;
17165
17311
  let stableChangeLength = 0;
17312
+ let lineDirectiveCount = 0;
17166
17313
  for (const line of stablePatch.split(`
17167
17314
  `)) {
17168
17315
  if (line.startsWith("+") && !line.startsWith("+++") || line.startsWith("-") && !line.startsWith("---")) {
17169
17316
  changeLines += 1;
17170
17317
  stableChangeLength += line.length;
17318
+ } else if (/^\*\*\* (?:Delete Lines in|Replace Lines in|Insert Before in|Insert After in): /.test(line) || line.startsWith("*** Lines:") || line.startsWith("*** Line:") || line.startsWith("*** With:")) {
17319
+ lineDirectiveCount += 1;
17171
17320
  }
17172
17321
  }
17173
- return changeLines > 0 ? `${changeLines}:${stableChangeLength}` : undefined;
17322
+ if (changeLines > 0)
17323
+ return `${changeLines}:${stableChangeLength}`;
17324
+ return lineDirectiveCount > 0 ? `lines:${lineDirectiveCount}:${stablePatch.length}` : undefined;
17174
17325
  };
17175
17326
  const extractPathsFromPatch = (patch) => {
17176
17327
  const paths = new Set;
17177
17328
  for (const line of patch.split(`
17178
17329
  `)) {
17179
17330
  const directive = line.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/);
17180
- if (directive?.[1]) {
17181
- paths.add(directive[1].trim());
17331
+ const replaceDirective = line.match(/^\*\*\* Replace in: (.+)$/);
17332
+ const lineDirective = line.match(/^\*\*\* (?:Delete Lines in|Replace Lines in|Insert Before in|Insert After in): (.+)$/);
17333
+ const path = directive?.[1] ?? replaceDirective?.[1] ?? lineDirective?.[1];
17334
+ if (path) {
17335
+ paths.add(path.trim());
17182
17336
  continue;
17183
17337
  }
17184
17338
  const unified = line.match(/^\+\+\+ (?:b\/)?(.+)$/);
@@ -17188,6 +17342,47 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17188
17342
  }
17189
17343
  return [...paths];
17190
17344
  };
17345
+ const getExtension = (path) => path.split(".").pop()?.toLowerCase() ?? "";
17346
+ const updateFileContentCache = (path, content) => {
17347
+ queryClient.setQueryData(["files", "read", path], {
17348
+ content,
17349
+ path,
17350
+ extension: getExtension(path),
17351
+ lineCount: content.split(`
17352
+ `).length
17353
+ });
17354
+ };
17355
+ const mergeReadResultIntoFileCache = (path, result, startLine, endLine) => {
17356
+ if (typeof result?.content !== "string")
17357
+ return;
17358
+ const readContent = result.content;
17359
+ if (!startLine || !endLine) {
17360
+ updateFileContentCache(path, readContent);
17361
+ return;
17362
+ }
17363
+ queryClient.setQueryData(["files", "read", path], (current) => {
17364
+ if (!current?.content)
17365
+ return current;
17366
+ const lines = current.content.split(`
17367
+ `);
17368
+ if (lines.at(-1) === "")
17369
+ lines.pop();
17370
+ const readLines = readContent.split(`
17371
+ `);
17372
+ lines.splice(startLine - 1, endLine - startLine + 1, ...readLines);
17373
+ const content = `${lines.join(`
17374
+ `)}
17375
+ `;
17376
+ return {
17377
+ ...current,
17378
+ content,
17379
+ lineCount: typeof result.totalLines === "number" ? result.totalLines : lines.length
17380
+ };
17381
+ });
17382
+ };
17383
+ const invalidateFileContentCache = (path) => {
17384
+ queryClient.invalidateQueries({ queryKey: ["files", "read", path] });
17385
+ };
17191
17386
  const getChangedLinesForPath = (result, path) => {
17192
17387
  const changes = Array.isArray(result?.changes) ? result.changes : [];
17193
17388
  const lines = new Set;
@@ -17217,9 +17412,6 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17217
17412
  return lines.size > 0 ? [...lines] : undefined;
17218
17413
  };
17219
17414
  const handleReadToolActivity = (eventType, payload, delta) => {
17220
- const viewerStore = useViewerTabsStore.getState();
17221
- if (!viewerStore.followToolActivity)
17222
- return;
17223
17415
  const name = getToolEventName(payload);
17224
17416
  if (name !== "read")
17225
17417
  return;
@@ -17232,6 +17424,12 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17232
17424
  const startLine = normalizeLineNumber(args.startLine) ?? normalizeLineNumber(args.start_line) ?? rangeFromResult.startLine;
17233
17425
  const endLine = normalizeLineNumber(args.endLine) ?? normalizeLineNumber(args.end_line) ?? rangeFromResult.endLine ?? startLine;
17234
17426
  const failed = result?.ok === false || eventType === "error";
17427
+ if (eventType === "tool.result" && !failed) {
17428
+ mergeReadResultIntoFileCache(path, result, startLine, endLine);
17429
+ }
17430
+ const viewerStore = useViewerTabsStore.getState();
17431
+ if (!viewerStore.followToolActivity)
17432
+ return;
17235
17433
  viewerStore.openToolReadTab(path, {
17236
17434
  startLine,
17237
17435
  endLine,
@@ -17241,9 +17439,6 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17241
17439
  });
17242
17440
  };
17243
17441
  const handleWriteToolActivity = (eventType, payload, delta) => {
17244
- const viewerStore = useViewerTabsStore.getState();
17245
- if (!viewerStore.followToolActivity)
17246
- return;
17247
17442
  const name = getToolEventName(payload);
17248
17443
  if (name !== "write")
17249
17444
  return;
@@ -17257,6 +17452,15 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17257
17452
  const callId = getToolEventCallId(payload) ?? undefined;
17258
17453
  const status = failed ? "error" : eventType === "tool.result" ? "success" : "streaming";
17259
17454
  const content = status === "streaming" ? getStreamingWritePreviewContent(args, buffer) : getStringArg(args, buffer, "content");
17455
+ if (status === "success") {
17456
+ if (content !== undefined)
17457
+ updateFileContentCache(path, content);
17458
+ else
17459
+ invalidateFileContentCache(path);
17460
+ }
17461
+ const viewerStore = useViewerTabsStore.getState();
17462
+ if (!viewerStore.followToolActivity)
17463
+ return;
17260
17464
  if (status === "streaming" && content !== undefined && content.length >= TOOL_PREVIEW_THROTTLE_MIN_CHARS) {
17261
17465
  const previewKey = callId ?? path;
17262
17466
  const now = Date.now();
@@ -17280,9 +17484,6 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17280
17484
  });
17281
17485
  };
17282
17486
  const handleApplyPatchToolActivity = (eventType, payload, delta) => {
17283
- const viewerStore = useViewerTabsStore.getState();
17284
- if (!viewerStore.followToolActivity)
17285
- return;
17286
17487
  const name = getToolEventName(payload);
17287
17488
  if (name !== "apply_patch")
17288
17489
  return;
@@ -17306,6 +17507,13 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17306
17507
  const patchPaths = extractPathsFromPatch(patch);
17307
17508
  if (patchPaths.length === 0)
17308
17509
  return;
17510
+ if (status === "success") {
17511
+ for (const path of patchPaths)
17512
+ invalidateFileContentCache(path);
17513
+ }
17514
+ const viewerStore = useViewerTabsStore.getState();
17515
+ if (!viewerStore.followToolActivity)
17516
+ return;
17309
17517
  const matchingFileTabs = viewerStore.tabs.filter((tab) => tab.type === "file" && patchPaths.some((path) => patchPathMayReferToTarget(path, tab.path)));
17310
17518
  const activeMatchingFileTab = matchingFileTabs.find((tab) => tab.id === viewerStore.activeTabId);
17311
17519
  const fallbackPath = patchPaths.find(isLikelyCompletePatchPath);
@@ -17411,6 +17619,11 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17411
17619
  const applyMessageDelta = (payload) => {
17412
17620
  const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
17413
17621
  const partId = typeof payload?.partId === "string" ? payload.partId : null;
17622
+ const payloadType = typeof payload?.type === "string" ? payload.type : undefined;
17623
+ if (payloadType === "error") {
17624
+ upsertErrorPart(payload);
17625
+ return;
17626
+ }
17414
17627
  const delta = typeof payload?.delta === "string" ? payload.delta : null;
17415
17628
  if (!messageId || !partId || delta === null)
17416
17629
  return;
@@ -17461,6 +17674,90 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17461
17674
  return nextMessages;
17462
17675
  });
17463
17676
  };
17677
+ const toRecord = (value) => {
17678
+ if (value && typeof value === "object" && !Array.isArray(value)) {
17679
+ return value;
17680
+ }
17681
+ return null;
17682
+ };
17683
+ const parseErrorContent = (payload) => {
17684
+ const contentRecord = toRecord(payload.content);
17685
+ if (contentRecord)
17686
+ return contentRecord;
17687
+ if (typeof payload.content === "string") {
17688
+ try {
17689
+ const parsed = JSON.parse(payload.content);
17690
+ const parsedRecord = toRecord(parsed);
17691
+ if (parsedRecord)
17692
+ return parsedRecord;
17693
+ } catch {}
17694
+ }
17695
+ const message = typeof payload.error === "string" ? payload.error : typeof payload.message === "string" ? payload.message : "Assistant run failed";
17696
+ return {
17697
+ message,
17698
+ type: typeof payload.errorType === "string" ? payload.errorType : "error",
17699
+ details: toRecord(payload.details) ?? undefined,
17700
+ isAborted: payload.isAborted === true,
17701
+ autoCompacted: payload.autoCompacted === true
17702
+ };
17703
+ };
17704
+ const upsertErrorPart = (payload) => {
17705
+ const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
17706
+ if (!payload || !messageId)
17707
+ return;
17708
+ const contentJson = parseErrorContent(payload);
17709
+ const content = JSON.stringify(contentJson);
17710
+ const errorMessage = typeof contentJson.message === "string" ? contentJson.message : typeof payload.error === "string" ? payload.error : "Assistant run failed";
17711
+ const stepIndex = typeof payload.stepIndex === "number" ? payload.stepIndex : null;
17712
+ const partId = typeof payload.partId === "string" ? payload.partId : `error-${messageId}`;
17713
+ queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
17714
+ if (!oldMessages)
17715
+ return oldMessages;
17716
+ const nextMessages = [...oldMessages];
17717
+ const messageIndex = nextMessages.findIndex((message) => message.id === messageId);
17718
+ if (messageIndex === -1)
17719
+ return oldMessages;
17720
+ const targetMessage = nextMessages[messageIndex];
17721
+ const parts = targetMessage.parts ? [...targetMessage.parts] : [];
17722
+ const partIndex = parts.findIndex((part) => part.id === partId);
17723
+ if (partIndex === -1) {
17724
+ const newPart = {
17725
+ id: partId,
17726
+ messageId,
17727
+ index: getOptimisticPartIndex(parts, stepIndex),
17728
+ stepIndex,
17729
+ type: "error",
17730
+ content,
17731
+ contentJson,
17732
+ agent: targetMessage.agent,
17733
+ provider: targetMessage.provider,
17734
+ model: targetMessage.model,
17735
+ startedAt: Date.now(),
17736
+ completedAt: Date.now(),
17737
+ toolName: null,
17738
+ toolCallId: null,
17739
+ toolDurationMs: null
17740
+ };
17741
+ parts.push(newPart);
17742
+ } else {
17743
+ parts[partIndex] = {
17744
+ ...parts[partIndex],
17745
+ content,
17746
+ contentJson,
17747
+ stepIndex: stepIndex ?? parts[partIndex].stepIndex ?? null,
17748
+ completedAt: Date.now()
17749
+ };
17750
+ }
17751
+ nextMessages[messageIndex] = {
17752
+ ...targetMessage,
17753
+ status: "error",
17754
+ completedAt: targetMessage.completedAt ?? Date.now(),
17755
+ error: errorMessage,
17756
+ parts
17757
+ };
17758
+ return nextMessages;
17759
+ });
17760
+ };
17464
17761
  const upsertEphemeralToolCall = (payload) => {
17465
17762
  if (!payload)
17466
17763
  return;
@@ -17890,6 +18187,17 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17890
18187
  }
17891
18188
  markMessageCompleted(payload);
17892
18189
  clearEphemeralForMessage(id);
18190
+ if (id) {
18191
+ queryClient.setQueryData(["queueState", sessionId], (current) => {
18192
+ if (!current || current.currentMessageId !== id)
18193
+ return current;
18194
+ return normalizeQueueState({
18195
+ currentMessageId: null,
18196
+ queuedMessages: [],
18197
+ isRunning: false
18198
+ });
18199
+ });
18200
+ }
17893
18201
  queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
17894
18202
  queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
17895
18203
  break;
@@ -17963,22 +18271,7 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
17963
18271
  assistantMessageIdRef.current = null;
17964
18272
  }
17965
18273
  clearEphemeralForMessage(messageId);
17966
- const errorMessage = typeof payload?.error === "string" ? payload.error : typeof payload?.message === "string" ? payload.message : "Assistant run failed";
17967
- queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
17968
- if (!oldMessages)
17969
- return oldMessages;
17970
- const idx = oldMessages.findIndex((m) => m.id === messageId);
17971
- if (idx === -1)
17972
- return oldMessages;
17973
- const next = [...oldMessages];
17974
- next[idx] = {
17975
- ...next[idx],
17976
- status: "error",
17977
- completedAt: next[idx].completedAt ?? Date.now(),
17978
- error: errorMessage
17979
- };
17980
- return next;
17981
- });
18274
+ upsertErrorPart(payload);
17982
18275
  }
17983
18276
  queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
17984
18277
  break;
@@ -18013,11 +18306,11 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
18013
18306
  break;
18014
18307
  }
18015
18308
  case "queue.updated": {
18016
- const queueState = {
18309
+ const queueState = normalizeQueueState({
18017
18310
  currentMessageId: payload?.currentMessageId,
18018
18311
  queuedMessages: payload?.queuedMessages ?? [],
18019
- queueLength: payload?.queueLength ?? 0
18020
- };
18312
+ isRunning: typeof payload?.isRunning === "boolean" ? payload.isRunning : undefined
18313
+ });
18021
18314
  queryClient.setQueryData(["queueState", sessionId], queueState);
18022
18315
  break;
18023
18316
  }
@@ -19579,7 +19872,7 @@ var syntaxTheme = HighlightStyle.define([
19579
19872
  { tag: tags.invalid, color: "var(--otto-cm-invalid)" }
19580
19873
  ]);
19581
19874
  function lineDecorationsExtension(highlightedLines, highlightTone = "primary", lineTones) {
19582
- return EditorView.decorations.compute([], (state) => {
19875
+ return EditorView.decorations.compute(["doc"], (state) => {
19583
19876
  const decorations = [];
19584
19877
  for (const [line, tone] of lineTones ?? []) {
19585
19878
  if (line > 0)
@@ -27412,6 +27705,35 @@ function getStablePatchLines(patch) {
27412
27705
  lines.pop();
27413
27706
  return lines;
27414
27707
  }
27708
+ function getEnvelopedPatchPath(line) {
27709
+ const directive = line.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/);
27710
+ const replaceDirective = line.match(/^\*\*\* Replace in: (.+)$/);
27711
+ const lineDirective = line.match(/^\*\*\* (?:Delete Lines in|Replace Lines in|Insert Before in|Insert After in): (.+)$/);
27712
+ return directive?.[1] ?? replaceDirective?.[1] ?? lineDirective?.[1];
27713
+ }
27714
+ function parsePatchLineNumber(value) {
27715
+ const trimmed = value.trim();
27716
+ if (!/^\d+$/.test(trimmed))
27717
+ return;
27718
+ const line = Number.parseInt(trimmed, 10);
27719
+ return line > 0 ? line : undefined;
27720
+ }
27721
+ function parsePatchLineRange(value) {
27722
+ const match = /^(\d+)(?:\s*-\s*(\d+|end|eof|\$))?$/i.exec(value.trim());
27723
+ if (!match)
27724
+ return;
27725
+ const startLine = parsePatchLineNumber(match[1]);
27726
+ if (!startLine)
27727
+ return;
27728
+ if (!match[2])
27729
+ return { startLine, endLine: startLine };
27730
+ const endLine = /^(end|eof|\$)$/i.test(match[2]) ? "end" : parsePatchLineNumber(match[2]);
27731
+ if (!endLine)
27732
+ return;
27733
+ if (typeof endLine === "number" && endLine < startLine)
27734
+ return;
27735
+ return { startLine, endLine };
27736
+ }
27415
27737
  function getPatchLineHighlights(patch, targetPath, fallbackLines) {
27416
27738
  const highlighted = new Set(fallbackLines ?? []);
27417
27739
  if (!patch)
@@ -27427,9 +27749,9 @@ function getPatchLineHighlights(patch, targetPath, fallbackLines) {
27427
27749
  let inHunk = false;
27428
27750
  let newLine = 0;
27429
27751
  for (const line of getStablePatchLines(patch)) {
27430
- const directive = line.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/);
27431
- if (directive?.[1]) {
27432
- activeFile = patchPathMatches(directive[1], targetPath);
27752
+ const directivePath = getEnvelopedPatchPath(line);
27753
+ if (directivePath) {
27754
+ activeFile = patchPathMatches(directivePath, targetPath);
27433
27755
  sawFileDirective = true;
27434
27756
  inHunk = false;
27435
27757
  newLine = 0;
@@ -27523,10 +27845,10 @@ function collectEnvelopedPatchHunks(patch, targetPath) {
27523
27845
  current = [];
27524
27846
  };
27525
27847
  for (const line of getStablePatchLines(patch)) {
27526
- const directive = line.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/);
27527
- if (directive?.[1]) {
27848
+ const directivePath = getEnvelopedPatchPath(line);
27849
+ if (directivePath) {
27528
27850
  flush();
27529
- activeFile = patchPathMatches(directive[1], targetPath);
27851
+ activeFile = patchPathMatches(directivePath, targetPath);
27530
27852
  sawFileDirective = true;
27531
27853
  inHunk = activeFile;
27532
27854
  continue;
@@ -27615,9 +27937,152 @@ function buildEnvelopedPatchPreview(content, patch, targetPath) {
27615
27937
  latestLine: changedLines.length > 0 ? Math.max(...changedLines) : undefined
27616
27938
  };
27617
27939
  }
27940
+ function collectLineNumberPatchOperations(patch, targetPath) {
27941
+ const operations = [];
27942
+ let current = null;
27943
+ let activeFile = false;
27944
+ let phase = "directives";
27945
+ const flush = () => {
27946
+ if (current && activeFile)
27947
+ operations.push(current);
27948
+ current = null;
27949
+ phase = "directives";
27950
+ };
27951
+ for (const line of getStablePatchLines(patch)) {
27952
+ const directive = line.match(/^\*\*\* (Delete Lines in|Replace Lines in|Insert Before in|Insert After in): (.+)$/);
27953
+ if (directive?.[1] && directive[2]) {
27954
+ flush();
27955
+ activeFile = patchPathMatches(directive[2], targetPath);
27956
+ if (directive[1] === "Delete Lines in") {
27957
+ current = { kind: "delete", filePath: directive[2], lines: [] };
27958
+ } else if (directive[1] === "Replace Lines in") {
27959
+ current = { kind: "replace", filePath: directive[2], lines: [] };
27960
+ } else {
27961
+ current = {
27962
+ kind: "insert",
27963
+ filePath: directive[2],
27964
+ position: directive[1] === "Insert Before in" ? "before" : "after",
27965
+ lines: []
27966
+ };
27967
+ }
27968
+ continue;
27969
+ }
27970
+ if (!current)
27971
+ continue;
27972
+ if (line.startsWith("*** End Patch")) {
27973
+ flush();
27974
+ continue;
27975
+ }
27976
+ if (line.startsWith("*** Begin Patch"))
27977
+ continue;
27978
+ if (phase === "with") {
27979
+ current.lines.push(line);
27980
+ continue;
27981
+ }
27982
+ if (line.startsWith("*** Lines:") && current.kind !== "insert") {
27983
+ const range = parsePatchLineRange(line.slice("*** Lines:".length));
27984
+ if (range) {
27985
+ current.startLine = range.startLine;
27986
+ current.endLine = range.endLine;
27987
+ }
27988
+ continue;
27989
+ }
27990
+ if (line.startsWith("*** Line:") && current.kind === "insert") {
27991
+ current.line = parsePatchLineNumber(line.slice("*** Line:".length));
27992
+ continue;
27993
+ }
27994
+ if (line.startsWith("*** With:")) {
27995
+ phase = "with";
27996
+ }
27997
+ }
27998
+ flush();
27999
+ return operations;
28000
+ }
28001
+ function getCurrentRecordPosition(records, currentIndex) {
28002
+ let seen = 0;
28003
+ for (let index = 0;index < records.length; index += 1) {
28004
+ if (records[index].removed)
28005
+ continue;
28006
+ if (seen === currentIndex)
28007
+ return index;
28008
+ seen += 1;
28009
+ }
28010
+ return records.length;
28011
+ }
28012
+ function buildLineNumberPatchPreview(content, patch, targetPath) {
28013
+ const operations = collectLineNumberPatchOperations(patch, targetPath);
28014
+ if (operations.length === 0)
28015
+ return null;
28016
+ const contentLines = content.split(`
28017
+ `);
28018
+ if (contentLines.at(-1) === "")
28019
+ contentLines.pop();
28020
+ const records = contentLines.map((line) => ({
28021
+ text: line
28022
+ }));
28023
+ for (const operation of operations) {
28024
+ const currentLines = records.filter((record) => !record.removed);
28025
+ if (operation.kind === "insert") {
28026
+ if (!operation.line)
28027
+ continue;
28028
+ const insertIndex = operation.position === "before" ? operation.line - 1 : operation.line;
28029
+ if (insertIndex < 0 || insertIndex > currentLines.length)
28030
+ continue;
28031
+ const recordIndex2 = getCurrentRecordPosition(records, insertIndex);
28032
+ records.splice(recordIndex2, 0, ...operation.lines.map((line) => ({
28033
+ text: line,
28034
+ tone: "add"
28035
+ })));
28036
+ continue;
28037
+ }
28038
+ if (!operation.startLine || !operation.endLine)
28039
+ continue;
28040
+ const endLine = operation.endLine === "end" ? currentLines.length : operation.endLine;
28041
+ if (operation.startLine > currentLines.length || endLine > currentLines.length)
28042
+ continue;
28043
+ const startIndex = operation.startLine - 1;
28044
+ const endIndex = endLine - 1;
28045
+ const recordIndex = getCurrentRecordPosition(records, startIndex);
28046
+ for (let index = startIndex;index <= endIndex; index += 1) {
28047
+ const position = getCurrentRecordPosition(records, startIndex);
28048
+ if (!records[position])
28049
+ continue;
28050
+ records[position].tone = "remove";
28051
+ records[position].removed = true;
28052
+ }
28053
+ if (operation.kind === "replace") {
28054
+ records.splice(recordIndex, 0, ...operation.lines.map((line) => ({
28055
+ text: line,
28056
+ tone: "add"
28057
+ })));
28058
+ }
28059
+ }
28060
+ const lineTones = new Map;
28061
+ const renderedLines = records.map((record, index) => {
28062
+ if (record.tone)
28063
+ lineTones.set(index + 1, record.tone);
28064
+ return record.text;
28065
+ });
28066
+ if (lineTones.size === 0)
28067
+ return null;
28068
+ const resultLines = records.filter((record) => !record.removed).map((record) => record.text);
28069
+ const changedLines = [...lineTones.keys()];
28070
+ return {
28071
+ content: renderedLines.join(`
28072
+ `),
28073
+ resultContent: resultLines.join(`
28074
+ `),
28075
+ lineTones,
28076
+ firstLine: Math.min(...changedLines),
28077
+ latestLine: Math.max(...changedLines)
28078
+ };
28079
+ }
27618
28080
  function buildLivePatchPreview(content, patch, targetPath) {
27619
28081
  if (!patch)
27620
28082
  return null;
28083
+ const lineNumberPreview = buildLineNumberPatchPreview(content, patch, targetPath);
28084
+ if (lineNumberPreview)
28085
+ return lineNumberPreview;
27621
28086
  const insertions = new Map;
27622
28087
  const removals = new Set;
27623
28088
  let activeFile = false;
@@ -27885,26 +28350,6 @@ function ToolPreviewPanel({ tab }) {
27885
28350
  return /* @__PURE__ */ jsxs96("div", {
27886
28351
  className: "h-full w-full bg-transparent flex flex-col",
27887
28352
  children: [
27888
- /* @__PURE__ */ jsxs96("div", {
27889
- className: "shrink-0 border-b border-sidebar-border bg-sidebar-accent/30 px-3 py-1.5 text-[12px] text-muted-foreground flex items-center gap-2",
27890
- children: [
27891
- /* @__PURE__ */ jsx109(StatusIcon, {
27892
- status: tab.status
27893
- }),
27894
- /* @__PURE__ */ jsx109("span", {
27895
- children: statusLabel
27896
- }),
27897
- /* @__PURE__ */ jsx109("span", {
27898
- className: "text-muted-foreground/60",
27899
- children: "·"
27900
- }),
27901
- /* @__PURE__ */ jsx109("span", {
27902
- className: "font-mono truncate",
27903
- title: tab.path,
27904
- children: tab.path
27905
- })
27906
- ]
27907
- }),
27908
28353
  tab.error && /* @__PURE__ */ jsx109("div", {
27909
28354
  className: "shrink-0 border-b border-red-500/20 bg-red-500/10 px-3 py-2 text-[12px] text-red-700 dark:text-red-300",
27910
28355
  children: tab.error
@@ -27952,6 +28397,26 @@ function ToolPreviewPanel({ tab }) {
27952
28397
  className: "h-full flex items-center justify-center text-sm text-muted-foreground",
27953
28398
  children: "Waiting for write content..."
27954
28399
  })
28400
+ }),
28401
+ /* @__PURE__ */ jsxs96("div", {
28402
+ className: "shrink-0 border-t border-sidebar-border bg-sidebar-accent/30 px-3 py-1.5 text-[12px] text-muted-foreground flex items-center gap-2",
28403
+ children: [
28404
+ /* @__PURE__ */ jsx109(StatusIcon, {
28405
+ status: tab.status
28406
+ }),
28407
+ /* @__PURE__ */ jsx109("span", {
28408
+ children: statusLabel
28409
+ }),
28410
+ /* @__PURE__ */ jsx109("span", {
28411
+ className: "text-muted-foreground/60",
28412
+ children: "·"
28413
+ }),
28414
+ /* @__PURE__ */ jsx109("span", {
28415
+ className: "font-mono truncate",
28416
+ title: tab.path,
28417
+ children: tab.path
28418
+ })
28419
+ ]
27955
28420
  })
27956
28421
  ]
27957
28422
  });
@@ -28061,6 +28526,7 @@ var FileViewerPanel = memo47(function FileViewerPanel2({
28061
28526
  open,
28062
28527
  file,
28063
28528
  highlight,
28529
+ annotations,
28064
28530
  patchPreview,
28065
28531
  writePreview,
28066
28532
  onClose
@@ -28139,6 +28605,28 @@ var FileViewerPanel = memo47(function FileViewerPanel2({
28139
28605
  const highlightStart = effectiveHighlight?.startLine;
28140
28606
  const highlightEnd = effectiveHighlight?.endLine ?? highlightStart;
28141
28607
  const patchChangedLines = patchPreview?.changedLines;
28608
+ const persistentLineTones = useMemo29(() => {
28609
+ if (!annotations?.length)
28610
+ return;
28611
+ const tones = new Map;
28612
+ for (const annotation of annotations) {
28613
+ for (const [line, tone] of annotation.lineTones) {
28614
+ if (line > 0)
28615
+ tones.set(line, tone);
28616
+ }
28617
+ }
28618
+ return tones.size > 0 ? tones : undefined;
28619
+ }, [annotations]);
28620
+ const writePreviewLineTones = useMemo29(() => {
28621
+ if (writePreview?.content === undefined)
28622
+ return;
28623
+ const tones = new Map;
28624
+ const lineCount = writePreview.content.length === 0 ? 1 : writePreview.content.split(`
28625
+ `).length;
28626
+ for (let line = 1;line <= lineCount; line += 1)
28627
+ tones.set(line, "add");
28628
+ return tones;
28629
+ }, [writePreview?.content]);
28142
28630
  const highlightedLines = useMemo29(() => {
28143
28631
  if (highlightStart && highlightEnd) {
28144
28632
  return new Set(Array.from({ length: highlightEnd - highlightStart + 1 }, (_, index) => highlightStart + index));
@@ -28152,18 +28640,21 @@ var FileViewerPanel = memo47(function FileViewerPanel2({
28152
28640
  const scrollToHighlightLine = highlightStart ?? fallbackPatchHighlightStart;
28153
28641
  const activePatchLineTones = useMemo29(() => {
28154
28642
  if (!activePatchPreview)
28155
- return;
28156
- const tones = new Map(activePatchPreview.lineTones);
28643
+ return persistentLineTones;
28644
+ const tones = new Map(persistentLineTones);
28645
+ for (const [line, tone] of activePatchPreview.lineTones) {
28646
+ tones.set(line, tone);
28647
+ }
28157
28648
  for (const line of patchChangedLines ?? []) {
28158
28649
  if (!tones.has(line))
28159
28650
  tones.set(line, "add");
28160
28651
  }
28161
28652
  return tones;
28162
- }, [activePatchPreview, patchChangedLines]);
28653
+ }, [activePatchPreview, patchChangedLines, persistentLineTones]);
28163
28654
  if (!isViewerOpen || !selectedFile)
28164
28655
  return null;
28165
28656
  const language = inferLanguage(selectedFile);
28166
- const renderMarkdown = isMarkdownFile(selectedFile) && !effectiveHighlight && !activePatchPreview && !writePreview?.content;
28657
+ const renderMarkdown = isMarkdownFile(selectedFile) && !effectiveHighlight && !persistentLineTones && !activePatchPreview && !writePreview?.content;
28167
28658
  return /* @__PURE__ */ jsxs97("div", {
28168
28659
  className: mode === "pane" ? "h-full w-full bg-transparent flex flex-col" : "absolute inset-0 bg-background z-50 flex flex-col animate-in slide-in-from-left duration-300",
28169
28660
  children: [
@@ -28208,7 +28699,8 @@ var FileViewerPanel = memo47(function FileViewerPanel2({
28208
28699
  className: "flex-1 overflow-auto",
28209
28700
  children: writePreview?.content !== undefined ? /* @__PURE__ */ jsx110(CodeMirrorViewer, {
28210
28701
  content: writePreview.content,
28211
- path: selectedFile
28702
+ path: selectedFile,
28703
+ lineTones: writePreviewLineTones
28212
28704
  }) : activePatchPreview ? /* @__PURE__ */ jsx110(CodeMirrorViewer, {
28213
28705
  content: activePatchPreview.content,
28214
28706
  path: selectedFile,
@@ -28259,6 +28751,7 @@ var FileViewerPanel = memo47(function FileViewerPanel2({
28259
28751
  content: data.content,
28260
28752
  path: selectedFile,
28261
28753
  highlightedLines,
28754
+ lineTones: persistentLineTones,
28262
28755
  scrollToLine: scrollToHighlightLine
28263
28756
  }) : /* @__PURE__ */ jsx110("div", {
28264
28757
  className: "h-full flex items-center justify-center text-muted-foreground",
@@ -28514,20 +29007,170 @@ function HighlightedPath({ path, query }) {
28514
29007
  });
28515
29008
  }
28516
29009
  // src/components/workspace/ViewerTabs.tsx
29010
+ import { Icon, addCollection } from "@iconify/react";
29011
+ import { icons as materialIconTheme } from "@iconify-json/material-icon-theme";
28517
29012
  import { memo as memo49, useEffect as useEffect53 } from "react";
28518
- import {
28519
- Braces as Braces2,
28520
- File as File3,
28521
- FileCode as FileCode5,
28522
- FileJson as FileJson2,
28523
- FileText as FileText8,
28524
- FileType as FileType2,
28525
- GitCommit as GitCommit5,
28526
- Image as Image2,
28527
- Settings as Settings4,
28528
- X as X21
28529
- } from "lucide-react";
29013
+ import { GitCommit as GitCommit5, X as X21 } from "lucide-react";
28530
29014
  import { jsx as jsx112, jsxs as jsxs99 } from "react/jsx-runtime";
29015
+ addCollection(materialIconTheme);
29016
+ var ICON_CLASS = "h-3.5 w-3.5 shrink-0";
29017
+ var FILENAME_ICON_MAP = {
29018
+ ".dockerignore": "docker",
29019
+ ".editorconfig": "editorconfig",
29020
+ ".env": "settings",
29021
+ ".env.example": "settings",
29022
+ ".env.local": "settings",
29023
+ ".eslintignore": "eslint",
29024
+ ".eslintrc": "eslint",
29025
+ ".gitattributes": "git",
29026
+ ".gitignore": "git",
29027
+ ".prettierrc": "prettier",
29028
+ "astro.config.mjs": "astro-config",
29029
+ "astro.config.ts": "astro-config",
29030
+ "biome.json": "biome",
29031
+ "bun.lock": "bun",
29032
+ "bun.lockb": "bun",
29033
+ "bunfig.toml": "bun",
29034
+ "cargo.lock": "lock",
29035
+ "cargo.toml": "rust",
29036
+ "compose.yaml": "docker",
29037
+ "compose.yml": "docker",
29038
+ "docker-compose.yaml": "docker",
29039
+ "docker-compose.yml": "docker",
29040
+ dockerfile: "docker",
29041
+ "eslint.config.js": "eslint",
29042
+ "eslint.config.mjs": "eslint",
29043
+ "eslint.config.ts": "eslint",
29044
+ gemfile: "gemfile",
29045
+ "go.mod": "go-mod",
29046
+ "go.sum": "go-mod",
29047
+ "jsconfig.json": "jsconfig",
29048
+ license: "license",
29049
+ makefile: "makefile",
29050
+ "next.config.js": "next",
29051
+ "next.config.mjs": "next",
29052
+ "next.config.ts": "next",
29053
+ "package-lock.json": "npm",
29054
+ "package.json": "npm",
29055
+ "pnpm-lock.yaml": "pnpm",
29056
+ "postcss.config.js": "postcss",
29057
+ "postcss.config.mjs": "postcss",
29058
+ "postcss.config.ts": "postcss",
29059
+ "prettier.config.js": "prettier",
29060
+ "prettier.config.mjs": "prettier",
29061
+ "prettier.config.ts": "prettier",
29062
+ "readme.md": "readme",
29063
+ "readme.mdx": "readme",
29064
+ "rollup.config.js": "rollup",
29065
+ "rollup.config.mjs": "rollup",
29066
+ "rollup.config.ts": "rollup",
29067
+ "svelte.config.js": "svelte",
29068
+ "svelte.config.ts": "svelte",
29069
+ "tailwind.config.js": "tailwindcss",
29070
+ "tailwind.config.ts": "tailwindcss",
29071
+ "tauri.conf.json": "tauri",
29072
+ "tsconfig.json": "tsconfig",
29073
+ "vite.config.js": "vite",
29074
+ "vite.config.mjs": "vite",
29075
+ "vite.config.ts": "vite",
29076
+ "vitest.config.js": "vitest",
29077
+ "vitest.config.mjs": "vitest",
29078
+ "vitest.config.ts": "vitest",
29079
+ "vue.config.js": "vue-config",
29080
+ "vue.config.ts": "vue-config",
29081
+ "yarn.lock": "lock"
29082
+ };
29083
+ var EXTENSION_ICON_MAP = {
29084
+ ai: "image",
29085
+ astro: "astro",
29086
+ avif: "image",
29087
+ bash: "console",
29088
+ bmp: "image",
29089
+ c: "c",
29090
+ cc: "cpp",
29091
+ clj: "clojure",
29092
+ cljc: "clojure",
29093
+ cljs: "clojure",
29094
+ cpp: "cpp",
29095
+ cs: "csharp",
29096
+ css: "css",
29097
+ cxx: "cpp",
29098
+ dart: "dart",
29099
+ dockerfile: "docker",
29100
+ ex: "elixir",
29101
+ exs: "elixir",
29102
+ erl: "erlang",
29103
+ fish: "console",
29104
+ gif: "image",
29105
+ go: "go",
29106
+ graphql: "graphql",
29107
+ gql: "graphql",
29108
+ groovy: "groovy",
29109
+ h: "c",
29110
+ hpp: "cpp",
29111
+ hrl: "erlang",
29112
+ hs: "haskell",
29113
+ htm: "html",
29114
+ html: "html",
29115
+ hxx: "cpp",
29116
+ ico: "favicon",
29117
+ java: "java",
29118
+ jpeg: "image",
29119
+ jpg: "image",
29120
+ js: "javascript",
29121
+ json: "json",
29122
+ jsonc: "json",
29123
+ jsx: "react",
29124
+ jl: "julia",
29125
+ kt: "kotlin",
29126
+ kts: "kotlin",
29127
+ less: "less",
29128
+ log: "log",
29129
+ lua: "lua",
29130
+ luau: "luau",
29131
+ mjs: "javascript",
29132
+ ml: "ocaml",
29133
+ mli: "ocaml",
29134
+ mov: "video",
29135
+ mp3: "audio",
29136
+ mp4: "video",
29137
+ md: "markdown",
29138
+ mdx: "markdown",
29139
+ nim: "nim",
29140
+ pdf: "pdf",
29141
+ php: "php-elephant",
29142
+ png: "image",
29143
+ prisma: "prisma",
29144
+ proto: "proto",
29145
+ ps1: "powershell",
29146
+ py: "python",
29147
+ pyw: "python",
29148
+ r: "r",
29149
+ rb: "ruby",
29150
+ rs: "rust",
29151
+ sass: "sass",
29152
+ scala: "scala",
29153
+ scss: "sass",
29154
+ sh: "console",
29155
+ sql: "database",
29156
+ svg: "svg",
29157
+ svelte: "svelte",
29158
+ swift: "swift",
29159
+ toml: "toml",
29160
+ ts: "typescript",
29161
+ tsx: "react-ts",
29162
+ txt: "document",
29163
+ vue: "vue",
29164
+ wav: "audio",
29165
+ webm: "video",
29166
+ webp: "image",
29167
+ xml: "xml",
29168
+ yaml: "yaml",
29169
+ yml: "yaml",
29170
+ zig: "zig",
29171
+ zip: "zip",
29172
+ zsh: "console"
29173
+ };
28531
29174
  function tabKindLabel(tab) {
28532
29175
  switch (tab.type) {
28533
29176
  case "git-diff":
@@ -28557,86 +29200,34 @@ function getFileExtension2(path) {
28557
29200
  const extension = path.split(".").pop()?.toLowerCase() ?? "";
28558
29201
  return extension && extension !== path.toLowerCase() ? extension : "";
28559
29202
  }
28560
- function renderLanguageIcon(extension) {
28561
- switch (extension) {
28562
- case "ts":
28563
- case "tsx":
28564
- return /* @__PURE__ */ jsx112("span", {
28565
- className: "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center bg-[#3178c6] text-[7px] font-bold leading-none text-white",
28566
- children: "TS"
28567
- });
28568
- case "js":
28569
- case "jsx":
28570
- case "mjs":
28571
- case "cjs":
28572
- return /* @__PURE__ */ jsx112("span", {
28573
- className: "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center bg-[#f7df1e] text-[7px] font-bold leading-none text-black",
28574
- children: "JS"
28575
- });
28576
- case "py":
28577
- return /* @__PURE__ */ jsx112("span", {
28578
- className: "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-[3px] bg-gradient-to-br from-[#3776ab] to-[#ffd43b] text-[7px] font-bold leading-none text-white",
28579
- children: "Py"
28580
- });
28581
- case "go":
28582
- return /* @__PURE__ */ jsx112("span", {
28583
- className: "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full bg-[#00add8] text-[7px] font-bold leading-none text-white",
28584
- children: "Go"
28585
- });
28586
- case "rs":
28587
- return /* @__PURE__ */ jsx112("span", {
28588
- className: "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full bg-[#ce422b] text-[8px] font-bold leading-none text-white",
28589
- children: "R"
28590
- });
28591
- case "json":
28592
- return /* @__PURE__ */ jsx112(FileJson2, {
28593
- className: "h-3.5 w-3.5 shrink-0 text-yellow-500"
28594
- });
28595
- case "md":
28596
- case "mdx":
28597
- return /* @__PURE__ */ jsx112(FileText8, {
28598
- className: "h-3.5 w-3.5 shrink-0 text-sky-500"
28599
- });
28600
- case "env":
28601
- case "toml":
28602
- case "yaml":
28603
- case "yml":
28604
- return /* @__PURE__ */ jsx112(Settings4, {
28605
- className: "h-3.5 w-3.5 shrink-0 text-violet-500"
28606
- });
28607
- case "css":
28608
- case "scss":
28609
- case "sass":
28610
- case "less":
28611
- return /* @__PURE__ */ jsx112(Braces2, {
28612
- className: "h-3.5 w-3.5 shrink-0 text-blue-500"
28613
- });
28614
- case "html":
28615
- case "xml":
28616
- return /* @__PURE__ */ jsx112(FileType2, {
28617
- className: "h-3.5 w-3.5 shrink-0 text-orange-500"
28618
- });
28619
- case "png":
28620
- case "jpg":
28621
- case "jpeg":
28622
- case "gif":
28623
- case "svg":
28624
- case "webp":
28625
- return /* @__PURE__ */ jsx112(Image2, {
28626
- className: "h-3.5 w-3.5 shrink-0 text-pink-500"
28627
- });
28628
- case "txt":
28629
- case "log":
28630
- return /* @__PURE__ */ jsx112(FileText8, {
28631
- className: "h-3.5 w-3.5 shrink-0 text-muted-foreground"
28632
- });
28633
- default:
28634
- return extension ? /* @__PURE__ */ jsx112(FileCode5, {
28635
- className: "h-3.5 w-3.5 shrink-0 text-muted-foreground/80"
28636
- }) : /* @__PURE__ */ jsx112(File3, {
28637
- className: "h-3.5 w-3.5 shrink-0 text-muted-foreground/80"
28638
- });
29203
+ function getFileName3(path) {
29204
+ return path.split(/[\\/]/).pop()?.toLowerCase() ?? path.toLowerCase();
29205
+ }
29206
+ function getIconNameForPath(path) {
29207
+ const fileName = getFileName3(path);
29208
+ const fileIcon = FILENAME_ICON_MAP[fileName];
29209
+ if (fileIcon)
29210
+ return fileIcon;
29211
+ if (fileName.endsWith(".d.ts"))
29212
+ return "typescript-def";
29213
+ if (fileName.endsWith(".test.ts") || fileName.endsWith(".spec.ts")) {
29214
+ return "test-ts";
29215
+ }
29216
+ if (fileName.endsWith(".test.js") || fileName.endsWith(".spec.js")) {
29217
+ return "test-js";
29218
+ }
29219
+ if (fileName.endsWith(".test.jsx") || fileName.endsWith(".spec.jsx") || fileName.endsWith(".test.tsx") || fileName.endsWith(".spec.tsx")) {
29220
+ return "test-jsx";
28639
29221
  }
29222
+ const extension = getFileExtension2(fileName);
29223
+ return EXTENSION_ICON_MAP[extension] ?? "document";
29224
+ }
29225
+ function renderFileIcon(path) {
29226
+ return /* @__PURE__ */ jsx112(Icon, {
29227
+ "aria-hidden": "true",
29228
+ className: ICON_CLASS,
29229
+ icon: `material-icon-theme:${getIconNameForPath(path)}`
29230
+ });
28640
29231
  }
28641
29232
  function renderTabIcon(tab) {
28642
29233
  if (tab.type === "git-diff") {
@@ -28654,10 +29245,10 @@ function renderTabIcon(tab) {
28654
29245
  className: `h-3.5 w-3.5 shrink-0 ${tab.status === "error" ? "text-red-500" : tab.status === "success" ? "text-emerald-500" : "text-blue-500"}`
28655
29246
  });
28656
29247
  }
28657
- const extension = getFileExtension2(getTabPath(tab));
29248
+ const path = getTabPath(tab);
28658
29249
  return /* @__PURE__ */ jsx112("span", {
28659
29250
  className: "shrink-0 inline-flex items-center text-muted-foreground/80",
28660
- children: renderLanguageIcon(extension)
29251
+ children: renderFileIcon(path)
28661
29252
  });
28662
29253
  }
28663
29254
  function renderTabContent(tab, closeTab, updateSessionFileOperationIndex) {
@@ -28686,6 +29277,7 @@ function renderTabContent(tab, closeTab, updateSessionFileOperationIndex) {
28686
29277
  open: true,
28687
29278
  file: tab.path,
28688
29279
  highlight: tab.highlight,
29280
+ annotations: tab.annotations,
28689
29281
  patchPreview: tab.patchPreview,
28690
29282
  writePreview: tab.writePreview,
28691
29283
  onClose: () => closeTab(tab.id)
@@ -31967,6 +32559,7 @@ export {
31967
32559
  openPlatformUrl,
31968
32560
  openPlatformSession,
31969
32561
  notifyPlatformFontFamilyChanged,
32562
+ normalizeQueueState,
31970
32563
  listPlatformSystemFonts,
31971
32564
  hasPlatformSystemFonts,
31972
32565
  hasPlatformOpenUrl,
@@ -32052,4 +32645,4 @@ export {
32052
32645
  API_BASE_URL
32053
32646
  };
32054
32647
 
32055
- //# debugId=1239796033F84F8A64756E2164756E21
32648
+ //# debugId=9AF775CD8BC5AA3D64756E2164756E21