@spotpatch/dev-server 0.4.0 → 0.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.cjs +383 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -2
- package/dist/index.d.ts +44 -2
- package/dist/index.js +380 -100
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -742,7 +742,8 @@ import {
|
|
|
742
742
|
DEFAULT_AGENT_LIMITS,
|
|
743
743
|
MAX_ANNOTATION_TARGETS,
|
|
744
744
|
SPOTPATCH_EDITOR_PREFERENCES,
|
|
745
|
-
SPOTPATCH_LOCALE_PREFERENCES
|
|
745
|
+
SPOTPATCH_LOCALE_PREFERENCES,
|
|
746
|
+
DEFAULT_DATA_FLOW_LIMITS
|
|
746
747
|
} from "@spotpatch/shared";
|
|
747
748
|
import { z } from "zod";
|
|
748
749
|
var DEFAULT_EXCLUDE = Object.freeze([
|
|
@@ -753,7 +754,7 @@ var DEFAULT_EXCLUDE = Object.freeze([
|
|
|
753
754
|
/(?:^|\/)dist(?:\/|$)/,
|
|
754
755
|
/(?:^|\/)coverage(?:\/|$)/
|
|
755
756
|
]);
|
|
756
|
-
var DEFAULT_INCLUDE = Object.freeze([/(?:^|[/\\])src[/\\].+\.(?:jsx|tsx)$/]);
|
|
757
|
+
var DEFAULT_INCLUDE = Object.freeze([/(?:^|[/\\])src[/\\].+\.(?:js|jsx|ts|tsx)$/]);
|
|
757
758
|
var DEFAULT_BUDGET = Object.freeze({
|
|
758
759
|
totalCharacters: 16e3,
|
|
759
760
|
domCharacters: 3e3,
|
|
@@ -774,7 +775,12 @@ var DEFAULT_OPTIONS = Object.freeze({
|
|
|
774
775
|
debug: false,
|
|
775
776
|
locale: "auto",
|
|
776
777
|
maxTargets: 8,
|
|
777
|
-
ai: false
|
|
778
|
+
ai: false,
|
|
779
|
+
dataFlow: Object.freeze({
|
|
780
|
+
enabled: false,
|
|
781
|
+
runtime: "dispatch",
|
|
782
|
+
limits: DEFAULT_DATA_FLOW_LIMITS
|
|
783
|
+
})
|
|
778
784
|
});
|
|
779
785
|
var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
780
786
|
var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,127}$/;
|
|
@@ -1066,6 +1072,36 @@ function assertPositiveBudget(budget) {
|
|
|
1066
1072
|
}
|
|
1067
1073
|
}
|
|
1068
1074
|
}
|
|
1075
|
+
function resolveDataFlowOptions(options) {
|
|
1076
|
+
if (options === void 0 || options === false) {
|
|
1077
|
+
return DEFAULT_OPTIONS.dataFlow;
|
|
1078
|
+
}
|
|
1079
|
+
const candidate = options;
|
|
1080
|
+
if (typeof candidate !== "object" || candidate === null) {
|
|
1081
|
+
throw new RangeError("SpotPatch dataFlow configuration is invalid.");
|
|
1082
|
+
}
|
|
1083
|
+
const runtime = options.runtime ?? "dispatch";
|
|
1084
|
+
if (runtime !== "dispatch") {
|
|
1085
|
+
throw new RangeError("SpotPatch dataFlow runtime mode is invalid.");
|
|
1086
|
+
}
|
|
1087
|
+
return Object.freeze({
|
|
1088
|
+
enabled: true,
|
|
1089
|
+
runtime,
|
|
1090
|
+
limits: DEFAULT_DATA_FLOW_LIMITS
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
function createRuntimeDataFlowConfig(options) {
|
|
1094
|
+
return Object.freeze({
|
|
1095
|
+
enabled: options.enabled,
|
|
1096
|
+
runtime: options.runtime,
|
|
1097
|
+
limits: Object.freeze({
|
|
1098
|
+
observationMaxEntries: options.limits.observationMaxEntries,
|
|
1099
|
+
observationMaxBytes: options.limits.observationMaxBytes,
|
|
1100
|
+
observationTtlMs: options.limits.observationTtlMs,
|
|
1101
|
+
reportMaxBytes: options.limits.reportMaxBytes
|
|
1102
|
+
})
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1069
1105
|
function resolveOptions(options = {}, environmentAi) {
|
|
1070
1106
|
if (options.trustedFastMode !== void 0 && typeof options.trustedFastMode !== "boolean") {
|
|
1071
1107
|
throw new RangeError("SpotPatch trustedFastMode must be a boolean.");
|
|
@@ -1101,7 +1137,8 @@ function resolveOptions(options = {}, environmentAi) {
|
|
|
1101
1137
|
debug: options.debug ?? DEFAULT_OPTIONS.debug,
|
|
1102
1138
|
locale,
|
|
1103
1139
|
maxTargets,
|
|
1104
|
-
ai: resolveAiOptions(options.ai ?? environmentAi)
|
|
1140
|
+
ai: resolveAiOptions(options.ai ?? environmentAi),
|
|
1141
|
+
dataFlow: resolveDataFlowOptions(options.dataFlow)
|
|
1105
1142
|
};
|
|
1106
1143
|
if (resolved.shortcut.trim().length === 0 || resolved.shortcut.length > 128 || resolved.shortcut.includes("\0")) {
|
|
1107
1144
|
throw new RangeError("SpotPatch shortcut is invalid.");
|
|
@@ -1287,37 +1324,62 @@ function createSourceRegistry(options = {}) {
|
|
|
1287
1324
|
const createId = options.createId ?? createRandomSourceId;
|
|
1288
1325
|
const pathToId = /* @__PURE__ */ new Map();
|
|
1289
1326
|
const idToPath = /* @__PURE__ */ new Map();
|
|
1327
|
+
const componentAnchors = /* @__PURE__ */ new Map();
|
|
1328
|
+
const componentIdsByPath = /* @__PURE__ */ new Map();
|
|
1329
|
+
function registerSourcePath(absolutePath) {
|
|
1330
|
+
const normalizedPath = normalizeAbsolutePath(absolutePath);
|
|
1331
|
+
const existingId = pathToId.get(normalizedPath);
|
|
1332
|
+
if (existingId !== void 0) {
|
|
1333
|
+
return existingId;
|
|
1334
|
+
}
|
|
1335
|
+
let fileId = createId();
|
|
1336
|
+
while (idToPath.has(fileId)) fileId = createId();
|
|
1337
|
+
pathToId.set(normalizedPath, fileId);
|
|
1338
|
+
idToPath.set(fileId, normalizedPath);
|
|
1339
|
+
return fileId;
|
|
1340
|
+
}
|
|
1290
1341
|
return Object.freeze({
|
|
1291
1342
|
register(absolutePath) {
|
|
1343
|
+
return registerSourcePath(absolutePath);
|
|
1344
|
+
},
|
|
1345
|
+
registerDataFlowComponents(absolutePath, sourceVersion, components) {
|
|
1292
1346
|
const normalizedPath = normalizeAbsolutePath(absolutePath);
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
|
|
1347
|
+
const previousIds = componentIdsByPath.get(normalizedPath);
|
|
1348
|
+
for (const componentSourceId of previousIds ?? []) {
|
|
1349
|
+
componentAnchors.delete(componentSourceId);
|
|
1296
1350
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1351
|
+
const fileId = registerSourcePath(normalizedPath);
|
|
1352
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
1353
|
+
for (const component of components) {
|
|
1354
|
+
currentIds.add(component.componentSourceId);
|
|
1355
|
+
componentAnchors.set(
|
|
1356
|
+
component.componentSourceId,
|
|
1357
|
+
Object.freeze({ ...component, fileId, sourceVersion })
|
|
1358
|
+
);
|
|
1300
1359
|
}
|
|
1301
|
-
|
|
1302
|
-
idToPath.set(fileId, normalizedPath);
|
|
1303
|
-
return fileId;
|
|
1360
|
+
componentIdsByPath.set(normalizedPath, currentIds);
|
|
1304
1361
|
},
|
|
1305
1362
|
resolve(fileId) {
|
|
1306
1363
|
return idToPath.get(fileId);
|
|
1307
1364
|
},
|
|
1365
|
+
resolveDataFlowComponent(componentSourceId) {
|
|
1366
|
+
return componentAnchors.get(componentSourceId);
|
|
1367
|
+
},
|
|
1308
1368
|
clear() {
|
|
1309
1369
|
pathToId.clear();
|
|
1310
1370
|
idToPath.clear();
|
|
1371
|
+
componentAnchors.clear();
|
|
1372
|
+
componentIdsByPath.clear();
|
|
1311
1373
|
}
|
|
1312
1374
|
});
|
|
1313
1375
|
}
|
|
1314
1376
|
|
|
1315
1377
|
// src/server/middleware.ts
|
|
1316
1378
|
import {
|
|
1317
|
-
ERROR_CODES as
|
|
1379
|
+
ERROR_CODES as ERROR_CODES10,
|
|
1318
1380
|
SPOTPATCH_API_BASE,
|
|
1319
1381
|
SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS2,
|
|
1320
|
-
SpotPatchError as
|
|
1382
|
+
SpotPatchError as SpotPatchError10,
|
|
1321
1383
|
openEditorRequestSchema,
|
|
1322
1384
|
sourceContextRequestSchema
|
|
1323
1385
|
} from "@spotpatch/shared";
|
|
@@ -2150,10 +2212,186 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
|
|
|
2150
2212
|
}
|
|
2151
2213
|
var launchConfiguredEditor = createEditorLauncher();
|
|
2152
2214
|
|
|
2215
|
+
// src/server/data-flow-http.ts
|
|
2216
|
+
import { createHash as createHash2 } from "crypto";
|
|
2217
|
+
import {
|
|
2218
|
+
createStaticDataFlowAnalyzer
|
|
2219
|
+
} from "@spotpatch/analyzer";
|
|
2220
|
+
import {
|
|
2221
|
+
DATA_FLOW_SCHEMA_VERSION,
|
|
2222
|
+
ERROR_CODES as ERROR_CODES7,
|
|
2223
|
+
SpotPatchError as SpotPatchError7,
|
|
2224
|
+
dataFlowComponentReportRequestSchema,
|
|
2225
|
+
dataFlowPageReportRequestSchema,
|
|
2226
|
+
limitDataFlowReportCollections
|
|
2227
|
+
} from "@spotpatch/shared";
|
|
2228
|
+
function envelopeBytes(report) {
|
|
2229
|
+
return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
|
|
2230
|
+
}
|
|
2231
|
+
function limitDataFlowReportToBytes(report, maximumBytes) {
|
|
2232
|
+
const structurallyLimited = limitDataFlowReportCollections(report);
|
|
2233
|
+
if (envelopeBytes(structurallyLimited) <= maximumBytes) {
|
|
2234
|
+
return structurallyLimited;
|
|
2235
|
+
}
|
|
2236
|
+
let limited = limitDataFlowReportCollections(structurallyLimited, {
|
|
2237
|
+
forceTruncation: true,
|
|
2238
|
+
maximumDependencies: 0,
|
|
2239
|
+
truncatedBy: "bytes"
|
|
2240
|
+
});
|
|
2241
|
+
if (envelopeBytes(limited) > maximumBytes) {
|
|
2242
|
+
throw new SpotPatchError7(ERROR_CODES7.INTERNAL_ERROR);
|
|
2243
|
+
}
|
|
2244
|
+
for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
|
|
2245
|
+
const candidate = limitDataFlowReportCollections(structurallyLimited, {
|
|
2246
|
+
forceTruncation: true,
|
|
2247
|
+
maximumDependencies,
|
|
2248
|
+
truncatedBy: "bytes"
|
|
2249
|
+
});
|
|
2250
|
+
if (envelopeBytes(candidate) > maximumBytes) break;
|
|
2251
|
+
limited = candidate;
|
|
2252
|
+
}
|
|
2253
|
+
return limited;
|
|
2254
|
+
}
|
|
2255
|
+
function createDataFlowAnalyzer(options) {
|
|
2256
|
+
if (!options.options.dataFlow.enabled) return void 0;
|
|
2257
|
+
return createStaticDataFlowAnalyzer({
|
|
2258
|
+
root: options.root,
|
|
2259
|
+
registryEpoch: options.session.id,
|
|
2260
|
+
registerSource: (absolutePath) => options.registry.register(absolutePath),
|
|
2261
|
+
limits: options.options.dataFlow.limits
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
async function analyzeTarget(request, analyzer, options) {
|
|
2265
|
+
const resolvedRequest = (() => {
|
|
2266
|
+
if ("componentSourceId" in request) {
|
|
2267
|
+
const anchor = options.registry.resolveDataFlowComponent(
|
|
2268
|
+
request.componentSourceId
|
|
2269
|
+
);
|
|
2270
|
+
if (anchor?.sourceVersion !== request.sourceVersion) {
|
|
2271
|
+
throw new SpotPatchError7(ERROR_CODES7.DATA_FLOW_SOURCE_STALE);
|
|
2272
|
+
}
|
|
2273
|
+
return anchor;
|
|
2274
|
+
}
|
|
2275
|
+
return request;
|
|
2276
|
+
})();
|
|
2277
|
+
const absolutePath = await resolveSourceFile({
|
|
2278
|
+
fileId: resolvedRequest.fileId,
|
|
2279
|
+
registry: options.registry,
|
|
2280
|
+
root: options.root
|
|
2281
|
+
});
|
|
2282
|
+
const report = analyzer.analyzeComponent({
|
|
2283
|
+
absolutePath,
|
|
2284
|
+
line: resolvedRequest.line,
|
|
2285
|
+
column: resolvedRequest.column
|
|
2286
|
+
});
|
|
2287
|
+
if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
|
|
2288
|
+
throw new SpotPatchError7(ERROR_CODES7.DATA_FLOW_SOURCE_STALE);
|
|
2289
|
+
}
|
|
2290
|
+
return limitDataFlowReportToBytes(
|
|
2291
|
+
report,
|
|
2292
|
+
options.options.dataFlow.limits.reportMaxBytes
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
function requireAnalyzer(analyzer) {
|
|
2296
|
+
if (analyzer === void 0) {
|
|
2297
|
+
throw new SpotPatchError7(ERROR_CODES7.DATA_FLOW_DISABLED);
|
|
2298
|
+
}
|
|
2299
|
+
return analyzer;
|
|
2300
|
+
}
|
|
2301
|
+
async function handleComponentDataFlowReport(request, analyzer, options) {
|
|
2302
|
+
const parsed = dataFlowComponentReportRequestSchema.safeParse(
|
|
2303
|
+
await readJsonRequestBody(
|
|
2304
|
+
request,
|
|
2305
|
+
options.options.dataFlow.limits.protocolRequestMaxBytes
|
|
2306
|
+
)
|
|
2307
|
+
);
|
|
2308
|
+
if (!parsed.success) {
|
|
2309
|
+
throw new SpotPatchError7(ERROR_CODES7.INVALID_REQUEST);
|
|
2310
|
+
}
|
|
2311
|
+
return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
|
|
2312
|
+
}
|
|
2313
|
+
async function handlePageDataFlowReport(request, analyzer, options) {
|
|
2314
|
+
const parsed = dataFlowPageReportRequestSchema.safeParse(
|
|
2315
|
+
await readJsonRequestBody(
|
|
2316
|
+
request,
|
|
2317
|
+
options.options.dataFlow.limits.protocolRequestMaxBytes
|
|
2318
|
+
)
|
|
2319
|
+
);
|
|
2320
|
+
if (!parsed.success) {
|
|
2321
|
+
throw new SpotPatchError7(ERROR_CODES7.INVALID_REQUEST);
|
|
2322
|
+
}
|
|
2323
|
+
const activeAnalyzer = requireAnalyzer(analyzer);
|
|
2324
|
+
const componentReports = await Promise.all(
|
|
2325
|
+
parsed.data.targets.map((target) => analyzeTarget(target, activeAnalyzer, options))
|
|
2326
|
+
);
|
|
2327
|
+
const dependencies = new Map(
|
|
2328
|
+
componentReports.flatMap(
|
|
2329
|
+
(report2) => report2.dependencies.map((dependency) => [dependency.id, dependency])
|
|
2330
|
+
)
|
|
2331
|
+
);
|
|
2332
|
+
const evidence = new Map(
|
|
2333
|
+
componentReports.flatMap(
|
|
2334
|
+
(report2) => report2.evidence.map((entry) => [entry.id, entry])
|
|
2335
|
+
)
|
|
2336
|
+
);
|
|
2337
|
+
const diagnostics = new Map(
|
|
2338
|
+
componentReports.flatMap(
|
|
2339
|
+
(report2) => report2.diagnostics.map((entry) => [entry.id, entry])
|
|
2340
|
+
)
|
|
2341
|
+
);
|
|
2342
|
+
const analyzedVersions = new Set(
|
|
2343
|
+
componentReports.flatMap((report2) => report2.baseline.analyzedSourceVersions)
|
|
2344
|
+
);
|
|
2345
|
+
const reportId = `page_${createHash2("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
|
|
2346
|
+
const complete = componentReports.every((report2) => report2.completeness.complete);
|
|
2347
|
+
const report = Object.freeze({
|
|
2348
|
+
schemaVersion: DATA_FLOW_SCHEMA_VERSION,
|
|
2349
|
+
reportId,
|
|
2350
|
+
baseline: Object.freeze({
|
|
2351
|
+
registryEpoch: options.session.id,
|
|
2352
|
+
analyzerVersion: componentReports[0]?.baseline.analyzerVersion ?? "unavailable",
|
|
2353
|
+
adapterSetHash: componentReports[0]?.baseline.adapterSetHash ?? "unavailable",
|
|
2354
|
+
analyzedSourceVersions: Object.freeze([...analyzedVersions].sort())
|
|
2355
|
+
}),
|
|
2356
|
+
capability: Object.freeze({
|
|
2357
|
+
enabled: true,
|
|
2358
|
+
staticAnalysis: complete ? "available" : "partial",
|
|
2359
|
+
runtimeObservation: "dispatch-only",
|
|
2360
|
+
responseShape: "consumed-fields-only",
|
|
2361
|
+
aiAssistance: "disabled",
|
|
2362
|
+
reasons: Object.freeze(
|
|
2363
|
+
componentReports.flatMap((report2) => report2.capability.reasons)
|
|
2364
|
+
)
|
|
2365
|
+
}),
|
|
2366
|
+
dependencies: Object.freeze([...dependencies.values()]),
|
|
2367
|
+
evidence: Object.freeze([...evidence.values()]),
|
|
2368
|
+
diagnostics: Object.freeze([...diagnostics.values()]),
|
|
2369
|
+
completeness: Object.freeze({
|
|
2370
|
+
complete,
|
|
2371
|
+
visitedModules: componentReports.reduce(
|
|
2372
|
+
(total, report2) => total + report2.completeness.visitedModules,
|
|
2373
|
+
0
|
|
2374
|
+
),
|
|
2375
|
+
visitedCallsites: componentReports.reduce(
|
|
2376
|
+
(total, report2) => total + report2.completeness.visitedCallsites,
|
|
2377
|
+
0
|
|
2378
|
+
),
|
|
2379
|
+
frontierCount: componentReports.reduce(
|
|
2380
|
+
(total, report2) => total + report2.completeness.frontierCount,
|
|
2381
|
+
0
|
|
2382
|
+
)
|
|
2383
|
+
})
|
|
2384
|
+
});
|
|
2385
|
+
return limitDataFlowReportToBytes(
|
|
2386
|
+
report,
|
|
2387
|
+
options.options.dataFlow.limits.reportMaxBytes
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2153
2391
|
// src/server/request-security.ts
|
|
2154
2392
|
import { timingSafeEqual } from "crypto";
|
|
2155
2393
|
import { isIP } from "net";
|
|
2156
|
-
import { ERROR_CODES as
|
|
2394
|
+
import { ERROR_CODES as ERROR_CODES8, SPOTPATCH_TOKEN_HEADER, SpotPatchError as SpotPatchError8 } from "@spotpatch/shared";
|
|
2157
2395
|
function getSingleHeader(request, name) {
|
|
2158
2396
|
const value = request.headers[name.toLowerCase()];
|
|
2159
2397
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -2200,32 +2438,32 @@ function parseOrigin(value) {
|
|
|
2200
2438
|
function assertRequestAuthorized(request, options) {
|
|
2201
2439
|
const actualToken = getSingleHeader(request, SPOTPATCH_TOKEN_HEADER);
|
|
2202
2440
|
if (!tokensMatch(actualToken, options.sessionToken)) {
|
|
2203
|
-
throw new
|
|
2441
|
+
throw new SpotPatchError8(ERROR_CODES8.INVALID_TOKEN);
|
|
2204
2442
|
}
|
|
2205
2443
|
const hostHeader = getSingleHeader(request, "host");
|
|
2206
2444
|
const originHeader = getSingleHeader(request, "origin");
|
|
2207
2445
|
const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
|
|
2208
2446
|
const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
|
|
2209
2447
|
if (host === void 0 || origin === void 0) {
|
|
2210
|
-
throw new
|
|
2448
|
+
throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
|
|
2211
2449
|
}
|
|
2212
2450
|
const hostIsLoopback = isLoopbackHostname(host.hostname);
|
|
2213
2451
|
const originIsLoopback = isLoopbackHostname(origin.hostname);
|
|
2214
2452
|
if (!options.allowLan) {
|
|
2215
2453
|
if (!hostIsLoopback || !originIsLoopback) {
|
|
2216
|
-
throw new
|
|
2454
|
+
throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
|
|
2217
2455
|
}
|
|
2218
2456
|
return;
|
|
2219
2457
|
}
|
|
2220
2458
|
if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
|
|
2221
|
-
throw new
|
|
2459
|
+
throw new SpotPatchError8(ERROR_CODES8.ORIGIN_NOT_ALLOWED);
|
|
2222
2460
|
}
|
|
2223
2461
|
}
|
|
2224
2462
|
|
|
2225
2463
|
// src/server/runtime-bootstrap.ts
|
|
2226
2464
|
import {
|
|
2227
|
-
ERROR_CODES as
|
|
2228
|
-
SpotPatchError as
|
|
2465
|
+
ERROR_CODES as ERROR_CODES9,
|
|
2466
|
+
SpotPatchError as SpotPatchError9,
|
|
2229
2467
|
runtimeBootstrapRequestSchema,
|
|
2230
2468
|
runtimeConfigSchema
|
|
2231
2469
|
} from "@spotpatch/shared";
|
|
@@ -2255,7 +2493,7 @@ function resolveRuntimeBootstrapOptions(options) {
|
|
|
2255
2493
|
function assertRuntimeBootstrapRequest(request, expectedOrigin) {
|
|
2256
2494
|
const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
2257
2495
|
if (request.method !== "POST" || contentType !== "application/json") {
|
|
2258
|
-
throw new
|
|
2496
|
+
throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
|
|
2259
2497
|
}
|
|
2260
2498
|
const host = getSingleHeader2(request, "host");
|
|
2261
2499
|
let hostIsLoopback = false;
|
|
@@ -2267,7 +2505,7 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
|
|
|
2267
2505
|
}
|
|
2268
2506
|
}
|
|
2269
2507
|
if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
|
|
2270
|
-
throw new
|
|
2508
|
+
throw new SpotPatchError9(ERROR_CODES9.ORIGIN_NOT_ALLOWED);
|
|
2271
2509
|
}
|
|
2272
2510
|
}
|
|
2273
2511
|
async function readRuntimeBootstrap(request, options) {
|
|
@@ -2276,81 +2514,87 @@ async function readRuntimeBootstrap(request, options) {
|
|
|
2276
2514
|
await readJsonRequestBody(request)
|
|
2277
2515
|
);
|
|
2278
2516
|
if (!parsedBody.success) {
|
|
2279
|
-
throw new
|
|
2517
|
+
throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
|
|
2280
2518
|
}
|
|
2281
2519
|
return options.runtimeConfig;
|
|
2282
2520
|
}
|
|
2283
2521
|
|
|
2284
2522
|
// src/server/middleware.ts
|
|
2285
2523
|
var STATUS_BY_ERROR = Object.freeze({
|
|
2286
|
-
[
|
|
2287
|
-
[
|
|
2288
|
-
[
|
|
2289
|
-
[
|
|
2290
|
-
[
|
|
2291
|
-
[
|
|
2292
|
-
[
|
|
2293
|
-
[
|
|
2294
|
-
[
|
|
2295
|
-
[
|
|
2296
|
-
[
|
|
2297
|
-
[
|
|
2298
|
-
[
|
|
2299
|
-
[
|
|
2300
|
-
[
|
|
2301
|
-
[
|
|
2302
|
-
[
|
|
2303
|
-
[
|
|
2304
|
-
[
|
|
2305
|
-
[
|
|
2306
|
-
[
|
|
2307
|
-
[
|
|
2308
|
-
[
|
|
2309
|
-
[
|
|
2310
|
-
[
|
|
2311
|
-
[
|
|
2312
|
-
[
|
|
2313
|
-
[
|
|
2314
|
-
[
|
|
2315
|
-
[
|
|
2316
|
-
[
|
|
2317
|
-
[
|
|
2318
|
-
[
|
|
2524
|
+
[ERROR_CODES10.INVALID_REQUEST]: 400,
|
|
2525
|
+
[ERROR_CODES10.INVALID_TOKEN]: 401,
|
|
2526
|
+
[ERROR_CODES10.ORIGIN_NOT_ALLOWED]: 403,
|
|
2527
|
+
[ERROR_CODES10.SOURCE_NOT_FOUND]: 404,
|
|
2528
|
+
[ERROR_CODES10.SOURCE_OUTSIDE_ROOT]: 403,
|
|
2529
|
+
[ERROR_CODES10.SOURCE_TOO_LARGE]: 413,
|
|
2530
|
+
[ERROR_CODES10.EDITOR_OPEN_FAILED]: 500,
|
|
2531
|
+
[ERROR_CODES10.DATA_FLOW_DISABLED]: 404,
|
|
2532
|
+
[ERROR_CODES10.DATA_FLOW_SOURCE_STALE]: 409,
|
|
2533
|
+
[ERROR_CODES10.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
|
|
2534
|
+
[ERROR_CODES10.AI_DISABLED]: 404,
|
|
2535
|
+
[ERROR_CODES10.PROVIDER_NOT_CONFIGURED]: 503,
|
|
2536
|
+
[ERROR_CODES10.PROVIDER_AUTH_FAILED]: 502,
|
|
2537
|
+
[ERROR_CODES10.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
|
|
2538
|
+
[ERROR_CODES10.MODEL_NOT_ALLOWED]: 400,
|
|
2539
|
+
[ERROR_CODES10.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
|
|
2540
|
+
[ERROR_CODES10.PROVIDER_RATE_LIMITED]: 429,
|
|
2541
|
+
[ERROR_CODES10.AGENT_BUSY]: 409,
|
|
2542
|
+
[ERROR_CODES10.AGENT_LIMIT_EXCEEDED]: 413,
|
|
2543
|
+
[ERROR_CODES10.AGENT_CANCELLED]: 409,
|
|
2544
|
+
[ERROR_CODES10.WORKTREE_DIRTY]: 409,
|
|
2545
|
+
[ERROR_CODES10.WORKTREE_NOT_REPOSITORY]: 409,
|
|
2546
|
+
[ERROR_CODES10.WORKTREE_OPERATION_IN_PROGRESS]: 409,
|
|
2547
|
+
[ERROR_CODES10.WORKTREE_CONFLICTED]: 409,
|
|
2548
|
+
[ERROR_CODES10.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
|
|
2549
|
+
[ERROR_CODES10.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
|
|
2550
|
+
[ERROR_CODES10.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
|
|
2551
|
+
[ERROR_CODES10.TOOL_DENIED]: 403,
|
|
2552
|
+
[ERROR_CODES10.TOOL_INPUT_INVALID]: 422,
|
|
2553
|
+
[ERROR_CODES10.TOOL_ARGUMENTS_INVALID]: 422,
|
|
2554
|
+
[ERROR_CODES10.TOOL_CALL_ID_CONFLICT]: 422,
|
|
2555
|
+
[ERROR_CODES10.TOOL_PATH_DENIED]: 403,
|
|
2556
|
+
[ERROR_CODES10.PATCH_REJECTED]: 422,
|
|
2557
|
+
[ERROR_CODES10.VALIDATION_FAILED]: 422,
|
|
2558
|
+
[ERROR_CODES10.APPLY_CONFLICT]: 409,
|
|
2559
|
+
[ERROR_CODES10.INTERNAL_ERROR]: 500
|
|
2319
2560
|
});
|
|
2320
2561
|
var PUBLIC_MESSAGES = Object.freeze({
|
|
2321
|
-
[
|
|
2322
|
-
[
|
|
2323
|
-
[
|
|
2324
|
-
[
|
|
2325
|
-
[
|
|
2326
|
-
[
|
|
2327
|
-
[
|
|
2328
|
-
[
|
|
2329
|
-
[
|
|
2330
|
-
[
|
|
2331
|
-
[
|
|
2332
|
-
[
|
|
2333
|
-
[
|
|
2334
|
-
[
|
|
2335
|
-
[
|
|
2336
|
-
[
|
|
2337
|
-
[
|
|
2338
|
-
[
|
|
2339
|
-
[
|
|
2340
|
-
[
|
|
2341
|
-
[
|
|
2342
|
-
[
|
|
2343
|
-
[
|
|
2344
|
-
[
|
|
2345
|
-
[
|
|
2346
|
-
[
|
|
2347
|
-
[
|
|
2348
|
-
[
|
|
2349
|
-
[
|
|
2350
|
-
[
|
|
2351
|
-
[
|
|
2352
|
-
[
|
|
2353
|
-
[
|
|
2562
|
+
[ERROR_CODES10.INVALID_REQUEST]: "The request is invalid.",
|
|
2563
|
+
[ERROR_CODES10.INVALID_TOKEN]: "The session token is invalid.",
|
|
2564
|
+
[ERROR_CODES10.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
|
|
2565
|
+
[ERROR_CODES10.SOURCE_NOT_FOUND]: "The source file is unavailable.",
|
|
2566
|
+
[ERROR_CODES10.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
|
|
2567
|
+
[ERROR_CODES10.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
|
|
2568
|
+
[ERROR_CODES10.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
|
|
2569
|
+
[ERROR_CODES10.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
|
|
2570
|
+
[ERROR_CODES10.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
|
|
2571
|
+
[ERROR_CODES10.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
|
|
2572
|
+
[ERROR_CODES10.AI_DISABLED]: "AI execution is not enabled.",
|
|
2573
|
+
[ERROR_CODES10.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
|
|
2574
|
+
[ERROR_CODES10.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
|
|
2575
|
+
[ERROR_CODES10.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
|
|
2576
|
+
[ERROR_CODES10.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
|
|
2577
|
+
[ERROR_CODES10.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
|
|
2578
|
+
[ERROR_CODES10.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
|
|
2579
|
+
[ERROR_CODES10.AGENT_BUSY]: "Another Agent job is already running.",
|
|
2580
|
+
[ERROR_CODES10.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
|
|
2581
|
+
[ERROR_CODES10.AGENT_CANCELLED]: "The Agent job was cancelled.",
|
|
2582
|
+
[ERROR_CODES10.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
|
|
2583
|
+
[ERROR_CODES10.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
|
|
2584
|
+
[ERROR_CODES10.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
|
|
2585
|
+
[ERROR_CODES10.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
|
|
2586
|
+
[ERROR_CODES10.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
|
|
2587
|
+
[ERROR_CODES10.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
|
|
2588
|
+
[ERROR_CODES10.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
|
|
2589
|
+
[ERROR_CODES10.TOOL_DENIED]: "The Agent tool request was denied.",
|
|
2590
|
+
[ERROR_CODES10.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
|
|
2591
|
+
[ERROR_CODES10.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
|
|
2592
|
+
[ERROR_CODES10.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
|
|
2593
|
+
[ERROR_CODES10.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
|
|
2594
|
+
[ERROR_CODES10.PATCH_REJECTED]: "The proposed patch was rejected.",
|
|
2595
|
+
[ERROR_CODES10.VALIDATION_FAILED]: "The proposed change failed validation.",
|
|
2596
|
+
[ERROR_CODES10.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
|
|
2597
|
+
[ERROR_CODES10.INTERNAL_ERROR]: "The request could not be completed."
|
|
2354
2598
|
});
|
|
2355
2599
|
function writeJson(response, status, payload) {
|
|
2356
2600
|
response.statusCode = status;
|
|
@@ -2359,11 +2603,11 @@ function writeJson(response, status, payload) {
|
|
|
2359
2603
|
response.end(JSON.stringify(payload));
|
|
2360
2604
|
}
|
|
2361
2605
|
function asSpotPatchError(error) {
|
|
2362
|
-
return error instanceof
|
|
2606
|
+
return error instanceof SpotPatchError10 ? error : new SpotPatchError10(ERROR_CODES10.INTERNAL_ERROR, void 0, { cause: error });
|
|
2363
2607
|
}
|
|
2364
2608
|
function writeError(response, error, logger) {
|
|
2365
2609
|
const normalized = asSpotPatchError(error);
|
|
2366
|
-
if (normalized.code ===
|
|
2610
|
+
if (normalized.code === ERROR_CODES10.INTERNAL_ERROR) {
|
|
2367
2611
|
logger?.warn("[spotpatch:server] Internal request failure.");
|
|
2368
2612
|
}
|
|
2369
2613
|
writeJson(response, STATUS_BY_ERROR[normalized.code], {
|
|
@@ -2386,7 +2630,7 @@ async function handleSourceContext(request, options) {
|
|
|
2386
2630
|
await readJsonRequestBody(request)
|
|
2387
2631
|
);
|
|
2388
2632
|
if (!parsed.success) {
|
|
2389
|
-
throw new
|
|
2633
|
+
throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
|
|
2390
2634
|
}
|
|
2391
2635
|
return readSourceContext({
|
|
2392
2636
|
request: parsed.data,
|
|
@@ -2399,7 +2643,7 @@ async function handleSourceContext(request, options) {
|
|
|
2399
2643
|
async function handleOpenEditor(request, options) {
|
|
2400
2644
|
const parsed = openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
|
|
2401
2645
|
if (!parsed.success) {
|
|
2402
|
-
throw new
|
|
2646
|
+
throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
|
|
2403
2647
|
}
|
|
2404
2648
|
const body = parsed.data;
|
|
2405
2649
|
const sourcePath = await resolveSourceFile({
|
|
@@ -2416,17 +2660,18 @@ async function handleOpenEditor(request, options) {
|
|
|
2416
2660
|
options.logger?.warn(
|
|
2417
2661
|
`[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
|
|
2418
2662
|
);
|
|
2419
|
-
throw new
|
|
2663
|
+
throw new SpotPatchError10(ERROR_CODES10.EDITOR_OPEN_FAILED, void 0, {
|
|
2420
2664
|
cause: error
|
|
2421
2665
|
});
|
|
2422
2666
|
}
|
|
2423
2667
|
}
|
|
2424
2668
|
function createSpotPatchMiddleware(options) {
|
|
2425
2669
|
const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
|
|
2670
|
+
const dataFlowAnalyzer = createDataFlowAnalyzer(options);
|
|
2426
2671
|
return (request, response, next) => {
|
|
2427
2672
|
const path8 = requestPath(request);
|
|
2428
2673
|
const agentRoute = matchAgentRequestPath(path8);
|
|
2429
|
-
if (path8 !== SPOTPATCH_ENDPOINTS2.sourceContext && path8 !== SPOTPATCH_ENDPOINTS2.openEditor && agentRoute === void 0 && !path8.startsWith(`${SPOTPATCH_API_BASE}/`)) {
|
|
2674
|
+
if (path8 !== SPOTPATCH_ENDPOINTS2.sourceContext && path8 !== SPOTPATCH_ENDPOINTS2.openEditor && path8 !== SPOTPATCH_ENDPOINTS2.dataFlowComponentReport && path8 !== SPOTPATCH_ENDPOINTS2.dataFlowPageReport && agentRoute === void 0 && !path8.startsWith(`${SPOTPATCH_API_BASE}/`)) {
|
|
2430
2675
|
next();
|
|
2431
2676
|
return;
|
|
2432
2677
|
}
|
|
@@ -2445,7 +2690,7 @@ function createSpotPatchMiddleware(options) {
|
|
|
2445
2690
|
});
|
|
2446
2691
|
if (path8 === SPOTPATCH_ENDPOINTS2.sourceContext) {
|
|
2447
2692
|
if (request.method !== "POST") {
|
|
2448
|
-
throw new
|
|
2693
|
+
throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
|
|
2449
2694
|
}
|
|
2450
2695
|
const data = await handleSourceContext(request, options);
|
|
2451
2696
|
writeJson(response, 200, { ok: true, data });
|
|
@@ -2453,14 +2698,34 @@ function createSpotPatchMiddleware(options) {
|
|
|
2453
2698
|
}
|
|
2454
2699
|
if (path8 === SPOTPATCH_ENDPOINTS2.openEditor) {
|
|
2455
2700
|
if (request.method !== "POST") {
|
|
2456
|
-
throw new
|
|
2701
|
+
throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
|
|
2457
2702
|
}
|
|
2458
2703
|
const data = await handleOpenEditor(request, options);
|
|
2459
2704
|
writeJson(response, 200, { ok: true, data });
|
|
2460
2705
|
return;
|
|
2461
2706
|
}
|
|
2707
|
+
if (path8 === SPOTPATCH_ENDPOINTS2.dataFlowComponentReport) {
|
|
2708
|
+
if (request.method !== "POST") {
|
|
2709
|
+
throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
|
|
2710
|
+
}
|
|
2711
|
+
const data = await handleComponentDataFlowReport(
|
|
2712
|
+
request,
|
|
2713
|
+
dataFlowAnalyzer,
|
|
2714
|
+
options
|
|
2715
|
+
);
|
|
2716
|
+
writeJson(response, 200, { ok: true, data });
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
if (path8 === SPOTPATCH_ENDPOINTS2.dataFlowPageReport) {
|
|
2720
|
+
if (request.method !== "POST") {
|
|
2721
|
+
throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
|
|
2722
|
+
}
|
|
2723
|
+
const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
|
|
2724
|
+
writeJson(response, 200, { ok: true, data });
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2462
2727
|
if (agentRoute === void 0) {
|
|
2463
|
-
throw new
|
|
2728
|
+
throw new SpotPatchError10(ERROR_CODES10.INVALID_REQUEST);
|
|
2464
2729
|
}
|
|
2465
2730
|
await handleAgentRequest(
|
|
2466
2731
|
request,
|
|
@@ -2611,6 +2876,7 @@ var OPTION_KEYS = Object.freeze([
|
|
|
2611
2876
|
"allowLan",
|
|
2612
2877
|
"budget",
|
|
2613
2878
|
"debug",
|
|
2879
|
+
"dataFlow",
|
|
2614
2880
|
"editor",
|
|
2615
2881
|
"enabled",
|
|
2616
2882
|
"exclude",
|
|
@@ -2698,6 +2964,9 @@ function serializeResolvedSpotPatchOptions(options) {
|
|
|
2698
2964
|
allowLan: options.allowLan,
|
|
2699
2965
|
budget: options.budget,
|
|
2700
2966
|
debug: options.debug,
|
|
2967
|
+
dataFlow: options.dataFlow.enabled ? Object.freeze({
|
|
2968
|
+
runtime: options.dataFlow.runtime
|
|
2969
|
+
}) : false,
|
|
2701
2970
|
editor: options.editor,
|
|
2702
2971
|
enabled: options.enabled,
|
|
2703
2972
|
exclude: Object.freeze(options.exclude.map(serializeFilter)),
|
|
@@ -2740,6 +3009,15 @@ function parseBudget(value) {
|
|
|
2740
3009
|
);
|
|
2741
3010
|
return Object.freeze(budget);
|
|
2742
3011
|
}
|
|
3012
|
+
function parseDataFlow(value) {
|
|
3013
|
+
if (value === false) return false;
|
|
3014
|
+
if (!isRecord2(value) || !hasExactKeys(value, ["runtime"]) || value.runtime !== "dispatch") {
|
|
3015
|
+
throw new TypeError("The SpotPatch data-flow transport is invalid.");
|
|
3016
|
+
}
|
|
3017
|
+
return Object.freeze({
|
|
3018
|
+
runtime: value.runtime
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
2743
3021
|
function parseSerializedSpotPatchOptions(value) {
|
|
2744
3022
|
if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
|
|
2745
3023
|
throw new TypeError("The SpotPatch options transport is invalid.");
|
|
@@ -2753,6 +3031,7 @@ function parseSerializedSpotPatchOptions(value) {
|
|
|
2753
3031
|
allowLan: value.allowLan,
|
|
2754
3032
|
budget: parseBudget(value.budget),
|
|
2755
3033
|
debug: value.debug,
|
|
3034
|
+
dataFlow: parseDataFlow(value.dataFlow),
|
|
2756
3035
|
editor: value.editor,
|
|
2757
3036
|
enabled: value.enabled,
|
|
2758
3037
|
exclude: parseFilterList(value.exclude),
|
|
@@ -2775,6 +3054,7 @@ export {
|
|
|
2775
3054
|
createAgentJobManager,
|
|
2776
3055
|
createIntegrationFileChange,
|
|
2777
3056
|
createRuntimeAiConfig,
|
|
3057
|
+
createRuntimeDataFlowConfig,
|
|
2778
3058
|
createSession,
|
|
2779
3059
|
createSourceRegistrationService,
|
|
2780
3060
|
createSourceRegistry,
|