ahp-inspector 1.4.3 → 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 +251 -161
- 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-DTLBo7X9.css +0 -1
- package/ui-dist/assets/index-dyClOfJ7.js +0 -81
- package/ui-dist/assets/index-dyClOfJ7.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2664,6 +2664,7 @@ import { Buffer as Buffer2 } from "buffer";
|
|
|
2664
2664
|
import { basename as basename6 } from "path";
|
|
2665
2665
|
|
|
2666
2666
|
// ../core/src/correlator.ts
|
|
2667
|
+
var MAX_PENDING = 1e4;
|
|
2667
2668
|
var Correlator = class {
|
|
2668
2669
|
#store;
|
|
2669
2670
|
#pendingRequests = /* @__PURE__ */ new Map();
|
|
@@ -2677,6 +2678,14 @@ var Correlator = class {
|
|
|
2677
2678
|
this.#store = store;
|
|
2678
2679
|
this.#unsubscribe = store.subscribe((range) => this.#onAppend(range));
|
|
2679
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
|
+
}
|
|
2680
2689
|
pairOf(idx) {
|
|
2681
2690
|
const v = this.pairIdx[idx];
|
|
2682
2691
|
return v === void 0 || v < 0 ? null : v;
|
|
@@ -2748,6 +2757,16 @@ var Correlator = class {
|
|
|
2748
2757
|
if (displaced !== void 0) {
|
|
2749
2758
|
this.status[displaced] = "orphan";
|
|
2750
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
|
+
}
|
|
2751
2770
|
}
|
|
2752
2771
|
this.#pendingRequests.set(key, idx);
|
|
2753
2772
|
this.status[idx] = "pending";
|
|
@@ -2765,6 +2784,9 @@ var Correlator = class {
|
|
|
2765
2784
|
if (displaced !== void 0) {
|
|
2766
2785
|
this.status[displaced] = "orphan";
|
|
2767
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);
|
|
2768
2790
|
}
|
|
2769
2791
|
this.#pendingResponses.set(key, idx);
|
|
2770
2792
|
}
|
|
@@ -3025,7 +3047,7 @@ function refreshSummaryStatus(state) {
|
|
|
3025
3047
|
}
|
|
3026
3048
|
return { ...state, summary: { ...state.summary, status } };
|
|
3027
3049
|
}
|
|
3028
|
-
function endTurn(state, turnId, turnState, terminalStatus, error) {
|
|
3050
|
+
function endTurn(state, turnId, turnState, terminalStatus, error, now = Date.now) {
|
|
3029
3051
|
if (!state.activeTurn || state.activeTurn.id !== turnId) {
|
|
3030
3052
|
return state;
|
|
3031
3053
|
}
|
|
@@ -3061,7 +3083,7 @@ function endTurn(state, turnId, turnState, terminalStatus, error) {
|
|
|
3061
3083
|
...state,
|
|
3062
3084
|
turns: [...state.turns, turn],
|
|
3063
3085
|
activeTurn: void 0,
|
|
3064
|
-
summary: { ...state.summary, modifiedAt:
|
|
3086
|
+
summary: { ...state.summary, modifiedAt: now() }
|
|
3065
3087
|
};
|
|
3066
3088
|
delete next.inputRequests;
|
|
3067
3089
|
return {
|
|
@@ -3069,7 +3091,7 @@ function endTurn(state, turnId, turnState, terminalStatus, error) {
|
|
|
3069
3091
|
summary: { ...next.summary, status: summaryStatus(next, terminalStatus) }
|
|
3070
3092
|
};
|
|
3071
3093
|
}
|
|
3072
|
-
function upsertInputRequest(state, request) {
|
|
3094
|
+
function upsertInputRequest(state, request, now = Date.now) {
|
|
3073
3095
|
const existing = state.inputRequests ?? [];
|
|
3074
3096
|
const idx = existing.findIndex((r) => r.id === request.id);
|
|
3075
3097
|
const inputRequests = [...existing];
|
|
@@ -3085,7 +3107,7 @@ function upsertInputRequest(state, request) {
|
|
|
3085
3107
|
summary: {
|
|
3086
3108
|
...next.summary,
|
|
3087
3109
|
status: withStatusFlag(summaryStatus(next), 32 /* IsRead */, false),
|
|
3088
|
-
modifiedAt:
|
|
3110
|
+
modifiedAt: now()
|
|
3089
3111
|
}
|
|
3090
3112
|
};
|
|
3091
3113
|
}
|
|
@@ -3138,7 +3160,7 @@ function updateResponsePart(state, turnId, partId, updater) {
|
|
|
3138
3160
|
activeTurn: { ...activeTurn, responseParts }
|
|
3139
3161
|
};
|
|
3140
3162
|
}
|
|
3141
|
-
function sessionReducer(state, action, log) {
|
|
3163
|
+
function sessionReducer(state, action, log, now = Date.now) {
|
|
3142
3164
|
switch (action.type) {
|
|
3143
3165
|
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
3144
3166
|
case "session/ready" /* SessionReady */:
|
|
@@ -3169,7 +3191,7 @@ function sessionReducer(state, action, log) {
|
|
|
3169
3191
|
summary: {
|
|
3170
3192
|
...next.summary,
|
|
3171
3193
|
status: withStatusFlag(summaryStatus(next), 32 /* IsRead */, false),
|
|
3172
|
-
modifiedAt:
|
|
3194
|
+
modifiedAt: now()
|
|
3173
3195
|
}
|
|
3174
3196
|
};
|
|
3175
3197
|
if (action.queuedMessageId) {
|
|
@@ -3202,11 +3224,11 @@ function sessionReducer(state, action, log) {
|
|
|
3202
3224
|
}
|
|
3203
3225
|
};
|
|
3204
3226
|
case "session/turnComplete" /* SessionTurnComplete */:
|
|
3205
|
-
return endTurn(state, action.turnId, "complete" /* Complete
|
|
3227
|
+
return endTurn(state, action.turnId, "complete" /* Complete */, void 0, void 0, now);
|
|
3206
3228
|
case "session/turnCancelled" /* SessionTurnCancelled */:
|
|
3207
|
-
return endTurn(state, action.turnId, "cancelled" /* Cancelled
|
|
3229
|
+
return endTurn(state, action.turnId, "cancelled" /* Cancelled */, void 0, void 0, now);
|
|
3208
3230
|
case "session/error" /* SessionError */:
|
|
3209
|
-
return endTurn(state, action.turnId, "error" /* Error */, 2 /* Error */, action.error);
|
|
3231
|
+
return endTurn(state, action.turnId, "error" /* Error */, 2 /* Error */, action.error, now);
|
|
3210
3232
|
// ── Tool Call State Machine ───────────────────────────────────────────
|
|
3211
3233
|
case "session/toolCallStart" /* SessionToolCallStart */:
|
|
3212
3234
|
if (!state.activeTurn || state.activeTurn.id !== action.turnId) {
|
|
@@ -3378,7 +3400,7 @@ function sessionReducer(state, action, log) {
|
|
|
3378
3400
|
case "session/titleChanged" /* SessionTitleChanged */:
|
|
3379
3401
|
return {
|
|
3380
3402
|
...state,
|
|
3381
|
-
summary: { ...state.summary, title: action.title, modifiedAt:
|
|
3403
|
+
summary: { ...state.summary, title: action.title, modifiedAt: now() }
|
|
3382
3404
|
};
|
|
3383
3405
|
case "session/usage" /* SessionUsage */:
|
|
3384
3406
|
if (!state.activeTurn || state.activeTurn.id !== action.turnId) {
|
|
@@ -3398,7 +3420,7 @@ function sessionReducer(state, action, log) {
|
|
|
3398
3420
|
case "session/modelChanged" /* SessionModelChanged */:
|
|
3399
3421
|
return {
|
|
3400
3422
|
...state,
|
|
3401
|
-
summary: { ...state.summary, model: action.model, modifiedAt:
|
|
3423
|
+
summary: { ...state.summary, model: action.model, modifiedAt: now() }
|
|
3402
3424
|
};
|
|
3403
3425
|
case "session/isReadChanged" /* SessionIsReadChanged */:
|
|
3404
3426
|
return {
|
|
@@ -3439,7 +3461,7 @@ function sessionReducer(state, action, log) {
|
|
|
3439
3461
|
},
|
|
3440
3462
|
summary: {
|
|
3441
3463
|
...state.summary,
|
|
3442
|
-
modifiedAt:
|
|
3464
|
+
modifiedAt: now()
|
|
3443
3465
|
}
|
|
3444
3466
|
};
|
|
3445
3467
|
}
|
|
@@ -3457,7 +3479,7 @@ function sessionReducer(state, action, log) {
|
|
|
3457
3479
|
},
|
|
3458
3480
|
summary: {
|
|
3459
3481
|
...state.summary,
|
|
3460
|
-
modifiedAt:
|
|
3482
|
+
modifiedAt: now()
|
|
3461
3483
|
}
|
|
3462
3484
|
};
|
|
3463
3485
|
}
|
|
@@ -3540,7 +3562,7 @@ function sessionReducer(state, action, log) {
|
|
|
3540
3562
|
...state,
|
|
3541
3563
|
turns,
|
|
3542
3564
|
activeTurn: void 0,
|
|
3543
|
-
summary: { ...state.summary, modifiedAt:
|
|
3565
|
+
summary: { ...state.summary, modifiedAt: now() }
|
|
3544
3566
|
};
|
|
3545
3567
|
delete next.inputRequests;
|
|
3546
3568
|
return {
|
|
@@ -3550,7 +3572,7 @@ function sessionReducer(state, action, log) {
|
|
|
3550
3572
|
}
|
|
3551
3573
|
// ── Session Input Requests ─────────────────────────────────────────────
|
|
3552
3574
|
case "session/inputRequested" /* SessionInputRequested */:
|
|
3553
|
-
return upsertInputRequest(state, action.request);
|
|
3575
|
+
return upsertInputRequest(state, action.request, now);
|
|
3554
3576
|
case "session/inputAnswerChanged" /* SessionInputAnswerChanged */: {
|
|
3555
3577
|
const existing = state.inputRequests;
|
|
3556
3578
|
const idx = existing?.findIndex((request2) => request2.id === action.requestId) ?? -1;
|
|
@@ -3572,7 +3594,7 @@ function sessionReducer(state, action, log) {
|
|
|
3572
3594
|
return {
|
|
3573
3595
|
...state,
|
|
3574
3596
|
inputRequests: updated,
|
|
3575
|
-
summary: { ...state.summary, modifiedAt:
|
|
3597
|
+
summary: { ...state.summary, modifiedAt: now() }
|
|
3576
3598
|
};
|
|
3577
3599
|
}
|
|
3578
3600
|
case "session/inputCompleted" /* SessionInputCompleted */: {
|
|
@@ -3591,7 +3613,7 @@ function sessionReducer(state, action, log) {
|
|
|
3591
3613
|
}
|
|
3592
3614
|
return {
|
|
3593
3615
|
...next,
|
|
3594
|
-
summary: { ...next.summary, status: summaryStatus(next), modifiedAt:
|
|
3616
|
+
summary: { ...next.summary, status: summaryStatus(next), modifiedAt: now() }
|
|
3595
3617
|
};
|
|
3596
3618
|
}
|
|
3597
3619
|
// ── Pending Messages ──────────────────────────────────────────────────
|
|
@@ -4004,15 +4026,20 @@ function applyEnvelope(ctx, envelope, eventIdx, eventTs) {
|
|
|
4004
4026
|
details: { actionType: actionTypeOf(envelope.action) }
|
|
4005
4027
|
});
|
|
4006
4028
|
};
|
|
4007
|
-
resource.state =
|
|
4029
|
+
resource.state = (() => {
|
|
4008
4030
|
if (target.kind === "root") {
|
|
4009
4031
|
return rootReducer(resource.state, envelope.action, log);
|
|
4010
4032
|
}
|
|
4011
4033
|
if (target.kind === "session") {
|
|
4012
|
-
return sessionReducer(
|
|
4034
|
+
return sessionReducer(
|
|
4035
|
+
resource.state,
|
|
4036
|
+
envelope.action,
|
|
4037
|
+
log,
|
|
4038
|
+
() => eventTs
|
|
4039
|
+
);
|
|
4013
4040
|
}
|
|
4014
4041
|
return terminalReducer(resource.state, envelope.action, log);
|
|
4015
|
-
});
|
|
4042
|
+
})();
|
|
4016
4043
|
resource.lastAppliedEventIdx = eventIdx;
|
|
4017
4044
|
resource.lastServerSeq = envelope.serverSeq;
|
|
4018
4045
|
linkAcceptedIntent(ctx, envelope);
|
|
@@ -4088,15 +4115,6 @@ function linkAcceptedIntent(ctx, envelope) {
|
|
|
4088
4115
|
}
|
|
4089
4116
|
ctx.intents[idx] = { ...intent, acceptedByServerSeq: envelope.serverSeq };
|
|
4090
4117
|
}
|
|
4091
|
-
function withEventTime(eventTs, fn) {
|
|
4092
|
-
const original = Date.now;
|
|
4093
|
-
Date.now = () => eventTs;
|
|
4094
|
-
try {
|
|
4095
|
-
return fn();
|
|
4096
|
-
} finally {
|
|
4097
|
-
Date.now = original;
|
|
4098
|
-
}
|
|
4099
|
-
}
|
|
4100
4118
|
function readSnapshot(value) {
|
|
4101
4119
|
if (!isRecord(value) || typeof value.resource !== "string" || typeof value.fromSeq !== "number") {
|
|
4102
4120
|
return null;
|
|
@@ -4680,9 +4698,15 @@ function formatTs(ms) {
|
|
|
4680
4698
|
function formatSessionShort(sessionId) {
|
|
4681
4699
|
const watched = formatResourceWatchChannel(sessionId);
|
|
4682
4700
|
if (watched) return watched;
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
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, "");
|
|
4686
4710
|
label = label.replace(/[-_]\d{4}[-_]\d{2}[-_]\d{2}$/u, "");
|
|
4687
4711
|
if (/^[0-9a-f]{16,}$/iu.test(label)) return label.slice(-8);
|
|
4688
4712
|
const uuidFirstSegment = label.match(/^[0-9a-f]{8}(?=-[0-9a-f]{4}-)/iu)?.[0];
|
|
@@ -4788,7 +4812,7 @@ function projectRow(event, idx, status, latencyMs, extras = DEFAULT_EXTRAS, pair
|
|
|
4788
4812
|
sessionId: session,
|
|
4789
4813
|
sessionShort: session ? formatSessionShort(session) : null,
|
|
4790
4814
|
turnId: turn,
|
|
4791
|
-
turnShort: turn ? turn
|
|
4815
|
+
turnShort: turn ? formatTurnShort(turn) : null,
|
|
4792
4816
|
keyId: idStr ? idStr.length > 12 ? idStr.slice(0, 12) : idStr : null,
|
|
4793
4817
|
status,
|
|
4794
4818
|
latencyMs,
|
|
@@ -5182,25 +5206,44 @@ async function createAppState(opts) {
|
|
|
5182
5206
|
};
|
|
5183
5207
|
}
|
|
5184
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
|
+
|
|
5185
5225
|
// ../server/src/cors.ts
|
|
5226
|
+
var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
5186
5227
|
var corsMiddleware = async (c, next) => {
|
|
5187
5228
|
const origin = c.req.header("origin");
|
|
5229
|
+
const allowed = isAllowedOrigin(origin);
|
|
5188
5230
|
if (c.req.method === "OPTIONS") {
|
|
5189
5231
|
const reqHeaders = c.req.header("access-control-request-headers") ?? "*";
|
|
5190
5232
|
const reqMethod = c.req.header("access-control-request-method") ?? "GET";
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
headers:
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
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);
|
|
5201
5244
|
}
|
|
5202
5245
|
await next();
|
|
5203
|
-
if (origin) {
|
|
5246
|
+
if (allowed && origin !== void 0) {
|
|
5204
5247
|
c.res.headers.set("access-control-allow-origin", origin);
|
|
5205
5248
|
c.res.headers.append("vary", "Origin");
|
|
5206
5249
|
}
|
|
@@ -7973,7 +8016,7 @@ function registerSearchRoutes(app, sessions) {
|
|
|
7973
8016
|
// ../server/src/session-routes.ts
|
|
7974
8017
|
function registerSessionRoutes(app, sessions) {
|
|
7975
8018
|
app.get("/api/sessions/discover", async (c) => {
|
|
7976
|
-
const r = await
|
|
8019
|
+
const r = await sessions.discover();
|
|
7977
8020
|
return c.json({ candidates: r.candidates, truncated: r.truncated });
|
|
7978
8021
|
});
|
|
7979
8022
|
app.post("/api/sessions/open", async (c) => {
|
|
@@ -8167,7 +8210,9 @@ var streamSSE = (c, cb, onError) => {
|
|
|
8167
8210
|
// ../server/src/sse-routes.ts
|
|
8168
8211
|
var SNAPSHOT_CHUNK = 2e3;
|
|
8169
8212
|
var PING_INTERVAL_MS = 2e4;
|
|
8213
|
+
var MAX_SSE_CONNECTIONS = 8;
|
|
8170
8214
|
function registerLogRoutes(app, sessions) {
|
|
8215
|
+
let activeStreams = 0;
|
|
8171
8216
|
app.get("/api/log/meta", (c) => {
|
|
8172
8217
|
const a = sessions.current();
|
|
8173
8218
|
if (!a) return c.body(null, 204);
|
|
@@ -8178,127 +8223,135 @@ function registerLogRoutes(app, sessions) {
|
|
|
8178
8223
|
if (!initial) {
|
|
8179
8224
|
return c.json({ code: "no-active-log" }, 409);
|
|
8180
8225
|
}
|
|
8226
|
+
if (activeStreams >= MAX_SSE_CONNECTIONS) {
|
|
8227
|
+
return c.json({ code: "too-many-streams" }, 503);
|
|
8228
|
+
}
|
|
8229
|
+
activeStreams++;
|
|
8181
8230
|
return streamSSE(c, async (stream2) => {
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
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
|
+
}
|
|
8199
8250
|
}
|
|
8251
|
+
} finally {
|
|
8252
|
+
if (queueStart === queue.length) {
|
|
8253
|
+
queue.length = 0;
|
|
8254
|
+
queueStart = 0;
|
|
8255
|
+
}
|
|
8256
|
+
pumping = false;
|
|
8200
8257
|
}
|
|
8201
|
-
}
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
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;
|
|
8205
8272
|
}
|
|
8206
|
-
|
|
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);
|
|
8207
8279
|
}
|
|
8208
|
-
};
|
|
8209
|
-
const off = a.appState.subscribe((msg) => {
|
|
8210
|
-
queue.push(msg);
|
|
8211
|
-
void pump();
|
|
8212
|
-
});
|
|
8213
|
-
const snap = a.appState.snapshot();
|
|
8214
|
-
await stream2.writeSSE({
|
|
8215
|
-
event: "snapshot-begin",
|
|
8216
|
-
data: JSON.stringify({ meta: snap.meta, total: snap.rows.length })
|
|
8217
|
-
});
|
|
8218
|
-
for (let i = 0; i < snap.rows.length; i += SNAPSHOT_CHUNK) {
|
|
8219
8280
|
if (stream2.aborted || stream2.closed) {
|
|
8220
8281
|
off();
|
|
8221
8282
|
return;
|
|
8222
8283
|
}
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
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
|
+
}
|
|
8239
8321
|
});
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
event: clearedBacklog.kind,
|
|
8258
|
-
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);
|
|
8259
8339
|
});
|
|
8260
|
-
|
|
8261
|
-
await pump();
|
|
8262
|
-
}
|
|
8263
|
-
let endRequested = false;
|
|
8264
|
-
const offChange = sessions.onChange(async () => {
|
|
8265
|
-
if (endRequested) return;
|
|
8266
|
-
endRequested = true;
|
|
8340
|
+
clearInterval(pinger);
|
|
8267
8341
|
try {
|
|
8268
|
-
|
|
8342
|
+
off();
|
|
8269
8343
|
} catch {
|
|
8270
8344
|
}
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
stream2.writeSSE({ event: "ping", data: "{}" }).catch(() => {
|
|
8275
|
-
});
|
|
8276
|
-
}, PING_INTERVAL_MS);
|
|
8277
|
-
await new Promise((resolveWait) => {
|
|
8278
|
-
if (stream2.aborted || stream2.closed) {
|
|
8279
|
-
resolveWait();
|
|
8280
|
-
return;
|
|
8345
|
+
try {
|
|
8346
|
+
offChange();
|
|
8347
|
+
} catch {
|
|
8281
8348
|
}
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
}, 50);
|
|
8289
|
-
});
|
|
8290
|
-
clearInterval(pinger);
|
|
8291
|
-
try {
|
|
8292
|
-
off();
|
|
8293
|
-
} catch {
|
|
8294
|
-
}
|
|
8295
|
-
try {
|
|
8296
|
-
offChange();
|
|
8297
|
-
} catch {
|
|
8298
|
-
}
|
|
8299
|
-
try {
|
|
8300
|
-
await stream2.writeSSE({ event: "bye", data: "{}" });
|
|
8301
|
-
} catch {
|
|
8349
|
+
try {
|
|
8350
|
+
await stream2.writeSSE({ event: "bye", data: "{}" });
|
|
8351
|
+
} catch {
|
|
8352
|
+
}
|
|
8353
|
+
} finally {
|
|
8354
|
+
activeStreams--;
|
|
8302
8355
|
}
|
|
8303
8356
|
});
|
|
8304
8357
|
});
|
|
@@ -8653,6 +8706,41 @@ import { tmpdir } from "os";
|
|
|
8653
8706
|
import { extname as extname2, join as join6, basename as pathBasename } from "path";
|
|
8654
8707
|
var MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
8655
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
|
+
}
|
|
8656
8744
|
function sanitizeFilename(raw2) {
|
|
8657
8745
|
let decoded;
|
|
8658
8746
|
try {
|
|
@@ -8703,8 +8791,9 @@ function createUploadStore() {
|
|
|
8703
8791
|
}
|
|
8704
8792
|
return { write, cleanupAllExcept, disposeAll };
|
|
8705
8793
|
}
|
|
8706
|
-
function registerUploadRoutes(app, sessions) {
|
|
8794
|
+
function registerUploadRoutes(app, sessions, opts) {
|
|
8707
8795
|
const store = createUploadStore();
|
|
8796
|
+
const maxBytes = opts?.maxUploadBytes ?? MAX_UPLOAD_BYTES;
|
|
8708
8797
|
const unsubscribe = sessions.onChange((active) => {
|
|
8709
8798
|
if (active === null) {
|
|
8710
8799
|
void store.disposeAll();
|
|
@@ -8725,24 +8814,24 @@ function registerUploadRoutes(app, sessions) {
|
|
|
8725
8814
|
}
|
|
8726
8815
|
const lengthHeader = c.req.header("content-length");
|
|
8727
8816
|
const declaredLength = lengthHeader ? Number.parseInt(lengthHeader, 10) : Number.NaN;
|
|
8728
|
-
if (Number.isFinite(declaredLength) && declaredLength >
|
|
8817
|
+
if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
|
|
8729
8818
|
return c.json({ code: "too-large", message: "too-large" }, 413);
|
|
8730
8819
|
}
|
|
8731
|
-
let
|
|
8820
|
+
let bytes;
|
|
8732
8821
|
try {
|
|
8733
|
-
|
|
8734
|
-
} 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
|
+
}
|
|
8735
8827
|
return c.json({ code: "bad-request", message: "could not read body" }, 400);
|
|
8736
8828
|
}
|
|
8737
|
-
if (
|
|
8829
|
+
if (bytes.byteLength === 0) {
|
|
8738
8830
|
return c.json({ code: "bad-request", message: "empty body" }, 400);
|
|
8739
8831
|
}
|
|
8740
|
-
if (buf.byteLength > MAX_UPLOAD_BYTES) {
|
|
8741
|
-
return c.json({ code: "too-large", message: "too-large" }, 413);
|
|
8742
|
-
}
|
|
8743
8832
|
let tempPath;
|
|
8744
8833
|
try {
|
|
8745
|
-
tempPath = await store.write(safeName,
|
|
8834
|
+
tempPath = await store.write(safeName, bytes);
|
|
8746
8835
|
} catch {
|
|
8747
8836
|
return c.json({ code: "io-error", message: "io-error" }, 500);
|
|
8748
8837
|
}
|
|
@@ -8875,6 +8964,7 @@ function createLogSessionManager(opts) {
|
|
|
8875
8964
|
}
|
|
8876
8965
|
return {
|
|
8877
8966
|
current: () => active,
|
|
8967
|
+
discover: () => opts.host.discoverLogs(),
|
|
8878
8968
|
async open(input) {
|
|
8879
8969
|
const next = chain.then(async () => {
|
|
8880
8970
|
if ("path" in input) return doOpen(input.path);
|