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 +297 -162
- package/package.json +1 -1
- package/ui-dist/assets/index-BHfzY_J_.js +81 -0
- package/ui-dist/assets/index-BHfzY_J_.js.map +1 -0
- package/ui-dist/assets/index-D7wKEskJ.css +1 -0
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-C3fgWj5L.js +0 -81
- package/ui-dist/assets/index-C3fgWj5L.js.map +0 -1
- package/ui-dist/assets/index-CkWXlGE4.css +0 -1
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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(
|
|
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
|
|
4657
|
-
|
|
4658
|
-
|
|
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
|
|
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
|
-
|
|
5147
|
-
|
|
5148
|
-
headers:
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
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
|
|
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
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
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
|
-
}
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
data: JSON.stringify(
|
|
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
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8342
|
+
off();
|
|
8224
8343
|
} catch {
|
|
8225
8344
|
}
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
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
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
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 >
|
|
8817
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
8684
8818
|
return c.json({ code: "too-large", message: "too-large" }, 413);
|
|
8685
8819
|
}
|
|
8686
|
-
let
|
|
8820
|
+
let bytes;
|
|
8687
8821
|
try {
|
|
8688
|
-
|
|
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 (
|
|
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,
|
|
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);
|