@tonyclaw/agent-inspector 3.0.29 → 3.0.30
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/cli.js +791 -191
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-B6cXJohL.js +1 -0
- package/.output/public/assets/InspectorPet-mAzeGmEE.js +4108 -0
- package/.output/public/assets/ProxyViewerContainer-CkFWv_Nm.js +126 -0
- package/.output/public/assets/{ReplayDialog-CctnITbt.js → ReplayDialog-DodiTL6E.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-BrN0RBbL.js → RequestAnatomy-B3IauToI.js} +1 -1
- package/.output/public/assets/ResponseView-Hn-5ordK.js +3 -0
- package/.output/public/assets/StreamingChunkSequence-vLDOVQM_.js +1 -0
- package/.output/public/assets/_sessionId-BFH4TOaI.js +1 -0
- package/.output/public/assets/{_sessionId-DKM91udC.js → _sessionId-Cj_HIFZx.js} +1 -1
- package/.output/public/assets/{index-Bq-nqolG.js → index-Bmj2lcWE.js} +1 -1
- package/.output/public/assets/index-CgJj-HBC.css +1 -0
- package/.output/public/assets/index-Ch7uqnCT.js +1 -0
- package/.output/public/assets/index-DkDdKvLl.js +76 -0
- package/.output/public/assets/{index-DPiESBo2.js → index-ZlhqCrSg.js} +6 -6
- package/.output/public/assets/{json-viewer-CfaDJwx8.js → json-viewer-BdrnVQu-.js} +1 -1
- package/.output/public/assets/jszip.min-CS_nt_o1.js +2 -0
- package/.output/public/assets/qwen-mMn3f5ul.webp +0 -0
- package/.output/server/_libs/jszip.mjs +35 -9
- package/.output/server/_libs/lucide-react.mjs +86 -86
- package/.output/server/_libs/radix-ui__react-dialog.mjs +3 -3
- package/.output/server/_libs/tanstack__react-router.mjs +1 -1
- package/.output/server/{_sessionId-CwGXPD4C.mjs → _sessionId-C5S3pGZd.mjs} +15 -15
- package/.output/server/_sessionId-g6D69lKq.mjs +81 -0
- package/.output/server/_ssr/{CompareDrawer-R_P1-VNr.mjs → CompareDrawer-CqUmGHal.mjs} +138 -103
- package/.output/server/_ssr/InspectorPet-DI0UJd01.mjs +925 -0
- package/.output/server/_ssr/{ProxyViewerContainer-DUgxRNbj.mjs → ProxyViewerContainer-CvsUmbJ3.mjs} +1157 -1613
- package/.output/server/_ssr/{ReplayDialog-De9wKKzS.mjs → ReplayDialog-DS48dpFG.mjs} +18 -18
- package/.output/server/_ssr/{RequestAnatomy-Db4MxUYo.mjs → RequestAnatomy-DxYVQU1C.mjs} +58 -58
- package/.output/server/_ssr/{ResponseView-8rDXOiNB.mjs → ResponseView-BLXW73eq.mjs} +60 -60
- package/.output/server/_ssr/{StreamingChunkSequence-N9teZHkA.mjs → StreamingChunkSequence-DD0iFuy0.mjs} +16 -16
- package/.output/server/_ssr/{index-D4DvByuW.mjs → index-B10LIaK0.mjs} +15 -15
- package/.output/server/_ssr/index-RHVJnU2p.mjs +81 -0
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BjoHRbjS.mjs → json-viewer-LDu2XBaK.mjs} +58 -58
- package/.output/server/_ssr/{router-BYlePDj8.mjs → router-GKvrwifJ.mjs} +9005 -6488
- package/.output/server/_tanstack-start-manifest_v-Df2n4mbT.mjs +4 -0
- package/.output/server/index.mjs +103 -75
- package/.output/workers/logFinalizer.worker.js +16908 -0
- package/.output/workers/sessionWorkerEntry.js +16904 -0
- package/README.md +21 -5
- package/package.json +6 -2
- package/src/assets/logos/qwen.webp +0 -0
- package/src/cli.ts +85 -10
- package/src/components/OnboardingBanner.tsx +41 -21
- package/src/components/ProxyViewer.tsx +198 -269
- package/src/components/ProxyViewerContainer.tsx +128 -17
- package/src/components/ecosystem/AgentLabDialog.tsx +166 -19
- package/src/components/errors/SafeErrorBoundary.tsx +201 -0
- package/src/components/pi-agent/PiAgentPanel.tsx +4 -1
- package/src/components/providers/ImportWizardDialog.tsx +18 -42
- package/src/components/providers/ProviderLogo.tsx +1 -1
- package/src/components/providers/ProvidersPanel.tsx +8 -5
- package/src/components/providers/SettingsDialog.tsx +34 -11
- package/src/components/proxy-viewer/ApplicationBar.tsx +63 -0
- package/src/components/proxy-viewer/CompareDrawer.tsx +92 -49
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +186 -0
- package/src/components/proxy-viewer/LogEntry.tsx +283 -275
- package/src/components/proxy-viewer/RawExportWarning.tsx +77 -0
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +61 -0
- package/src/components/proxy-viewer/ThreadConnector.tsx +16 -1
- package/src/components/proxy-viewer/TurnGroup.tsx +13 -2
- package/src/components/proxy-viewer/accessibility.ts +8 -0
- package/src/components/proxy-viewer/lazy.ts +4 -0
- package/src/components/proxy-viewer/liveConnectionState.ts +158 -0
- package/src/components/proxy-viewer/proxyViewerLogic.ts +10 -0
- package/src/components/proxy-viewer/viewerState.ts +2 -6
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/tabs.tsx +3 -3
- package/src/components/ui/transient-toast.tsx +1 -1
- package/src/lib/apiClient.ts +17 -2
- package/src/lib/ecosystemContract.ts +34 -0
- package/src/lib/export-logs.ts +1 -1
- package/src/lib/providerContract.ts +70 -4
- package/src/lib/providerImportContract.ts +27 -0
- package/src/lib/providerModelMetadata.ts +16 -7
- package/src/lib/resourceLimits.ts +152 -0
- package/src/lib/safeDiagnostic.ts +38 -0
- package/src/lib/useProviders.ts +4 -4
- package/src/mcp/server.ts +39 -6
- package/src/mcp/toolHandlers.ts +131 -4
- package/src/proxy/chunkStorage.ts +20 -6
- package/src/proxy/config.ts +20 -6
- package/src/proxy/dataDir.ts +3 -0
- package/src/proxy/ecosystemExecutionConfirmation.ts +114 -0
- package/src/proxy/ecosystemExecutionRoute.ts +116 -0
- package/src/proxy/ecosystemTasks.ts +48 -0
- package/src/proxy/evidenceExporter.ts +23 -9
- package/src/proxy/groupEvidenceExporter.ts +26 -5
- package/src/proxy/groupStore.ts +13 -3
- package/src/proxy/handler.ts +103 -20
- package/src/proxy/identityProxy.ts +333 -14
- package/src/proxy/logFinalizer.ts +73 -6
- package/src/proxy/logImportUpload.ts +128 -0
- package/src/proxy/logImporter.ts +321 -70
- package/src/proxy/logIndex.ts +16 -6
- package/src/proxy/logger.ts +294 -41
- package/src/proxy/privateDataPath.ts +183 -0
- package/src/proxy/providerScanStore.ts +87 -0
- package/src/proxy/providerSecretStore.ts +58 -19
- package/src/proxy/providers.ts +175 -58
- package/src/proxy/rawStreamCapture.ts +66 -5
- package/src/proxy/runStore.ts +13 -3
- package/src/proxy/runtimeAdmission.ts +52 -0
- package/src/proxy/runtimeHealth.ts +206 -0
- package/src/proxy/runtimeShutdown.ts +75 -0
- package/src/proxy/sessionArchive.ts +15 -2
- package/src/proxy/sessionProcess.ts +19 -0
- package/src/proxy/sessionRuntime.ts +7 -0
- package/src/proxy/shutdownCoordinator.ts +90 -0
- package/src/proxy/sqliteLogIndex.ts +18 -2
- package/src/proxy/store.ts +18 -2
- package/src/routes/__root.tsx +18 -1
- package/src/routes/api/alerts.summary.ts +12 -8
- package/src/routes/api/alerts.ts +27 -9
- package/src/routes/api/ecosystem.packages.$packageId.help.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.install.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +3 -11
- package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +3 -8
- package/src/routes/api/ecosystem.recipes.$recipeId.run.ts +3 -8
- package/src/routes/api/health.ts +9 -2
- package/src/routes/api/knowledge.project-context.ts +11 -0
- package/src/routes/api/knowledge.search.ts +33 -2
- package/src/routes/api/logs.$id.body.ts +16 -2
- package/src/routes/api/logs.import.ts +7 -18
- package/src/routes/api/logs.stream.ts +147 -69
- package/src/routes/api/logs.ts +55 -14
- package/src/routes/api/providers.$providerId.model-metadata.ts +22 -4
- package/src/routes/api/providers.$providerId.ts +37 -6
- package/src/routes/api/providers.export.ts +34 -16
- package/src/routes/api/providers.import.ts +42 -8
- package/src/routes/api/providers.scan.ts +13 -8
- package/src/routes/api/providers.ts +36 -5
- package/src/routes/api/runs.ts +12 -12
- package/src/routes/api/sessions.ts +15 -8
- package/src/routes/index.tsx +6 -0
- package/src/routes/livez.ts +13 -0
- package/src/routes/readyz.ts +18 -0
- package/src/routes/session/$sessionId.tsx +6 -0
- package/styles/globals.css +19 -3
- package/.output/public/assets/CompareDrawer-Cb5ccPeX.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-COBe5GTT.js +0 -4234
- package/.output/public/assets/ResponseView-Cqq87lX_.js +0 -3
- package/.output/public/assets/StreamingChunkSequence-CnTa9mCr.js +0 -1
- package/.output/public/assets/index-DTLNVcgc.js +0 -76
- package/.output/public/assets/index-DliqmwUw.css +0 -1
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/server/_tanstack-start-manifest_v-DMCA8KK3.mjs +0 -4
- package/src/assets/logos/mcp.png +0 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/components/ui/mcp-logo.tsx +0 -20
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { logger, resolveLogDir } from "./logger";
|
|
4
4
|
import type { ApiFormat } from "./schemas";
|
|
5
5
|
import type { LogIndexEntry, LogIndexSummary } from "./logIndex";
|
|
6
|
+
import { ensurePrivateDirectorySync, securePrivateFileSync } from "./privateDataPath";
|
|
6
7
|
|
|
7
8
|
type SqliteState =
|
|
8
9
|
| { status: "ready"; db: unknown; path: string }
|
|
@@ -299,10 +300,19 @@ async function initializeSqliteState(): Promise<SqliteState> {
|
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
try {
|
|
302
|
-
|
|
303
|
+
if (!ensurePrivateDirectorySync(dirname(path))) {
|
|
304
|
+
return { status: "unavailable", path, reason: "SQLite index directory is not private" };
|
|
305
|
+
}
|
|
303
306
|
const db = sqliteConstructor.constructor(path);
|
|
304
307
|
execSql(db, "PRAGMA journal_mode = WAL");
|
|
305
308
|
execSql(db, "PRAGMA synchronous = NORMAL");
|
|
309
|
+
if (!securePrivateFileSync(path, true)) {
|
|
310
|
+
callMethod(db, "close", []);
|
|
311
|
+
return { status: "unavailable", path, reason: "SQLite index file is not private" };
|
|
312
|
+
}
|
|
313
|
+
for (const sidecarPath of [`${path}-wal`, `${path}-shm`]) {
|
|
314
|
+
if (existsSync(sidecarPath)) void securePrivateFileSync(sidecarPath);
|
|
315
|
+
}
|
|
306
316
|
if (!execSql(db, CREATE_SCHEMA_SQL)) {
|
|
307
317
|
return { status: "unavailable", path, reason: "SQLite schema initialization failed" };
|
|
308
318
|
}
|
|
@@ -326,6 +336,12 @@ async function getSqliteState(): Promise<SqliteState> {
|
|
|
326
336
|
return await sqliteStatePromise;
|
|
327
337
|
}
|
|
328
338
|
|
|
339
|
+
/** Report only index availability; internal paths and failure reasons remain private. */
|
|
340
|
+
export async function isSqliteLogIndexReady(): Promise<boolean> {
|
|
341
|
+
const state = await getSqliteState();
|
|
342
|
+
return state.status === "ready";
|
|
343
|
+
}
|
|
344
|
+
|
|
329
345
|
export function closeSqliteLogIndex(): void {
|
|
330
346
|
if (activeDb !== null) {
|
|
331
347
|
callMethod(activeDb, "close", []);
|
package/src/proxy/store.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { open, unlink, rename } from "node:fs/promises";
|
|
|
10
10
|
import { readdir as readdirCallback } from "node:fs/promises";
|
|
11
11
|
import { createInterface } from "node:readline";
|
|
12
12
|
import { Buffer } from "node:buffer";
|
|
13
|
-
import { join } from "node:path";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
14
|
import {
|
|
15
15
|
appendLogEntry,
|
|
16
16
|
resolveLogDir,
|
|
@@ -50,6 +50,11 @@ import {
|
|
|
50
50
|
listArchivedSessionLogs,
|
|
51
51
|
listArchivedSessionIds,
|
|
52
52
|
} from "./sessionArchive";
|
|
53
|
+
import {
|
|
54
|
+
ensurePrivateDirectorySync,
|
|
55
|
+
PRIVATE_FILE_MODE,
|
|
56
|
+
securePrivateFileSync,
|
|
57
|
+
} from "./privateDataPath";
|
|
53
58
|
import {
|
|
54
59
|
clearSessionRegistry,
|
|
55
60
|
getLogSessionId,
|
|
@@ -1383,7 +1388,13 @@ async function rewriteLogFileWithoutIds(
|
|
|
1383
1388
|
ids: ReadonlySet<number>,
|
|
1384
1389
|
): Promise<RewriteLogFileResult> {
|
|
1385
1390
|
const tempPath = tempRewritePath(filePath);
|
|
1386
|
-
|
|
1391
|
+
if (!ensurePrivateDirectorySync(dirname(filePath))) {
|
|
1392
|
+
return { rewritten: false, removedIds: new Set() };
|
|
1393
|
+
}
|
|
1394
|
+
const writer = createWriteStream(tempPath, {
|
|
1395
|
+
encoding: "utf-8",
|
|
1396
|
+
mode: PRIVATE_FILE_MODE,
|
|
1397
|
+
});
|
|
1387
1398
|
const removedIds = new Set<number>();
|
|
1388
1399
|
let keptLineCount = 0;
|
|
1389
1400
|
let writeOk = true;
|
|
@@ -1415,6 +1426,11 @@ async function rewriteLogFileWithoutIds(
|
|
|
1415
1426
|
return { rewritten: false, removedIds: new Set() };
|
|
1416
1427
|
}
|
|
1417
1428
|
|
|
1429
|
+
if (!securePrivateFileSync(tempPath)) {
|
|
1430
|
+
await removeTempFile(tempPath);
|
|
1431
|
+
return { rewritten: false, removedIds: new Set() };
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1418
1434
|
if (removedIds.size === 0) {
|
|
1419
1435
|
await removeTempFile(tempPath);
|
|
1420
1436
|
return { rewritten: false, removedIds };
|
package/src/routes/__root.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { Outlet, createRootRoute, HeadContent, Scripts } from "@tanstack/react-r
|
|
|
3
3
|
import { useEffect, type ReactNode } from "react";
|
|
4
4
|
import { SWRConfig } from "swr";
|
|
5
5
|
import faviconSvg from "../assets/favicon.svg?url";
|
|
6
|
+
import { SafeErrorBoundary, SafeRouteError } from "../components/errors/SafeErrorBoundary";
|
|
6
7
|
import appCss from "../index.css?url";
|
|
7
8
|
import { installChunkLoadRecovery } from "../lib/chunkLoadRecovery";
|
|
8
9
|
import { THEME_MODE_STORAGE_KEY } from "../lib/themeMode";
|
|
@@ -94,6 +95,7 @@ export const Route = createRootRoute({
|
|
|
94
95
|
],
|
|
95
96
|
}),
|
|
96
97
|
component: RootComponent,
|
|
98
|
+
errorComponent: RootRouteErrorComponent,
|
|
97
99
|
notFoundComponent: RootNotFoundComponent,
|
|
98
100
|
});
|
|
99
101
|
|
|
@@ -102,7 +104,22 @@ function RootComponent() {
|
|
|
102
104
|
|
|
103
105
|
return (
|
|
104
106
|
<RootDocument>
|
|
105
|
-
<
|
|
107
|
+
<SafeErrorBoundary
|
|
108
|
+
scope="root-render"
|
|
109
|
+
title="Agent Inspector could not continue"
|
|
110
|
+
description="The application stopped this render safely. Retry or reload Inspector."
|
|
111
|
+
variant="page"
|
|
112
|
+
>
|
|
113
|
+
<Outlet />
|
|
114
|
+
</SafeErrorBoundary>
|
|
115
|
+
</RootDocument>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function RootRouteErrorComponent({ error, reset }: { error: Error; reset: () => void }) {
|
|
120
|
+
return (
|
|
121
|
+
<RootDocument>
|
|
122
|
+
<SafeRouteError error={error} reset={reset} scope="root-route" />
|
|
106
123
|
</RootDocument>
|
|
107
124
|
);
|
|
108
125
|
}
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
2
|
import { getInspectorAlertSummary } from "../../services/alerts";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
if (value === null) return undefined;
|
|
6
|
-
const parsed = Number(value);
|
|
7
|
-
if (!Number.isInteger(parsed) || parsed < 1) return undefined;
|
|
8
|
-
return parsed;
|
|
9
|
-
}
|
|
3
|
+
import { DEFAULT_ALERT_SCAN_LIMIT, MAX_ALERT_SCAN_LIMIT } from "../../proxy/alerts";
|
|
4
|
+
import { parseBoundedIntegerParameter } from "../../lib/resourceLimits";
|
|
10
5
|
|
|
11
6
|
export const Route = createFileRoute("/api/alerts/summary")({
|
|
12
7
|
server: {
|
|
13
8
|
handlers: {
|
|
14
9
|
GET: async ({ request }: { request: Request }) => {
|
|
15
10
|
const url = new URL(request.url);
|
|
11
|
+
const scanLimitResult = parseBoundedIntegerParameter({
|
|
12
|
+
value: url.searchParams.get("scanLimit"),
|
|
13
|
+
parameter: "scanLimit",
|
|
14
|
+
fallback: DEFAULT_ALERT_SCAN_LIMIT,
|
|
15
|
+
minimum: 1,
|
|
16
|
+
maximum: MAX_ALERT_SCAN_LIMIT,
|
|
17
|
+
resource: "alerts-scan-size",
|
|
18
|
+
});
|
|
19
|
+
if (!scanLimitResult.ok) return scanLimitResult.response;
|
|
16
20
|
return Response.json(
|
|
17
21
|
await getInspectorAlertSummary({
|
|
18
|
-
scanLimit:
|
|
22
|
+
scanLimit: scanLimitResult.value,
|
|
19
23
|
}),
|
|
20
24
|
);
|
|
21
25
|
},
|
package/src/routes/api/alerts.ts
CHANGED
|
@@ -8,6 +8,13 @@ import {
|
|
|
8
8
|
type AlertSource,
|
|
9
9
|
} from "../../lib/alertContract";
|
|
10
10
|
import { listInspectorAlerts } from "../../services/alerts";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_ALERT_LIMIT,
|
|
13
|
+
DEFAULT_ALERT_SCAN_LIMIT,
|
|
14
|
+
MAX_ALERT_LIMIT,
|
|
15
|
+
MAX_ALERT_SCAN_LIMIT,
|
|
16
|
+
} from "../../proxy/alerts";
|
|
17
|
+
import { parseBoundedIntegerParameter } from "../../lib/resourceLimits";
|
|
11
18
|
|
|
12
19
|
type ParsedFilters = {
|
|
13
20
|
severity?: AlertSeverity;
|
|
@@ -15,13 +22,6 @@ type ParsedFilters = {
|
|
|
15
22
|
source?: AlertSource;
|
|
16
23
|
};
|
|
17
24
|
|
|
18
|
-
function parsePositiveInt(value: string | null): number | undefined {
|
|
19
|
-
if (value === null) return undefined;
|
|
20
|
-
const parsed = Number(value);
|
|
21
|
-
if (!Number.isInteger(parsed) || parsed < 1) return undefined;
|
|
22
|
-
return parsed;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
25
|
function parseFilters(url: URL): ParsedFilters {
|
|
26
26
|
const severityResult = AlertSeveritySchema.safeParse(url.searchParams.get("severity"));
|
|
27
27
|
const categoryResult = AlertCategorySchema.safeParse(url.searchParams.get("category"));
|
|
@@ -39,11 +39,29 @@ export const Route = createFileRoute("/api/alerts")({
|
|
|
39
39
|
handlers: {
|
|
40
40
|
GET: async ({ request }: { request: Request }) => {
|
|
41
41
|
const url = new URL(request.url);
|
|
42
|
+
const limitResult = parseBoundedIntegerParameter({
|
|
43
|
+
value: url.searchParams.get("limit"),
|
|
44
|
+
parameter: "limit",
|
|
45
|
+
fallback: DEFAULT_ALERT_LIMIT,
|
|
46
|
+
minimum: 1,
|
|
47
|
+
maximum: MAX_ALERT_LIMIT,
|
|
48
|
+
resource: "alerts-page-size",
|
|
49
|
+
});
|
|
50
|
+
if (!limitResult.ok) return limitResult.response;
|
|
51
|
+
const scanLimitResult = parseBoundedIntegerParameter({
|
|
52
|
+
value: url.searchParams.get("scanLimit"),
|
|
53
|
+
parameter: "scanLimit",
|
|
54
|
+
fallback: DEFAULT_ALERT_SCAN_LIMIT,
|
|
55
|
+
minimum: 1,
|
|
56
|
+
maximum: MAX_ALERT_SCAN_LIMIT,
|
|
57
|
+
resource: "alerts-scan-size",
|
|
58
|
+
});
|
|
59
|
+
if (!scanLimitResult.ok) return scanLimitResult.response;
|
|
42
60
|
return Response.json(
|
|
43
61
|
await listInspectorAlerts({
|
|
44
62
|
...parseFilters(url),
|
|
45
|
-
limit:
|
|
46
|
-
scanLimit:
|
|
63
|
+
limit: limitResult.value,
|
|
64
|
+
scanLimit: scanLimitResult.value,
|
|
47
65
|
}),
|
|
48
66
|
);
|
|
49
67
|
},
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
2
|
+
import { handleEcosystemPackageExecution } from "../../proxy/ecosystemExecutionRoute";
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute("/api/ecosystem/packages/$packageId/help")({
|
|
5
5
|
server: {
|
|
6
6
|
handlers: {
|
|
7
|
-
POST: ({ params }: { params: { packageId: string } }) =>
|
|
8
|
-
|
|
9
|
-
if (task === null) {
|
|
10
|
-
return Response.json({ error: "Unknown ecosystem package" }, { status: 404 });
|
|
11
|
-
}
|
|
12
|
-
return Response.json({ task });
|
|
13
|
-
},
|
|
7
|
+
POST: ({ params, request }: { params: { packageId: string }; request: Request }) =>
|
|
8
|
+
handleEcosystemPackageExecution(request, params.packageId, "help"),
|
|
14
9
|
},
|
|
15
10
|
},
|
|
16
11
|
});
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
2
|
+
import { handleEcosystemPackageExecution } from "../../proxy/ecosystemExecutionRoute";
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute("/api/ecosystem/packages/$packageId/install")({
|
|
5
5
|
server: {
|
|
6
6
|
handlers: {
|
|
7
|
-
POST: ({ params }: { params: { packageId: string } }) =>
|
|
8
|
-
|
|
9
|
-
if (task === null) {
|
|
10
|
-
return Response.json({ error: "Unknown ecosystem package" }, { status: 404 });
|
|
11
|
-
}
|
|
12
|
-
return Response.json({ task });
|
|
13
|
-
},
|
|
7
|
+
POST: ({ params, request }: { params: { packageId: string }; request: Request }) =>
|
|
8
|
+
handleEcosystemPackageExecution(request, params.packageId, "install"),
|
|
14
9
|
},
|
|
15
10
|
},
|
|
16
11
|
});
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
2
|
+
import { handleEcosystemPackageExecution } from "../../proxy/ecosystemExecutionRoute";
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute("/api/ecosystem/packages/$packageId/runner-presets")({
|
|
5
5
|
server: {
|
|
6
6
|
handlers: {
|
|
7
|
-
POST: ({ params }: { params: { packageId: string } }) =>
|
|
8
|
-
|
|
9
|
-
if (task === null) {
|
|
10
|
-
return Response.json(
|
|
11
|
-
{ error: "Runner presets are only available for Eval Harness" },
|
|
12
|
-
{ status: 404 },
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
return Response.json({ task });
|
|
16
|
-
},
|
|
7
|
+
POST: ({ params, request }: { params: { packageId: string }; request: Request }) =>
|
|
8
|
+
handleEcosystemPackageExecution(request, params.packageId, "runner-presets"),
|
|
17
9
|
},
|
|
18
10
|
},
|
|
19
11
|
});
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
2
|
+
import { handleEcosystemPackageExecution } from "../../proxy/ecosystemExecutionRoute";
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute("/api/ecosystem/packages/$packageId/upgrade")({
|
|
5
5
|
server: {
|
|
6
6
|
handlers: {
|
|
7
|
-
POST: ({ params }: { params: { packageId: string } }) =>
|
|
8
|
-
|
|
9
|
-
if (task === null) {
|
|
10
|
-
return Response.json({ error: "Unknown ecosystem package" }, { status: 404 });
|
|
11
|
-
}
|
|
12
|
-
return Response.json({ task });
|
|
13
|
-
},
|
|
7
|
+
POST: ({ params, request }: { params: { packageId: string }; request: Request }) =>
|
|
8
|
+
handleEcosystemPackageExecution(request, params.packageId, "upgrade"),
|
|
14
9
|
},
|
|
15
10
|
},
|
|
16
11
|
});
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
2
|
+
import { handleEcosystemRecipeExecution } from "../../proxy/ecosystemExecutionRoute";
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute("/api/ecosystem/recipes/$recipeId/run")({
|
|
5
5
|
server: {
|
|
6
6
|
handlers: {
|
|
7
|
-
POST: ({ params }: { params: { recipeId: string } }) =>
|
|
8
|
-
|
|
9
|
-
if (task === null) {
|
|
10
|
-
return Response.json({ error: "Unknown ecosystem recipe" }, { status: 404 });
|
|
11
|
-
}
|
|
12
|
-
return Response.json({ task });
|
|
13
|
-
},
|
|
7
|
+
POST: ({ params, request }: { params: { recipeId: string }; request: Request }) =>
|
|
8
|
+
handleEcosystemRecipeExecution(request, params.recipeId),
|
|
14
9
|
},
|
|
15
10
|
},
|
|
16
11
|
});
|
package/src/routes/api/health.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
2
|
import { logger } from "../../proxy/logger";
|
|
3
|
+
import { getRuntimeReadiness } from "../../proxy/runtimeHealth";
|
|
4
|
+
import { installRuntimeShutdownSignalHandlers } from "../../proxy/runtimeShutdown";
|
|
3
5
|
|
|
4
6
|
// Trigger logger auto-initialization when this module loads
|
|
7
|
+
installRuntimeShutdownSignalHandlers();
|
|
5
8
|
logger.debug("Health endpoint loaded");
|
|
6
9
|
|
|
7
10
|
export const Route = createFileRoute("/api/health")({
|
|
8
11
|
server: {
|
|
9
12
|
handlers: {
|
|
10
|
-
GET: () => {
|
|
11
|
-
|
|
13
|
+
GET: async () => {
|
|
14
|
+
const readiness = await getRuntimeReadiness();
|
|
15
|
+
return Response.json(
|
|
16
|
+
{ status: "ok", readiness: readiness.status },
|
|
17
|
+
{ headers: { "cache-control": "no-store" } },
|
|
18
|
+
);
|
|
12
19
|
},
|
|
13
20
|
},
|
|
14
21
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
2
|
import { searchOpenClaw } from "../../knowledge/openclawClient";
|
|
3
|
+
import { MAX_SEARCH_PROJECT_CHARS, resourceLimitResponse } from "../../lib/resourceLimits";
|
|
3
4
|
|
|
4
5
|
export const Route = createFileRoute("/api/knowledge/project-context")({
|
|
5
6
|
server: {
|
|
@@ -10,6 +11,16 @@ export const Route = createFileRoute("/api/knowledge/project-context")({
|
|
|
10
11
|
if (project === "") {
|
|
11
12
|
return Response.json({ error: "Missing project search parameter" }, { status: 400 });
|
|
12
13
|
}
|
|
14
|
+
if (project.length > MAX_SEARCH_PROJECT_CHARS) {
|
|
15
|
+
return resourceLimitResponse({
|
|
16
|
+
error: `Search project filters may contain at most ${String(MAX_SEARCH_PROJECT_CHARS)} characters.`,
|
|
17
|
+
code: "search-limit-exceeded",
|
|
18
|
+
status: 400,
|
|
19
|
+
resource: "knowledge-search-project",
|
|
20
|
+
limit: MAX_SEARCH_PROJECT_CHARS,
|
|
21
|
+
retryable: false,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
13
24
|
const response = await searchOpenClaw(`project context ${project}`, project);
|
|
14
25
|
return Response.json({ project, ...response });
|
|
15
26
|
},
|
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { searchOpenClaw } from "../../knowledge/openclawClient";
|
|
4
|
+
import { MAX_SEARCH_QUERY_CHARS, resourceLimitResponse } from "../../lib/resourceLimits";
|
|
4
5
|
|
|
5
6
|
const SearchBodySchema = z.object({
|
|
6
|
-
query: z.string().min(1),
|
|
7
|
+
query: z.string().min(1).max(MAX_SEARCH_QUERY_CHARS),
|
|
7
8
|
project: z.string().optional(),
|
|
8
9
|
});
|
|
9
10
|
|
|
11
|
+
function searchLimitResponse(): Response {
|
|
12
|
+
return resourceLimitResponse({
|
|
13
|
+
error: `Search queries may contain at most ${String(MAX_SEARCH_QUERY_CHARS)} characters.`,
|
|
14
|
+
code: "search-limit-exceeded",
|
|
15
|
+
status: 400,
|
|
16
|
+
resource: "knowledge-search-query",
|
|
17
|
+
limit: MAX_SEARCH_QUERY_CHARS,
|
|
18
|
+
retryable: false,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function bodySearchQuery(value: unknown): string | null {
|
|
23
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
24
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, "query");
|
|
25
|
+
if (descriptor === undefined) return null;
|
|
26
|
+
const query: unknown = descriptor.value;
|
|
27
|
+
return typeof query === "string" ? query : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
export const Route = createFileRoute("/api/knowledge/search")({
|
|
11
31
|
server: {
|
|
12
32
|
handlers: {
|
|
@@ -17,10 +37,21 @@ export const Route = createFileRoute("/api/knowledge/search")({
|
|
|
17
37
|
if (query === "") {
|
|
18
38
|
return Response.json({ error: "Missing q search parameter" }, { status: 400 });
|
|
19
39
|
}
|
|
40
|
+
if (query.length > MAX_SEARCH_QUERY_CHARS) return searchLimitResponse();
|
|
20
41
|
return Response.json(await searchOpenClaw(query, project));
|
|
21
42
|
},
|
|
22
43
|
POST: async ({ request }: { request: Request }) => {
|
|
23
|
-
|
|
44
|
+
let body: unknown;
|
|
45
|
+
try {
|
|
46
|
+
body = await request.json();
|
|
47
|
+
} catch {
|
|
48
|
+
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
49
|
+
}
|
|
50
|
+
const query = bodySearchQuery(body);
|
|
51
|
+
if (query !== null && query.length > MAX_SEARCH_QUERY_CHARS) {
|
|
52
|
+
return searchLimitResponse();
|
|
53
|
+
}
|
|
54
|
+
const parsed = SearchBodySchema.safeParse(body);
|
|
24
55
|
if (!parsed.success) {
|
|
25
56
|
return Response.json({ error: parsed.error.message }, { status: 400 });
|
|
26
57
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
2
|
import { LogBodyPartSchema } from "../../contracts";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createLogBodyChunk,
|
|
5
|
+
DEFAULT_LOG_BODY_CHUNK_BYTES,
|
|
6
|
+
MAX_LOG_BODY_CHUNK_BYTES,
|
|
7
|
+
} from "../../proxy/logBodyChunks";
|
|
4
8
|
import { getLogById } from "../../proxy/store";
|
|
9
|
+
import { parseBoundedIntegerParameter } from "../../lib/resourceLimits";
|
|
5
10
|
|
|
6
11
|
function parseIntegerParam(value: string | null, fallback: number): number {
|
|
7
12
|
if (value === null) return fallback;
|
|
@@ -24,6 +29,15 @@ export const Route = createFileRoute("/api/logs/$id/body")({
|
|
|
24
29
|
if (!partResult.success) {
|
|
25
30
|
return Response.json({ error: "Invalid body part" }, { status: 400 });
|
|
26
31
|
}
|
|
32
|
+
const limitResult = parseBoundedIntegerParameter({
|
|
33
|
+
value: url.searchParams.get("limit"),
|
|
34
|
+
parameter: "limit",
|
|
35
|
+
fallback: DEFAULT_LOG_BODY_CHUNK_BYTES,
|
|
36
|
+
minimum: 1,
|
|
37
|
+
maximum: MAX_LOG_BODY_CHUNK_BYTES,
|
|
38
|
+
resource: "log-body-page-bytes",
|
|
39
|
+
});
|
|
40
|
+
if (!limitResult.ok) return limitResult.response;
|
|
27
41
|
|
|
28
42
|
const log = await getLogById(id);
|
|
29
43
|
if (log === null) {
|
|
@@ -35,7 +49,7 @@ export const Route = createFileRoute("/api/logs/$id/body")({
|
|
|
35
49
|
log,
|
|
36
50
|
part: partResult.data,
|
|
37
51
|
offset: parseIntegerParam(url.searchParams.get("offset"), 0),
|
|
38
|
-
limit:
|
|
52
|
+
limit: limitResult.value,
|
|
39
53
|
}),
|
|
40
54
|
);
|
|
41
55
|
},
|
|
@@ -1,32 +1,21 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
2
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
3
2
|
import { parseImportedLogFile, sessionNameFromImportFileName } from "../../proxy/logImporter";
|
|
3
|
+
import { readBoundedLogImportUpload } from "../../proxy/logImportUpload";
|
|
4
4
|
import { compactLogForList, importCapturedLogs } from "../../proxy/store";
|
|
5
5
|
|
|
6
|
-
function isUploadedFile(value: FormDataEntryValue | null): value is File {
|
|
7
|
-
return value instanceof File;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
6
|
export const Route = createFileRoute("/api/logs/import")({
|
|
11
7
|
server: {
|
|
12
8
|
handlers: {
|
|
13
9
|
POST: async ({ request }: { request: Request }) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} catch {
|
|
18
|
-
return Response.json({ error: "Expected multipart form data" }, { status: 400 });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const upload = formData.get("file");
|
|
22
|
-
if (!isUploadedFile(upload)) {
|
|
23
|
-
return Response.json({ error: "Missing import file" }, { status: 400 });
|
|
10
|
+
const upload = await readBoundedLogImportUpload(request);
|
|
11
|
+
if (!upload.ok) {
|
|
12
|
+
return Response.json({ error: upload.message }, { status: upload.status });
|
|
24
13
|
}
|
|
25
14
|
|
|
26
|
-
const sessionId = sessionNameFromImportFileName(upload.
|
|
15
|
+
const sessionId = sessionNameFromImportFileName(upload.fileName);
|
|
27
16
|
const parsed = await parseImportedLogFile({
|
|
28
|
-
fileName: upload.
|
|
29
|
-
data:
|
|
17
|
+
fileName: upload.fileName,
|
|
18
|
+
data: upload.data,
|
|
30
19
|
});
|
|
31
20
|
|
|
32
21
|
if (!parsed.ok) {
|