@spotpatch/dev-server 0.6.0 → 0.8.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 +478 -251
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -4
- package/dist/index.d.ts +32 -4
- package/dist/index.js +443 -198
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -51,8 +51,10 @@ __export(index_exports, {
|
|
|
51
51
|
readRuntimeBootstrap: () => readRuntimeBootstrap,
|
|
52
52
|
resolveCredentialEnvironment: () => resolveCredentialEnvironment,
|
|
53
53
|
resolveEnvironmentAiConfiguration: () => resolveEnvironmentAiConfiguration,
|
|
54
|
+
resolveManagedExecutionValidation: () => resolveManagedExecutionValidation,
|
|
54
55
|
resolveOptions: () => resolveOptions,
|
|
55
56
|
resolveProjectOptions: () => resolveProjectOptions,
|
|
57
|
+
resolveProjectValidationChecks: () => resolveProjectValidationChecks,
|
|
56
58
|
resolveRuntimeBootstrapOptions: () => resolveRuntimeBootstrapOptions,
|
|
57
59
|
serializeResolvedSpotPatchOptions: () => serializeResolvedSpotPatchOptions
|
|
58
60
|
});
|
|
@@ -2250,6 +2252,7 @@ var import_promises3 = require("fs/promises");
|
|
|
2250
2252
|
var import_node_module = require("module");
|
|
2251
2253
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
2252
2254
|
var import_node_util = require("util");
|
|
2255
|
+
var import_shared9 = require("@spotpatch/shared");
|
|
2253
2256
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
2254
2257
|
var TYPESCRIPT_CHECK_ID = "spotpatch-typecheck";
|
|
2255
2258
|
var TYPESCRIPT_CHECK_LABEL = "TypeScript";
|
|
@@ -2317,6 +2320,15 @@ async function resolveTypeScriptCli(appRoot) {
|
|
|
2317
2320
|
function portableRelativePath(from, to) {
|
|
2318
2321
|
return import_node_path3.default.relative(from, to).split(import_node_path3.default.sep).join("/");
|
|
2319
2322
|
}
|
|
2323
|
+
function hasRequiredCheck(checks) {
|
|
2324
|
+
return Object.values(checks).some((check) => check.required);
|
|
2325
|
+
}
|
|
2326
|
+
function availableCheckId(checks, preferred) {
|
|
2327
|
+
if (checks[preferred] === void 0) return preferred;
|
|
2328
|
+
let suffix = 2;
|
|
2329
|
+
while (checks[`${preferred}-${String(suffix)}`] !== void 0) suffix += 1;
|
|
2330
|
+
return `${preferred}-${String(suffix)}`;
|
|
2331
|
+
}
|
|
2320
2332
|
async function discoverProjectValidationCheck(options) {
|
|
2321
2333
|
const appRoot = await (0, import_promises3.realpath)(options.appRoot);
|
|
2322
2334
|
const tsconfigPath = import_node_path3.default.join(appRoot, "tsconfig.json");
|
|
@@ -2345,6 +2357,8 @@ async function discoverProjectValidationCheck(options) {
|
|
|
2345
2357
|
"--noEmit",
|
|
2346
2358
|
"--pretty",
|
|
2347
2359
|
"false",
|
|
2360
|
+
"--incremental",
|
|
2361
|
+
"false",
|
|
2348
2362
|
"--project",
|
|
2349
2363
|
projectPath
|
|
2350
2364
|
]),
|
|
@@ -2352,21 +2366,30 @@ async function discoverProjectValidationCheck(options) {
|
|
|
2352
2366
|
timeoutMs: options.timeoutMs
|
|
2353
2367
|
});
|
|
2354
2368
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2369
|
+
async function resolveProjectValidationChecks(options) {
|
|
2370
|
+
if (hasRequiredCheck(options.checks)) return options.checks;
|
|
2371
|
+
const discovered = await discoverProjectValidationCheck(options);
|
|
2372
|
+
if (discovered === void 0) return options.checks;
|
|
2373
|
+
const id = availableCheckId(options.checks, discovered.id);
|
|
2374
|
+
return Object.freeze({
|
|
2375
|
+
...options.checks,
|
|
2376
|
+
[id]: Object.freeze({ ...discovered, id })
|
|
2377
|
+
});
|
|
2359
2378
|
}
|
|
2360
|
-
function
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2379
|
+
async function resolveManagedExecutionValidation(options) {
|
|
2380
|
+
const checks = options.ai === false ? Object.freeze({}) : options.ai.execution.checks;
|
|
2381
|
+
const limits = options.ai === false ? import_shared9.DEFAULT_AGENT_LIMITS : options.ai.execution.limits;
|
|
2382
|
+
return Object.freeze({
|
|
2383
|
+
checks: await resolveProjectValidationChecks({
|
|
2384
|
+
appRoot: options.appRoot,
|
|
2385
|
+
checks,
|
|
2386
|
+
timeoutMs: limits.checkTimeoutMs
|
|
2387
|
+
}),
|
|
2388
|
+
limits
|
|
2389
|
+
});
|
|
2369
2390
|
}
|
|
2391
|
+
|
|
2392
|
+
// src/project-options.ts
|
|
2370
2393
|
async function resolveProjectOptions(input) {
|
|
2371
2394
|
const userOptions = input.options ?? {};
|
|
2372
2395
|
const resolved = resolveOptions(userOptions, input.environmentAi);
|
|
@@ -2378,22 +2401,15 @@ async function resolveProjectOptions(input) {
|
|
|
2378
2401
|
"SpotPatch trustedFastMode cannot be combined with applyMode auto."
|
|
2379
2402
|
);
|
|
2380
2403
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
);
|
|
2391
|
-
}
|
|
2392
|
-
const id = availableCheckId(checks, discovered.id);
|
|
2393
|
-
checks = Object.freeze({
|
|
2394
|
-
...checks,
|
|
2395
|
-
[id]: Object.freeze({ ...discovered, id })
|
|
2396
|
-
});
|
|
2404
|
+
const checks = await resolveProjectValidationChecks({
|
|
2405
|
+
appRoot: input.appRoot,
|
|
2406
|
+
checks: resolved.ai.execution.checks,
|
|
2407
|
+
timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
|
|
2408
|
+
});
|
|
2409
|
+
if (!Object.values(checks).some((check) => check.required)) {
|
|
2410
|
+
throw new RangeError(
|
|
2411
|
+
"SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
|
|
2412
|
+
);
|
|
2397
2413
|
}
|
|
2398
2414
|
const ai = Object.freeze({
|
|
2399
2415
|
...resolved.ai,
|
|
@@ -2456,6 +2472,7 @@ function createSourceRegistry(options = {}) {
|
|
|
2456
2472
|
);
|
|
2457
2473
|
}
|
|
2458
2474
|
componentIdsByPath.set(normalizedPath, currentIds);
|
|
2475
|
+
return fileId;
|
|
2459
2476
|
},
|
|
2460
2477
|
resolve(fileId) {
|
|
2461
2478
|
return idToPath.get(fileId);
|
|
@@ -2473,20 +2490,20 @@ function createSourceRegistry(options = {}) {
|
|
|
2473
2490
|
}
|
|
2474
2491
|
|
|
2475
2492
|
// src/server/middleware.ts
|
|
2476
|
-
var
|
|
2493
|
+
var import_shared20 = require("@spotpatch/shared");
|
|
2477
2494
|
|
|
2478
2495
|
// src/external-handoff/browser-http.ts
|
|
2479
|
-
var
|
|
2496
|
+
var import_shared13 = require("@spotpatch/shared");
|
|
2480
2497
|
|
|
2481
2498
|
// src/server/annotation-authorizer.ts
|
|
2482
2499
|
var import_promises6 = require("fs/promises");
|
|
2483
2500
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
2484
|
-
var
|
|
2501
|
+
var import_shared12 = require("@spotpatch/shared");
|
|
2485
2502
|
|
|
2486
2503
|
// src/server/source-context.ts
|
|
2487
2504
|
var import_promises5 = require("fs/promises");
|
|
2488
2505
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
2489
|
-
var
|
|
2506
|
+
var import_shared11 = require("@spotpatch/shared");
|
|
2490
2507
|
|
|
2491
2508
|
// src/server/extract-code-context.ts
|
|
2492
2509
|
var import_oxc_parser = require("oxc-parser");
|
|
@@ -2717,7 +2734,7 @@ function extractCodeContext(options) {
|
|
|
2717
2734
|
// src/server/source-file.ts
|
|
2718
2735
|
var import_promises4 = require("fs/promises");
|
|
2719
2736
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
2720
|
-
var
|
|
2737
|
+
var import_shared10 = require("@spotpatch/shared");
|
|
2721
2738
|
var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
|
|
2722
2739
|
function isMissingFileError(error) {
|
|
2723
2740
|
return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
@@ -2732,7 +2749,7 @@ async function assertInsideRoot(root, candidate) {
|
|
|
2732
2749
|
]);
|
|
2733
2750
|
} catch (error) {
|
|
2734
2751
|
if (isMissingFileError(error)) {
|
|
2735
|
-
throw new
|
|
2752
|
+
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
|
|
2736
2753
|
cause: error
|
|
2737
2754
|
});
|
|
2738
2755
|
}
|
|
@@ -2741,35 +2758,35 @@ async function assertInsideRoot(root, candidate) {
|
|
|
2741
2758
|
const relative = import_node_path5.default.relative(realRoot, realCandidate);
|
|
2742
2759
|
const outside = relative.startsWith(`..${import_node_path5.default.sep}`) || relative === ".." || import_node_path5.default.isAbsolute(relative);
|
|
2743
2760
|
if (outside) {
|
|
2744
|
-
throw new
|
|
2761
|
+
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
|
|
2745
2762
|
}
|
|
2746
2763
|
return realCandidate;
|
|
2747
2764
|
}
|
|
2748
2765
|
async function resolveSourceFile(options) {
|
|
2749
2766
|
const registeredPath = options.registry.resolve(options.fileId);
|
|
2750
2767
|
if (registeredPath === void 0) {
|
|
2751
|
-
throw new
|
|
2768
|
+
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
|
|
2752
2769
|
}
|
|
2753
2770
|
const sourcePath = await assertInsideRoot(options.root, registeredPath);
|
|
2754
2771
|
if (!ALLOWED_EXTENSIONS.has(import_node_path5.default.extname(sourcePath).toLowerCase())) {
|
|
2755
|
-
throw new
|
|
2772
|
+
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
|
|
2756
2773
|
}
|
|
2757
2774
|
let sourceStat;
|
|
2758
2775
|
try {
|
|
2759
2776
|
sourceStat = await (0, import_promises4.stat)(sourcePath);
|
|
2760
2777
|
} catch (error) {
|
|
2761
2778
|
if (isMissingFileError(error)) {
|
|
2762
|
-
throw new
|
|
2779
|
+
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
|
|
2763
2780
|
cause: error
|
|
2764
2781
|
});
|
|
2765
2782
|
}
|
|
2766
2783
|
throw error;
|
|
2767
2784
|
}
|
|
2768
2785
|
if (!sourceStat.isFile()) {
|
|
2769
|
-
throw new
|
|
2786
|
+
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
|
|
2770
2787
|
}
|
|
2771
2788
|
if (sourceStat.size > MAX_SOURCE_FILE_BYTES) {
|
|
2772
|
-
throw new
|
|
2789
|
+
throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_TOO_LARGE);
|
|
2773
2790
|
}
|
|
2774
2791
|
return sourcePath;
|
|
2775
2792
|
}
|
|
@@ -2789,7 +2806,7 @@ async function readSourceContext(options) {
|
|
|
2789
2806
|
source = await (0, import_promises5.readFile)(sourcePath, "utf8");
|
|
2790
2807
|
} catch (error) {
|
|
2791
2808
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
|
|
2792
|
-
throw new
|
|
2809
|
+
throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
|
|
2793
2810
|
cause: error
|
|
2794
2811
|
});
|
|
2795
2812
|
}
|
|
@@ -2797,7 +2814,7 @@ async function readSourceContext(options) {
|
|
|
2797
2814
|
}
|
|
2798
2815
|
const lines = source.split(/\r?\n/);
|
|
2799
2816
|
if (options.request.line > lines.length) {
|
|
2800
|
-
throw new
|
|
2817
|
+
throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
|
|
2801
2818
|
}
|
|
2802
2819
|
const extension = import_node_path6.default.extname(sourcePath).toLowerCase();
|
|
2803
2820
|
return extractCodeContext({
|
|
@@ -2815,9 +2832,9 @@ async function readSourceContext(options) {
|
|
|
2815
2832
|
// src/server/annotation-authorizer.ts
|
|
2816
2833
|
function sanitizePageContext(page) {
|
|
2817
2834
|
return Object.freeze({
|
|
2818
|
-
url: (0,
|
|
2819
|
-
pathname: (0,
|
|
2820
|
-
title: (0,
|
|
2835
|
+
url: (0, import_shared12.sanitizeUrl)(page.url, page.url),
|
|
2836
|
+
pathname: (0, import_shared12.redactSensitiveText)(page.pathname),
|
|
2837
|
+
title: (0, import_shared12.redactSensitiveText)(page.title),
|
|
2821
2838
|
viewportWidth: page.viewportWidth,
|
|
2822
2839
|
viewportHeight: page.viewportHeight,
|
|
2823
2840
|
devicePixelRatio: page.devicePixelRatio
|
|
@@ -2836,18 +2853,18 @@ function compactSourceRef(source) {
|
|
|
2836
2853
|
async function authorizeSourceRef(source, registry, root) {
|
|
2837
2854
|
const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
|
|
2838
2855
|
if (markerOrigin && (source.fileId === void 0 || source.line === void 0 || source.column === void 0)) {
|
|
2839
|
-
throw new
|
|
2856
|
+
throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
|
|
2840
2857
|
}
|
|
2841
2858
|
if (source.fileId === void 0) {
|
|
2842
2859
|
if (source.origin === "none" && source.relativePath !== void 0) {
|
|
2843
|
-
throw new
|
|
2860
|
+
throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
|
|
2844
2861
|
}
|
|
2845
2862
|
return compactSourceRef(source);
|
|
2846
2863
|
}
|
|
2847
2864
|
const sourcePath = await resolveSourceFile({ fileId: source.fileId, registry, root });
|
|
2848
2865
|
const relativePath = import_node_path7.default.relative(await (0, import_promises6.realpath)(root), sourcePath).split(import_node_path7.default.sep).join("/");
|
|
2849
2866
|
if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
|
|
2850
|
-
throw new
|
|
2867
|
+
throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
|
|
2851
2868
|
}
|
|
2852
2869
|
return Object.freeze({ ...compactSourceRef(source), relativePath });
|
|
2853
2870
|
}
|
|
@@ -2883,7 +2900,7 @@ async function authorizeTarget(target, input) {
|
|
|
2883
2900
|
maxLines: input.options.budget.maxCodeLines
|
|
2884
2901
|
});
|
|
2885
2902
|
if (marker === void 0 && target.code !== void 0) {
|
|
2886
|
-
throw new
|
|
2903
|
+
throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
|
|
2887
2904
|
}
|
|
2888
2905
|
const code = marker === void 0 ? void 0 : await readSourceContext({
|
|
2889
2906
|
request: marker,
|
|
@@ -2893,7 +2910,7 @@ async function authorizeTarget(target, input) {
|
|
|
2893
2910
|
maxLines: input.options.budget.maxCodeLines
|
|
2894
2911
|
});
|
|
2895
2912
|
if (target.code !== void 0 && target.code.relativePath !== code?.relativePath) {
|
|
2896
|
-
throw new
|
|
2913
|
+
throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
|
|
2897
2914
|
}
|
|
2898
2915
|
return Object.freeze({
|
|
2899
2916
|
instruction: target.instruction,
|
|
@@ -2928,11 +2945,11 @@ async function authorizeTarget(target, input) {
|
|
|
2928
2945
|
async function authorizeAnnotation(input) {
|
|
2929
2946
|
const requestedTargets = input.annotation.targets;
|
|
2930
2947
|
if (requestedTargets.length > input.options.maxTargets) {
|
|
2931
|
-
throw new
|
|
2948
|
+
throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
|
|
2932
2949
|
}
|
|
2933
2950
|
const identities = requestedTargets.map(targetIdentity);
|
|
2934
2951
|
if (new Set(identities).size !== identities.length) {
|
|
2935
|
-
throw new
|
|
2952
|
+
throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
|
|
2936
2953
|
}
|
|
2937
2954
|
const targets = Object.freeze(
|
|
2938
2955
|
await Promise.all(requestedTargets.map((target) => authorizeTarget(target, input)))
|
|
@@ -2949,29 +2966,29 @@ async function authorizeAnnotation(input) {
|
|
|
2949
2966
|
|
|
2950
2967
|
// src/external-handoff/browser-http.ts
|
|
2951
2968
|
function matchExternalHandoffBrowserPath(path9) {
|
|
2952
|
-
if (path9 ===
|
|
2953
|
-
if (path9 ===
|
|
2954
|
-
if (path9 ===
|
|
2955
|
-
if (path9 ===
|
|
2969
|
+
if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffCapability) return "capability";
|
|
2970
|
+
if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffPublish) return "publish";
|
|
2971
|
+
if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffStatus) return "status";
|
|
2972
|
+
if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery) {
|
|
2956
2973
|
return "resolve-delivery";
|
|
2957
2974
|
}
|
|
2958
2975
|
return void 0;
|
|
2959
2976
|
}
|
|
2960
2977
|
function requireService(options) {
|
|
2961
2978
|
if (!options.options.externalAgent.enabled || options.service === void 0) {
|
|
2962
|
-
throw new
|
|
2979
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
|
|
2963
2980
|
}
|
|
2964
2981
|
return options.service;
|
|
2965
2982
|
}
|
|
2966
2983
|
function remapAuthorizationError(error) {
|
|
2967
|
-
if (error instanceof
|
|
2968
|
-
if (error.code ===
|
|
2969
|
-
throw new
|
|
2984
|
+
if (error instanceof import_shared13.SpotPatchError) {
|
|
2985
|
+
if (error.code === import_shared13.ERROR_CODES.SOURCE_NOT_FOUND || error.code === import_shared13.ERROR_CODES.SOURCE_OUTSIDE_ROOT || error.code === import_shared13.ERROR_CODES.SOURCE_TOO_LARGE) {
|
|
2986
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_SOURCE_STALE, void 0, {
|
|
2970
2987
|
cause: error
|
|
2971
2988
|
});
|
|
2972
2989
|
}
|
|
2973
|
-
if (error.code ===
|
|
2974
|
-
throw new
|
|
2990
|
+
if (error.code === import_shared13.ERROR_CODES.INVALID_REQUEST) {
|
|
2991
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_VALIDATION_FAILED, void 0, {
|
|
2975
2992
|
cause: error
|
|
2976
2993
|
});
|
|
2977
2994
|
}
|
|
@@ -2980,38 +2997,38 @@ function remapAuthorizationError(error) {
|
|
|
2980
2997
|
}
|
|
2981
2998
|
async function handleExternalHandoffBrowserRequest(request, response, route, options, writeSuccess) {
|
|
2982
2999
|
if (request.method !== "POST") {
|
|
2983
|
-
throw new
|
|
3000
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
|
|
2984
3001
|
}
|
|
2985
3002
|
const service = requireService(options);
|
|
2986
3003
|
if (route === "capability") {
|
|
2987
|
-
const parsed2 =
|
|
3004
|
+
const parsed2 = import_shared13.externalHandoffCapabilityRequestSchema.safeParse(
|
|
2988
3005
|
await readJsonRequestBody(request)
|
|
2989
3006
|
);
|
|
2990
|
-
if (!parsed2.success) throw new
|
|
3007
|
+
if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
|
|
2991
3008
|
writeSuccess(response, 200, service.capability());
|
|
2992
3009
|
return;
|
|
2993
3010
|
}
|
|
2994
3011
|
if (route === "status") {
|
|
2995
|
-
const parsed2 =
|
|
3012
|
+
const parsed2 = import_shared13.externalHandoffStatusRequestSchema.safeParse(
|
|
2996
3013
|
await readJsonRequestBody(request)
|
|
2997
3014
|
);
|
|
2998
|
-
if (!parsed2.success) throw new
|
|
3015
|
+
if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
|
|
2999
3016
|
writeSuccess(response, 200, service.status(parsed2.data.cursor));
|
|
3000
3017
|
return;
|
|
3001
3018
|
}
|
|
3002
3019
|
if (route === "resolve-delivery") {
|
|
3003
|
-
const parsed2 =
|
|
3020
|
+
const parsed2 = import_shared13.externalHandoffResolveDeliveryRequestSchema.safeParse(
|
|
3004
3021
|
await readJsonRequestBody(request)
|
|
3005
3022
|
);
|
|
3006
|
-
if (!parsed2.success) throw new
|
|
3023
|
+
if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
|
|
3007
3024
|
writeSuccess(response, 200, service.resolveDelivery(parsed2.data.cursor));
|
|
3008
3025
|
return;
|
|
3009
3026
|
}
|
|
3010
|
-
const parsed =
|
|
3011
|
-
await readJsonRequestBody(request,
|
|
3027
|
+
const parsed = import_shared13.externalHandoffPublishRequestSchema.safeParse(
|
|
3028
|
+
await readJsonRequestBody(request, import_shared13.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
|
|
3012
3029
|
);
|
|
3013
3030
|
if (!parsed.success) {
|
|
3014
|
-
throw new
|
|
3031
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
|
|
3015
3032
|
}
|
|
3016
3033
|
const result = await service.publish(parsed.data, async (annotation) => {
|
|
3017
3034
|
try {
|
|
@@ -3028,11 +3045,157 @@ async function handleExternalHandoffBrowserRequest(request, response, route, opt
|
|
|
3028
3045
|
writeSuccess(response, result.replayed ? 200 : 201, result);
|
|
3029
3046
|
}
|
|
3030
3047
|
|
|
3031
|
-
// src/
|
|
3048
|
+
// src/external-agent/browser-http.ts
|
|
3032
3049
|
var import_shared14 = require("@spotpatch/shared");
|
|
3050
|
+
function matchExternalAgentBrowserPath(path9) {
|
|
3051
|
+
if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlStatus) return "status";
|
|
3052
|
+
if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlConnect) return "connect";
|
|
3053
|
+
if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlDisconnect) {
|
|
3054
|
+
return "disconnect";
|
|
3055
|
+
}
|
|
3056
|
+
if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlCancel) return "cancel";
|
|
3057
|
+
if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentEvents) return "events";
|
|
3058
|
+
if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentResult) return "result";
|
|
3059
|
+
return void 0;
|
|
3060
|
+
}
|
|
3061
|
+
function writeEvent(response, event) {
|
|
3062
|
+
response.write(`${JSON.stringify(import_shared14.externalAgentEventSchema.parse(event))}
|
|
3063
|
+
`);
|
|
3064
|
+
}
|
|
3065
|
+
function createExternalAgentBrowserController(port) {
|
|
3066
|
+
const streams = /* @__PURE__ */ new Set();
|
|
3067
|
+
let disposed = false;
|
|
3068
|
+
const handleEvents = async (request, response) => {
|
|
3069
|
+
const parsed = import_shared14.externalAgentEventsRequestSchema.safeParse(
|
|
3070
|
+
await readJsonRequestBody(request)
|
|
3071
|
+
);
|
|
3072
|
+
if (!parsed.success) {
|
|
3073
|
+
throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
|
|
3074
|
+
}
|
|
3075
|
+
if (disposed || streams.size >= import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
|
|
3076
|
+
throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.BRIDGE_BUSY);
|
|
3077
|
+
}
|
|
3078
|
+
response.statusCode = 200;
|
|
3079
|
+
response.setHeader("Cache-Control", "no-store");
|
|
3080
|
+
response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
|
|
3081
|
+
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
3082
|
+
streams.add(response);
|
|
3083
|
+
const initial = import_shared14.externalAgentControlStatusSchema.parse(port.getStatus());
|
|
3084
|
+
writeEvent(response, { type: "status", data: initial });
|
|
3085
|
+
const unsubscribe = port.subscribe((status) => {
|
|
3086
|
+
if (!response.destroyed && status.sequence > (parsed.data.afterSequence ?? -1)) {
|
|
3087
|
+
writeEvent(response, { type: "status", data: status });
|
|
3088
|
+
}
|
|
3089
|
+
});
|
|
3090
|
+
const heartbeat = setInterval(() => {
|
|
3091
|
+
if (!response.destroyed) {
|
|
3092
|
+
const status = port.getStatus();
|
|
3093
|
+
writeEvent(response, {
|
|
3094
|
+
type: "heartbeat",
|
|
3095
|
+
sequence: status.sequence,
|
|
3096
|
+
emittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
}, import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
|
|
3100
|
+
heartbeat.unref();
|
|
3101
|
+
await new Promise((resolve) => {
|
|
3102
|
+
const close = () => {
|
|
3103
|
+
response.off("close", close);
|
|
3104
|
+
clearInterval(heartbeat);
|
|
3105
|
+
unsubscribe();
|
|
3106
|
+
streams.delete(response);
|
|
3107
|
+
resolve();
|
|
3108
|
+
};
|
|
3109
|
+
response.once("close", close);
|
|
3110
|
+
});
|
|
3111
|
+
};
|
|
3112
|
+
return Object.freeze({
|
|
3113
|
+
async handle(request, response, route, writeSuccess) {
|
|
3114
|
+
if (request.method !== "POST" || disposed) {
|
|
3115
|
+
throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
|
|
3116
|
+
}
|
|
3117
|
+
if (route === "events") {
|
|
3118
|
+
await handleEvents(request, response);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
if (route === "status") {
|
|
3122
|
+
const parsed2 = import_shared14.externalAgentControlStatusRequestSchema.safeParse(
|
|
3123
|
+
await readJsonRequestBody(request)
|
|
3124
|
+
);
|
|
3125
|
+
if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
|
|
3126
|
+
writeSuccess(
|
|
3127
|
+
response,
|
|
3128
|
+
200,
|
|
3129
|
+
import_shared14.externalAgentControlStatusSchema.parse(port.getStatus())
|
|
3130
|
+
);
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
if (route === "connect") {
|
|
3134
|
+
const parsed2 = import_shared14.externalAgentControlConnectRequestSchema.safeParse(
|
|
3135
|
+
await readJsonRequestBody(request)
|
|
3136
|
+
);
|
|
3137
|
+
if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
|
|
3138
|
+
const controller = new AbortController();
|
|
3139
|
+
response.once("close", () => {
|
|
3140
|
+
if (!response.writableEnded) controller.abort("browser-disconnected");
|
|
3141
|
+
});
|
|
3142
|
+
writeSuccess(
|
|
3143
|
+
response,
|
|
3144
|
+
200,
|
|
3145
|
+
import_shared14.externalAgentControlStatusSchema.parse(
|
|
3146
|
+
await port.connect(parsed2.data, controller.signal)
|
|
3147
|
+
)
|
|
3148
|
+
);
|
|
3149
|
+
return;
|
|
3150
|
+
}
|
|
3151
|
+
if (route === "disconnect") {
|
|
3152
|
+
const parsed2 = import_shared14.externalAgentControlDisconnectRequestSchema.safeParse(
|
|
3153
|
+
await readJsonRequestBody(request)
|
|
3154
|
+
);
|
|
3155
|
+
if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
|
|
3156
|
+
writeSuccess(
|
|
3157
|
+
response,
|
|
3158
|
+
200,
|
|
3159
|
+
import_shared14.externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
|
|
3160
|
+
);
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
if (route === "cancel") {
|
|
3164
|
+
const parsed2 = import_shared14.externalAgentControlCancelRequestSchema.safeParse(
|
|
3165
|
+
await readJsonRequestBody(request)
|
|
3166
|
+
);
|
|
3167
|
+
if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
|
|
3168
|
+
writeSuccess(
|
|
3169
|
+
response,
|
|
3170
|
+
200,
|
|
3171
|
+
import_shared14.externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
|
|
3172
|
+
);
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
const parsed = import_shared14.externalAgentResultRequestSchema.safeParse(
|
|
3176
|
+
await readJsonRequestBody(request)
|
|
3177
|
+
);
|
|
3178
|
+
if (!parsed.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
|
|
3179
|
+
const result = port.getResult(parsed.data.revision);
|
|
3180
|
+
if (result === void 0) {
|
|
3181
|
+
throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.HANDOFF_NOT_FOUND);
|
|
3182
|
+
}
|
|
3183
|
+
writeSuccess(response, 200, import_shared14.externalAgentManagedResultSchema.parse(result));
|
|
3184
|
+
},
|
|
3185
|
+
dispose() {
|
|
3186
|
+
if (disposed) return;
|
|
3187
|
+
disposed = true;
|
|
3188
|
+
for (const response of streams) response.end();
|
|
3189
|
+
streams.clear();
|
|
3190
|
+
}
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
// src/server/agent-http.ts
|
|
3195
|
+
var import_shared16 = require("@spotpatch/shared");
|
|
3033
3196
|
|
|
3034
3197
|
// src/server/agent-request.ts
|
|
3035
|
-
var
|
|
3198
|
+
var import_shared15 = require("@spotpatch/shared");
|
|
3036
3199
|
async function authorizeAgentJobRequest(input) {
|
|
3037
3200
|
const annotation = await authorizeAnnotation({
|
|
3038
3201
|
annotation: input.request.annotation,
|
|
@@ -3069,16 +3232,16 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
|
|
|
3069
3232
|
"failed"
|
|
3070
3233
|
]);
|
|
3071
3234
|
function matchAgentRequestPath(path9) {
|
|
3072
|
-
if (path9 ===
|
|
3235
|
+
if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentCapability) {
|
|
3073
3236
|
return Object.freeze({ kind: "capability" });
|
|
3074
3237
|
}
|
|
3075
|
-
if (path9 ===
|
|
3238
|
+
if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
|
|
3076
3239
|
return Object.freeze({ kind: "workspace-health" });
|
|
3077
3240
|
}
|
|
3078
|
-
if (path9 ===
|
|
3241
|
+
if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentJobs) {
|
|
3079
3242
|
return Object.freeze({ kind: "create-job" });
|
|
3080
3243
|
}
|
|
3081
|
-
const prefix = `${
|
|
3244
|
+
const prefix = `${import_shared16.SPOTPATCH_ENDPOINTS.agentJobs}/`;
|
|
3082
3245
|
if (!path9.startsWith(prefix)) {
|
|
3083
3246
|
return void 0;
|
|
3084
3247
|
}
|
|
@@ -3096,7 +3259,7 @@ function matchAgentRequestPath(path9) {
|
|
|
3096
3259
|
}
|
|
3097
3260
|
function requireAgentManager(options) {
|
|
3098
3261
|
if (options.agentManager === void 0 || options.options.ai === false) {
|
|
3099
|
-
throw new
|
|
3262
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.AI_DISABLED);
|
|
3100
3263
|
}
|
|
3101
3264
|
return options.agentManager;
|
|
3102
3265
|
}
|
|
@@ -3149,13 +3312,13 @@ function streamAgentJobEvents(response, manager, jobId) {
|
|
|
3149
3312
|
}
|
|
3150
3313
|
async function handleCapability(request, response, options, writeSuccess) {
|
|
3151
3314
|
if (request.method !== "POST") {
|
|
3152
|
-
throw new
|
|
3315
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3153
3316
|
}
|
|
3154
|
-
const parsed =
|
|
3317
|
+
const parsed = import_shared16.agentCapabilityRequestSchema.safeParse(
|
|
3155
3318
|
await readJsonRequestBody(request)
|
|
3156
3319
|
);
|
|
3157
3320
|
if (!parsed.success) {
|
|
3158
|
-
throw new
|
|
3321
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3159
3322
|
}
|
|
3160
3323
|
const controller = new AbortController();
|
|
3161
3324
|
const abort = () => {
|
|
@@ -3174,13 +3337,13 @@ async function handleCapability(request, response, options, writeSuccess) {
|
|
|
3174
3337
|
}
|
|
3175
3338
|
async function handleCreateJob(request, response, options, writeSuccess) {
|
|
3176
3339
|
if (request.method !== "POST") {
|
|
3177
|
-
throw new
|
|
3340
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3178
3341
|
}
|
|
3179
|
-
const parsed =
|
|
3342
|
+
const parsed = import_shared16.agentJobCreateRequestSchema.safeParse(
|
|
3180
3343
|
await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
|
|
3181
3344
|
);
|
|
3182
3345
|
if (!parsed.success) {
|
|
3183
|
-
throw new
|
|
3346
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3184
3347
|
}
|
|
3185
3348
|
const authorizedRequest = await authorizeAgentJobRequest({
|
|
3186
3349
|
request: parsed.data,
|
|
@@ -3193,13 +3356,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
|
|
|
3193
3356
|
}
|
|
3194
3357
|
async function handleWorkspaceHealth(request, response, options, writeSuccess) {
|
|
3195
3358
|
if (request.method !== "POST") {
|
|
3196
|
-
throw new
|
|
3359
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3197
3360
|
}
|
|
3198
|
-
const parsed =
|
|
3361
|
+
const parsed = import_shared16.agentWorkspaceHealthRequestSchema.safeParse(
|
|
3199
3362
|
await readJsonRequestBody(request)
|
|
3200
3363
|
);
|
|
3201
3364
|
if (!parsed.success) {
|
|
3202
|
-
throw new
|
|
3365
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3203
3366
|
}
|
|
3204
3367
|
const controller = new AbortController();
|
|
3205
3368
|
const abort = () => {
|
|
@@ -3216,13 +3379,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
|
|
|
3216
3379
|
async function handleJobAction(request, response, options, route, writeSuccess) {
|
|
3217
3380
|
const manager = requireAgentManager(options);
|
|
3218
3381
|
if (request.method !== "POST") {
|
|
3219
|
-
throw new
|
|
3382
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3220
3383
|
}
|
|
3221
|
-
const parsed =
|
|
3384
|
+
const parsed = import_shared16.agentJobActionRequestSchema.safeParse(
|
|
3222
3385
|
await readJsonRequestBody(request)
|
|
3223
3386
|
);
|
|
3224
3387
|
if (!parsed.success) {
|
|
3225
|
-
throw new
|
|
3388
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3226
3389
|
}
|
|
3227
3390
|
if (route.action === "events") {
|
|
3228
3391
|
streamAgentJobEvents(response, manager, route.jobId);
|
|
@@ -3339,25 +3502,25 @@ var launchConfiguredEditor = createEditorLauncher();
|
|
|
3339
3502
|
// src/server/data-flow-http.ts
|
|
3340
3503
|
var import_node_crypto9 = require("crypto");
|
|
3341
3504
|
var import_analyzer = require("@spotpatch/analyzer");
|
|
3342
|
-
var
|
|
3505
|
+
var import_shared17 = require("@spotpatch/shared");
|
|
3343
3506
|
function envelopeBytes(report) {
|
|
3344
3507
|
return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
|
|
3345
3508
|
}
|
|
3346
3509
|
function limitDataFlowReportToBytes(report, maximumBytes) {
|
|
3347
|
-
const structurallyLimited = (0,
|
|
3510
|
+
const structurallyLimited = (0, import_shared17.limitDataFlowReportCollections)(report);
|
|
3348
3511
|
if (envelopeBytes(structurallyLimited) <= maximumBytes) {
|
|
3349
3512
|
return structurallyLimited;
|
|
3350
3513
|
}
|
|
3351
|
-
let limited = (0,
|
|
3514
|
+
let limited = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
|
|
3352
3515
|
forceTruncation: true,
|
|
3353
3516
|
maximumDependencies: 0,
|
|
3354
3517
|
truncatedBy: "bytes"
|
|
3355
3518
|
});
|
|
3356
3519
|
if (envelopeBytes(limited) > maximumBytes) {
|
|
3357
|
-
throw new
|
|
3520
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INTERNAL_ERROR);
|
|
3358
3521
|
}
|
|
3359
3522
|
for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
|
|
3360
|
-
const candidate = (0,
|
|
3523
|
+
const candidate = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
|
|
3361
3524
|
forceTruncation: true,
|
|
3362
3525
|
maximumDependencies,
|
|
3363
3526
|
truncatedBy: "bytes"
|
|
@@ -3383,7 +3546,7 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3383
3546
|
request.componentSourceId
|
|
3384
3547
|
);
|
|
3385
3548
|
if (anchor?.sourceVersion !== request.sourceVersion) {
|
|
3386
|
-
throw new
|
|
3549
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
|
|
3387
3550
|
}
|
|
3388
3551
|
return anchor;
|
|
3389
3552
|
}
|
|
@@ -3400,7 +3563,7 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3400
3563
|
column: resolvedRequest.column
|
|
3401
3564
|
});
|
|
3402
3565
|
if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
|
|
3403
|
-
throw new
|
|
3566
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
|
|
3404
3567
|
}
|
|
3405
3568
|
return limitDataFlowReportToBytes(
|
|
3406
3569
|
report,
|
|
@@ -3409,31 +3572,31 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3409
3572
|
}
|
|
3410
3573
|
function requireAnalyzer(analyzer) {
|
|
3411
3574
|
if (analyzer === void 0) {
|
|
3412
|
-
throw new
|
|
3575
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_DISABLED);
|
|
3413
3576
|
}
|
|
3414
3577
|
return analyzer;
|
|
3415
3578
|
}
|
|
3416
3579
|
async function handleComponentDataFlowReport(request, analyzer, options) {
|
|
3417
|
-
const parsed =
|
|
3580
|
+
const parsed = import_shared17.dataFlowComponentReportRequestSchema.safeParse(
|
|
3418
3581
|
await readJsonRequestBody(
|
|
3419
3582
|
request,
|
|
3420
3583
|
options.options.dataFlow.limits.protocolRequestMaxBytes
|
|
3421
3584
|
)
|
|
3422
3585
|
);
|
|
3423
3586
|
if (!parsed.success) {
|
|
3424
|
-
throw new
|
|
3587
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3425
3588
|
}
|
|
3426
3589
|
return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
|
|
3427
3590
|
}
|
|
3428
3591
|
async function handlePageDataFlowReport(request, analyzer, options) {
|
|
3429
|
-
const parsed =
|
|
3592
|
+
const parsed = import_shared17.dataFlowPageReportRequestSchema.safeParse(
|
|
3430
3593
|
await readJsonRequestBody(
|
|
3431
3594
|
request,
|
|
3432
3595
|
options.options.dataFlow.limits.protocolRequestMaxBytes
|
|
3433
3596
|
)
|
|
3434
3597
|
);
|
|
3435
3598
|
if (!parsed.success) {
|
|
3436
|
-
throw new
|
|
3599
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3437
3600
|
}
|
|
3438
3601
|
const activeAnalyzer = requireAnalyzer(analyzer);
|
|
3439
3602
|
const componentReports = await Promise.all(
|
|
@@ -3460,7 +3623,7 @@ async function handlePageDataFlowReport(request, analyzer, options) {
|
|
|
3460
3623
|
const reportId = `page_${(0, import_node_crypto9.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
|
|
3461
3624
|
const complete = componentReports.every((report2) => report2.completeness.complete);
|
|
3462
3625
|
const report = Object.freeze({
|
|
3463
|
-
schemaVersion:
|
|
3626
|
+
schemaVersion: import_shared17.DATA_FLOW_SCHEMA_VERSION,
|
|
3464
3627
|
reportId,
|
|
3465
3628
|
baseline: Object.freeze({
|
|
3466
3629
|
registryEpoch: options.session.id,
|
|
@@ -3506,7 +3669,7 @@ async function handlePageDataFlowReport(request, analyzer, options) {
|
|
|
3506
3669
|
// src/server/request-security.ts
|
|
3507
3670
|
var import_node_crypto10 = require("crypto");
|
|
3508
3671
|
var import_node_net = require("net");
|
|
3509
|
-
var
|
|
3672
|
+
var import_shared18 = require("@spotpatch/shared");
|
|
3510
3673
|
function getSingleHeader(request, name) {
|
|
3511
3674
|
const value = request.headers[name.toLowerCase()];
|
|
3512
3675
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -3551,32 +3714,32 @@ function parseOrigin(value) {
|
|
|
3551
3714
|
}
|
|
3552
3715
|
}
|
|
3553
3716
|
function assertRequestAuthorized(request, options) {
|
|
3554
|
-
const actualToken = getSingleHeader(request,
|
|
3717
|
+
const actualToken = getSingleHeader(request, import_shared18.SPOTPATCH_TOKEN_HEADER);
|
|
3555
3718
|
if (!tokensMatch2(actualToken, options.sessionToken)) {
|
|
3556
|
-
throw new
|
|
3719
|
+
throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_TOKEN);
|
|
3557
3720
|
}
|
|
3558
3721
|
const hostHeader = getSingleHeader(request, "host");
|
|
3559
3722
|
const originHeader = getSingleHeader(request, "origin");
|
|
3560
3723
|
const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
|
|
3561
3724
|
const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
|
|
3562
3725
|
if (host === void 0 || origin === void 0) {
|
|
3563
|
-
throw new
|
|
3726
|
+
throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
3564
3727
|
}
|
|
3565
3728
|
const hostIsLoopback = isLoopbackHostname(host.hostname);
|
|
3566
3729
|
const originIsLoopback = isLoopbackHostname(origin.hostname);
|
|
3567
3730
|
if (!options.allowLan) {
|
|
3568
3731
|
if (!hostIsLoopback || !originIsLoopback) {
|
|
3569
|
-
throw new
|
|
3732
|
+
throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
3570
3733
|
}
|
|
3571
3734
|
return;
|
|
3572
3735
|
}
|
|
3573
3736
|
if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
|
|
3574
|
-
throw new
|
|
3737
|
+
throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
3575
3738
|
}
|
|
3576
3739
|
}
|
|
3577
3740
|
|
|
3578
3741
|
// src/server/runtime-bootstrap.ts
|
|
3579
|
-
var
|
|
3742
|
+
var import_shared19 = require("@spotpatch/shared");
|
|
3580
3743
|
function getSingleHeader2(request, name) {
|
|
3581
3744
|
const value = request.headers[name.toLowerCase()];
|
|
3582
3745
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -3591,7 +3754,7 @@ function resolveRuntimeBootstrapOptions(options) {
|
|
|
3591
3754
|
if (expectedOrigin.origin !== options.expectedOrigin || expectedOrigin.protocol !== "http:" || !isLoopbackHostname(expectedOrigin.hostname)) {
|
|
3592
3755
|
throw new TypeError("The SpotPatch bootstrap origin must be a loopback origin.");
|
|
3593
3756
|
}
|
|
3594
|
-
const parsedConfig =
|
|
3757
|
+
const parsedConfig = import_shared19.runtimeConfigSchema.safeParse(options.runtimeConfig);
|
|
3595
3758
|
if (!parsedConfig.success) {
|
|
3596
3759
|
throw new TypeError("The SpotPatch Runtime configuration is invalid.");
|
|
3597
3760
|
}
|
|
@@ -3603,7 +3766,7 @@ function resolveRuntimeBootstrapOptions(options) {
|
|
|
3603
3766
|
function assertRuntimeBootstrapRequest(request, expectedOrigin) {
|
|
3604
3767
|
const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
3605
3768
|
if (request.method !== "POST" || contentType !== "application/json") {
|
|
3606
|
-
throw new
|
|
3769
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3607
3770
|
}
|
|
3608
3771
|
const host = getSingleHeader2(request, "host");
|
|
3609
3772
|
let hostIsLoopback = false;
|
|
@@ -3615,132 +3778,132 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
|
|
|
3615
3778
|
}
|
|
3616
3779
|
}
|
|
3617
3780
|
if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
|
|
3618
|
-
throw new
|
|
3781
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
3619
3782
|
}
|
|
3620
3783
|
}
|
|
3621
3784
|
async function readRuntimeBootstrap(request, options) {
|
|
3622
3785
|
assertRuntimeBootstrapRequest(request, options.expectedOrigin);
|
|
3623
|
-
const parsedBody =
|
|
3786
|
+
const parsedBody = import_shared19.runtimeBootstrapRequestSchema.safeParse(
|
|
3624
3787
|
await readJsonRequestBody(request)
|
|
3625
3788
|
);
|
|
3626
3789
|
if (!parsedBody.success) {
|
|
3627
|
-
throw new
|
|
3790
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3628
3791
|
}
|
|
3629
3792
|
return options.runtimeConfig;
|
|
3630
3793
|
}
|
|
3631
3794
|
|
|
3632
3795
|
// src/server/middleware.ts
|
|
3633
3796
|
var STATUS_BY_ERROR = Object.freeze({
|
|
3634
|
-
[
|
|
3635
|
-
[
|
|
3636
|
-
[
|
|
3637
|
-
[
|
|
3638
|
-
[
|
|
3639
|
-
[
|
|
3640
|
-
[
|
|
3641
|
-
[
|
|
3642
|
-
[
|
|
3643
|
-
[
|
|
3644
|
-
[
|
|
3645
|
-
[
|
|
3646
|
-
[
|
|
3647
|
-
[
|
|
3648
|
-
[
|
|
3649
|
-
[
|
|
3650
|
-
[
|
|
3651
|
-
[
|
|
3652
|
-
[
|
|
3653
|
-
[
|
|
3654
|
-
[
|
|
3655
|
-
[
|
|
3656
|
-
[
|
|
3657
|
-
[
|
|
3658
|
-
[
|
|
3659
|
-
[
|
|
3660
|
-
[
|
|
3661
|
-
[
|
|
3662
|
-
[
|
|
3663
|
-
[
|
|
3664
|
-
[
|
|
3665
|
-
[
|
|
3666
|
-
[
|
|
3667
|
-
[
|
|
3668
|
-
[
|
|
3669
|
-
[
|
|
3670
|
-
[
|
|
3671
|
-
[
|
|
3672
|
-
[
|
|
3673
|
-
[
|
|
3674
|
-
[
|
|
3675
|
-
[
|
|
3676
|
-
[
|
|
3677
|
-
[
|
|
3678
|
-
[
|
|
3679
|
-
[
|
|
3680
|
-
[
|
|
3681
|
-
[
|
|
3682
|
-
[
|
|
3683
|
-
[
|
|
3684
|
-
[
|
|
3685
|
-
[
|
|
3686
|
-
[
|
|
3687
|
-
[
|
|
3797
|
+
[import_shared20.ERROR_CODES.INVALID_REQUEST]: 400,
|
|
3798
|
+
[import_shared20.ERROR_CODES.INVALID_TOKEN]: 401,
|
|
3799
|
+
[import_shared20.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
|
|
3800
|
+
[import_shared20.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
|
|
3801
|
+
[import_shared20.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
|
|
3802
|
+
[import_shared20.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
|
|
3803
|
+
[import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
|
|
3804
|
+
[import_shared20.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
|
|
3805
|
+
[import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
|
|
3806
|
+
[import_shared20.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
|
|
3807
|
+
[import_shared20.ERROR_CODES.AI_DISABLED]: 404,
|
|
3808
|
+
[import_shared20.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
|
|
3809
|
+
[import_shared20.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
|
|
3810
|
+
[import_shared20.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
|
|
3811
|
+
[import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
|
|
3812
|
+
[import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
|
|
3813
|
+
[import_shared20.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
|
|
3814
|
+
[import_shared20.ERROR_CODES.AGENT_BUSY]: 409,
|
|
3815
|
+
[import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
|
|
3816
|
+
[import_shared20.ERROR_CODES.AGENT_CANCELLED]: 409,
|
|
3817
|
+
[import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: 404,
|
|
3818
|
+
[import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
|
|
3819
|
+
[import_shared20.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: 422,
|
|
3820
|
+
[import_shared20.ERROR_CODES.HANDOFF_SOURCE_STALE]: 409,
|
|
3821
|
+
[import_shared20.ERROR_CODES.HANDOFF_NOT_FOUND]: 404,
|
|
3822
|
+
[import_shared20.ERROR_CODES.HANDOFF_EXPIRED]: 410,
|
|
3823
|
+
[import_shared20.ERROR_CODES.HANDOFF_CURSOR_INVALID]: 409,
|
|
3824
|
+
[import_shared20.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: 413,
|
|
3825
|
+
[import_shared20.ERROR_CODES.BRIDGE_UNAUTHORIZED]: 401,
|
|
3826
|
+
[import_shared20.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: 409,
|
|
3827
|
+
[import_shared20.ERROR_CODES.BRIDGE_BUSY]: 429,
|
|
3828
|
+
[import_shared20.ERROR_CODES.EXTERNAL_AGENT_BUSY]: 409,
|
|
3829
|
+
[import_shared20.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: 409,
|
|
3830
|
+
[import_shared20.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
|
|
3831
|
+
[import_shared20.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: 409,
|
|
3832
|
+
[import_shared20.ERROR_CODES.SESSION_NOT_FOUND]: 404,
|
|
3833
|
+
[import_shared20.ERROR_CODES.SESSION_AMBIGUOUS]: 409,
|
|
3834
|
+
[import_shared20.ERROR_CODES.SESSION_CLOSED]: 410,
|
|
3835
|
+
[import_shared20.ERROR_CODES.WORKTREE_DIRTY]: 409,
|
|
3836
|
+
[import_shared20.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
|
|
3837
|
+
[import_shared20.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
|
|
3838
|
+
[import_shared20.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
|
|
3839
|
+
[import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
|
|
3840
|
+
[import_shared20.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
|
|
3841
|
+
[import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
|
|
3842
|
+
[import_shared20.ERROR_CODES.TOOL_DENIED]: 403,
|
|
3843
|
+
[import_shared20.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
|
|
3844
|
+
[import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
|
|
3845
|
+
[import_shared20.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
|
|
3846
|
+
[import_shared20.ERROR_CODES.TOOL_PATH_DENIED]: 403,
|
|
3847
|
+
[import_shared20.ERROR_CODES.PATCH_REJECTED]: 422,
|
|
3848
|
+
[import_shared20.ERROR_CODES.VALIDATION_FAILED]: 422,
|
|
3849
|
+
[import_shared20.ERROR_CODES.APPLY_CONFLICT]: 409,
|
|
3850
|
+
[import_shared20.ERROR_CODES.INTERNAL_ERROR]: 500
|
|
3688
3851
|
});
|
|
3689
3852
|
var PUBLIC_MESSAGES = Object.freeze({
|
|
3690
|
-
[
|
|
3691
|
-
[
|
|
3692
|
-
[
|
|
3693
|
-
[
|
|
3694
|
-
[
|
|
3695
|
-
[
|
|
3696
|
-
[
|
|
3697
|
-
[
|
|
3698
|
-
[
|
|
3699
|
-
[
|
|
3700
|
-
[
|
|
3701
|
-
[
|
|
3702
|
-
[
|
|
3703
|
-
[
|
|
3704
|
-
[
|
|
3705
|
-
[
|
|
3706
|
-
[
|
|
3707
|
-
[
|
|
3708
|
-
[
|
|
3709
|
-
[
|
|
3710
|
-
[
|
|
3711
|
-
[
|
|
3712
|
-
[
|
|
3713
|
-
[
|
|
3714
|
-
[
|
|
3715
|
-
[
|
|
3716
|
-
[
|
|
3717
|
-
[
|
|
3718
|
-
[
|
|
3719
|
-
[
|
|
3720
|
-
[
|
|
3721
|
-
[
|
|
3722
|
-
[
|
|
3723
|
-
[
|
|
3724
|
-
[
|
|
3725
|
-
[
|
|
3726
|
-
[
|
|
3727
|
-
[
|
|
3728
|
-
[
|
|
3729
|
-
[
|
|
3730
|
-
[
|
|
3731
|
-
[
|
|
3732
|
-
[
|
|
3733
|
-
[
|
|
3734
|
-
[
|
|
3735
|
-
[
|
|
3736
|
-
[
|
|
3737
|
-
[
|
|
3738
|
-
[
|
|
3739
|
-
[
|
|
3740
|
-
[
|
|
3741
|
-
[
|
|
3742
|
-
[
|
|
3743
|
-
[
|
|
3853
|
+
[import_shared20.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
|
|
3854
|
+
[import_shared20.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
|
|
3855
|
+
[import_shared20.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
|
|
3856
|
+
[import_shared20.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
|
|
3857
|
+
[import_shared20.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
|
|
3858
|
+
[import_shared20.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
|
|
3859
|
+
[import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
|
|
3860
|
+
[import_shared20.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
|
|
3861
|
+
[import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
|
|
3862
|
+
[import_shared20.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
|
|
3863
|
+
[import_shared20.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
|
|
3864
|
+
[import_shared20.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
|
|
3865
|
+
[import_shared20.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
|
|
3866
|
+
[import_shared20.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
|
|
3867
|
+
[import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
|
|
3868
|
+
[import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
|
|
3869
|
+
[import_shared20.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
|
|
3870
|
+
[import_shared20.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
|
|
3871
|
+
[import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
|
|
3872
|
+
[import_shared20.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
|
|
3873
|
+
[import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
|
|
3874
|
+
[import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
|
|
3875
|
+
[import_shared20.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
|
|
3876
|
+
[import_shared20.ERROR_CODES.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
|
|
3877
|
+
[import_shared20.ERROR_CODES.HANDOFF_NOT_FOUND]: "No current handoff is available.",
|
|
3878
|
+
[import_shared20.ERROR_CODES.HANDOFF_EXPIRED]: "The handoff has expired.",
|
|
3879
|
+
[import_shared20.ERROR_CODES.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
|
|
3880
|
+
[import_shared20.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
|
|
3881
|
+
[import_shared20.ERROR_CODES.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
|
|
3882
|
+
[import_shared20.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
|
|
3883
|
+
[import_shared20.ERROR_CODES.BRIDGE_BUSY]: "The local bridge is busy.",
|
|
3884
|
+
[import_shared20.ERROR_CODES.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
|
|
3885
|
+
[import_shared20.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
|
|
3886
|
+
[import_shared20.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
|
|
3887
|
+
[import_shared20.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
|
|
3888
|
+
[import_shared20.ERROR_CODES.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
|
|
3889
|
+
[import_shared20.ERROR_CODES.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
|
|
3890
|
+
[import_shared20.ERROR_CODES.SESSION_CLOSED]: "The SpotPatch session has closed.",
|
|
3891
|
+
[import_shared20.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
|
|
3892
|
+
[import_shared20.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
|
|
3893
|
+
[import_shared20.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
|
|
3894
|
+
[import_shared20.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
|
|
3895
|
+
[import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
|
|
3896
|
+
[import_shared20.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
|
|
3897
|
+
[import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
|
|
3898
|
+
[import_shared20.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
|
|
3899
|
+
[import_shared20.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
|
|
3900
|
+
[import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
|
|
3901
|
+
[import_shared20.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
|
|
3902
|
+
[import_shared20.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
|
|
3903
|
+
[import_shared20.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
|
|
3904
|
+
[import_shared20.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
|
|
3905
|
+
[import_shared20.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
|
|
3906
|
+
[import_shared20.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
|
|
3744
3907
|
});
|
|
3745
3908
|
function writeJson2(response, status, payload) {
|
|
3746
3909
|
response.statusCode = status;
|
|
@@ -3749,11 +3912,11 @@ function writeJson2(response, status, payload) {
|
|
|
3749
3912
|
response.end(JSON.stringify(payload));
|
|
3750
3913
|
}
|
|
3751
3914
|
function asSpotPatchError(error) {
|
|
3752
|
-
return error instanceof
|
|
3915
|
+
return error instanceof import_shared20.SpotPatchError ? error : new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
|
|
3753
3916
|
}
|
|
3754
3917
|
function writeError(response, error, logger) {
|
|
3755
3918
|
const normalized = asSpotPatchError(error);
|
|
3756
|
-
if (normalized.code ===
|
|
3919
|
+
if (normalized.code === import_shared20.ERROR_CODES.INTERNAL_ERROR) {
|
|
3757
3920
|
logger?.warn("[spotpatch:server] Internal request failure.");
|
|
3758
3921
|
}
|
|
3759
3922
|
writeJson2(response, STATUS_BY_ERROR[normalized.code], {
|
|
@@ -3772,11 +3935,11 @@ function requestPath(request) {
|
|
|
3772
3935
|
}
|
|
3773
3936
|
}
|
|
3774
3937
|
async function handleSourceContext(request, options) {
|
|
3775
|
-
const parsed =
|
|
3938
|
+
const parsed = import_shared20.sourceContextRequestSchema.safeParse(
|
|
3776
3939
|
await readJsonRequestBody(request)
|
|
3777
3940
|
);
|
|
3778
3941
|
if (!parsed.success) {
|
|
3779
|
-
throw new
|
|
3942
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3780
3943
|
}
|
|
3781
3944
|
return readSourceContext({
|
|
3782
3945
|
request: parsed.data,
|
|
@@ -3787,9 +3950,9 @@ async function handleSourceContext(request, options) {
|
|
|
3787
3950
|
});
|
|
3788
3951
|
}
|
|
3789
3952
|
async function handleOpenEditor(request, options) {
|
|
3790
|
-
const parsed =
|
|
3953
|
+
const parsed = import_shared20.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
|
|
3791
3954
|
if (!parsed.success) {
|
|
3792
|
-
throw new
|
|
3955
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3793
3956
|
}
|
|
3794
3957
|
const body = parsed.data;
|
|
3795
3958
|
const sourcePath = await resolveSourceFile({
|
|
@@ -3806,7 +3969,7 @@ async function handleOpenEditor(request, options) {
|
|
|
3806
3969
|
options.logger?.warn(
|
|
3807
3970
|
`[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
|
|
3808
3971
|
);
|
|
3809
|
-
throw new
|
|
3972
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
|
|
3810
3973
|
cause: error
|
|
3811
3974
|
});
|
|
3812
3975
|
}
|
|
@@ -3814,16 +3977,18 @@ async function handleOpenEditor(request, options) {
|
|
|
3814
3977
|
function createSpotPatchMiddleware(options) {
|
|
3815
3978
|
const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
|
|
3816
3979
|
const dataFlowAnalyzer = createDataFlowAnalyzer(options);
|
|
3817
|
-
|
|
3980
|
+
const externalAgentController = options.externalAgentControl === void 0 ? void 0 : createExternalAgentBrowserController(options.externalAgentControl);
|
|
3981
|
+
const middleware = (request, response, next) => {
|
|
3818
3982
|
const path9 = requestPath(request);
|
|
3819
3983
|
const agentRoute = matchAgentRequestPath(path9);
|
|
3984
|
+
const externalAgentRoute = matchExternalAgentBrowserPath(path9);
|
|
3820
3985
|
const externalHandoffRoute = matchExternalHandoffBrowserPath(path9);
|
|
3821
|
-
if (path9 !==
|
|
3986
|
+
if (path9 !== import_shared20.SPOTPATCH_ENDPOINTS.sourceContext && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.openEditor && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport && agentRoute === void 0 && externalAgentRoute === void 0 && externalHandoffRoute === void 0 && !path9.startsWith(`${import_shared20.SPOTPATCH_API_BASE}/`)) {
|
|
3822
3987
|
next();
|
|
3823
3988
|
return;
|
|
3824
3989
|
}
|
|
3825
3990
|
const handle = async () => {
|
|
3826
|
-
if (path9 ===
|
|
3991
|
+
if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
|
|
3827
3992
|
const data = await readRuntimeBootstrap(
|
|
3828
3993
|
request,
|
|
3829
3994
|
bootstrap
|
|
@@ -3835,25 +4000,25 @@ function createSpotPatchMiddleware(options) {
|
|
|
3835
4000
|
allowLan: options.options.allowLan,
|
|
3836
4001
|
sessionToken: options.session.token
|
|
3837
4002
|
});
|
|
3838
|
-
if (path9 ===
|
|
4003
|
+
if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.sourceContext) {
|
|
3839
4004
|
if (request.method !== "POST") {
|
|
3840
|
-
throw new
|
|
4005
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3841
4006
|
}
|
|
3842
4007
|
const data = await handleSourceContext(request, options);
|
|
3843
4008
|
writeJson2(response, 200, { ok: true, data });
|
|
3844
4009
|
return;
|
|
3845
4010
|
}
|
|
3846
|
-
if (path9 ===
|
|
4011
|
+
if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.openEditor) {
|
|
3847
4012
|
if (request.method !== "POST") {
|
|
3848
|
-
throw new
|
|
4013
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3849
4014
|
}
|
|
3850
4015
|
const data = await handleOpenEditor(request, options);
|
|
3851
4016
|
writeJson2(response, 200, { ok: true, data });
|
|
3852
4017
|
return;
|
|
3853
4018
|
}
|
|
3854
|
-
if (path9 ===
|
|
4019
|
+
if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
|
|
3855
4020
|
if (request.method !== "POST") {
|
|
3856
|
-
throw new
|
|
4021
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3857
4022
|
}
|
|
3858
4023
|
const data = await handleComponentDataFlowReport(
|
|
3859
4024
|
request,
|
|
@@ -3863,14 +4028,28 @@ function createSpotPatchMiddleware(options) {
|
|
|
3863
4028
|
writeJson2(response, 200, { ok: true, data });
|
|
3864
4029
|
return;
|
|
3865
4030
|
}
|
|
3866
|
-
if (path9 ===
|
|
4031
|
+
if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
|
|
3867
4032
|
if (request.method !== "POST") {
|
|
3868
|
-
throw new
|
|
4033
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3869
4034
|
}
|
|
3870
4035
|
const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
|
|
3871
4036
|
writeJson2(response, 200, { ok: true, data });
|
|
3872
4037
|
return;
|
|
3873
4038
|
}
|
|
4039
|
+
if (externalAgentRoute !== void 0) {
|
|
4040
|
+
if (!options.options.externalAgent.enabled || externalAgentController === void 0) {
|
|
4041
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
|
|
4042
|
+
}
|
|
4043
|
+
await externalAgentController.handle(
|
|
4044
|
+
request,
|
|
4045
|
+
response,
|
|
4046
|
+
externalAgentRoute,
|
|
4047
|
+
(target, status, data) => {
|
|
4048
|
+
writeJson2(target, status, { ok: true, data });
|
|
4049
|
+
}
|
|
4050
|
+
);
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
3874
4053
|
if (externalHandoffRoute !== void 0) {
|
|
3875
4054
|
await handleExternalHandoffBrowserRequest(
|
|
3876
4055
|
request,
|
|
@@ -3889,7 +4068,7 @@ function createSpotPatchMiddleware(options) {
|
|
|
3889
4068
|
return;
|
|
3890
4069
|
}
|
|
3891
4070
|
if (agentRoute === void 0) {
|
|
3892
|
-
throw new
|
|
4071
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3893
4072
|
}
|
|
3894
4073
|
await handleAgentRequest(
|
|
3895
4074
|
request,
|
|
@@ -3905,6 +4084,11 @@ function createSpotPatchMiddleware(options) {
|
|
|
3905
4084
|
writeError(response, error, options.logger);
|
|
3906
4085
|
});
|
|
3907
4086
|
};
|
|
4087
|
+
return Object.assign(middleware, {
|
|
4088
|
+
dispose() {
|
|
4089
|
+
externalAgentController?.dispose();
|
|
4090
|
+
}
|
|
4091
|
+
});
|
|
3908
4092
|
}
|
|
3909
4093
|
|
|
3910
4094
|
// src/server/source-registration.ts
|
|
@@ -3912,14 +4096,26 @@ var import_node_crypto11 = require("crypto");
|
|
|
3912
4096
|
var import_promises7 = require("fs/promises");
|
|
3913
4097
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
3914
4098
|
var import_compiler = require("@spotpatch/compiler");
|
|
4099
|
+
var import_shared21 = require("@spotpatch/shared");
|
|
3915
4100
|
var import_zod2 = require("zod");
|
|
3916
|
-
var REGISTRATION_BODY_LIMIT_BYTES =
|
|
4101
|
+
var REGISTRATION_BODY_LIMIT_BYTES = import_shared21.DEFAULT_DATA_FLOW_LIMITS.protocolRequestMaxBytes;
|
|
3917
4102
|
var REGISTRATION_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{16,128}$/u;
|
|
4103
|
+
var DATA_FLOW_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
|
|
3918
4104
|
var INTERNAL_SECRET_HEADER = "x-spotpatch-internal";
|
|
3919
4105
|
var FORBIDDEN_SOURCE_SEGMENTS = /* @__PURE__ */ new Set([".next", "node_modules"]);
|
|
3920
4106
|
var registrationRequestSchema = import_zod2.z.strictObject({
|
|
3921
4107
|
epoch: import_zod2.z.string().regex(REGISTRATION_IDENTITY_PATTERN),
|
|
3922
|
-
resourcePath: import_zod2.z.string().min(1).max(3072)
|
|
4108
|
+
resourcePath: import_zod2.z.string().min(1).max(3072),
|
|
4109
|
+
dataFlow: import_zod2.z.strictObject({
|
|
4110
|
+
sourceVersion: import_zod2.z.string().regex(DATA_FLOW_IDENTITY_PATTERN),
|
|
4111
|
+
components: import_zod2.z.array(
|
|
4112
|
+
import_zod2.z.strictObject({
|
|
4113
|
+
componentSourceId: import_zod2.z.string().regex(DATA_FLOW_IDENTITY_PATTERN),
|
|
4114
|
+
line: import_zod2.z.number().int().positive().max(1e7),
|
|
4115
|
+
column: import_zod2.z.number().int().positive().max(1e7)
|
|
4116
|
+
})
|
|
4117
|
+
).max(import_shared21.DEFAULT_DATA_FLOW_LIMITS.sourceMaxComponents)
|
|
4118
|
+
}).optional()
|
|
3923
4119
|
});
|
|
3924
4120
|
function getSingleHeader3(request, name) {
|
|
3925
4121
|
const value = request.headers[name.toLowerCase()];
|
|
@@ -3983,6 +4179,33 @@ async function createSourceRegistrationService(input) {
|
|
|
3983
4179
|
}
|
|
3984
4180
|
const root = await (0, import_promises7.realpath)(input.root);
|
|
3985
4181
|
const sourceFilter = (0, import_compiler.createSourceFilter)(root, input.options);
|
|
4182
|
+
const dataFlowFilter = (0, import_compiler.createDataFlowSourceFilter)(root, input.options);
|
|
4183
|
+
const registrationQueues = /* @__PURE__ */ new Map();
|
|
4184
|
+
async function registerCurrentSource(sourcePath, dataFlow) {
|
|
4185
|
+
if (dataFlow === void 0) {
|
|
4186
|
+
return input.registry.register(sourcePath);
|
|
4187
|
+
}
|
|
4188
|
+
let fileId;
|
|
4189
|
+
const previous = registrationQueues.get(sourcePath) ?? Promise.resolve();
|
|
4190
|
+
const current = previous.catch(() => void 0).then(async () => {
|
|
4191
|
+
const currentSource = await (0, import_promises7.readFile)(sourcePath, "utf8");
|
|
4192
|
+
if ((0, import_compiler.createDataFlowSourceVersion)(currentSource) !== dataFlow.sourceVersion) {
|
|
4193
|
+
return;
|
|
4194
|
+
}
|
|
4195
|
+
fileId = input.registry.registerDataFlowComponents(
|
|
4196
|
+
sourcePath,
|
|
4197
|
+
dataFlow.sourceVersion,
|
|
4198
|
+
dataFlow.components
|
|
4199
|
+
);
|
|
4200
|
+
});
|
|
4201
|
+
registrationQueues.set(sourcePath, current);
|
|
4202
|
+
await current.finally(() => {
|
|
4203
|
+
if (registrationQueues.get(sourcePath) === current) {
|
|
4204
|
+
registrationQueues.delete(sourcePath);
|
|
4205
|
+
}
|
|
4206
|
+
});
|
|
4207
|
+
return fileId;
|
|
4208
|
+
}
|
|
3986
4209
|
const handler = (request, response) => {
|
|
3987
4210
|
const handle = async () => {
|
|
3988
4211
|
const contentType = getSingleHeader3(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
@@ -4003,16 +4226,18 @@ async function createSourceRegistrationService(input) {
|
|
|
4003
4226
|
const sourcePath = await resolveAuthorizedSource(
|
|
4004
4227
|
root,
|
|
4005
4228
|
parsed.data.resourcePath,
|
|
4006
|
-
(absolutePath) => sourceFilter.shouldTransform(absolutePath, "<")
|
|
4229
|
+
(absolutePath) => parsed.data.dataFlow === void 0 ? sourceFilter.shouldTransform(absolutePath, "<") : dataFlowFilter.shouldTransform(absolutePath, "")
|
|
4007
4230
|
);
|
|
4008
4231
|
if (sourcePath === void 0) {
|
|
4009
4232
|
writeJson3(response, 403, { ok: false });
|
|
4010
4233
|
return;
|
|
4011
4234
|
}
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4235
|
+
const fileId = await registerCurrentSource(sourcePath, parsed.data.dataFlow);
|
|
4236
|
+
if (fileId === void 0) {
|
|
4237
|
+
writeJson3(response, 409, { ok: false });
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
4240
|
+
writeJson3(response, 200, { epoch: input.registryEpoch, fileId });
|
|
4016
4241
|
};
|
|
4017
4242
|
void handle().catch(() => {
|
|
4018
4243
|
if (!response.headersSent) {
|
|
@@ -4237,8 +4462,10 @@ function parseSerializedSpotPatchOptions(value) {
|
|
|
4237
4462
|
readRuntimeBootstrap,
|
|
4238
4463
|
resolveCredentialEnvironment,
|
|
4239
4464
|
resolveEnvironmentAiConfiguration,
|
|
4465
|
+
resolveManagedExecutionValidation,
|
|
4240
4466
|
resolveOptions,
|
|
4241
4467
|
resolveProjectOptions,
|
|
4468
|
+
resolveProjectValidationChecks,
|
|
4242
4469
|
resolveRuntimeBootstrapOptions,
|
|
4243
4470
|
serializeResolvedSpotPatchOptions
|
|
4244
4471
|
});
|