ahp-inspector 1.4.2 → 1.5.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
@@ -464,6 +464,13 @@ function objectChild(parent, key) {
464
464
  return typeof child === "object" && child !== null ? child : null;
465
465
  }
466
466
  function candidateObjects(p) {
467
+ if (Array.isArray(p)) {
468
+ const items = [];
469
+ for (const item of p) {
470
+ if (typeof item === "object" && item !== null) items.push(item);
471
+ }
472
+ return items;
473
+ }
467
474
  const out = [p];
468
475
  const action = objectChild(p, "action");
469
476
  if (action) out.push(action);
@@ -478,7 +485,25 @@ function sessionFromObject(p) {
478
485
  const uri = session.uri;
479
486
  if (typeof uri === "string") return uri;
480
487
  }
481
- return asString(p.sessionId);
488
+ const sessionId = asString(p.sessionId);
489
+ if (sessionId) return sessionId;
490
+ const channel = asString(p.channel);
491
+ if (channel) return channel;
492
+ const terminal = asString(p.terminal);
493
+ if (terminal) return terminal;
494
+ const resource = asString(p.resource);
495
+ if (resource) return resource;
496
+ const external = asString(p.external);
497
+ if (external) return external;
498
+ const scheme = asString(p.scheme);
499
+ const path = asString(p.path);
500
+ if (scheme && path) return `${scheme}:${path}`;
501
+ const summary = p.summary;
502
+ if (typeof summary === "object" && summary !== null) {
503
+ const summaryResource = asString(summary.resource);
504
+ if (summaryResource) return summaryResource;
505
+ }
506
+ return null;
482
507
  }
483
508
  function turnFromObject(p) {
484
509
  const fromTop = asString(p.turnId);
@@ -2639,6 +2664,7 @@ import { Buffer as Buffer2 } from "buffer";
2639
2664
  import { basename as basename6 } from "path";
2640
2665
 
2641
2666
  // ../core/src/correlator.ts
2667
+ var MAX_PENDING = 1e4;
2642
2668
  var Correlator = class {
2643
2669
  #store;
2644
2670
  #pendingRequests = /* @__PURE__ */ new Map();
@@ -2652,6 +2678,14 @@ var Correlator = class {
2652
2678
  this.#store = store;
2653
2679
  this.#unsubscribe = store.subscribe((range) => this.#onAppend(range));
2654
2680
  }
2681
+ /** Outstanding requests awaiting a response (bounded by {@link MAX_PENDING}). */
2682
+ get pendingRequestCount() {
2683
+ return this.#pendingRequests.size;
2684
+ }
2685
+ /** Out-of-order responses awaiting a request (bounded by {@link MAX_PENDING}). */
2686
+ get pendingResponseCount() {
2687
+ return this.#pendingResponses.size;
2688
+ }
2655
2689
  pairOf(idx) {
2656
2690
  const v = this.pairIdx[idx];
2657
2691
  return v === void 0 || v < 0 ? null : v;
@@ -2723,6 +2757,16 @@ var Correlator = class {
2723
2757
  if (displaced !== void 0) {
2724
2758
  this.status[displaced] = "orphan";
2725
2759
  this.#changedIndexes.add(displaced);
2760
+ } else if (this.#pendingRequests.size >= MAX_PENDING) {
2761
+ const oldestKey = this.#pendingRequests.keys().next().value;
2762
+ if (oldestKey !== void 0) {
2763
+ const oldIdx = this.#pendingRequests.get(oldestKey);
2764
+ this.#pendingRequests.delete(oldestKey);
2765
+ if (oldIdx !== void 0) {
2766
+ this.status[oldIdx] = "unmatched";
2767
+ this.#changedIndexes.add(oldIdx);
2768
+ }
2769
+ }
2726
2770
  }
2727
2771
  this.#pendingRequests.set(key, idx);
2728
2772
  this.status[idx] = "pending";
@@ -2740,6 +2784,9 @@ var Correlator = class {
2740
2784
  if (displaced !== void 0) {
2741
2785
  this.status[displaced] = "orphan";
2742
2786
  this.#changedIndexes.add(displaced);
2787
+ } else if (this.#pendingResponses.size >= MAX_PENDING) {
2788
+ const oldestKey = this.#pendingResponses.keys().next().value;
2789
+ if (oldestKey !== void 0) this.#pendingResponses.delete(oldestKey);
2743
2790
  }
2744
2791
  this.#pendingResponses.set(key, idx);
2745
2792
  }
@@ -3000,7 +3047,7 @@ function refreshSummaryStatus(state) {
3000
3047
  }
3001
3048
  return { ...state, summary: { ...state.summary, status } };
3002
3049
  }
3003
- function endTurn(state, turnId, turnState, terminalStatus, error) {
3050
+ function endTurn(state, turnId, turnState, terminalStatus, error, now = Date.now) {
3004
3051
  if (!state.activeTurn || state.activeTurn.id !== turnId) {
3005
3052
  return state;
3006
3053
  }
@@ -3036,7 +3083,7 @@ function endTurn(state, turnId, turnState, terminalStatus, error) {
3036
3083
  ...state,
3037
3084
  turns: [...state.turns, turn],
3038
3085
  activeTurn: void 0,
3039
- summary: { ...state.summary, modifiedAt: Date.now() }
3086
+ summary: { ...state.summary, modifiedAt: now() }
3040
3087
  };
3041
3088
  delete next.inputRequests;
3042
3089
  return {
@@ -3044,7 +3091,7 @@ function endTurn(state, turnId, turnState, terminalStatus, error) {
3044
3091
  summary: { ...next.summary, status: summaryStatus(next, terminalStatus) }
3045
3092
  };
3046
3093
  }
3047
- function upsertInputRequest(state, request) {
3094
+ function upsertInputRequest(state, request, now = Date.now) {
3048
3095
  const existing = state.inputRequests ?? [];
3049
3096
  const idx = existing.findIndex((r) => r.id === request.id);
3050
3097
  const inputRequests = [...existing];
@@ -3060,7 +3107,7 @@ function upsertInputRequest(state, request) {
3060
3107
  summary: {
3061
3108
  ...next.summary,
3062
3109
  status: withStatusFlag(summaryStatus(next), 32 /* IsRead */, false),
3063
- modifiedAt: Date.now()
3110
+ modifiedAt: now()
3064
3111
  }
3065
3112
  };
3066
3113
  }
@@ -3113,7 +3160,7 @@ function updateResponsePart(state, turnId, partId, updater) {
3113
3160
  activeTurn: { ...activeTurn, responseParts }
3114
3161
  };
3115
3162
  }
3116
- function sessionReducer(state, action, log) {
3163
+ function sessionReducer(state, action, log, now = Date.now) {
3117
3164
  switch (action.type) {
3118
3165
  // ── Lifecycle ──────────────────────────────────────────────────────────
3119
3166
  case "session/ready" /* SessionReady */:
@@ -3144,7 +3191,7 @@ function sessionReducer(state, action, log) {
3144
3191
  summary: {
3145
3192
  ...next.summary,
3146
3193
  status: withStatusFlag(summaryStatus(next), 32 /* IsRead */, false),
3147
- modifiedAt: Date.now()
3194
+ modifiedAt: now()
3148
3195
  }
3149
3196
  };
3150
3197
  if (action.queuedMessageId) {
@@ -3177,11 +3224,11 @@ function sessionReducer(state, action, log) {
3177
3224
  }
3178
3225
  };
3179
3226
  case "session/turnComplete" /* SessionTurnComplete */:
3180
- return endTurn(state, action.turnId, "complete" /* Complete */);
3227
+ return endTurn(state, action.turnId, "complete" /* Complete */, void 0, void 0, now);
3181
3228
  case "session/turnCancelled" /* SessionTurnCancelled */:
3182
- return endTurn(state, action.turnId, "cancelled" /* Cancelled */);
3229
+ return endTurn(state, action.turnId, "cancelled" /* Cancelled */, void 0, void 0, now);
3183
3230
  case "session/error" /* SessionError */:
3184
- return endTurn(state, action.turnId, "error" /* Error */, 2 /* Error */, action.error);
3231
+ return endTurn(state, action.turnId, "error" /* Error */, 2 /* Error */, action.error, now);
3185
3232
  // ── Tool Call State Machine ───────────────────────────────────────────
3186
3233
  case "session/toolCallStart" /* SessionToolCallStart */:
3187
3234
  if (!state.activeTurn || state.activeTurn.id !== action.turnId) {
@@ -3353,7 +3400,7 @@ function sessionReducer(state, action, log) {
3353
3400
  case "session/titleChanged" /* SessionTitleChanged */:
3354
3401
  return {
3355
3402
  ...state,
3356
- summary: { ...state.summary, title: action.title, modifiedAt: Date.now() }
3403
+ summary: { ...state.summary, title: action.title, modifiedAt: now() }
3357
3404
  };
3358
3405
  case "session/usage" /* SessionUsage */:
3359
3406
  if (!state.activeTurn || state.activeTurn.id !== action.turnId) {
@@ -3373,7 +3420,7 @@ function sessionReducer(state, action, log) {
3373
3420
  case "session/modelChanged" /* SessionModelChanged */:
3374
3421
  return {
3375
3422
  ...state,
3376
- summary: { ...state.summary, model: action.model, modifiedAt: Date.now() }
3423
+ summary: { ...state.summary, model: action.model, modifiedAt: now() }
3377
3424
  };
3378
3425
  case "session/isReadChanged" /* SessionIsReadChanged */:
3379
3426
  return {
@@ -3414,7 +3461,7 @@ function sessionReducer(state, action, log) {
3414
3461
  },
3415
3462
  summary: {
3416
3463
  ...state.summary,
3417
- modifiedAt: Date.now()
3464
+ modifiedAt: now()
3418
3465
  }
3419
3466
  };
3420
3467
  }
@@ -3432,7 +3479,7 @@ function sessionReducer(state, action, log) {
3432
3479
  },
3433
3480
  summary: {
3434
3481
  ...state.summary,
3435
- modifiedAt: Date.now()
3482
+ modifiedAt: now()
3436
3483
  }
3437
3484
  };
3438
3485
  }
@@ -3515,7 +3562,7 @@ function sessionReducer(state, action, log) {
3515
3562
  ...state,
3516
3563
  turns,
3517
3564
  activeTurn: void 0,
3518
- summary: { ...state.summary, modifiedAt: Date.now() }
3565
+ summary: { ...state.summary, modifiedAt: now() }
3519
3566
  };
3520
3567
  delete next.inputRequests;
3521
3568
  return {
@@ -3525,7 +3572,7 @@ function sessionReducer(state, action, log) {
3525
3572
  }
3526
3573
  // ── Session Input Requests ─────────────────────────────────────────────
3527
3574
  case "session/inputRequested" /* SessionInputRequested */:
3528
- return upsertInputRequest(state, action.request);
3575
+ return upsertInputRequest(state, action.request, now);
3529
3576
  case "session/inputAnswerChanged" /* SessionInputAnswerChanged */: {
3530
3577
  const existing = state.inputRequests;
3531
3578
  const idx = existing?.findIndex((request2) => request2.id === action.requestId) ?? -1;
@@ -3547,7 +3594,7 @@ function sessionReducer(state, action, log) {
3547
3594
  return {
3548
3595
  ...state,
3549
3596
  inputRequests: updated,
3550
- summary: { ...state.summary, modifiedAt: Date.now() }
3597
+ summary: { ...state.summary, modifiedAt: now() }
3551
3598
  };
3552
3599
  }
3553
3600
  case "session/inputCompleted" /* SessionInputCompleted */: {
@@ -3566,7 +3613,7 @@ function sessionReducer(state, action, log) {
3566
3613
  }
3567
3614
  return {
3568
3615
  ...next,
3569
- summary: { ...next.summary, status: summaryStatus(next), modifiedAt: Date.now() }
3616
+ summary: { ...next.summary, status: summaryStatus(next), modifiedAt: now() }
3570
3617
  };
3571
3618
  }
3572
3619
  // ── Pending Messages ──────────────────────────────────────────────────
@@ -3979,15 +4026,20 @@ function applyEnvelope(ctx, envelope, eventIdx, eventTs) {
3979
4026
  details: { actionType: actionTypeOf(envelope.action) }
3980
4027
  });
3981
4028
  };
3982
- resource.state = withEventTime(eventTs, () => {
4029
+ resource.state = (() => {
3983
4030
  if (target.kind === "root") {
3984
4031
  return rootReducer(resource.state, envelope.action, log);
3985
4032
  }
3986
4033
  if (target.kind === "session") {
3987
- return sessionReducer(resource.state, envelope.action, log);
4034
+ return sessionReducer(
4035
+ resource.state,
4036
+ envelope.action,
4037
+ log,
4038
+ () => eventTs
4039
+ );
3988
4040
  }
3989
4041
  return terminalReducer(resource.state, envelope.action, log);
3990
- });
4042
+ })();
3991
4043
  resource.lastAppliedEventIdx = eventIdx;
3992
4044
  resource.lastServerSeq = envelope.serverSeq;
3993
4045
  linkAcceptedIntent(ctx, envelope);
@@ -4063,15 +4115,6 @@ function linkAcceptedIntent(ctx, envelope) {
4063
4115
  }
4064
4116
  ctx.intents[idx] = { ...intent, acceptedByServerSeq: envelope.serverSeq };
4065
4117
  }
4066
- function withEventTime(eventTs, fn) {
4067
- const original = Date.now;
4068
- Date.now = () => eventTs;
4069
- try {
4070
- return fn();
4071
- } finally {
4072
- Date.now = original;
4073
- }
4074
- }
4075
4118
  function readSnapshot(value) {
4076
4119
  if (!isRecord(value) || typeof value.resource !== "string" || typeof value.fromSeq !== "number") {
4077
4120
  return null;
@@ -4653,9 +4696,17 @@ function formatTs(ms) {
4653
4696
  )}`;
4654
4697
  }
4655
4698
  function formatSessionShort(sessionId) {
4656
- const parts = sessionId.split(/[/:]+/).filter(Boolean);
4657
- let label = parts.at(-1) ?? sessionId;
4658
- label = label.replace(/^session[-_:]?/i, "");
4699
+ const watched = formatResourceWatchChannel(sessionId);
4700
+ if (watched) return watched;
4701
+ return shortenIdLabel(sessionId, /^session[-_:]?/i);
4702
+ }
4703
+ function formatTurnShort(turnId) {
4704
+ return shortenIdLabel(turnId, /^turn[-_:]?/i);
4705
+ }
4706
+ function shortenIdLabel(id, stripPrefix) {
4707
+ const parts = id.split(/[/:]+/).filter(Boolean);
4708
+ let label = parts.at(-1) ?? id;
4709
+ label = label.replace(stripPrefix, "");
4659
4710
  label = label.replace(/[-_]\d{4}[-_]\d{2}[-_]\d{2}$/u, "");
4660
4711
  if (/^[0-9a-f]{16,}$/iu.test(label)) return label.slice(-8);
4661
4712
  const uuidFirstSegment = label.match(/^[0-9a-f]{8}(?=-[0-9a-f]{4}-)/iu)?.[0];
@@ -4663,6 +4714,24 @@ function formatSessionShort(sessionId) {
4663
4714
  if (label.length <= 18) return label;
4664
4715
  return `${label.slice(0, 17)}\u2026`;
4665
4716
  }
4717
+ function formatResourceWatchChannel(sessionId) {
4718
+ const match2 = sessionId.match(/^ahp-resource-watch:\/\/[^/]*\/(.+)$/u);
4719
+ if (!match2) return null;
4720
+ const encoded = match2[1];
4721
+ if (!encoded) return null;
4722
+ try {
4723
+ const normalized = encoded.replace(/-/g, "+").replace(/_/g, "/");
4724
+ const json = atob(normalized);
4725
+ const parsed = JSON.parse(json);
4726
+ const root = parsed.root ?? parsed.uri ?? parsed.resource;
4727
+ if (typeof root !== "string") return null;
4728
+ const tail = root.split(/[/\\]/u).filter(Boolean).at(-1) ?? root;
4729
+ const name = decodeURIComponent(tail);
4730
+ return `watch:${name.length <= 24 ? name : `${name.slice(0, 23)}\u2026`}`;
4731
+ } catch {
4732
+ return null;
4733
+ }
4734
+ }
4666
4735
  function payloadPreviewOf(raw2) {
4667
4736
  if (raw2 === void 0 || raw2 === null) return "";
4668
4737
  let src = raw2;
@@ -4743,7 +4812,7 @@ function projectRow(event, idx, status, latencyMs, extras = DEFAULT_EXTRAS, pair
4743
4812
  sessionId: session,
4744
4813
  sessionShort: session ? formatSessionShort(session) : null,
4745
4814
  turnId: turn,
4746
- turnShort: turn ? turn.slice(-6) : null,
4815
+ turnShort: turn ? formatTurnShort(turn) : null,
4747
4816
  keyId: idStr ? idStr.length > 12 ? idStr.slice(0, 12) : idStr : null,
4748
4817
  status,
4749
4818
  latencyMs,
@@ -5137,25 +5206,44 @@ async function createAppState(opts) {
5137
5206
  };
5138
5207
  }
5139
5208
 
5209
+ // ../server/src/origin.ts
5210
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
5211
+ function isAllowedOrigin(origin) {
5212
+ if (origin === void 0 || origin === null || origin === "") return true;
5213
+ if (origin === "null") return true;
5214
+ if (origin.startsWith("vscode-webview://")) return true;
5215
+ let url;
5216
+ try {
5217
+ url = new URL(origin);
5218
+ } catch {
5219
+ return false;
5220
+ }
5221
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
5222
+ return LOOPBACK_HOSTS.has(url.hostname);
5223
+ }
5224
+
5140
5225
  // ../server/src/cors.ts
5226
+ var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
5141
5227
  var corsMiddleware = async (c, next) => {
5142
5228
  const origin = c.req.header("origin");
5229
+ const allowed = isAllowedOrigin(origin);
5143
5230
  if (c.req.method === "OPTIONS") {
5144
5231
  const reqHeaders = c.req.header("access-control-request-headers") ?? "*";
5145
5232
  const reqMethod = c.req.header("access-control-request-method") ?? "GET";
5146
- return new Response(null, {
5147
- status: 204,
5148
- headers: {
5149
- "access-control-allow-origin": origin ?? "*",
5150
- "access-control-allow-methods": `${reqMethod}, GET, POST, OPTIONS`,
5151
- "access-control-allow-headers": reqHeaders,
5152
- "access-control-max-age": "600",
5153
- vary: "Origin"
5154
- }
5155
- });
5233
+ const headers = {
5234
+ "access-control-allow-methods": `${reqMethod}, GET, POST, OPTIONS`,
5235
+ "access-control-allow-headers": reqHeaders,
5236
+ "access-control-max-age": "600",
5237
+ vary: "Origin"
5238
+ };
5239
+ if (allowed) headers["access-control-allow-origin"] = origin ?? "*";
5240
+ return new Response(null, { status: 204, headers });
5241
+ }
5242
+ if (origin !== void 0 && !allowed && MUTATING_METHODS.has(c.req.method)) {
5243
+ return c.json({ code: "forbidden-origin", message: "forbidden-origin" }, 403);
5156
5244
  }
5157
5245
  await next();
5158
- if (origin) {
5246
+ if (allowed && origin !== void 0) {
5159
5247
  c.res.headers.set("access-control-allow-origin", origin);
5160
5248
  c.res.headers.append("vary", "Origin");
5161
5249
  }
@@ -7928,7 +8016,7 @@ function registerSearchRoutes(app, sessions) {
7928
8016
  // ../server/src/session-routes.ts
7929
8017
  function registerSessionRoutes(app, sessions) {
7930
8018
  app.get("/api/sessions/discover", async (c) => {
7931
- const r = await discoverVsCodeLogs();
8019
+ const r = await sessions.discover();
7932
8020
  return c.json({ candidates: r.candidates, truncated: r.truncated });
7933
8021
  });
7934
8022
  app.post("/api/sessions/open", async (c) => {
@@ -8122,7 +8210,9 @@ var streamSSE = (c, cb, onError) => {
8122
8210
  // ../server/src/sse-routes.ts
8123
8211
  var SNAPSHOT_CHUNK = 2e3;
8124
8212
  var PING_INTERVAL_MS = 2e4;
8213
+ var MAX_SSE_CONNECTIONS = 8;
8125
8214
  function registerLogRoutes(app, sessions) {
8215
+ let activeStreams = 0;
8126
8216
  app.get("/api/log/meta", (c) => {
8127
8217
  const a = sessions.current();
8128
8218
  if (!a) return c.body(null, 204);
@@ -8133,127 +8223,135 @@ function registerLogRoutes(app, sessions) {
8133
8223
  if (!initial) {
8134
8224
  return c.json({ code: "no-active-log" }, 409);
8135
8225
  }
8226
+ if (activeStreams >= MAX_SSE_CONNECTIONS) {
8227
+ return c.json({ code: "too-many-streams" }, 503);
8228
+ }
8229
+ activeStreams++;
8136
8230
  return streamSSE(c, async (stream2) => {
8137
- const a = initial;
8138
- const queue = [];
8139
- let queueStart = 0;
8140
- let pumping = false;
8141
- let snapshotStreaming = true;
8142
- const queuedFrameCount = () => queue.length - queueStart;
8143
- const pump = async () => {
8144
- if (pumping || snapshotStreaming) return;
8145
- pumping = true;
8146
- try {
8147
- while (queuedFrameCount() > 0 && !stream2.aborted && !stream2.closed) {
8148
- const msg = queue[queueStart++];
8149
- if (!msg) break;
8150
- try {
8151
- await stream2.writeSSE({ event: msg.kind, data: JSON.stringify(msg) });
8152
- } catch {
8153
- return;
8231
+ try {
8232
+ const a = initial;
8233
+ const queue = [];
8234
+ let queueStart = 0;
8235
+ let pumping = false;
8236
+ let snapshotStreaming = true;
8237
+ const queuedFrameCount = () => queue.length - queueStart;
8238
+ const pump = async () => {
8239
+ if (pumping || snapshotStreaming) return;
8240
+ pumping = true;
8241
+ try {
8242
+ while (queuedFrameCount() > 0 && !stream2.aborted && !stream2.closed) {
8243
+ const msg = queue[queueStart++];
8244
+ if (!msg) break;
8245
+ try {
8246
+ await stream2.writeSSE({ event: msg.kind, data: JSON.stringify(msg) });
8247
+ } catch {
8248
+ return;
8249
+ }
8154
8250
  }
8251
+ } finally {
8252
+ if (queueStart === queue.length) {
8253
+ queue.length = 0;
8254
+ queueStart = 0;
8255
+ }
8256
+ pumping = false;
8155
8257
  }
8156
- } finally {
8157
- if (queueStart === queue.length) {
8158
- queue.length = 0;
8159
- queueStart = 0;
8258
+ };
8259
+ const off = a.appState.subscribe((msg) => {
8260
+ queue.push(msg);
8261
+ void pump();
8262
+ });
8263
+ const snap = a.appState.snapshot();
8264
+ await stream2.writeSSE({
8265
+ event: "snapshot-begin",
8266
+ data: JSON.stringify({ meta: snap.meta, total: snap.rows.length })
8267
+ });
8268
+ for (let i = 0; i < snap.rows.length; i += SNAPSHOT_CHUNK) {
8269
+ if (stream2.aborted || stream2.closed) {
8270
+ off();
8271
+ return;
8160
8272
  }
8161
- pumping = false;
8273
+ const chunk = snap.rows.slice(i, i + SNAPSHOT_CHUNK);
8274
+ await stream2.writeSSE({
8275
+ event: "snapshot-chunk",
8276
+ data: JSON.stringify({ rows: chunk, from: i })
8277
+ });
8278
+ await stream2.sleep(0);
8162
8279
  }
8163
- };
8164
- const off = a.appState.subscribe((msg) => {
8165
- queue.push(msg);
8166
- void pump();
8167
- });
8168
- const snap = a.appState.snapshot();
8169
- await stream2.writeSSE({
8170
- event: "snapshot-begin",
8171
- data: JSON.stringify({ meta: snap.meta, total: snap.rows.length })
8172
- });
8173
- for (let i = 0; i < snap.rows.length; i += SNAPSHOT_CHUNK) {
8174
8280
  if (stream2.aborted || stream2.closed) {
8175
8281
  off();
8176
8282
  return;
8177
8283
  }
8178
- const chunk = snap.rows.slice(i, i + SNAPSHOT_CHUNK);
8179
- await stream2.writeSSE({
8180
- event: "snapshot-chunk",
8181
- data: JSON.stringify({ rows: chunk, from: i })
8182
- });
8183
- await stream2.sleep(0);
8184
- }
8185
- if (stream2.aborted || stream2.closed) {
8186
- off();
8187
- return;
8188
- }
8189
- await stream2.writeSSE({ event: "snapshot-end", data: "{}" });
8190
- if (snap.loadProgress.phase !== "idle") {
8191
- await stream2.writeSSE({
8192
- event: snap.loadProgress.kind,
8193
- data: JSON.stringify(snap.loadProgress)
8284
+ await stream2.writeSSE({ event: "snapshot-end", data: "{}" });
8285
+ if (snap.loadProgress.phase !== "idle") {
8286
+ await stream2.writeSSE({
8287
+ event: snap.loadProgress.kind,
8288
+ data: JSON.stringify(snap.loadProgress)
8289
+ });
8290
+ }
8291
+ snapshotStreaming = false;
8292
+ if (queuedFrameCount() > 0) {
8293
+ const queuedRows = queue.slice(queueStart).reduce((total, msg) => total + (msg.kind === "append" ? msg.rows.length : 0), 0);
8294
+ const backlog = {
8295
+ kind: "stream-backlog",
8296
+ queuedFrames: queuedFrameCount(),
8297
+ queuedRows
8298
+ };
8299
+ await stream2.writeSSE({ event: backlog.kind, data: JSON.stringify(backlog) });
8300
+ await pump();
8301
+ const clearedBacklog = {
8302
+ kind: "stream-backlog",
8303
+ queuedFrames: 0,
8304
+ queuedRows: 0
8305
+ };
8306
+ await stream2.writeSSE({
8307
+ event: clearedBacklog.kind,
8308
+ data: JSON.stringify(clearedBacklog)
8309
+ });
8310
+ } else {
8311
+ await pump();
8312
+ }
8313
+ let endRequested = false;
8314
+ const offChange = sessions.onChange(async () => {
8315
+ if (endRequested) return;
8316
+ endRequested = true;
8317
+ try {
8318
+ await stream2.writeSSE({ event: "log-reset", data: "{}" });
8319
+ } catch {
8320
+ }
8194
8321
  });
8195
- }
8196
- snapshotStreaming = false;
8197
- if (queuedFrameCount() > 0) {
8198
- const queuedRows = queue.slice(queueStart).reduce((total, msg) => total + (msg.kind === "append" ? msg.rows.length : 0), 0);
8199
- const backlog = {
8200
- kind: "stream-backlog",
8201
- queuedFrames: queuedFrameCount(),
8202
- queuedRows
8203
- };
8204
- await stream2.writeSSE({ event: backlog.kind, data: JSON.stringify(backlog) });
8205
- await pump();
8206
- const clearedBacklog = {
8207
- kind: "stream-backlog",
8208
- queuedFrames: 0,
8209
- queuedRows: 0
8210
- };
8211
- await stream2.writeSSE({
8212
- event: clearedBacklog.kind,
8213
- data: JSON.stringify(clearedBacklog)
8322
+ const pinger = setInterval(() => {
8323
+ if (stream2.aborted || stream2.closed) return;
8324
+ stream2.writeSSE({ event: "ping", data: "{}" }).catch(() => {
8325
+ });
8326
+ }, PING_INTERVAL_MS);
8327
+ await new Promise((resolveWait) => {
8328
+ if (stream2.aborted || stream2.closed) {
8329
+ resolveWait();
8330
+ return;
8331
+ }
8332
+ stream2.onAbort(() => resolveWait());
8333
+ const tick = setInterval(() => {
8334
+ if (endRequested) {
8335
+ clearInterval(tick);
8336
+ resolveWait();
8337
+ }
8338
+ }, 50);
8214
8339
  });
8215
- } else {
8216
- await pump();
8217
- }
8218
- let endRequested = false;
8219
- const offChange = sessions.onChange(async () => {
8220
- if (endRequested) return;
8221
- endRequested = true;
8340
+ clearInterval(pinger);
8222
8341
  try {
8223
- await stream2.writeSSE({ event: "log-reset", data: "{}" });
8342
+ off();
8224
8343
  } catch {
8225
8344
  }
8226
- });
8227
- const pinger = setInterval(() => {
8228
- if (stream2.aborted || stream2.closed) return;
8229
- stream2.writeSSE({ event: "ping", data: "{}" }).catch(() => {
8230
- });
8231
- }, PING_INTERVAL_MS);
8232
- await new Promise((resolveWait) => {
8233
- if (stream2.aborted || stream2.closed) {
8234
- resolveWait();
8235
- return;
8345
+ try {
8346
+ offChange();
8347
+ } catch {
8236
8348
  }
8237
- stream2.onAbort(() => resolveWait());
8238
- const tick = setInterval(() => {
8239
- if (endRequested) {
8240
- clearInterval(tick);
8241
- resolveWait();
8242
- }
8243
- }, 50);
8244
- });
8245
- clearInterval(pinger);
8246
- try {
8247
- off();
8248
- } catch {
8249
- }
8250
- try {
8251
- offChange();
8252
- } catch {
8253
- }
8254
- try {
8255
- await stream2.writeSSE({ event: "bye", data: "{}" });
8256
- } catch {
8349
+ try {
8350
+ await stream2.writeSSE({ event: "bye", data: "{}" });
8351
+ } catch {
8352
+ }
8353
+ } finally {
8354
+ activeStreams--;
8257
8355
  }
8258
8356
  });
8259
8357
  });
@@ -8608,6 +8706,41 @@ import { tmpdir } from "os";
8608
8706
  import { extname as extname2, join as join6, basename as pathBasename } from "path";
8609
8707
  var MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
8610
8708
  var UPLOAD_DIR_PREFIX = "ahp-inspector-upload-";
8709
+ var BodyTooLargeError = class extends Error {
8710
+ };
8711
+ async function readBodyCapped(body, maxBytes) {
8712
+ if (!body) return new Uint8Array(0);
8713
+ const reader = body.getReader();
8714
+ const chunks = [];
8715
+ let total = 0;
8716
+ for (; ; ) {
8717
+ let result;
8718
+ try {
8719
+ result = await reader.read();
8720
+ } catch {
8721
+ reader.releaseLock();
8722
+ throw new Error("read-failed");
8723
+ }
8724
+ if (result.done) break;
8725
+ const value = result.value;
8726
+ if (!value) continue;
8727
+ total += value.byteLength;
8728
+ if (total > maxBytes) {
8729
+ await reader.cancel().catch(() => {
8730
+ });
8731
+ throw new BodyTooLargeError();
8732
+ }
8733
+ chunks.push(value);
8734
+ }
8735
+ reader.releaseLock();
8736
+ const out = new Uint8Array(total);
8737
+ let offset = 0;
8738
+ for (const chunk of chunks) {
8739
+ out.set(chunk, offset);
8740
+ offset += chunk.byteLength;
8741
+ }
8742
+ return out;
8743
+ }
8611
8744
  function sanitizeFilename(raw2) {
8612
8745
  let decoded;
8613
8746
  try {
@@ -8658,8 +8791,9 @@ function createUploadStore() {
8658
8791
  }
8659
8792
  return { write, cleanupAllExcept, disposeAll };
8660
8793
  }
8661
- function registerUploadRoutes(app, sessions) {
8794
+ function registerUploadRoutes(app, sessions, opts) {
8662
8795
  const store = createUploadStore();
8796
+ const maxBytes = opts?.maxUploadBytes ?? MAX_UPLOAD_BYTES;
8663
8797
  const unsubscribe = sessions.onChange((active) => {
8664
8798
  if (active === null) {
8665
8799
  void store.disposeAll();
@@ -8680,24 +8814,24 @@ function registerUploadRoutes(app, sessions) {
8680
8814
  }
8681
8815
  const lengthHeader = c.req.header("content-length");
8682
8816
  const declaredLength = lengthHeader ? Number.parseInt(lengthHeader, 10) : Number.NaN;
8683
- if (Number.isFinite(declaredLength) && declaredLength > MAX_UPLOAD_BYTES) {
8817
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
8684
8818
  return c.json({ code: "too-large", message: "too-large" }, 413);
8685
8819
  }
8686
- let buf;
8820
+ let bytes;
8687
8821
  try {
8688
- buf = await c.req.arrayBuffer();
8689
- } catch {
8822
+ bytes = await readBodyCapped(c.req.raw.body, maxBytes);
8823
+ } catch (err) {
8824
+ if (err instanceof BodyTooLargeError) {
8825
+ return c.json({ code: "too-large", message: "too-large" }, 413);
8826
+ }
8690
8827
  return c.json({ code: "bad-request", message: "could not read body" }, 400);
8691
8828
  }
8692
- if (buf.byteLength === 0) {
8829
+ if (bytes.byteLength === 0) {
8693
8830
  return c.json({ code: "bad-request", message: "empty body" }, 400);
8694
8831
  }
8695
- if (buf.byteLength > MAX_UPLOAD_BYTES) {
8696
- return c.json({ code: "too-large", message: "too-large" }, 413);
8697
- }
8698
8832
  let tempPath;
8699
8833
  try {
8700
- tempPath = await store.write(safeName, new Uint8Array(buf));
8834
+ tempPath = await store.write(safeName, bytes);
8701
8835
  } catch {
8702
8836
  return c.json({ code: "io-error", message: "io-error" }, 500);
8703
8837
  }
@@ -8830,6 +8964,7 @@ function createLogSessionManager(opts) {
8830
8964
  }
8831
8965
  return {
8832
8966
  current: () => active,
8967
+ discover: () => opts.host.discoverLogs(),
8833
8968
  async open(input) {
8834
8969
  const next = chain.then(async () => {
8835
8970
  if ("path" in input) return doOpen(input.path);