@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.js CHANGED
@@ -2256,6 +2256,9 @@ import { access as access2, lstat as lstat3, readFile as readFile2, realpath as
2256
2256
  import { createRequire } from "module";
2257
2257
  import path3 from "path";
2258
2258
  import { promisify } from "util";
2259
+ import {
2260
+ DEFAULT_AGENT_LIMITS as DEFAULT_AGENT_LIMITS2
2261
+ } from "@spotpatch/shared";
2259
2262
  var execFileAsync = promisify(execFile);
2260
2263
  var TYPESCRIPT_CHECK_ID = "spotpatch-typecheck";
2261
2264
  var TYPESCRIPT_CHECK_LABEL = "TypeScript";
@@ -2323,6 +2326,15 @@ async function resolveTypeScriptCli(appRoot) {
2323
2326
  function portableRelativePath(from, to) {
2324
2327
  return path3.relative(from, to).split(path3.sep).join("/");
2325
2328
  }
2329
+ function hasRequiredCheck(checks) {
2330
+ return Object.values(checks).some((check) => check.required);
2331
+ }
2332
+ function availableCheckId(checks, preferred) {
2333
+ if (checks[preferred] === void 0) return preferred;
2334
+ let suffix = 2;
2335
+ while (checks[`${preferred}-${String(suffix)}`] !== void 0) suffix += 1;
2336
+ return `${preferred}-${String(suffix)}`;
2337
+ }
2326
2338
  async function discoverProjectValidationCheck(options) {
2327
2339
  const appRoot = await realpath2(options.appRoot);
2328
2340
  const tsconfigPath = path3.join(appRoot, "tsconfig.json");
@@ -2351,6 +2363,8 @@ async function discoverProjectValidationCheck(options) {
2351
2363
  "--noEmit",
2352
2364
  "--pretty",
2353
2365
  "false",
2366
+ "--incremental",
2367
+ "false",
2354
2368
  "--project",
2355
2369
  projectPath
2356
2370
  ]),
@@ -2358,21 +2372,30 @@ async function discoverProjectValidationCheck(options) {
2358
2372
  timeoutMs: options.timeoutMs
2359
2373
  });
2360
2374
  }
2361
-
2362
- // src/project-options.ts
2363
- function hasRequiredCheck(ai) {
2364
- return Object.values(ai.execution.checks).some((check) => check.required);
2375
+ async function resolveProjectValidationChecks(options) {
2376
+ if (hasRequiredCheck(options.checks)) return options.checks;
2377
+ const discovered = await discoverProjectValidationCheck(options);
2378
+ if (discovered === void 0) return options.checks;
2379
+ const id = availableCheckId(options.checks, discovered.id);
2380
+ return Object.freeze({
2381
+ ...options.checks,
2382
+ [id]: Object.freeze({ ...discovered, id })
2383
+ });
2365
2384
  }
2366
- function availableCheckId(checks, preferred) {
2367
- if (checks[preferred] === void 0) {
2368
- return preferred;
2369
- }
2370
- let suffix = 2;
2371
- while (checks[`${preferred}-${String(suffix)}`] !== void 0) {
2372
- suffix += 1;
2373
- }
2374
- return `${preferred}-${String(suffix)}`;
2385
+ async function resolveManagedExecutionValidation(options) {
2386
+ const checks = options.ai === false ? Object.freeze({}) : options.ai.execution.checks;
2387
+ const limits = options.ai === false ? DEFAULT_AGENT_LIMITS2 : options.ai.execution.limits;
2388
+ return Object.freeze({
2389
+ checks: await resolveProjectValidationChecks({
2390
+ appRoot: options.appRoot,
2391
+ checks,
2392
+ timeoutMs: limits.checkTimeoutMs
2393
+ }),
2394
+ limits
2395
+ });
2375
2396
  }
2397
+
2398
+ // src/project-options.ts
2376
2399
  async function resolveProjectOptions(input) {
2377
2400
  const userOptions = input.options ?? {};
2378
2401
  const resolved = resolveOptions(userOptions, input.environmentAi);
@@ -2384,22 +2407,15 @@ async function resolveProjectOptions(input) {
2384
2407
  "SpotPatch trustedFastMode cannot be combined with applyMode auto."
2385
2408
  );
2386
2409
  }
2387
- let checks = resolved.ai.execution.checks;
2388
- if (!hasRequiredCheck(resolved.ai)) {
2389
- const discovered = await discoverProjectValidationCheck({
2390
- appRoot: input.appRoot,
2391
- timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
2392
- });
2393
- if (discovered === void 0) {
2394
- throw new RangeError(
2395
- "SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
2396
- );
2397
- }
2398
- const id = availableCheckId(checks, discovered.id);
2399
- checks = Object.freeze({
2400
- ...checks,
2401
- [id]: Object.freeze({ ...discovered, id })
2402
- });
2410
+ const checks = await resolveProjectValidationChecks({
2411
+ appRoot: input.appRoot,
2412
+ checks: resolved.ai.execution.checks,
2413
+ timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
2414
+ });
2415
+ if (!Object.values(checks).some((check) => check.required)) {
2416
+ throw new RangeError(
2417
+ "SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
2418
+ );
2403
2419
  }
2404
2420
  const ai = Object.freeze({
2405
2421
  ...resolved.ai,
@@ -2462,6 +2478,7 @@ function createSourceRegistry(options = {}) {
2462
2478
  );
2463
2479
  }
2464
2480
  componentIdsByPath.set(normalizedPath, currentIds);
2481
+ return fileId;
2465
2482
  },
2466
2483
  resolve(fileId) {
2467
2484
  return idToPath.get(fileId);
@@ -2480,10 +2497,10 @@ function createSourceRegistry(options = {}) {
2480
2497
 
2481
2498
  // src/server/middleware.ts
2482
2499
  import {
2483
- ERROR_CODES as ERROR_CODES15,
2500
+ ERROR_CODES as ERROR_CODES16,
2484
2501
  SPOTPATCH_API_BASE,
2485
- SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS3,
2486
- SpotPatchError as SpotPatchError15,
2502
+ SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS4,
2503
+ SpotPatchError as SpotPatchError16,
2487
2504
  openEditorRequestSchema,
2488
2505
  sourceContextRequestSchema
2489
2506
  } from "@spotpatch/shared";
@@ -3061,11 +3078,171 @@ async function handleExternalHandoffBrowserRequest(request, response, route, opt
3061
3078
  writeSuccess(response, result.replayed ? 200 : 201, result);
3062
3079
  }
3063
3080
 
3064
- // src/server/agent-http.ts
3081
+ // src/external-agent/browser-http.ts
3065
3082
  import {
3066
3083
  ERROR_CODES as ERROR_CODES11,
3084
+ EXTERNAL_AGENT_CONTROL_LIMITS,
3067
3085
  SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS2,
3068
3086
  SpotPatchError as SpotPatchError11,
3087
+ externalAgentControlCancelRequestSchema,
3088
+ externalAgentControlConnectRequestSchema,
3089
+ externalAgentControlDisconnectRequestSchema,
3090
+ externalAgentControlStatusRequestSchema,
3091
+ externalAgentControlStatusSchema,
3092
+ externalAgentEventSchema,
3093
+ externalAgentEventsRequestSchema,
3094
+ externalAgentManagedResultSchema,
3095
+ externalAgentResultRequestSchema
3096
+ } from "@spotpatch/shared";
3097
+ function matchExternalAgentBrowserPath(path9) {
3098
+ if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentControlStatus) return "status";
3099
+ if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentControlConnect) return "connect";
3100
+ if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentControlDisconnect) {
3101
+ return "disconnect";
3102
+ }
3103
+ if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentControlCancel) return "cancel";
3104
+ if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentEvents) return "events";
3105
+ if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentResult) return "result";
3106
+ return void 0;
3107
+ }
3108
+ function writeEvent(response, event) {
3109
+ response.write(`${JSON.stringify(externalAgentEventSchema.parse(event))}
3110
+ `);
3111
+ }
3112
+ function createExternalAgentBrowserController(port) {
3113
+ const streams = /* @__PURE__ */ new Set();
3114
+ let disposed = false;
3115
+ const handleEvents = async (request, response) => {
3116
+ const parsed = externalAgentEventsRequestSchema.safeParse(
3117
+ await readJsonRequestBody(request)
3118
+ );
3119
+ if (!parsed.success) {
3120
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3121
+ }
3122
+ if (disposed || streams.size >= EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
3123
+ throw new SpotPatchError11(ERROR_CODES11.BRIDGE_BUSY);
3124
+ }
3125
+ response.statusCode = 200;
3126
+ response.setHeader("Cache-Control", "no-store");
3127
+ response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
3128
+ response.setHeader("X-Content-Type-Options", "nosniff");
3129
+ streams.add(response);
3130
+ const initial = externalAgentControlStatusSchema.parse(port.getStatus());
3131
+ writeEvent(response, { type: "status", data: initial });
3132
+ const unsubscribe = port.subscribe((status) => {
3133
+ if (!response.destroyed && status.sequence > (parsed.data.afterSequence ?? -1)) {
3134
+ writeEvent(response, { type: "status", data: status });
3135
+ }
3136
+ });
3137
+ const heartbeat = setInterval(() => {
3138
+ if (!response.destroyed) {
3139
+ const status = port.getStatus();
3140
+ writeEvent(response, {
3141
+ type: "heartbeat",
3142
+ sequence: status.sequence,
3143
+ emittedAt: (/* @__PURE__ */ new Date()).toISOString()
3144
+ });
3145
+ }
3146
+ }, EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
3147
+ heartbeat.unref();
3148
+ await new Promise((resolve) => {
3149
+ const close = () => {
3150
+ response.off("close", close);
3151
+ clearInterval(heartbeat);
3152
+ unsubscribe();
3153
+ streams.delete(response);
3154
+ resolve();
3155
+ };
3156
+ response.once("close", close);
3157
+ });
3158
+ };
3159
+ return Object.freeze({
3160
+ async handle(request, response, route, writeSuccess) {
3161
+ if (request.method !== "POST" || disposed) {
3162
+ throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3163
+ }
3164
+ if (route === "events") {
3165
+ await handleEvents(request, response);
3166
+ return;
3167
+ }
3168
+ if (route === "status") {
3169
+ const parsed2 = externalAgentControlStatusRequestSchema.safeParse(
3170
+ await readJsonRequestBody(request)
3171
+ );
3172
+ if (!parsed2.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3173
+ writeSuccess(
3174
+ response,
3175
+ 200,
3176
+ externalAgentControlStatusSchema.parse(port.getStatus())
3177
+ );
3178
+ return;
3179
+ }
3180
+ if (route === "connect") {
3181
+ const parsed2 = externalAgentControlConnectRequestSchema.safeParse(
3182
+ await readJsonRequestBody(request)
3183
+ );
3184
+ if (!parsed2.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3185
+ const controller = new AbortController();
3186
+ response.once("close", () => {
3187
+ if (!response.writableEnded) controller.abort("browser-disconnected");
3188
+ });
3189
+ writeSuccess(
3190
+ response,
3191
+ 200,
3192
+ externalAgentControlStatusSchema.parse(
3193
+ await port.connect(parsed2.data, controller.signal)
3194
+ )
3195
+ );
3196
+ return;
3197
+ }
3198
+ if (route === "disconnect") {
3199
+ const parsed2 = externalAgentControlDisconnectRequestSchema.safeParse(
3200
+ await readJsonRequestBody(request)
3201
+ );
3202
+ if (!parsed2.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3203
+ writeSuccess(
3204
+ response,
3205
+ 200,
3206
+ externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
3207
+ );
3208
+ return;
3209
+ }
3210
+ if (route === "cancel") {
3211
+ const parsed2 = externalAgentControlCancelRequestSchema.safeParse(
3212
+ await readJsonRequestBody(request)
3213
+ );
3214
+ if (!parsed2.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3215
+ writeSuccess(
3216
+ response,
3217
+ 200,
3218
+ externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
3219
+ );
3220
+ return;
3221
+ }
3222
+ const parsed = externalAgentResultRequestSchema.safeParse(
3223
+ await readJsonRequestBody(request)
3224
+ );
3225
+ if (!parsed.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3226
+ const result = port.getResult(parsed.data.revision);
3227
+ if (result === void 0) {
3228
+ throw new SpotPatchError11(ERROR_CODES11.HANDOFF_NOT_FOUND);
3229
+ }
3230
+ writeSuccess(response, 200, externalAgentManagedResultSchema.parse(result));
3231
+ },
3232
+ dispose() {
3233
+ if (disposed) return;
3234
+ disposed = true;
3235
+ for (const response of streams) response.end();
3236
+ streams.clear();
3237
+ }
3238
+ });
3239
+ }
3240
+
3241
+ // src/server/agent-http.ts
3242
+ import {
3243
+ ERROR_CODES as ERROR_CODES12,
3244
+ SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS3,
3245
+ SpotPatchError as SpotPatchError12,
3069
3246
  agentCapabilityRequestSchema,
3070
3247
  agentJobActionRequestSchema,
3071
3248
  agentJobCreateRequestSchema,
@@ -3110,16 +3287,16 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
3110
3287
  "failed"
3111
3288
  ]);
3112
3289
  function matchAgentRequestPath(path9) {
3113
- if (path9 === SPOTPATCH_ENDPOINTS2.agentCapability) {
3290
+ if (path9 === SPOTPATCH_ENDPOINTS3.agentCapability) {
3114
3291
  return Object.freeze({ kind: "capability" });
3115
3292
  }
3116
- if (path9 === SPOTPATCH_ENDPOINTS2.agentWorkspaceHealth) {
3293
+ if (path9 === SPOTPATCH_ENDPOINTS3.agentWorkspaceHealth) {
3117
3294
  return Object.freeze({ kind: "workspace-health" });
3118
3295
  }
3119
- if (path9 === SPOTPATCH_ENDPOINTS2.agentJobs) {
3296
+ if (path9 === SPOTPATCH_ENDPOINTS3.agentJobs) {
3120
3297
  return Object.freeze({ kind: "create-job" });
3121
3298
  }
3122
- const prefix = `${SPOTPATCH_ENDPOINTS2.agentJobs}/`;
3299
+ const prefix = `${SPOTPATCH_ENDPOINTS3.agentJobs}/`;
3123
3300
  if (!path9.startsWith(prefix)) {
3124
3301
  return void 0;
3125
3302
  }
@@ -3137,7 +3314,7 @@ function matchAgentRequestPath(path9) {
3137
3314
  }
3138
3315
  function requireAgentManager(options) {
3139
3316
  if (options.agentManager === void 0 || options.options.ai === false) {
3140
- throw new SpotPatchError11(ERROR_CODES11.AI_DISABLED);
3317
+ throw new SpotPatchError12(ERROR_CODES12.AI_DISABLED);
3141
3318
  }
3142
3319
  return options.agentManager;
3143
3320
  }
@@ -3190,13 +3367,13 @@ function streamAgentJobEvents(response, manager, jobId) {
3190
3367
  }
3191
3368
  async function handleCapability(request, response, options, writeSuccess) {
3192
3369
  if (request.method !== "POST") {
3193
- throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3370
+ throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3194
3371
  }
3195
3372
  const parsed = agentCapabilityRequestSchema.safeParse(
3196
3373
  await readJsonRequestBody(request)
3197
3374
  );
3198
3375
  if (!parsed.success) {
3199
- throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3376
+ throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3200
3377
  }
3201
3378
  const controller = new AbortController();
3202
3379
  const abort = () => {
@@ -3215,13 +3392,13 @@ async function handleCapability(request, response, options, writeSuccess) {
3215
3392
  }
3216
3393
  async function handleCreateJob(request, response, options, writeSuccess) {
3217
3394
  if (request.method !== "POST") {
3218
- throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3395
+ throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3219
3396
  }
3220
3397
  const parsed = agentJobCreateRequestSchema.safeParse(
3221
3398
  await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
3222
3399
  );
3223
3400
  if (!parsed.success) {
3224
- throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3401
+ throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3225
3402
  }
3226
3403
  const authorizedRequest = await authorizeAgentJobRequest({
3227
3404
  request: parsed.data,
@@ -3234,13 +3411,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
3234
3411
  }
3235
3412
  async function handleWorkspaceHealth(request, response, options, writeSuccess) {
3236
3413
  if (request.method !== "POST") {
3237
- throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3414
+ throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3238
3415
  }
3239
3416
  const parsed = agentWorkspaceHealthRequestSchema.safeParse(
3240
3417
  await readJsonRequestBody(request)
3241
3418
  );
3242
3419
  if (!parsed.success) {
3243
- throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3420
+ throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3244
3421
  }
3245
3422
  const controller = new AbortController();
3246
3423
  const abort = () => {
@@ -3257,13 +3434,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
3257
3434
  async function handleJobAction(request, response, options, route, writeSuccess) {
3258
3435
  const manager = requireAgentManager(options);
3259
3436
  if (request.method !== "POST") {
3260
- throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3437
+ throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3261
3438
  }
3262
3439
  const parsed = agentJobActionRequestSchema.safeParse(
3263
3440
  await readJsonRequestBody(request)
3264
3441
  );
3265
3442
  if (!parsed.success) {
3266
- throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
3443
+ throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3267
3444
  }
3268
3445
  if (route.action === "events") {
3269
3446
  streamAgentJobEvents(response, manager, route.jobId);
@@ -3384,8 +3561,8 @@ import {
3384
3561
  } from "@spotpatch/analyzer";
3385
3562
  import {
3386
3563
  DATA_FLOW_SCHEMA_VERSION,
3387
- ERROR_CODES as ERROR_CODES12,
3388
- SpotPatchError as SpotPatchError12,
3564
+ ERROR_CODES as ERROR_CODES13,
3565
+ SpotPatchError as SpotPatchError13,
3389
3566
  dataFlowComponentReportRequestSchema,
3390
3567
  dataFlowPageReportRequestSchema,
3391
3568
  limitDataFlowReportCollections
@@ -3404,7 +3581,7 @@ function limitDataFlowReportToBytes(report, maximumBytes) {
3404
3581
  truncatedBy: "bytes"
3405
3582
  });
3406
3583
  if (envelopeBytes(limited) > maximumBytes) {
3407
- throw new SpotPatchError12(ERROR_CODES12.INTERNAL_ERROR);
3584
+ throw new SpotPatchError13(ERROR_CODES13.INTERNAL_ERROR);
3408
3585
  }
3409
3586
  for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
3410
3587
  const candidate = limitDataFlowReportCollections(structurallyLimited, {
@@ -3433,7 +3610,7 @@ async function analyzeTarget(request, analyzer, options) {
3433
3610
  request.componentSourceId
3434
3611
  );
3435
3612
  if (anchor?.sourceVersion !== request.sourceVersion) {
3436
- throw new SpotPatchError12(ERROR_CODES12.DATA_FLOW_SOURCE_STALE);
3613
+ throw new SpotPatchError13(ERROR_CODES13.DATA_FLOW_SOURCE_STALE);
3437
3614
  }
3438
3615
  return anchor;
3439
3616
  }
@@ -3450,7 +3627,7 @@ async function analyzeTarget(request, analyzer, options) {
3450
3627
  column: resolvedRequest.column
3451
3628
  });
3452
3629
  if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
3453
- throw new SpotPatchError12(ERROR_CODES12.DATA_FLOW_SOURCE_STALE);
3630
+ throw new SpotPatchError13(ERROR_CODES13.DATA_FLOW_SOURCE_STALE);
3454
3631
  }
3455
3632
  return limitDataFlowReportToBytes(
3456
3633
  report,
@@ -3459,7 +3636,7 @@ async function analyzeTarget(request, analyzer, options) {
3459
3636
  }
3460
3637
  function requireAnalyzer(analyzer) {
3461
3638
  if (analyzer === void 0) {
3462
- throw new SpotPatchError12(ERROR_CODES12.DATA_FLOW_DISABLED);
3639
+ throw new SpotPatchError13(ERROR_CODES13.DATA_FLOW_DISABLED);
3463
3640
  }
3464
3641
  return analyzer;
3465
3642
  }
@@ -3471,7 +3648,7 @@ async function handleComponentDataFlowReport(request, analyzer, options) {
3471
3648
  )
3472
3649
  );
3473
3650
  if (!parsed.success) {
3474
- throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3651
+ throw new SpotPatchError13(ERROR_CODES13.INVALID_REQUEST);
3475
3652
  }
3476
3653
  return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
3477
3654
  }
@@ -3483,7 +3660,7 @@ async function handlePageDataFlowReport(request, analyzer, options) {
3483
3660
  )
3484
3661
  );
3485
3662
  if (!parsed.success) {
3486
- throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
3663
+ throw new SpotPatchError13(ERROR_CODES13.INVALID_REQUEST);
3487
3664
  }
3488
3665
  const activeAnalyzer = requireAnalyzer(analyzer);
3489
3666
  const componentReports = await Promise.all(
@@ -3556,7 +3733,7 @@ async function handlePageDataFlowReport(request, analyzer, options) {
3556
3733
  // src/server/request-security.ts
3557
3734
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
3558
3735
  import { isIP } from "net";
3559
- import { ERROR_CODES as ERROR_CODES13, SPOTPATCH_TOKEN_HEADER, SpotPatchError as SpotPatchError13 } from "@spotpatch/shared";
3736
+ import { ERROR_CODES as ERROR_CODES14, SPOTPATCH_TOKEN_HEADER, SpotPatchError as SpotPatchError14 } from "@spotpatch/shared";
3560
3737
  function getSingleHeader(request, name) {
3561
3738
  const value = request.headers[name.toLowerCase()];
3562
3739
  return Array.isArray(value) ? value[0] : value;
@@ -3603,32 +3780,32 @@ function parseOrigin(value) {
3603
3780
  function assertRequestAuthorized(request, options) {
3604
3781
  const actualToken = getSingleHeader(request, SPOTPATCH_TOKEN_HEADER);
3605
3782
  if (!tokensMatch2(actualToken, options.sessionToken)) {
3606
- throw new SpotPatchError13(ERROR_CODES13.INVALID_TOKEN);
3783
+ throw new SpotPatchError14(ERROR_CODES14.INVALID_TOKEN);
3607
3784
  }
3608
3785
  const hostHeader = getSingleHeader(request, "host");
3609
3786
  const originHeader = getSingleHeader(request, "origin");
3610
3787
  const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
3611
3788
  const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
3612
3789
  if (host === void 0 || origin === void 0) {
3613
- throw new SpotPatchError13(ERROR_CODES13.ORIGIN_NOT_ALLOWED);
3790
+ throw new SpotPatchError14(ERROR_CODES14.ORIGIN_NOT_ALLOWED);
3614
3791
  }
3615
3792
  const hostIsLoopback = isLoopbackHostname(host.hostname);
3616
3793
  const originIsLoopback = isLoopbackHostname(origin.hostname);
3617
3794
  if (!options.allowLan) {
3618
3795
  if (!hostIsLoopback || !originIsLoopback) {
3619
- throw new SpotPatchError13(ERROR_CODES13.ORIGIN_NOT_ALLOWED);
3796
+ throw new SpotPatchError14(ERROR_CODES14.ORIGIN_NOT_ALLOWED);
3620
3797
  }
3621
3798
  return;
3622
3799
  }
3623
3800
  if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
3624
- throw new SpotPatchError13(ERROR_CODES13.ORIGIN_NOT_ALLOWED);
3801
+ throw new SpotPatchError14(ERROR_CODES14.ORIGIN_NOT_ALLOWED);
3625
3802
  }
3626
3803
  }
3627
3804
 
3628
3805
  // src/server/runtime-bootstrap.ts
3629
3806
  import {
3630
- ERROR_CODES as ERROR_CODES14,
3631
- SpotPatchError as SpotPatchError14,
3807
+ ERROR_CODES as ERROR_CODES15,
3808
+ SpotPatchError as SpotPatchError15,
3632
3809
  runtimeBootstrapRequestSchema,
3633
3810
  runtimeConfigSchema
3634
3811
  } from "@spotpatch/shared";
@@ -3658,7 +3835,7 @@ function resolveRuntimeBootstrapOptions(options) {
3658
3835
  function assertRuntimeBootstrapRequest(request, expectedOrigin) {
3659
3836
  const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
3660
3837
  if (request.method !== "POST" || contentType !== "application/json") {
3661
- throw new SpotPatchError14(ERROR_CODES14.INVALID_REQUEST);
3838
+ throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
3662
3839
  }
3663
3840
  const host = getSingleHeader2(request, "host");
3664
3841
  let hostIsLoopback = false;
@@ -3670,7 +3847,7 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
3670
3847
  }
3671
3848
  }
3672
3849
  if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
3673
- throw new SpotPatchError14(ERROR_CODES14.ORIGIN_NOT_ALLOWED);
3850
+ throw new SpotPatchError15(ERROR_CODES15.ORIGIN_NOT_ALLOWED);
3674
3851
  }
3675
3852
  }
3676
3853
  async function readRuntimeBootstrap(request, options) {
@@ -3679,123 +3856,123 @@ async function readRuntimeBootstrap(request, options) {
3679
3856
  await readJsonRequestBody(request)
3680
3857
  );
3681
3858
  if (!parsedBody.success) {
3682
- throw new SpotPatchError14(ERROR_CODES14.INVALID_REQUEST);
3859
+ throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
3683
3860
  }
3684
3861
  return options.runtimeConfig;
3685
3862
  }
3686
3863
 
3687
3864
  // src/server/middleware.ts
3688
3865
  var STATUS_BY_ERROR = Object.freeze({
3689
- [ERROR_CODES15.INVALID_REQUEST]: 400,
3690
- [ERROR_CODES15.INVALID_TOKEN]: 401,
3691
- [ERROR_CODES15.ORIGIN_NOT_ALLOWED]: 403,
3692
- [ERROR_CODES15.SOURCE_NOT_FOUND]: 404,
3693
- [ERROR_CODES15.SOURCE_OUTSIDE_ROOT]: 403,
3694
- [ERROR_CODES15.SOURCE_TOO_LARGE]: 413,
3695
- [ERROR_CODES15.EDITOR_OPEN_FAILED]: 500,
3696
- [ERROR_CODES15.DATA_FLOW_DISABLED]: 404,
3697
- [ERROR_CODES15.DATA_FLOW_SOURCE_STALE]: 409,
3698
- [ERROR_CODES15.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
3699
- [ERROR_CODES15.AI_DISABLED]: 404,
3700
- [ERROR_CODES15.PROVIDER_NOT_CONFIGURED]: 503,
3701
- [ERROR_CODES15.PROVIDER_AUTH_FAILED]: 502,
3702
- [ERROR_CODES15.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
3703
- [ERROR_CODES15.MODEL_NOT_ALLOWED]: 400,
3704
- [ERROR_CODES15.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
3705
- [ERROR_CODES15.PROVIDER_RATE_LIMITED]: 429,
3706
- [ERROR_CODES15.AGENT_BUSY]: 409,
3707
- [ERROR_CODES15.AGENT_LIMIT_EXCEEDED]: 413,
3708
- [ERROR_CODES15.AGENT_CANCELLED]: 409,
3709
- [ERROR_CODES15.EXTERNAL_HANDOFF_DISABLED]: 404,
3710
- [ERROR_CODES15.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
3711
- [ERROR_CODES15.HANDOFF_VALIDATION_FAILED]: 422,
3712
- [ERROR_CODES15.HANDOFF_SOURCE_STALE]: 409,
3713
- [ERROR_CODES15.HANDOFF_NOT_FOUND]: 404,
3714
- [ERROR_CODES15.HANDOFF_EXPIRED]: 410,
3715
- [ERROR_CODES15.HANDOFF_CURSOR_INVALID]: 409,
3716
- [ERROR_CODES15.HANDOFF_RESPONSE_TOO_LARGE]: 413,
3717
- [ERROR_CODES15.BRIDGE_UNAUTHORIZED]: 401,
3718
- [ERROR_CODES15.BRIDGE_PROTOCOL_MISMATCH]: 409,
3719
- [ERROR_CODES15.BRIDGE_BUSY]: 429,
3720
- [ERROR_CODES15.EXTERNAL_AGENT_BUSY]: 409,
3721
- [ERROR_CODES15.ACTIVE_ADAPTER_CONFLICT]: 409,
3722
- [ERROR_CODES15.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
3723
- [ERROR_CODES15.ACTIVE_DISPATCH_INVALID]: 409,
3724
- [ERROR_CODES15.SESSION_NOT_FOUND]: 404,
3725
- [ERROR_CODES15.SESSION_AMBIGUOUS]: 409,
3726
- [ERROR_CODES15.SESSION_CLOSED]: 410,
3727
- [ERROR_CODES15.WORKTREE_DIRTY]: 409,
3728
- [ERROR_CODES15.WORKTREE_NOT_REPOSITORY]: 409,
3729
- [ERROR_CODES15.WORKTREE_OPERATION_IN_PROGRESS]: 409,
3730
- [ERROR_CODES15.WORKTREE_CONFLICTED]: 409,
3731
- [ERROR_CODES15.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
3732
- [ERROR_CODES15.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
3733
- [ERROR_CODES15.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
3734
- [ERROR_CODES15.TOOL_DENIED]: 403,
3735
- [ERROR_CODES15.TOOL_INPUT_INVALID]: 422,
3736
- [ERROR_CODES15.TOOL_ARGUMENTS_INVALID]: 422,
3737
- [ERROR_CODES15.TOOL_CALL_ID_CONFLICT]: 422,
3738
- [ERROR_CODES15.TOOL_PATH_DENIED]: 403,
3739
- [ERROR_CODES15.PATCH_REJECTED]: 422,
3740
- [ERROR_CODES15.VALIDATION_FAILED]: 422,
3741
- [ERROR_CODES15.APPLY_CONFLICT]: 409,
3742
- [ERROR_CODES15.INTERNAL_ERROR]: 500
3866
+ [ERROR_CODES16.INVALID_REQUEST]: 400,
3867
+ [ERROR_CODES16.INVALID_TOKEN]: 401,
3868
+ [ERROR_CODES16.ORIGIN_NOT_ALLOWED]: 403,
3869
+ [ERROR_CODES16.SOURCE_NOT_FOUND]: 404,
3870
+ [ERROR_CODES16.SOURCE_OUTSIDE_ROOT]: 403,
3871
+ [ERROR_CODES16.SOURCE_TOO_LARGE]: 413,
3872
+ [ERROR_CODES16.EDITOR_OPEN_FAILED]: 500,
3873
+ [ERROR_CODES16.DATA_FLOW_DISABLED]: 404,
3874
+ [ERROR_CODES16.DATA_FLOW_SOURCE_STALE]: 409,
3875
+ [ERROR_CODES16.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
3876
+ [ERROR_CODES16.AI_DISABLED]: 404,
3877
+ [ERROR_CODES16.PROVIDER_NOT_CONFIGURED]: 503,
3878
+ [ERROR_CODES16.PROVIDER_AUTH_FAILED]: 502,
3879
+ [ERROR_CODES16.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
3880
+ [ERROR_CODES16.MODEL_NOT_ALLOWED]: 400,
3881
+ [ERROR_CODES16.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
3882
+ [ERROR_CODES16.PROVIDER_RATE_LIMITED]: 429,
3883
+ [ERROR_CODES16.AGENT_BUSY]: 409,
3884
+ [ERROR_CODES16.AGENT_LIMIT_EXCEEDED]: 413,
3885
+ [ERROR_CODES16.AGENT_CANCELLED]: 409,
3886
+ [ERROR_CODES16.EXTERNAL_HANDOFF_DISABLED]: 404,
3887
+ [ERROR_CODES16.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
3888
+ [ERROR_CODES16.HANDOFF_VALIDATION_FAILED]: 422,
3889
+ [ERROR_CODES16.HANDOFF_SOURCE_STALE]: 409,
3890
+ [ERROR_CODES16.HANDOFF_NOT_FOUND]: 404,
3891
+ [ERROR_CODES16.HANDOFF_EXPIRED]: 410,
3892
+ [ERROR_CODES16.HANDOFF_CURSOR_INVALID]: 409,
3893
+ [ERROR_CODES16.HANDOFF_RESPONSE_TOO_LARGE]: 413,
3894
+ [ERROR_CODES16.BRIDGE_UNAUTHORIZED]: 401,
3895
+ [ERROR_CODES16.BRIDGE_PROTOCOL_MISMATCH]: 409,
3896
+ [ERROR_CODES16.BRIDGE_BUSY]: 429,
3897
+ [ERROR_CODES16.EXTERNAL_AGENT_BUSY]: 409,
3898
+ [ERROR_CODES16.ACTIVE_ADAPTER_CONFLICT]: 409,
3899
+ [ERROR_CODES16.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
3900
+ [ERROR_CODES16.ACTIVE_DISPATCH_INVALID]: 409,
3901
+ [ERROR_CODES16.SESSION_NOT_FOUND]: 404,
3902
+ [ERROR_CODES16.SESSION_AMBIGUOUS]: 409,
3903
+ [ERROR_CODES16.SESSION_CLOSED]: 410,
3904
+ [ERROR_CODES16.WORKTREE_DIRTY]: 409,
3905
+ [ERROR_CODES16.WORKTREE_NOT_REPOSITORY]: 409,
3906
+ [ERROR_CODES16.WORKTREE_OPERATION_IN_PROGRESS]: 409,
3907
+ [ERROR_CODES16.WORKTREE_CONFLICTED]: 409,
3908
+ [ERROR_CODES16.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
3909
+ [ERROR_CODES16.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
3910
+ [ERROR_CODES16.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
3911
+ [ERROR_CODES16.TOOL_DENIED]: 403,
3912
+ [ERROR_CODES16.TOOL_INPUT_INVALID]: 422,
3913
+ [ERROR_CODES16.TOOL_ARGUMENTS_INVALID]: 422,
3914
+ [ERROR_CODES16.TOOL_CALL_ID_CONFLICT]: 422,
3915
+ [ERROR_CODES16.TOOL_PATH_DENIED]: 403,
3916
+ [ERROR_CODES16.PATCH_REJECTED]: 422,
3917
+ [ERROR_CODES16.VALIDATION_FAILED]: 422,
3918
+ [ERROR_CODES16.APPLY_CONFLICT]: 409,
3919
+ [ERROR_CODES16.INTERNAL_ERROR]: 500
3743
3920
  });
3744
3921
  var PUBLIC_MESSAGES = Object.freeze({
3745
- [ERROR_CODES15.INVALID_REQUEST]: "The request is invalid.",
3746
- [ERROR_CODES15.INVALID_TOKEN]: "The session token is invalid.",
3747
- [ERROR_CODES15.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
3748
- [ERROR_CODES15.SOURCE_NOT_FOUND]: "The source file is unavailable.",
3749
- [ERROR_CODES15.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
3750
- [ERROR_CODES15.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
3751
- [ERROR_CODES15.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
3752
- [ERROR_CODES15.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
3753
- [ERROR_CODES15.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
3754
- [ERROR_CODES15.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
3755
- [ERROR_CODES15.AI_DISABLED]: "AI execution is not enabled.",
3756
- [ERROR_CODES15.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
3757
- [ERROR_CODES15.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
3758
- [ERROR_CODES15.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
3759
- [ERROR_CODES15.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
3760
- [ERROR_CODES15.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
3761
- [ERROR_CODES15.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
3762
- [ERROR_CODES15.AGENT_BUSY]: "Another Agent job is already running.",
3763
- [ERROR_CODES15.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
3764
- [ERROR_CODES15.AGENT_CANCELLED]: "The Agent job was cancelled.",
3765
- [ERROR_CODES15.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
3766
- [ERROR_CODES15.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
3767
- [ERROR_CODES15.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
3768
- [ERROR_CODES15.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
3769
- [ERROR_CODES15.HANDOFF_NOT_FOUND]: "No current handoff is available.",
3770
- [ERROR_CODES15.HANDOFF_EXPIRED]: "The handoff has expired.",
3771
- [ERROR_CODES15.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
3772
- [ERROR_CODES15.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
3773
- [ERROR_CODES15.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
3774
- [ERROR_CODES15.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
3775
- [ERROR_CODES15.BRIDGE_BUSY]: "The local bridge is busy.",
3776
- [ERROR_CODES15.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
3777
- [ERROR_CODES15.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
3778
- [ERROR_CODES15.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
3779
- [ERROR_CODES15.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
3780
- [ERROR_CODES15.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
3781
- [ERROR_CODES15.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
3782
- [ERROR_CODES15.SESSION_CLOSED]: "The SpotPatch session has closed.",
3783
- [ERROR_CODES15.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
3784
- [ERROR_CODES15.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
3785
- [ERROR_CODES15.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
3786
- [ERROR_CODES15.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
3787
- [ERROR_CODES15.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
3788
- [ERROR_CODES15.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
3789
- [ERROR_CODES15.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
3790
- [ERROR_CODES15.TOOL_DENIED]: "The Agent tool request was denied.",
3791
- [ERROR_CODES15.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
3792
- [ERROR_CODES15.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
3793
- [ERROR_CODES15.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
3794
- [ERROR_CODES15.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
3795
- [ERROR_CODES15.PATCH_REJECTED]: "The proposed patch was rejected.",
3796
- [ERROR_CODES15.VALIDATION_FAILED]: "The proposed change failed validation.",
3797
- [ERROR_CODES15.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
3798
- [ERROR_CODES15.INTERNAL_ERROR]: "The request could not be completed."
3922
+ [ERROR_CODES16.INVALID_REQUEST]: "The request is invalid.",
3923
+ [ERROR_CODES16.INVALID_TOKEN]: "The session token is invalid.",
3924
+ [ERROR_CODES16.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
3925
+ [ERROR_CODES16.SOURCE_NOT_FOUND]: "The source file is unavailable.",
3926
+ [ERROR_CODES16.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
3927
+ [ERROR_CODES16.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
3928
+ [ERROR_CODES16.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
3929
+ [ERROR_CODES16.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
3930
+ [ERROR_CODES16.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
3931
+ [ERROR_CODES16.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
3932
+ [ERROR_CODES16.AI_DISABLED]: "AI execution is not enabled.",
3933
+ [ERROR_CODES16.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
3934
+ [ERROR_CODES16.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
3935
+ [ERROR_CODES16.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
3936
+ [ERROR_CODES16.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
3937
+ [ERROR_CODES16.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
3938
+ [ERROR_CODES16.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
3939
+ [ERROR_CODES16.AGENT_BUSY]: "Another Agent job is already running.",
3940
+ [ERROR_CODES16.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
3941
+ [ERROR_CODES16.AGENT_CANCELLED]: "The Agent job was cancelled.",
3942
+ [ERROR_CODES16.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
3943
+ [ERROR_CODES16.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
3944
+ [ERROR_CODES16.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
3945
+ [ERROR_CODES16.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
3946
+ [ERROR_CODES16.HANDOFF_NOT_FOUND]: "No current handoff is available.",
3947
+ [ERROR_CODES16.HANDOFF_EXPIRED]: "The handoff has expired.",
3948
+ [ERROR_CODES16.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
3949
+ [ERROR_CODES16.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
3950
+ [ERROR_CODES16.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
3951
+ [ERROR_CODES16.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
3952
+ [ERROR_CODES16.BRIDGE_BUSY]: "The local bridge is busy.",
3953
+ [ERROR_CODES16.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
3954
+ [ERROR_CODES16.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
3955
+ [ERROR_CODES16.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
3956
+ [ERROR_CODES16.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
3957
+ [ERROR_CODES16.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
3958
+ [ERROR_CODES16.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
3959
+ [ERROR_CODES16.SESSION_CLOSED]: "The SpotPatch session has closed.",
3960
+ [ERROR_CODES16.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
3961
+ [ERROR_CODES16.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
3962
+ [ERROR_CODES16.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
3963
+ [ERROR_CODES16.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
3964
+ [ERROR_CODES16.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
3965
+ [ERROR_CODES16.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
3966
+ [ERROR_CODES16.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
3967
+ [ERROR_CODES16.TOOL_DENIED]: "The Agent tool request was denied.",
3968
+ [ERROR_CODES16.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
3969
+ [ERROR_CODES16.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
3970
+ [ERROR_CODES16.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
3971
+ [ERROR_CODES16.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
3972
+ [ERROR_CODES16.PATCH_REJECTED]: "The proposed patch was rejected.",
3973
+ [ERROR_CODES16.VALIDATION_FAILED]: "The proposed change failed validation.",
3974
+ [ERROR_CODES16.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
3975
+ [ERROR_CODES16.INTERNAL_ERROR]: "The request could not be completed."
3799
3976
  });
3800
3977
  function writeJson2(response, status, payload) {
3801
3978
  response.statusCode = status;
@@ -3804,11 +3981,11 @@ function writeJson2(response, status, payload) {
3804
3981
  response.end(JSON.stringify(payload));
3805
3982
  }
3806
3983
  function asSpotPatchError(error) {
3807
- return error instanceof SpotPatchError15 ? error : new SpotPatchError15(ERROR_CODES15.INTERNAL_ERROR, void 0, { cause: error });
3984
+ return error instanceof SpotPatchError16 ? error : new SpotPatchError16(ERROR_CODES16.INTERNAL_ERROR, void 0, { cause: error });
3808
3985
  }
3809
3986
  function writeError(response, error, logger) {
3810
3987
  const normalized = asSpotPatchError(error);
3811
- if (normalized.code === ERROR_CODES15.INTERNAL_ERROR) {
3988
+ if (normalized.code === ERROR_CODES16.INTERNAL_ERROR) {
3812
3989
  logger?.warn("[spotpatch:server] Internal request failure.");
3813
3990
  }
3814
3991
  writeJson2(response, STATUS_BY_ERROR[normalized.code], {
@@ -3831,7 +4008,7 @@ async function handleSourceContext(request, options) {
3831
4008
  await readJsonRequestBody(request)
3832
4009
  );
3833
4010
  if (!parsed.success) {
3834
- throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
4011
+ throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
3835
4012
  }
3836
4013
  return readSourceContext({
3837
4014
  request: parsed.data,
@@ -3844,7 +4021,7 @@ async function handleSourceContext(request, options) {
3844
4021
  async function handleOpenEditor(request, options) {
3845
4022
  const parsed = openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3846
4023
  if (!parsed.success) {
3847
- throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
4024
+ throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
3848
4025
  }
3849
4026
  const body = parsed.data;
3850
4027
  const sourcePath = await resolveSourceFile({
@@ -3861,7 +4038,7 @@ async function handleOpenEditor(request, options) {
3861
4038
  options.logger?.warn(
3862
4039
  `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
3863
4040
  );
3864
- throw new SpotPatchError15(ERROR_CODES15.EDITOR_OPEN_FAILED, void 0, {
4041
+ throw new SpotPatchError16(ERROR_CODES16.EDITOR_OPEN_FAILED, void 0, {
3865
4042
  cause: error
3866
4043
  });
3867
4044
  }
@@ -3869,16 +4046,18 @@ async function handleOpenEditor(request, options) {
3869
4046
  function createSpotPatchMiddleware(options) {
3870
4047
  const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
3871
4048
  const dataFlowAnalyzer = createDataFlowAnalyzer(options);
3872
- return (request, response, next) => {
4049
+ const externalAgentController = options.externalAgentControl === void 0 ? void 0 : createExternalAgentBrowserController(options.externalAgentControl);
4050
+ const middleware = (request, response, next) => {
3873
4051
  const path9 = requestPath(request);
3874
4052
  const agentRoute = matchAgentRequestPath(path9);
4053
+ const externalAgentRoute = matchExternalAgentBrowserPath(path9);
3875
4054
  const externalHandoffRoute = matchExternalHandoffBrowserPath(path9);
3876
- if (path9 !== SPOTPATCH_ENDPOINTS3.sourceContext && path9 !== SPOTPATCH_ENDPOINTS3.openEditor && path9 !== SPOTPATCH_ENDPOINTS3.dataFlowComponentReport && path9 !== SPOTPATCH_ENDPOINTS3.dataFlowPageReport && agentRoute === void 0 && externalHandoffRoute === void 0 && !path9.startsWith(`${SPOTPATCH_API_BASE}/`)) {
4055
+ if (path9 !== SPOTPATCH_ENDPOINTS4.sourceContext && path9 !== SPOTPATCH_ENDPOINTS4.openEditor && path9 !== SPOTPATCH_ENDPOINTS4.dataFlowComponentReport && path9 !== SPOTPATCH_ENDPOINTS4.dataFlowPageReport && agentRoute === void 0 && externalAgentRoute === void 0 && externalHandoffRoute === void 0 && !path9.startsWith(`${SPOTPATCH_API_BASE}/`)) {
3877
4056
  next();
3878
4057
  return;
3879
4058
  }
3880
4059
  const handle = async () => {
3881
- if (path9 === SPOTPATCH_ENDPOINTS3.bootstrap && bootstrap !== void 0) {
4060
+ if (path9 === SPOTPATCH_ENDPOINTS4.bootstrap && bootstrap !== void 0) {
3882
4061
  const data = await readRuntimeBootstrap(
3883
4062
  request,
3884
4063
  bootstrap
@@ -3890,25 +4069,25 @@ function createSpotPatchMiddleware(options) {
3890
4069
  allowLan: options.options.allowLan,
3891
4070
  sessionToken: options.session.token
3892
4071
  });
3893
- if (path9 === SPOTPATCH_ENDPOINTS3.sourceContext) {
4072
+ if (path9 === SPOTPATCH_ENDPOINTS4.sourceContext) {
3894
4073
  if (request.method !== "POST") {
3895
- throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
4074
+ throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
3896
4075
  }
3897
4076
  const data = await handleSourceContext(request, options);
3898
4077
  writeJson2(response, 200, { ok: true, data });
3899
4078
  return;
3900
4079
  }
3901
- if (path9 === SPOTPATCH_ENDPOINTS3.openEditor) {
4080
+ if (path9 === SPOTPATCH_ENDPOINTS4.openEditor) {
3902
4081
  if (request.method !== "POST") {
3903
- throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
4082
+ throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
3904
4083
  }
3905
4084
  const data = await handleOpenEditor(request, options);
3906
4085
  writeJson2(response, 200, { ok: true, data });
3907
4086
  return;
3908
4087
  }
3909
- if (path9 === SPOTPATCH_ENDPOINTS3.dataFlowComponentReport) {
4088
+ if (path9 === SPOTPATCH_ENDPOINTS4.dataFlowComponentReport) {
3910
4089
  if (request.method !== "POST") {
3911
- throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
4090
+ throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
3912
4091
  }
3913
4092
  const data = await handleComponentDataFlowReport(
3914
4093
  request,
@@ -3918,14 +4097,28 @@ function createSpotPatchMiddleware(options) {
3918
4097
  writeJson2(response, 200, { ok: true, data });
3919
4098
  return;
3920
4099
  }
3921
- if (path9 === SPOTPATCH_ENDPOINTS3.dataFlowPageReport) {
4100
+ if (path9 === SPOTPATCH_ENDPOINTS4.dataFlowPageReport) {
3922
4101
  if (request.method !== "POST") {
3923
- throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
4102
+ throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
3924
4103
  }
3925
4104
  const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
3926
4105
  writeJson2(response, 200, { ok: true, data });
3927
4106
  return;
3928
4107
  }
4108
+ if (externalAgentRoute !== void 0) {
4109
+ if (!options.options.externalAgent.enabled || externalAgentController === void 0) {
4110
+ throw new SpotPatchError16(ERROR_CODES16.EXTERNAL_HANDOFF_DISABLED);
4111
+ }
4112
+ await externalAgentController.handle(
4113
+ request,
4114
+ response,
4115
+ externalAgentRoute,
4116
+ (target, status, data) => {
4117
+ writeJson2(target, status, { ok: true, data });
4118
+ }
4119
+ );
4120
+ return;
4121
+ }
3929
4122
  if (externalHandoffRoute !== void 0) {
3930
4123
  await handleExternalHandoffBrowserRequest(
3931
4124
  request,
@@ -3944,7 +4137,7 @@ function createSpotPatchMiddleware(options) {
3944
4137
  return;
3945
4138
  }
3946
4139
  if (agentRoute === void 0) {
3947
- throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
4140
+ throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
3948
4141
  }
3949
4142
  await handleAgentRequest(
3950
4143
  request,
@@ -3960,21 +4153,42 @@ function createSpotPatchMiddleware(options) {
3960
4153
  writeError(response, error, options.logger);
3961
4154
  });
3962
4155
  };
4156
+ return Object.assign(middleware, {
4157
+ dispose() {
4158
+ externalAgentController?.dispose();
4159
+ }
4160
+ });
3963
4161
  }
3964
4162
 
3965
4163
  // src/server/source-registration.ts
3966
4164
  import { timingSafeEqual as timingSafeEqual3 } from "crypto";
3967
- import { lstat as lstat4, realpath as realpath6 } from "fs/promises";
4165
+ import { lstat as lstat4, readFile as readFile4, realpath as realpath6 } from "fs/promises";
3968
4166
  import path8 from "path";
3969
- import { createSourceFilter } from "@spotpatch/compiler";
4167
+ import {
4168
+ createDataFlowSourceFilter,
4169
+ createDataFlowSourceVersion,
4170
+ createSourceFilter
4171
+ } from "@spotpatch/compiler";
4172
+ import { DEFAULT_DATA_FLOW_LIMITS as DEFAULT_DATA_FLOW_LIMITS2 } from "@spotpatch/shared";
3970
4173
  import { z as z2 } from "zod";
3971
- var REGISTRATION_BODY_LIMIT_BYTES = 4096;
4174
+ var REGISTRATION_BODY_LIMIT_BYTES = DEFAULT_DATA_FLOW_LIMITS2.protocolRequestMaxBytes;
3972
4175
  var REGISTRATION_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{16,128}$/u;
4176
+ var DATA_FLOW_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
3973
4177
  var INTERNAL_SECRET_HEADER = "x-spotpatch-internal";
3974
4178
  var FORBIDDEN_SOURCE_SEGMENTS = /* @__PURE__ */ new Set([".next", "node_modules"]);
3975
4179
  var registrationRequestSchema = z2.strictObject({
3976
4180
  epoch: z2.string().regex(REGISTRATION_IDENTITY_PATTERN),
3977
- resourcePath: z2.string().min(1).max(3072)
4181
+ resourcePath: z2.string().min(1).max(3072),
4182
+ dataFlow: z2.strictObject({
4183
+ sourceVersion: z2.string().regex(DATA_FLOW_IDENTITY_PATTERN),
4184
+ components: z2.array(
4185
+ z2.strictObject({
4186
+ componentSourceId: z2.string().regex(DATA_FLOW_IDENTITY_PATTERN),
4187
+ line: z2.number().int().positive().max(1e7),
4188
+ column: z2.number().int().positive().max(1e7)
4189
+ })
4190
+ ).max(DEFAULT_DATA_FLOW_LIMITS2.sourceMaxComponents)
4191
+ }).optional()
3978
4192
  });
3979
4193
  function getSingleHeader3(request, name) {
3980
4194
  const value = request.headers[name.toLowerCase()];
@@ -4038,6 +4252,33 @@ async function createSourceRegistrationService(input) {
4038
4252
  }
4039
4253
  const root = await realpath6(input.root);
4040
4254
  const sourceFilter = createSourceFilter(root, input.options);
4255
+ const dataFlowFilter = createDataFlowSourceFilter(root, input.options);
4256
+ const registrationQueues = /* @__PURE__ */ new Map();
4257
+ async function registerCurrentSource(sourcePath, dataFlow) {
4258
+ if (dataFlow === void 0) {
4259
+ return input.registry.register(sourcePath);
4260
+ }
4261
+ let fileId;
4262
+ const previous = registrationQueues.get(sourcePath) ?? Promise.resolve();
4263
+ const current = previous.catch(() => void 0).then(async () => {
4264
+ const currentSource = await readFile4(sourcePath, "utf8");
4265
+ if (createDataFlowSourceVersion(currentSource) !== dataFlow.sourceVersion) {
4266
+ return;
4267
+ }
4268
+ fileId = input.registry.registerDataFlowComponents(
4269
+ sourcePath,
4270
+ dataFlow.sourceVersion,
4271
+ dataFlow.components
4272
+ );
4273
+ });
4274
+ registrationQueues.set(sourcePath, current);
4275
+ await current.finally(() => {
4276
+ if (registrationQueues.get(sourcePath) === current) {
4277
+ registrationQueues.delete(sourcePath);
4278
+ }
4279
+ });
4280
+ return fileId;
4281
+ }
4041
4282
  const handler = (request, response) => {
4042
4283
  const handle = async () => {
4043
4284
  const contentType = getSingleHeader3(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
@@ -4058,16 +4299,18 @@ async function createSourceRegistrationService(input) {
4058
4299
  const sourcePath = await resolveAuthorizedSource(
4059
4300
  root,
4060
4301
  parsed.data.resourcePath,
4061
- (absolutePath) => sourceFilter.shouldTransform(absolutePath, "<")
4302
+ (absolutePath) => parsed.data.dataFlow === void 0 ? sourceFilter.shouldTransform(absolutePath, "<") : dataFlowFilter.shouldTransform(absolutePath, "")
4062
4303
  );
4063
4304
  if (sourcePath === void 0) {
4064
4305
  writeJson3(response, 403, { ok: false });
4065
4306
  return;
4066
4307
  }
4067
- writeJson3(response, 200, {
4068
- epoch: input.registryEpoch,
4069
- fileId: input.registry.register(sourcePath)
4070
- });
4308
+ const fileId = await registerCurrentSource(sourcePath, parsed.data.dataFlow);
4309
+ if (fileId === void 0) {
4310
+ writeJson3(response, 409, { ok: false });
4311
+ return;
4312
+ }
4313
+ writeJson3(response, 200, { epoch: input.registryEpoch, fileId });
4071
4314
  };
4072
4315
  void handle().catch(() => {
4073
4316
  if (!response.headersSent) {
@@ -4291,8 +4534,10 @@ export {
4291
4534
  readRuntimeBootstrap,
4292
4535
  resolveCredentialEnvironment,
4293
4536
  resolveEnvironmentAiConfiguration,
4537
+ resolveManagedExecutionValidation,
4294
4538
  resolveOptions,
4295
4539
  resolveProjectOptions,
4540
+ resolveProjectValidationChecks,
4296
4541
  resolveRuntimeBootstrapOptions,
4297
4542
  serializeResolvedSpotPatchOptions
4298
4543
  };