@tonyclaw/agent-inspector 3.1.20 → 3.1.22

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 (57) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +137 -20
  3. package/.output/server/_ssr/index.mjs +2 -2
  4. package/.output/server/_ssr/{router-DpXchCXm.mjs → router-DKFaWvAr.mjs} +2371 -1171
  5. package/.output/server/_tanstack-start-manifest_v-ClTaMkEj.mjs +4 -0
  6. package/.output/server/index.mjs +1 -1
  7. package/.output/ui/assets/{CompareDrawer-4i1QHaQf.js → CompareDrawer-pPcsyvc6.js} +1 -1
  8. package/.output/ui/assets/{InspectorPet-Cw6VJLr7.js → InspectorPet-DR3JEqze.js} +1 -1
  9. package/.output/ui/assets/ProxyViewerContainer-DOyYRnb-.js +60 -0
  10. package/.output/ui/assets/{ReplayDialog-DTs575iW.js → ReplayDialog-Z-1-gUDm.js} +1 -1
  11. package/.output/ui/assets/{RequestAnatomy-Cl6Es3gv.js → RequestAnatomy-BsnZjE5G.js} +1 -1
  12. package/.output/ui/assets/{ResponseView-DNCCLkzG.js → ResponseView-HY64_ZY2.js} +1 -1
  13. package/.output/ui/assets/{StreamingChunkSequence-DKaxRL9U.js → StreamingChunkSequence-CfQUFbTA.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-rkR3WgRq.js → _sessionId-Dq97lD86.js} +1 -1
  15. package/.output/ui/assets/{_sessionId-C-YjWz50.js → _sessionId-_F9wwa3a.js} +1 -1
  16. package/.output/ui/assets/index-B-Qod-aA.css +1 -0
  17. package/.output/ui/assets/{index-DxiecWHw.js → index-B2yLwOEu.js} +1 -1
  18. package/.output/ui/assets/{index-D0d-V67x.js → index-CN-gZDqV.js} +1 -1
  19. package/.output/ui/assets/{index-DYbzZTDM.js → index-DOAGzrq2.js} +1 -1
  20. package/.output/ui/assets/{index-B48RD1-5.js → index-DYnqmBBx.js} +2 -2
  21. package/.output/ui/assets/{json-viewer-CjAwZhHA.js → json-viewer-B2SQN0Lg.js} +1 -1
  22. package/.output/ui/assets/{jszip.min-DEGEN1HP.js → jszip.min-Dpks67u2.js} +1 -1
  23. package/.output/ui/index.html +2 -2
  24. package/.output/workers/logFinalizer.worker.js +258 -0
  25. package/.output/workers/sessionWorkerEntry.js +258 -0
  26. package/package.json +13 -6
  27. package/src/backend/routes/api/logs.$id.replay.ts +6 -1
  28. package/src/backend/routes/api/logs.stream.ts +7 -0
  29. package/src/backend/routes/api/providers.$providerId.model-metadata.ts +15 -4
  30. package/src/backend/routes/api/storage.ts +56 -0
  31. package/src/backend/routes/metrics.ts +14 -0
  32. package/src/cli/doctor.ts +135 -2
  33. package/src/cli.ts +7 -4
  34. package/src/components/ProxyViewerContainer.tsx +41 -24
  35. package/src/components/providers/SettingsDialog.tsx +261 -70
  36. package/src/components/proxy-viewer/LogEntry.tsx +19 -5
  37. package/src/components/proxy-viewer/bodyHydration.ts +67 -0
  38. package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +78 -0
  39. package/src/knowledge/openclawClient.ts +11 -2
  40. package/src/knowledge/openclawGatewayClient.ts +6 -1
  41. package/src/lib/resourceLimits.ts +191 -0
  42. package/src/lib/safeFetch.ts +428 -20
  43. package/src/proxy/ecosystemTasks.ts +20 -0
  44. package/src/proxy/handler.ts +17 -4
  45. package/src/proxy/identityProxy.ts +36 -16
  46. package/src/proxy/logIndex.ts +38 -6
  47. package/src/proxy/logger.ts +4 -0
  48. package/src/proxy/rawStreamCapture.ts +16 -1
  49. package/src/proxy/runtimeHealth.ts +4 -2
  50. package/src/proxy/runtimeMetrics.ts +149 -0
  51. package/src/proxy/sessionRuntime.ts +38 -0
  52. package/src/proxy/sqliteLogIndex.ts +51 -0
  53. package/src/proxy/storageLifecycle.ts +432 -0
  54. package/src/proxy/store.ts +62 -12
  55. package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +0 -4
  56. package/.output/ui/assets/ProxyViewerContainer-BST0B7lY.js +0 -60
  57. package/.output/ui/assets/index-IBEDJoV_.css +0 -1
package/src/cli/doctor.ts CHANGED
@@ -35,6 +35,7 @@ export type DoctorOptions = {
35
35
  chromeExtension: boolean;
36
36
  fix: boolean;
37
37
  json: boolean;
38
+ supportBundle: boolean;
38
39
  verbose: boolean;
39
40
  help: boolean;
40
41
  };
@@ -48,6 +49,12 @@ export type HealthProbe = {
48
49
  status: number | null;
49
50
  };
50
51
 
52
+ export type ReadinessProbe = {
53
+ ok: boolean;
54
+ status: number | null;
55
+ body: unknown;
56
+ };
57
+
51
58
  export type DoctorDeps = {
52
59
  env: Readonly<Record<string, string | undefined>>;
53
60
  platform: typeof process.platform;
@@ -57,17 +64,35 @@ export type DoctorDeps = {
57
64
  listDir: (path: string) => readonly string[] | null;
58
65
  stat: (path: string) => { isDirectory: boolean; size: number } | null;
59
66
  fetchHealth: (port: number, basePath: string) => Promise<HealthProbe>;
67
+ fetchReadiness: (port: number, basePath: string) => Promise<ReadinessProbe>;
60
68
  isPortOpen: (port: number) => Promise<boolean>;
61
69
  };
62
70
 
63
71
  export type DoctorReport = {
64
72
  checks: readonly DoctorCheck[];
73
+ readiness: ReadinessProbe | null;
65
74
  passCount: number;
66
75
  warnCount: number;
67
76
  failCount: number;
68
77
  exitCode: number;
69
78
  };
70
79
 
80
+ export type DoctorSupportBundle = {
81
+ schema: "agent-inspector.support-bundle/v1";
82
+ createdAt: string;
83
+ report: DoctorReport;
84
+ environment: {
85
+ platform: typeof process.platform;
86
+ cwd: string;
87
+ basePath: string;
88
+ port: number;
89
+ hasControlToken: boolean;
90
+ hasProxyToken: boolean;
91
+ hasProviderJsonOverride: boolean;
92
+ };
93
+ storage: readonly DoctorCheck[];
94
+ };
95
+
71
96
  function check(name: string, severity: DoctorSeverity, message: string, hint = ""): DoctorCheck {
72
97
  return {
73
98
  name,
@@ -96,6 +121,7 @@ export function parseDoctorArgs(
96
121
  chromeExtension: false,
97
122
  fix: false,
98
123
  json: false,
124
+ supportBundle: false,
99
125
  verbose: false,
100
126
  help: false,
101
127
  };
@@ -146,6 +172,10 @@ export function parseDoctorArgs(
146
172
  case "--json":
147
173
  options.json = true;
148
174
  break;
175
+ case "--support-bundle":
176
+ options.supportBundle = true;
177
+ options.json = true;
178
+ break;
149
179
  case "--verbose":
150
180
  options.verbose = true;
151
181
  break;
@@ -316,6 +346,42 @@ async function checkProxyHealth(port: number, deps: DoctorDeps): Promise<DoctorC
316
346
  );
317
347
  }
318
348
 
349
+ function readinessChecks(readiness: ReadinessProbe): readonly DoctorCheck[] {
350
+ if (!readiness.ok) {
351
+ const suffix = readiness.status === null ? "" : ` (HTTP ${readiness.status})`;
352
+ return [
353
+ check(
354
+ "Readiness",
355
+ "warn",
356
+ `Readiness probe is unavailable${suffix}.`,
357
+ "Use /readyz locally or run with --verbose for storage and worker hints.",
358
+ ),
359
+ ];
360
+ }
361
+ if (!isObject(readiness.body)) {
362
+ return [check("Readiness", "warn", "Readiness response was not an object.")];
363
+ }
364
+ const status = readiness.body["status"];
365
+ const checks = readiness.body["checks"];
366
+ if (typeof status !== "string" || !isObject(checks)) {
367
+ return [check("Readiness", "warn", "Readiness response did not include status/checks.")];
368
+ }
369
+ const degraded = Object.entries(checks)
370
+ .filter(([, value]) => value !== "ok")
371
+ .map(([name, value]) => `${name}=${String(value)}`);
372
+ if (degraded.length === 0) {
373
+ return [check("Readiness", "pass", `Runtime readiness is ${status}.`)];
374
+ }
375
+ return [
376
+ check(
377
+ "Readiness",
378
+ status === "unavailable" ? "fail" : "warn",
379
+ `Runtime readiness is ${status}: ${degraded.join(", ")}.`,
380
+ "Inspect /readyz and /metrics for safe aggregate runtime details.",
381
+ ),
382
+ ];
383
+ }
384
+
319
385
  function findProjectRoot(startDir: string, deps: Pick<DoctorDeps, "exists">): string {
320
386
  let current = resolve(startDir);
321
387
  for (let i = 0; i < 6; i++) {
@@ -549,8 +615,11 @@ export async function buildDoctorReport(
549
615
  deps: DoctorDeps = createDoctorDeps(),
550
616
  ): Promise<DoctorReport> {
551
617
  const rootDir = findProjectRoot(deps.cwd, deps);
618
+ const basePath = getConfiguredBasePath(deps.env);
619
+ const readiness = await deps.fetchReadiness(options.port, basePath);
552
620
  const coreChecks = [
553
621
  await checkProxyHealth(options.port, deps),
622
+ ...readinessChecks(readiness),
554
623
  checkProviderConfig(options, deps),
555
624
  checkPackage(rootDir, deps),
556
625
  ];
@@ -567,6 +636,7 @@ export async function buildDoctorReport(
567
636
 
568
637
  return {
569
638
  checks,
639
+ readiness,
570
640
  passCount,
571
641
  warnCount,
572
642
  failCount,
@@ -574,6 +644,31 @@ export async function buildDoctorReport(
574
644
  };
575
645
  }
576
646
 
647
+ export function buildDoctorSupportBundle(
648
+ options: DoctorOptions,
649
+ deps: DoctorDeps,
650
+ report: DoctorReport,
651
+ ): DoctorSupportBundle {
652
+ return {
653
+ schema: "agent-inspector.support-bundle/v1",
654
+ createdAt: new Date().toISOString(),
655
+ report,
656
+ environment: {
657
+ platform: deps.platform,
658
+ cwd: deps.cwd,
659
+ basePath: getConfiguredBasePath(deps.env),
660
+ port: options.port,
661
+ hasControlToken:
662
+ deps.env["AGENT_INSPECTOR_CONTROL_TOKEN"] !== undefined ||
663
+ deps.env["AGENT_INSPECTOR_API_TOKEN"] !== undefined,
664
+ hasProxyToken: deps.env["AGENT_INSPECTOR_PROXY_TOKEN"] !== undefined,
665
+ hasProviderJsonOverride:
666
+ options.providersJson !== null || deps.env["AGENT_INSPECTOR_PROVIDERS_JSON"] !== undefined,
667
+ },
668
+ storage: verboseChecks(options, deps).filter((item) => item.name.includes("storage")),
669
+ };
670
+ }
671
+
577
672
  export function formatDoctorReport(report: DoctorReport): string {
578
673
  const lines = ["agent-inspector doctor", ""];
579
674
  for (const item of report.checks) {
@@ -593,17 +688,22 @@ export function formatDoctorJson(report: DoctorReport): string {
593
688
  return `${JSON.stringify(report, null, 2)}\n`;
594
689
  }
595
690
 
691
+ export function formatDoctorSupportBundle(bundle: DoctorSupportBundle): string {
692
+ return `${JSON.stringify(bundle, null, 2)}\n`;
693
+ }
694
+
596
695
  export function doctorHelp(): string {
597
696
  return [
598
697
  "agent-inspector doctor",
599
698
  "",
600
699
  "Usage:",
601
- " agent-inspector doctor [--port <port>] [--config-dir <dir>] [--providers <json>] [--fix] [--json] [--verbose] [--chrome-extension]",
700
+ " agent-inspector doctor [--port <port>] [--config-dir <dir>] [--providers <json>] [--fix] [--json] [--support-bundle] [--verbose] [--chrome-extension]",
602
701
  "",
603
702
  "Checks local backend health, provider config presence, and package metadata.",
604
703
  "The backend check probes the local health endpoint and does not require a composed Web UI.",
605
704
  "Pass --fix to run safe local repairs before checks, including legacy data-dir migration.",
606
705
  "Pass --json to emit a machine-readable report for CI, scripts, and Jenkins.",
706
+ "Pass --support-bundle to emit a redacted JSON bundle with readiness causes and storage summary.",
607
707
  "Pass --verbose to include local paths, capture mode, and log storage checks.",
608
708
  "Pass --chrome-extension to also check Chrome companion source and packaged artifacts.",
609
709
  "",
@@ -629,6 +729,31 @@ async function fetchHealth(port: number, basePath: string): Promise<HealthProbe>
629
729
  }
630
730
  }
631
731
 
732
+ async function fetchReadiness(port: number, basePath: string): Promise<ReadinessProbe> {
733
+ const controller = new AbortController();
734
+ const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
735
+ try {
736
+ const response = await fetch(
737
+ appendBasePathToOrigin(`http://127.0.0.1:${port}`, "/readyz", basePath),
738
+ {
739
+ cache: "no-store",
740
+ signal: controller.signal,
741
+ },
742
+ );
743
+ let body: unknown = null;
744
+ try {
745
+ body = await response.json();
746
+ } catch {
747
+ body = null;
748
+ }
749
+ return { ok: response.ok, status: response.status, body };
750
+ } catch {
751
+ return { ok: false, status: null, body: null };
752
+ } finally {
753
+ clearTimeout(timeout);
754
+ }
755
+ }
756
+
632
757
  function isPortOpen(port: number): Promise<boolean> {
633
758
  return new Promise((resolveOpen) => {
634
759
  const socket = createConnection({ host: "127.0.0.1", port });
@@ -680,6 +805,7 @@ export function createDoctorDeps(): DoctorDeps {
680
805
  listDir,
681
806
  stat,
682
807
  fetchHealth,
808
+ fetchReadiness,
683
809
  isPortOpen,
684
810
  };
685
811
  }
@@ -698,6 +824,13 @@ export async function runDoctor(argv: readonly string[]): Promise<number> {
698
824
  migrateLegacyDataDirs();
699
825
  }
700
826
  const report = await buildDoctorReport(parsed.options);
701
- process.stdout.write(parsed.options.json ? formatDoctorJson(report) : formatDoctorReport(report));
827
+ if (parsed.options.supportBundle) {
828
+ const bundle = buildDoctorSupportBundle(parsed.options, createDoctorDeps(), report);
829
+ process.stdout.write(formatDoctorSupportBundle(bundle));
830
+ } else {
831
+ process.stdout.write(
832
+ parsed.options.json ? formatDoctorJson(report) : formatDoctorReport(report),
833
+ );
834
+ }
702
835
  return report.exitCode;
703
836
  }
package/src/cli.ts CHANGED
@@ -1137,18 +1137,21 @@ async function runStart(args: string[]): Promise<void> {
1137
1137
  const backgroundSupervisorLogs = runningInBackgroundSupervisor
1138
1138
  ? createBackgroundSupervisorLogs(serverEnv)
1139
1139
  : null;
1140
+ const shouldPipeBackgroundRuntimeLogs = backgroundSupervisorLogs !== null;
1140
1141
  const serverProcess = spawn(serverCommand.command, serverCommand.args, {
1141
1142
  stdio:
1142
1143
  managedLaunch === null
1143
- ? background
1144
+ ? background && !shouldPipeBackgroundRuntimeLogs
1144
1145
  ? ["ignore", "ignore", "ignore"]
1145
1146
  : ["ignore", "pipe", "pipe"]
1146
1147
  : backend === "typescript"
1147
- ? background
1148
+ ? background && !shouldPipeBackgroundRuntimeLogs
1148
1149
  ? ["ignore", "ignore", "ignore", "ipc"]
1149
1150
  : ["ignore", "pipe", "pipe", "ipc"]
1150
1151
  : background
1151
- ? ["ignore", "ignore", "ignore"]
1152
+ ? shouldPipeBackgroundRuntimeLogs
1153
+ ? ["ignore", "pipe", "pipe"]
1154
+ : ["ignore", "ignore", "ignore"]
1152
1155
  : ["ignore", "pipe", "pipe"],
1153
1156
  detached: background,
1154
1157
  env: serverEnv,
@@ -1158,7 +1161,7 @@ async function runStart(args: string[]): Promise<void> {
1158
1161
  console.error(`Failed to start Agent Inspector runtime: ${err.message}`);
1159
1162
  });
1160
1163
  const serverExit = waitForProcessExit(serverProcess);
1161
- if (!background) {
1164
+ if (!background || backgroundSupervisorLogs !== null) {
1162
1165
  const outputContext = {
1163
1166
  enableIdentityProxy,
1164
1167
  publicPort: port,
@@ -18,7 +18,9 @@ import {
18
18
  import type { SessionLoadProgress } from "./proxy-viewer/SessionLoadProgressBar";
19
19
  import { browserPrefersReducedMotion, preferredScrollBehavior } from "./proxy-viewer/accessibility";
20
20
  import {
21
+ buildLogIndexById,
21
22
  filterLogs,
23
+ mergeLogsIncrementally,
22
24
  mergeLogsById,
23
25
  mergeSessionIds,
24
26
  } from "./proxy-viewer/proxyViewerContainerLogic";
@@ -133,6 +135,7 @@ const SESSION_LOGS_FETCH_TIMEOUT_MS = 20_000;
133
135
  const SESSION_MEMBERSHIP_FETCH_TIMEOUT_MS = 10_000;
134
136
  const SSE_RECONNECT_DELAY_MS = 3_000;
135
137
  const SSE_STALE_AFTER_MS = 10_000;
138
+ const LIVE_RECENT_LOG_LIMIT = 2_000;
136
139
 
137
140
  function buildSessionLogsPageUrl(
138
141
  sessionId: string,
@@ -163,15 +166,6 @@ function buildSessionLogsPageUrl(
163
166
  return `/api/logs?${params.toString()}`;
164
167
  }
165
168
 
166
- function buildLogIndex(logs: readonly CapturedLog[]): Map<number, number> {
167
- const idx = new Map<number, number>();
168
- for (let i = 0; i < logs.length; i++) {
169
- const log = logs[i];
170
- if (log !== undefined) idx.set(log.id, i);
171
- }
172
- return idx;
173
- }
174
-
175
169
  function fetchSessionLogsPage(
176
170
  sessionId: string,
177
171
  request: SessionPageRequest,
@@ -275,6 +269,14 @@ export function ProxyViewerContainer({
275
269
  // Debounce buffer for SSE updates
276
270
  const pendingUpdatesRef = useRef<CapturedLog[]>([]);
277
271
  const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
272
+ const logMergeOptions = useCallback(
273
+ () => ({
274
+ recentLimit: LIVE_RECENT_LOG_LIMIT,
275
+ retainSessionId:
276
+ initialSessionId ?? (selectedSession === "__all__" ? undefined : selectedSession),
277
+ }),
278
+ [initialSessionId, selectedSession],
279
+ );
278
280
  const showErrorToast = useCallback(
279
281
  (message: string) => {
280
282
  showToast({ message, tone: "error" });
@@ -359,12 +361,17 @@ export function ProxyViewerContainer({
359
361
 
360
362
  startLogTransition(() => {
361
363
  setAllLogs((prev) => {
362
- const next = mergeLogsById(prev, updates);
363
- logIndexRef.current = buildLogIndex(next);
364
- return next;
364
+ const merged = mergeLogsIncrementally(
365
+ prev,
366
+ logIndexRef.current,
367
+ updates,
368
+ logMergeOptions(),
369
+ );
370
+ logIndexRef.current = merged.index;
371
+ return merged.logs;
365
372
  });
366
373
  });
367
- }, [startLogTransition]);
374
+ }, [logMergeOptions, startLogTransition]);
368
375
 
369
376
  const scheduleUpdate = useCallback(
370
377
  (log: CapturedLog) => {
@@ -425,9 +432,14 @@ export function ProxyViewerContainer({
425
432
  pendingUpdatesRef.current = [];
426
433
 
427
434
  setAllLogs((prev) => {
428
- const nextLogs = mergeLogsById(prev, update.logs);
429
- logIndexRef.current = buildLogIndex(nextLogs);
430
- return nextLogs;
435
+ const merged = mergeLogsIncrementally(
436
+ prev,
437
+ logIndexRef.current,
438
+ update.logs,
439
+ logMergeOptions(),
440
+ );
441
+ logIndexRef.current = merged.index;
442
+ return merged.logs;
431
443
  });
432
444
  setKnownSessions((prev) => mergeSessionIds(prev, extractSessions(update.logs)));
433
445
  if (staleTimeoutRef.current !== null) {
@@ -523,7 +535,7 @@ export function ProxyViewerContainer({
523
535
  .then((page) => {
524
536
  if (sessionPageRequestIdRef.current !== requestId) return;
525
537
  const nextLogs = page.logs;
526
- logIndexRef.current = buildLogIndex(nextLogs);
538
+ logIndexRef.current = buildLogIndexById(nextLogs);
527
539
  setAllLogs(nextLogs);
528
540
  setLogPage(page);
529
541
  setSelectedSession(initialSessionId);
@@ -592,9 +604,14 @@ export function ProxyViewerContainer({
592
604
 
593
605
  if (sessionPageRequestIdRef.current !== requestId) return;
594
606
  setAllLogs((prev) => {
595
- const next = mergeLogsById(prev, collected);
596
- logIndexRef.current = buildLogIndex(next);
597
- return next;
607
+ const merged = mergeLogsIncrementally(
608
+ prev,
609
+ logIndexRef.current,
610
+ collected,
611
+ logMergeOptions(),
612
+ );
613
+ logIndexRef.current = merged.index;
614
+ return merged.logs;
598
615
  });
599
616
  setKnownSessions((prev) => mergeSessionIds(prev, [sessionId]));
600
617
  setSessionPageLoading(false);
@@ -796,7 +813,7 @@ export function ProxyViewerContainer({
796
813
  const removed = prev.filter((log) => idSet.has(log.id));
797
814
  const remaining = prev.filter((log) => !idSet.has(log.id));
798
815
  setKnownSessions((known) => mergeSessionIds(known, extractSessions(removed)));
799
- logIndexRef.current = buildLogIndex(remaining);
816
+ logIndexRef.current = buildLogIndexById(remaining);
800
817
  return remaining;
801
818
  });
802
819
  showToast({ message: "Cleared this group from the current view.", tone: "success" });
@@ -856,7 +873,7 @@ export function ProxyViewerContainer({
856
873
  if (sessionPageRequestIdRef.current !== deleteRequestId) return;
857
874
  setAllLogs((prev) => {
858
875
  const remaining = prev.filter((log) => log.sessionId !== sessionId);
859
- logIndexRef.current = buildLogIndex(remaining);
876
+ logIndexRef.current = buildLogIndexById(remaining);
860
877
  return remaining;
861
878
  });
862
879
  setLogPage((prev) =>
@@ -886,7 +903,7 @@ export function ProxyViewerContainer({
886
903
  if (sessionPageRequestIdRef.current !== deleteRequestId) return;
887
904
  setAllLogs((prev) => {
888
905
  const remaining = prev.filter((log) => log.sessionId !== sessionId);
889
- logIndexRef.current = buildLogIndex(remaining);
906
+ logIndexRef.current = buildLogIndexById(remaining);
890
907
  return remaining;
891
908
  });
892
909
  setLogPage((prev) =>
@@ -949,7 +966,7 @@ export function ProxyViewerContainer({
949
966
  prev.filter((log) => !importedIds.has(log.id)),
950
967
  result.logs,
951
968
  );
952
- logIndexRef.current = buildLogIndex(next);
969
+ logIndexRef.current = buildLogIndexById(next);
953
970
  return next;
954
971
  });
955
972
  setSelectedSession(result.sessionId);