@spotpatch/dev-server 0.6.0 → 0.7.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,
@@ -2473,20 +2489,20 @@ function createSourceRegistry(options = {}) {
2473
2489
  }
2474
2490
 
2475
2491
  // src/server/middleware.ts
2476
- var import_shared18 = require("@spotpatch/shared");
2492
+ var import_shared20 = require("@spotpatch/shared");
2477
2493
 
2478
2494
  // src/external-handoff/browser-http.ts
2479
- var import_shared12 = require("@spotpatch/shared");
2495
+ var import_shared13 = require("@spotpatch/shared");
2480
2496
 
2481
2497
  // src/server/annotation-authorizer.ts
2482
2498
  var import_promises6 = require("fs/promises");
2483
2499
  var import_node_path7 = __toESM(require("path"), 1);
2484
- var import_shared11 = require("@spotpatch/shared");
2500
+ var import_shared12 = require("@spotpatch/shared");
2485
2501
 
2486
2502
  // src/server/source-context.ts
2487
2503
  var import_promises5 = require("fs/promises");
2488
2504
  var import_node_path6 = __toESM(require("path"), 1);
2489
- var import_shared10 = require("@spotpatch/shared");
2505
+ var import_shared11 = require("@spotpatch/shared");
2490
2506
 
2491
2507
  // src/server/extract-code-context.ts
2492
2508
  var import_oxc_parser = require("oxc-parser");
@@ -2717,7 +2733,7 @@ function extractCodeContext(options) {
2717
2733
  // src/server/source-file.ts
2718
2734
  var import_promises4 = require("fs/promises");
2719
2735
  var import_node_path5 = __toESM(require("path"), 1);
2720
- var import_shared9 = require("@spotpatch/shared");
2736
+ var import_shared10 = require("@spotpatch/shared");
2721
2737
  var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
2722
2738
  function isMissingFileError(error) {
2723
2739
  return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
@@ -2732,7 +2748,7 @@ async function assertInsideRoot(root, candidate) {
2732
2748
  ]);
2733
2749
  } catch (error) {
2734
2750
  if (isMissingFileError(error)) {
2735
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2751
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2736
2752
  cause: error
2737
2753
  });
2738
2754
  }
@@ -2741,35 +2757,35 @@ async function assertInsideRoot(root, candidate) {
2741
2757
  const relative = import_node_path5.default.relative(realRoot, realCandidate);
2742
2758
  const outside = relative.startsWith(`..${import_node_path5.default.sep}`) || relative === ".." || import_node_path5.default.isAbsolute(relative);
2743
2759
  if (outside) {
2744
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
2760
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
2745
2761
  }
2746
2762
  return realCandidate;
2747
2763
  }
2748
2764
  async function resolveSourceFile(options) {
2749
2765
  const registeredPath = options.registry.resolve(options.fileId);
2750
2766
  if (registeredPath === void 0) {
2751
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
2767
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
2752
2768
  }
2753
2769
  const sourcePath = await assertInsideRoot(options.root, registeredPath);
2754
2770
  if (!ALLOWED_EXTENSIONS.has(import_node_path5.default.extname(sourcePath).toLowerCase())) {
2755
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
2771
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
2756
2772
  }
2757
2773
  let sourceStat;
2758
2774
  try {
2759
2775
  sourceStat = await (0, import_promises4.stat)(sourcePath);
2760
2776
  } catch (error) {
2761
2777
  if (isMissingFileError(error)) {
2762
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2778
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2763
2779
  cause: error
2764
2780
  });
2765
2781
  }
2766
2782
  throw error;
2767
2783
  }
2768
2784
  if (!sourceStat.isFile()) {
2769
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
2785
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
2770
2786
  }
2771
2787
  if (sourceStat.size > MAX_SOURCE_FILE_BYTES) {
2772
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_TOO_LARGE);
2788
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_TOO_LARGE);
2773
2789
  }
2774
2790
  return sourcePath;
2775
2791
  }
@@ -2789,7 +2805,7 @@ async function readSourceContext(options) {
2789
2805
  source = await (0, import_promises5.readFile)(sourcePath, "utf8");
2790
2806
  } catch (error) {
2791
2807
  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, {
2808
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2793
2809
  cause: error
2794
2810
  });
2795
2811
  }
@@ -2797,7 +2813,7 @@ async function readSourceContext(options) {
2797
2813
  }
2798
2814
  const lines = source.split(/\r?\n/);
2799
2815
  if (options.request.line > lines.length) {
2800
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
2816
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
2801
2817
  }
2802
2818
  const extension = import_node_path6.default.extname(sourcePath).toLowerCase();
2803
2819
  return extractCodeContext({
@@ -2815,9 +2831,9 @@ async function readSourceContext(options) {
2815
2831
  // src/server/annotation-authorizer.ts
2816
2832
  function sanitizePageContext(page) {
2817
2833
  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),
2834
+ url: (0, import_shared12.sanitizeUrl)(page.url, page.url),
2835
+ pathname: (0, import_shared12.redactSensitiveText)(page.pathname),
2836
+ title: (0, import_shared12.redactSensitiveText)(page.title),
2821
2837
  viewportWidth: page.viewportWidth,
2822
2838
  viewportHeight: page.viewportHeight,
2823
2839
  devicePixelRatio: page.devicePixelRatio
@@ -2836,18 +2852,18 @@ function compactSourceRef(source) {
2836
2852
  async function authorizeSourceRef(source, registry, root) {
2837
2853
  const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
2838
2854
  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);
2855
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2840
2856
  }
2841
2857
  if (source.fileId === void 0) {
2842
2858
  if (source.origin === "none" && source.relativePath !== void 0) {
2843
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
2859
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2844
2860
  }
2845
2861
  return compactSourceRef(source);
2846
2862
  }
2847
2863
  const sourcePath = await resolveSourceFile({ fileId: source.fileId, registry, root });
2848
2864
  const relativePath = import_node_path7.default.relative(await (0, import_promises6.realpath)(root), sourcePath).split(import_node_path7.default.sep).join("/");
2849
2865
  if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
2850
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
2866
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2851
2867
  }
2852
2868
  return Object.freeze({ ...compactSourceRef(source), relativePath });
2853
2869
  }
@@ -2883,7 +2899,7 @@ async function authorizeTarget(target, input) {
2883
2899
  maxLines: input.options.budget.maxCodeLines
2884
2900
  });
2885
2901
  if (marker === void 0 && target.code !== void 0) {
2886
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
2902
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2887
2903
  }
2888
2904
  const code = marker === void 0 ? void 0 : await readSourceContext({
2889
2905
  request: marker,
@@ -2893,7 +2909,7 @@ async function authorizeTarget(target, input) {
2893
2909
  maxLines: input.options.budget.maxCodeLines
2894
2910
  });
2895
2911
  if (target.code !== void 0 && target.code.relativePath !== code?.relativePath) {
2896
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
2912
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2897
2913
  }
2898
2914
  return Object.freeze({
2899
2915
  instruction: target.instruction,
@@ -2928,11 +2944,11 @@ async function authorizeTarget(target, input) {
2928
2944
  async function authorizeAnnotation(input) {
2929
2945
  const requestedTargets = input.annotation.targets;
2930
2946
  if (requestedTargets.length > input.options.maxTargets) {
2931
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
2947
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2932
2948
  }
2933
2949
  const identities = requestedTargets.map(targetIdentity);
2934
2950
  if (new Set(identities).size !== identities.length) {
2935
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
2951
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2936
2952
  }
2937
2953
  const targets = Object.freeze(
2938
2954
  await Promise.all(requestedTargets.map((target) => authorizeTarget(target, input)))
@@ -2949,29 +2965,29 @@ async function authorizeAnnotation(input) {
2949
2965
 
2950
2966
  // src/external-handoff/browser-http.ts
2951
2967
  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) {
2968
+ if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffCapability) return "capability";
2969
+ if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffPublish) return "publish";
2970
+ if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffStatus) return "status";
2971
+ if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery) {
2956
2972
  return "resolve-delivery";
2957
2973
  }
2958
2974
  return void 0;
2959
2975
  }
2960
2976
  function requireService(options) {
2961
2977
  if (!options.options.externalAgent.enabled || options.service === void 0) {
2962
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
2978
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
2963
2979
  }
2964
2980
  return options.service;
2965
2981
  }
2966
2982
  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, {
2983
+ if (error instanceof import_shared13.SpotPatchError) {
2984
+ 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) {
2985
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_SOURCE_STALE, void 0, {
2970
2986
  cause: error
2971
2987
  });
2972
2988
  }
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, {
2989
+ if (error.code === import_shared13.ERROR_CODES.INVALID_REQUEST) {
2990
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_VALIDATION_FAILED, void 0, {
2975
2991
  cause: error
2976
2992
  });
2977
2993
  }
@@ -2980,38 +2996,38 @@ function remapAuthorizationError(error) {
2980
2996
  }
2981
2997
  async function handleExternalHandoffBrowserRequest(request, response, route, options, writeSuccess) {
2982
2998
  if (request.method !== "POST") {
2983
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2999
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
2984
3000
  }
2985
3001
  const service = requireService(options);
2986
3002
  if (route === "capability") {
2987
- const parsed2 = import_shared12.externalHandoffCapabilityRequestSchema.safeParse(
3003
+ const parsed2 = import_shared13.externalHandoffCapabilityRequestSchema.safeParse(
2988
3004
  await readJsonRequestBody(request)
2989
3005
  );
2990
- if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
3006
+ if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
2991
3007
  writeSuccess(response, 200, service.capability());
2992
3008
  return;
2993
3009
  }
2994
3010
  if (route === "status") {
2995
- const parsed2 = import_shared12.externalHandoffStatusRequestSchema.safeParse(
3011
+ const parsed2 = import_shared13.externalHandoffStatusRequestSchema.safeParse(
2996
3012
  await readJsonRequestBody(request)
2997
3013
  );
2998
- if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
3014
+ if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
2999
3015
  writeSuccess(response, 200, service.status(parsed2.data.cursor));
3000
3016
  return;
3001
3017
  }
3002
3018
  if (route === "resolve-delivery") {
3003
- const parsed2 = import_shared12.externalHandoffResolveDeliveryRequestSchema.safeParse(
3019
+ const parsed2 = import_shared13.externalHandoffResolveDeliveryRequestSchema.safeParse(
3004
3020
  await readJsonRequestBody(request)
3005
3021
  );
3006
- if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
3022
+ if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
3007
3023
  writeSuccess(response, 200, service.resolveDelivery(parsed2.data.cursor));
3008
3024
  return;
3009
3025
  }
3010
- const parsed = import_shared12.externalHandoffPublishRequestSchema.safeParse(
3011
- await readJsonRequestBody(request, import_shared12.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
3026
+ const parsed = import_shared13.externalHandoffPublishRequestSchema.safeParse(
3027
+ await readJsonRequestBody(request, import_shared13.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
3012
3028
  );
3013
3029
  if (!parsed.success) {
3014
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
3030
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
3015
3031
  }
3016
3032
  const result = await service.publish(parsed.data, async (annotation) => {
3017
3033
  try {
@@ -3028,11 +3044,157 @@ async function handleExternalHandoffBrowserRequest(request, response, route, opt
3028
3044
  writeSuccess(response, result.replayed ? 200 : 201, result);
3029
3045
  }
3030
3046
 
3031
- // src/server/agent-http.ts
3047
+ // src/external-agent/browser-http.ts
3032
3048
  var import_shared14 = require("@spotpatch/shared");
3049
+ function matchExternalAgentBrowserPath(path9) {
3050
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlStatus) return "status";
3051
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlConnect) return "connect";
3052
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlDisconnect) {
3053
+ return "disconnect";
3054
+ }
3055
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlCancel) return "cancel";
3056
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentEvents) return "events";
3057
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentResult) return "result";
3058
+ return void 0;
3059
+ }
3060
+ function writeEvent(response, event) {
3061
+ response.write(`${JSON.stringify(import_shared14.externalAgentEventSchema.parse(event))}
3062
+ `);
3063
+ }
3064
+ function createExternalAgentBrowserController(port) {
3065
+ const streams = /* @__PURE__ */ new Set();
3066
+ let disposed = false;
3067
+ const handleEvents = async (request, response) => {
3068
+ const parsed = import_shared14.externalAgentEventsRequestSchema.safeParse(
3069
+ await readJsonRequestBody(request)
3070
+ );
3071
+ if (!parsed.success) {
3072
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3073
+ }
3074
+ if (disposed || streams.size >= import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
3075
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.BRIDGE_BUSY);
3076
+ }
3077
+ response.statusCode = 200;
3078
+ response.setHeader("Cache-Control", "no-store");
3079
+ response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
3080
+ response.setHeader("X-Content-Type-Options", "nosniff");
3081
+ streams.add(response);
3082
+ const initial = import_shared14.externalAgentControlStatusSchema.parse(port.getStatus());
3083
+ writeEvent(response, { type: "status", data: initial });
3084
+ const unsubscribe = port.subscribe((status) => {
3085
+ if (!response.destroyed && status.sequence > (parsed.data.afterSequence ?? -1)) {
3086
+ writeEvent(response, { type: "status", data: status });
3087
+ }
3088
+ });
3089
+ const heartbeat = setInterval(() => {
3090
+ if (!response.destroyed) {
3091
+ const status = port.getStatus();
3092
+ writeEvent(response, {
3093
+ type: "heartbeat",
3094
+ sequence: status.sequence,
3095
+ emittedAt: (/* @__PURE__ */ new Date()).toISOString()
3096
+ });
3097
+ }
3098
+ }, import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
3099
+ heartbeat.unref();
3100
+ await new Promise((resolve) => {
3101
+ const close = () => {
3102
+ response.off("close", close);
3103
+ clearInterval(heartbeat);
3104
+ unsubscribe();
3105
+ streams.delete(response);
3106
+ resolve();
3107
+ };
3108
+ response.once("close", close);
3109
+ });
3110
+ };
3111
+ return Object.freeze({
3112
+ async handle(request, response, route, writeSuccess) {
3113
+ if (request.method !== "POST" || disposed) {
3114
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3115
+ }
3116
+ if (route === "events") {
3117
+ await handleEvents(request, response);
3118
+ return;
3119
+ }
3120
+ if (route === "status") {
3121
+ const parsed2 = import_shared14.externalAgentControlStatusRequestSchema.safeParse(
3122
+ await readJsonRequestBody(request)
3123
+ );
3124
+ if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3125
+ writeSuccess(
3126
+ response,
3127
+ 200,
3128
+ import_shared14.externalAgentControlStatusSchema.parse(port.getStatus())
3129
+ );
3130
+ return;
3131
+ }
3132
+ if (route === "connect") {
3133
+ const parsed2 = import_shared14.externalAgentControlConnectRequestSchema.safeParse(
3134
+ await readJsonRequestBody(request)
3135
+ );
3136
+ if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3137
+ const controller = new AbortController();
3138
+ response.once("close", () => {
3139
+ if (!response.writableEnded) controller.abort("browser-disconnected");
3140
+ });
3141
+ writeSuccess(
3142
+ response,
3143
+ 200,
3144
+ import_shared14.externalAgentControlStatusSchema.parse(
3145
+ await port.connect(parsed2.data, controller.signal)
3146
+ )
3147
+ );
3148
+ return;
3149
+ }
3150
+ if (route === "disconnect") {
3151
+ const parsed2 = import_shared14.externalAgentControlDisconnectRequestSchema.safeParse(
3152
+ await readJsonRequestBody(request)
3153
+ );
3154
+ if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3155
+ writeSuccess(
3156
+ response,
3157
+ 200,
3158
+ import_shared14.externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
3159
+ );
3160
+ return;
3161
+ }
3162
+ if (route === "cancel") {
3163
+ const parsed2 = import_shared14.externalAgentControlCancelRequestSchema.safeParse(
3164
+ await readJsonRequestBody(request)
3165
+ );
3166
+ if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3167
+ writeSuccess(
3168
+ response,
3169
+ 200,
3170
+ import_shared14.externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
3171
+ );
3172
+ return;
3173
+ }
3174
+ const parsed = import_shared14.externalAgentResultRequestSchema.safeParse(
3175
+ await readJsonRequestBody(request)
3176
+ );
3177
+ if (!parsed.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3178
+ const result = port.getResult(parsed.data.revision);
3179
+ if (result === void 0) {
3180
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.HANDOFF_NOT_FOUND);
3181
+ }
3182
+ writeSuccess(response, 200, import_shared14.externalAgentManagedResultSchema.parse(result));
3183
+ },
3184
+ dispose() {
3185
+ if (disposed) return;
3186
+ disposed = true;
3187
+ for (const response of streams) response.end();
3188
+ streams.clear();
3189
+ }
3190
+ });
3191
+ }
3192
+
3193
+ // src/server/agent-http.ts
3194
+ var import_shared16 = require("@spotpatch/shared");
3033
3195
 
3034
3196
  // src/server/agent-request.ts
3035
- var import_shared13 = require("@spotpatch/shared");
3197
+ var import_shared15 = require("@spotpatch/shared");
3036
3198
  async function authorizeAgentJobRequest(input) {
3037
3199
  const annotation = await authorizeAnnotation({
3038
3200
  annotation: input.request.annotation,
@@ -3069,16 +3231,16 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
3069
3231
  "failed"
3070
3232
  ]);
3071
3233
  function matchAgentRequestPath(path9) {
3072
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.agentCapability) {
3234
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentCapability) {
3073
3235
  return Object.freeze({ kind: "capability" });
3074
3236
  }
3075
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
3237
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
3076
3238
  return Object.freeze({ kind: "workspace-health" });
3077
3239
  }
3078
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.agentJobs) {
3240
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentJobs) {
3079
3241
  return Object.freeze({ kind: "create-job" });
3080
3242
  }
3081
- const prefix = `${import_shared14.SPOTPATCH_ENDPOINTS.agentJobs}/`;
3243
+ const prefix = `${import_shared16.SPOTPATCH_ENDPOINTS.agentJobs}/`;
3082
3244
  if (!path9.startsWith(prefix)) {
3083
3245
  return void 0;
3084
3246
  }
@@ -3096,7 +3258,7 @@ function matchAgentRequestPath(path9) {
3096
3258
  }
3097
3259
  function requireAgentManager(options) {
3098
3260
  if (options.agentManager === void 0 || options.options.ai === false) {
3099
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.AI_DISABLED);
3261
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.AI_DISABLED);
3100
3262
  }
3101
3263
  return options.agentManager;
3102
3264
  }
@@ -3149,13 +3311,13 @@ function streamAgentJobEvents(response, manager, jobId) {
3149
3311
  }
3150
3312
  async function handleCapability(request, response, options, writeSuccess) {
3151
3313
  if (request.method !== "POST") {
3152
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3314
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3153
3315
  }
3154
- const parsed = import_shared14.agentCapabilityRequestSchema.safeParse(
3316
+ const parsed = import_shared16.agentCapabilityRequestSchema.safeParse(
3155
3317
  await readJsonRequestBody(request)
3156
3318
  );
3157
3319
  if (!parsed.success) {
3158
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3320
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3159
3321
  }
3160
3322
  const controller = new AbortController();
3161
3323
  const abort = () => {
@@ -3174,13 +3336,13 @@ async function handleCapability(request, response, options, writeSuccess) {
3174
3336
  }
3175
3337
  async function handleCreateJob(request, response, options, writeSuccess) {
3176
3338
  if (request.method !== "POST") {
3177
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3339
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3178
3340
  }
3179
- const parsed = import_shared14.agentJobCreateRequestSchema.safeParse(
3341
+ const parsed = import_shared16.agentJobCreateRequestSchema.safeParse(
3180
3342
  await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
3181
3343
  );
3182
3344
  if (!parsed.success) {
3183
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3345
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3184
3346
  }
3185
3347
  const authorizedRequest = await authorizeAgentJobRequest({
3186
3348
  request: parsed.data,
@@ -3193,13 +3355,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
3193
3355
  }
3194
3356
  async function handleWorkspaceHealth(request, response, options, writeSuccess) {
3195
3357
  if (request.method !== "POST") {
3196
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3358
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3197
3359
  }
3198
- const parsed = import_shared14.agentWorkspaceHealthRequestSchema.safeParse(
3360
+ const parsed = import_shared16.agentWorkspaceHealthRequestSchema.safeParse(
3199
3361
  await readJsonRequestBody(request)
3200
3362
  );
3201
3363
  if (!parsed.success) {
3202
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3364
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3203
3365
  }
3204
3366
  const controller = new AbortController();
3205
3367
  const abort = () => {
@@ -3216,13 +3378,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
3216
3378
  async function handleJobAction(request, response, options, route, writeSuccess) {
3217
3379
  const manager = requireAgentManager(options);
3218
3380
  if (request.method !== "POST") {
3219
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3381
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3220
3382
  }
3221
- const parsed = import_shared14.agentJobActionRequestSchema.safeParse(
3383
+ const parsed = import_shared16.agentJobActionRequestSchema.safeParse(
3222
3384
  await readJsonRequestBody(request)
3223
3385
  );
3224
3386
  if (!parsed.success) {
3225
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3387
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3226
3388
  }
3227
3389
  if (route.action === "events") {
3228
3390
  streamAgentJobEvents(response, manager, route.jobId);
@@ -3339,25 +3501,25 @@ var launchConfiguredEditor = createEditorLauncher();
3339
3501
  // src/server/data-flow-http.ts
3340
3502
  var import_node_crypto9 = require("crypto");
3341
3503
  var import_analyzer = require("@spotpatch/analyzer");
3342
- var import_shared15 = require("@spotpatch/shared");
3504
+ var import_shared17 = require("@spotpatch/shared");
3343
3505
  function envelopeBytes(report) {
3344
3506
  return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
3345
3507
  }
3346
3508
  function limitDataFlowReportToBytes(report, maximumBytes) {
3347
- const structurallyLimited = (0, import_shared15.limitDataFlowReportCollections)(report);
3509
+ const structurallyLimited = (0, import_shared17.limitDataFlowReportCollections)(report);
3348
3510
  if (envelopeBytes(structurallyLimited) <= maximumBytes) {
3349
3511
  return structurallyLimited;
3350
3512
  }
3351
- let limited = (0, import_shared15.limitDataFlowReportCollections)(structurallyLimited, {
3513
+ let limited = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
3352
3514
  forceTruncation: true,
3353
3515
  maximumDependencies: 0,
3354
3516
  truncatedBy: "bytes"
3355
3517
  });
3356
3518
  if (envelopeBytes(limited) > maximumBytes) {
3357
- throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INTERNAL_ERROR);
3519
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INTERNAL_ERROR);
3358
3520
  }
3359
3521
  for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
3360
- const candidate = (0, import_shared15.limitDataFlowReportCollections)(structurallyLimited, {
3522
+ const candidate = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
3361
3523
  forceTruncation: true,
3362
3524
  maximumDependencies,
3363
3525
  truncatedBy: "bytes"
@@ -3383,7 +3545,7 @@ async function analyzeTarget(request, analyzer, options) {
3383
3545
  request.componentSourceId
3384
3546
  );
3385
3547
  if (anchor?.sourceVersion !== request.sourceVersion) {
3386
- throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
3548
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
3387
3549
  }
3388
3550
  return anchor;
3389
3551
  }
@@ -3400,7 +3562,7 @@ async function analyzeTarget(request, analyzer, options) {
3400
3562
  column: resolvedRequest.column
3401
3563
  });
3402
3564
  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);
3565
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
3404
3566
  }
3405
3567
  return limitDataFlowReportToBytes(
3406
3568
  report,
@@ -3409,31 +3571,31 @@ async function analyzeTarget(request, analyzer, options) {
3409
3571
  }
3410
3572
  function requireAnalyzer(analyzer) {
3411
3573
  if (analyzer === void 0) {
3412
- throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.DATA_FLOW_DISABLED);
3574
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_DISABLED);
3413
3575
  }
3414
3576
  return analyzer;
3415
3577
  }
3416
3578
  async function handleComponentDataFlowReport(request, analyzer, options) {
3417
- const parsed = import_shared15.dataFlowComponentReportRequestSchema.safeParse(
3579
+ const parsed = import_shared17.dataFlowComponentReportRequestSchema.safeParse(
3418
3580
  await readJsonRequestBody(
3419
3581
  request,
3420
3582
  options.options.dataFlow.limits.protocolRequestMaxBytes
3421
3583
  )
3422
3584
  );
3423
3585
  if (!parsed.success) {
3424
- throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
3586
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3425
3587
  }
3426
3588
  return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
3427
3589
  }
3428
3590
  async function handlePageDataFlowReport(request, analyzer, options) {
3429
- const parsed = import_shared15.dataFlowPageReportRequestSchema.safeParse(
3591
+ const parsed = import_shared17.dataFlowPageReportRequestSchema.safeParse(
3430
3592
  await readJsonRequestBody(
3431
3593
  request,
3432
3594
  options.options.dataFlow.limits.protocolRequestMaxBytes
3433
3595
  )
3434
3596
  );
3435
3597
  if (!parsed.success) {
3436
- throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
3598
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3437
3599
  }
3438
3600
  const activeAnalyzer = requireAnalyzer(analyzer);
3439
3601
  const componentReports = await Promise.all(
@@ -3460,7 +3622,7 @@ async function handlePageDataFlowReport(request, analyzer, options) {
3460
3622
  const reportId = `page_${(0, import_node_crypto9.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
3461
3623
  const complete = componentReports.every((report2) => report2.completeness.complete);
3462
3624
  const report = Object.freeze({
3463
- schemaVersion: import_shared15.DATA_FLOW_SCHEMA_VERSION,
3625
+ schemaVersion: import_shared17.DATA_FLOW_SCHEMA_VERSION,
3464
3626
  reportId,
3465
3627
  baseline: Object.freeze({
3466
3628
  registryEpoch: options.session.id,
@@ -3506,7 +3668,7 @@ async function handlePageDataFlowReport(request, analyzer, options) {
3506
3668
  // src/server/request-security.ts
3507
3669
  var import_node_crypto10 = require("crypto");
3508
3670
  var import_node_net = require("net");
3509
- var import_shared16 = require("@spotpatch/shared");
3671
+ var import_shared18 = require("@spotpatch/shared");
3510
3672
  function getSingleHeader(request, name) {
3511
3673
  const value = request.headers[name.toLowerCase()];
3512
3674
  return Array.isArray(value) ? value[0] : value;
@@ -3551,32 +3713,32 @@ function parseOrigin(value) {
3551
3713
  }
3552
3714
  }
3553
3715
  function assertRequestAuthorized(request, options) {
3554
- const actualToken = getSingleHeader(request, import_shared16.SPOTPATCH_TOKEN_HEADER);
3716
+ const actualToken = getSingleHeader(request, import_shared18.SPOTPATCH_TOKEN_HEADER);
3555
3717
  if (!tokensMatch2(actualToken, options.sessionToken)) {
3556
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_TOKEN);
3718
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_TOKEN);
3557
3719
  }
3558
3720
  const hostHeader = getSingleHeader(request, "host");
3559
3721
  const originHeader = getSingleHeader(request, "origin");
3560
3722
  const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
3561
3723
  const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
3562
3724
  if (host === void 0 || origin === void 0) {
3563
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3725
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3564
3726
  }
3565
3727
  const hostIsLoopback = isLoopbackHostname(host.hostname);
3566
3728
  const originIsLoopback = isLoopbackHostname(origin.hostname);
3567
3729
  if (!options.allowLan) {
3568
3730
  if (!hostIsLoopback || !originIsLoopback) {
3569
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3731
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3570
3732
  }
3571
3733
  return;
3572
3734
  }
3573
3735
  if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
3574
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3736
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3575
3737
  }
3576
3738
  }
3577
3739
 
3578
3740
  // src/server/runtime-bootstrap.ts
3579
- var import_shared17 = require("@spotpatch/shared");
3741
+ var import_shared19 = require("@spotpatch/shared");
3580
3742
  function getSingleHeader2(request, name) {
3581
3743
  const value = request.headers[name.toLowerCase()];
3582
3744
  return Array.isArray(value) ? value[0] : value;
@@ -3591,7 +3753,7 @@ function resolveRuntimeBootstrapOptions(options) {
3591
3753
  if (expectedOrigin.origin !== options.expectedOrigin || expectedOrigin.protocol !== "http:" || !isLoopbackHostname(expectedOrigin.hostname)) {
3592
3754
  throw new TypeError("The SpotPatch bootstrap origin must be a loopback origin.");
3593
3755
  }
3594
- const parsedConfig = import_shared17.runtimeConfigSchema.safeParse(options.runtimeConfig);
3756
+ const parsedConfig = import_shared19.runtimeConfigSchema.safeParse(options.runtimeConfig);
3595
3757
  if (!parsedConfig.success) {
3596
3758
  throw new TypeError("The SpotPatch Runtime configuration is invalid.");
3597
3759
  }
@@ -3603,7 +3765,7 @@ function resolveRuntimeBootstrapOptions(options) {
3603
3765
  function assertRuntimeBootstrapRequest(request, expectedOrigin) {
3604
3766
  const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
3605
3767
  if (request.method !== "POST" || contentType !== "application/json") {
3606
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3768
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3607
3769
  }
3608
3770
  const host = getSingleHeader2(request, "host");
3609
3771
  let hostIsLoopback = false;
@@ -3615,132 +3777,132 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
3615
3777
  }
3616
3778
  }
3617
3779
  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);
3780
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3619
3781
  }
3620
3782
  }
3621
3783
  async function readRuntimeBootstrap(request, options) {
3622
3784
  assertRuntimeBootstrapRequest(request, options.expectedOrigin);
3623
- const parsedBody = import_shared17.runtimeBootstrapRequestSchema.safeParse(
3785
+ const parsedBody = import_shared19.runtimeBootstrapRequestSchema.safeParse(
3624
3786
  await readJsonRequestBody(request)
3625
3787
  );
3626
3788
  if (!parsedBody.success) {
3627
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3789
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3628
3790
  }
3629
3791
  return options.runtimeConfig;
3630
3792
  }
3631
3793
 
3632
3794
  // src/server/middleware.ts
3633
3795
  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
3796
+ [import_shared20.ERROR_CODES.INVALID_REQUEST]: 400,
3797
+ [import_shared20.ERROR_CODES.INVALID_TOKEN]: 401,
3798
+ [import_shared20.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
3799
+ [import_shared20.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
3800
+ [import_shared20.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
3801
+ [import_shared20.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
3802
+ [import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
3803
+ [import_shared20.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
3804
+ [import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
3805
+ [import_shared20.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
3806
+ [import_shared20.ERROR_CODES.AI_DISABLED]: 404,
3807
+ [import_shared20.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
3808
+ [import_shared20.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
3809
+ [import_shared20.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
3810
+ [import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
3811
+ [import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
3812
+ [import_shared20.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
3813
+ [import_shared20.ERROR_CODES.AGENT_BUSY]: 409,
3814
+ [import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
3815
+ [import_shared20.ERROR_CODES.AGENT_CANCELLED]: 409,
3816
+ [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: 404,
3817
+ [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
3818
+ [import_shared20.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: 422,
3819
+ [import_shared20.ERROR_CODES.HANDOFF_SOURCE_STALE]: 409,
3820
+ [import_shared20.ERROR_CODES.HANDOFF_NOT_FOUND]: 404,
3821
+ [import_shared20.ERROR_CODES.HANDOFF_EXPIRED]: 410,
3822
+ [import_shared20.ERROR_CODES.HANDOFF_CURSOR_INVALID]: 409,
3823
+ [import_shared20.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: 413,
3824
+ [import_shared20.ERROR_CODES.BRIDGE_UNAUTHORIZED]: 401,
3825
+ [import_shared20.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: 409,
3826
+ [import_shared20.ERROR_CODES.BRIDGE_BUSY]: 429,
3827
+ [import_shared20.ERROR_CODES.EXTERNAL_AGENT_BUSY]: 409,
3828
+ [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: 409,
3829
+ [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
3830
+ [import_shared20.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: 409,
3831
+ [import_shared20.ERROR_CODES.SESSION_NOT_FOUND]: 404,
3832
+ [import_shared20.ERROR_CODES.SESSION_AMBIGUOUS]: 409,
3833
+ [import_shared20.ERROR_CODES.SESSION_CLOSED]: 410,
3834
+ [import_shared20.ERROR_CODES.WORKTREE_DIRTY]: 409,
3835
+ [import_shared20.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
3836
+ [import_shared20.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
3837
+ [import_shared20.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
3838
+ [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
3839
+ [import_shared20.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
3840
+ [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
3841
+ [import_shared20.ERROR_CODES.TOOL_DENIED]: 403,
3842
+ [import_shared20.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
3843
+ [import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
3844
+ [import_shared20.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
3845
+ [import_shared20.ERROR_CODES.TOOL_PATH_DENIED]: 403,
3846
+ [import_shared20.ERROR_CODES.PATCH_REJECTED]: 422,
3847
+ [import_shared20.ERROR_CODES.VALIDATION_FAILED]: 422,
3848
+ [import_shared20.ERROR_CODES.APPLY_CONFLICT]: 409,
3849
+ [import_shared20.ERROR_CODES.INTERNAL_ERROR]: 500
3688
3850
  });
3689
3851
  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."
3852
+ [import_shared20.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
3853
+ [import_shared20.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
3854
+ [import_shared20.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
3855
+ [import_shared20.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
3856
+ [import_shared20.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
3857
+ [import_shared20.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
3858
+ [import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
3859
+ [import_shared20.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
3860
+ [import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
3861
+ [import_shared20.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
3862
+ [import_shared20.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
3863
+ [import_shared20.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
3864
+ [import_shared20.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
3865
+ [import_shared20.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
3866
+ [import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
3867
+ [import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
3868
+ [import_shared20.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
3869
+ [import_shared20.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
3870
+ [import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
3871
+ [import_shared20.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
3872
+ [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
3873
+ [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
3874
+ [import_shared20.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
3875
+ [import_shared20.ERROR_CODES.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
3876
+ [import_shared20.ERROR_CODES.HANDOFF_NOT_FOUND]: "No current handoff is available.",
3877
+ [import_shared20.ERROR_CODES.HANDOFF_EXPIRED]: "The handoff has expired.",
3878
+ [import_shared20.ERROR_CODES.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
3879
+ [import_shared20.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
3880
+ [import_shared20.ERROR_CODES.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
3881
+ [import_shared20.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
3882
+ [import_shared20.ERROR_CODES.BRIDGE_BUSY]: "The local bridge is busy.",
3883
+ [import_shared20.ERROR_CODES.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
3884
+ [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
3885
+ [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
3886
+ [import_shared20.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
3887
+ [import_shared20.ERROR_CODES.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
3888
+ [import_shared20.ERROR_CODES.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
3889
+ [import_shared20.ERROR_CODES.SESSION_CLOSED]: "The SpotPatch session has closed.",
3890
+ [import_shared20.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
3891
+ [import_shared20.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
3892
+ [import_shared20.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
3893
+ [import_shared20.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
3894
+ [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
3895
+ [import_shared20.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
3896
+ [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
3897
+ [import_shared20.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
3898
+ [import_shared20.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
3899
+ [import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
3900
+ [import_shared20.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
3901
+ [import_shared20.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
3902
+ [import_shared20.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
3903
+ [import_shared20.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
3904
+ [import_shared20.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
3905
+ [import_shared20.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
3744
3906
  });
3745
3907
  function writeJson2(response, status, payload) {
3746
3908
  response.statusCode = status;
@@ -3749,11 +3911,11 @@ function writeJson2(response, status, payload) {
3749
3911
  response.end(JSON.stringify(payload));
3750
3912
  }
3751
3913
  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 });
3914
+ return error instanceof import_shared20.SpotPatchError ? error : new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
3753
3915
  }
3754
3916
  function writeError(response, error, logger) {
3755
3917
  const normalized = asSpotPatchError(error);
3756
- if (normalized.code === import_shared18.ERROR_CODES.INTERNAL_ERROR) {
3918
+ if (normalized.code === import_shared20.ERROR_CODES.INTERNAL_ERROR) {
3757
3919
  logger?.warn("[spotpatch:server] Internal request failure.");
3758
3920
  }
3759
3921
  writeJson2(response, STATUS_BY_ERROR[normalized.code], {
@@ -3772,11 +3934,11 @@ function requestPath(request) {
3772
3934
  }
3773
3935
  }
3774
3936
  async function handleSourceContext(request, options) {
3775
- const parsed = import_shared18.sourceContextRequestSchema.safeParse(
3937
+ const parsed = import_shared20.sourceContextRequestSchema.safeParse(
3776
3938
  await readJsonRequestBody(request)
3777
3939
  );
3778
3940
  if (!parsed.success) {
3779
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
3941
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3780
3942
  }
3781
3943
  return readSourceContext({
3782
3944
  request: parsed.data,
@@ -3787,9 +3949,9 @@ async function handleSourceContext(request, options) {
3787
3949
  });
3788
3950
  }
3789
3951
  async function handleOpenEditor(request, options) {
3790
- const parsed = import_shared18.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3952
+ const parsed = import_shared20.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3791
3953
  if (!parsed.success) {
3792
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
3954
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3793
3955
  }
3794
3956
  const body = parsed.data;
3795
3957
  const sourcePath = await resolveSourceFile({
@@ -3806,7 +3968,7 @@ async function handleOpenEditor(request, options) {
3806
3968
  options.logger?.warn(
3807
3969
  `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
3808
3970
  );
3809
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
3971
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
3810
3972
  cause: error
3811
3973
  });
3812
3974
  }
@@ -3814,16 +3976,18 @@ async function handleOpenEditor(request, options) {
3814
3976
  function createSpotPatchMiddleware(options) {
3815
3977
  const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
3816
3978
  const dataFlowAnalyzer = createDataFlowAnalyzer(options);
3817
- return (request, response, next) => {
3979
+ const externalAgentController = options.externalAgentControl === void 0 ? void 0 : createExternalAgentBrowserController(options.externalAgentControl);
3980
+ const middleware = (request, response, next) => {
3818
3981
  const path9 = requestPath(request);
3819
3982
  const agentRoute = matchAgentRequestPath(path9);
3983
+ const externalAgentRoute = matchExternalAgentBrowserPath(path9);
3820
3984
  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}/`)) {
3985
+ 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
3986
  next();
3823
3987
  return;
3824
3988
  }
3825
3989
  const handle = async () => {
3826
- if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
3990
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
3827
3991
  const data = await readRuntimeBootstrap(
3828
3992
  request,
3829
3993
  bootstrap
@@ -3835,25 +3999,25 @@ function createSpotPatchMiddleware(options) {
3835
3999
  allowLan: options.options.allowLan,
3836
4000
  sessionToken: options.session.token
3837
4001
  });
3838
- if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.sourceContext) {
4002
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.sourceContext) {
3839
4003
  if (request.method !== "POST") {
3840
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
4004
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3841
4005
  }
3842
4006
  const data = await handleSourceContext(request, options);
3843
4007
  writeJson2(response, 200, { ok: true, data });
3844
4008
  return;
3845
4009
  }
3846
- if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.openEditor) {
4010
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.openEditor) {
3847
4011
  if (request.method !== "POST") {
3848
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
4012
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3849
4013
  }
3850
4014
  const data = await handleOpenEditor(request, options);
3851
4015
  writeJson2(response, 200, { ok: true, data });
3852
4016
  return;
3853
4017
  }
3854
- if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
4018
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
3855
4019
  if (request.method !== "POST") {
3856
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
4020
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3857
4021
  }
3858
4022
  const data = await handleComponentDataFlowReport(
3859
4023
  request,
@@ -3863,14 +4027,28 @@ function createSpotPatchMiddleware(options) {
3863
4027
  writeJson2(response, 200, { ok: true, data });
3864
4028
  return;
3865
4029
  }
3866
- if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
4030
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
3867
4031
  if (request.method !== "POST") {
3868
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
4032
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3869
4033
  }
3870
4034
  const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
3871
4035
  writeJson2(response, 200, { ok: true, data });
3872
4036
  return;
3873
4037
  }
4038
+ if (externalAgentRoute !== void 0) {
4039
+ if (!options.options.externalAgent.enabled || externalAgentController === void 0) {
4040
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
4041
+ }
4042
+ await externalAgentController.handle(
4043
+ request,
4044
+ response,
4045
+ externalAgentRoute,
4046
+ (target, status, data) => {
4047
+ writeJson2(target, status, { ok: true, data });
4048
+ }
4049
+ );
4050
+ return;
4051
+ }
3874
4052
  if (externalHandoffRoute !== void 0) {
3875
4053
  await handleExternalHandoffBrowserRequest(
3876
4054
  request,
@@ -3889,7 +4067,7 @@ function createSpotPatchMiddleware(options) {
3889
4067
  return;
3890
4068
  }
3891
4069
  if (agentRoute === void 0) {
3892
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
4070
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3893
4071
  }
3894
4072
  await handleAgentRequest(
3895
4073
  request,
@@ -3905,6 +4083,11 @@ function createSpotPatchMiddleware(options) {
3905
4083
  writeError(response, error, options.logger);
3906
4084
  });
3907
4085
  };
4086
+ return Object.assign(middleware, {
4087
+ dispose() {
4088
+ externalAgentController?.dispose();
4089
+ }
4090
+ });
3908
4091
  }
3909
4092
 
3910
4093
  // src/server/source-registration.ts
@@ -4237,8 +4420,10 @@ function parseSerializedSpotPatchOptions(value) {
4237
4420
  readRuntimeBootstrap,
4238
4421
  resolveCredentialEnvironment,
4239
4422
  resolveEnvironmentAiConfiguration,
4423
+ resolveManagedExecutionValidation,
4240
4424
  resolveOptions,
4241
4425
  resolveProjectOptions,
4426
+ resolveProjectValidationChecks,
4242
4427
  resolveRuntimeBootstrapOptions,
4243
4428
  serializeResolvedSpotPatchOptions
4244
4429
  });