@tonyclaw/agent-inspector 3.0.46 → 3.0.48
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/.output/backend/nitro.json +1 -1
- package/.output/cli.js +6791 -6006
- package/.output/server/_ssr/index.mjs +1 -1
- package/.output/server/_ssr/{router-D18yUq36.mjs → router-Cj10mv-Z.mjs} +334 -5
- package/.output/server/index.mjs +1 -1
- package/.output/ui/assets/{CompareDrawer-DAUuIJ6G.js → CompareDrawer-uRiXFOCp.js} +1 -1
- package/.output/ui/assets/{InspectorPet-BRBVjOWI.js → InspectorPet-j-r6xk0V.js} +1 -1
- package/.output/ui/assets/{ProxyViewerContainer-D7Sq0ctc.js → ProxyViewerContainer-DnUrkMJF.js} +23 -23
- package/.output/ui/assets/{ReplayDialog-nDLsjOhs.js → ReplayDialog-B2URyHMv.js} +1 -1
- package/.output/ui/assets/{RequestAnatomy-zd4BDgL0.js → RequestAnatomy-CK0M0CJO.js} +1 -1
- package/.output/ui/assets/{ResponseView-AdIXGcSn.js → ResponseView-CYSVA4lT.js} +1 -1
- package/.output/ui/assets/{StreamingChunkSequence-CesTz8He.js → StreamingChunkSequence-CHf54ZUV.js} +1 -1
- package/.output/ui/assets/{_sessionId-QnBomgPD.js → _sessionId-BXULpTNP.js} +1 -1
- package/.output/ui/assets/{_sessionId-BAfzrhSU.js → _sessionId-E4lbj5-I.js} +1 -1
- package/.output/ui/assets/{index-D0rtCN9V.js → index-B4ixI3kG.js} +1 -1
- package/.output/ui/assets/{index-BcsdxBAd.js → index-BJ-71WLq.js} +1 -1
- package/.output/ui/assets/{index-DHpwr08Z.js → index-CW0ab9r3.js} +2 -2
- package/.output/ui/assets/{index-dNuk2dsU.js → index-Ce-lO51c.js} +1 -1
- package/.output/ui/assets/{json-viewer-BvFHglMb.js → json-viewer-C6E8aLaV.js} +1 -1
- package/.output/ui/assets/{jszip.min-B5Z7gNZB.js → jszip.min-DjWdTBbp.js} +1 -1
- package/.output/ui/index.html +1 -1
- package/README.md +53 -0
- package/package.json +17 -3
- package/src/backend/routes/api/-instances.ts +7 -109
- package/src/cli/instance.ts +21 -0
- package/src/cli/instanceApi.ts +213 -0
- package/src/cli/instanceArgs.ts +16 -0
- package/src/cli/instanceControl.ts +10 -0
- package/src/cli/instanceModel.ts +9 -0
- package/src/cli/rustBackendPackage.ts +458 -0
- package/src/cli.ts +171 -14
- package/src/components/ProxyViewer.tsx +4 -13
- package/src/components/proxy-viewer/proxyViewerLogic.ts +9 -0
- package/src/lib/backendImplementation.ts +16 -0
- package/src/lib/instanceContract.ts +3 -0
- package/src/lib/managedInstance.ts +9 -1
- package/src/mcp/instanceHandlers.ts +1 -0
- package/src/proxy/identityProxy.ts +148 -0
- package/src/proxy/sessionArchive.ts +0 -8
- package/src/proxy/sqliteLogIndex.ts +0 -40
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
BROWSER_RUNTIME_SCHEMA_VERSION,
|
|
8
8
|
type BrowserRuntimeDeployment,
|
|
9
9
|
} from "../lib/browserRuntimeContract";
|
|
10
|
+
import {
|
|
11
|
+
AGENT_INSPECTOR_BACKEND_HEADER,
|
|
12
|
+
type BackendImplementation,
|
|
13
|
+
} from "../lib/backendImplementation";
|
|
10
14
|
import {
|
|
11
15
|
AGENT_INSPECTOR_BASE_PATH_META_NAME,
|
|
12
16
|
AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER,
|
|
@@ -39,6 +43,8 @@ const CSRF_HEADER = "x-agent-inspector-csrf";
|
|
|
39
43
|
const DEFAULT_MAX_REQUEST_BYTES = 64 * 1024 * 1024;
|
|
40
44
|
const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
|
|
41
45
|
const ALIAS_CONTROL_MAX_REQUEST_BYTES = 8 * 1024;
|
|
46
|
+
const INSTANCE_API_MAX_REQUEST_BYTES = 64 * 1024;
|
|
47
|
+
const LOCAL_CONTROL_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
42
48
|
|
|
43
49
|
export const IDENTITY_PROXY_CONTROL_NAMESPACE = "/.agent-inspector";
|
|
44
50
|
export const IDENTITY_PROXY_ALIAS_CONTROL_PATH = `${IDENTITY_PROXY_CONTROL_NAMESPACE}/base-path-aliases`;
|
|
@@ -480,6 +486,8 @@ async function tryServeUiAsset(
|
|
|
480
486
|
}
|
|
481
487
|
|
|
482
488
|
export type IdentityProxyOptions = {
|
|
489
|
+
/** Backend child implementation supervised behind this protected ingress. */
|
|
490
|
+
backendImplementation?: BackendImplementation;
|
|
483
491
|
/** Public port to listen on. Clients point at this. */
|
|
484
492
|
listenPort: number;
|
|
485
493
|
/** Interface exposed to clients. Defaults are chosen by the CLI. */
|
|
@@ -514,9 +522,12 @@ export type IdentityProxyOptions = {
|
|
|
514
522
|
aliasRegistry?: BasePathAliasRegistry;
|
|
515
523
|
/** Private lifecycle identity exposed only to the owning local instance manager. */
|
|
516
524
|
managedInstance?: ManagedInstanceControlOptions;
|
|
525
|
+
/** Optional Node-supervisor instance API retained when the selected backend is Rust. */
|
|
526
|
+
instanceApiHandler?: (request: Request) => Promise<Response>;
|
|
517
527
|
};
|
|
518
528
|
|
|
519
529
|
export type ManagedInstanceControlOptions = {
|
|
530
|
+
backend?: BackendImplementation;
|
|
520
531
|
name: string;
|
|
521
532
|
instanceId: string;
|
|
522
533
|
launchId: string;
|
|
@@ -900,6 +911,128 @@ function readAliasControlBody(req: http.IncomingMessage): Promise<AliasControlBo
|
|
|
900
911
|
});
|
|
901
912
|
}
|
|
902
913
|
|
|
914
|
+
type LocalControlBodyResult =
|
|
915
|
+
| { ok: true; body: Buffer }
|
|
916
|
+
| { ok: false; status: number; error: string };
|
|
917
|
+
|
|
918
|
+
function readLocalControlBody(
|
|
919
|
+
req: http.IncomingMessage,
|
|
920
|
+
maxBytes: number,
|
|
921
|
+
timeoutMs: number,
|
|
922
|
+
): Promise<LocalControlBodyResult> {
|
|
923
|
+
return new Promise((resolve) => {
|
|
924
|
+
const chunks: Buffer[] = [];
|
|
925
|
+
let receivedBytes = 0;
|
|
926
|
+
let settled = false;
|
|
927
|
+
const timer = setTimeout(() => {
|
|
928
|
+
finish({ ok: false, status: 408, error: "Instance request body timed out" });
|
|
929
|
+
req.resume();
|
|
930
|
+
}, timeoutMs);
|
|
931
|
+
const finish = (result: LocalControlBodyResult): void => {
|
|
932
|
+
if (settled) return;
|
|
933
|
+
settled = true;
|
|
934
|
+
clearTimeout(timer);
|
|
935
|
+
resolve(result);
|
|
936
|
+
};
|
|
937
|
+
req.on("data", (chunk: unknown) => {
|
|
938
|
+
if (settled) return;
|
|
939
|
+
const buffer = toBuffer(chunk);
|
|
940
|
+
receivedBytes += buffer.byteLength;
|
|
941
|
+
if (receivedBytes > maxBytes) {
|
|
942
|
+
finish({
|
|
943
|
+
ok: false,
|
|
944
|
+
status: 413,
|
|
945
|
+
error: `Instance request body exceeds ${String(maxBytes)} bytes`,
|
|
946
|
+
});
|
|
947
|
+
req.resume();
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
chunks.push(buffer);
|
|
951
|
+
});
|
|
952
|
+
req.on("end", () => finish({ ok: true, body: Buffer.concat(chunks) }));
|
|
953
|
+
req.on("aborted", () =>
|
|
954
|
+
finish({ ok: false, status: 400, error: "Client aborted instance request body" }),
|
|
955
|
+
);
|
|
956
|
+
req.on("error", () =>
|
|
957
|
+
finish({ ok: false, status: 400, error: "Instance request body failed" }),
|
|
958
|
+
);
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function writeLocalControlError(
|
|
963
|
+
res: http.ServerResponse,
|
|
964
|
+
status: number,
|
|
965
|
+
error: string,
|
|
966
|
+
allowedUiOrigin: string | null,
|
|
967
|
+
): void {
|
|
968
|
+
res.writeHead(status, {
|
|
969
|
+
"content-type": "application/json; charset=utf-8",
|
|
970
|
+
"cache-control": "no-store",
|
|
971
|
+
...uiCorsHeaders(allowedUiOrigin),
|
|
972
|
+
});
|
|
973
|
+
res.end(JSON.stringify({ error }));
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
async function handleLocalInstanceApi(
|
|
977
|
+
req: http.IncomingMessage,
|
|
978
|
+
res: http.ServerResponse,
|
|
979
|
+
handler: (request: Request) => Promise<Response>,
|
|
980
|
+
upstreamPath: string,
|
|
981
|
+
requestPublicOrigin: string,
|
|
982
|
+
allowedUiOrigin: string | null,
|
|
983
|
+
maxRequestBytes: number,
|
|
984
|
+
requestTimeoutMs: number,
|
|
985
|
+
): Promise<void> {
|
|
986
|
+
const body = await readLocalControlBody(
|
|
987
|
+
req,
|
|
988
|
+
Math.min(maxRequestBytes, INSTANCE_API_MAX_REQUEST_BYTES),
|
|
989
|
+
requestTimeoutMs,
|
|
990
|
+
);
|
|
991
|
+
if (!body.ok) {
|
|
992
|
+
writeLocalControlError(res, body.status, body.error, allowedUiOrigin);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
996
|
+
const headers = new Headers();
|
|
997
|
+
const contentType = firstHeader(req.headers["content-type"]);
|
|
998
|
+
if (contentType !== undefined) headers.set("content-type", contentType);
|
|
999
|
+
const request = new Request(new URL(upstreamPath, requestPublicOrigin), {
|
|
1000
|
+
method,
|
|
1001
|
+
headers,
|
|
1002
|
+
body: method === "GET" || method === "HEAD" ? undefined : body.body.toString("utf8"),
|
|
1003
|
+
});
|
|
1004
|
+
let response: Response;
|
|
1005
|
+
try {
|
|
1006
|
+
response = await handler(request);
|
|
1007
|
+
} catch {
|
|
1008
|
+
writeLocalControlError(res, 503, "Instance supervisor request failed", allowedUiOrigin);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
1012
|
+
if (responseBody.byteLength > LOCAL_CONTROL_MAX_RESPONSE_BYTES) {
|
|
1013
|
+
writeLocalControlError(
|
|
1014
|
+
res,
|
|
1015
|
+
502,
|
|
1016
|
+
"Instance supervisor response exceeded its resource limit",
|
|
1017
|
+
allowedUiOrigin,
|
|
1018
|
+
);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const responseHeaders: Record<string, string> = {
|
|
1022
|
+
...uiCorsHeaders(allowedUiOrigin),
|
|
1023
|
+
"content-length": String(responseBody.byteLength),
|
|
1024
|
+
};
|
|
1025
|
+
response.headers.forEach((value, name) => {
|
|
1026
|
+
const lowerName = name.toLowerCase();
|
|
1027
|
+
if (!REQUEST_HOP_BY_HOP.has(lowerName) && lowerName !== "content-length") {
|
|
1028
|
+
responseHeaders[lowerName] = value;
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
responseHeaders["cache-control"] = "no-store";
|
|
1032
|
+
res.writeHead(response.status, responseHeaders);
|
|
1033
|
+
res.end(responseBody);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
903
1036
|
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
904
1037
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
905
1038
|
}
|
|
@@ -1070,6 +1203,7 @@ function handleManagedInstanceControlRequest(
|
|
|
1070
1203
|
}
|
|
1071
1204
|
writeManagedInstanceControlResponse(res, 200, {
|
|
1072
1205
|
schemaVersion: 1,
|
|
1206
|
+
backend: managedInstance.backend ?? "typescript",
|
|
1073
1207
|
name: managedInstance.name,
|
|
1074
1208
|
instanceId: managedInstance.instanceId,
|
|
1075
1209
|
launchId: managedInstance.launchId,
|
|
@@ -1483,6 +1617,19 @@ async function handleRequest(
|
|
|
1483
1617
|
options.requestTimeoutMs,
|
|
1484
1618
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
1485
1619
|
);
|
|
1620
|
+
if (options.instanceApiHandler !== undefined && isInstanceManagementPath(upstreamPathname)) {
|
|
1621
|
+
await handleLocalInstanceApi(
|
|
1622
|
+
req,
|
|
1623
|
+
res,
|
|
1624
|
+
options.instanceApiHandler,
|
|
1625
|
+
upstreamPath,
|
|
1626
|
+
requestPublicOrigin,
|
|
1627
|
+
allowedUiOrigin,
|
|
1628
|
+
maxRequestBytes,
|
|
1629
|
+
requestTimeoutMs,
|
|
1630
|
+
);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1486
1633
|
const proxyReq = http.request(
|
|
1487
1634
|
{
|
|
1488
1635
|
hostname: options.upstreamHost,
|
|
@@ -1554,6 +1701,7 @@ async function handleRequest(
|
|
|
1554
1701
|
for (const [name, value] of Object.entries(BASE_SECURITY_HEADERS)) {
|
|
1555
1702
|
outHeaders[name] = value;
|
|
1556
1703
|
}
|
|
1704
|
+
outHeaders[AGENT_INSPECTOR_BACKEND_HEADER] = options.backendImplementation ?? "typescript";
|
|
1557
1705
|
if (allowedUiOrigin !== null && isUiControlPath(upstreamPathname)) {
|
|
1558
1706
|
outHeaders["access-control-allow-origin"] = allowedUiOrigin;
|
|
1559
1707
|
outHeaders["access-control-expose-headers"] = "Content-Disposition";
|
|
@@ -278,10 +278,6 @@ export async function archiveSessionLogs(logs: readonly CapturedLog[]): Promise<
|
|
|
278
278
|
return archivedCount;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
export async function archiveSessionLog(log: CapturedLog): Promise<boolean> {
|
|
282
|
-
return (await archiveSessionLogs([log])) === 1;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
281
|
function readString(row: unknown, name: string): string | null {
|
|
286
282
|
const value = readProperty(row, name);
|
|
287
283
|
return typeof value === "string" ? value : null;
|
|
@@ -481,7 +477,3 @@ export async function clearSessionArchives(): Promise<number> {
|
|
|
481
477
|
return 0;
|
|
482
478
|
}
|
|
483
479
|
}
|
|
484
|
-
|
|
485
|
-
export function _resetSessionArchiveCacheForTests(): void {
|
|
486
|
-
cachedArchiveMax = null;
|
|
487
|
-
}
|
|
@@ -605,46 +605,6 @@ export async function syncSqliteLogIndexEntries(
|
|
|
605
605
|
}
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
-
export async function findSqliteLogIndexEntry(id: number): Promise<LogIndexEntry | null> {
|
|
609
|
-
const state = await getSqliteState();
|
|
610
|
-
if (state.status !== "ready") return null;
|
|
611
|
-
const row = getPrepared(state.db, "SELECT * FROM log_index WHERE id = ?", [id]);
|
|
612
|
-
return entryFromRow(row);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
export async function listSqliteLogIndexEntries({
|
|
616
|
-
sessionId,
|
|
617
|
-
model,
|
|
618
|
-
}: {
|
|
619
|
-
sessionId?: string;
|
|
620
|
-
model?: string;
|
|
621
|
-
} = {}): Promise<LogIndexEntry[] | null> {
|
|
622
|
-
const state = await getSqliteState();
|
|
623
|
-
if (state.status !== "ready") return null;
|
|
624
|
-
|
|
625
|
-
const clauses: string[] = [];
|
|
626
|
-
const params: unknown[] = [];
|
|
627
|
-
if (sessionId !== undefined) {
|
|
628
|
-
clauses.push("session_id = ?");
|
|
629
|
-
params.push(sessionId);
|
|
630
|
-
}
|
|
631
|
-
if (model !== undefined) {
|
|
632
|
-
clauses.push("model = ?");
|
|
633
|
-
params.push(model);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
const where = clauses.length === 0 ? "" : ` WHERE ${clauses.join(" AND ")}`;
|
|
637
|
-
const rows = allPrepared(state.db, `SELECT * FROM log_index${where} ORDER BY id ASC`, params);
|
|
638
|
-
if (rows === null) return null;
|
|
639
|
-
|
|
640
|
-
const entries: LogIndexEntry[] = [];
|
|
641
|
-
for (const row of rows) {
|
|
642
|
-
const entry = entryFromRow(row);
|
|
643
|
-
if (entry !== null) entries.push(entry);
|
|
644
|
-
}
|
|
645
|
-
return entries;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
608
|
export async function replaceSqliteLogIndexEntries(
|
|
649
609
|
entries: readonly LogIndexEntry[],
|
|
650
610
|
): Promise<boolean> {
|