ahp-inspector 1.2.0 → 1.3.1

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
@@ -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) {
@@ -2739,7 +2760,7 @@ var IS_CLIENT_DISPATCHABLE = {
2739
2760
  ["root/configChanged" /* RootConfigChanged */]: true,
2740
2761
  ["session/ready" /* SessionReady */]: false,
2741
2762
  ["session/creationFailed" /* SessionCreationFailed */]: false,
2742
- ["session/turnStarted" /* SessionTurnStarted */]: true,
2763
+ ["session/turnStarted" /* SessionTurnStarted */]: false,
2743
2764
  ["session/delta" /* SessionDelta */]: false,
2744
2765
  ["session/responsePart" /* SessionResponsePart */]: false,
2745
2766
  ["session/toolCallStart" /* SessionToolCallStart */]: false,
@@ -2767,13 +2788,19 @@ var IS_CLIENT_DISPATCHABLE = {
2767
2788
  ["session/inputCompleted" /* SessionInputCompleted */]: true,
2768
2789
  ["session/customizationsChanged" /* SessionCustomizationsChanged */]: false,
2769
2790
  ["session/customizationToggled" /* SessionCustomizationToggled */]: true,
2791
+ ["session/customizationUpdated" /* SessionCustomizationUpdated */]: false,
2770
2792
  ["session/truncated" /* SessionTruncated */]: true,
2771
2793
  ["session/isReadChanged" /* SessionIsReadChanged */]: true,
2772
2794
  ["session/isArchivedChanged" /* SessionIsArchivedChanged */]: true,
2773
2795
  ["session/activityChanged" /* SessionActivityChanged */]: false,
2774
- ["session/diffsChanged" /* SessionDiffsChanged */]: false,
2796
+ ["session/changesetsChanged" /* SessionChangesetsChanged */]: false,
2775
2797
  ["session/configChanged" /* SessionConfigChanged */]: true,
2776
2798
  ["session/metaChanged" /* SessionMetaChanged */]: false,
2799
+ ["changeset/statusChanged" /* ChangesetStatusChanged */]: false,
2800
+ ["changeset/fileSet" /* ChangesetFileSet */]: false,
2801
+ ["changeset/fileRemoved" /* ChangesetFileRemoved */]: false,
2802
+ ["changeset/operationsChanged" /* ChangesetOperationsChanged */]: false,
2803
+ ["changeset/cleared" /* ChangesetCleared */]: false,
2777
2804
  ["terminal/data" /* TerminalData */]: false,
2778
2805
  ["terminal/input" /* TerminalInput */]: true,
2779
2806
  ["terminal/resized" /* TerminalResized */]: true,
@@ -2787,11 +2814,39 @@ var IS_CLIENT_DISPATCHABLE = {
2787
2814
  ["terminal/commandFinished" /* TerminalCommandFinished */]: false
2788
2815
  };
2789
2816
 
2790
- // ../protocol/src/reducers.ts
2817
+ // ../protocol/src/common/reducer-helpers.ts
2791
2818
  function softAssertNever(value, log) {
2792
2819
  const msg = `Unhandled action type: ${JSON.stringify(value)}`;
2793
2820
  (log ?? console.warn)(msg);
2794
2821
  }
2822
+
2823
+ // ../protocol/src/channels-root/reducer.ts
2824
+ function rootReducer(state, action, log) {
2825
+ switch (action.type) {
2826
+ case "root/agentsChanged" /* RootAgentsChanged */:
2827
+ return { ...state, agents: action.agents };
2828
+ case "root/activeSessionsChanged" /* RootActiveSessionsChanged */:
2829
+ return { ...state, activeSessions: action.activeSessions };
2830
+ case "root/terminalsChanged" /* RootTerminalsChanged */:
2831
+ return { ...state, terminals: action.terminals };
2832
+ case "root/configChanged" /* RootConfigChanged */:
2833
+ if (!state.config) {
2834
+ return state;
2835
+ }
2836
+ return {
2837
+ ...state,
2838
+ config: {
2839
+ ...state.config,
2840
+ values: action.replace ? { ...action.config } : { ...state.config.values, ...action.config }
2841
+ }
2842
+ };
2843
+ default:
2844
+ softAssertNever(action, log);
2845
+ return state;
2846
+ }
2847
+ }
2848
+
2849
+ // ../protocol/src/channels-session/reducer.ts
2795
2850
  function tcBase(tc) {
2796
2851
  return {
2797
2852
  toolCallId: tc.toolCallId,
@@ -2952,30 +3007,6 @@ function updateResponsePart(state, turnId, partId, updater) {
2952
3007
  activeTurn: { ...activeTurn, responseParts }
2953
3008
  };
2954
3009
  }
2955
- function rootReducer(state, action, log) {
2956
- switch (action.type) {
2957
- case "root/agentsChanged" /* RootAgentsChanged */:
2958
- return { ...state, agents: action.agents };
2959
- case "root/activeSessionsChanged" /* RootActiveSessionsChanged */:
2960
- return { ...state, activeSessions: action.activeSessions };
2961
- case "root/terminalsChanged" /* RootTerminalsChanged */:
2962
- return { ...state, terminals: action.terminals };
2963
- case "root/configChanged" /* RootConfigChanged */:
2964
- if (!state.config) {
2965
- return state;
2966
- }
2967
- return {
2968
- ...state,
2969
- config: {
2970
- ...state.config,
2971
- values: action.replace ? { ...action.config } : { ...state.config.values, ...action.config }
2972
- }
2973
- };
2974
- default:
2975
- softAssertNever(action, log);
2976
- return state;
2977
- }
2978
- }
2979
3010
  function sessionReducer(state, action, log) {
2980
3011
  switch (action.type) {
2981
3012
  // ── Lifecycle ──────────────────────────────────────────────────────────
@@ -3259,26 +3290,46 @@ function sessionReducer(state, action, log) {
3259
3290
  ...state,
3260
3291
  summary: { ...state.summary, activity: action.activity }
3261
3292
  };
3262
- case "session/diffsChanged" /* SessionDiffsChanged */:
3263
- return {
3264
- ...state,
3265
- summary: { ...state.summary, diffs: action.diffs }
3266
- };
3267
- case "session/configChanged" /* SessionConfigChanged */:
3293
+ case "session/changesetsChanged" /* SessionChangesetsChanged */: {
3294
+ const { changesets: _omit, ...summaryWithoutChangesets } = state.summary;
3295
+ const newSummary = action.changesets ? { ...summaryWithoutChangesets, changesets: action.changesets } : summaryWithoutChangesets;
3296
+ return { ...state, summary: newSummary };
3297
+ }
3298
+ case "session/configChanged" /* SessionConfigChanged */: {
3268
3299
  if (!state.config) {
3300
+ if (!action.schema) {
3301
+ return state;
3302
+ }
3303
+ return {
3304
+ ...state,
3305
+ config: {
3306
+ schema: action.schema,
3307
+ values: action.config ? { ...action.config } : {}
3308
+ },
3309
+ summary: {
3310
+ ...state.summary,
3311
+ modifiedAt: Date.now()
3312
+ }
3313
+ };
3314
+ }
3315
+ const values = action.config !== void 0 ? action.replace ? { ...action.config } : { ...state.config.values, ...action.config } : state.config.values;
3316
+ const schema = action.schema ?? state.config.schema;
3317
+ if (values === state.config.values && schema === state.config.schema) {
3269
3318
  return state;
3270
3319
  }
3271
3320
  return {
3272
3321
  ...state,
3273
3322
  config: {
3274
3323
  ...state.config,
3275
- values: action.replace ? { ...action.config } : { ...state.config.values, ...action.config }
3324
+ schema,
3325
+ values
3276
3326
  },
3277
3327
  summary: {
3278
3328
  ...state.summary,
3279
3329
  modifiedAt: Date.now()
3280
3330
  }
3281
3331
  };
3332
+ }
3282
3333
  case "session/metaChanged" /* SessionMetaChanged */:
3283
3334
  return { ...state, _meta: action._meta };
3284
3335
  case "session/serverToolsChanged" /* SessionServerToolsChanged */:
@@ -3312,6 +3363,36 @@ function sessionReducer(state, action, log) {
3312
3363
  updated[idx] = { ...list[idx], enabled: action.enabled };
3313
3364
  return { ...state, customizations: updated };
3314
3365
  }
3366
+ case "session/customizationUpdated" /* SessionCustomizationUpdated */: {
3367
+ const list = state.customizations ?? [];
3368
+ const idx = list.findIndex((c) => c.customization.uri === action.customization.uri);
3369
+ if (idx < 0) {
3370
+ const inserted = {
3371
+ customization: action.customization,
3372
+ enabled: action.enabled ?? false
3373
+ };
3374
+ if (action.status !== void 0) {
3375
+ inserted.status = action.status;
3376
+ }
3377
+ if (action.statusMessage !== void 0) {
3378
+ inserted.statusMessage = action.statusMessage;
3379
+ }
3380
+ return { ...state, customizations: [...list, inserted] };
3381
+ }
3382
+ const updated = [...list];
3383
+ const next = { ...list[idx], customization: action.customization };
3384
+ if (action.enabled !== void 0) {
3385
+ next.enabled = action.enabled;
3386
+ }
3387
+ if (action.status !== void 0) {
3388
+ next.status = action.status;
3389
+ }
3390
+ if (action.statusMessage !== void 0) {
3391
+ next.statusMessage = action.statusMessage;
3392
+ }
3393
+ updated[idx] = next;
3394
+ return { ...state, customizations: updated };
3395
+ }
3315
3396
  // ── Truncation ────────────────────────────────────────────────────────
3316
3397
  case "session/truncated" /* SessionTruncated */: {
3317
3398
  let turns;
@@ -3437,6 +3518,8 @@ function sessionReducer(state, action, log) {
3437
3518
  return state;
3438
3519
  }
3439
3520
  }
3521
+
3522
+ // ../protocol/src/channels-terminal/reducer.ts
3440
3523
  function terminalReducer(state, action, log) {
3441
3524
  switch (action.type) {
3442
3525
  case "terminal/data" /* TerminalData */: {
@@ -3541,13 +3624,19 @@ var ACTION_INTRODUCED_IN = {
3541
3624
  ["session/inputCompleted" /* SessionInputCompleted */]: "0.1.0",
3542
3625
  ["session/customizationsChanged" /* SessionCustomizationsChanged */]: "0.1.0",
3543
3626
  ["session/customizationToggled" /* SessionCustomizationToggled */]: "0.1.0",
3627
+ ["session/customizationUpdated" /* SessionCustomizationUpdated */]: "0.1.0",
3544
3628
  ["session/truncated" /* SessionTruncated */]: "0.1.0",
3545
3629
  ["session/isReadChanged" /* SessionIsReadChanged */]: "0.1.0",
3546
3630
  ["session/isArchivedChanged" /* SessionIsArchivedChanged */]: "0.1.0",
3547
3631
  ["session/activityChanged" /* SessionActivityChanged */]: "0.1.0",
3548
- ["session/diffsChanged" /* SessionDiffsChanged */]: "0.1.0",
3632
+ ["session/changesetsChanged" /* SessionChangesetsChanged */]: "0.2.0",
3549
3633
  ["session/configChanged" /* SessionConfigChanged */]: "0.1.0",
3550
3634
  ["session/metaChanged" /* SessionMetaChanged */]: "0.1.0",
3635
+ ["changeset/statusChanged" /* ChangesetStatusChanged */]: "0.2.0",
3636
+ ["changeset/fileSet" /* ChangesetFileSet */]: "0.2.0",
3637
+ ["changeset/fileRemoved" /* ChangesetFileRemoved */]: "0.2.0",
3638
+ ["changeset/operationsChanged" /* ChangesetOperationsChanged */]: "0.2.0",
3639
+ ["changeset/cleared" /* ChangesetCleared */]: "0.2.0",
3551
3640
  ["root/terminalsChanged" /* RootTerminalsChanged */]: "0.1.0",
3552
3641
  ["root/configChanged" /* RootConfigChanged */]: "0.1.0",
3553
3642
  ["terminal/data" /* TerminalData */]: "0.1.0",
@@ -3562,12 +3651,6 @@ var ACTION_INTRODUCED_IN = {
3562
3651
  ["terminal/commandExecuted" /* TerminalCommandExecuted */]: "0.1.0",
3563
3652
  ["terminal/commandFinished" /* TerminalCommandFinished */]: "0.1.0"
3564
3653
  };
3565
- var NOTIFICATION_INTRODUCED_IN = {
3566
- ["notify/sessionAdded" /* SessionAdded */]: "0.1.0",
3567
- ["notify/sessionRemoved" /* SessionRemoved */]: "0.1.0",
3568
- ["notify/sessionSummaryChanged" /* SessionSummaryChanged */]: "0.1.0",
3569
- ["notify/authRequired" /* AuthRequired */]: "0.1.0"
3570
- };
3571
3654
 
3572
3655
  // ../core/src/replay.ts
3573
3656
  var ROOT_RESOURCE = "agenthost:/root";
@@ -3747,7 +3830,7 @@ function installSnapshot(ctx, value, eventIdx) {
3747
3830
  });
3748
3831
  }
3749
3832
  function applyEnvelope(ctx, envelope, eventIdx, eventTs) {
3750
- const target = inferActionTarget(envelope.action);
3833
+ const target = inferActionTarget(envelope.action, envelope.channel);
3751
3834
  if (!target) {
3752
3835
  addDiagnostic(ctx, {
3753
3836
  code: "unknown-action",
@@ -3838,7 +3921,8 @@ function recordClientIntent(ctx, event, eventIdx) {
3838
3921
  const action = isRecord(params) && isRecord(params.action) ? params.action : null;
3839
3922
  const clientSeq = isRecord(params) && typeof params.clientSeq === "number" ? params.clientSeq : null;
3840
3923
  const actionType = actionTypeOf(action);
3841
- const resource = action ? inferActionTarget(action) : null;
3924
+ const channel = isRecord(params) && typeof params.channel === "string" ? params.channel : void 0;
3925
+ const resource = action ? inferActionTarget(action, channel) : null;
3842
3926
  const intent = {
3843
3927
  eventIdx,
3844
3928
  ts: event.ts,
@@ -3896,7 +3980,7 @@ function readSnapshot(value) {
3896
3980
  };
3897
3981
  }
3898
3982
  function readActionEnvelope(value) {
3899
- if (!isRecord(value) || typeof value.serverSeq !== "number" || !isRecord(value.action)) {
3983
+ if (!isRecord(value) || typeof value.channel !== "string" || typeof value.serverSeq !== "number" || !isRecord(value.action)) {
3900
3984
  return null;
3901
3985
  }
3902
3986
  if (typeof value.action.type !== "string") {
@@ -3904,6 +3988,7 @@ function readActionEnvelope(value) {
3904
3988
  }
3905
3989
  const origin = isRecord(value.origin) && typeof value.origin.clientId === "string" && typeof value.origin.clientSeq === "number" ? { clientId: value.origin.clientId, clientSeq: value.origin.clientSeq } : void 0;
3906
3990
  return {
3991
+ channel: value.channel,
3907
3992
  action: value.action,
3908
3993
  serverSeq: value.serverSeq,
3909
3994
  origin,
@@ -3925,20 +4010,20 @@ function inferSnapshotKind(snapshot) {
3925
4010
  }
3926
4011
  return "unknown";
3927
4012
  }
3928
- function inferActionTarget(action) {
4013
+ function inferActionTarget(action, channel) {
3929
4014
  const type = actionTypeOf(action);
3930
4015
  if (!type) {
3931
4016
  return null;
3932
4017
  }
3933
4018
  if (type.startsWith("root/")) {
3934
- return { kind: "root", uri: ROOT_RESOURCE };
4019
+ return { kind: "root", uri: channel ?? ROOT_RESOURCE };
3935
4020
  }
3936
4021
  if (type.startsWith("session/")) {
3937
- const session = isRecord(action) && typeof action.session === "string" ? action.session : null;
4022
+ const session = channel ?? (isRecord(action) && typeof action.session === "string" ? action.session : null);
3938
4023
  return session ? { kind: "session", uri: session } : null;
3939
4024
  }
3940
4025
  if (type.startsWith("terminal/")) {
3941
- const terminal = isRecord(action) && typeof action.terminal === "string" ? action.terminal : null;
4026
+ const terminal = channel ?? (isRecord(action) && typeof action.terminal === "string" ? action.terminal : null);
3942
4027
  return terminal ? { kind: "terminal", uri: terminal } : null;
3943
4028
  }
3944
4029
  return null;
@@ -4103,12 +4188,14 @@ function readKnownAction(value) {
4103
4188
  }
4104
4189
  function readActionEnvelope2(value) {
4105
4190
  if (!isRecord2(value)) return null;
4191
+ if (typeof value.channel !== "string") return null;
4106
4192
  if (typeof value.serverSeq !== "number") return null;
4107
4193
  const action = asRecord(value.action);
4108
4194
  if (!action || typeof action.type !== "string") return null;
4109
4195
  const origin = isRecord2(value.origin) && typeof value.origin.clientId === "string" && typeof value.origin.clientSeq === "number" ? { clientId: value.origin.clientId, clientSeq: value.origin.clientSeq } : void 0;
4110
4196
  const known = readKnownAction(action);
4111
4197
  return {
4198
+ channel: value.channel,
4112
4199
  action: known ?? action,
4113
4200
  serverSeq: value.serverSeq,
4114
4201
  origin,
@@ -4194,7 +4281,7 @@ function summarizeResponse(view) {
4194
4281
  }
4195
4282
  function summarizeAction(view) {
4196
4283
  if (isKnownAction(view.action)) {
4197
- return summarizeKnownAction(view.action);
4284
+ return summarizeKnownAction(view.action, view.envelope?.channel ?? null);
4198
4285
  }
4199
4286
  return summarizeUnknownAction(view.action);
4200
4287
  }
@@ -4207,7 +4294,7 @@ function summarizeProtocolNotification(view) {
4207
4294
  function summarizeLog(view) {
4208
4295
  return `log ${clip(view.message ?? "details unavailable")}`;
4209
4296
  }
4210
- function summarizeKnownAction(action) {
4297
+ function summarizeKnownAction(action, channel) {
4211
4298
  switch (action.type) {
4212
4299
  case "session/delta" /* SessionDelta */:
4213
4300
  return `delta "${clip(action.content)}"`;
@@ -4216,13 +4303,13 @@ function summarizeKnownAction(action) {
4216
4303
  case "session/toolCallStart" /* SessionToolCallStart */:
4217
4304
  return `tool start ${action.toolName}${action.displayName ? ` (${clip(action.displayName, 32)})` : ""}`;
4218
4305
  case "session/toolCallDelta" /* SessionToolCallDelta */:
4219
- return `tool delta ${action.toolCallId} +${action.content.length}b`;
4306
+ return summarizeToolCallDelta(action);
4220
4307
  case "session/toolCallReady" /* SessionToolCallReady */:
4221
4308
  return `tool ready ${action.toolCallId} ${stringOrMd(action.invocationMessage, 48)}`;
4222
4309
  case "session/toolCallComplete" /* SessionToolCallComplete */:
4223
4310
  return `tool result ${action.toolCallId} success=${action.result.success}`;
4224
4311
  case "session/toolCallContentChanged" /* SessionToolCallContentChanged */:
4225
- return `tool content ${action.toolCallId} (${action.content.length} parts)`;
4312
+ return summarizeToolCallContentChanged(action);
4226
4313
  case "session/turnStarted" /* SessionTurnStarted */:
4227
4314
  return `turn start ${shortId(action.turnId)}`;
4228
4315
  case "session/turnComplete" /* SessionTurnComplete */:
@@ -4240,15 +4327,23 @@ function summarizeKnownAction(action) {
4240
4327
  case "session/reasoning" /* SessionReasoning */:
4241
4328
  return `reasoning "${clip(action.content, 60)}"`;
4242
4329
  case "session/ready" /* SessionReady */:
4243
- return `session ready ${shortId(action.session)}`;
4330
+ return channel ? `session ready ${shortId(channel)}` : "session ready";
4244
4331
  case "terminal/data" /* TerminalData */:
4245
- return `terminal data ${shortId(action.terminal)} +${action.data.length}b`;
4332
+ return channel ? `terminal data ${shortId(channel)} +${action.data.length}b` : `terminal data +${action.data.length}b`;
4246
4333
  case "terminal/commandFinished" /* TerminalCommandFinished */:
4247
4334
  return `terminal cmd done exit=${action.exitCode ?? "?"}`;
4248
4335
  default:
4249
4336
  return innerActionTypeAndDetail(toUnknownPayload(action));
4250
4337
  }
4251
4338
  }
4339
+ function summarizeToolCallDelta(action) {
4340
+ const content = typeof action.content === "string" ? action.content : null;
4341
+ return content === null ? `tool delta ${action.toolCallId}` : `tool delta ${action.toolCallId} +${content.length}b`;
4342
+ }
4343
+ function summarizeToolCallContentChanged(action) {
4344
+ const content = Array.isArray(action.content) ? action.content : null;
4345
+ return content === null ? `tool content ${action.toolCallId}` : `tool content ${action.toolCallId} (${content.length} parts)`;
4346
+ }
4252
4347
  function summarizeUnknownAction(action) {
4253
4348
  if (!action.type) return "action details unavailable";
4254
4349
  const t = action.type;
@@ -4680,6 +4775,9 @@ async function createAppState(opts) {
4680
4775
  const searchIdx = new SearchIndex();
4681
4776
  let seq = 0;
4682
4777
  let byteOffset = 0;
4778
+ let initialReadLoadedBytes = 0;
4779
+ let initialReadTotalBytes;
4780
+ let initialReadPhase = "idle";
4683
4781
  function emit(payload) {
4684
4782
  for (const l of listeners) {
4685
4783
  try {
@@ -4688,6 +4786,22 @@ async function createAppState(opts) {
4688
4786
  }
4689
4787
  }
4690
4788
  }
4789
+ function currentLoadProgress(phase = initialReadPhase) {
4790
+ const totalBytes = initialReadTotalBytes;
4791
+ const percent = totalBytes !== void 0 && totalBytes > 0 ? Math.min(100, Math.round(initialReadLoadedBytes / totalBytes * 100)) : void 0;
4792
+ return {
4793
+ kind: "load-progress",
4794
+ phase,
4795
+ loadedRows: rows.length,
4796
+ loadedBytes: initialReadLoadedBytes,
4797
+ ...totalBytes !== void 0 ? { totalBytes } : {},
4798
+ ...percent !== void 0 ? { percent } : {}
4799
+ };
4800
+ }
4801
+ function emitLoadProgress(phase) {
4802
+ initialReadPhase = phase;
4803
+ emit(currentLoadProgress());
4804
+ }
4691
4805
  let lastSeenServerSeq = null;
4692
4806
  function buildRow(idx) {
4693
4807
  const ev = store.at(idx);
@@ -4717,6 +4831,30 @@ async function createAppState(opts) {
4717
4831
  };
4718
4832
  return projectRow(ev, idx, status, latency, extras, pairMethod);
4719
4833
  }
4834
+ function buildPatchUpdates(indexes) {
4835
+ const updates = [];
4836
+ for (const idx of indexes) {
4837
+ const prev = rows[idx];
4838
+ if (!prev) continue;
4839
+ const status = correlator.statusOf(idx);
4840
+ const latencyMs = correlator.latencyOf(idx);
4841
+ const pairIdx = correlator.pairOf(idx);
4842
+ if (prev.status === status && prev.latencyMs === latencyMs && prev.pairIdx === pairIdx) {
4843
+ continue;
4844
+ }
4845
+ const latencyBand = bandFor(latencyMs);
4846
+ rows[idx] = { ...prev, status, latencyMs, latencyBand, pairIdx };
4847
+ updates.push({
4848
+ idx,
4849
+ status,
4850
+ latencyMs,
4851
+ latencyBand,
4852
+ ...prev.summary !== void 0 ? { summary: prev.summary } : {},
4853
+ pairIdx
4854
+ });
4855
+ }
4856
+ return updates;
4857
+ }
4720
4858
  const offStore = store.subscribe((range) => {
4721
4859
  const newRows = [];
4722
4860
  for (let i = range.from; i < range.to; i++) {
@@ -4728,30 +4866,27 @@ async function createAppState(opts) {
4728
4866
  if (ev) searchIdx.append(ev);
4729
4867
  }
4730
4868
  }
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
- }
4869
+ const changedIndexes = correlator.drainChangedIndexes().filter((idx) => idx < range.from);
4870
+ const updates = buildPatchUpdates(changedIndexes);
4751
4871
  if (newRows.length > 0) emit({ kind: "append", rows: newRows, from: range.from });
4752
4872
  if (updates.length > 0) emit({ kind: "patch", updates });
4753
4873
  });
4754
4874
  const watcher = opts.host.watchLog(handle, {
4875
+ onInitialReadStart(info) {
4876
+ initialReadLoadedBytes = 0;
4877
+ initialReadTotalBytes = info.totalBytes;
4878
+ emitLoadProgress("loading");
4879
+ },
4880
+ onInitialReadProgress(info) {
4881
+ initialReadLoadedBytes = info.loadedBytes;
4882
+ initialReadTotalBytes = info.totalBytes;
4883
+ emitLoadProgress("loading");
4884
+ },
4885
+ onInitialReadComplete(info) {
4886
+ initialReadLoadedBytes = info.loadedBytes;
4887
+ initialReadTotalBytes = info.totalBytes;
4888
+ emitLoadProgress("complete");
4889
+ },
4755
4890
  onChunk(chunk, _byteOffset) {
4756
4891
  const text = decoder.decode(chunk, { stream: true });
4757
4892
  const newlineSize = text.includes("\r\n") ? 2 : 1;
@@ -4793,6 +4928,9 @@ async function createAppState(opts) {
4793
4928
  rows.length = 0;
4794
4929
  searchIdx.reset();
4795
4930
  lastSeenServerSeq = null;
4931
+ initialReadLoadedBytes = 0;
4932
+ initialReadTotalBytes = void 0;
4933
+ emitLoadProgress("idle");
4796
4934
  emit({ kind: "rotation", newSize: info.newSize, reason: info.reason });
4797
4935
  },
4798
4936
  onError(err, fatal) {
@@ -4806,26 +4944,7 @@ async function createAppState(opts) {
4806
4944
  function runFlush(nowMs) {
4807
4945
  const now = nowMs ?? Date.now();
4808
4946
  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
- }
4947
+ const updates = buildPatchUpdates(correlator.drainChangedIndexes());
4829
4948
  if (updates.length > 0) emit({ kind: "patch", updates });
4830
4949
  }
4831
4950
  const flushIntervalMs = opts.flushIntervalMs ?? 1e3;
@@ -4835,7 +4954,7 @@ async function createAppState(opts) {
4835
4954
  meta,
4836
4955
  searchIndex: searchIdx,
4837
4956
  snapshot() {
4838
- return { meta, rows: rows.slice() };
4957
+ return { meta, rows: rows.slice(), loadProgress: currentLoadProgress() };
4839
4958
  },
4840
4959
  subscribe(l) {
4841
4960
  listeners.add(l);
@@ -7881,30 +8000,17 @@ function registerLogRoutes(app, sessions) {
7881
8000
  }
7882
8001
  return streamSSE(c, async (stream2) => {
7883
8002
  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
8003
  const queue = [];
8004
+ let queueStart = 0;
7901
8005
  let pumping = false;
8006
+ let snapshotStreaming = true;
8007
+ const queuedFrameCount = () => queue.length - queueStart;
7902
8008
  const pump = async () => {
7903
- if (pumping) return;
8009
+ if (pumping || snapshotStreaming) return;
7904
8010
  pumping = true;
7905
8011
  try {
7906
- while (queue.length > 0 && !stream2.aborted && !stream2.closed) {
7907
- const msg = queue.shift();
8012
+ while (queuedFrameCount() > 0 && !stream2.aborted && !stream2.closed) {
8013
+ const msg = queue[queueStart++];
7908
8014
  if (!msg) break;
7909
8015
  try {
7910
8016
  await stream2.writeSSE({ event: msg.kind, data: JSON.stringify(msg) });
@@ -7913,6 +8019,10 @@ function registerLogRoutes(app, sessions) {
7913
8019
  }
7914
8020
  }
7915
8021
  } finally {
8022
+ if (queueStart === queue.length) {
8023
+ queue.length = 0;
8024
+ queueStart = 0;
8025
+ }
7916
8026
  pumping = false;
7917
8027
  }
7918
8028
  };
@@ -7920,6 +8030,56 @@ function registerLogRoutes(app, sessions) {
7920
8030
  queue.push(msg);
7921
8031
  void pump();
7922
8032
  });
8033
+ const snap = a.appState.snapshot();
8034
+ await stream2.writeSSE({
8035
+ event: "snapshot-begin",
8036
+ data: JSON.stringify({ meta: snap.meta, total: snap.rows.length })
8037
+ });
8038
+ for (let i = 0; i < snap.rows.length; i += SNAPSHOT_CHUNK) {
8039
+ if (stream2.aborted || stream2.closed) {
8040
+ off();
8041
+ return;
8042
+ }
8043
+ const chunk = snap.rows.slice(i, i + SNAPSHOT_CHUNK);
8044
+ await stream2.writeSSE({
8045
+ event: "snapshot-chunk",
8046
+ data: JSON.stringify({ rows: chunk, from: i })
8047
+ });
8048
+ await stream2.sleep(0);
8049
+ }
8050
+ if (stream2.aborted || stream2.closed) {
8051
+ off();
8052
+ return;
8053
+ }
8054
+ await stream2.writeSSE({ event: "snapshot-end", data: "{}" });
8055
+ if (snap.loadProgress.phase !== "idle") {
8056
+ await stream2.writeSSE({
8057
+ event: snap.loadProgress.kind,
8058
+ data: JSON.stringify(snap.loadProgress)
8059
+ });
8060
+ }
8061
+ snapshotStreaming = false;
8062
+ if (queuedFrameCount() > 0) {
8063
+ const queuedRows = queue.slice(queueStart).reduce((total, msg) => total + (msg.kind === "append" ? msg.rows.length : 0), 0);
8064
+ const backlog = {
8065
+ kind: "stream-backlog",
8066
+ queuedFrames: queuedFrameCount(),
8067
+ queuedRows
8068
+ };
8069
+ await stream2.writeSSE({ event: backlog.kind, data: JSON.stringify(backlog) });
8070
+ await pump();
8071
+ const clearedBacklog = {
8072
+ kind: "stream-backlog",
8073
+ queuedFrames: 0,
8074
+ queuedRows: 0
8075
+ };
8076
+ await stream2.writeSSE({
8077
+ event: clearedBacklog.kind,
8078
+ data: JSON.stringify(clearedBacklog)
8079
+ });
8080
+ } else {
8081
+ await pump();
8082
+ }
7923
8083
  let endRequested = false;
7924
8084
  const offChange = sessions.onChange(async () => {
7925
8085
  if (endRequested) return;