ahp-inspector 1.1.2 → 1.3.0

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.
package/dist/index.js CHANGED
@@ -19,10 +19,10 @@ var MAX_DEPTH_BELOW_LAUNCH = 3;
19
19
  function defaultRoots() {
20
20
  const home = homedir();
21
21
  const platform = process.platform;
22
- const ossDev = {
23
- origin: "vscode-oss-dev",
24
- dir: join(home, ".vscode-oss-agents-dev", "logs")
25
- };
22
+ const ossDevRoots = [
23
+ { origin: "vscode-oss-dev", dir: join(home, ".vscode-oss-dev", "logs") },
24
+ { origin: "vscode-oss-dev", dir: join(home, ".vscode-oss-agents-dev", "logs") }
25
+ ];
26
26
  if (platform === "darwin") {
27
27
  return [
28
28
  { origin: "vscode", dir: join(home, "Library", "Application Support", "Code", "logs") },
@@ -30,7 +30,7 @@ function defaultRoots() {
30
30
  origin: "vscode-insiders",
31
31
  dir: join(home, "Library", "Application Support", "Code - Insiders", "logs")
32
32
  },
33
- ossDev
33
+ ...ossDevRoots
34
34
  ];
35
35
  }
36
36
  if (platform === "win32") {
@@ -38,13 +38,13 @@ function defaultRoots() {
38
38
  return [
39
39
  { origin: "vscode", dir: join(appData, "Code", "logs") },
40
40
  { origin: "vscode-insiders", dir: join(appData, "Code - Insiders", "logs") },
41
- ossDev
41
+ ...ossDevRoots
42
42
  ];
43
43
  }
44
44
  return [
45
45
  { origin: "vscode", dir: join(home, ".config", "Code", "logs") },
46
46
  { origin: "vscode-insiders", dir: join(home, ".config", "Code - Insiders", "logs") },
47
- ossDev
47
+ ...ossDevRoots
48
48
  ];
49
49
  }
50
50
  var FILENAME_RE_AHP_JSONL = /^(agenthost|agent-host|ahp).*\.jsonl$/i;
@@ -2351,12 +2351,19 @@ var TailReader = class {
2351
2351
  this.#lastOffset = 0;
2352
2352
  return;
2353
2353
  }
2354
+ sink.onInitialReadStart?.({ totalBytes: sizeAtStart });
2354
2355
  if (sizeAtStart === 0) {
2355
2356
  this.#lastOffset = 0;
2357
+ sink.onInitialReadComplete?.({ loadedBytes: 0, totalBytes: 0 });
2356
2358
  return;
2357
2359
  }
2358
- await this.#readRange(0, sizeAtStart, sink);
2359
- this.#lastOffset = sizeAtStart;
2360
+ const result = await this.#readRange(0, sizeAtStart, sink, (loadedBytes) => {
2361
+ sink.onInitialReadProgress?.({ loadedBytes, totalBytes: sizeAtStart });
2362
+ });
2363
+ this.#lastOffset = result.nextOffset;
2364
+ if (result.completed && result.nextOffset >= sizeAtStart) {
2365
+ sink.onInitialReadComplete?.({ loadedBytes: sizeAtStart, totalBytes: sizeAtStart });
2366
+ }
2360
2367
  }
2361
2368
  /**
2362
2369
  * Subscribe to file growth, rotation, and errors. Returns a disposer that
@@ -2410,8 +2417,8 @@ var TailReader = class {
2410
2417
  }
2411
2418
  if (nextSize === this.#lastOffset) return;
2412
2419
  const start = this.#lastOffset;
2413
- await this.#readRange(start, nextSize, sink);
2414
- this.#lastOffset = nextSize;
2420
+ const result = await this.#readRange(start, nextSize, sink);
2421
+ this.#lastOffset = result.nextOffset;
2415
2422
  } finally {
2416
2423
  this.#readInFlight = false;
2417
2424
  if (this.#readAgainAfterCurrent && !this.#disposed) {
@@ -2433,11 +2440,11 @@ var TailReader = class {
2433
2440
  this.#lastOffset = 0;
2434
2441
  sink.onReset({ newSize, reason });
2435
2442
  if (newSize > 0) {
2436
- await this.#readRange(0, newSize, sink);
2437
- this.#lastOffset = newSize;
2443
+ const result = await this.#readRange(0, newSize, sink);
2444
+ this.#lastOffset = result.nextOffset;
2438
2445
  }
2439
2446
  }
2440
- #readRange(start, end, sink) {
2447
+ #readRange(start, end, sink, onProgress) {
2441
2448
  return new Promise((resolve3) => {
2442
2449
  const stream2 = createReadStream(this.#path, {
2443
2450
  start,
@@ -2453,11 +2460,12 @@ var TailReader = class {
2453
2460
  })();
2454
2461
  sink.onChunk(bytes, cursor);
2455
2462
  cursor += bytes.byteLength;
2463
+ onProgress?.(cursor);
2456
2464
  });
2457
- stream2.on("end", () => resolve3());
2465
+ stream2.on("end", () => resolve3({ completed: true, nextOffset: cursor }));
2458
2466
  stream2.on("error", (err) => {
2459
2467
  sink.onError(err, false);
2460
- resolve3();
2468
+ resolve3({ completed: false, nextOffset: cursor });
2461
2469
  });
2462
2470
  });
2463
2471
  }
@@ -2529,6 +2537,7 @@ var Correlator = class {
2529
2537
  #store;
2530
2538
  #pendingRequests = /* @__PURE__ */ new Map();
2531
2539
  #pendingResponses = /* @__PURE__ */ new Map();
2540
+ #changedIndexes = /* @__PURE__ */ new Set();
2532
2541
  #unsubscribe;
2533
2542
  pairIdx = [];
2534
2543
  latencyMs = [];
@@ -2548,6 +2557,11 @@ var Correlator = class {
2548
2557
  statusOf(idx) {
2549
2558
  return this.status[idx] ?? "n/a";
2550
2559
  }
2560
+ drainChangedIndexes() {
2561
+ const indexes = [...this.#changedIndexes];
2562
+ this.#changedIndexes.clear();
2563
+ return indexes;
2564
+ }
2551
2565
  /**
2552
2566
  * Advance time. Any request still pending older than `timeoutMs` becomes
2553
2567
  * 'unmatched'. Cheap O(pendingRequests).
@@ -2557,6 +2571,7 @@ var Correlator = class {
2557
2571
  const ts = this.#store.ts[idx];
2558
2572
  if (ts !== void 0 && nowMs - ts >= timeoutMs) {
2559
2573
  this.status[idx] = "unmatched";
2574
+ this.#changedIndexes.add(idx);
2560
2575
  this.#pendingRequests.delete(key);
2561
2576
  }
2562
2577
  }
@@ -2569,6 +2584,7 @@ var Correlator = class {
2569
2584
  reset() {
2570
2585
  this.#pendingRequests.clear();
2571
2586
  this.#pendingResponses.clear();
2587
+ this.#changedIndexes.clear();
2572
2588
  this.pairIdx.length = 0;
2573
2589
  this.latencyMs.length = 0;
2574
2590
  this.status.length = 0;
@@ -2600,9 +2616,11 @@ var Correlator = class {
2600
2616
  const displaced = this.#pendingRequests.get(key);
2601
2617
  if (displaced !== void 0) {
2602
2618
  this.status[displaced] = "orphan";
2619
+ this.#changedIndexes.add(displaced);
2603
2620
  }
2604
2621
  this.#pendingRequests.set(key, idx);
2605
2622
  this.status[idx] = "pending";
2623
+ this.#changedIndexes.add(idx);
2606
2624
  }
2607
2625
  #onResponse(idx, ev) {
2608
2626
  const key = correlationKeyForResponse(ev);
@@ -2615,6 +2633,7 @@ var Correlator = class {
2615
2633
  const displaced = this.#pendingResponses.get(key);
2616
2634
  if (displaced !== void 0) {
2617
2635
  this.status[displaced] = "orphan";
2636
+ this.#changedIndexes.add(displaced);
2618
2637
  }
2619
2638
  this.#pendingResponses.set(key, idx);
2620
2639
  }
@@ -2629,6 +2648,8 @@ var Correlator = class {
2629
2648
  const s = isError ? "error" : "ok";
2630
2649
  this.status[reqIdx] = s;
2631
2650
  this.status[respIdx] = s;
2651
+ this.#changedIndexes.add(reqIdx);
2652
+ this.#changedIndexes.add(respIdx);
2632
2653
  }
2633
2654
  };
2634
2655
  function isErrorResponse(ev) {
@@ -4680,6 +4701,9 @@ async function createAppState(opts) {
4680
4701
  const searchIdx = new SearchIndex();
4681
4702
  let seq = 0;
4682
4703
  let byteOffset = 0;
4704
+ let initialReadLoadedBytes = 0;
4705
+ let initialReadTotalBytes;
4706
+ let initialReadPhase = "idle";
4683
4707
  function emit(payload) {
4684
4708
  for (const l of listeners) {
4685
4709
  try {
@@ -4688,6 +4712,22 @@ async function createAppState(opts) {
4688
4712
  }
4689
4713
  }
4690
4714
  }
4715
+ function currentLoadProgress(phase = initialReadPhase) {
4716
+ const totalBytes = initialReadTotalBytes;
4717
+ const percent = totalBytes !== void 0 && totalBytes > 0 ? Math.min(100, Math.round(initialReadLoadedBytes / totalBytes * 100)) : void 0;
4718
+ return {
4719
+ kind: "load-progress",
4720
+ phase,
4721
+ loadedRows: rows.length,
4722
+ loadedBytes: initialReadLoadedBytes,
4723
+ ...totalBytes !== void 0 ? { totalBytes } : {},
4724
+ ...percent !== void 0 ? { percent } : {}
4725
+ };
4726
+ }
4727
+ function emitLoadProgress(phase) {
4728
+ initialReadPhase = phase;
4729
+ emit(currentLoadProgress());
4730
+ }
4691
4731
  let lastSeenServerSeq = null;
4692
4732
  function buildRow(idx) {
4693
4733
  const ev = store.at(idx);
@@ -4717,6 +4757,30 @@ async function createAppState(opts) {
4717
4757
  };
4718
4758
  return projectRow(ev, idx, status, latency, extras, pairMethod);
4719
4759
  }
4760
+ function buildPatchUpdates(indexes) {
4761
+ const updates = [];
4762
+ for (const idx of indexes) {
4763
+ const prev = rows[idx];
4764
+ if (!prev) continue;
4765
+ const status = correlator.statusOf(idx);
4766
+ const latencyMs = correlator.latencyOf(idx);
4767
+ const pairIdx = correlator.pairOf(idx);
4768
+ if (prev.status === status && prev.latencyMs === latencyMs && prev.pairIdx === pairIdx) {
4769
+ continue;
4770
+ }
4771
+ const latencyBand = bandFor(latencyMs);
4772
+ rows[idx] = { ...prev, status, latencyMs, latencyBand, pairIdx };
4773
+ updates.push({
4774
+ idx,
4775
+ status,
4776
+ latencyMs,
4777
+ latencyBand,
4778
+ ...prev.summary !== void 0 ? { summary: prev.summary } : {},
4779
+ pairIdx
4780
+ });
4781
+ }
4782
+ return updates;
4783
+ }
4720
4784
  const offStore = store.subscribe((range) => {
4721
4785
  const newRows = [];
4722
4786
  for (let i = range.from; i < range.to; i++) {
@@ -4728,30 +4792,27 @@ async function createAppState(opts) {
4728
4792
  if (ev) searchIdx.append(ev);
4729
4793
  }
4730
4794
  }
4731
- const updates = [];
4732
- for (let i = 0; i < range.from; i++) {
4733
- const prev = rows[i];
4734
- if (!prev) continue;
4735
- const status = correlator.statusOf(i);
4736
- const latencyMs = correlator.latencyOf(i);
4737
- const pairIdx = correlator.pairOf(i);
4738
- if (prev.status !== status || prev.latencyMs !== latencyMs || prev.pairIdx !== pairIdx) {
4739
- const latencyBand = bandFor(latencyMs);
4740
- rows[i] = { ...prev, status, latencyMs, latencyBand, pairIdx };
4741
- updates.push({
4742
- idx: i,
4743
- status,
4744
- latencyMs,
4745
- latencyBand,
4746
- ...prev.summary !== void 0 ? { summary: prev.summary } : {},
4747
- pairIdx
4748
- });
4749
- }
4750
- }
4795
+ const changedIndexes = correlator.drainChangedIndexes().filter((idx) => idx < range.from);
4796
+ const updates = buildPatchUpdates(changedIndexes);
4751
4797
  if (newRows.length > 0) emit({ kind: "append", rows: newRows, from: range.from });
4752
4798
  if (updates.length > 0) emit({ kind: "patch", updates });
4753
4799
  });
4754
4800
  const watcher = opts.host.watchLog(handle, {
4801
+ onInitialReadStart(info) {
4802
+ initialReadLoadedBytes = 0;
4803
+ initialReadTotalBytes = info.totalBytes;
4804
+ emitLoadProgress("loading");
4805
+ },
4806
+ onInitialReadProgress(info) {
4807
+ initialReadLoadedBytes = info.loadedBytes;
4808
+ initialReadTotalBytes = info.totalBytes;
4809
+ emitLoadProgress("loading");
4810
+ },
4811
+ onInitialReadComplete(info) {
4812
+ initialReadLoadedBytes = info.loadedBytes;
4813
+ initialReadTotalBytes = info.totalBytes;
4814
+ emitLoadProgress("complete");
4815
+ },
4755
4816
  onChunk(chunk, _byteOffset) {
4756
4817
  const text = decoder.decode(chunk, { stream: true });
4757
4818
  const newlineSize = text.includes("\r\n") ? 2 : 1;
@@ -4793,6 +4854,9 @@ async function createAppState(opts) {
4793
4854
  rows.length = 0;
4794
4855
  searchIdx.reset();
4795
4856
  lastSeenServerSeq = null;
4857
+ initialReadLoadedBytes = 0;
4858
+ initialReadTotalBytes = void 0;
4859
+ emitLoadProgress("idle");
4796
4860
  emit({ kind: "rotation", newSize: info.newSize, reason: info.reason });
4797
4861
  },
4798
4862
  onError(err, fatal) {
@@ -4806,26 +4870,7 @@ async function createAppState(opts) {
4806
4870
  function runFlush(nowMs) {
4807
4871
  const now = nowMs ?? Date.now();
4808
4872
  correlator.flush(now, unmatchedTimeoutMs);
4809
- const updates = [];
4810
- for (let i = 0; i < store.size(); i++) {
4811
- const prev = rows[i];
4812
- if (!prev) continue;
4813
- const status = correlator.statusOf(i);
4814
- const latencyMs = correlator.latencyOf(i);
4815
- const pairIdx = correlator.pairOf(i);
4816
- if (prev.status !== status || prev.latencyMs !== latencyMs || prev.pairIdx !== pairIdx) {
4817
- const latencyBand = bandFor(latencyMs);
4818
- rows[i] = { ...prev, status, latencyMs, latencyBand, pairIdx };
4819
- updates.push({
4820
- idx: i,
4821
- status,
4822
- latencyMs,
4823
- latencyBand,
4824
- ...prev.summary !== void 0 ? { summary: prev.summary } : {},
4825
- pairIdx
4826
- });
4827
- }
4828
- }
4873
+ const updates = buildPatchUpdates(correlator.drainChangedIndexes());
4829
4874
  if (updates.length > 0) emit({ kind: "patch", updates });
4830
4875
  }
4831
4876
  const flushIntervalMs = opts.flushIntervalMs ?? 1e3;
@@ -4835,7 +4880,7 @@ async function createAppState(opts) {
4835
4880
  meta,
4836
4881
  searchIndex: searchIdx,
4837
4882
  snapshot() {
4838
- return { meta, rows: rows.slice() };
4883
+ return { meta, rows: rows.slice(), loadProgress: currentLoadProgress() };
4839
4884
  },
4840
4885
  subscribe(l) {
4841
4886
  listeners.add(l);
@@ -7881,30 +7926,17 @@ function registerLogRoutes(app, sessions) {
7881
7926
  }
7882
7927
  return streamSSE(c, async (stream2) => {
7883
7928
  const a = initial;
7884
- const snap = a.appState.snapshot();
7885
- await stream2.writeSSE({
7886
- event: "snapshot-begin",
7887
- data: JSON.stringify({ meta: snap.meta, total: snap.rows.length })
7888
- });
7889
- for (let i = 0; i < snap.rows.length; i += SNAPSHOT_CHUNK) {
7890
- if (stream2.aborted || stream2.closed) return;
7891
- const chunk = snap.rows.slice(i, i + SNAPSHOT_CHUNK);
7892
- await stream2.writeSSE({
7893
- event: "snapshot-chunk",
7894
- data: JSON.stringify({ rows: chunk, from: i })
7895
- });
7896
- await stream2.sleep(0);
7897
- }
7898
- if (stream2.aborted || stream2.closed) return;
7899
- await stream2.writeSSE({ event: "snapshot-end", data: "{}" });
7900
7929
  const queue = [];
7930
+ let queueStart = 0;
7901
7931
  let pumping = false;
7932
+ let snapshotStreaming = true;
7933
+ const queuedFrameCount = () => queue.length - queueStart;
7902
7934
  const pump = async () => {
7903
- if (pumping) return;
7935
+ if (pumping || snapshotStreaming) return;
7904
7936
  pumping = true;
7905
7937
  try {
7906
- while (queue.length > 0 && !stream2.aborted && !stream2.closed) {
7907
- const msg = queue.shift();
7938
+ while (queuedFrameCount() > 0 && !stream2.aborted && !stream2.closed) {
7939
+ const msg = queue[queueStart++];
7908
7940
  if (!msg) break;
7909
7941
  try {
7910
7942
  await stream2.writeSSE({ event: msg.kind, data: JSON.stringify(msg) });
@@ -7913,6 +7945,10 @@ function registerLogRoutes(app, sessions) {
7913
7945
  }
7914
7946
  }
7915
7947
  } finally {
7948
+ if (queueStart === queue.length) {
7949
+ queue.length = 0;
7950
+ queueStart = 0;
7951
+ }
7916
7952
  pumping = false;
7917
7953
  }
7918
7954
  };
@@ -7920,6 +7956,56 @@ function registerLogRoutes(app, sessions) {
7920
7956
  queue.push(msg);
7921
7957
  void pump();
7922
7958
  });
7959
+ const snap = a.appState.snapshot();
7960
+ await stream2.writeSSE({
7961
+ event: "snapshot-begin",
7962
+ data: JSON.stringify({ meta: snap.meta, total: snap.rows.length })
7963
+ });
7964
+ for (let i = 0; i < snap.rows.length; i += SNAPSHOT_CHUNK) {
7965
+ if (stream2.aborted || stream2.closed) {
7966
+ off();
7967
+ return;
7968
+ }
7969
+ const chunk = snap.rows.slice(i, i + SNAPSHOT_CHUNK);
7970
+ await stream2.writeSSE({
7971
+ event: "snapshot-chunk",
7972
+ data: JSON.stringify({ rows: chunk, from: i })
7973
+ });
7974
+ await stream2.sleep(0);
7975
+ }
7976
+ if (stream2.aborted || stream2.closed) {
7977
+ off();
7978
+ return;
7979
+ }
7980
+ await stream2.writeSSE({ event: "snapshot-end", data: "{}" });
7981
+ if (snap.loadProgress.phase !== "idle") {
7982
+ await stream2.writeSSE({
7983
+ event: snap.loadProgress.kind,
7984
+ data: JSON.stringify(snap.loadProgress)
7985
+ });
7986
+ }
7987
+ snapshotStreaming = false;
7988
+ if (queuedFrameCount() > 0) {
7989
+ const queuedRows = queue.slice(queueStart).reduce((total, msg) => total + (msg.kind === "append" ? msg.rows.length : 0), 0);
7990
+ const backlog = {
7991
+ kind: "stream-backlog",
7992
+ queuedFrames: queuedFrameCount(),
7993
+ queuedRows
7994
+ };
7995
+ await stream2.writeSSE({ event: backlog.kind, data: JSON.stringify(backlog) });
7996
+ await pump();
7997
+ const clearedBacklog = {
7998
+ kind: "stream-backlog",
7999
+ queuedFrames: 0,
8000
+ queuedRows: 0
8001
+ };
8002
+ await stream2.writeSSE({
8003
+ event: clearedBacklog.kind,
8004
+ data: JSON.stringify(clearedBacklog)
8005
+ });
8006
+ } else {
8007
+ await pump();
8008
+ }
7923
8009
  let endRequested = false;
7924
8010
  const offChange = sessions.onChange(async () => {
7925
8011
  if (endRequested) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ahp-inspector",
3
- "version": "1.1.2",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "Local-first viewer for VS Code Agent Host Protocol (AHP) JSONL logs.",
6
6
  "keywords": [