@ottocode/web-sdk 0.1.275 → 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
@@ -1 +1 @@
1
- {"version":3,"file":"MessagePartItem.d.ts","sourceRoot":"","sources":["../../../src/components/messages/MessagePartItem.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAI1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAwInD,UAAU,oBAAoB;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC7C,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,4DAigB3B,CAAC"}
1
+ {"version":3,"file":"MessagePartItem.d.ts","sourceRoot":"","sources":["../../../src/components/messages/MessagePartItem.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAI1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAwInD,UAAU,oBAAoB;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC7C,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,4DAif3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"DiffView.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/renderers/DiffView.tsx"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACtB,KAAK,EAAE,MAAM,CAAC;CACd;AAgKD,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,aAAa,2CAyHhD"}
1
+ {"version":3,"file":"DiffView.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/renderers/DiffView.tsx"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACtB,KAAK,EAAE,MAAM,CAAC;CACd;AAwKD,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,aAAa,2CAyHhD"}
@@ -1 +1 @@
1
- {"version":3,"file":"Toaster.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Toaster.tsx"],"names":[],"mappings":"AAmKA,wBAAgB,OAAO,4CAYtB"}
1
+ {"version":3,"file":"Toaster.d.ts","sourceRoot":"","sources":["../../../src/components/ui/Toaster.tsx"],"names":[],"mappings":"AAsKA,wBAAgB,OAAO,4CAYtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ToolPreviewPanel.d.ts","sourceRoot":"","sources":["../../../src/components/workspace/ToolPreviewPanel.tsx"],"names":[],"mappings":"AAGA,OAAO,EACN,KAAK,SAAS,EAEd,MAAM,8BAA8B,CAAC;AAGtC,UAAU,qBAAqB;IAC9B,GAAG,EAAE,OAAO,CAAC,SAAS,EAAE;QAAE,IAAI,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;CAClD;AAOD,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAsVD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,UAAU,EAAE,MAAM,GAChB,gBAAgB,GAAG,IAAI,CA+FzB;AAmFD,wBAAgB,gBAAgB,CAAC,EAAE,GAAG,EAAE,EAAE,qBAAqB,2CA0P9D"}
1
+ {"version":3,"file":"ToolPreviewPanel.d.ts","sourceRoot":"","sources":["../../../src/components/workspace/ToolPreviewPanel.tsx"],"names":[],"mappings":"AAGA,OAAO,EACN,KAAK,SAAS,EAEd,MAAM,8BAA8B,CAAC;AAGtC,UAAU,qBAAqB;IAC9B,GAAG,EAAE,OAAO,CAAC,SAAS,EAAE;QAAE,IAAI,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;CAClD;AAOD,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAujBD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,UAAU,EAAE,MAAM,GAChB,gBAAgB,GAAG,IAAI,CAsGzB;AAmFD,wBAAgB,gBAAgB,CAAC,EAAE,GAAG,EAAE,EAAE,qBAAqB,2CA0P9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"ViewerTabs.d.ts","sourceRoot":"","sources":["../../../src/components/workspace/ViewerTabs.tsx"],"names":[],"mappings":"AAkOA,eAAO,MAAM,UAAU,8CAkFrB,CAAC"}
1
+ {"version":3,"file":"ViewerTabs.d.ts","sourceRoot":"","sources":["../../../src/components/workspace/ViewerTabs.tsx"],"names":[],"mappings":"AAyVA,eAAO,MAAM,UAAU,8CAkFrB,CAAC"}
@@ -159,6 +159,21 @@ var sessionsMixin = {
159
159
  throw new Error(extractErrorMessage(response.error));
160
160
  return response.data;
161
161
  },
162
+ async createHandoff(sessionId) {
163
+ const response = await fetch(`${getBaseUrl()}/v1/sessions/${encodeURIComponent(sessionId)}/handoff`, { method: "POST" });
164
+ const data = await response.json().catch(() => null);
165
+ if (!response.ok)
166
+ throw new Error(extractErrorMessage(data));
167
+ if (!data?.session || !data?.sessionId) {
168
+ throw new Error("No data returned from handoff");
169
+ }
170
+ return {
171
+ session: convertSession(data.session),
172
+ sessionId: String(data.sessionId),
173
+ sourceSessionId: String(data.sourceSessionId),
174
+ message: String(data.message ?? "")
175
+ };
176
+ },
162
177
  async abortSession(sessionId) {
163
178
  const response = await apiAbortSession({ path: { sessionId } });
164
179
  if (response.error)
@@ -893,6 +908,7 @@ class ApiClient {
893
908
  updateSession = sessionsMixin.updateSession;
894
909
  markSessionViewed = sessionsMixin.markSessionViewed;
895
910
  deleteSession = sessionsMixin.deleteSession;
911
+ createHandoff = sessionsMixin.createHandoff;
896
912
  abortSession = sessionsMixin.abortSession;
897
913
  abortMessage = sessionsMixin.abortMessage;
898
914
  getQueueState = sessionsMixin.getQueueState;
@@ -1279,6 +1295,53 @@ function mergeChangedLines(existing, incoming) {
1279
1295
  return existing;
1280
1296
  return [...new Set([...existing, ...incoming])].sort((a, b) => a - b);
1281
1297
  }
1298
+ function countContentLines(content) {
1299
+ return content.length === 0 ? 1 : content.split(`
1300
+ `).length;
1301
+ }
1302
+ function annotationId(preview, targetPath) {
1303
+ return `${preview.toolName}:${preview.callId ?? `${normalizeViewerPath(targetPath)}:${preview.patch ?? preview.content ?? ""}`}`;
1304
+ }
1305
+ function buildAnnotation(preview, targetPath, existing) {
1306
+ if (preview.status === "error")
1307
+ return existing;
1308
+ const id = annotationId(preview, targetPath);
1309
+ if (preview.toolName === "write") {
1310
+ const content = preview.content;
1311
+ if (content === undefined)
1312
+ return existing;
1313
+ return {
1314
+ id,
1315
+ reason: "write",
1316
+ callId: preview.callId,
1317
+ status: preview.status,
1318
+ lineTones: Array.from({ length: countContentLines(content) }, (_, index) => [index + 1, "add"]),
1319
+ createdAt: existing?.createdAt ?? Date.now()
1320
+ };
1321
+ }
1322
+ const lineTones = preview.previewLineTones?.length ? preview.previewLineTones : preview.changedLines?.length ? preview.changedLines.map((line) => [line, "add"]) : existing?.lineTones;
1323
+ if (!lineTones?.length)
1324
+ return existing;
1325
+ return {
1326
+ id,
1327
+ reason: "apply_patch",
1328
+ callId: preview.callId,
1329
+ status: preview.status,
1330
+ lineTones,
1331
+ createdAt: existing?.createdAt ?? Date.now()
1332
+ };
1333
+ }
1334
+ function upsertAnnotation(annotations, annotation) {
1335
+ if (!annotation)
1336
+ return annotations;
1337
+ const existing = annotations ?? [];
1338
+ const index = existing.findIndex((item) => item.id === annotation.id);
1339
+ if (index === -1)
1340
+ return [...existing, annotation];
1341
+ const next = [...existing];
1342
+ next[index] = annotation;
1343
+ return next;
1344
+ }
1282
1345
  var useViewerTabsStore = create((set) => ({
1283
1346
  tabs: [],
1284
1347
  activeTabId: null,
@@ -1325,7 +1388,11 @@ var useViewerTabsStore = create((set) => ({
1325
1388
  id: targetId,
1326
1389
  type: "file",
1327
1390
  title: existingFile?.title ?? titleFromPath(targetPath),
1328
- path: targetPath
1391
+ path: targetPath,
1392
+ highlight: existingFile?.highlight,
1393
+ annotations: existingFile?.annotations,
1394
+ patchPreview: existingFile?.patchPreview,
1395
+ writePreview: existingFile?.writePreview
1329
1396
  }),
1330
1397
  activeTabId: targetId
1331
1398
  };
@@ -1346,6 +1413,7 @@ var useViewerTabsStore = create((set) => ({
1346
1413
  title: existingFile?.title ?? titleFromPath(targetPath),
1347
1414
  path: targetPath,
1348
1415
  highlight,
1416
+ annotations: existingFile?.annotations,
1349
1417
  patchPreview: undefined,
1350
1418
  writePreview: undefined
1351
1419
  }),
@@ -1367,6 +1435,12 @@ var useViewerTabsStore = create((set) => ({
1367
1435
  const samePatchCall = isSamePatchCall(existingPatchPreview, preview);
1368
1436
  const baseContent = preview.baseContent ?? (samePatchCall ? existingPatchPreview?.baseContent : existingPatchPreview?.resultContent ?? existingPatchPreview?.baseContent);
1369
1437
  const changedLines = samePatchCall ? preview.changedLines ?? existingPatchPreview?.changedLines : mergeChangedLines(existingPatchPreview?.changedLines, preview.changedLines);
1438
+ const annotationPreview = {
1439
+ ...preview,
1440
+ changedLines: preview.changedLines
1441
+ };
1442
+ const existingAnnotation = existingFile?.annotations?.find((annotation2) => annotation2.id === annotationId(annotationPreview, targetPath));
1443
+ const annotations = upsertAnnotation(existingFile?.annotations, buildAnnotation(annotationPreview, targetPath, existingAnnotation));
1370
1444
  return {
1371
1445
  tabs: upsertTab(tabs, {
1372
1446
  id: targetId,
@@ -1374,6 +1448,7 @@ var useViewerTabsStore = create((set) => ({
1374
1448
  title: existingFile?.title ?? titleFromPath(targetPath),
1375
1449
  path: targetPath,
1376
1450
  highlight: undefined,
1451
+ annotations,
1377
1452
  writePreview: undefined,
1378
1453
  patchPreview: {
1379
1454
  path: targetPath,
@@ -1396,10 +1471,13 @@ var useViewerTabsStore = create((set) => ({
1396
1471
  }
1397
1472
  if (existingFile) {
1398
1473
  const existingWritePreview = existingFile.writePreview;
1474
+ const existingAnnotation = existingFile.annotations?.find((annotation2) => annotation2.id === annotationId(preview, targetPath));
1475
+ const annotations = upsertAnnotation(existingFile.annotations, buildAnnotation(preview, targetPath, existingAnnotation));
1399
1476
  return {
1400
1477
  tabs: upsertTab(tabs, {
1401
1478
  ...existingFile,
1402
1479
  highlight: undefined,
1480
+ annotations,
1403
1481
  patchPreview: undefined,
1404
1482
  writePreview: {
1405
1483
  path: targetPath,
@@ -1414,25 +1492,24 @@ var useViewerTabsStore = create((set) => ({
1414
1492
  };
1415
1493
  }
1416
1494
  const existingWrite = existing?.toolName === "write" ? existing : undefined;
1495
+ const annotation = buildAnnotation(preview, preview.path);
1417
1496
  return {
1418
1497
  tabs: upsertTab(tabs, {
1419
1498
  id,
1420
- type: "tool-preview",
1499
+ type: "file",
1421
1500
  title: titleFromPath(preview.path),
1422
1501
  path: preview.path,
1423
- toolName: preview.toolName,
1424
- callId: preview.callId,
1425
- content: preview.content ?? existingWrite?.content,
1426
- baseContent: undefined,
1427
- patch: undefined,
1428
- changedLines: undefined,
1429
- previewContent: undefined,
1430
- resultContent: undefined,
1431
- previewLineTones: undefined,
1432
- previewFirstLine: undefined,
1433
- previewLatestLine: undefined,
1434
- status: preview.status,
1435
- error: preview.error ?? existingWrite?.error
1502
+ highlight: undefined,
1503
+ annotations: annotation ? [annotation] : undefined,
1504
+ patchPreview: undefined,
1505
+ writePreview: {
1506
+ path: preview.path,
1507
+ toolName: "write",
1508
+ callId: preview.callId,
1509
+ content: preview.content ?? existingWrite?.content,
1510
+ status: preview.status,
1511
+ error: preview.error ?? existingWrite?.error
1512
+ }
1436
1513
  }),
1437
1514
  activeTabId: id
1438
1515
  };
@@ -2035,14 +2112,26 @@ import { useQuery as useQuery4 } from "@tanstack/react-query";
2035
2112
  var defaultQueueState = {
2036
2113
  currentMessageId: null,
2037
2114
  queuedMessages: [],
2038
- queueLength: 0
2115
+ queueLength: 0,
2116
+ isRunning: false
2039
2117
  };
2118
+ function normalizeQueueState(state) {
2119
+ const isRunning = state.isRunning ?? Boolean(state.currentMessageId);
2120
+ const currentMessageId = isRunning ? state.currentMessageId : null;
2121
+ const hasActiveTurn = Boolean(currentMessageId);
2122
+ const queuedMessages = hasActiveTurn ? state.queuedMessages : [];
2123
+ return {
2124
+ currentMessageId,
2125
+ queuedMessages,
2126
+ queueLength: queuedMessages.length,
2127
+ isRunning: hasActiveTurn
2128
+ };
2129
+ }
2040
2130
  function optimisticallyQueueMessage(queryClient, sessionId, messageId) {
2041
2131
  queryClient.setQueryData(["queueState", sessionId], (current) => {
2042
2132
  if (!current)
2043
2133
  return current;
2044
- const isBusy = Boolean(current.currentMessageId) || current.queuedMessages.length > 0 || current.queueLength > 0;
2045
- if (!isBusy)
2134
+ if (!current.isRunning || !current.currentMessageId)
2046
2135
  return current;
2047
2136
  if (current.currentMessageId === messageId)
2048
2137
  return current;
@@ -2067,11 +2156,7 @@ function useQueueState(sessionId) {
2067
2156
  if (!sessionId)
2068
2157
  return defaultQueueState;
2069
2158
  const queueState = await apiClient.getQueueState(sessionId);
2070
- return {
2071
- currentMessageId: queueState.currentMessageId,
2072
- queuedMessages: queueState.queuedMessages,
2073
- queueLength: queueState.queuedMessages.length
2074
- };
2159
+ return normalizeQueueState(queueState);
2075
2160
  },
2076
2161
  enabled: !!sessionId,
2077
2162
  placeholderData: defaultQueueState,
@@ -2654,22 +2739,30 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2654
2739
  return;
2655
2740
  let changeLines = 0;
2656
2741
  let stableChangeLength = 0;
2742
+ let lineDirectiveCount = 0;
2657
2743
  for (const line of stablePatch.split(`
2658
2744
  `)) {
2659
2745
  if (line.startsWith("+") && !line.startsWith("+++") || line.startsWith("-") && !line.startsWith("---")) {
2660
2746
  changeLines += 1;
2661
2747
  stableChangeLength += line.length;
2748
+ } 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:")) {
2749
+ lineDirectiveCount += 1;
2662
2750
  }
2663
2751
  }
2664
- return changeLines > 0 ? `${changeLines}:${stableChangeLength}` : undefined;
2752
+ if (changeLines > 0)
2753
+ return `${changeLines}:${stableChangeLength}`;
2754
+ return lineDirectiveCount > 0 ? `lines:${lineDirectiveCount}:${stablePatch.length}` : undefined;
2665
2755
  };
2666
2756
  const extractPathsFromPatch = (patch) => {
2667
2757
  const paths = new Set;
2668
2758
  for (const line of patch.split(`
2669
2759
  `)) {
2670
2760
  const directive = line.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/);
2671
- if (directive?.[1]) {
2672
- paths.add(directive[1].trim());
2761
+ const replaceDirective = line.match(/^\*\*\* Replace in: (.+)$/);
2762
+ const lineDirective = line.match(/^\*\*\* (?:Delete Lines in|Replace Lines in|Insert Before in|Insert After in): (.+)$/);
2763
+ const path = directive?.[1] ?? replaceDirective?.[1] ?? lineDirective?.[1];
2764
+ if (path) {
2765
+ paths.add(path.trim());
2673
2766
  continue;
2674
2767
  }
2675
2768
  const unified = line.match(/^\+\+\+ (?:b\/)?(.+)$/);
@@ -2679,6 +2772,47 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2679
2772
  }
2680
2773
  return [...paths];
2681
2774
  };
2775
+ const getExtension = (path) => path.split(".").pop()?.toLowerCase() ?? "";
2776
+ const updateFileContentCache = (path, content) => {
2777
+ queryClient.setQueryData(["files", "read", path], {
2778
+ content,
2779
+ path,
2780
+ extension: getExtension(path),
2781
+ lineCount: content.split(`
2782
+ `).length
2783
+ });
2784
+ };
2785
+ const mergeReadResultIntoFileCache = (path, result, startLine, endLine) => {
2786
+ if (typeof result?.content !== "string")
2787
+ return;
2788
+ const readContent = result.content;
2789
+ if (!startLine || !endLine) {
2790
+ updateFileContentCache(path, readContent);
2791
+ return;
2792
+ }
2793
+ queryClient.setQueryData(["files", "read", path], (current) => {
2794
+ if (!current?.content)
2795
+ return current;
2796
+ const lines = current.content.split(`
2797
+ `);
2798
+ if (lines.at(-1) === "")
2799
+ lines.pop();
2800
+ const readLines = readContent.split(`
2801
+ `);
2802
+ lines.splice(startLine - 1, endLine - startLine + 1, ...readLines);
2803
+ const content = `${lines.join(`
2804
+ `)}
2805
+ `;
2806
+ return {
2807
+ ...current,
2808
+ content,
2809
+ lineCount: typeof result.totalLines === "number" ? result.totalLines : lines.length
2810
+ };
2811
+ });
2812
+ };
2813
+ const invalidateFileContentCache = (path) => {
2814
+ queryClient.invalidateQueries({ queryKey: ["files", "read", path] });
2815
+ };
2682
2816
  const getChangedLinesForPath = (result, path) => {
2683
2817
  const changes = Array.isArray(result?.changes) ? result.changes : [];
2684
2818
  const lines = new Set;
@@ -2708,9 +2842,6 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2708
2842
  return lines.size > 0 ? [...lines] : undefined;
2709
2843
  };
2710
2844
  const handleReadToolActivity = (eventType, payload, delta) => {
2711
- const viewerStore = useViewerTabsStore.getState();
2712
- if (!viewerStore.followToolActivity)
2713
- return;
2714
2845
  const name = getToolEventName(payload);
2715
2846
  if (name !== "read")
2716
2847
  return;
@@ -2723,6 +2854,12 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2723
2854
  const startLine = normalizeLineNumber(args.startLine) ?? normalizeLineNumber(args.start_line) ?? rangeFromResult.startLine;
2724
2855
  const endLine = normalizeLineNumber(args.endLine) ?? normalizeLineNumber(args.end_line) ?? rangeFromResult.endLine ?? startLine;
2725
2856
  const failed = result?.ok === false || eventType === "error";
2857
+ if (eventType === "tool.result" && !failed) {
2858
+ mergeReadResultIntoFileCache(path, result, startLine, endLine);
2859
+ }
2860
+ const viewerStore = useViewerTabsStore.getState();
2861
+ if (!viewerStore.followToolActivity)
2862
+ return;
2726
2863
  viewerStore.openToolReadTab(path, {
2727
2864
  startLine,
2728
2865
  endLine,
@@ -2732,9 +2869,6 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2732
2869
  });
2733
2870
  };
2734
2871
  const handleWriteToolActivity = (eventType, payload, delta) => {
2735
- const viewerStore = useViewerTabsStore.getState();
2736
- if (!viewerStore.followToolActivity)
2737
- return;
2738
2872
  const name = getToolEventName(payload);
2739
2873
  if (name !== "write")
2740
2874
  return;
@@ -2748,6 +2882,15 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2748
2882
  const callId = getToolEventCallId(payload) ?? undefined;
2749
2883
  const status = failed ? "error" : eventType === "tool.result" ? "success" : "streaming";
2750
2884
  const content = status === "streaming" ? getStreamingWritePreviewContent(args, buffer) : getStringArg(args, buffer, "content");
2885
+ if (status === "success") {
2886
+ if (content !== undefined)
2887
+ updateFileContentCache(path, content);
2888
+ else
2889
+ invalidateFileContentCache(path);
2890
+ }
2891
+ const viewerStore = useViewerTabsStore.getState();
2892
+ if (!viewerStore.followToolActivity)
2893
+ return;
2751
2894
  if (status === "streaming" && content !== undefined && content.length >= TOOL_PREVIEW_THROTTLE_MIN_CHARS) {
2752
2895
  const previewKey = callId ?? path;
2753
2896
  const now = Date.now();
@@ -2771,9 +2914,6 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2771
2914
  });
2772
2915
  };
2773
2916
  const handleApplyPatchToolActivity = (eventType, payload, delta) => {
2774
- const viewerStore = useViewerTabsStore.getState();
2775
- if (!viewerStore.followToolActivity)
2776
- return;
2777
2917
  const name = getToolEventName(payload);
2778
2918
  if (name !== "apply_patch")
2779
2919
  return;
@@ -2797,6 +2937,13 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2797
2937
  const patchPaths = extractPathsFromPatch(patch);
2798
2938
  if (patchPaths.length === 0)
2799
2939
  return;
2940
+ if (status === "success") {
2941
+ for (const path of patchPaths)
2942
+ invalidateFileContentCache(path);
2943
+ }
2944
+ const viewerStore = useViewerTabsStore.getState();
2945
+ if (!viewerStore.followToolActivity)
2946
+ return;
2800
2947
  const matchingFileTabs = viewerStore.tabs.filter((tab) => tab.type === "file" && patchPaths.some((path) => patchPathMayReferToTarget(path, tab.path)));
2801
2948
  const activeMatchingFileTab = matchingFileTabs.find((tab) => tab.id === viewerStore.activeTabId);
2802
2949
  const fallbackPath = patchPaths.find(isLikelyCompletePatchPath);
@@ -2902,6 +3049,11 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2902
3049
  const applyMessageDelta = (payload) => {
2903
3050
  const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
2904
3051
  const partId = typeof payload?.partId === "string" ? payload.partId : null;
3052
+ const payloadType = typeof payload?.type === "string" ? payload.type : undefined;
3053
+ if (payloadType === "error") {
3054
+ upsertErrorPart(payload);
3055
+ return;
3056
+ }
2905
3057
  const delta = typeof payload?.delta === "string" ? payload.delta : null;
2906
3058
  if (!messageId || !partId || delta === null)
2907
3059
  return;
@@ -2952,6 +3104,90 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2952
3104
  return nextMessages;
2953
3105
  });
2954
3106
  };
3107
+ const toRecord = (value) => {
3108
+ if (value && typeof value === "object" && !Array.isArray(value)) {
3109
+ return value;
3110
+ }
3111
+ return null;
3112
+ };
3113
+ const parseErrorContent = (payload) => {
3114
+ const contentRecord = toRecord(payload.content);
3115
+ if (contentRecord)
3116
+ return contentRecord;
3117
+ if (typeof payload.content === "string") {
3118
+ try {
3119
+ const parsed = JSON.parse(payload.content);
3120
+ const parsedRecord = toRecord(parsed);
3121
+ if (parsedRecord)
3122
+ return parsedRecord;
3123
+ } catch {}
3124
+ }
3125
+ const message = typeof payload.error === "string" ? payload.error : typeof payload.message === "string" ? payload.message : "Assistant run failed";
3126
+ return {
3127
+ message,
3128
+ type: typeof payload.errorType === "string" ? payload.errorType : "error",
3129
+ details: toRecord(payload.details) ?? undefined,
3130
+ isAborted: payload.isAborted === true,
3131
+ autoCompacted: payload.autoCompacted === true
3132
+ };
3133
+ };
3134
+ const upsertErrorPart = (payload) => {
3135
+ const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
3136
+ if (!payload || !messageId)
3137
+ return;
3138
+ const contentJson = parseErrorContent(payload);
3139
+ const content = JSON.stringify(contentJson);
3140
+ const errorMessage = typeof contentJson.message === "string" ? contentJson.message : typeof payload.error === "string" ? payload.error : "Assistant run failed";
3141
+ const stepIndex = typeof payload.stepIndex === "number" ? payload.stepIndex : null;
3142
+ const partId = typeof payload.partId === "string" ? payload.partId : `error-${messageId}`;
3143
+ queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
3144
+ if (!oldMessages)
3145
+ return oldMessages;
3146
+ const nextMessages = [...oldMessages];
3147
+ const messageIndex = nextMessages.findIndex((message) => message.id === messageId);
3148
+ if (messageIndex === -1)
3149
+ return oldMessages;
3150
+ const targetMessage = nextMessages[messageIndex];
3151
+ const parts = targetMessage.parts ? [...targetMessage.parts] : [];
3152
+ const partIndex = parts.findIndex((part) => part.id === partId);
3153
+ if (partIndex === -1) {
3154
+ const newPart = {
3155
+ id: partId,
3156
+ messageId,
3157
+ index: getOptimisticPartIndex(parts, stepIndex),
3158
+ stepIndex,
3159
+ type: "error",
3160
+ content,
3161
+ contentJson,
3162
+ agent: targetMessage.agent,
3163
+ provider: targetMessage.provider,
3164
+ model: targetMessage.model,
3165
+ startedAt: Date.now(),
3166
+ completedAt: Date.now(),
3167
+ toolName: null,
3168
+ toolCallId: null,
3169
+ toolDurationMs: null
3170
+ };
3171
+ parts.push(newPart);
3172
+ } else {
3173
+ parts[partIndex] = {
3174
+ ...parts[partIndex],
3175
+ content,
3176
+ contentJson,
3177
+ stepIndex: stepIndex ?? parts[partIndex].stepIndex ?? null,
3178
+ completedAt: Date.now()
3179
+ };
3180
+ }
3181
+ nextMessages[messageIndex] = {
3182
+ ...targetMessage,
3183
+ status: "error",
3184
+ completedAt: targetMessage.completedAt ?? Date.now(),
3185
+ error: errorMessage,
3186
+ parts
3187
+ };
3188
+ return nextMessages;
3189
+ });
3190
+ };
2955
3191
  const upsertEphemeralToolCall = (payload) => {
2956
3192
  if (!payload)
2957
3193
  return;
@@ -3381,6 +3617,17 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3381
3617
  }
3382
3618
  markMessageCompleted(payload);
3383
3619
  clearEphemeralForMessage(id);
3620
+ if (id) {
3621
+ queryClient.setQueryData(["queueState", sessionId], (current) => {
3622
+ if (!current || current.currentMessageId !== id)
3623
+ return current;
3624
+ return normalizeQueueState({
3625
+ currentMessageId: null,
3626
+ queuedMessages: [],
3627
+ isRunning: false
3628
+ });
3629
+ });
3630
+ }
3384
3631
  queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
3385
3632
  queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
3386
3633
  break;
@@ -3454,22 +3701,7 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3454
3701
  assistantMessageIdRef.current = null;
3455
3702
  }
3456
3703
  clearEphemeralForMessage(messageId);
3457
- const errorMessage = typeof payload?.error === "string" ? payload.error : typeof payload?.message === "string" ? payload.message : "Assistant run failed";
3458
- queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
3459
- if (!oldMessages)
3460
- return oldMessages;
3461
- const idx = oldMessages.findIndex((m) => m.id === messageId);
3462
- if (idx === -1)
3463
- return oldMessages;
3464
- const next = [...oldMessages];
3465
- next[idx] = {
3466
- ...next[idx],
3467
- status: "error",
3468
- completedAt: next[idx].completedAt ?? Date.now(),
3469
- error: errorMessage
3470
- };
3471
- return next;
3472
- });
3704
+ upsertErrorPart(payload);
3473
3705
  }
3474
3706
  queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
3475
3707
  break;
@@ -3504,11 +3736,11 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3504
3736
  break;
3505
3737
  }
3506
3738
  case "queue.updated": {
3507
- const queueState = {
3739
+ const queueState = normalizeQueueState({
3508
3740
  currentMessageId: payload?.currentMessageId,
3509
3741
  queuedMessages: payload?.queuedMessages ?? [],
3510
- queueLength: payload?.queueLength ?? 0
3511
- };
3742
+ isRunning: typeof payload?.isRunning === "boolean" ? payload.isRunning : undefined
3743
+ });
3512
3744
  queryClient.setQueryData(["queueState", sessionId], queueState);
3513
3745
  break;
3514
3746
  }
@@ -5986,21 +6218,23 @@ function useTunnelStream() {
5986
6218
  // src/hooks/useProviderUsage.ts
5987
6219
  import { useEffect as useEffect14, useCallback as useCallback10, useRef as useRef7 } from "react";
5988
6220
  var POLL_INTERVAL = 60000;
5989
- var STALE_THRESHOLD = 30000;
6221
+ var STALE_THRESHOLD = 60000;
5990
6222
  var inflight = new Set;
5991
6223
  function useProviderUsage(provider, authType) {
5992
6224
  const setUsage = useUsageStore((s) => s.setUsage);
5993
6225
  const setLoading = useUsageStore((s) => s.setLoading);
5994
6226
  const setLastFetched = useUsageStore((s) => s.setLastFetched);
6227
+ const isModalOpen = useUsageStore((s) => s.isModalOpen);
6228
+ const modalProvider = useUsageStore((s) => s.modalProvider);
5995
6229
  const usage = useUsageStore((s) => provider ? s.usage[provider] : undefined);
5996
6230
  const isOAuthProvider = authType === "oauth" && (provider === "anthropic" || provider === "openai");
5997
- const fetchUsage = useCallback10(async () => {
6231
+ const fetchUsage = useCallback10(async (force = false) => {
5998
6232
  if (!provider || !isOAuthProvider)
5999
6233
  return;
6000
6234
  if (inflight.has(provider))
6001
6235
  return;
6002
6236
  const last = useUsageStore.getState().lastFetched[provider] ?? 0;
6003
- if (last && Date.now() - last < STALE_THRESHOLD)
6237
+ if (!force && last && Date.now() - last < STALE_THRESHOLD)
6004
6238
  return;
6005
6239
  inflight.add(provider);
6006
6240
  setLoading(provider, true);
@@ -6019,9 +6253,15 @@ function useProviderUsage(provider, authType) {
6019
6253
  if (!provider || !isOAuthProvider)
6020
6254
  return;
6021
6255
  fetchRef.current();
6256
+ }, [isOAuthProvider, provider]);
6257
+ useEffect14(() => {
6258
+ if (!provider || !isOAuthProvider || !isModalOpen || modalProvider !== provider) {
6259
+ return;
6260
+ }
6261
+ fetchRef.current(true);
6022
6262
  const interval = setInterval(() => fetchRef.current(), POLL_INTERVAL);
6023
6263
  return () => clearInterval(interval);
6024
- }, [isOAuthProvider, provider]);
6264
+ }, [isModalOpen, isOAuthProvider, modalProvider, provider]);
6025
6265
  return {
6026
6266
  usage,
6027
6267
  fetchUsage,
@@ -6436,7 +6676,8 @@ export {
6436
6676
  useAddRemote,
6437
6677
  useAddMCPServer,
6438
6678
  sessionsQueryKey,
6439
- optimisticallyQueueMessage
6679
+ optimisticallyQueueMessage,
6680
+ normalizeQueueState
6440
6681
  };
6441
6682
 
6442
- //# debugId=90296322954CBDC864756E2164756E21
6683
+ //# debugId=289696F18C6536EA64756E2164756E21