@ottocode/web-sdk 0.1.272 → 0.1.273

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 (88) hide show
  1. package/dist/components/branch/BranchModal.d.ts.map +1 -1
  2. package/dist/components/chat/ChatInputContainer.d.ts +1 -0
  3. package/dist/components/chat/ChatInputContainer.d.ts.map +1 -1
  4. package/dist/components/chat/ConfigSelector.d.ts.map +1 -1
  5. package/dist/components/file-browser/FileBrowserSidebar.d.ts.map +1 -1
  6. package/dist/components/file-browser/FileViewerPanel.d.ts +3 -0
  7. package/dist/components/file-browser/FileViewerPanel.d.ts.map +1 -1
  8. package/dist/components/git/GitCommitModal.d.ts.map +1 -1
  9. package/dist/components/git/GitDiffViewer.d.ts.map +1 -1
  10. package/dist/components/git/GitFileList.d.ts.map +1 -1
  11. package/dist/components/git/GitSidebar.d.ts.map +1 -1
  12. package/dist/components/index.d.ts +1 -0
  13. package/dist/components/index.d.ts.map +1 -1
  14. package/dist/components/index.js +5000 -3678
  15. package/dist/components/index.js.map +47 -43
  16. package/dist/components/mcp/AddMCPServerModal.d.ts.map +1 -1
  17. package/dist/components/mcp/MCPSidebar.d.ts.map +1 -1
  18. package/dist/components/messages/ActionToolBox.d.ts.map +1 -1
  19. package/dist/components/messages/MessagePartItem.d.ts.map +1 -1
  20. package/dist/components/messages/TopupApprovalCard.d.ts.map +1 -1
  21. package/dist/components/onboarding/steps/DefaultsStep.d.ts.map +1 -1
  22. package/dist/components/onboarding/steps/ProviderSetupStep.d.ts.map +1 -1
  23. package/dist/components/session-files/SessionFilesDiffPanel.d.ts.map +1 -1
  24. package/dist/components/session-files/SessionFilesSidebar.d.ts.map +1 -1
  25. package/dist/components/sessions/LeanHeader.d.ts.map +1 -1
  26. package/dist/components/sessions/SessionHeader.d.ts.map +1 -1
  27. package/dist/components/sessions/SessionItem.d.ts.map +1 -1
  28. package/dist/components/sessions/SessionListContainer.d.ts.map +1 -1
  29. package/dist/components/settings/OttoRouterTopupModal.d.ts.map +1 -1
  30. package/dist/components/skills/SkillViewerPanel.d.ts.map +1 -1
  31. package/dist/components/skills/SkillsSidebar.d.ts.map +1 -1
  32. package/dist/components/terminals/TerminalViewer.d.ts.map +1 -1
  33. package/dist/components/tunnel/TunnelSidebar.d.ts.map +1 -1
  34. package/dist/components/ui/CodeMirrorViewer.d.ts +14 -0
  35. package/dist/components/ui/CodeMirrorViewer.d.ts.map +1 -0
  36. package/dist/components/ui/StableSpinner.d.ts +15 -0
  37. package/dist/components/ui/StableSpinner.d.ts.map +1 -0
  38. package/dist/components/ui/Toaster.d.ts.map +1 -1
  39. package/dist/components/workspace/ToolActivityToggle.d.ts +6 -0
  40. package/dist/components/workspace/ToolActivityToggle.d.ts.map +1 -0
  41. package/dist/components/workspace/ToolPreviewPanel.d.ts +17 -0
  42. package/dist/components/workspace/ToolPreviewPanel.d.ts.map +1 -0
  43. package/dist/components/workspace/ViewerTabs.d.ts.map +1 -1
  44. package/dist/hooks/index.js +522 -17
  45. package/dist/hooks/index.js.map +10 -10
  46. package/dist/hooks/useBranch.d.ts +2 -2
  47. package/dist/hooks/useBranch.d.ts.map +1 -1
  48. package/dist/hooks/useClientEvents.d.ts.map +1 -1
  49. package/dist/hooks/useConfig.d.ts +5 -5
  50. package/dist/hooks/useConfig.d.ts.map +1 -1
  51. package/dist/hooks/useFileBrowser.d.ts +5 -5
  52. package/dist/hooks/useFileBrowser.d.ts.map +1 -1
  53. package/dist/hooks/useFiles.d.ts +2 -2
  54. package/dist/hooks/useFiles.d.ts.map +1 -1
  55. package/dist/hooks/useGit.d.ts +5 -4
  56. package/dist/hooks/useGit.d.ts.map +1 -1
  57. package/dist/hooks/useMCP.d.ts +3 -3
  58. package/dist/hooks/useMCP.d.ts.map +1 -1
  59. package/dist/hooks/useMessages.d.ts +1 -1
  60. package/dist/hooks/useMessages.d.ts.map +1 -1
  61. package/dist/hooks/useResearch.d.ts +1 -1
  62. package/dist/hooks/useResearch.d.ts.map +1 -1
  63. package/dist/hooks/useSessionFiles.d.ts +1 -1
  64. package/dist/hooks/useSessionFiles.d.ts.map +1 -1
  65. package/dist/hooks/useSessionStream.d.ts.map +1 -1
  66. package/dist/hooks/useSkills.d.ts +6 -6
  67. package/dist/hooks/useSkills.d.ts.map +1 -1
  68. package/dist/hooks/useTerminals.d.ts +2 -2
  69. package/dist/hooks/useTerminals.d.ts.map +1 -1
  70. package/dist/hooks/useTunnel.d.ts +2 -2
  71. package/dist/hooks/useTunnel.d.ts.map +1 -1
  72. package/dist/index.js +5026 -3699
  73. package/dist/index.js.map +49 -45
  74. package/dist/lib/api-client/git.d.ts +2 -1
  75. package/dist/lib/api-client/git.d.ts.map +1 -1
  76. package/dist/lib/api-client/index.d.ts +1 -0
  77. package/dist/lib/api-client/index.d.ts.map +1 -1
  78. package/dist/lib/index.js +21 -2
  79. package/dist/lib/index.js.map +5 -5
  80. package/dist/lib/platform.d.ts +1 -0
  81. package/dist/lib/platform.d.ts.map +1 -1
  82. package/dist/stores/index.js +108 -11
  83. package/dist/stores/index.js.map +3 -3
  84. package/dist/stores/viewerTabsStore.d.ts +51 -0
  85. package/dist/stores/viewerTabsStore.d.ts.map +1 -1
  86. package/dist/types/api.d.ts +16 -0
  87. package/dist/types/api.d.ts.map +1 -1
  88. package/package.json +17 -3
@@ -228,6 +228,7 @@ import {
228
228
  generateCommitMessage as apiGenerateCommitMessage,
229
229
  pushCommits as apiPushCommits,
230
230
  pullChanges as apiPullChanges,
231
+ performGitRebaseAction as apiPerformGitRebaseAction,
231
232
  initGitRepo as apiInitGitRepo,
232
233
  getGitRemotes as apiGetGitRemotes,
233
234
  addGitRemote as apiAddGitRemote,
@@ -235,7 +236,9 @@ import {
235
236
  } from "@ottocode/api";
236
237
  var gitMixin = {
237
238
  async initGitRepo() {
238
- const response = await apiInitGitRepo();
239
+ const response = await apiInitGitRepo({
240
+ body: {}
241
+ });
239
242
  if (response.error)
240
243
  throw new Error(extractErrorMessage(response.error));
241
244
  return response.data?.data;
@@ -336,6 +339,14 @@ var gitMixin = {
336
339
  throw new Error(extractErrorMessage(response.error));
337
340
  return response.data?.data;
338
341
  },
342
+ async performRebaseAction(action) {
343
+ const response = await apiPerformGitRebaseAction({
344
+ body: { action }
345
+ });
346
+ if (response.error)
347
+ throw new Error(extractErrorMessage(response.error));
348
+ return response.data?.data;
349
+ },
339
350
  async getRemotes() {
340
351
  const response = await apiGetGitRemotes();
341
352
  if (response.error)
@@ -903,6 +914,7 @@ class ApiClient {
903
914
  getGitBranch = gitMixin.getGitBranch;
904
915
  pushCommits = gitMixin.pushCommits;
905
916
  pullChanges = gitMixin.pullChanges;
917
+ performRebaseAction = gitMixin.performRebaseAction;
906
918
  getRemotes = gitMixin.getRemotes;
907
919
  addRemote = gitMixin.addRemote;
908
920
  removeRemote = gitMixin.removeRemote;
@@ -1057,6 +1069,12 @@ function openPlatformSession(sessionId) {
1057
1069
  win.OTTO_OPEN_SESSION(sessionId);
1058
1070
  return true;
1059
1071
  }
1072
+ function getPlatformWindowFocused() {
1073
+ const win = getPlatformWindow();
1074
+ if (!win?.OTTO_IS_WINDOW_FOCUSED)
1075
+ return null;
1076
+ return win.OTTO_IS_WINDOW_FOCUSED();
1077
+ }
1060
1078
  function hasPlatformOpenUrl() {
1061
1079
  return !!getPlatformWindow()?.OTTO_OPEN_URL;
1062
1080
  }
@@ -1221,6 +1239,9 @@ import { create } from "zustand";
1221
1239
  function titleFromPath(path) {
1222
1240
  return path.split("/").pop() || path;
1223
1241
  }
1242
+ function fileTabId(path) {
1243
+ return `file:${path}`;
1244
+ }
1224
1245
  function upsertTab(tabs, tab) {
1225
1246
  const existingIndex = tabs.findIndex((item) => item.id === tab.id);
1226
1247
  if (existingIndex === -1) {
@@ -1230,9 +1251,25 @@ function upsertTab(tabs, tab) {
1230
1251
  next[existingIndex] = tab;
1231
1252
  return next;
1232
1253
  }
1254
+ function isSamePatchCall(existing, preview) {
1255
+ if (!existing)
1256
+ return false;
1257
+ if (preview.callId || existing.callId) {
1258
+ return Boolean(preview.callId && existing.callId === preview.callId);
1259
+ }
1260
+ if (preview.status === "streaming" && existing.status !== "streaming") {
1261
+ return false;
1262
+ }
1263
+ if (!preview.patch || !existing.patch)
1264
+ return existing.status === "streaming";
1265
+ return preview.patch === existing.patch || preview.patch.startsWith(existing.patch) || existing.patch.startsWith(preview.patch);
1266
+ }
1233
1267
  var useViewerTabsStore = create((set) => ({
1234
1268
  tabs: [],
1235
1269
  activeTabId: null,
1270
+ followToolActivity: false,
1271
+ toggleFollowToolActivity: () => set((state) => ({ followToolActivity: !state.followToolActivity })),
1272
+ setFollowToolActivity: (enabled) => set({ followToolActivity: enabled }),
1236
1273
  openGitDiffTab: (path, staged) => {
1237
1274
  const id = `git-diff:${staged ? "staged" : "unstaged"}:${path}`;
1238
1275
  set((state) => ({
@@ -1261,16 +1298,94 @@ var useViewerTabsStore = create((set) => ({
1261
1298
  }));
1262
1299
  },
1263
1300
  openFileTab: (path) => {
1264
- const id = `file:${path}`;
1265
- set((state) => ({
1266
- tabs: upsertTab(state.tabs, {
1267
- id,
1268
- type: "file",
1269
- title: titleFromPath(path),
1270
- path
1271
- }),
1272
- activeTabId: id
1273
- }));
1301
+ const id = fileTabId(path);
1302
+ set((state) => {
1303
+ const tabs = state.tabs.filter((tab) => !(tab.type === "tool-preview" && tab.path === path && tab.id !== id));
1304
+ return {
1305
+ tabs: upsertTab(tabs, {
1306
+ id,
1307
+ type: "file",
1308
+ title: titleFromPath(path),
1309
+ path
1310
+ }),
1311
+ activeTabId: id
1312
+ };
1313
+ });
1314
+ },
1315
+ openToolReadTab: (path, highlight) => {
1316
+ const id = fileTabId(path);
1317
+ set((state) => {
1318
+ const tabs = state.tabs.filter((tab) => !(tab.type === "tool-preview" && tab.path === path && tab.id !== id));
1319
+ return {
1320
+ tabs: upsertTab(tabs, {
1321
+ id,
1322
+ type: "file",
1323
+ title: titleFromPath(path),
1324
+ path,
1325
+ highlight
1326
+ }),
1327
+ activeTabId: id
1328
+ };
1329
+ });
1330
+ },
1331
+ openToolPreviewTab: (preview) => {
1332
+ const id = fileTabId(preview.path);
1333
+ 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;
1339
+ const samePatchCall = isSamePatchCall(existingPatchPreview, preview);
1340
+ const baseContent2 = preview.baseContent ?? (samePatchCall ? existingPatchPreview?.baseContent : existingPatchPreview?.resultContent ?? existingPatchPreview?.baseContent);
1341
+ return {
1342
+ tabs: upsertTab(tabs, {
1343
+ ...existingFile,
1344
+ highlight: undefined,
1345
+ patchPreview: {
1346
+ path: preview.path,
1347
+ toolName: "apply_patch",
1348
+ callId: preview.callId ?? existingPatchPreview?.callId,
1349
+ baseContent: baseContent2,
1350
+ patch: preview.patch ?? existingPatchPreview?.patch,
1351
+ changedLines: preview.changedLines ?? (samePatchCall ? existingPatchPreview?.changedLines : undefined),
1352
+ previewContent: preview.previewContent ?? (samePatchCall ? existingPatchPreview?.previewContent : undefined),
1353
+ resultContent: preview.resultContent ?? (samePatchCall ? existingPatchPreview?.resultContent : undefined),
1354
+ previewLineTones: preview.previewLineTones ?? (samePatchCall ? existingPatchPreview?.previewLineTones : undefined),
1355
+ previewFirstLine: preview.previewFirstLine ?? (samePatchCall ? existingPatchPreview?.previewFirstLine : undefined),
1356
+ previewLatestLine: preview.previewLatestLine ?? (samePatchCall ? existingPatchPreview?.previewLatestLine : undefined),
1357
+ status: preview.status,
1358
+ error: preview.error ?? existingPatchPreview?.error
1359
+ }
1360
+ }),
1361
+ activeTabId: id
1362
+ };
1363
+ }
1364
+ const sameToolPreviewCall = isSamePatchCall(existing, preview);
1365
+ const baseContent = preview.toolName === "apply_patch" ? preview.baseContent ?? (sameToolPreviewCall ? existing?.baseContent : existing?.resultContent ?? existing?.baseContent) : undefined;
1366
+ return {
1367
+ tabs: upsertTab(tabs, {
1368
+ id,
1369
+ type: "tool-preview",
1370
+ title: titleFromPath(preview.path),
1371
+ path: preview.path,
1372
+ toolName: preview.toolName,
1373
+ 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),
1383
+ status: preview.status,
1384
+ error: preview.error ?? existing?.error
1385
+ }),
1386
+ activeTabId: id
1387
+ };
1388
+ });
1274
1389
  },
1275
1390
  openSkillFileTab: (skill, file) => {
1276
1391
  const displayFile = file ?? "SKILL.md";
@@ -1811,6 +1926,16 @@ function usePullChanges() {
1811
1926
  }
1812
1927
  });
1813
1928
  }
1929
+ function useGitRebaseAction() {
1930
+ const queryClient = useQueryClient2();
1931
+ return useMutation2({
1932
+ mutationFn: (action) => apiClient.performRebaseAction(action),
1933
+ onSuccess: () => {
1934
+ queryClient.invalidateQueries({ queryKey: ["git", "status"] });
1935
+ queryClient.invalidateQueries({ queryKey: ["git", "branch"] });
1936
+ }
1937
+ });
1938
+ }
1814
1939
  function useGitInit() {
1815
1940
  const queryClient = useQueryClient2();
1816
1941
  return useMutation2({
@@ -2125,10 +2250,22 @@ var useToolApprovalStore = create10((set) => ({
2125
2250
  }));
2126
2251
 
2127
2252
  // src/hooks/useSessionStream.ts
2253
+ var TOOL_PREVIEW_THROTTLE_MS = 500;
2254
+ var TOOL_PREVIEW_THROTTLE_MIN_CHARS = 8000;
2255
+ var TOOL_PREVIEW_THROTTLE_MIN_DELTA_CHARS = 16000;
2256
+ var STREAMING_WRITE_CONTENT_PREVIEW_CHARS = 24000;
2257
+ var STREAMING_PATCH_PREVIEW_HEAD_CHARS = 12000;
2258
+ var STREAMING_PATCH_PREVIEW_TAIL_CHARS = 24000;
2259
+ var STREAMING_TOOL_INPUT_HEAD_CHARS = 8000;
2260
+ var STREAMING_TOOL_INPUT_TAIL_CHARS = 16000;
2261
+ var STREAMING_TOOL_MESSAGE_THROTTLE_MS = 500;
2128
2262
  function useSessionStream(sessionId, enabled = true) {
2129
2263
  const queryClient = useQueryClient5();
2130
2264
  const clientRef = useRef(null);
2131
2265
  const assistantMessageIdRef = useRef(null);
2266
+ const toolInputBuffersRef = useRef(new Map);
2267
+ const toolPreviewEmitRef = useRef(new Map);
2268
+ const toolMessageEmitRef = useRef(new Map);
2132
2269
  const {
2133
2270
  addPendingApproval,
2134
2271
  removePendingApproval,
@@ -2140,6 +2277,9 @@ function useSessionStream(sessionId, enabled = true) {
2140
2277
  return;
2141
2278
  }
2142
2279
  assistantMessageIdRef.current = null;
2280
+ toolInputBuffersRef.current.clear();
2281
+ toolPreviewEmitRef.current.clear();
2282
+ toolMessageEmitRef.current.clear();
2143
2283
  let lastSessionInvalidation = 0;
2144
2284
  apiClient.getPendingApprovals(sessionId).then((result) => {
2145
2285
  if (result.ok && result.pending.length > 0) {
@@ -2194,6 +2334,347 @@ function useSessionStream(sessionId, enabled = true) {
2194
2334
  return typeof payload?.toolName === "string" ? payload.toolName : null;
2195
2335
  };
2196
2336
  const getToolEventArgs = (payload) => payload?.args ?? payload?.input;
2337
+ const getToolBufferKey = (payload) => {
2338
+ const callId = getToolEventCallId(payload);
2339
+ if (callId)
2340
+ return callId;
2341
+ const name = getToolEventName(payload);
2342
+ return name ? `name:${name}` : null;
2343
+ };
2344
+ const parseArgsRecord = (value) => {
2345
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2346
+ return value;
2347
+ }
2348
+ if (typeof value !== "string")
2349
+ return null;
2350
+ try {
2351
+ const parsed = JSON.parse(value);
2352
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2353
+ } catch {
2354
+ return null;
2355
+ }
2356
+ };
2357
+ const normalizeLineNumber = (value) => {
2358
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : Number.NaN;
2359
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : undefined;
2360
+ };
2361
+ const parseLineRange = (value) => {
2362
+ if (typeof value !== "string")
2363
+ return {};
2364
+ const match = value.match(/@(\d+)(?:-(\d+))?/);
2365
+ if (!match)
2366
+ return {};
2367
+ return {
2368
+ startLine: normalizeLineNumber(match[1]),
2369
+ endLine: normalizeLineNumber(match[2] ?? match[1])
2370
+ };
2371
+ };
2372
+ const getBoundedStreamingToolInput = (value) => {
2373
+ const maxLength = STREAMING_TOOL_INPUT_HEAD_CHARS + STREAMING_TOOL_INPUT_TAIL_CHARS;
2374
+ if (value.length <= maxLength)
2375
+ return value;
2376
+ return `${value.slice(0, STREAMING_TOOL_INPUT_HEAD_CHARS)}
2377
+ … streamed tool input truncated for UI responsiveness …
2378
+ ${value.slice(-STREAMING_TOOL_INPUT_TAIL_CHARS)}`;
2379
+ };
2380
+ const getToolArgsForViewer = (payload, delta) => {
2381
+ const args = parseArgsRecord(getToolEventArgs(payload));
2382
+ if (args)
2383
+ return args;
2384
+ const key = getToolBufferKey(payload);
2385
+ if (!key)
2386
+ return null;
2387
+ const previous = toolInputBuffersRef.current.get(key) ?? "";
2388
+ if (!delta)
2389
+ return parseArgsRecord(previous);
2390
+ const next = getBoundedStreamingToolInput(`${previous}${delta}`);
2391
+ toolInputBuffersRef.current.set(key, next);
2392
+ return parseArgsRecord(next);
2393
+ };
2394
+ const bestEffortUnescapeJsonString = (value) => {
2395
+ try {
2396
+ return JSON.parse(`"${value.replace(/\\$/, "")}"`);
2397
+ } catch {
2398
+ return value.replace(/\\n/g, `
2399
+ `).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
2400
+ }
2401
+ };
2402
+ const extractJsonStringField = (text, field, requireClosed = false) => {
2403
+ const marker = `"${field}"`;
2404
+ const markerIndex = text.indexOf(marker);
2405
+ if (markerIndex === -1)
2406
+ return;
2407
+ const colonIndex = text.indexOf(":", markerIndex + marker.length);
2408
+ if (colonIndex === -1)
2409
+ return;
2410
+ const quoteIndex = text.indexOf('"', colonIndex + 1);
2411
+ if (quoteIndex === -1)
2412
+ return;
2413
+ let escaped = "";
2414
+ let escaping = false;
2415
+ let closed = false;
2416
+ for (let i = quoteIndex + 1;i < text.length; i += 1) {
2417
+ const char = text[i];
2418
+ if (escaping) {
2419
+ escaped += `\\${char}`;
2420
+ escaping = false;
2421
+ continue;
2422
+ }
2423
+ if (char === "\\") {
2424
+ escaping = true;
2425
+ continue;
2426
+ }
2427
+ if (char === '"') {
2428
+ closed = true;
2429
+ break;
2430
+ }
2431
+ escaped += char;
2432
+ }
2433
+ if (requireClosed && !closed)
2434
+ return;
2435
+ return bestEffortUnescapeJsonString(escaped);
2436
+ };
2437
+ const getBufferedToolInput = (payload) => {
2438
+ const key = getToolBufferKey(payload);
2439
+ return key ? toolInputBuffersRef.current.get(key) ?? "" : "";
2440
+ };
2441
+ const getStringArg = (args, buffer, field, requireClosed = false) => {
2442
+ const value = args?.[field];
2443
+ if (typeof value === "string")
2444
+ return value;
2445
+ return extractJsonStringField(buffer, field, requireClosed);
2446
+ };
2447
+ const getStreamingWritePreviewContent = (args, buffer) => {
2448
+ const argContent = args?.content;
2449
+ if (typeof argContent === "string") {
2450
+ if (argContent.length <= STREAMING_WRITE_CONTENT_PREVIEW_CHARS) {
2451
+ return argContent;
2452
+ }
2453
+ return `… showing latest streamed content only …
2454
+ ${argContent.slice(-STREAMING_WRITE_CONTENT_PREVIEW_CHARS)}`;
2455
+ }
2456
+ const marker = '"content"';
2457
+ const markerIndex = buffer.indexOf(marker);
2458
+ if (markerIndex === -1)
2459
+ return;
2460
+ const colonIndex = buffer.indexOf(":", markerIndex + marker.length);
2461
+ if (colonIndex === -1)
2462
+ return;
2463
+ const quoteIndex = buffer.indexOf('"', colonIndex + 1);
2464
+ if (quoteIndex === -1)
2465
+ return;
2466
+ const valueStart = quoteIndex + 1;
2467
+ if (buffer.length - valueStart <= STREAMING_WRITE_CONTENT_PREVIEW_CHARS) {
2468
+ return extractJsonStringField(buffer, "content");
2469
+ }
2470
+ const rawTail = buffer.slice(Math.max(valueStart, buffer.length - STREAMING_WRITE_CONTENT_PREVIEW_CHARS));
2471
+ return `… showing latest streamed content only …
2472
+ ${bestEffortUnescapeJsonString(rawTail)}`;
2473
+ };
2474
+ const getStreamingPatchPreviewContent = (args, buffer) => {
2475
+ const argPatch = args?.patch;
2476
+ if (typeof argPatch === "string") {
2477
+ if (argPatch.length <= STREAMING_PATCH_PREVIEW_HEAD_CHARS + STREAMING_PATCH_PREVIEW_TAIL_CHARS) {
2478
+ return argPatch;
2479
+ }
2480
+ return `${argPatch.slice(0, STREAMING_PATCH_PREVIEW_HEAD_CHARS)}
2481
+ … patch preview truncated while streaming …
2482
+ ${argPatch.slice(-STREAMING_PATCH_PREVIEW_TAIL_CHARS)}`;
2483
+ }
2484
+ const marker = '"patch"';
2485
+ const markerIndex = buffer.indexOf(marker);
2486
+ if (markerIndex === -1)
2487
+ return;
2488
+ const colonIndex = buffer.indexOf(":", markerIndex + marker.length);
2489
+ if (colonIndex === -1)
2490
+ return;
2491
+ const quoteIndex = buffer.indexOf('"', colonIndex + 1);
2492
+ if (quoteIndex === -1)
2493
+ return;
2494
+ const valueStart = quoteIndex + 1;
2495
+ const rawLength = buffer.length - valueStart;
2496
+ if (rawLength <= STREAMING_PATCH_PREVIEW_HEAD_CHARS + STREAMING_PATCH_PREVIEW_TAIL_CHARS) {
2497
+ return extractJsonStringField(buffer, "patch");
2498
+ }
2499
+ const rawHead = buffer.slice(valueStart, valueStart + STREAMING_PATCH_PREVIEW_HEAD_CHARS);
2500
+ const rawTail = buffer.slice(-STREAMING_PATCH_PREVIEW_TAIL_CHARS);
2501
+ return `${bestEffortUnescapeJsonString(rawHead)}
2502
+ … patch preview truncated while streaming …
2503
+ ${bestEffortUnescapeJsonString(rawTail)}`;
2504
+ };
2505
+ const getResultRecord = (payload) => payload?.result && typeof payload.result === "object" && !Array.isArray(payload.result) ? payload.result : null;
2506
+ const getArtifactRecord = (payload) => payload?.artifact && typeof payload.artifact === "object" && !Array.isArray(payload.artifact) ? payload.artifact : null;
2507
+ const extractErrorMessage2 = (payload) => {
2508
+ const result = getResultRecord(payload);
2509
+ if (typeof payload?.error === "string")
2510
+ return payload.error;
2511
+ return typeof result?.error === "string" ? result.error : undefined;
2512
+ };
2513
+ const normalizePatchPath = (path) => path.replace(/^a\//, "").replace(/^b\//, "").trim();
2514
+ const patchPathMatches = (patchPath, targetPath) => {
2515
+ const normalizedPatch = normalizePatchPath(patchPath);
2516
+ const normalizedTarget = normalizePatchPath(targetPath);
2517
+ return normalizedPatch === normalizedTarget || normalizedPatch.endsWith(`/${normalizedTarget}`) || normalizedTarget.endsWith(`/${normalizedPatch}`);
2518
+ };
2519
+ const extractPathsFromPatch = (patch) => {
2520
+ const paths = new Set;
2521
+ for (const line of patch.split(`
2522
+ `)) {
2523
+ const directive = line.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/);
2524
+ if (directive?.[1]) {
2525
+ paths.add(directive[1].trim());
2526
+ continue;
2527
+ }
2528
+ const unified = line.match(/^\+\+\+ (?:b\/)?(.+)$/);
2529
+ if (unified?.[1] && unified[1] !== "/dev/null") {
2530
+ paths.add(unified[1].trim());
2531
+ }
2532
+ }
2533
+ return [...paths];
2534
+ };
2535
+ const getChangedLinesForPath = (result, path) => {
2536
+ const changes = Array.isArray(result?.changes) ? result.changes : [];
2537
+ const lines = new Set;
2538
+ for (const change of changes) {
2539
+ if (!change || typeof change !== "object")
2540
+ continue;
2541
+ const record = change;
2542
+ if (typeof record.filePath !== "string")
2543
+ continue;
2544
+ if (!patchPathMatches(record.filePath, path))
2545
+ continue;
2546
+ if (!Array.isArray(record.hunks))
2547
+ continue;
2548
+ for (const hunk of record.hunks) {
2549
+ if (!hunk || typeof hunk !== "object")
2550
+ continue;
2551
+ const hunkRecord = hunk;
2552
+ const newStart = typeof hunkRecord.newStart === "number" ? hunkRecord.newStart : undefined;
2553
+ const newLines = typeof hunkRecord.newLines === "number" ? hunkRecord.newLines : undefined;
2554
+ if (!newStart || !newLines)
2555
+ continue;
2556
+ for (let line = newStart;line < newStart + newLines; line += 1) {
2557
+ lines.add(line);
2558
+ }
2559
+ }
2560
+ }
2561
+ return lines.size > 0 ? [...lines] : undefined;
2562
+ };
2563
+ const handleReadToolActivity = (eventType, payload, delta) => {
2564
+ const viewerStore = useViewerTabsStore.getState();
2565
+ if (!viewerStore.followToolActivity)
2566
+ return;
2567
+ const name = getToolEventName(payload);
2568
+ if (name !== "read")
2569
+ return;
2570
+ const args = getToolArgsForViewer(payload, delta);
2571
+ const path = typeof args?.path === "string" ? args.path : null;
2572
+ if (!path)
2573
+ return;
2574
+ const result = payload?.result && typeof payload.result === "object" && !Array.isArray(payload.result) ? payload.result : null;
2575
+ const rangeFromResult = parseLineRange(result?.lineRange);
2576
+ const startLine = normalizeLineNumber(args.startLine) ?? normalizeLineNumber(args.start_line) ?? rangeFromResult.startLine;
2577
+ const endLine = normalizeLineNumber(args.endLine) ?? normalizeLineNumber(args.end_line) ?? rangeFromResult.endLine ?? startLine;
2578
+ const failed = result?.ok === false || eventType === "error";
2579
+ viewerStore.openToolReadTab(path, {
2580
+ startLine,
2581
+ endLine,
2582
+ reason: "read",
2583
+ callId: getToolEventCallId(payload) ?? undefined,
2584
+ status: failed ? "error" : eventType === "tool.result" ? "success" : "streaming"
2585
+ });
2586
+ };
2587
+ const handleWriteToolActivity = (eventType, payload, delta) => {
2588
+ const viewerStore = useViewerTabsStore.getState();
2589
+ if (!viewerStore.followToolActivity)
2590
+ return;
2591
+ const name = getToolEventName(payload);
2592
+ if (name !== "write")
2593
+ return;
2594
+ const args = getToolArgsForViewer(payload, delta);
2595
+ const buffer = getBufferedToolInput(payload);
2596
+ const result = getResultRecord(payload);
2597
+ const path = (typeof result?.path === "string" ? result.path : undefined) ?? getStringArg(args, buffer, "path", true);
2598
+ if (!path)
2599
+ return;
2600
+ const failed = result?.ok === false || eventType === "error";
2601
+ const callId = getToolEventCallId(payload) ?? undefined;
2602
+ const status = failed ? "error" : eventType === "tool.result" ? "success" : "streaming";
2603
+ const content = status === "streaming" ? getStreamingWritePreviewContent(args, buffer) : getStringArg(args, buffer, "content");
2604
+ if (status === "streaming" && content !== undefined && content.length >= TOOL_PREVIEW_THROTTLE_MIN_CHARS) {
2605
+ const previewKey = callId ?? path;
2606
+ const now = Date.now();
2607
+ const last = toolPreviewEmitRef.current.get(previewKey);
2608
+ const contentDelta = Math.abs(content.length - (last?.contentLength ?? 0));
2609
+ if (last && now - last.emittedAt < TOOL_PREVIEW_THROTTLE_MS && contentDelta < TOOL_PREVIEW_THROTTLE_MIN_DELTA_CHARS) {
2610
+ return;
2611
+ }
2612
+ toolPreviewEmitRef.current.set(previewKey, {
2613
+ emittedAt: now,
2614
+ contentLength: content.length
2615
+ });
2616
+ }
2617
+ viewerStore.openToolPreviewTab({
2618
+ path,
2619
+ toolName: "write",
2620
+ callId,
2621
+ content,
2622
+ status,
2623
+ error: extractErrorMessage2(payload)
2624
+ });
2625
+ };
2626
+ const handleApplyPatchToolActivity = (eventType, payload, delta) => {
2627
+ const viewerStore = useViewerTabsStore.getState();
2628
+ if (!viewerStore.followToolActivity)
2629
+ return;
2630
+ const name = getToolEventName(payload);
2631
+ if (name !== "apply_patch")
2632
+ return;
2633
+ const args = getToolArgsForViewer(payload, delta);
2634
+ const buffer = getBufferedToolInput(payload);
2635
+ const artifact = getArtifactRecord(payload);
2636
+ const result = getResultRecord(payload);
2637
+ const failed = result?.ok === false || eventType === "error";
2638
+ const status = failed ? "error" : eventType === "tool.result" ? "success" : "streaming";
2639
+ 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();
2643
+ 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) {
2646
+ return;
2647
+ }
2648
+ toolPreviewEmitRef.current.set(previewKey, {
2649
+ emittedAt: now,
2650
+ contentLength: buffer.length
2651
+ });
2652
+ }
2653
+ const patch = (typeof artifact?.patch === "string" ? artifact.patch : undefined) ?? (status === "streaming" ? getStreamingPatchPreviewContent(args, buffer) : getStringArg(args, buffer, "patch"));
2654
+ if (!patch)
2655
+ 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)
2665
+ });
2666
+ }
2667
+ };
2668
+ const handleToolActivityViewerEvent = (eventType, payload, delta) => {
2669
+ const name = getToolEventName(payload);
2670
+ if (name === "read")
2671
+ handleReadToolActivity(eventType, payload, delta);
2672
+ if (name === "write")
2673
+ handleWriteToolActivity(eventType, payload, delta);
2674
+ if (name === "apply_patch") {
2675
+ handleApplyPatchToolActivity(eventType, payload, delta);
2676
+ }
2677
+ };
2197
2678
  const getToolInputDelta = (payload) => {
2198
2679
  if (typeof payload?.delta === "string")
2199
2680
  return payload.delta;
@@ -2331,6 +2812,18 @@ function useSessionStream(sessionId, enabled = true) {
2331
2812
  const name = getToolEventName(payload);
2332
2813
  if (!name)
2333
2814
  return;
2815
+ if (name === "write" || name === "apply_patch") {
2816
+ const bufferKey = getToolBufferKey(payload);
2817
+ const bufferedLength = bufferKey ? toolInputBuffersRef.current.get(bufferKey)?.length ?? 0 : 0;
2818
+ if (bufferedLength >= TOOL_PREVIEW_THROTTLE_MIN_CHARS) {
2819
+ const emitKey = callId ?? `name:${name}`;
2820
+ const now = Date.now();
2821
+ const last = toolMessageEmitRef.current.get(emitKey) ?? 0;
2822
+ if (now - last < STREAMING_TOOL_MESSAGE_THROTTLE_MS)
2823
+ return;
2824
+ toolMessageEmitRef.current.set(emitKey, now);
2825
+ }
2826
+ }
2334
2827
  queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
2335
2828
  if (!oldMessages)
2336
2829
  return oldMessages;
@@ -2434,7 +2927,7 @@ function useSessionStream(sessionId, enabled = true) {
2434
2927
  if (partIndex === -1) {
2435
2928
  const contentJsonBase = {
2436
2929
  name,
2437
- _streamedInput: delta
2930
+ _streamedInput: getBoundedStreamingToolInput(delta)
2438
2931
  };
2439
2932
  if (callId)
2440
2933
  contentJsonBase.callId = callId;
@@ -2462,7 +2955,7 @@ function useSessionStream(sessionId, enabled = true) {
2462
2955
  const prev = typeof existing.contentJson?._streamedInput === "string" ? existing.contentJson._streamedInput : "";
2463
2956
  const nextContentJson = {
2464
2957
  ...typeof existing.contentJson === "object" && !Array.isArray(existing.contentJson) ? existing.contentJson : {},
2465
- _streamedInput: prev + delta
2958
+ _streamedInput: getBoundedStreamingToolInput(prev + delta)
2466
2959
  };
2467
2960
  parts[partIndex] = {
2468
2961
  ...existing,
@@ -2750,8 +3243,10 @@ function useSessionStream(sessionId, enabled = true) {
2750
3243
  if (channel === "input" || channel == null && delta) {
2751
3244
  if (delta) {
2752
3245
  accumulateToolInputDelta(payload, delta);
3246
+ handleToolActivityViewerEvent("tool.delta", payload, delta);
2753
3247
  } else {
2754
3248
  upsertEphemeralToolCall(payload);
3249
+ handleToolActivityViewerEvent("tool.delta", payload);
2755
3250
  }
2756
3251
  } else if (channel === "output" && delta) {
2757
3252
  accumulateToolOutputDelta(payload, delta);
@@ -2760,10 +3255,15 @@ function useSessionStream(sessionId, enabled = true) {
2760
3255
  }
2761
3256
  case "tool.call": {
2762
3257
  upsertEphemeralToolCall(payload);
3258
+ handleToolActivityViewerEvent("tool.call", payload);
2763
3259
  break;
2764
3260
  }
2765
3261
  case "tool.result": {
2766
3262
  resolveEphemeralToolCall(payload);
3263
+ handleToolActivityViewerEvent("tool.result", payload);
3264
+ const key = getToolBufferKey(payload);
3265
+ if (key)
3266
+ toolInputBuffersRef.current.delete(key);
2767
3267
  break;
2768
3268
  }
2769
3269
  case "tool.approval.required": {
@@ -2798,6 +3298,7 @@ function useSessionStream(sessionId, enabled = true) {
2798
3298
  break;
2799
3299
  }
2800
3300
  case "error": {
3301
+ handleToolActivityViewerEvent("error", payload);
2801
3302
  removeEphemeralToolCall(payload);
2802
3303
  const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
2803
3304
  if (messageId) {
@@ -3022,6 +3523,9 @@ function sendBrowserNotification(notification) {
3022
3523
  };
3023
3524
  return true;
3024
3525
  }
3526
+ function isAppForeground() {
3527
+ return getPlatformWindowFocused() ?? document.visibilityState === "visible";
3528
+ }
3025
3529
  function updateSessionStatusInCache(queryClient, status) {
3026
3530
  queryClient.setQueryData(sessionsQueryKey, (old) => {
3027
3531
  if (!old)
@@ -3167,7 +3671,7 @@ function useClientEvents(activeSessionId) {
3167
3671
  }
3168
3672
  if (event.event === "notification") {
3169
3673
  const notification = payload;
3170
- const isActiveVisibleSession = notification.sessionId === activeSessionIdRef.current && document.visibilityState === "visible";
3674
+ const isActiveVisibleSession = notification.sessionId === activeSessionIdRef.current && isAppForeground();
3171
3675
  const isSessionNotification = notification.source === "session" || !!notification.sessionId;
3172
3676
  let sentSystemNotification = false;
3173
3677
  if (!isActiveVisibleSession) {
@@ -3423,7 +3927,7 @@ function useKeyboardShortcuts({
3423
3927
  }
3424
3928
  return;
3425
3929
  }
3426
- if ((e.ctrlKey || e.metaKey) && e.key === "h") {
3930
+ if ((e.ctrlKey || e.metaKey) && e.key === "b") {
3427
3931
  e.preventDefault();
3428
3932
  if (currentFocus === "sessions") {
3429
3933
  document.activeElement?.blur();
@@ -3446,7 +3950,7 @@ function useKeyboardShortcuts({
3446
3950
  }
3447
3951
  return;
3448
3952
  }
3449
- if ((e.ctrlKey || e.metaKey) && e.key === "l") {
3953
+ if ((e.ctrlKey || e.metaKey) && !e.shiftKey && e.key === "r") {
3450
3954
  e.preventDefault();
3451
3955
  if (currentFocus === "git") {
3452
3956
  document.activeElement?.blur();
@@ -5767,6 +6271,7 @@ export {
5767
6271
  useImageUpload,
5768
6272
  useGitStatus,
5769
6273
  useGitRemotes,
6274
+ useGitRebaseAction,
5770
6275
  useGitInit,
5771
6276
  useGitDiffFullFile,
5772
6277
  useGitDiff,
@@ -5797,4 +6302,4 @@ export {
5797
6302
  sessionsQueryKey
5798
6303
  };
5799
6304
 
5800
- //# debugId=AFCFC55375F9489664756E2164756E21
6305
+ //# debugId=88EDF96A27BB9BCF64756E2164756E21