@ottocode/web-sdk 0.1.273 → 0.1.275

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 (31) hide show
  1. package/dist/components/file-browser/FileViewerPanel.d.ts +2 -1
  2. package/dist/components/file-browser/FileViewerPanel.d.ts.map +1 -1
  3. package/dist/components/index.js +3172 -2632
  4. package/dist/components/index.js.map +20 -19
  5. package/dist/components/messages/ActionToolBox.d.ts.map +1 -1
  6. package/dist/components/messages/AssistantMessageGroup.d.ts.map +1 -1
  7. package/dist/components/messages/CompactActivityGroup.d.ts.map +1 -1
  8. package/dist/components/messages/MessagePartItem.d.ts.map +1 -1
  9. package/dist/components/messages/MessageThread.d.ts.map +1 -1
  10. package/dist/components/messages/compactActivity.d.ts.map +1 -1
  11. package/dist/components/messages/renderers/CopyIntoRenderer.d.ts +3 -0
  12. package/dist/components/messages/renderers/CopyIntoRenderer.d.ts.map +1 -0
  13. package/dist/components/messages/renderers/DatabaseToolRenderer.d.ts.map +1 -1
  14. package/dist/components/messages/renderers/index.d.ts.map +1 -1
  15. package/dist/components/messages/renderers/types.d.ts +22 -0
  16. package/dist/components/messages/renderers/types.d.ts.map +1 -1
  17. package/dist/components/ui/CodeMirrorViewer.d.ts.map +1 -1
  18. package/dist/components/workspace/ViewerTabs.d.ts.map +1 -1
  19. package/dist/hooks/index.js +255 -118
  20. package/dist/hooks/index.js.map +7 -7
  21. package/dist/hooks/useMessages.d.ts.map +1 -1
  22. package/dist/hooks/useQueueState.d.ts +2 -0
  23. package/dist/hooks/useQueueState.d.ts.map +1 -1
  24. package/dist/hooks/useSessionStream.d.ts.map +1 -1
  25. package/dist/index.js +3189 -2648
  26. package/dist/index.js.map +20 -19
  27. package/dist/stores/index.js +87 -36
  28. package/dist/stores/index.js.map +3 -3
  29. package/dist/stores/viewerTabsStore.d.ts +4 -0
  30. package/dist/stores/viewerTabsStore.d.ts.map +1 -1
  31. package/package.json +3 -3
@@ -1239,8 +1239,16 @@ import { create } from "zustand";
1239
1239
  function titleFromPath(path) {
1240
1240
  return path.split("/").pop() || path;
1241
1241
  }
1242
+ function normalizeViewerPath(path) {
1243
+ return path.trim().replace(/^a\//, "").replace(/^b\//, "").replace(/^\.\//, "").replace(/\/+/g, "/").replace(/\/+$/, "");
1244
+ }
1245
+ function viewerPathsMatch(left, right) {
1246
+ const normalizedLeft = normalizeViewerPath(left);
1247
+ const normalizedRight = normalizeViewerPath(right);
1248
+ return normalizedLeft === normalizedRight || normalizedLeft.endsWith(`/${normalizedRight}`) || normalizedRight.endsWith(`/${normalizedLeft}`);
1249
+ }
1242
1250
  function fileTabId(path) {
1243
- return `file:${path}`;
1251
+ return `file:${normalizeViewerPath(path)}`;
1244
1252
  }
1245
1253
  function upsertTab(tabs, tab) {
1246
1254
  const existingIndex = tabs.findIndex((item) => item.id === tab.id);
@@ -1264,6 +1272,13 @@ function isSamePatchCall(existing, preview) {
1264
1272
  return existing.status === "streaming";
1265
1273
  return preview.patch === existing.patch || preview.patch.startsWith(existing.patch) || existing.patch.startsWith(preview.patch);
1266
1274
  }
1275
+ function mergeChangedLines(existing, incoming) {
1276
+ if (!existing?.length)
1277
+ return incoming;
1278
+ if (!incoming?.length)
1279
+ return existing;
1280
+ return [...new Set([...existing, ...incoming])].sort((a, b) => a - b);
1281
+ }
1267
1282
  var useViewerTabsStore = create((set) => ({
1268
1283
  tabs: [],
1269
1284
  activeTabId: null,
@@ -1300,55 +1315,73 @@ var useViewerTabsStore = create((set) => ({
1300
1315
  openFileTab: (path) => {
1301
1316
  const id = fileTabId(path);
1302
1317
  set((state) => {
1303
- const tabs = state.tabs.filter((tab) => !(tab.type === "tool-preview" && tab.path === path && tab.id !== id));
1318
+ const matchingFileTabs = state.tabs.filter((tab) => tab.type === "file" && viewerPathsMatch(tab.path, path));
1319
+ const existingFile = matchingFileTabs.find((tab) => tab.id === state.activeTabId) ?? matchingFileTabs[0];
1320
+ const targetId = existingFile?.id ?? id;
1321
+ const targetPath = existingFile?.path ?? path;
1322
+ const tabs = state.tabs.filter((tab) => !((tab.type === "tool-preview" || tab.type === "file") && tab.id !== targetId && viewerPathsMatch(tab.path, path)));
1304
1323
  return {
1305
1324
  tabs: upsertTab(tabs, {
1306
- id,
1325
+ id: targetId,
1307
1326
  type: "file",
1308
- title: titleFromPath(path),
1309
- path
1327
+ title: existingFile?.title ?? titleFromPath(targetPath),
1328
+ path: targetPath
1310
1329
  }),
1311
- activeTabId: id
1330
+ activeTabId: targetId
1312
1331
  };
1313
1332
  });
1314
1333
  },
1315
1334
  openToolReadTab: (path, highlight) => {
1316
1335
  const id = fileTabId(path);
1317
1336
  set((state) => {
1318
- const tabs = state.tabs.filter((tab) => !(tab.type === "tool-preview" && tab.path === path && tab.id !== id));
1337
+ const matchingFileTabs = state.tabs.filter((tab) => tab.type === "file" && viewerPathsMatch(tab.path, path));
1338
+ const existingFile = matchingFileTabs.find((tab) => tab.id === state.activeTabId) ?? matchingFileTabs[0];
1339
+ const targetId = existingFile?.id ?? id;
1340
+ const targetPath = existingFile?.path ?? path;
1341
+ const tabs = state.tabs.filter((tab) => !((tab.type === "tool-preview" || tab.type === "file") && tab.id !== targetId && viewerPathsMatch(tab.path, path)));
1319
1342
  return {
1320
1343
  tabs: upsertTab(tabs, {
1321
- id,
1344
+ id: targetId,
1322
1345
  type: "file",
1323
- title: titleFromPath(path),
1324
- path,
1325
- highlight
1346
+ title: existingFile?.title ?? titleFromPath(targetPath),
1347
+ path: targetPath,
1348
+ highlight,
1349
+ patchPreview: undefined,
1350
+ writePreview: undefined
1326
1351
  }),
1327
- activeTabId: id
1352
+ activeTabId: targetId
1328
1353
  };
1329
1354
  });
1330
1355
  },
1331
1356
  openToolPreviewTab: (preview) => {
1332
1357
  const id = fileTabId(preview.path);
1333
1358
  set((state) => {
1334
- const existingFile = state.tabs.find((tab) => tab.id === id && tab.type === "file");
1335
- const existing = state.tabs.find((tab) => tab.id === id && tab.type === "tool-preview");
1336
- const tabs = state.tabs.filter((tab) => !(tab.type === "tool-preview" && tab.id !== id && (tab.path === preview.path || Boolean(preview.callId && tab.callId === preview.callId))));
1337
- if (preview.toolName === "apply_patch" && existingFile) {
1338
- const existingPatchPreview = existingFile.patchPreview;
1359
+ const matchingFileTabs = state.tabs.filter((tab) => tab.type === "file" && viewerPathsMatch(tab.path, preview.path));
1360
+ const existingFile = matchingFileTabs.find((tab) => tab.id === state.activeTabId) ?? matchingFileTabs[0];
1361
+ const targetId = existingFile?.id ?? id;
1362
+ const targetPath = existingFile?.path ?? preview.path;
1363
+ const existing = state.tabs.find((tab) => tab.type === "tool-preview" && viewerPathsMatch(tab.path, preview.path));
1364
+ const tabs = state.tabs.filter((tab) => !((tab.type === "tool-preview" || tab.type === "file") && tab.id !== targetId && (viewerPathsMatch(tab.path, preview.path) || Boolean(preview.callId && "toolName" in tab && tab.callId === preview.callId))));
1365
+ if (preview.toolName === "apply_patch") {
1366
+ const existingPatchPreview = existingFile?.patchPreview ?? (existing?.toolName === "apply_patch" ? existing : undefined);
1339
1367
  const samePatchCall = isSamePatchCall(existingPatchPreview, preview);
1340
- const baseContent2 = preview.baseContent ?? (samePatchCall ? existingPatchPreview?.baseContent : existingPatchPreview?.resultContent ?? existingPatchPreview?.baseContent);
1368
+ const baseContent = preview.baseContent ?? (samePatchCall ? existingPatchPreview?.baseContent : existingPatchPreview?.resultContent ?? existingPatchPreview?.baseContent);
1369
+ const changedLines = samePatchCall ? preview.changedLines ?? existingPatchPreview?.changedLines : mergeChangedLines(existingPatchPreview?.changedLines, preview.changedLines);
1341
1370
  return {
1342
1371
  tabs: upsertTab(tabs, {
1343
- ...existingFile,
1372
+ id: targetId,
1373
+ type: "file",
1374
+ title: existingFile?.title ?? titleFromPath(targetPath),
1375
+ path: targetPath,
1344
1376
  highlight: undefined,
1377
+ writePreview: undefined,
1345
1378
  patchPreview: {
1346
- path: preview.path,
1379
+ path: targetPath,
1347
1380
  toolName: "apply_patch",
1348
1381
  callId: preview.callId ?? existingPatchPreview?.callId,
1349
- baseContent: baseContent2,
1382
+ baseContent,
1350
1383
  patch: preview.patch ?? existingPatchPreview?.patch,
1351
- changedLines: preview.changedLines ?? (samePatchCall ? existingPatchPreview?.changedLines : undefined),
1384
+ changedLines,
1352
1385
  previewContent: preview.previewContent ?? (samePatchCall ? existingPatchPreview?.previewContent : undefined),
1353
1386
  resultContent: preview.resultContent ?? (samePatchCall ? existingPatchPreview?.resultContent : undefined),
1354
1387
  previewLineTones: preview.previewLineTones ?? (samePatchCall ? existingPatchPreview?.previewLineTones : undefined),
@@ -1358,11 +1391,29 @@ var useViewerTabsStore = create((set) => ({
1358
1391
  error: preview.error ?? existingPatchPreview?.error
1359
1392
  }
1360
1393
  }),
1361
- activeTabId: id
1394
+ activeTabId: targetId
1362
1395
  };
1363
1396
  }
1364
- const sameToolPreviewCall = isSamePatchCall(existing, preview);
1365
- const baseContent = preview.toolName === "apply_patch" ? preview.baseContent ?? (sameToolPreviewCall ? existing?.baseContent : existing?.resultContent ?? existing?.baseContent) : undefined;
1397
+ if (existingFile) {
1398
+ const existingWritePreview = existingFile.writePreview;
1399
+ return {
1400
+ tabs: upsertTab(tabs, {
1401
+ ...existingFile,
1402
+ highlight: undefined,
1403
+ patchPreview: undefined,
1404
+ writePreview: {
1405
+ path: targetPath,
1406
+ toolName: "write",
1407
+ callId: preview.callId ?? existingWritePreview?.callId,
1408
+ content: preview.content ?? existingWritePreview?.content,
1409
+ status: preview.status,
1410
+ error: preview.error ?? existingWritePreview?.error
1411
+ }
1412
+ }),
1413
+ activeTabId: targetId
1414
+ };
1415
+ }
1416
+ const existingWrite = existing?.toolName === "write" ? existing : undefined;
1366
1417
  return {
1367
1418
  tabs: upsertTab(tabs, {
1368
1419
  id,
@@ -1371,17 +1422,17 @@ var useViewerTabsStore = create((set) => ({
1371
1422
  path: preview.path,
1372
1423
  toolName: preview.toolName,
1373
1424
  callId: preview.callId,
1374
- content: preview.content ?? existing?.content,
1375
- baseContent,
1376
- patch: preview.patch ?? existing?.patch,
1377
- changedLines: preview.changedLines ?? existing?.changedLines,
1378
- previewContent: preview.previewContent ?? (sameToolPreviewCall ? existing?.previewContent : undefined),
1379
- resultContent: preview.resultContent ?? (sameToolPreviewCall ? existing?.resultContent : undefined),
1380
- previewLineTones: preview.previewLineTones ?? (sameToolPreviewCall ? existing?.previewLineTones : undefined),
1381
- previewFirstLine: preview.previewFirstLine ?? (sameToolPreviewCall ? existing?.previewFirstLine : undefined),
1382
- previewLatestLine: preview.previewLatestLine ?? (sameToolPreviewCall ? existing?.previewLatestLine : undefined),
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,
1383
1434
  status: preview.status,
1384
- error: preview.error ?? existing?.error
1435
+ error: preview.error ?? existingWrite?.error
1385
1436
  }),
1386
1437
  activeTabId: id
1387
1438
  };
@@ -1977,7 +2028,71 @@ function useRemoveRemote() {
1977
2028
  });
1978
2029
  }
1979
2030
  // src/hooks/useMessages.ts
1980
- import { useQuery as useQuery4, useMutation as useMutation4, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
2031
+ import { useQuery as useQuery5, useMutation as useMutation4, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
2032
+
2033
+ // src/hooks/useQueueState.ts
2034
+ import { useQuery as useQuery4 } from "@tanstack/react-query";
2035
+ var defaultQueueState = {
2036
+ currentMessageId: null,
2037
+ queuedMessages: [],
2038
+ queueLength: 0
2039
+ };
2040
+ function optimisticallyQueueMessage(queryClient, sessionId, messageId) {
2041
+ queryClient.setQueryData(["queueState", sessionId], (current) => {
2042
+ if (!current)
2043
+ return current;
2044
+ const isBusy = Boolean(current.currentMessageId) || current.queuedMessages.length > 0 || current.queueLength > 0;
2045
+ if (!isBusy)
2046
+ return current;
2047
+ if (current.currentMessageId === messageId)
2048
+ return current;
2049
+ if (current.queuedMessages.some((item) => item.messageId === messageId)) {
2050
+ return current;
2051
+ }
2052
+ const queuedMessages = [
2053
+ ...current.queuedMessages,
2054
+ { messageId, position: current.queuedMessages.length }
2055
+ ];
2056
+ return {
2057
+ ...current,
2058
+ queuedMessages,
2059
+ queueLength: queuedMessages.length
2060
+ };
2061
+ });
2062
+ }
2063
+ function useQueueState(sessionId) {
2064
+ const { data } = useQuery4({
2065
+ queryKey: ["queueState", sessionId],
2066
+ queryFn: async () => {
2067
+ if (!sessionId)
2068
+ return defaultQueueState;
2069
+ const queueState = await apiClient.getQueueState(sessionId);
2070
+ return {
2071
+ currentMessageId: queueState.currentMessageId,
2072
+ queuedMessages: queueState.queuedMessages,
2073
+ queueLength: queueState.queuedMessages.length
2074
+ };
2075
+ },
2076
+ enabled: !!sessionId,
2077
+ placeholderData: defaultQueueState,
2078
+ staleTime: Infinity
2079
+ });
2080
+ return data ?? defaultQueueState;
2081
+ }
2082
+ function useMessageQueuePosition(sessionId, messageId) {
2083
+ const queueState = useQueueState(sessionId);
2084
+ if (!sessionId || !queueState) {
2085
+ return { isQueued: false, isRunning: false, position: null };
2086
+ }
2087
+ if (queueState.currentMessageId === messageId) {
2088
+ return { isQueued: false, isRunning: true, position: null };
2089
+ }
2090
+ const queuedItem = queueState.queuedMessages.find((item) => item.messageId === messageId);
2091
+ if (queuedItem) {
2092
+ return { isQueued: true, isRunning: false, position: queuedItem.position };
2093
+ }
2094
+ return { isQueued: false, isRunning: false, position: null };
2095
+ }
1981
2096
 
1982
2097
  // src/hooks/useSessions.ts
1983
2098
  import {
@@ -2088,7 +2203,7 @@ function useDeleteSession() {
2088
2203
  // src/hooks/useMessages.ts
2089
2204
  function useMessages(sessionId, options = {}) {
2090
2205
  const { enabled = true, staleTime = 15000 } = options;
2091
- return useQuery4({
2206
+ return useQuery5({
2092
2207
  queryKey: ["messages", sessionId],
2093
2208
  queryFn: () => {
2094
2209
  if (!sessionId) {
@@ -2110,7 +2225,8 @@ function useSendMessage(sessionId) {
2110
2225
  });
2111
2226
  return apiClient.sendMessage(sessionId, data);
2112
2227
  },
2113
- onSuccess: () => {
2228
+ onSuccess: (result) => {
2229
+ optimisticallyQueueMessage(queryClient, sessionId, result.messageId);
2114
2230
  queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
2115
2231
  queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
2116
2232
  }
@@ -2303,7 +2419,7 @@ function useSessionStream(sessionId, enabled = true) {
2303
2419
  }
2304
2420
  for (let i = messages.length - 1;i >= 0; i -= 1) {
2305
2421
  const candidate = messages[i];
2306
- if (candidate.role === "assistant" && candidate.status !== "complete") {
2422
+ if (candidate.role === "assistant" && candidate.status === "pending") {
2307
2423
  return i;
2308
2424
  }
2309
2425
  }
@@ -2516,6 +2632,37 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2516
2632
  const normalizedTarget = normalizePatchPath(targetPath);
2517
2633
  return normalizedPatch === normalizedTarget || normalizedPatch.endsWith(`/${normalizedTarget}`) || normalizedTarget.endsWith(`/${normalizedPatch}`);
2518
2634
  };
2635
+ const patchPathMayReferToTarget = (patchPath, targetPath) => {
2636
+ const normalizedPatch = normalizePatchPath(patchPath).replace(/\/+$/, "");
2637
+ const normalizedTarget = normalizePatchPath(targetPath);
2638
+ if (!normalizedPatch)
2639
+ return false;
2640
+ return patchPathMatches(patchPath, targetPath) || normalizedTarget.startsWith(`${normalizedPatch}/`);
2641
+ };
2642
+ const isLikelyCompletePatchPath = (path) => {
2643
+ const normalized = normalizePatchPath(path);
2644
+ if (!normalized || normalized.endsWith("/"))
2645
+ return false;
2646
+ const name = normalized.split("/").pop() ?? "";
2647
+ return name.includes(".");
2648
+ };
2649
+ const getCompletedPatchChangeLineSignature = (patch) => {
2650
+ const stablePatch = patch.endsWith(`
2651
+ `) ? patch : patch.slice(0, patch.lastIndexOf(`
2652
+ `) + 1);
2653
+ if (!stablePatch)
2654
+ return;
2655
+ let changeLines = 0;
2656
+ let stableChangeLength = 0;
2657
+ for (const line of stablePatch.split(`
2658
+ `)) {
2659
+ if (line.startsWith("+") && !line.startsWith("+++") || line.startsWith("-") && !line.startsWith("---")) {
2660
+ changeLines += 1;
2661
+ stableChangeLength += line.length;
2662
+ }
2663
+ }
2664
+ return changeLines > 0 ? `${changeLines}:${stableChangeLength}` : undefined;
2665
+ };
2519
2666
  const extractPathsFromPatch = (patch) => {
2520
2667
  const paths = new Set;
2521
2668
  for (const line of patch.split(`
@@ -2637,31 +2784,41 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2637
2784
  const failed = result?.ok === false || eventType === "error";
2638
2785
  const status = failed ? "error" : eventType === "tool.result" ? "success" : "streaming";
2639
2786
  const callId = getToolEventCallId(payload) ?? undefined;
2640
- if (status === "streaming" && buffer.length >= TOOL_PREVIEW_THROTTLE_MIN_CHARS) {
2641
- const previewKey = callId ?? "apply_patch";
2642
- const now = Date.now();
2787
+ const patch = (typeof artifact?.patch === "string" ? artifact.patch : undefined) ?? (status === "streaming" ? getStreamingPatchPreviewContent(args, buffer) : getStringArg(args, buffer, "patch"));
2788
+ if (!patch)
2789
+ return;
2790
+ const previewKey = callId ?? "apply_patch";
2791
+ const lineSignature = status === "streaming" ? getCompletedPatchChangeLineSignature(patch) : undefined;
2792
+ if (status === "streaming") {
2643
2793
  const last = toolPreviewEmitRef.current.get(previewKey);
2644
- const contentDelta = Math.abs(buffer.length - (last?.contentLength ?? 0));
2645
- if (last && now - last.emittedAt < TOOL_PREVIEW_THROTTLE_MS && contentDelta < TOOL_PREVIEW_THROTTLE_MIN_DELTA_CHARS) {
2794
+ if (last?.lineSignature === lineSignature)
2646
2795
  return;
2647
- }
2648
- toolPreviewEmitRef.current.set(previewKey, {
2649
- emittedAt: now,
2650
- contentLength: buffer.length
2651
- });
2652
2796
  }
2653
- const patch = (typeof artifact?.patch === "string" ? artifact.patch : undefined) ?? (status === "streaming" ? getStreamingPatchPreviewContent(args, buffer) : getStringArg(args, buffer, "patch"));
2654
- if (!patch)
2797
+ const patchPaths = extractPathsFromPatch(patch);
2798
+ if (patchPaths.length === 0)
2799
+ return;
2800
+ const matchingFileTabs = viewerStore.tabs.filter((tab) => tab.type === "file" && patchPaths.some((path) => patchPathMayReferToTarget(path, tab.path)));
2801
+ const activeMatchingFileTab = matchingFileTabs.find((tab) => tab.id === viewerStore.activeTabId);
2802
+ const fallbackPath = patchPaths.find(isLikelyCompletePatchPath);
2803
+ if (!activeMatchingFileTab && !matchingFileTabs[0] && !fallbackPath)
2804
+ return;
2805
+ const targetPath = activeMatchingFileTab?.path ?? matchingFileTabs[0]?.path ?? fallbackPath;
2806
+ if (!targetPath)
2655
2807
  return;
2656
- for (const path of extractPathsFromPatch(patch)) {
2657
- viewerStore.openToolPreviewTab({
2658
- path,
2659
- toolName: "apply_patch",
2660
- callId,
2661
- patch,
2662
- changedLines: getChangedLinesForPath(result, path),
2663
- status,
2664
- error: extractErrorMessage2(payload)
2808
+ viewerStore.openToolPreviewTab({
2809
+ path: targetPath,
2810
+ toolName: "apply_patch",
2811
+ callId,
2812
+ patch,
2813
+ changedLines: getChangedLinesForPath(result, targetPath),
2814
+ status,
2815
+ error: extractErrorMessage2(payload)
2816
+ });
2817
+ if (status === "streaming") {
2818
+ toolPreviewEmitRef.current.set(previewKey, {
2819
+ emittedAt: Date.now(),
2820
+ contentLength: buffer.length,
2821
+ lineSignature
2665
2822
  });
2666
2823
  }
2667
2824
  };
@@ -2685,19 +2842,9 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
2685
2842
  return payload.delta;
2686
2843
  return typeof payload?.outputTextDelta === "string" ? payload.outputTextDelta : null;
2687
2844
  };
2688
- const getOptimisticPartIndex = (parts, stepIndex) => {
2689
- const appendIndex = (() => {
2690
- const indexes = parts.map((part) => part.index).filter((index) => Number.isFinite(index));
2691
- return indexes.length > 0 ? Math.max(...indexes) + 0.001 : 0;
2692
- })();
2693
- if (typeof stepIndex !== "number") {
2694
- return appendIndex;
2695
- }
2696
- const sameStepIndexes = parts.filter((part) => part.stepIndex === stepIndex).map((part) => part.index).filter((index) => Number.isFinite(index));
2697
- if (sameStepIndexes.length > 0) {
2698
- return Math.max(...sameStepIndexes) + 0.001;
2699
- }
2700
- return appendIndex;
2845
+ const getOptimisticPartIndex = (parts, _stepIndex) => {
2846
+ const indexes = parts.map((part) => part.index).filter((index) => Number.isFinite(index));
2847
+ return indexes.length > 0 ? Math.max(...indexes) + 0.001 : 0;
2701
2848
  };
2702
2849
  const applyReasoningDelta = (payload) => {
2703
2850
  const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
@@ -3164,6 +3311,7 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3164
3311
  const id = typeof payload?.id === "string" ? payload.id : null;
3165
3312
  if (role === "assistant" && id) {
3166
3313
  assistantMessageIdRef.current = id;
3314
+ optimisticallyQueueMessage(queryClient, sessionId, id);
3167
3315
  }
3168
3316
  if (id && role) {
3169
3317
  const agent = typeof payload?.agent === "string" ? payload.agent : "";
@@ -3302,7 +3450,26 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3302
3450
  removeEphemeralToolCall(payload);
3303
3451
  const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
3304
3452
  if (messageId) {
3453
+ if (assistantMessageIdRef.current === messageId) {
3454
+ assistantMessageIdRef.current = null;
3455
+ }
3305
3456
  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
+ });
3306
3473
  }
3307
3474
  queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
3308
3475
  break;
@@ -3311,6 +3478,13 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3311
3478
  const id = typeof payload?.id === "string" ? payload.id : null;
3312
3479
  const status = typeof payload?.status === "string" ? payload.status : null;
3313
3480
  if (id && status) {
3481
+ if (status !== "pending" && assistantMessageIdRef.current === id) {
3482
+ assistantMessageIdRef.current = null;
3483
+ }
3484
+ if (status !== "pending") {
3485
+ clearEphemeralForMessage(id);
3486
+ }
3487
+ const error = typeof payload?.error === "string" ? payload.error : undefined;
3314
3488
  queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
3315
3489
  if (!oldMessages)
3316
3490
  return oldMessages;
@@ -3320,7 +3494,9 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
3320
3494
  const next = [...oldMessages];
3321
3495
  next[idx] = {
3322
3496
  ...next[idx],
3323
- status
3497
+ status,
3498
+ completedAt: status === "pending" ? next[idx].completedAt : next[idx].completedAt ?? Date.now(),
3499
+ error: error ?? next[idx].error
3324
3500
  };
3325
3501
  return next;
3326
3502
  });
@@ -4666,10 +4842,10 @@ function useFileUpload(options = {}) {
4666
4842
  };
4667
4843
  }
4668
4844
  // src/hooks/useSessionFiles.ts
4669
- import { useQuery as useQuery5 } from "@tanstack/react-query";
4845
+ import { useQuery as useQuery6 } from "@tanstack/react-query";
4670
4846
  function useSessionFiles(sessionId, enabled = true) {
4671
4847
  const isExpanded = useSessionFilesStore((state) => state.isExpanded);
4672
- return useQuery5({
4848
+ return useQuery6({
4673
4849
  queryKey: ["session", sessionId, "files"],
4674
4850
  queryFn: () => sessionId ? apiClient.getSessionFiles(sessionId) : null,
4675
4851
  enabled: !!sessionId && enabled,
@@ -4678,46 +4854,6 @@ function useSessionFiles(sessionId, enabled = true) {
4678
4854
  staleTime: 3000
4679
4855
  });
4680
4856
  }
4681
- // src/hooks/useQueueState.ts
4682
- import { useQuery as useQuery6 } from "@tanstack/react-query";
4683
- var defaultQueueState = {
4684
- currentMessageId: null,
4685
- queuedMessages: [],
4686
- queueLength: 0
4687
- };
4688
- function useQueueState(sessionId) {
4689
- const { data } = useQuery6({
4690
- queryKey: ["queueState", sessionId],
4691
- queryFn: async () => {
4692
- if (!sessionId)
4693
- return defaultQueueState;
4694
- const queueState = await apiClient.getQueueState(sessionId);
4695
- return {
4696
- currentMessageId: queueState.currentMessageId,
4697
- queuedMessages: queueState.queuedMessages,
4698
- queueLength: queueState.queuedMessages.length
4699
- };
4700
- },
4701
- enabled: !!sessionId,
4702
- placeholderData: defaultQueueState,
4703
- staleTime: Infinity
4704
- });
4705
- return data ?? defaultQueueState;
4706
- }
4707
- function useMessageQueuePosition(sessionId, messageId) {
4708
- const queueState = useQueueState(sessionId);
4709
- if (!sessionId || !queueState) {
4710
- return { isQueued: false, isRunning: false, position: null };
4711
- }
4712
- if (queueState.currentMessageId === messageId) {
4713
- return { isQueued: false, isRunning: true, position: null };
4714
- }
4715
- const queuedItem = queueState.queuedMessages.find((item) => item.messageId === messageId);
4716
- if (queuedItem) {
4717
- return { isQueued: true, isRunning: false, position: queuedItem.position };
4718
- }
4719
- return { isQueued: false, isRunning: false, position: null };
4720
- }
4721
4857
  // src/hooks/useBranch.ts
4722
4858
  import { useQuery as useQuery7, useMutation as useMutation5, useQueryClient as useQueryClient7 } from "@tanstack/react-query";
4723
4859
  function useCreateBranch(sessionId) {
@@ -6299,7 +6435,8 @@ export {
6299
6435
  useAllModels,
6300
6436
  useAddRemote,
6301
6437
  useAddMCPServer,
6302
- sessionsQueryKey
6438
+ sessionsQueryKey,
6439
+ optimisticallyQueueMessage
6303
6440
  };
6304
6441
 
6305
- //# debugId=88EDF96A27BB9BCF64756E2164756E21
6442
+ //# debugId=90296322954CBDC864756E2164756E21