@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 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
- // src/project-options.ts
2357
- function hasRequiredCheck(ai) {
2358
- return Object.values(ai.execution.checks).some((check) => check.required);
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 availableCheckId(checks, preferred) {
2361
- if (checks[preferred] === void 0) {
2362
- return preferred;
2363
- }
2364
- let suffix = 2;
2365
- while (checks[`${preferred}-${String(suffix)}`] !== void 0) {
2366
- suffix += 1;
2367
- }
2368
- return `${preferred}-${String(suffix)}`;
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
- let checks = resolved.ai.execution.checks;
2382
- if (!hasRequiredCheck(resolved.ai)) {
2383
- const discovered = await discoverProjectValidationCheck({
2384
- appRoot: input.appRoot,
2385
- timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
2386
- });
2387
- if (discovered === void 0) {
2388
- throw new RangeError(
2389
- "SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
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 import_shared18 = require("@spotpatch/shared");
2493
+ var import_shared20 = require("@spotpatch/shared");
2477
2494
 
2478
2495
  // src/external-handoff/browser-http.ts
2479
- var import_shared12 = require("@spotpatch/shared");
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 import_shared11 = require("@spotpatch/shared");
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 import_shared10 = require("@spotpatch/shared");
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 import_shared9 = require("@spotpatch/shared");
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 import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
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 import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
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 import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
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 import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
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 import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
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 import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
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 import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_TOO_LARGE);
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 import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
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 import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
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, import_shared11.sanitizeUrl)(page.url, page.url),
2819
- pathname: (0, import_shared11.redactSensitiveText)(page.pathname),
2820
- title: (0, import_shared11.redactSensitiveText)(page.title),
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 import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
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 import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
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 import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
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 import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
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 import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
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 import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
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 import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
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 === import_shared12.SPOTPATCH_ENDPOINTS.externalHandoffCapability) return "capability";
2953
- if (path9 === import_shared12.SPOTPATCH_ENDPOINTS.externalHandoffPublish) return "publish";
2954
- if (path9 === import_shared12.SPOTPATCH_ENDPOINTS.externalHandoffStatus) return "status";
2955
- if (path9 === import_shared12.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery) {
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 import_shared12.SpotPatchError(import_shared12.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
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 import_shared12.SpotPatchError) {
2968
- if (error.code === import_shared12.ERROR_CODES.SOURCE_NOT_FOUND || error.code === import_shared12.ERROR_CODES.SOURCE_OUTSIDE_ROOT || error.code === import_shared12.ERROR_CODES.SOURCE_TOO_LARGE) {
2969
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.HANDOFF_SOURCE_STALE, void 0, {
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 === import_shared12.ERROR_CODES.INVALID_REQUEST) {
2974
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.HANDOFF_VALIDATION_FAILED, void 0, {
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 import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
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 = import_shared12.externalHandoffCapabilityRequestSchema.safeParse(
3004
+ const parsed2 = import_shared13.externalHandoffCapabilityRequestSchema.safeParse(
2988
3005
  await readJsonRequestBody(request)
2989
3006
  );
2990
- if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
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 = import_shared12.externalHandoffStatusRequestSchema.safeParse(
3012
+ const parsed2 = import_shared13.externalHandoffStatusRequestSchema.safeParse(
2996
3013
  await readJsonRequestBody(request)
2997
3014
  );
2998
- if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
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 = import_shared12.externalHandoffResolveDeliveryRequestSchema.safeParse(
3020
+ const parsed2 = import_shared13.externalHandoffResolveDeliveryRequestSchema.safeParse(
3004
3021
  await readJsonRequestBody(request)
3005
3022
  );
3006
- if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
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 = import_shared12.externalHandoffPublishRequestSchema.safeParse(
3011
- await readJsonRequestBody(request, import_shared12.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
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 import_shared12.SpotPatchError(import_shared12.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
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/server/agent-http.ts
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 import_shared13 = require("@spotpatch/shared");
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 === import_shared14.SPOTPATCH_ENDPOINTS.agentCapability) {
3235
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentCapability) {
3073
3236
  return Object.freeze({ kind: "capability" });
3074
3237
  }
3075
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
3238
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
3076
3239
  return Object.freeze({ kind: "workspace-health" });
3077
3240
  }
3078
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.agentJobs) {
3241
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentJobs) {
3079
3242
  return Object.freeze({ kind: "create-job" });
3080
3243
  }
3081
- const prefix = `${import_shared14.SPOTPATCH_ENDPOINTS.agentJobs}/`;
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 import_shared14.SpotPatchError(import_shared14.ERROR_CODES.AI_DISABLED);
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 import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3315
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3153
3316
  }
3154
- const parsed = import_shared14.agentCapabilityRequestSchema.safeParse(
3317
+ const parsed = import_shared16.agentCapabilityRequestSchema.safeParse(
3155
3318
  await readJsonRequestBody(request)
3156
3319
  );
3157
3320
  if (!parsed.success) {
3158
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
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 import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3340
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3178
3341
  }
3179
- const parsed = import_shared14.agentJobCreateRequestSchema.safeParse(
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 import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
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 import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3359
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3197
3360
  }
3198
- const parsed = import_shared14.agentWorkspaceHealthRequestSchema.safeParse(
3361
+ const parsed = import_shared16.agentWorkspaceHealthRequestSchema.safeParse(
3199
3362
  await readJsonRequestBody(request)
3200
3363
  );
3201
3364
  if (!parsed.success) {
3202
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
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 import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3382
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3220
3383
  }
3221
- const parsed = import_shared14.agentJobActionRequestSchema.safeParse(
3384
+ const parsed = import_shared16.agentJobActionRequestSchema.safeParse(
3222
3385
  await readJsonRequestBody(request)
3223
3386
  );
3224
3387
  if (!parsed.success) {
3225
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
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 import_shared15 = require("@spotpatch/shared");
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, import_shared15.limitDataFlowReportCollections)(report);
3510
+ const structurallyLimited = (0, import_shared17.limitDataFlowReportCollections)(report);
3348
3511
  if (envelopeBytes(structurallyLimited) <= maximumBytes) {
3349
3512
  return structurallyLimited;
3350
3513
  }
3351
- let limited = (0, import_shared15.limitDataFlowReportCollections)(structurallyLimited, {
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 import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INTERNAL_ERROR);
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, import_shared15.limitDataFlowReportCollections)(structurallyLimited, {
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 import_shared15.SpotPatchError(import_shared15.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
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 import_shared15.SpotPatchError(import_shared15.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
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 import_shared15.SpotPatchError(import_shared15.ERROR_CODES.DATA_FLOW_DISABLED);
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 = import_shared15.dataFlowComponentReportRequestSchema.safeParse(
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 import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
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 = import_shared15.dataFlowPageReportRequestSchema.safeParse(
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 import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
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: import_shared15.DATA_FLOW_SCHEMA_VERSION,
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 import_shared16 = require("@spotpatch/shared");
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, import_shared16.SPOTPATCH_TOKEN_HEADER);
3717
+ const actualToken = getSingleHeader(request, import_shared18.SPOTPATCH_TOKEN_HEADER);
3555
3718
  if (!tokensMatch2(actualToken, options.sessionToken)) {
3556
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_TOKEN);
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 import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
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 import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
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 import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
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 import_shared17 = require("@spotpatch/shared");
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 = import_shared17.runtimeConfigSchema.safeParse(options.runtimeConfig);
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 import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
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 import_shared17.SpotPatchError(import_shared17.ERROR_CODES.ORIGIN_NOT_ALLOWED);
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 = import_shared17.runtimeBootstrapRequestSchema.safeParse(
3786
+ const parsedBody = import_shared19.runtimeBootstrapRequestSchema.safeParse(
3624
3787
  await readJsonRequestBody(request)
3625
3788
  );
3626
3789
  if (!parsedBody.success) {
3627
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
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
- [import_shared18.ERROR_CODES.INVALID_REQUEST]: 400,
3635
- [import_shared18.ERROR_CODES.INVALID_TOKEN]: 401,
3636
- [import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
3637
- [import_shared18.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
3638
- [import_shared18.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
3639
- [import_shared18.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
3640
- [import_shared18.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
3641
- [import_shared18.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
3642
- [import_shared18.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
3643
- [import_shared18.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
3644
- [import_shared18.ERROR_CODES.AI_DISABLED]: 404,
3645
- [import_shared18.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
3646
- [import_shared18.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
3647
- [import_shared18.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
3648
- [import_shared18.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
3649
- [import_shared18.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
3650
- [import_shared18.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
3651
- [import_shared18.ERROR_CODES.AGENT_BUSY]: 409,
3652
- [import_shared18.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
3653
- [import_shared18.ERROR_CODES.AGENT_CANCELLED]: 409,
3654
- [import_shared18.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: 404,
3655
- [import_shared18.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
3656
- [import_shared18.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: 422,
3657
- [import_shared18.ERROR_CODES.HANDOFF_SOURCE_STALE]: 409,
3658
- [import_shared18.ERROR_CODES.HANDOFF_NOT_FOUND]: 404,
3659
- [import_shared18.ERROR_CODES.HANDOFF_EXPIRED]: 410,
3660
- [import_shared18.ERROR_CODES.HANDOFF_CURSOR_INVALID]: 409,
3661
- [import_shared18.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: 413,
3662
- [import_shared18.ERROR_CODES.BRIDGE_UNAUTHORIZED]: 401,
3663
- [import_shared18.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: 409,
3664
- [import_shared18.ERROR_CODES.BRIDGE_BUSY]: 429,
3665
- [import_shared18.ERROR_CODES.EXTERNAL_AGENT_BUSY]: 409,
3666
- [import_shared18.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: 409,
3667
- [import_shared18.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
3668
- [import_shared18.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: 409,
3669
- [import_shared18.ERROR_CODES.SESSION_NOT_FOUND]: 404,
3670
- [import_shared18.ERROR_CODES.SESSION_AMBIGUOUS]: 409,
3671
- [import_shared18.ERROR_CODES.SESSION_CLOSED]: 410,
3672
- [import_shared18.ERROR_CODES.WORKTREE_DIRTY]: 409,
3673
- [import_shared18.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
3674
- [import_shared18.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
3675
- [import_shared18.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
3676
- [import_shared18.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
3677
- [import_shared18.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
3678
- [import_shared18.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
3679
- [import_shared18.ERROR_CODES.TOOL_DENIED]: 403,
3680
- [import_shared18.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
3681
- [import_shared18.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
3682
- [import_shared18.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
3683
- [import_shared18.ERROR_CODES.TOOL_PATH_DENIED]: 403,
3684
- [import_shared18.ERROR_CODES.PATCH_REJECTED]: 422,
3685
- [import_shared18.ERROR_CODES.VALIDATION_FAILED]: 422,
3686
- [import_shared18.ERROR_CODES.APPLY_CONFLICT]: 409,
3687
- [import_shared18.ERROR_CODES.INTERNAL_ERROR]: 500
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
- [import_shared18.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
3691
- [import_shared18.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
3692
- [import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
3693
- [import_shared18.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
3694
- [import_shared18.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
3695
- [import_shared18.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
3696
- [import_shared18.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
3697
- [import_shared18.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
3698
- [import_shared18.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
3699
- [import_shared18.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
3700
- [import_shared18.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
3701
- [import_shared18.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
3702
- [import_shared18.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
3703
- [import_shared18.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
3704
- [import_shared18.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
3705
- [import_shared18.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
3706
- [import_shared18.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
3707
- [import_shared18.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
3708
- [import_shared18.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
3709
- [import_shared18.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
3710
- [import_shared18.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
3711
- [import_shared18.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
3712
- [import_shared18.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
3713
- [import_shared18.ERROR_CODES.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
3714
- [import_shared18.ERROR_CODES.HANDOFF_NOT_FOUND]: "No current handoff is available.",
3715
- [import_shared18.ERROR_CODES.HANDOFF_EXPIRED]: "The handoff has expired.",
3716
- [import_shared18.ERROR_CODES.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
3717
- [import_shared18.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
3718
- [import_shared18.ERROR_CODES.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
3719
- [import_shared18.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
3720
- [import_shared18.ERROR_CODES.BRIDGE_BUSY]: "The local bridge is busy.",
3721
- [import_shared18.ERROR_CODES.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
3722
- [import_shared18.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
3723
- [import_shared18.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
3724
- [import_shared18.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
3725
- [import_shared18.ERROR_CODES.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
3726
- [import_shared18.ERROR_CODES.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
3727
- [import_shared18.ERROR_CODES.SESSION_CLOSED]: "The SpotPatch session has closed.",
3728
- [import_shared18.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
3729
- [import_shared18.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
3730
- [import_shared18.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
3731
- [import_shared18.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
3732
- [import_shared18.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
3733
- [import_shared18.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
3734
- [import_shared18.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
3735
- [import_shared18.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
3736
- [import_shared18.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
3737
- [import_shared18.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
3738
- [import_shared18.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
3739
- [import_shared18.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
3740
- [import_shared18.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
3741
- [import_shared18.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
3742
- [import_shared18.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
3743
- [import_shared18.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
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 import_shared18.SpotPatchError ? error : new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
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 === import_shared18.ERROR_CODES.INTERNAL_ERROR) {
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 = import_shared18.sourceContextRequestSchema.safeParse(
3938
+ const parsed = import_shared20.sourceContextRequestSchema.safeParse(
3776
3939
  await readJsonRequestBody(request)
3777
3940
  );
3778
3941
  if (!parsed.success) {
3779
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
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 = import_shared18.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3953
+ const parsed = import_shared20.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3791
3954
  if (!parsed.success) {
3792
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
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 import_shared18.SpotPatchError(import_shared18.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
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
- return (request, response, next) => {
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 !== import_shared18.SPOTPATCH_ENDPOINTS.sourceContext && path9 !== import_shared18.SPOTPATCH_ENDPOINTS.openEditor && path9 !== import_shared18.SPOTPATCH_ENDPOINTS.dataFlowComponentReport && path9 !== import_shared18.SPOTPATCH_ENDPOINTS.dataFlowPageReport && agentRoute === void 0 && externalHandoffRoute === void 0 && !path9.startsWith(`${import_shared18.SPOTPATCH_API_BASE}/`)) {
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 === import_shared18.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
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 === import_shared18.SPOTPATCH_ENDPOINTS.sourceContext) {
4003
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.sourceContext) {
3839
4004
  if (request.method !== "POST") {
3840
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
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 === import_shared18.SPOTPATCH_ENDPOINTS.openEditor) {
4011
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.openEditor) {
3847
4012
  if (request.method !== "POST") {
3848
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
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 === import_shared18.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
4019
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
3855
4020
  if (request.method !== "POST") {
3856
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
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 === import_shared18.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
4031
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
3867
4032
  if (request.method !== "POST") {
3868
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
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 import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
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 = 4096;
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
- writeJson3(response, 200, {
4013
- epoch: input.registryEpoch,
4014
- fileId: input.registry.register(sourcePath)
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
  });