@spotpatch/dev-server 0.7.0 → 0.9.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
@@ -30,10 +30,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ ContextualAskError: () => ContextualAskError,
33
34
  DEFAULT_EXCLUDE: () => DEFAULT_EXCLUDE,
34
35
  DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
35
36
  applyIntegrationPlan: () => applyIntegrationPlan,
37
+ captureAskReadSnapshot: () => captureAskReadSnapshot,
38
+ composeContextualAskExecutors: () => composeContextualAskExecutors,
36
39
  createAgentJobManager: () => createAgentJobManager,
40
+ createConfiguredKeyAskExecutors: () => createConfiguredKeyAskExecutors,
41
+ createContextualAskManager: () => createContextualAskManager,
37
42
  createExternalHandoffService: () => createExternalHandoffService,
38
43
  createIntegrationFileChange: () => createIntegrationFileChange,
39
44
  createRuntimeAiConfig: () => createRuntimeAiConfig,
@@ -42,9 +47,12 @@ __export(index_exports, {
42
47
  createSourceRegistrationService: () => createSourceRegistrationService,
43
48
  createSourceRegistry: () => createSourceRegistry,
44
49
  createSpotPatchMiddleware: () => createSpotPatchMiddleware,
50
+ createWorkspaceActivityCoordinator: () => createWorkspaceActivityCoordinator,
45
51
  discoverProjectValidationCheck: () => discoverProjectValidationCheck,
52
+ handleContextualAskRequest: () => handleContextualAskRequest,
46
53
  integrationPathExists: () => integrationPathExists,
47
54
  isLoopbackHostname: () => isLoopbackHostname,
55
+ matchContextualAskPath: () => matchContextualAskPath,
48
56
  parseSerializedSpotPatchOptions: () => parseSerializedSpotPatchOptions,
49
57
  readIntegrationFile: () => readIntegrationFile,
50
58
  readJsonRequestBody: () => readJsonRequestBody,
@@ -221,6 +229,10 @@ function createAgentJobManager(options) {
221
229
  job.updatedAt = dependencies.now();
222
230
  emitSnapshot(job);
223
231
  emitPhase(job, phaseMessage);
232
+ if (!isActive(status)) {
233
+ job.lease?.release();
234
+ job.lease = void 0;
235
+ }
224
236
  };
225
237
  const probeResolved = async (selection, signal) => {
226
238
  const key = capabilityCacheKey(selection.provider, selection.model);
@@ -410,6 +422,7 @@ function createAgentJobManager(options) {
410
422
  [...jobs.values()].map((job) => job.runPromise).filter((promise) => promise !== void 0)
411
423
  );
412
424
  capabilityCache.clear();
425
+ for (const job of jobs.values()) job.lease?.release();
413
426
  jobs.clear();
414
427
  },
415
428
  create(request) {
@@ -434,8 +447,13 @@ function createAgentJobManager(options) {
434
447
  request.providerProfileId,
435
448
  request.modelProfileId
436
449
  );
450
+ const lease = options.coordinator?.acquire("change");
451
+ if (options.coordinator !== void 0 && lease === void 0) {
452
+ throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.AGENT_BUSY);
453
+ }
437
454
  const id = dependencies.createJobId();
438
455
  if (!JOB_ID_PATTERN.test(id) || jobs.has(id)) {
456
+ lease?.release();
439
457
  throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.INTERNAL_ERROR);
440
458
  }
441
459
  const timestamp = dependencies.now();
@@ -449,6 +467,7 @@ function createAgentJobManager(options) {
449
467
  events: [],
450
468
  id,
451
469
  listeners: /* @__PURE__ */ new Set(),
470
+ lease,
452
471
  model: selection.model,
453
472
  phaseMessage: "Agent job queued.",
454
473
  preparedChange: void 0,
@@ -491,6 +510,11 @@ function createAgentJobManager(options) {
491
510
  hasActiveJob(job.id) ? import_shared.ERROR_CODES.AGENT_BUSY : import_shared.ERROR_CODES.APPLY_CONFLICT
492
511
  );
493
512
  }
513
+ const lease = options.coordinator?.acquire("change");
514
+ if (options.coordinator !== void 0 && lease === void 0) {
515
+ throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.AGENT_BUSY);
516
+ }
517
+ job.lease = lease;
494
518
  transition(job, "reverting", "Reverting the applied Agent change.");
495
519
  try {
496
520
  await dependencies.revertChange(job.preparedChange);
@@ -1262,12 +1286,12 @@ function createExternalHandoffStore(options) {
1262
1286
  }
1263
1287
  return current.snapshot;
1264
1288
  };
1265
- const findReplay = (requestId, fingerprint) => {
1289
+ const findReplay = (requestId, fingerprint2) => {
1266
1290
  requireOpen();
1267
1291
  sweep();
1268
1292
  const record = idempotency.get(requestId);
1269
1293
  if (record === void 0) return void 0;
1270
- if (record.fingerprint !== fingerprint) {
1294
+ if (record.fingerprint !== fingerprint2) {
1271
1295
  throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
1272
1296
  }
1273
1297
  return replayResult(record);
@@ -1512,12 +1536,12 @@ function createExternalHandoffService(options) {
1512
1536
  },
1513
1537
  async publish(request, authorize) {
1514
1538
  requireReady();
1515
- const fingerprint = fingerprintExternalHandoffAnnotation(request.annotation);
1516
- const replayed = store.replay(request.requestId, fingerprint);
1539
+ const fingerprint2 = fingerprintExternalHandoffAnnotation(request.annotation);
1540
+ const replayed = store.replay(request.requestId, fingerprint2);
1517
1541
  if (replayed !== void 0) return replayed;
1518
1542
  const pending = inFlight.get(request.requestId);
1519
1543
  if (pending !== void 0) {
1520
- if (pending.fingerprint !== fingerprint) {
1544
+ if (pending.fingerprint !== fingerprint2) {
1521
1545
  throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
1522
1546
  }
1523
1547
  return asReplay(await pending.promise);
@@ -1527,12 +1551,12 @@ function createExternalHandoffService(options) {
1527
1551
  const annotation = await authorize(request.annotation);
1528
1552
  return store.publish({
1529
1553
  annotation,
1530
- fingerprint,
1554
+ fingerprint: fingerprint2,
1531
1555
  requestId: request.requestId,
1532
1556
  reserve: activeRegistry.reserve
1533
1557
  });
1534
1558
  })();
1535
- const activePublish = Object.freeze({ fingerprint, promise });
1559
+ const activePublish = Object.freeze({ fingerprint: fingerprint2, promise });
1536
1560
  inFlight.set(request.requestId, activePublish);
1537
1561
  try {
1538
1562
  return await promise;
@@ -1874,7 +1898,8 @@ var DEFAULT_OPTIONS = Object.freeze({
1874
1898
  runtime: "dispatch",
1875
1899
  limits: import_shared8.DEFAULT_DATA_FLOW_LIMITS
1876
1900
  }),
1877
- externalAgent: Object.freeze({ enabled: false })
1901
+ externalAgent: Object.freeze({ enabled: false }),
1902
+ contextualAsk: Object.freeze({ enabled: false })
1878
1903
  });
1879
1904
  var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
1880
1905
  var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,127}$/;
@@ -2184,6 +2209,35 @@ function resolveDataFlowOptions(options) {
2184
2209
  limits: import_shared8.DEFAULT_DATA_FLOW_LIMITS
2185
2210
  });
2186
2211
  }
2212
+ function resolveContextualAskOptions(value, ai) {
2213
+ if (value === void 0 || value === false) return DEFAULT_OPTIONS.contextualAsk;
2214
+ const parsed = import_shared8.contextualAskOptionsSchema.safeParse(value);
2215
+ if (!parsed.success) {
2216
+ throw new RangeError("SpotPatch contextualAsk configuration is invalid.");
2217
+ }
2218
+ const configured = typeof parsed.data === "boolean" ? {} : parsed.data;
2219
+ const preference = configured.defaultExecutor;
2220
+ if (preference?.kind === "configured-key") {
2221
+ const provider = ai === false ? void 0 : ai.providers[preference.providerProfileId];
2222
+ if (provider?.models[preference.modelProfileId] === void 0) {
2223
+ throw new RangeError(
2224
+ "SpotPatch contextualAsk default Key executor must reference a configured AI model."
2225
+ );
2226
+ }
2227
+ }
2228
+ if (ai !== false && Object.values(ai.providers).reduce(
2229
+ (total, provider) => total + Object.keys(provider.models).length,
2230
+ 0
2231
+ ) >= import_shared8.CONTEXTUAL_ASK_LIMITS.maximumExecutors) {
2232
+ throw new RangeError(
2233
+ "SpotPatch contextualAsk configuration exceeds the executor limit."
2234
+ );
2235
+ }
2236
+ return Object.freeze({
2237
+ enabled: true,
2238
+ ...preference === void 0 ? {} : { defaultExecutor: Object.freeze({ ...preference }) }
2239
+ });
2240
+ }
2187
2241
  function createRuntimeDataFlowConfig(options) {
2188
2242
  return Object.freeze({
2189
2243
  enabled: options.enabled,
@@ -2222,6 +2276,7 @@ function resolveOptions(options = {}, environmentAi) {
2222
2276
  `SpotPatch maxTargets must be an integer between 1 and ${String(import_shared8.MAX_ANNOTATION_TARGETS)}.`
2223
2277
  );
2224
2278
  }
2279
+ const ai = resolveAiOptions(options.ai ?? environmentAi);
2225
2280
  const resolved = {
2226
2281
  enabled: options.enabled ?? DEFAULT_OPTIONS.enabled,
2227
2282
  include: Object.freeze([...options.include ?? DEFAULT_OPTIONS.include]),
@@ -2234,11 +2289,12 @@ function resolveOptions(options = {}, environmentAi) {
2234
2289
  debug: options.debug ?? DEFAULT_OPTIONS.debug,
2235
2290
  locale,
2236
2291
  maxTargets,
2237
- ai: resolveAiOptions(options.ai ?? environmentAi),
2292
+ ai,
2238
2293
  dataFlow: resolveDataFlowOptions(options.dataFlow),
2239
2294
  externalAgent: Object.freeze({
2240
2295
  enabled: options.externalAgent ?? DEFAULT_OPTIONS.externalAgent.enabled
2241
- })
2296
+ }),
2297
+ contextualAsk: resolveContextualAskOptions(options.contextualAsk, ai)
2242
2298
  };
2243
2299
  if (resolved.shortcut.trim().length === 0 || resolved.shortcut.length > 128 || resolved.shortcut.includes("\0")) {
2244
2300
  throw new RangeError("SpotPatch shortcut is invalid.");
@@ -2472,10 +2528,14 @@ function createSourceRegistry(options = {}) {
2472
2528
  );
2473
2529
  }
2474
2530
  componentIdsByPath.set(normalizedPath, currentIds);
2531
+ return fileId;
2475
2532
  },
2476
2533
  resolve(fileId) {
2477
2534
  return idToPath.get(fileId);
2478
2535
  },
2536
+ findRegisteredId(absolutePath) {
2537
+ return pathToId.get(normalizeAbsolutePath(absolutePath));
2538
+ },
2479
2539
  resolveDataFlowComponent(componentSourceId) {
2480
2540
  return componentAnchors.get(componentSourceId);
2481
2541
  },
@@ -2488,179 +2548,1478 @@ function createSourceRegistry(options = {}) {
2488
2548
  });
2489
2549
  }
2490
2550
 
2491
- // src/server/middleware.ts
2492
- var import_shared20 = require("@spotpatch/shared");
2493
-
2494
- // src/external-handoff/browser-http.ts
2495
- var import_shared13 = require("@spotpatch/shared");
2496
-
2497
- // src/server/annotation-authorizer.ts
2498
- var import_promises6 = require("fs/promises");
2499
- var import_node_path7 = __toESM(require("path"), 1);
2500
- var import_shared12 = require("@spotpatch/shared");
2501
-
2502
- // src/server/source-context.ts
2503
- var import_promises5 = require("fs/promises");
2504
- var import_node_path6 = __toESM(require("path"), 1);
2551
+ // src/contextual-ask/manager.ts
2552
+ var import_node_crypto10 = require("crypto");
2505
2553
  var import_shared11 = require("@spotpatch/shared");
2506
2554
 
2507
- // src/server/extract-code-context.ts
2508
- var import_oxc_parser = require("oxc-parser");
2509
- function isComponentName(name) {
2510
- return /^[A-Z]/u.test(name);
2511
- }
2512
- function unwrapTypeExpression(expression) {
2513
- switch (expression.type) {
2514
- case "TSAsExpression":
2515
- case "TSSatisfiesExpression":
2516
- case "TSTypeAssertion":
2517
- case "TSNonNullExpression":
2518
- case "TSInstantiationExpression":
2519
- return unwrapTypeExpression(expression.expression);
2520
- default:
2521
- return expression;
2522
- }
2523
- }
2524
- function calleeName(expression) {
2525
- const unwrapped = unwrapTypeExpression(expression);
2526
- if (unwrapped.type === "Identifier") {
2527
- return unwrapped.name;
2555
+ // src/contextual-ask/error.ts
2556
+ var import_agent2 = require("@spotpatch/agent");
2557
+ var ContextualAskError = class extends Error {
2558
+ code;
2559
+ constructor(code, options = {}) {
2560
+ super(code, options.cause === void 0 ? void 0 : { cause: options.cause });
2561
+ this.name = "ContextualAskError";
2562
+ this.code = code;
2528
2563
  }
2529
- if (unwrapped.type === "MemberExpression" && !unwrapped.computed) {
2530
- return unwrapped.property.type === "Identifier" ? unwrapped.property.name : void 0;
2564
+ };
2565
+ function asContextualAskError(error) {
2566
+ if (error instanceof ContextualAskError) return error;
2567
+ if (error instanceof import_agent2.ContextualAskExecutorError) {
2568
+ return new ContextualAskError(error.code, { cause: error });
2531
2569
  }
2532
- return void 0;
2570
+ return new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", { cause: error });
2533
2571
  }
2534
- function isFunctionExpression(expression) {
2535
- const unwrapped = unwrapTypeExpression(expression);
2536
- return unwrapped.type === "ArrowFunctionExpression" || unwrapped.type === "FunctionExpression";
2572
+
2573
+ // src/contextual-ask/read-snapshot.ts
2574
+ var import_node_crypto9 = require("crypto");
2575
+ var import_node_fs = require("fs");
2576
+ var import_promises4 = require("fs/promises");
2577
+ var import_node_path5 = __toESM(require("path"), 1);
2578
+ var import_shared10 = require("@spotpatch/shared");
2579
+ var import_oxc_parser = require("oxc-parser");
2580
+ var SOURCE_EXTENSIONS = Object.freeze([
2581
+ ".ts",
2582
+ ".tsx",
2583
+ ".js",
2584
+ ".jsx",
2585
+ ".mjs",
2586
+ ".cjs",
2587
+ ".css",
2588
+ ".scss",
2589
+ ".sass",
2590
+ ".less"
2591
+ ]);
2592
+ var PROTECTED_DIRECTORIES = /* @__PURE__ */ new Set([
2593
+ ".git",
2594
+ ".ssh",
2595
+ ".spotpatch",
2596
+ "coverage",
2597
+ "dist",
2598
+ "node_modules"
2599
+ ]);
2600
+ var PROTECTED_FILES = /* @__PURE__ */ new Set([
2601
+ ".gitmodules",
2602
+ ".git-credentials",
2603
+ ".netrc",
2604
+ ".npmrc",
2605
+ ".pnpmrc",
2606
+ ".pypirc",
2607
+ ".yarnrc",
2608
+ "bun.lock",
2609
+ "bun.lockb",
2610
+ "npm-shrinkwrap.json",
2611
+ "package-lock.json",
2612
+ "pnpm-lock.yaml",
2613
+ "yarn.lock"
2614
+ ]);
2615
+ var decoder = new TextDecoder("utf-8", { fatal: true });
2616
+ function deny(code) {
2617
+ throw new ContextualAskError(code);
2537
2618
  }
2538
- function isSupportedComponentInitializer(expression) {
2539
- const unwrapped = unwrapTypeExpression(expression);
2540
- if (isFunctionExpression(unwrapped)) {
2541
- return true;
2542
- }
2543
- if (unwrapped.type !== "CallExpression") {
2544
- return false;
2619
+ function toPosixRelative(realRoot, absolutePath) {
2620
+ const relative = import_node_path5.default.relative(realRoot, absolutePath);
2621
+ if (relative.length === 0 || relative === ".." || relative.startsWith(`..${import_node_path5.default.sep}`) || import_node_path5.default.isAbsolute(relative)) {
2622
+ return deny("ASK_SOURCE_SCOPE_DENIED");
2545
2623
  }
2546
- const name = calleeName(unwrapped.callee);
2547
- if (name !== "memo" && name !== "forwardRef") {
2548
- return false;
2624
+ const normalized = relative.split(import_node_path5.default.sep).join("/");
2625
+ const segments = normalized.toLowerCase().split("/");
2626
+ const fileName = segments.at(-1) ?? "";
2627
+ if (segments.some((segment) => PROTECTED_DIRECTORIES.has(segment)) || PROTECTED_FILES.has(fileName) || fileName === ".env" || fileName === ".envrc" || fileName === ".dev.vars" || fileName.startsWith(".env.") || fileName.endsWith(".pem") || fileName.endsWith(".key") || fileName.endsWith(".p12") || fileName.endsWith(".pfx") || fileName.startsWith("id_rsa")) {
2628
+ return deny("ASK_SOURCE_SCOPE_DENIED");
2549
2629
  }
2550
- const firstArgument = unwrapped.arguments[0];
2551
- return firstArgument !== void 0 && firstArgument.type !== "SpreadElement" && (isFunctionExpression(firstArgument) || isSupportedComponentInitializer(firstArgument));
2630
+ return normalized;
2552
2631
  }
2553
- function variableComponent(node) {
2554
- if (node.id.type !== "Identifier" || !isComponentName(node.id.name) || node.init === null || !isSupportedComponentInitializer(node.init)) {
2555
- return void 0;
2632
+ async function assertNoSymlinkPath(realRoot, absolutePath) {
2633
+ const relative = toPosixRelative(realRoot, absolutePath);
2634
+ let current = realRoot;
2635
+ for (const segment of relative.split("/")) {
2636
+ current = import_node_path5.default.join(current, segment);
2637
+ const metadata = await (0, import_promises4.lstat)(current).catch(() => void 0);
2638
+ if (metadata === void 0 || metadata.isSymbolicLink()) {
2639
+ deny("ASK_SOURCE_SCOPE_DENIED");
2640
+ }
2556
2641
  }
2557
- return Object.freeze({ start: node.start, end: node.end, name: node.id.name });
2558
- }
2559
- function functionComponent(node) {
2560
- return node.id !== null && isComponentName(node.id.name) && node.body !== null ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
2561
2642
  }
2562
- function isReactComponentSuperclass(expression) {
2563
- if (expression === null) {
2564
- return false;
2643
+ async function readSecureSource(realRoot, absolutePath) {
2644
+ const normalizedAbsolute = import_node_path5.default.normalize(import_node_path5.default.resolve(absolutePath));
2645
+ const relativePath = toPosixRelative(realRoot, normalizedAbsolute);
2646
+ await assertNoSymlinkPath(realRoot, normalizedAbsolute);
2647
+ const canonical = await (0, import_promises4.realpath)(normalizedAbsolute).catch(() => void 0);
2648
+ if (canonical === void 0 || canonical !== normalizedAbsolute) {
2649
+ return deny("ASK_SOURCE_SCOPE_DENIED");
2565
2650
  }
2566
- const unwrapped = unwrapTypeExpression(expression);
2567
- if (unwrapped.type === "Identifier") {
2568
- return unwrapped.name === "Component" || unwrapped.name === "PureComponent";
2651
+ let handle;
2652
+ try {
2653
+ handle = await (0, import_promises4.open)(normalizedAbsolute, import_node_fs.constants.O_RDONLY | import_node_fs.constants.O_NOFOLLOW);
2654
+ const before = await handle.stat();
2655
+ if (!before.isFile() || before.nlink !== 1) {
2656
+ return deny("ASK_SOURCE_SCOPE_DENIED");
2657
+ }
2658
+ if (before.size > import_shared10.CONTEXTUAL_ASK_LIMITS.maximumReadBytesPerFile) {
2659
+ throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
2660
+ }
2661
+ const bytes = await handle.readFile();
2662
+ const after = await handle.stat();
2663
+ if (bytes.byteLength !== before.size || before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || bytes.includes(0)) {
2664
+ return deny("ASK_SOURCE_SCOPE_DENIED");
2665
+ }
2666
+ let content;
2667
+ try {
2668
+ content = decoder.decode(bytes);
2669
+ } catch (error) {
2670
+ throw new ContextualAskError("ASK_SOURCE_SCOPE_DENIED", { cause: error });
2671
+ }
2672
+ return Object.freeze({
2673
+ content,
2674
+ contentHash: (0, import_node_crypto9.createHash)("sha256").update(bytes).digest("hex"),
2675
+ relativePath,
2676
+ size: bytes.byteLength
2677
+ });
2678
+ } finally {
2679
+ await handle?.close().catch(() => void 0);
2569
2680
  }
2570
- return unwrapped.type === "MemberExpression" && !unwrapped.computed && unwrapped.object.type === "Identifier" && unwrapped.object.name === "React" && (unwrapped.property.name === "Component" || unwrapped.property.name === "PureComponent");
2571
2681
  }
2572
- function classComponent(node) {
2573
- return node.id !== null && isComponentName(node.id.name) && isReactComponentSuperclass(node.superClass) ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
2682
+ function confidence(value) {
2683
+ return value === "exact" || value === "probable" ? value : "approximate";
2574
2684
  }
2575
- function selectedOffset(source, line, column) {
2576
- const lines = source.split(/\r?\n/u);
2577
- if (line < 1 || line > lines.length) {
2578
- return void 0;
2685
+ async function authorizeSeeds(selection, registry, root, realRoot) {
2686
+ const byPath = /* @__PURE__ */ new Map();
2687
+ for (const target of selection.targets) {
2688
+ let fileId = target.react.source?.fileId ?? target.source.fileId;
2689
+ let sourceVersion;
2690
+ if (target.react.componentSourceId !== void 0) {
2691
+ const anchor = registry.resolveDataFlowComponent(target.react.componentSourceId);
2692
+ if (anchor === void 0 || target.react.sourceVersion !== anchor.sourceVersion) {
2693
+ deny("ASK_SELECTION_STALE");
2694
+ }
2695
+ fileId = anchor.fileId;
2696
+ sourceVersion = anchor.sourceVersion;
2697
+ }
2698
+ if (fileId === void 0) continue;
2699
+ const registered = registry.resolve(fileId);
2700
+ if (registered === void 0) deny("ASK_SELECTION_STALE");
2701
+ const canonical = await (0, import_promises4.realpath)(registered).catch(() => void 0);
2702
+ if (canonical === void 0) deny("ASK_SELECTION_STALE");
2703
+ const absolutePath = import_node_path5.default.normalize(canonical);
2704
+ const registeredRelative = toPosixRelative(realRoot, absolutePath);
2705
+ const claimedRelative = target.react.source?.relativePath ?? target.source.relativePath;
2706
+ if (claimedRelative !== void 0 && claimedRelative.split("\\").join("/") !== registeredRelative) {
2707
+ deny("ASK_SELECTION_STALE");
2708
+ }
2709
+ const existing = byPath.get(absolutePath);
2710
+ const targetIds = Object.freeze([
2711
+ ...existing?.targetIds ?? [],
2712
+ ...existing?.targetIds.includes(target.targetId) === true ? [] : [target.targetId]
2713
+ ]);
2714
+ byPath.set(
2715
+ absolutePath,
2716
+ Object.freeze({
2717
+ absolutePath,
2718
+ fileId,
2719
+ confidence: confidence(
2720
+ target.react.source?.confidence ?? target.source.confidence
2721
+ ),
2722
+ targetIds,
2723
+ ...sourceVersion === void 0 ? {} : { sourceVersion }
2724
+ })
2725
+ );
2579
2726
  }
2580
- const lineStart = lines.slice(0, line - 1).reduce((total, value) => total + value.length + 1, 0);
2581
- const lineLength = lines[line - 1]?.length ?? 0;
2582
- return lineStart + Math.min(Math.max(0, column - 1), lineLength);
2727
+ for (const target of selection.targets) {
2728
+ for (const rule of target.styles.matchedRules) {
2729
+ const claimed = rule.source;
2730
+ if (claimed === void 0 || claimed.startsWith("/") || claimed.includes("\\") || claimed.includes("\0") || claimed.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
2731
+ continue;
2732
+ }
2733
+ const registeredPath = import_node_path5.default.resolve(root, ...claimed.split("/"));
2734
+ const fileId = registry.findRegisteredId(registeredPath);
2735
+ if (fileId === void 0) continue;
2736
+ const canonical = await (0, import_promises4.realpath)(registeredPath).catch(() => void 0);
2737
+ if (canonical === void 0) continue;
2738
+ const absolutePath = import_node_path5.default.normalize(canonical);
2739
+ toPosixRelative(realRoot, absolutePath);
2740
+ const existing = byPath.get(absolutePath);
2741
+ const targetIds = Object.freeze([
2742
+ ...existing?.targetIds ?? [],
2743
+ ...existing?.targetIds.includes(target.targetId) === true ? [] : [target.targetId]
2744
+ ]);
2745
+ byPath.set(
2746
+ absolutePath,
2747
+ Object.freeze({
2748
+ absolutePath,
2749
+ fileId,
2750
+ confidence: existing?.confidence ?? "probable",
2751
+ targetIds,
2752
+ ...existing?.sourceVersion === void 0 ? {} : { sourceVersion: existing.sourceVersion }
2753
+ })
2754
+ );
2755
+ }
2756
+ }
2757
+ if (byPath.size === 0) deny("ASK_SELECTION_REQUIRED");
2758
+ return [...byPath.values()];
2583
2759
  }
2584
- function findComponentSpan(options) {
2585
- const offset = selectedOffset(options.source, options.line, options.column);
2586
- if (offset === void 0) {
2587
- return void 0;
2760
+ function localSpecifiers(filePath, content) {
2761
+ const found = /* @__PURE__ */ new Set();
2762
+ if (/\.(?:css|scss|sass|less)$/iu.test(filePath)) {
2763
+ const pattern = /@import\s+(?:url\(\s*)?["']([^"']+)["']/gu;
2764
+ for (const match of content.matchAll(pattern)) {
2765
+ if (match[1]?.startsWith(".")) found.add(match[1]);
2766
+ }
2767
+ return [...found].sort();
2588
2768
  }
2589
- let parseResult;
2590
2769
  try {
2591
- parseResult = (0, import_oxc_parser.parseSync)(options.sourcePath, options.source, {
2592
- sourceType: "module"
2770
+ const parsed = (0, import_oxc_parser.parseSync)(filePath, content, { sourceType: "module" });
2771
+ if (parsed.errors.length > 0) return [];
2772
+ const add = (value) => {
2773
+ if (typeof value === "string" && value.startsWith(".")) found.add(value);
2774
+ };
2775
+ const visitor = new import_oxc_parser.Visitor({
2776
+ ImportDeclaration(node) {
2777
+ add(node.source.value);
2778
+ },
2779
+ ExportNamedDeclaration(node) {
2780
+ add(node.source?.value);
2781
+ },
2782
+ ExportAllDeclaration(node) {
2783
+ add(node.source.value);
2784
+ },
2785
+ ImportExpression(node) {
2786
+ if ("value" in node.source) add(node.source.value);
2787
+ }
2593
2788
  });
2789
+ visitor.visit(parsed.program);
2594
2790
  } catch {
2595
- return void 0;
2791
+ return [];
2596
2792
  }
2597
- if (parseResult.errors.length > 0) {
2598
- return void 0;
2793
+ return [...found].sort();
2794
+ }
2795
+ function importCandidates(importer, specifier) {
2796
+ const base = import_node_path5.default.resolve(import_node_path5.default.dirname(importer), specifier);
2797
+ if (import_node_path5.default.extname(base) !== "") return Object.freeze([base]);
2798
+ return Object.freeze([
2799
+ ...SOURCE_EXTENSIONS.map((extension) => `${base}${extension}`),
2800
+ ...SOURCE_EXTENSIONS.map((extension) => import_node_path5.default.join(base, `index${extension}`))
2801
+ ]);
2802
+ }
2803
+ function countLines(content) {
2804
+ return content.length === 0 ? 1 : content.split("\n").length;
2805
+ }
2806
+ function publicSourceEntry(source) {
2807
+ return Object.freeze({
2808
+ handleId: source.handleId,
2809
+ fileId: source.fileId,
2810
+ relativePath: source.relativePath,
2811
+ label: source.label,
2812
+ lineCount: source.lineCount,
2813
+ size: source.size,
2814
+ contentHash: source.contentHash,
2815
+ confidence: source.confidence,
2816
+ targetIds: source.targetIds,
2817
+ ...source.sourceVersion === void 0 ? {} : { sourceVersion: source.sourceVersion }
2818
+ });
2819
+ }
2820
+ function assertSelectionEvidenceCurrent(selection, captured) {
2821
+ for (const target of selection.targets) {
2822
+ if (target.code === void 0) continue;
2823
+ const source = captured.find(
2824
+ (candidate) => candidate.relativePath === target.code?.relativePath
2825
+ );
2826
+ if (source === void 0) deny("ASK_SELECTION_STALE");
2827
+ const lines = source.content.split(/\r?\n/u);
2828
+ if (target.code.endLine > lines.length) deny("ASK_SELECTION_STALE");
2829
+ const currentExcerpt = lines.slice(target.code.startLine - 1, target.code.endLine).join("\n");
2830
+ if (currentExcerpt !== target.code.excerpt) deny("ASK_SELECTION_STALE");
2831
+ }
2832
+ }
2833
+ async function captureAskReadSnapshot(options) {
2834
+ options.signal?.throwIfAborted();
2835
+ const realRoot = await (0, import_promises4.realpath)(options.root).catch(() => void 0);
2836
+ if (realRoot === void 0) deny("ASK_SOURCE_SCOPE_DENIED");
2837
+ const seeds = await authorizeSeeds(
2838
+ options.selection,
2839
+ options.registry,
2840
+ options.root,
2841
+ realRoot
2842
+ );
2843
+ const createHandleId = options.createHandleId ?? (() => (0, import_node_crypto9.randomBytes)(16).toString("base64url"));
2844
+ const captured = [];
2845
+ const capturedPaths = /* @__PURE__ */ new Set();
2846
+ const queue = [...seeds];
2847
+ let totalBytes = 0;
2848
+ let truncated = false;
2849
+ while (queue.length > 0) {
2850
+ options.signal?.throwIfAborted();
2851
+ const candidate = queue.shift();
2852
+ if (candidate === void 0 || capturedPaths.has(candidate.absolutePath)) continue;
2853
+ if (captured.length >= import_shared10.CONTEXTUAL_ASK_LIMITS.maximumReadFiles) {
2854
+ truncated = true;
2855
+ break;
2856
+ }
2857
+ let read;
2858
+ try {
2859
+ read = await readSecureSource(realRoot, candidate.absolutePath);
2860
+ options.signal?.throwIfAborted();
2861
+ } catch (error) {
2862
+ if (captured.length < seeds.length) throw error;
2863
+ truncated = true;
2864
+ continue;
2865
+ }
2866
+ if (totalBytes + read.size > import_shared10.CONTEXTUAL_ASK_LIMITS.maximumReadBytesTotal) {
2867
+ if (captured.length < seeds.length)
2868
+ throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
2869
+ truncated = true;
2870
+ continue;
2871
+ }
2872
+ capturedPaths.add(candidate.absolutePath);
2873
+ totalBytes += read.size;
2874
+ const entry = Object.freeze({
2875
+ handleId: createHandleId(),
2876
+ fileId: candidate.fileId,
2877
+ relativePath: read.relativePath,
2878
+ label: import_node_path5.default.basename(read.relativePath),
2879
+ lineCount: countLines(read.content),
2880
+ size: read.size,
2881
+ contentHash: read.contentHash,
2882
+ confidence: candidate.confidence,
2883
+ targetIds: Object.freeze([...candidate.targetIds]),
2884
+ ...candidate.sourceVersion === void 0 ? {} : { sourceVersion: candidate.sourceVersion },
2885
+ absolutePath: candidate.absolutePath,
2886
+ content: read.content
2887
+ });
2888
+ captured.push(entry);
2889
+ for (const specifier of localSpecifiers(candidate.absolutePath, read.content)) {
2890
+ options.signal?.throwIfAborted();
2891
+ for (const importPath of importCandidates(candidate.absolutePath, specifier)) {
2892
+ if (capturedPaths.has(importPath)) continue;
2893
+ const metadata = await (0, import_promises4.lstat)(importPath).catch(() => void 0);
2894
+ if (metadata?.isFile() !== true || metadata.isSymbolicLink()) continue;
2895
+ const fileId = options.registry.findRegisteredId(importPath) ?? options.registry.register(importPath);
2896
+ queue.push(
2897
+ Object.freeze({
2898
+ absolutePath: import_node_path5.default.normalize(importPath),
2899
+ fileId,
2900
+ confidence: "probable",
2901
+ targetIds: candidate.targetIds
2902
+ })
2903
+ );
2904
+ break;
2905
+ }
2906
+ }
2599
2907
  }
2600
- const jsxNodes = [];
2601
- const components = [];
2602
- const visitor = new import_oxc_parser.Visitor({
2603
- JSXElement(node) {
2604
- jsxNodes.push(node);
2908
+ assertSelectionEvidenceCurrent(options.selection, captured);
2909
+ const sourceByHandle = new Map(captured.map((source) => [source.handleId, source]));
2910
+ const publicEntries = Object.freeze(
2911
+ captured.map((source) => publicSourceEntry(source))
2912
+ );
2913
+ const contextHash = (0, import_node_crypto9.createHash)("sha256").update(
2914
+ JSON.stringify({
2915
+ selectionId: options.selection.selectionId,
2916
+ sources: publicEntries.map((source) => ({
2917
+ relativePath: source.relativePath,
2918
+ contentHash: source.contentHash,
2919
+ targetIds: source.targetIds,
2920
+ sourceVersion: source.sourceVersion ?? null
2921
+ }))
2922
+ })
2923
+ ).digest("hex");
2924
+ let disposed = false;
2925
+ const requireSource = (handleId) => {
2926
+ if (disposed) throw new ContextualAskError("ASK_RESULT_EXPIRED");
2927
+ const source = sourceByHandle.get(handleId);
2928
+ if (source === void 0) throw new ContextualAskError("ASK_SOURCE_SCOPE_DENIED");
2929
+ return source;
2930
+ };
2931
+ const snapshot2 = Object.freeze({
2932
+ manifest: () => {
2933
+ if (disposed) throw new ContextualAskError("ASK_RESULT_EXPIRED");
2934
+ return publicEntries;
2605
2935
  },
2606
- JSXFragment(node) {
2607
- jsxNodes.push(node);
2936
+ read(handleId, range = {}) {
2937
+ const source = requireSource(handleId);
2938
+ const startLine = range.startLine ?? 1;
2939
+ const endLine = range.endLine ?? source.lineCount;
2940
+ if (!Number.isSafeInteger(startLine) || !Number.isSafeInteger(endLine) || startLine < 1 || endLine < startLine || endLine > source.lineCount) {
2941
+ throw new ContextualAskError("ASK_SOURCE_SCOPE_DENIED");
2942
+ }
2943
+ return Object.freeze({
2944
+ handleId,
2945
+ startLine,
2946
+ endLine,
2947
+ content: source.content.split("\n").slice(startLine - 1, endLine).join("\n")
2948
+ });
2608
2949
  },
2609
- FunctionDeclaration(node) {
2610
- const candidate = functionComponent(node);
2611
- if (candidate !== void 0) {
2612
- components.push(candidate);
2950
+ search(query) {
2951
+ if (query.length === 0 || query.length > import_shared10.CONTEXTUAL_ASK_LIMITS.maximumSearchQueryCharacters || query.includes("\0")) {
2952
+ throw new ContextualAskError("ASK_SOURCE_SCOPE_DENIED");
2953
+ }
2954
+ const normalized = query.toLocaleLowerCase("en-US");
2955
+ const matches = [];
2956
+ for (const source of captured) {
2957
+ for (const [index, line] of source.content.split("\n").entries()) {
2958
+ if (line.toLocaleLowerCase("en-US").includes(normalized)) {
2959
+ matches.push(
2960
+ Object.freeze({
2961
+ handleId: source.handleId,
2962
+ line: index + 1,
2963
+ preview: line.slice(
2964
+ 0,
2965
+ import_shared10.CONTEXTUAL_ASK_LIMITS.maximumSearchPreviewCharacters
2966
+ )
2967
+ })
2968
+ );
2969
+ if (matches.length >= import_shared10.CONTEXTUAL_ASK_LIMITS.maximumSearchResults) {
2970
+ return Object.freeze(matches);
2971
+ }
2972
+ }
2973
+ }
2613
2974
  }
2975
+ return Object.freeze(matches);
2976
+ }
2977
+ });
2978
+ return Object.freeze({
2979
+ grant: Object.freeze({ contextHash, truncated, sources: publicEntries }),
2980
+ snapshot: snapshot2,
2981
+ dispose() {
2982
+ disposed = true;
2983
+ sourceByHandle.clear();
2984
+ captured.splice(0, captured.length);
2614
2985
  },
2615
- VariableDeclarator(node) {
2616
- const candidate = variableComponent(node);
2617
- if (candidate !== void 0) {
2618
- components.push(candidate);
2986
+ async isStale() {
2987
+ for (const source of captured) {
2988
+ try {
2989
+ const current = await readSecureSource(realRoot, source.absolutePath);
2990
+ if (current.contentHash !== source.contentHash) return true;
2991
+ } catch {
2992
+ return true;
2993
+ }
2619
2994
  }
2995
+ return false;
2620
2996
  },
2621
- ClassDeclaration(node) {
2622
- const candidate = classComponent(node);
2623
- if (candidate !== void 0) {
2624
- components.push(candidate);
2997
+ projectCitation(handleId, startLine, endLine) {
2998
+ const source = requireSource(handleId);
2999
+ if (!Number.isSafeInteger(startLine) || !Number.isSafeInteger(endLine) || startLine < 1 || endLine < startLine || endLine > source.lineCount) {
3000
+ throw new ContextualAskError("ASK_ANSWER_INVALID");
2625
3001
  }
3002
+ return Object.freeze({ ...publicSourceEntry(source), startLine, endLine });
2626
3003
  }
2627
3004
  });
2628
- visitor.visit(parseResult.program);
2629
- const selectedJsx = jsxNodes.filter((node) => node.start <= offset && node.end >= offset).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
2630
- if (selectedJsx === void 0) {
2631
- return void 0;
2632
- }
2633
- return components.filter(
2634
- (component) => component.start <= selectedJsx.start && component.end >= selectedJsx.end
2635
- ).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
2636
- }
2637
- function lineAtOffset(source, offset) {
2638
- let line = 1;
2639
- for (let index = 0; index < offset; index += 1) {
2640
- if (source[index] === "\n") {
2641
- line += 1;
2642
- }
2643
- }
2644
- return line;
2645
3005
  }
2646
- function componentRange(source, component) {
2647
- return Object.freeze({
2648
- startLine: lineAtOffset(source, component.start),
2649
- endLine: lineAtOffset(source, Math.max(component.start, component.end - 1))
3006
+
3007
+ // src/contextual-ask/manager.ts
3008
+ var ACTIVE_STATUSES = /* @__PURE__ */ new Set([
3009
+ "queued",
3010
+ "authorizing",
3011
+ "running",
3012
+ "cancelling"
3013
+ ]);
3014
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["answered", "cancelled", "failed"]);
3015
+ var DEFAULT_DEPENDENCIES2 = Object.freeze({
3016
+ captureSnapshot: captureAskReadSnapshot,
3017
+ createId: () => (0, import_node_crypto10.randomBytes)(16).toString("base64url"),
3018
+ now: () => /* @__PURE__ */ new Date()
3019
+ });
3020
+ function fingerprint(request) {
3021
+ return (0, import_node_crypto10.createHash)("sha256").update(
3022
+ JSON.stringify({
3023
+ envelope: request.envelope,
3024
+ executorId: request.executorId,
3025
+ providerDataConsent: request.providerDataConsent
3026
+ })
3027
+ ).digest("hex");
3028
+ }
3029
+ function fileCountBucket(count) {
3030
+ if (count <= 1) return "one";
3031
+ if (count <= 5) return "few";
3032
+ return "many";
3033
+ }
3034
+ function raceWithAbort(operation, signal) {
3035
+ const reason = () => signal.reason instanceof Error ? signal.reason : new ContextualAskError("ASK_CANCELLED");
3036
+ if (signal.aborted) return Promise.reject(reason());
3037
+ return new Promise((resolve, reject) => {
3038
+ const onAbort = () => {
3039
+ reject(reason());
3040
+ };
3041
+ signal.addEventListener("abort", onAbort, { once: true });
3042
+ operation.then(
3043
+ (value) => {
3044
+ signal.removeEventListener("abort", onAbort);
3045
+ resolve(value);
3046
+ },
3047
+ (error) => {
3048
+ signal.removeEventListener("abort", onAbort);
3049
+ reject(
3050
+ error instanceof Error ? error : new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", { cause: error })
3051
+ );
3052
+ }
3053
+ );
2650
3054
  });
2651
3055
  }
2652
- function truncateSelectedLine(line, column, maxCharacters) {
2653
- if (line.length <= maxCharacters) {
2654
- return line;
2655
- }
2656
- if (maxCharacters === 1) {
2657
- return "\u2026";
3056
+ function createContextualAskManager(options) {
3057
+ const dependencies = Object.freeze({
3058
+ ...DEFAULT_DEPENDENCIES2,
3059
+ ...options.dependencies
3060
+ });
3061
+ const executors = /* @__PURE__ */ new Map();
3062
+ for (const executor of options.executors ?? []) {
3063
+ if (executors.has(executor.executorId)) {
3064
+ throw new TypeError("Contextual Ask executor IDs must be unique.");
3065
+ }
3066
+ executors.set(executor.executorId, executor);
2658
3067
  }
2659
- const contentCharacters = maxCharacters - 2;
2660
- const desiredStart = Math.max(0, column - 1 - Math.floor(contentCharacters / 2));
2661
- const start = Math.min(desiredStart, line.length - contentCharacters);
2662
- const end = start + contentCharacters;
2663
- return `${start > 0 ? "\u2026" : ""}${line.slice(start, end)}${end < line.length ? "\u2026" : ""}`.slice(
3068
+ const jobs = /* @__PURE__ */ new Map();
3069
+ const requests = /* @__PURE__ */ new Map();
3070
+ const pendingRequests = /* @__PURE__ */ new Map();
3071
+ let closed = false;
3072
+ const isClosed = () => closed;
3073
+ const cleanupExpired = () => {
3074
+ const now = dependencies.now().getTime();
3075
+ for (const [jobId, job] of jobs) {
3076
+ if (job.expiresAt !== void 0 && now >= job.expiresAt) {
3077
+ job.snapshot?.dispose();
3078
+ jobs.delete(jobId);
3079
+ const request = requests.get(job.request.requestId);
3080
+ if (request?.jobId === jobId) requests.delete(job.request.requestId);
3081
+ }
3082
+ }
3083
+ };
3084
+ const requireJob = (jobId) => {
3085
+ cleanupExpired();
3086
+ const job = jobs.get(jobId);
3087
+ if (job === void 0) throw new ContextualAskError("ASK_RESULT_EXPIRED");
3088
+ return job;
3089
+ };
3090
+ const publicSnapshot = (job) => import_shared11.askJobSnapshotSchema.parse({
3091
+ schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
3092
+ jobId: job.id,
3093
+ selectionId: job.request.envelope.selection.selectionId,
3094
+ status: job.status,
3095
+ executor: {
3096
+ executorId: job.executorCapability.executorId,
3097
+ kind: job.executorCapability.kind,
3098
+ label: job.executorCapability.label,
3099
+ modelLabel: job.executorCapability.effectiveModelLabel
3100
+ },
3101
+ ...job.phaseMessage === void 0 ? {} : { phaseMessage: job.phaseMessage },
3102
+ createdAt: job.createdAt,
3103
+ updatedAt: job.updatedAt,
3104
+ canCancel: ["queued", "authorizing", "running"].includes(job.status),
3105
+ ...job.errorCode === void 0 ? {} : { errorCode: job.errorCode }
3106
+ });
3107
+ const appendEvent = (job, event) => {
3108
+ job.events.push(import_shared11.askJobEventSchema.parse(event));
3109
+ if (job.events.length > import_shared11.CONTEXTUAL_ASK_LIMITS.maximumRetainedEvents) {
3110
+ job.events.splice(
3111
+ 0,
3112
+ job.events.length - import_shared11.CONTEXTUAL_ASK_LIMITS.maximumRetainedEvents
3113
+ );
3114
+ }
3115
+ for (const listener of job.listeners) listener(event);
3116
+ };
3117
+ const eventBase = (job) => {
3118
+ job.sequence += 1;
3119
+ return {
3120
+ schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
3121
+ sequence: job.sequence,
3122
+ jobId: job.id,
3123
+ status: job.status,
3124
+ timestamp: dependencies.now().toISOString()
3125
+ };
3126
+ };
3127
+ const emitSnapshot = (job) => {
3128
+ appendEvent(job, {
3129
+ ...eventBase(job),
3130
+ type: "snapshot",
3131
+ snapshot: publicSnapshot(job)
3132
+ });
3133
+ };
3134
+ const emitPhase = (job, message) => {
3135
+ appendEvent(job, { ...eventBase(job), type: "phase", message });
3136
+ };
3137
+ const emitReadActivity = (job, entry, state) => {
3138
+ appendEvent(job, {
3139
+ ...eventBase(job),
3140
+ type: "read-activity",
3141
+ activity: entry === void 0 ? {
3142
+ kind: "file-count",
3143
+ bucket: fileCountBucket(job.snapshot?.grant.sources.length ?? 0)
3144
+ } : {
3145
+ kind: "source",
3146
+ sourceId: entry.fileId,
3147
+ relativePath: entry.relativePath
3148
+ },
3149
+ state
3150
+ });
3151
+ };
3152
+ const transition = (job, status, phaseMessage, errorCode) => {
3153
+ job.status = status;
3154
+ job.phaseMessage = phaseMessage;
3155
+ job.errorCode = errorCode;
3156
+ job.updatedAt = dependencies.now().toISOString();
3157
+ emitSnapshot(job);
3158
+ if (!TERMINAL_STATUSES.has(status)) emitPhase(job, phaseMessage);
3159
+ };
3160
+ const finish = (job) => {
3161
+ job.expiresAt = dependencies.now().getTime() + import_shared11.CONTEXTUAL_ASK_LIMITS.resultTtlMs;
3162
+ job.lease.release();
3163
+ job.snapshot?.dispose();
3164
+ job.snapshot = void 0;
3165
+ };
3166
+ const fail = (job, error) => {
3167
+ if (TERMINAL_STATUSES.has(job.status)) return;
3168
+ const normalized = asContextualAskError(error);
3169
+ const abortReason = job.controller.signal.reason;
3170
+ const cancelled = normalized.code === "ASK_CANCELLED" || abortReason instanceof ContextualAskError && abortReason.code === "ASK_CANCELLED";
3171
+ transition(
3172
+ job,
3173
+ cancelled ? "cancelled" : "failed",
3174
+ cancelled ? "Question cancelled." : "Question failed.",
3175
+ cancelled ? "ASK_CANCELLED" : normalized.code
3176
+ );
3177
+ appendEvent(job, {
3178
+ ...eventBase(job),
3179
+ type: "error",
3180
+ errorCode: cancelled ? "ASK_CANCELLED" : normalized.code
3181
+ });
3182
+ finish(job);
3183
+ };
3184
+ const instrumentSnapshot = (job, snapshot2, onLimitExceeded) => {
3185
+ const byHandle = new Map(
3186
+ snapshot2.manifest().map((entry) => [entry.handleId, entry])
3187
+ );
3188
+ let readOperations = 0;
3189
+ const beginOperation = () => {
3190
+ readOperations += 1;
3191
+ if (readOperations > import_shared11.CONTEXTUAL_ASK_LIMITS.maximumToolCalls) {
3192
+ onLimitExceeded();
3193
+ throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
3194
+ }
3195
+ };
3196
+ return Object.freeze({
3197
+ manifest: () => snapshot2.manifest(),
3198
+ read(handleId, range) {
3199
+ beginOperation();
3200
+ const entry = byHandle.get(handleId);
3201
+ if (job.status === "running") emitReadActivity(job, entry, "started");
3202
+ try {
3203
+ const result = snapshot2.read(handleId, range);
3204
+ if (job.status === "running") emitReadActivity(job, entry, "succeeded");
3205
+ return result;
3206
+ } catch (error) {
3207
+ if (job.status === "running") emitReadActivity(job, entry, "failed");
3208
+ throw error;
3209
+ }
3210
+ },
3211
+ search(query) {
3212
+ beginOperation();
3213
+ if (job.status === "running") emitReadActivity(job, void 0, "started");
3214
+ try {
3215
+ const result = snapshot2.search(query);
3216
+ if (job.status === "running") {
3217
+ emitReadActivity(job, void 0, "succeeded");
3218
+ }
3219
+ return result;
3220
+ } catch (error) {
3221
+ if (job.status === "running") emitReadActivity(job, void 0, "failed");
3222
+ throw error;
3223
+ }
3224
+ }
3225
+ });
3226
+ };
3227
+ const projectAnswer = async (job, draft, captured) => {
3228
+ const sourceIds = /* @__PURE__ */ new Map();
3229
+ const sources = /* @__PURE__ */ new Map();
3230
+ const projectCitations = (citations) => citations.map((citation) => {
3231
+ const key = `${citation.handleId}:${String(citation.startLine)}:${String(citation.endLine)}`;
3232
+ let sourceId = sourceIds.get(key);
3233
+ if (sourceId === void 0) {
3234
+ sourceId = dependencies.createId();
3235
+ sourceIds.set(key, sourceId);
3236
+ sources.set(
3237
+ sourceId,
3238
+ captured.projectCitation(
3239
+ citation.handleId,
3240
+ citation.startLine,
3241
+ citation.endLine
3242
+ )
3243
+ );
3244
+ }
3245
+ return sourceId;
3246
+ });
3247
+ const blocks = draft.blocks.map((block) => {
3248
+ if (block.kind === "list") {
3249
+ return {
3250
+ kind: "list",
3251
+ items: block.items.map((item) => ({
3252
+ text: item.text,
3253
+ sourceIds: projectCitations(item.citations)
3254
+ }))
3255
+ };
3256
+ }
3257
+ return Object.freeze({
3258
+ kind: block.kind,
3259
+ ...block.kind === "paragraph" ? { text: block.text } : { code: block.code },
3260
+ ...block.kind === "code" && block.language !== void 0 ? { language: block.language } : {},
3261
+ sourceIds: projectCitations(block.citations)
3262
+ });
3263
+ });
3264
+ const warnings = [...draft.warnings];
3265
+ if (captured.grant.truncated && !warnings.some((warning) => warning.code === "source-truncated")) {
3266
+ warnings.push(Object.freeze({ code: "source-truncated" }));
3267
+ }
3268
+ if (await captured.isStale() && !warnings.some((warning) => warning.code === "source-stale")) {
3269
+ warnings.push(Object.freeze({ code: "source-stale" }));
3270
+ }
3271
+ const created = dependencies.now();
3272
+ return import_shared11.askAnswerResultSchema.parse({
3273
+ schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
3274
+ jobId: job.id,
3275
+ selectionId: job.request.envelope.selection.selectionId,
3276
+ contextHash: captured.grant.contextHash,
3277
+ executor: {
3278
+ executorId: job.executorCapability.executorId,
3279
+ kind: job.executorCapability.kind,
3280
+ label: job.executorCapability.label,
3281
+ modelLabel: job.executorCapability.effectiveModelLabel
3282
+ },
3283
+ blocks,
3284
+ sources: [...sources].map(([sourceId, source]) => ({
3285
+ sourceId,
3286
+ label: source.label,
3287
+ relativePath: source.relativePath,
3288
+ fileId: source.fileId,
3289
+ startLine: source.startLine,
3290
+ endLine: source.endLine,
3291
+ confidence: source.confidence,
3292
+ targetIds: source.targetIds,
3293
+ ...source.sourceVersion === void 0 ? {} : { sourceVersion: source.sourceVersion },
3294
+ contentHash: source.contentHash
3295
+ })),
3296
+ warnings,
3297
+ createdAt: created.toISOString(),
3298
+ expiresAt: new Date(
3299
+ created.getTime() + import_shared11.CONTEXTUAL_ASK_LIMITS.resultTtlMs
3300
+ ).toISOString()
3301
+ });
3302
+ };
3303
+ const run = async (job) => {
3304
+ const timeout = setTimeout(() => {
3305
+ job.controller.abort(new ContextualAskError("ASK_TIMEOUT"));
3306
+ }, import_shared11.CONTEXTUAL_ASK_LIMITS.jobTimeoutMs);
3307
+ timeout.unref();
3308
+ try {
3309
+ transition(job, "authorizing", "Authorizing selected sources.");
3310
+ const captured = await dependencies.captureSnapshot({
3311
+ root: options.root,
3312
+ registry: options.registry,
3313
+ selection: job.request.envelope.selection,
3314
+ signal: job.controller.signal,
3315
+ createHandleId: dependencies.createId
3316
+ });
3317
+ if (job.controller.signal.aborted) {
3318
+ captured.dispose();
3319
+ }
3320
+ job.controller.signal.throwIfAborted();
3321
+ job.snapshot = captured;
3322
+ transition(job, "running", "Analyzing the authorized snapshot.");
3323
+ emitReadActivity(job, void 0, "succeeded");
3324
+ const readLimit = { exceeded: false };
3325
+ const execution = await raceWithAbort(
3326
+ job.executor.execute(
3327
+ {
3328
+ jobId: job.id,
3329
+ envelope: job.request.envelope,
3330
+ grant: captured.grant,
3331
+ snapshot: instrumentSnapshot(job, captured.snapshot, () => {
3332
+ readLimit.exceeded = true;
3333
+ })
3334
+ },
3335
+ job.controller.signal
3336
+ ),
3337
+ job.controller.signal
3338
+ );
3339
+ const draft = import_shared11.askAnswerDraftSchema.parse(execution);
3340
+ const effectiveModelLabel = job.executor.effectiveModelLabel?.();
3341
+ if (effectiveModelLabel !== void 0) {
3342
+ job.executorCapability = import_shared11.contextualAskExecutorCapabilitySchema.parse({
3343
+ ...job.executorCapability,
3344
+ effectiveModelLabel
3345
+ });
3346
+ }
3347
+ if (readLimit.exceeded) throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
3348
+ job.controller.signal.throwIfAborted();
3349
+ const result = await projectAnswer(job, draft, captured);
3350
+ job.controller.signal.throwIfAborted();
3351
+ job.result = result;
3352
+ transition(job, "answered", "Answer ready.");
3353
+ appendEvent(job, { ...eventBase(job), type: "answer-ready" });
3354
+ finish(job);
3355
+ } catch (error) {
3356
+ fail(
3357
+ job,
3358
+ error instanceof Error && error.name === "ZodError" ? new ContextualAskError("ASK_ANSWER_INVALID", { cause: error }) : asContextualAskError(error)
3359
+ );
3360
+ } finally {
3361
+ clearTimeout(timeout);
3362
+ }
3363
+ };
3364
+ return Object.freeze({
3365
+ async capability(signal) {
3366
+ if (!options.enabled || closed) {
3367
+ return import_shared11.contextualAskCapabilitySchema.parse({
3368
+ schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
3369
+ enabled: false,
3370
+ executors: [],
3371
+ safety: {
3372
+ selectionRequired: true,
3373
+ singleTurn: true,
3374
+ writesAllowed: false,
3375
+ historyStored: false
3376
+ },
3377
+ checkedAt: dependencies.now().toISOString()
3378
+ });
3379
+ }
3380
+ const capabilities = (await Promise.all(
3381
+ [...executors.values()].map(async (executor) => {
3382
+ try {
3383
+ return import_shared11.contextualAskExecutorCapabilitySchema.parse(
3384
+ await raceWithAbort(executor.capability(signal), signal)
3385
+ );
3386
+ } catch {
3387
+ return void 0;
3388
+ }
3389
+ })
3390
+ )).filter((capability) => capability !== void 0);
3391
+ return import_shared11.contextualAskCapabilitySchema.parse({
3392
+ schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
3393
+ enabled: true,
3394
+ executors: capabilities,
3395
+ safety: {
3396
+ selectionRequired: true,
3397
+ singleTurn: true,
3398
+ writesAllowed: false,
3399
+ historyStored: false
3400
+ },
3401
+ checkedAt: dependencies.now().toISOString()
3402
+ });
3403
+ },
3404
+ async create(rawRequest) {
3405
+ cleanupExpired();
3406
+ if (!options.enabled || closed) throw new ContextualAskError("ASK_DISABLED");
3407
+ const parsed = import_shared11.askJobCreateRequestSchema.safeParse(rawRequest);
3408
+ if (!parsed.success) throw new ContextualAskError("ASK_QUESTION_INVALID");
3409
+ const request = parsed.data;
3410
+ const requestFingerprint = fingerprint(request);
3411
+ const previous = requests.get(request.requestId);
3412
+ if (previous !== void 0) {
3413
+ if (previous.fingerprint !== requestFingerprint) {
3414
+ throw new ContextualAskError("ASK_IDEMPOTENCY_CONFLICT");
3415
+ }
3416
+ return publicSnapshot(requireJob(previous.jobId));
3417
+ }
3418
+ const pending = pendingRequests.get(request.requestId);
3419
+ if (pending !== void 0) {
3420
+ if (pending.fingerprint !== requestFingerprint) {
3421
+ throw new ContextualAskError("ASK_IDEMPOTENCY_CONFLICT");
3422
+ }
3423
+ return await pending.promise;
3424
+ }
3425
+ if ([...jobs.values()].some((job) => ACTIVE_STATUSES.has(job.status))) {
3426
+ throw new ContextualAskError("ASK_BUSY");
3427
+ }
3428
+ const executor = executors.get(request.executorId);
3429
+ if (executor === void 0)
3430
+ throw new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE");
3431
+ const lease = options.coordinator.acquire("ask");
3432
+ if (lease === void 0) throw new ContextualAskError("ASK_BUSY");
3433
+ const controller = new AbortController();
3434
+ let leaseTransferred = false;
3435
+ const creation = (async () => {
3436
+ try {
3437
+ const capabilitySignal = AbortSignal.any([
3438
+ controller.signal,
3439
+ AbortSignal.timeout(import_shared11.CONTEXTUAL_ASK_LIMITS.capabilityTimeoutMs)
3440
+ ]);
3441
+ const capability = import_shared11.contextualAskExecutorCapabilitySchema.parse(
3442
+ await raceWithAbort(
3443
+ executor.capability(capabilitySignal),
3444
+ capabilitySignal
3445
+ )
3446
+ );
3447
+ if (isClosed()) throw new ContextualAskError("ASK_DISABLED");
3448
+ if (capability.executorId !== executor.executorId || capability.state !== "ready" || !capability.readOnlyProven) {
3449
+ throw new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE");
3450
+ }
3451
+ for (const [jobId, existing] of jobs) {
3452
+ if (TERMINAL_STATUSES.has(existing.status)) {
3453
+ existing.snapshot?.dispose();
3454
+ jobs.delete(jobId);
3455
+ requests.delete(existing.request.requestId);
3456
+ }
3457
+ }
3458
+ const createdAt = dependencies.now().toISOString();
3459
+ const job = {
3460
+ controller: new AbortController(),
3461
+ createdAt,
3462
+ events: [],
3463
+ executor,
3464
+ executorCapability: capability,
3465
+ fingerprint: requestFingerprint,
3466
+ id: dependencies.createId(),
3467
+ lease,
3468
+ listeners: /* @__PURE__ */ new Set(),
3469
+ request,
3470
+ errorCode: void 0,
3471
+ expiresAt: void 0,
3472
+ phaseMessage: "Question queued.",
3473
+ result: void 0,
3474
+ runPromise: void 0,
3475
+ sequence: 0,
3476
+ snapshot: void 0,
3477
+ status: "queued",
3478
+ updatedAt: createdAt
3479
+ };
3480
+ jobs.set(job.id, job);
3481
+ requests.set(request.requestId, {
3482
+ fingerprint: requestFingerprint,
3483
+ jobId: job.id
3484
+ });
3485
+ leaseTransferred = true;
3486
+ emitSnapshot(job);
3487
+ emitPhase(job, "Question queued.");
3488
+ job.runPromise = run(job);
3489
+ return publicSnapshot(job);
3490
+ } catch (error) {
3491
+ throw error instanceof ContextualAskError ? error : new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", {
3492
+ cause: error
3493
+ });
3494
+ } finally {
3495
+ if (!leaseTransferred) lease.release();
3496
+ pendingRequests.delete(request.requestId);
3497
+ }
3498
+ })();
3499
+ pendingRequests.set(request.requestId, {
3500
+ controller,
3501
+ fingerprint: requestFingerprint,
3502
+ promise: creation
3503
+ });
3504
+ return await creation;
3505
+ },
3506
+ cancel(jobId) {
3507
+ const job = requireJob(jobId);
3508
+ if (TERMINAL_STATUSES.has(job.status)) return publicSnapshot(job);
3509
+ if (job.status !== "cancelling") {
3510
+ transition(job, "cancelling", "Cancelling question.");
3511
+ job.controller.abort(new ContextualAskError("ASK_CANCELLED"));
3512
+ job.snapshot?.dispose();
3513
+ job.snapshot = void 0;
3514
+ }
3515
+ return publicSnapshot(job);
3516
+ },
3517
+ events(jobId, afterSequence = 0) {
3518
+ if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {
3519
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3520
+ }
3521
+ return Object.freeze(
3522
+ requireJob(jobId).events.filter((event) => event.sequence > afterSequence)
3523
+ );
3524
+ },
3525
+ async result(jobId) {
3526
+ const job = requireJob(jobId);
3527
+ await job.runPromise;
3528
+ const current = requireJob(jobId);
3529
+ return import_shared11.askJobResultResponseSchema.parse({
3530
+ snapshot: publicSnapshot(current),
3531
+ ...current.result === void 0 ? {} : { result: current.result }
3532
+ });
3533
+ },
3534
+ snapshot(jobId) {
3535
+ return publicSnapshot(requireJob(jobId));
3536
+ },
3537
+ subscribe(jobId, listener) {
3538
+ const job = requireJob(jobId);
3539
+ if (job.listeners.size >= import_shared11.CONTEXTUAL_ASK_LIMITS.maximumEventSubscribers) {
3540
+ throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
3541
+ }
3542
+ job.listeners.add(listener);
3543
+ return () => job.listeners.delete(listener);
3544
+ },
3545
+ async close() {
3546
+ if (closed) return;
3547
+ closed = true;
3548
+ for (const pending of pendingRequests.values()) {
3549
+ pending.controller.abort(new ContextualAskError("ASK_CANCELLED"));
3550
+ }
3551
+ await Promise.allSettled(
3552
+ [...pendingRequests.values()].map((pending) => pending.promise)
3553
+ );
3554
+ for (const job of jobs.values()) {
3555
+ if (ACTIVE_STATUSES.has(job.status)) {
3556
+ job.controller.abort(new ContextualAskError("ASK_CANCELLED"));
3557
+ }
3558
+ }
3559
+ await Promise.allSettled(
3560
+ [...jobs.values()].map((job) => job.runPromise ?? Promise.resolve())
3561
+ );
3562
+ for (const job of jobs.values()) {
3563
+ job.snapshot?.dispose();
3564
+ job.listeners.clear();
3565
+ job.lease.release();
3566
+ }
3567
+ jobs.clear();
3568
+ requests.clear();
3569
+ pendingRequests.clear();
3570
+ await Promise.allSettled(
3571
+ [...executors.values()].map(async (executor) => executor.dispose?.())
3572
+ );
3573
+ }
3574
+ });
3575
+ }
3576
+
3577
+ // src/contextual-ask/configured-key-executors.ts
3578
+ var import_agent3 = require("@spotpatch/agent");
3579
+ function createConfiguredKeyAskExecutors(options) {
3580
+ const preferred = options.defaultExecutor?.kind === "configured-key" ? options.defaultExecutor : Object.freeze({
3581
+ kind: "configured-key",
3582
+ providerProfileId: options.ai.defaultProvider,
3583
+ modelProfileId: options.ai.providers[options.ai.defaultProvider]?.defaultModel ?? ""
3584
+ });
3585
+ const providers = Object.values(options.ai.providers).sort(
3586
+ (left, right) => left.id === preferred.providerProfileId ? -1 : right.id === preferred.providerProfileId ? 1 : 0
3587
+ );
3588
+ return Object.freeze(
3589
+ providers.flatMap((provider) => {
3590
+ const credential = (0, import_agent3.resolveProviderCredential)(
3591
+ provider.apiKeyEnv,
3592
+ options.environment
3593
+ );
3594
+ const models = Object.values(provider.models).sort(
3595
+ (left, right) => provider.id !== preferred.providerProfileId ? 0 : left.id === preferred.modelProfileId ? -1 : right.id === preferred.modelProfileId ? 1 : 0
3596
+ );
3597
+ return models.map(
3598
+ (model) => (0, import_agent3.createConfiguredKeyAskExecutor)({
3599
+ provider,
3600
+ model,
3601
+ credential,
3602
+ limits: options.ai.execution.limits,
3603
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch }
3604
+ })
3605
+ );
3606
+ })
3607
+ );
3608
+ }
3609
+
3610
+ // src/contextual-ask/executors.ts
3611
+ function composeContextualAskExecutors(options) {
3612
+ return Object.freeze(
3613
+ options.defaultExecutor?.kind === "managed-codex" ? [options.managedCodex, ...options.configuredKey] : [...options.configuredKey, options.managedCodex]
3614
+ );
3615
+ }
3616
+
3617
+ // src/workspace/activity-coordinator.ts
3618
+ function createWorkspaceActivityCoordinator() {
3619
+ let active;
3620
+ return Object.freeze({
3621
+ acquire(kind) {
3622
+ if (active !== void 0) return void 0;
3623
+ active = kind;
3624
+ let released = false;
3625
+ return Object.freeze({
3626
+ release() {
3627
+ if (released) return;
3628
+ released = true;
3629
+ if (active === kind) active = void 0;
3630
+ }
3631
+ });
3632
+ },
3633
+ current() {
3634
+ return active;
3635
+ }
3636
+ });
3637
+ }
3638
+
3639
+ // src/server/middleware.ts
3640
+ var import_shared23 = require("@spotpatch/shared");
3641
+
3642
+ // src/contextual-ask/http.ts
3643
+ var import_shared12 = require("@spotpatch/shared");
3644
+ var STATUS_BY_ERROR = Object.freeze({
3645
+ ASK_DISABLED: 404,
3646
+ ASK_SELECTION_REQUIRED: 422,
3647
+ ASK_SELECTION_STALE: 409,
3648
+ ASK_QUESTION_INVALID: 422,
3649
+ ASK_EXECUTOR_UNAVAILABLE: 503,
3650
+ ASK_TIMEOUT: 504,
3651
+ ASK_CONSENT_REQUIRED: 403,
3652
+ ASK_BUSY: 409,
3653
+ ASK_IDEMPOTENCY_CONFLICT: 409,
3654
+ ASK_SOURCE_SCOPE_DENIED: 403,
3655
+ ASK_LIMIT_EXCEEDED: 413,
3656
+ ASK_ANSWER_INVALID: 502,
3657
+ ASK_WRITE_ATTEMPTED: 403,
3658
+ ASK_CANCELLED: 409,
3659
+ ASK_RESULT_EXPIRED: 410,
3660
+ ASK_PROTOCOL_INCOMPATIBLE: 400
3661
+ });
3662
+ var PUBLIC_MESSAGES = Object.freeze({
3663
+ ASK_DISABLED: "Contextual Ask is not enabled.",
3664
+ ASK_SELECTION_REQUIRED: "Select at least one source-backed element.",
3665
+ ASK_SELECTION_STALE: "The selected source is stale.",
3666
+ ASK_QUESTION_INVALID: "The question is invalid.",
3667
+ ASK_EXECUTOR_UNAVAILABLE: "The selected Ask executor is unavailable.",
3668
+ ASK_TIMEOUT: "The Ask executor did not answer before the deadline.",
3669
+ ASK_CONSENT_REQUIRED: "Provider data consent is required.",
3670
+ ASK_BUSY: "Another workspace task is active.",
3671
+ ASK_IDEMPOTENCY_CONFLICT: "The request ID was reused with different content.",
3672
+ ASK_SOURCE_SCOPE_DENIED: "The requested source scope was denied.",
3673
+ ASK_LIMIT_EXCEEDED: "The Ask request exceeded a safety limit.",
3674
+ ASK_ANSWER_INVALID: "The executor returned an invalid answer.",
3675
+ ASK_WRITE_ATTEMPTED: "Ask executors cannot write project files.",
3676
+ ASK_CANCELLED: "The Ask job was cancelled.",
3677
+ ASK_RESULT_EXPIRED: "The Ask result has expired.",
3678
+ ASK_PROTOCOL_INCOMPATIBLE: "The Ask protocol request is incompatible."
3679
+ });
3680
+ function writeJson2(response, status, data) {
3681
+ response.statusCode = status;
3682
+ response.setHeader("Cache-Control", "no-store");
3683
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
3684
+ response.end(JSON.stringify({ ok: true, data }));
3685
+ }
3686
+ function writeError(response, error, logger) {
3687
+ const normalized = error instanceof ContextualAskError ? error : new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", { cause: error });
3688
+ if (normalized.code === "ASK_EXECUTOR_UNAVAILABLE" && error !== normalized) {
3689
+ logger?.warn("[spotpatch:ask] Internal contextual Ask request failure.");
3690
+ }
3691
+ response.statusCode = STATUS_BY_ERROR[normalized.code];
3692
+ response.setHeader("Cache-Control", "no-store");
3693
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
3694
+ response.end(
3695
+ JSON.stringify({
3696
+ ok: false,
3697
+ error: {
3698
+ code: normalized.code,
3699
+ message: PUBLIC_MESSAGES[normalized.code]
3700
+ }
3701
+ })
3702
+ );
3703
+ }
3704
+ function matchContextualAskPath(pathname) {
3705
+ if (pathname === import_shared12.SPOTPATCH_ENDPOINTS.askCapability) {
3706
+ return Object.freeze({ kind: "capability" });
3707
+ }
3708
+ if (pathname === import_shared12.SPOTPATCH_ENDPOINTS.askJobs) {
3709
+ return Object.freeze({ kind: "create" });
3710
+ }
3711
+ const prefix = `${import_shared12.SPOTPATCH_ENDPOINTS.askJobs}/`;
3712
+ if (!pathname.startsWith(prefix)) return void 0;
3713
+ const segments = pathname.slice(prefix.length).split("/");
3714
+ if (segments.length !== 2) return void 0;
3715
+ let jobId;
3716
+ try {
3717
+ jobId = decodeURIComponent(segments[0] ?? "");
3718
+ } catch {
3719
+ return void 0;
3720
+ }
3721
+ const action = segments[1];
3722
+ if (!/^[A-Za-z0-9_-]{1,128}$/u.test(jobId) || action !== "events" && action !== "result" && action !== "cancel") {
3723
+ return void 0;
3724
+ }
3725
+ return Object.freeze({ kind: "job", action, jobId });
3726
+ }
3727
+ function parseAfterSequence(request) {
3728
+ let url;
3729
+ try {
3730
+ url = new URL(request.url ?? "/", "http://spotpatch.invalid");
3731
+ } catch {
3732
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3733
+ }
3734
+ if ([...url.searchParams.keys()].some((key) => key !== "afterSequence")) {
3735
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3736
+ }
3737
+ const raw = url.searchParams.get("afterSequence");
3738
+ if (raw === null) return 0;
3739
+ if (!/^\d+$/u.test(raw)) throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3740
+ const parsed = import_shared12.askJobEventsRequestSchema.safeParse({ afterSequence: Number(raw) });
3741
+ if (!parsed.success || !Number.isSafeInteger(parsed.data.afterSequence)) {
3742
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3743
+ }
3744
+ return parsed.data.afterSequence ?? 0;
3745
+ }
3746
+ function streamEvents(request, response, manager, jobId) {
3747
+ const afterSequence = parseAfterSequence(request);
3748
+ response.statusCode = 200;
3749
+ response.setHeader("Cache-Control", "no-store");
3750
+ response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
3751
+ response.setHeader("X-Content-Type-Options", "nosniff");
3752
+ let unsubscribe = () => void 0;
3753
+ const heartbeat = setInterval(
3754
+ () => response.write("\n"),
3755
+ import_shared12.CONTEXTUAL_ASK_LIMITS.eventHeartbeatMs
3756
+ );
3757
+ heartbeat.unref();
3758
+ const cleanup = () => {
3759
+ clearInterval(heartbeat);
3760
+ unsubscribe();
3761
+ };
3762
+ unsubscribe = manager.subscribe(jobId, (event) => {
3763
+ response.write(`${JSON.stringify(event)}
3764
+ `);
3765
+ if (["answered", "cancelled", "failed"].includes(event.status)) {
3766
+ cleanup();
3767
+ response.end();
3768
+ }
3769
+ });
3770
+ const replay = manager.events(jobId, afterSequence);
3771
+ for (const event of replay) response.write(`${JSON.stringify(event)}
3772
+ `);
3773
+ const latest = replay.at(-1);
3774
+ const currentStatus = latest?.status ?? manager.snapshot(jobId).status;
3775
+ if (["answered", "cancelled", "failed"].includes(currentStatus)) {
3776
+ cleanup();
3777
+ response.end();
3778
+ return;
3779
+ }
3780
+ request.once("close", cleanup);
3781
+ response.once("close", cleanup);
3782
+ }
3783
+ function hasEmptySelection(value) {
3784
+ if (typeof value !== "object" || value === null) return false;
3785
+ const envelope = value.envelope;
3786
+ if (typeof envelope !== "object" || envelope === null) return false;
3787
+ const selection = envelope.selection;
3788
+ if (typeof selection !== "object" || selection === null) return false;
3789
+ return Array.isArray(selection.targets) && selection.targets.length === 0;
3790
+ }
3791
+ function lacksProviderDataConsent(value) {
3792
+ return typeof value === "object" && value !== null && value.providerDataConsent !== true;
3793
+ }
3794
+ async function handleContextualAskRequest(request, response, route, manager, logger) {
3795
+ try {
3796
+ if (manager === void 0) throw new ContextualAskError("ASK_DISABLED");
3797
+ if (route.kind === "capability") {
3798
+ if (request.method !== "GET")
3799
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3800
+ writeJson2(
3801
+ response,
3802
+ 200,
3803
+ await manager.capability(
3804
+ AbortSignal.timeout(import_shared12.CONTEXTUAL_ASK_LIMITS.capabilityTimeoutMs)
3805
+ )
3806
+ );
3807
+ return;
3808
+ }
3809
+ if (route.kind === "create") {
3810
+ if (request.method !== "POST")
3811
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3812
+ const raw = await readJsonRequestBody(
3813
+ request,
3814
+ import_shared12.CONTEXTUAL_ASK_LIMITS.maximumRequestBodyBytes
3815
+ ).catch((error) => {
3816
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE", { cause: error });
3817
+ });
3818
+ const parsed = import_shared12.askJobCreateRequestSchema.safeParse(raw);
3819
+ if (!parsed.success) {
3820
+ throw new ContextualAskError(
3821
+ hasEmptySelection(raw) ? "ASK_SELECTION_REQUIRED" : lacksProviderDataConsent(raw) ? "ASK_CONSENT_REQUIRED" : "ASK_PROTOCOL_INCOMPATIBLE"
3822
+ );
3823
+ }
3824
+ writeJson2(response, 202, await manager.create(parsed.data));
3825
+ return;
3826
+ }
3827
+ if (route.action === "events") {
3828
+ if (request.method !== "GET")
3829
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3830
+ streamEvents(request, response, manager, route.jobId);
3831
+ return;
3832
+ }
3833
+ if (route.action === "result") {
3834
+ if (request.method !== "GET")
3835
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3836
+ writeJson2(response, 200, await manager.result(route.jobId));
3837
+ return;
3838
+ }
3839
+ if (request.method !== "POST")
3840
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3841
+ const action = import_shared12.askJobActionRequestSchema.safeParse(
3842
+ await readJsonRequestBody(request).catch((error) => {
3843
+ throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE", { cause: error });
3844
+ })
3845
+ );
3846
+ if (!action.success) throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
3847
+ writeJson2(response, 200, manager.cancel(route.jobId));
3848
+ } catch (error) {
3849
+ writeError(response, error, logger);
3850
+ }
3851
+ }
3852
+
3853
+ // src/external-handoff/browser-http.ts
3854
+ var import_shared16 = require("@spotpatch/shared");
3855
+
3856
+ // src/server/annotation-authorizer.ts
3857
+ var import_promises7 = require("fs/promises");
3858
+ var import_node_path8 = __toESM(require("path"), 1);
3859
+ var import_shared15 = require("@spotpatch/shared");
3860
+
3861
+ // src/server/source-context.ts
3862
+ var import_promises6 = require("fs/promises");
3863
+ var import_node_path7 = __toESM(require("path"), 1);
3864
+ var import_shared14 = require("@spotpatch/shared");
3865
+
3866
+ // src/server/extract-code-context.ts
3867
+ var import_oxc_parser2 = require("oxc-parser");
3868
+ function isComponentName(name) {
3869
+ return /^[A-Z]/u.test(name);
3870
+ }
3871
+ function unwrapTypeExpression(expression) {
3872
+ switch (expression.type) {
3873
+ case "TSAsExpression":
3874
+ case "TSSatisfiesExpression":
3875
+ case "TSTypeAssertion":
3876
+ case "TSNonNullExpression":
3877
+ case "TSInstantiationExpression":
3878
+ return unwrapTypeExpression(expression.expression);
3879
+ default:
3880
+ return expression;
3881
+ }
3882
+ }
3883
+ function calleeName(expression) {
3884
+ const unwrapped = unwrapTypeExpression(expression);
3885
+ if (unwrapped.type === "Identifier") {
3886
+ return unwrapped.name;
3887
+ }
3888
+ if (unwrapped.type === "MemberExpression" && !unwrapped.computed) {
3889
+ return unwrapped.property.type === "Identifier" ? unwrapped.property.name : void 0;
3890
+ }
3891
+ return void 0;
3892
+ }
3893
+ function isFunctionExpression(expression) {
3894
+ const unwrapped = unwrapTypeExpression(expression);
3895
+ return unwrapped.type === "ArrowFunctionExpression" || unwrapped.type === "FunctionExpression";
3896
+ }
3897
+ function isSupportedComponentInitializer(expression) {
3898
+ const unwrapped = unwrapTypeExpression(expression);
3899
+ if (isFunctionExpression(unwrapped)) {
3900
+ return true;
3901
+ }
3902
+ if (unwrapped.type !== "CallExpression") {
3903
+ return false;
3904
+ }
3905
+ const name = calleeName(unwrapped.callee);
3906
+ if (name !== "memo" && name !== "forwardRef") {
3907
+ return false;
3908
+ }
3909
+ const firstArgument = unwrapped.arguments[0];
3910
+ return firstArgument !== void 0 && firstArgument.type !== "SpreadElement" && (isFunctionExpression(firstArgument) || isSupportedComponentInitializer(firstArgument));
3911
+ }
3912
+ function variableComponent(node) {
3913
+ if (node.id.type !== "Identifier" || !isComponentName(node.id.name) || node.init === null || !isSupportedComponentInitializer(node.init)) {
3914
+ return void 0;
3915
+ }
3916
+ return Object.freeze({ start: node.start, end: node.end, name: node.id.name });
3917
+ }
3918
+ function functionComponent(node) {
3919
+ return node.id !== null && isComponentName(node.id.name) && node.body !== null ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
3920
+ }
3921
+ function isReactComponentSuperclass(expression) {
3922
+ if (expression === null) {
3923
+ return false;
3924
+ }
3925
+ const unwrapped = unwrapTypeExpression(expression);
3926
+ if (unwrapped.type === "Identifier") {
3927
+ return unwrapped.name === "Component" || unwrapped.name === "PureComponent";
3928
+ }
3929
+ return unwrapped.type === "MemberExpression" && !unwrapped.computed && unwrapped.object.type === "Identifier" && unwrapped.object.name === "React" && (unwrapped.property.name === "Component" || unwrapped.property.name === "PureComponent");
3930
+ }
3931
+ function classComponent(node) {
3932
+ return node.id !== null && isComponentName(node.id.name) && isReactComponentSuperclass(node.superClass) ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
3933
+ }
3934
+ function selectedOffset(source, line, column) {
3935
+ const lines = source.split(/\r?\n/u);
3936
+ if (line < 1 || line > lines.length) {
3937
+ return void 0;
3938
+ }
3939
+ const lineStart = lines.slice(0, line - 1).reduce((total, value) => total + value.length + 1, 0);
3940
+ const lineLength = lines[line - 1]?.length ?? 0;
3941
+ return lineStart + Math.min(Math.max(0, column - 1), lineLength);
3942
+ }
3943
+ function findComponentSpan(options) {
3944
+ const offset = selectedOffset(options.source, options.line, options.column);
3945
+ if (offset === void 0) {
3946
+ return void 0;
3947
+ }
3948
+ let parseResult;
3949
+ try {
3950
+ parseResult = (0, import_oxc_parser2.parseSync)(options.sourcePath, options.source, {
3951
+ sourceType: "module"
3952
+ });
3953
+ } catch {
3954
+ return void 0;
3955
+ }
3956
+ if (parseResult.errors.length > 0) {
3957
+ return void 0;
3958
+ }
3959
+ const jsxNodes = [];
3960
+ const components = [];
3961
+ const visitor = new import_oxc_parser2.Visitor({
3962
+ JSXElement(node) {
3963
+ jsxNodes.push(node);
3964
+ },
3965
+ JSXFragment(node) {
3966
+ jsxNodes.push(node);
3967
+ },
3968
+ FunctionDeclaration(node) {
3969
+ const candidate = functionComponent(node);
3970
+ if (candidate !== void 0) {
3971
+ components.push(candidate);
3972
+ }
3973
+ },
3974
+ VariableDeclarator(node) {
3975
+ const candidate = variableComponent(node);
3976
+ if (candidate !== void 0) {
3977
+ components.push(candidate);
3978
+ }
3979
+ },
3980
+ ClassDeclaration(node) {
3981
+ const candidate = classComponent(node);
3982
+ if (candidate !== void 0) {
3983
+ components.push(candidate);
3984
+ }
3985
+ }
3986
+ });
3987
+ visitor.visit(parseResult.program);
3988
+ const selectedJsx = jsxNodes.filter((node) => node.start <= offset && node.end >= offset).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
3989
+ if (selectedJsx === void 0) {
3990
+ return void 0;
3991
+ }
3992
+ return components.filter(
3993
+ (component) => component.start <= selectedJsx.start && component.end >= selectedJsx.end
3994
+ ).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
3995
+ }
3996
+ function lineAtOffset(source, offset) {
3997
+ let line = 1;
3998
+ for (let index = 0; index < offset; index += 1) {
3999
+ if (source[index] === "\n") {
4000
+ line += 1;
4001
+ }
4002
+ }
4003
+ return line;
4004
+ }
4005
+ function componentRange(source, component) {
4006
+ return Object.freeze({
4007
+ startLine: lineAtOffset(source, component.start),
4008
+ endLine: lineAtOffset(source, Math.max(component.start, component.end - 1))
4009
+ });
4010
+ }
4011
+ function truncateSelectedLine(line, column, maxCharacters) {
4012
+ if (line.length <= maxCharacters) {
4013
+ return line;
4014
+ }
4015
+ if (maxCharacters === 1) {
4016
+ return "\u2026";
4017
+ }
4018
+ const contentCharacters = maxCharacters - 2;
4019
+ const desiredStart = Math.max(0, column - 1 - Math.floor(contentCharacters / 2));
4020
+ const start = Math.min(desiredStart, line.length - contentCharacters);
4021
+ const end = start + contentCharacters;
4022
+ return `${start > 0 ? "\u2026" : ""}${line.slice(start, end)}${end < line.length ? "\u2026" : ""}`.slice(
2664
4023
  0,
2665
4024
  maxCharacters
2666
4025
  );
@@ -2731,9 +4090,9 @@ function extractCodeContext(options) {
2731
4090
  }
2732
4091
 
2733
4092
  // src/server/source-file.ts
2734
- var import_promises4 = require("fs/promises");
2735
- var import_node_path5 = __toESM(require("path"), 1);
2736
- var import_shared10 = require("@spotpatch/shared");
4093
+ var import_promises5 = require("fs/promises");
4094
+ var import_node_path6 = __toESM(require("path"), 1);
4095
+ var import_shared13 = require("@spotpatch/shared");
2737
4096
  var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
2738
4097
  function isMissingFileError(error) {
2739
4098
  return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
@@ -2743,56 +4102,56 @@ async function assertInsideRoot(root, candidate) {
2743
4102
  let realCandidate;
2744
4103
  try {
2745
4104
  [realRoot, realCandidate] = await Promise.all([
2746
- (0, import_promises4.realpath)(root),
2747
- (0, import_promises4.realpath)(candidate)
4105
+ (0, import_promises5.realpath)(root),
4106
+ (0, import_promises5.realpath)(candidate)
2748
4107
  ]);
2749
4108
  } catch (error) {
2750
4109
  if (isMissingFileError(error)) {
2751
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
4110
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2752
4111
  cause: error
2753
4112
  });
2754
4113
  }
2755
4114
  throw error;
2756
4115
  }
2757
- const relative = import_node_path5.default.relative(realRoot, realCandidate);
2758
- const outside = relative.startsWith(`..${import_node_path5.default.sep}`) || relative === ".." || import_node_path5.default.isAbsolute(relative);
4116
+ const relative = import_node_path6.default.relative(realRoot, realCandidate);
4117
+ const outside = relative.startsWith(`..${import_node_path6.default.sep}`) || relative === ".." || import_node_path6.default.isAbsolute(relative);
2759
4118
  if (outside) {
2760
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
4119
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
2761
4120
  }
2762
4121
  return realCandidate;
2763
4122
  }
2764
4123
  async function resolveSourceFile(options) {
2765
4124
  const registeredPath = options.registry.resolve(options.fileId);
2766
4125
  if (registeredPath === void 0) {
2767
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
4126
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND);
2768
4127
  }
2769
4128
  const sourcePath = await assertInsideRoot(options.root, registeredPath);
2770
- if (!ALLOWED_EXTENSIONS.has(import_node_path5.default.extname(sourcePath).toLowerCase())) {
2771
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
4129
+ if (!ALLOWED_EXTENSIONS.has(import_node_path6.default.extname(sourcePath).toLowerCase())) {
4130
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND);
2772
4131
  }
2773
4132
  let sourceStat;
2774
4133
  try {
2775
- sourceStat = await (0, import_promises4.stat)(sourcePath);
4134
+ sourceStat = await (0, import_promises5.stat)(sourcePath);
2776
4135
  } catch (error) {
2777
4136
  if (isMissingFileError(error)) {
2778
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
4137
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2779
4138
  cause: error
2780
4139
  });
2781
4140
  }
2782
4141
  throw error;
2783
4142
  }
2784
4143
  if (!sourceStat.isFile()) {
2785
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
4144
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND);
2786
4145
  }
2787
4146
  if (sourceStat.size > MAX_SOURCE_FILE_BYTES) {
2788
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_TOO_LARGE);
4147
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_TOO_LARGE);
2789
4148
  }
2790
4149
  return sourcePath;
2791
4150
  }
2792
4151
 
2793
4152
  // src/server/source-context.ts
2794
4153
  function toDisplayPath(root, sourcePath) {
2795
- return import_node_path6.default.relative(root, sourcePath).split(import_node_path6.default.sep).join("/");
4154
+ return import_node_path7.default.relative(root, sourcePath).split(import_node_path7.default.sep).join("/");
2796
4155
  }
2797
4156
  async function readSourceContext(options) {
2798
4157
  const sourcePath = await resolveSourceFile({
@@ -2802,10 +4161,10 @@ async function readSourceContext(options) {
2802
4161
  });
2803
4162
  let source;
2804
4163
  try {
2805
- source = await (0, import_promises5.readFile)(sourcePath, "utf8");
4164
+ source = await (0, import_promises6.readFile)(sourcePath, "utf8");
2806
4165
  } catch (error) {
2807
4166
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
2808
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
4167
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2809
4168
  cause: error
2810
4169
  });
2811
4170
  }
@@ -2813,13 +4172,13 @@ async function readSourceContext(options) {
2813
4172
  }
2814
4173
  const lines = source.split(/\r?\n/);
2815
4174
  if (options.request.line > lines.length) {
2816
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
4175
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2817
4176
  }
2818
- const extension = import_node_path6.default.extname(sourcePath).toLowerCase();
4177
+ const extension = import_node_path7.default.extname(sourcePath).toLowerCase();
2819
4178
  return extractCodeContext({
2820
4179
  source,
2821
4180
  sourcePath,
2822
- relativePath: toDisplayPath(await (0, import_promises5.realpath)(options.root), sourcePath),
4181
+ relativePath: toDisplayPath(await (0, import_promises6.realpath)(options.root), sourcePath),
2823
4182
  language: extension === ".tsx" ? "tsx" : "jsx",
2824
4183
  line: options.request.line,
2825
4184
  column: options.request.column,
@@ -2831,9 +4190,9 @@ async function readSourceContext(options) {
2831
4190
  // src/server/annotation-authorizer.ts
2832
4191
  function sanitizePageContext(page) {
2833
4192
  return Object.freeze({
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),
4193
+ url: (0, import_shared15.sanitizeUrl)(page.url, page.url),
4194
+ pathname: (0, import_shared15.redactSensitiveText)(page.pathname),
4195
+ title: (0, import_shared15.redactSensitiveText)(page.title),
2837
4196
  viewportWidth: page.viewportWidth,
2838
4197
  viewportHeight: page.viewportHeight,
2839
4198
  devicePixelRatio: page.devicePixelRatio
@@ -2852,18 +4211,18 @@ function compactSourceRef(source) {
2852
4211
  async function authorizeSourceRef(source, registry, root) {
2853
4212
  const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
2854
4213
  if (markerOrigin && (source.fileId === void 0 || source.line === void 0 || source.column === void 0)) {
2855
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4214
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2856
4215
  }
2857
4216
  if (source.fileId === void 0) {
2858
4217
  if (source.origin === "none" && source.relativePath !== void 0) {
2859
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4218
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2860
4219
  }
2861
4220
  return compactSourceRef(source);
2862
4221
  }
2863
4222
  const sourcePath = await resolveSourceFile({ fileId: source.fileId, registry, root });
2864
- const relativePath = import_node_path7.default.relative(await (0, import_promises6.realpath)(root), sourcePath).split(import_node_path7.default.sep).join("/");
4223
+ const relativePath = import_node_path8.default.relative(await (0, import_promises7.realpath)(root), sourcePath).split(import_node_path8.default.sep).join("/");
2865
4224
  if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
2866
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4225
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2867
4226
  }
2868
4227
  return Object.freeze({ ...compactSourceRef(source), relativePath });
2869
4228
  }
@@ -2899,7 +4258,7 @@ async function authorizeTarget(target, input) {
2899
4258
  maxLines: input.options.budget.maxCodeLines
2900
4259
  });
2901
4260
  if (marker === void 0 && target.code !== void 0) {
2902
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4261
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2903
4262
  }
2904
4263
  const code = marker === void 0 ? void 0 : await readSourceContext({
2905
4264
  request: marker,
@@ -2909,7 +4268,7 @@ async function authorizeTarget(target, input) {
2909
4268
  maxLines: input.options.budget.maxCodeLines
2910
4269
  });
2911
4270
  if (target.code !== void 0 && target.code.relativePath !== code?.relativePath) {
2912
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4271
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2913
4272
  }
2914
4273
  return Object.freeze({
2915
4274
  instruction: target.instruction,
@@ -2944,11 +4303,11 @@ async function authorizeTarget(target, input) {
2944
4303
  async function authorizeAnnotation(input) {
2945
4304
  const requestedTargets = input.annotation.targets;
2946
4305
  if (requestedTargets.length > input.options.maxTargets) {
2947
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4306
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2948
4307
  }
2949
4308
  const identities = requestedTargets.map(targetIdentity);
2950
4309
  if (new Set(identities).size !== identities.length) {
2951
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4310
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2952
4311
  }
2953
4312
  const targets = Object.freeze(
2954
4313
  await Promise.all(requestedTargets.map((target) => authorizeTarget(target, input)))
@@ -2964,30 +4323,30 @@ async function authorizeAnnotation(input) {
2964
4323
  }
2965
4324
 
2966
4325
  // src/external-handoff/browser-http.ts
2967
- function matchExternalHandoffBrowserPath(path9) {
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) {
4326
+ function matchExternalHandoffBrowserPath(path10) {
4327
+ if (path10 === import_shared16.SPOTPATCH_ENDPOINTS.externalHandoffCapability) return "capability";
4328
+ if (path10 === import_shared16.SPOTPATCH_ENDPOINTS.externalHandoffPublish) return "publish";
4329
+ if (path10 === import_shared16.SPOTPATCH_ENDPOINTS.externalHandoffStatus) return "status";
4330
+ if (path10 === import_shared16.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery) {
2972
4331
  return "resolve-delivery";
2973
4332
  }
2974
4333
  return void 0;
2975
4334
  }
2976
4335
  function requireService(options) {
2977
4336
  if (!options.options.externalAgent.enabled || options.service === void 0) {
2978
- throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
4337
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
2979
4338
  }
2980
4339
  return options.service;
2981
4340
  }
2982
4341
  function remapAuthorizationError(error) {
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, {
4342
+ if (error instanceof import_shared16.SpotPatchError) {
4343
+ if (error.code === import_shared16.ERROR_CODES.SOURCE_NOT_FOUND || error.code === import_shared16.ERROR_CODES.SOURCE_OUTSIDE_ROOT || error.code === import_shared16.ERROR_CODES.SOURCE_TOO_LARGE) {
4344
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.HANDOFF_SOURCE_STALE, void 0, {
2986
4345
  cause: error
2987
4346
  });
2988
4347
  }
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, {
4348
+ if (error.code === import_shared16.ERROR_CODES.INVALID_REQUEST) {
4349
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.HANDOFF_VALIDATION_FAILED, void 0, {
2991
4350
  cause: error
2992
4351
  });
2993
4352
  }
@@ -2996,38 +4355,38 @@ function remapAuthorizationError(error) {
2996
4355
  }
2997
4356
  async function handleExternalHandoffBrowserRequest(request, response, route, options, writeSuccess) {
2998
4357
  if (request.method !== "POST") {
2999
- throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
4358
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3000
4359
  }
3001
4360
  const service = requireService(options);
3002
4361
  if (route === "capability") {
3003
- const parsed2 = import_shared13.externalHandoffCapabilityRequestSchema.safeParse(
4362
+ const parsed2 = import_shared16.externalHandoffCapabilityRequestSchema.safeParse(
3004
4363
  await readJsonRequestBody(request)
3005
4364
  );
3006
- if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
4365
+ if (!parsed2.success) throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3007
4366
  writeSuccess(response, 200, service.capability());
3008
4367
  return;
3009
4368
  }
3010
4369
  if (route === "status") {
3011
- const parsed2 = import_shared13.externalHandoffStatusRequestSchema.safeParse(
4370
+ const parsed2 = import_shared16.externalHandoffStatusRequestSchema.safeParse(
3012
4371
  await readJsonRequestBody(request)
3013
4372
  );
3014
- if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
4373
+ if (!parsed2.success) throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3015
4374
  writeSuccess(response, 200, service.status(parsed2.data.cursor));
3016
4375
  return;
3017
4376
  }
3018
4377
  if (route === "resolve-delivery") {
3019
- const parsed2 = import_shared13.externalHandoffResolveDeliveryRequestSchema.safeParse(
4378
+ const parsed2 = import_shared16.externalHandoffResolveDeliveryRequestSchema.safeParse(
3020
4379
  await readJsonRequestBody(request)
3021
4380
  );
3022
- if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
4381
+ if (!parsed2.success) throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3023
4382
  writeSuccess(response, 200, service.resolveDelivery(parsed2.data.cursor));
3024
4383
  return;
3025
4384
  }
3026
- const parsed = import_shared13.externalHandoffPublishRequestSchema.safeParse(
3027
- await readJsonRequestBody(request, import_shared13.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
4385
+ const parsed = import_shared16.externalHandoffPublishRequestSchema.safeParse(
4386
+ await readJsonRequestBody(request, import_shared16.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
3028
4387
  );
3029
4388
  if (!parsed.success) {
3030
- throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
4389
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
3031
4390
  }
3032
4391
  const result = await service.publish(parsed.data, async (annotation) => {
3033
4392
  try {
@@ -3045,41 +4404,41 @@ async function handleExternalHandoffBrowserRequest(request, response, route, opt
3045
4404
  }
3046
4405
 
3047
4406
  // src/external-agent/browser-http.ts
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) {
4407
+ var import_shared17 = require("@spotpatch/shared");
4408
+ function matchExternalAgentBrowserPath(path10) {
4409
+ if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentControlStatus) return "status";
4410
+ if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentControlConnect) return "connect";
4411
+ if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentControlDisconnect) {
3053
4412
  return "disconnect";
3054
4413
  }
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";
4414
+ if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentControlCancel) return "cancel";
4415
+ if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentEvents) return "events";
4416
+ if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentResult) return "result";
3058
4417
  return void 0;
3059
4418
  }
3060
4419
  function writeEvent(response, event) {
3061
- response.write(`${JSON.stringify(import_shared14.externalAgentEventSchema.parse(event))}
4420
+ response.write(`${JSON.stringify(import_shared17.externalAgentEventSchema.parse(event))}
3062
4421
  `);
3063
4422
  }
3064
4423
  function createExternalAgentBrowserController(port) {
3065
4424
  const streams = /* @__PURE__ */ new Set();
3066
4425
  let disposed = false;
3067
4426
  const handleEvents = async (request, response) => {
3068
- const parsed = import_shared14.externalAgentEventsRequestSchema.safeParse(
4427
+ const parsed = import_shared17.externalAgentEventsRequestSchema.safeParse(
3069
4428
  await readJsonRequestBody(request)
3070
4429
  );
3071
4430
  if (!parsed.success) {
3072
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4431
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3073
4432
  }
3074
- if (disposed || streams.size >= import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
3075
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.BRIDGE_BUSY);
4433
+ if (disposed || streams.size >= import_shared17.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
4434
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.BRIDGE_BUSY);
3076
4435
  }
3077
4436
  response.statusCode = 200;
3078
4437
  response.setHeader("Cache-Control", "no-store");
3079
4438
  response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
3080
4439
  response.setHeader("X-Content-Type-Options", "nosniff");
3081
4440
  streams.add(response);
3082
- const initial = import_shared14.externalAgentControlStatusSchema.parse(port.getStatus());
4441
+ const initial = import_shared17.externalAgentControlStatusSchema.parse(port.getStatus());
3083
4442
  writeEvent(response, { type: "status", data: initial });
3084
4443
  const unsubscribe = port.subscribe((status) => {
3085
4444
  if (!response.destroyed && status.sequence > (parsed.data.afterSequence ?? -1)) {
@@ -3095,7 +4454,7 @@ function createExternalAgentBrowserController(port) {
3095
4454
  emittedAt: (/* @__PURE__ */ new Date()).toISOString()
3096
4455
  });
3097
4456
  }
3098
- }, import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
4457
+ }, import_shared17.EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
3099
4458
  heartbeat.unref();
3100
4459
  await new Promise((resolve) => {
3101
4460
  const close = () => {
@@ -3111,29 +4470,29 @@ function createExternalAgentBrowserController(port) {
3111
4470
  return Object.freeze({
3112
4471
  async handle(request, response, route, writeSuccess) {
3113
4472
  if (request.method !== "POST" || disposed) {
3114
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4473
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3115
4474
  }
3116
4475
  if (route === "events") {
3117
4476
  await handleEvents(request, response);
3118
4477
  return;
3119
4478
  }
3120
4479
  if (route === "status") {
3121
- const parsed2 = import_shared14.externalAgentControlStatusRequestSchema.safeParse(
4480
+ const parsed2 = import_shared17.externalAgentControlStatusRequestSchema.safeParse(
3122
4481
  await readJsonRequestBody(request)
3123
4482
  );
3124
- if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4483
+ if (!parsed2.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3125
4484
  writeSuccess(
3126
4485
  response,
3127
4486
  200,
3128
- import_shared14.externalAgentControlStatusSchema.parse(port.getStatus())
4487
+ import_shared17.externalAgentControlStatusSchema.parse(port.getStatus())
3129
4488
  );
3130
4489
  return;
3131
4490
  }
3132
4491
  if (route === "connect") {
3133
- const parsed2 = import_shared14.externalAgentControlConnectRequestSchema.safeParse(
4492
+ const parsed2 = import_shared17.externalAgentControlConnectRequestSchema.safeParse(
3134
4493
  await readJsonRequestBody(request)
3135
4494
  );
3136
- if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4495
+ if (!parsed2.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3137
4496
  const controller = new AbortController();
3138
4497
  response.once("close", () => {
3139
4498
  if (!response.writableEnded) controller.abort("browser-disconnected");
@@ -3141,45 +4500,45 @@ function createExternalAgentBrowserController(port) {
3141
4500
  writeSuccess(
3142
4501
  response,
3143
4502
  200,
3144
- import_shared14.externalAgentControlStatusSchema.parse(
4503
+ import_shared17.externalAgentControlStatusSchema.parse(
3145
4504
  await port.connect(parsed2.data, controller.signal)
3146
4505
  )
3147
4506
  );
3148
4507
  return;
3149
4508
  }
3150
4509
  if (route === "disconnect") {
3151
- const parsed2 = import_shared14.externalAgentControlDisconnectRequestSchema.safeParse(
4510
+ const parsed2 = import_shared17.externalAgentControlDisconnectRequestSchema.safeParse(
3152
4511
  await readJsonRequestBody(request)
3153
4512
  );
3154
- if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4513
+ if (!parsed2.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3155
4514
  writeSuccess(
3156
4515
  response,
3157
4516
  200,
3158
- import_shared14.externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
4517
+ import_shared17.externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
3159
4518
  );
3160
4519
  return;
3161
4520
  }
3162
4521
  if (route === "cancel") {
3163
- const parsed2 = import_shared14.externalAgentControlCancelRequestSchema.safeParse(
4522
+ const parsed2 = import_shared17.externalAgentControlCancelRequestSchema.safeParse(
3164
4523
  await readJsonRequestBody(request)
3165
4524
  );
3166
- if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4525
+ if (!parsed2.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3167
4526
  writeSuccess(
3168
4527
  response,
3169
4528
  200,
3170
- import_shared14.externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
4529
+ import_shared17.externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
3171
4530
  );
3172
4531
  return;
3173
4532
  }
3174
- const parsed = import_shared14.externalAgentResultRequestSchema.safeParse(
4533
+ const parsed = import_shared17.externalAgentResultRequestSchema.safeParse(
3175
4534
  await readJsonRequestBody(request)
3176
4535
  );
3177
- if (!parsed.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4536
+ if (!parsed.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3178
4537
  const result = port.getResult(parsed.data.revision);
3179
4538
  if (result === void 0) {
3180
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.HANDOFF_NOT_FOUND);
4539
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.HANDOFF_NOT_FOUND);
3181
4540
  }
3182
- writeSuccess(response, 200, import_shared14.externalAgentManagedResultSchema.parse(result));
4541
+ writeSuccess(response, 200, import_shared17.externalAgentManagedResultSchema.parse(result));
3183
4542
  },
3184
4543
  dispose() {
3185
4544
  if (disposed) return;
@@ -3191,10 +4550,10 @@ function createExternalAgentBrowserController(port) {
3191
4550
  }
3192
4551
 
3193
4552
  // src/server/agent-http.ts
3194
- var import_shared16 = require("@spotpatch/shared");
4553
+ var import_shared19 = require("@spotpatch/shared");
3195
4554
 
3196
4555
  // src/server/agent-request.ts
3197
- var import_shared15 = require("@spotpatch/shared");
4556
+ var import_shared18 = require("@spotpatch/shared");
3198
4557
  async function authorizeAgentJobRequest(input) {
3199
4558
  const annotation = await authorizeAnnotation({
3200
4559
  annotation: input.request.annotation,
@@ -3230,21 +4589,21 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
3230
4589
  "reverted",
3231
4590
  "failed"
3232
4591
  ]);
3233
- function matchAgentRequestPath(path9) {
3234
- if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentCapability) {
4592
+ function matchAgentRequestPath(path10) {
4593
+ if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentCapability) {
3235
4594
  return Object.freeze({ kind: "capability" });
3236
4595
  }
3237
- if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
4596
+ if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
3238
4597
  return Object.freeze({ kind: "workspace-health" });
3239
4598
  }
3240
- if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentJobs) {
4599
+ if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentJobs) {
3241
4600
  return Object.freeze({ kind: "create-job" });
3242
4601
  }
3243
- const prefix = `${import_shared16.SPOTPATCH_ENDPOINTS.agentJobs}/`;
3244
- if (!path9.startsWith(prefix)) {
4602
+ const prefix = `${import_shared19.SPOTPATCH_ENDPOINTS.agentJobs}/`;
4603
+ if (!path10.startsWith(prefix)) {
3245
4604
  return void 0;
3246
4605
  }
3247
- const segments = path9.slice(prefix.length).split("/");
4606
+ const segments = path10.slice(prefix.length).split("/");
3248
4607
  const jobId = segments[0];
3249
4608
  const action = segments[1];
3250
4609
  if (segments.length !== 2 || jobId === void 0 || !AGENT_JOB_ID_PATTERN.test(jobId) || action === void 0 || !AGENT_JOB_ACTIONS.has(action)) {
@@ -3258,7 +4617,7 @@ function matchAgentRequestPath(path9) {
3258
4617
  }
3259
4618
  function requireAgentManager(options) {
3260
4619
  if (options.agentManager === void 0 || options.options.ai === false) {
3261
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.AI_DISABLED);
4620
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.AI_DISABLED);
3262
4621
  }
3263
4622
  return options.agentManager;
3264
4623
  }
@@ -3311,13 +4670,13 @@ function streamAgentJobEvents(response, manager, jobId) {
3311
4670
  }
3312
4671
  async function handleCapability(request, response, options, writeSuccess) {
3313
4672
  if (request.method !== "POST") {
3314
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4673
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3315
4674
  }
3316
- const parsed = import_shared16.agentCapabilityRequestSchema.safeParse(
4675
+ const parsed = import_shared19.agentCapabilityRequestSchema.safeParse(
3317
4676
  await readJsonRequestBody(request)
3318
4677
  );
3319
4678
  if (!parsed.success) {
3320
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4679
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3321
4680
  }
3322
4681
  const controller = new AbortController();
3323
4682
  const abort = () => {
@@ -3336,13 +4695,13 @@ async function handleCapability(request, response, options, writeSuccess) {
3336
4695
  }
3337
4696
  async function handleCreateJob(request, response, options, writeSuccess) {
3338
4697
  if (request.method !== "POST") {
3339
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4698
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3340
4699
  }
3341
- const parsed = import_shared16.agentJobCreateRequestSchema.safeParse(
4700
+ const parsed = import_shared19.agentJobCreateRequestSchema.safeParse(
3342
4701
  await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
3343
4702
  );
3344
4703
  if (!parsed.success) {
3345
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4704
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3346
4705
  }
3347
4706
  const authorizedRequest = await authorizeAgentJobRequest({
3348
4707
  request: parsed.data,
@@ -3355,13 +4714,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
3355
4714
  }
3356
4715
  async function handleWorkspaceHealth(request, response, options, writeSuccess) {
3357
4716
  if (request.method !== "POST") {
3358
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4717
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3359
4718
  }
3360
- const parsed = import_shared16.agentWorkspaceHealthRequestSchema.safeParse(
4719
+ const parsed = import_shared19.agentWorkspaceHealthRequestSchema.safeParse(
3361
4720
  await readJsonRequestBody(request)
3362
4721
  );
3363
4722
  if (!parsed.success) {
3364
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4723
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3365
4724
  }
3366
4725
  const controller = new AbortController();
3367
4726
  const abort = () => {
@@ -3378,13 +4737,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
3378
4737
  async function handleJobAction(request, response, options, route, writeSuccess) {
3379
4738
  const manager = requireAgentManager(options);
3380
4739
  if (request.method !== "POST") {
3381
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4740
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3382
4741
  }
3383
- const parsed = import_shared16.agentJobActionRequestSchema.safeParse(
4742
+ const parsed = import_shared19.agentJobActionRequestSchema.safeParse(
3384
4743
  await readJsonRequestBody(request)
3385
4744
  );
3386
4745
  if (!parsed.success) {
3387
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4746
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3388
4747
  }
3389
4748
  if (route.action === "events") {
3390
4749
  streamAgentJobEvents(response, manager, route.jobId);
@@ -3438,13 +4797,13 @@ function detectIntegratedEditor(environment) {
3438
4797
  function editorCommand(editor) {
3439
4798
  return editor === "cursor" ? "cursor" : "code";
3440
4799
  }
3441
- var DEFAULT_DEPENDENCIES2 = Object.freeze({
4800
+ var DEFAULT_DEPENDENCIES3 = Object.freeze({
3442
4801
  environment: process.env,
3443
4802
  fallbackLauncher: import_launch_editor.default,
3444
4803
  processSpawner: (command, arguments_, options) => (0, import_node_child_process2.spawn)(command, [...arguments_], options),
3445
4804
  startupGraceMs: EDITOR_STARTUP_GRACE_MS
3446
4805
  });
3447
- function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
4806
+ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES3) {
3448
4807
  return (target, configuredEditor) => {
3449
4808
  const integratedEditor = detectIntegratedEditor(dependencies.environment);
3450
4809
  const resolvedEditor = configuredEditor === "auto" ? integratedEditor : configuredEditor;
@@ -3499,27 +4858,27 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
3499
4858
  var launchConfiguredEditor = createEditorLauncher();
3500
4859
 
3501
4860
  // src/server/data-flow-http.ts
3502
- var import_node_crypto9 = require("crypto");
4861
+ var import_node_crypto11 = require("crypto");
3503
4862
  var import_analyzer = require("@spotpatch/analyzer");
3504
- var import_shared17 = require("@spotpatch/shared");
4863
+ var import_shared20 = require("@spotpatch/shared");
3505
4864
  function envelopeBytes(report) {
3506
4865
  return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
3507
4866
  }
3508
4867
  function limitDataFlowReportToBytes(report, maximumBytes) {
3509
- const structurallyLimited = (0, import_shared17.limitDataFlowReportCollections)(report);
4868
+ const structurallyLimited = (0, import_shared20.limitDataFlowReportCollections)(report);
3510
4869
  if (envelopeBytes(structurallyLimited) <= maximumBytes) {
3511
4870
  return structurallyLimited;
3512
4871
  }
3513
- let limited = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
4872
+ let limited = (0, import_shared20.limitDataFlowReportCollections)(structurallyLimited, {
3514
4873
  forceTruncation: true,
3515
4874
  maximumDependencies: 0,
3516
4875
  truncatedBy: "bytes"
3517
4876
  });
3518
4877
  if (envelopeBytes(limited) > maximumBytes) {
3519
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INTERNAL_ERROR);
4878
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INTERNAL_ERROR);
3520
4879
  }
3521
4880
  for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
3522
- const candidate = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
4881
+ const candidate = (0, import_shared20.limitDataFlowReportCollections)(structurallyLimited, {
3523
4882
  forceTruncation: true,
3524
4883
  maximumDependencies,
3525
4884
  truncatedBy: "bytes"
@@ -3545,7 +4904,7 @@ async function analyzeTarget(request, analyzer, options) {
3545
4904
  request.componentSourceId
3546
4905
  );
3547
4906
  if (anchor?.sourceVersion !== request.sourceVersion) {
3548
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
4907
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
3549
4908
  }
3550
4909
  return anchor;
3551
4910
  }
@@ -3562,7 +4921,7 @@ async function analyzeTarget(request, analyzer, options) {
3562
4921
  column: resolvedRequest.column
3563
4922
  });
3564
4923
  if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
3565
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
4924
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
3566
4925
  }
3567
4926
  return limitDataFlowReportToBytes(
3568
4927
  report,
@@ -3571,31 +4930,31 @@ async function analyzeTarget(request, analyzer, options) {
3571
4930
  }
3572
4931
  function requireAnalyzer(analyzer) {
3573
4932
  if (analyzer === void 0) {
3574
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_DISABLED);
4933
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.DATA_FLOW_DISABLED);
3575
4934
  }
3576
4935
  return analyzer;
3577
4936
  }
3578
4937
  async function handleComponentDataFlowReport(request, analyzer, options) {
3579
- const parsed = import_shared17.dataFlowComponentReportRequestSchema.safeParse(
4938
+ const parsed = import_shared20.dataFlowComponentReportRequestSchema.safeParse(
3580
4939
  await readJsonRequestBody(
3581
4940
  request,
3582
4941
  options.options.dataFlow.limits.protocolRequestMaxBytes
3583
4942
  )
3584
4943
  );
3585
4944
  if (!parsed.success) {
3586
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
4945
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3587
4946
  }
3588
4947
  return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
3589
4948
  }
3590
4949
  async function handlePageDataFlowReport(request, analyzer, options) {
3591
- const parsed = import_shared17.dataFlowPageReportRequestSchema.safeParse(
4950
+ const parsed = import_shared20.dataFlowPageReportRequestSchema.safeParse(
3592
4951
  await readJsonRequestBody(
3593
4952
  request,
3594
4953
  options.options.dataFlow.limits.protocolRequestMaxBytes
3595
4954
  )
3596
4955
  );
3597
4956
  if (!parsed.success) {
3598
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
4957
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3599
4958
  }
3600
4959
  const activeAnalyzer = requireAnalyzer(analyzer);
3601
4960
  const componentReports = await Promise.all(
@@ -3619,10 +4978,10 @@ async function handlePageDataFlowReport(request, analyzer, options) {
3619
4978
  const analyzedVersions = new Set(
3620
4979
  componentReports.flatMap((report2) => report2.baseline.analyzedSourceVersions)
3621
4980
  );
3622
- const reportId = `page_${(0, import_node_crypto9.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
4981
+ const reportId = `page_${(0, import_node_crypto11.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
3623
4982
  const complete = componentReports.every((report2) => report2.completeness.complete);
3624
4983
  const report = Object.freeze({
3625
- schemaVersion: import_shared17.DATA_FLOW_SCHEMA_VERSION,
4984
+ schemaVersion: import_shared20.DATA_FLOW_SCHEMA_VERSION,
3626
4985
  reportId,
3627
4986
  baseline: Object.freeze({
3628
4987
  registryEpoch: options.session.id,
@@ -3666,9 +5025,9 @@ async function handlePageDataFlowReport(request, analyzer, options) {
3666
5025
  }
3667
5026
 
3668
5027
  // src/server/request-security.ts
3669
- var import_node_crypto10 = require("crypto");
5028
+ var import_node_crypto12 = require("crypto");
3670
5029
  var import_node_net = require("net");
3671
- var import_shared18 = require("@spotpatch/shared");
5030
+ var import_shared21 = require("@spotpatch/shared");
3672
5031
  function getSingleHeader(request, name) {
3673
5032
  const value = request.headers[name.toLowerCase()];
3674
5033
  return Array.isArray(value) ? value[0] : value;
@@ -3679,7 +5038,7 @@ function tokensMatch2(actual, expected) {
3679
5038
  }
3680
5039
  const actualBytes = Buffer.from(actual);
3681
5040
  const expectedBytes = Buffer.from(expected);
3682
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto10.timingSafeEqual)(actualBytes, expectedBytes);
5041
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto12.timingSafeEqual)(actualBytes, expectedBytes);
3683
5042
  }
3684
5043
  function isLoopbackHostname(hostname) {
3685
5044
  const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
@@ -3712,33 +5071,42 @@ function parseOrigin(value) {
3712
5071
  return void 0;
3713
5072
  }
3714
5073
  }
5074
+ function isSameOriginBrowserGet(request) {
5075
+ return request.method === "GET" && getSingleHeader(request, "sec-fetch-site")?.toLowerCase() === "same-origin" && getSingleHeader(request, "sec-fetch-mode")?.toLowerCase() === "cors" && getSingleHeader(request, "sec-fetch-dest")?.toLowerCase() === "empty";
5076
+ }
3715
5077
  function assertRequestAuthorized(request, options) {
3716
- const actualToken = getSingleHeader(request, import_shared18.SPOTPATCH_TOKEN_HEADER);
5078
+ const actualToken = getSingleHeader(request, import_shared21.SPOTPATCH_TOKEN_HEADER);
3717
5079
  if (!tokensMatch2(actualToken, options.sessionToken)) {
3718
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_TOKEN);
5080
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.INVALID_TOKEN);
3719
5081
  }
3720
5082
  const hostHeader = getSingleHeader(request, "host");
3721
5083
  const originHeader = getSingleHeader(request, "origin");
3722
5084
  const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
3723
5085
  const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
3724
- if (host === void 0 || origin === void 0) {
3725
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
5086
+ if (host === void 0) {
5087
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3726
5088
  }
3727
5089
  const hostIsLoopback = isLoopbackHostname(host.hostname);
5090
+ if (origin === void 0) {
5091
+ if (!isSameOriginBrowserGet(request) || !options.allowLan && !hostIsLoopback) {
5092
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.ORIGIN_NOT_ALLOWED);
5093
+ }
5094
+ return;
5095
+ }
3728
5096
  const originIsLoopback = isLoopbackHostname(origin.hostname);
3729
5097
  if (!options.allowLan) {
3730
5098
  if (!hostIsLoopback || !originIsLoopback) {
3731
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
5099
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3732
5100
  }
3733
5101
  return;
3734
5102
  }
3735
5103
  if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
3736
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
5104
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3737
5105
  }
3738
5106
  }
3739
5107
 
3740
5108
  // src/server/runtime-bootstrap.ts
3741
- var import_shared19 = require("@spotpatch/shared");
5109
+ var import_shared22 = require("@spotpatch/shared");
3742
5110
  function getSingleHeader2(request, name) {
3743
5111
  const value = request.headers[name.toLowerCase()];
3744
5112
  return Array.isArray(value) ? value[0] : value;
@@ -3753,7 +5121,7 @@ function resolveRuntimeBootstrapOptions(options) {
3753
5121
  if (expectedOrigin.origin !== options.expectedOrigin || expectedOrigin.protocol !== "http:" || !isLoopbackHostname(expectedOrigin.hostname)) {
3754
5122
  throw new TypeError("The SpotPatch bootstrap origin must be a loopback origin.");
3755
5123
  }
3756
- const parsedConfig = import_shared19.runtimeConfigSchema.safeParse(options.runtimeConfig);
5124
+ const parsedConfig = import_shared22.runtimeConfigSchema.safeParse(options.runtimeConfig);
3757
5125
  if (!parsedConfig.success) {
3758
5126
  throw new TypeError("The SpotPatch Runtime configuration is invalid.");
3759
5127
  }
@@ -3765,7 +5133,7 @@ function resolveRuntimeBootstrapOptions(options) {
3765
5133
  function assertRuntimeBootstrapRequest(request, expectedOrigin) {
3766
5134
  const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
3767
5135
  if (request.method !== "POST" || contentType !== "application/json") {
3768
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
5136
+ throw new import_shared22.SpotPatchError(import_shared22.ERROR_CODES.INVALID_REQUEST);
3769
5137
  }
3770
5138
  const host = getSingleHeader2(request, "host");
3771
5139
  let hostIsLoopback = false;
@@ -3777,152 +5145,152 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
3777
5145
  }
3778
5146
  }
3779
5147
  if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
3780
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.ORIGIN_NOT_ALLOWED);
5148
+ throw new import_shared22.SpotPatchError(import_shared22.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3781
5149
  }
3782
5150
  }
3783
5151
  async function readRuntimeBootstrap(request, options) {
3784
5152
  assertRuntimeBootstrapRequest(request, options.expectedOrigin);
3785
- const parsedBody = import_shared19.runtimeBootstrapRequestSchema.safeParse(
5153
+ const parsedBody = import_shared22.runtimeBootstrapRequestSchema.safeParse(
3786
5154
  await readJsonRequestBody(request)
3787
5155
  );
3788
5156
  if (!parsedBody.success) {
3789
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
5157
+ throw new import_shared22.SpotPatchError(import_shared22.ERROR_CODES.INVALID_REQUEST);
3790
5158
  }
3791
5159
  return options.runtimeConfig;
3792
5160
  }
3793
5161
 
3794
5162
  // src/server/middleware.ts
3795
- var STATUS_BY_ERROR = Object.freeze({
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
5163
+ var STATUS_BY_ERROR2 = Object.freeze({
5164
+ [import_shared23.ERROR_CODES.INVALID_REQUEST]: 400,
5165
+ [import_shared23.ERROR_CODES.INVALID_TOKEN]: 401,
5166
+ [import_shared23.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
5167
+ [import_shared23.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
5168
+ [import_shared23.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
5169
+ [import_shared23.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
5170
+ [import_shared23.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
5171
+ [import_shared23.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
5172
+ [import_shared23.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
5173
+ [import_shared23.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
5174
+ [import_shared23.ERROR_CODES.AI_DISABLED]: 404,
5175
+ [import_shared23.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
5176
+ [import_shared23.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
5177
+ [import_shared23.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
5178
+ [import_shared23.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
5179
+ [import_shared23.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
5180
+ [import_shared23.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
5181
+ [import_shared23.ERROR_CODES.AGENT_BUSY]: 409,
5182
+ [import_shared23.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
5183
+ [import_shared23.ERROR_CODES.AGENT_CANCELLED]: 409,
5184
+ [import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: 404,
5185
+ [import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
5186
+ [import_shared23.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: 422,
5187
+ [import_shared23.ERROR_CODES.HANDOFF_SOURCE_STALE]: 409,
5188
+ [import_shared23.ERROR_CODES.HANDOFF_NOT_FOUND]: 404,
5189
+ [import_shared23.ERROR_CODES.HANDOFF_EXPIRED]: 410,
5190
+ [import_shared23.ERROR_CODES.HANDOFF_CURSOR_INVALID]: 409,
5191
+ [import_shared23.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: 413,
5192
+ [import_shared23.ERROR_CODES.BRIDGE_UNAUTHORIZED]: 401,
5193
+ [import_shared23.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: 409,
5194
+ [import_shared23.ERROR_CODES.BRIDGE_BUSY]: 429,
5195
+ [import_shared23.ERROR_CODES.EXTERNAL_AGENT_BUSY]: 409,
5196
+ [import_shared23.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: 409,
5197
+ [import_shared23.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
5198
+ [import_shared23.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: 409,
5199
+ [import_shared23.ERROR_CODES.SESSION_NOT_FOUND]: 404,
5200
+ [import_shared23.ERROR_CODES.SESSION_AMBIGUOUS]: 409,
5201
+ [import_shared23.ERROR_CODES.SESSION_CLOSED]: 410,
5202
+ [import_shared23.ERROR_CODES.WORKTREE_DIRTY]: 409,
5203
+ [import_shared23.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
5204
+ [import_shared23.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
5205
+ [import_shared23.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
5206
+ [import_shared23.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
5207
+ [import_shared23.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
5208
+ [import_shared23.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
5209
+ [import_shared23.ERROR_CODES.TOOL_DENIED]: 403,
5210
+ [import_shared23.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
5211
+ [import_shared23.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
5212
+ [import_shared23.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
5213
+ [import_shared23.ERROR_CODES.TOOL_PATH_DENIED]: 403,
5214
+ [import_shared23.ERROR_CODES.PATCH_REJECTED]: 422,
5215
+ [import_shared23.ERROR_CODES.VALIDATION_FAILED]: 422,
5216
+ [import_shared23.ERROR_CODES.APPLY_CONFLICT]: 409,
5217
+ [import_shared23.ERROR_CODES.INTERNAL_ERROR]: 500
3850
5218
  });
3851
- var PUBLIC_MESSAGES = Object.freeze({
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."
5219
+ var PUBLIC_MESSAGES2 = Object.freeze({
5220
+ [import_shared23.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
5221
+ [import_shared23.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
5222
+ [import_shared23.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
5223
+ [import_shared23.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
5224
+ [import_shared23.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
5225
+ [import_shared23.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
5226
+ [import_shared23.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
5227
+ [import_shared23.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
5228
+ [import_shared23.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
5229
+ [import_shared23.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
5230
+ [import_shared23.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
5231
+ [import_shared23.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
5232
+ [import_shared23.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
5233
+ [import_shared23.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
5234
+ [import_shared23.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
5235
+ [import_shared23.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
5236
+ [import_shared23.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
5237
+ [import_shared23.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
5238
+ [import_shared23.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
5239
+ [import_shared23.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
5240
+ [import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
5241
+ [import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
5242
+ [import_shared23.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
5243
+ [import_shared23.ERROR_CODES.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
5244
+ [import_shared23.ERROR_CODES.HANDOFF_NOT_FOUND]: "No current handoff is available.",
5245
+ [import_shared23.ERROR_CODES.HANDOFF_EXPIRED]: "The handoff has expired.",
5246
+ [import_shared23.ERROR_CODES.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
5247
+ [import_shared23.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
5248
+ [import_shared23.ERROR_CODES.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
5249
+ [import_shared23.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
5250
+ [import_shared23.ERROR_CODES.BRIDGE_BUSY]: "The local bridge is busy.",
5251
+ [import_shared23.ERROR_CODES.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
5252
+ [import_shared23.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
5253
+ [import_shared23.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
5254
+ [import_shared23.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
5255
+ [import_shared23.ERROR_CODES.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
5256
+ [import_shared23.ERROR_CODES.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
5257
+ [import_shared23.ERROR_CODES.SESSION_CLOSED]: "The SpotPatch session has closed.",
5258
+ [import_shared23.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
5259
+ [import_shared23.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
5260
+ [import_shared23.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
5261
+ [import_shared23.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
5262
+ [import_shared23.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
5263
+ [import_shared23.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
5264
+ [import_shared23.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
5265
+ [import_shared23.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
5266
+ [import_shared23.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
5267
+ [import_shared23.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
5268
+ [import_shared23.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
5269
+ [import_shared23.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
5270
+ [import_shared23.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
5271
+ [import_shared23.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
5272
+ [import_shared23.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
5273
+ [import_shared23.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
3906
5274
  });
3907
- function writeJson2(response, status, payload) {
5275
+ function writeJson3(response, status, payload) {
3908
5276
  response.statusCode = status;
3909
5277
  response.setHeader("Cache-Control", "no-store");
3910
5278
  response.setHeader("Content-Type", "application/json; charset=utf-8");
3911
5279
  response.end(JSON.stringify(payload));
3912
5280
  }
3913
5281
  function asSpotPatchError(error) {
3914
- return error instanceof import_shared20.SpotPatchError ? error : new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
5282
+ return error instanceof import_shared23.SpotPatchError ? error : new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
3915
5283
  }
3916
- function writeError(response, error, logger) {
5284
+ function writeError2(response, error, logger) {
3917
5285
  const normalized = asSpotPatchError(error);
3918
- if (normalized.code === import_shared20.ERROR_CODES.INTERNAL_ERROR) {
5286
+ if (normalized.code === import_shared23.ERROR_CODES.INTERNAL_ERROR) {
3919
5287
  logger?.warn("[spotpatch:server] Internal request failure.");
3920
5288
  }
3921
- writeJson2(response, STATUS_BY_ERROR[normalized.code], {
5289
+ writeJson3(response, STATUS_BY_ERROR2[normalized.code], {
3922
5290
  ok: false,
3923
5291
  error: {
3924
5292
  code: normalized.code,
3925
- message: PUBLIC_MESSAGES[normalized.code]
5293
+ message: PUBLIC_MESSAGES2[normalized.code]
3926
5294
  }
3927
5295
  });
3928
5296
  }
@@ -3934,11 +5302,11 @@ function requestPath(request) {
3934
5302
  }
3935
5303
  }
3936
5304
  async function handleSourceContext(request, options) {
3937
- const parsed = import_shared20.sourceContextRequestSchema.safeParse(
5305
+ const parsed = import_shared23.sourceContextRequestSchema.safeParse(
3938
5306
  await readJsonRequestBody(request)
3939
5307
  );
3940
5308
  if (!parsed.success) {
3941
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5309
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
3942
5310
  }
3943
5311
  return readSourceContext({
3944
5312
  request: parsed.data,
@@ -3949,9 +5317,9 @@ async function handleSourceContext(request, options) {
3949
5317
  });
3950
5318
  }
3951
5319
  async function handleOpenEditor(request, options) {
3952
- const parsed = import_shared20.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
5320
+ const parsed = import_shared23.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3953
5321
  if (!parsed.success) {
3954
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5322
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
3955
5323
  }
3956
5324
  const body = parsed.data;
3957
5325
  const sourcePath = await resolveSourceFile({
@@ -3968,7 +5336,7 @@ async function handleOpenEditor(request, options) {
3968
5336
  options.logger?.warn(
3969
5337
  `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
3970
5338
  );
3971
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
5339
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
3972
5340
  cause: error
3973
5341
  });
3974
5342
  }
@@ -3978,73 +5346,84 @@ function createSpotPatchMiddleware(options) {
3978
5346
  const dataFlowAnalyzer = createDataFlowAnalyzer(options);
3979
5347
  const externalAgentController = options.externalAgentControl === void 0 ? void 0 : createExternalAgentBrowserController(options.externalAgentControl);
3980
5348
  const middleware = (request, response, next) => {
3981
- const path9 = requestPath(request);
3982
- const agentRoute = matchAgentRequestPath(path9);
3983
- const externalAgentRoute = matchExternalAgentBrowserPath(path9);
3984
- const externalHandoffRoute = matchExternalHandoffBrowserPath(path9);
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}/`)) {
5349
+ const path10 = requestPath(request);
5350
+ const agentRoute = matchAgentRequestPath(path10);
5351
+ const askRoute = matchContextualAskPath(path10);
5352
+ const externalAgentRoute = matchExternalAgentBrowserPath(path10);
5353
+ const externalHandoffRoute = matchExternalHandoffBrowserPath(path10);
5354
+ if (path10 !== import_shared23.SPOTPATCH_ENDPOINTS.sourceContext && path10 !== import_shared23.SPOTPATCH_ENDPOINTS.openEditor && path10 !== import_shared23.SPOTPATCH_ENDPOINTS.dataFlowComponentReport && path10 !== import_shared23.SPOTPATCH_ENDPOINTS.dataFlowPageReport && agentRoute === void 0 && askRoute === void 0 && externalAgentRoute === void 0 && externalHandoffRoute === void 0 && !path10.startsWith(`${import_shared23.SPOTPATCH_API_BASE}/`)) {
3986
5355
  next();
3987
5356
  return;
3988
5357
  }
3989
5358
  const handle = async () => {
3990
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
5359
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
3991
5360
  const data = await readRuntimeBootstrap(
3992
5361
  request,
3993
5362
  bootstrap
3994
5363
  );
3995
- writeJson2(response, 200, { ok: true, data });
5364
+ writeJson3(response, 200, { ok: true, data });
3996
5365
  return;
3997
5366
  }
3998
5367
  assertRequestAuthorized(request, {
3999
5368
  allowLan: options.options.allowLan,
4000
5369
  sessionToken: options.session.token
4001
5370
  });
4002
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.sourceContext) {
5371
+ if (askRoute !== void 0) {
5372
+ await handleContextualAskRequest(
5373
+ request,
5374
+ response,
5375
+ askRoute,
5376
+ options.contextualAskManager,
5377
+ options.logger
5378
+ );
5379
+ return;
5380
+ }
5381
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.sourceContext) {
4003
5382
  if (request.method !== "POST") {
4004
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5383
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4005
5384
  }
4006
5385
  const data = await handleSourceContext(request, options);
4007
- writeJson2(response, 200, { ok: true, data });
5386
+ writeJson3(response, 200, { ok: true, data });
4008
5387
  return;
4009
5388
  }
4010
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.openEditor) {
5389
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.openEditor) {
4011
5390
  if (request.method !== "POST") {
4012
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5391
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4013
5392
  }
4014
5393
  const data = await handleOpenEditor(request, options);
4015
- writeJson2(response, 200, { ok: true, data });
5394
+ writeJson3(response, 200, { ok: true, data });
4016
5395
  return;
4017
5396
  }
4018
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
5397
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
4019
5398
  if (request.method !== "POST") {
4020
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5399
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4021
5400
  }
4022
5401
  const data = await handleComponentDataFlowReport(
4023
5402
  request,
4024
5403
  dataFlowAnalyzer,
4025
5404
  options
4026
5405
  );
4027
- writeJson2(response, 200, { ok: true, data });
5406
+ writeJson3(response, 200, { ok: true, data });
4028
5407
  return;
4029
5408
  }
4030
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
5409
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
4031
5410
  if (request.method !== "POST") {
4032
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5411
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4033
5412
  }
4034
5413
  const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
4035
- writeJson2(response, 200, { ok: true, data });
5414
+ writeJson3(response, 200, { ok: true, data });
4036
5415
  return;
4037
5416
  }
4038
5417
  if (externalAgentRoute !== void 0) {
4039
5418
  if (!options.options.externalAgent.enabled || externalAgentController === void 0) {
4040
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
5419
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
4041
5420
  }
4042
5421
  await externalAgentController.handle(
4043
5422
  request,
4044
5423
  response,
4045
5424
  externalAgentRoute,
4046
5425
  (target, status, data) => {
4047
- writeJson2(target, status, { ok: true, data });
5426
+ writeJson3(target, status, { ok: true, data });
4048
5427
  }
4049
5428
  );
4050
5429
  return;
@@ -4061,13 +5440,13 @@ function createSpotPatchMiddleware(options) {
4061
5440
  ...options.externalHandoffService === void 0 ? {} : { service: options.externalHandoffService }
4062
5441
  },
4063
5442
  (target, status, data) => {
4064
- writeJson2(target, status, { ok: true, data });
5443
+ writeJson3(target, status, { ok: true, data });
4065
5444
  }
4066
5445
  );
4067
5446
  return;
4068
5447
  }
4069
5448
  if (agentRoute === void 0) {
4070
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5449
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4071
5450
  }
4072
5451
  await handleAgentRequest(
4073
5452
  request,
@@ -4075,12 +5454,12 @@ function createSpotPatchMiddleware(options) {
4075
5454
  options,
4076
5455
  agentRoute,
4077
5456
  (target, status, data) => {
4078
- writeJson2(target, status, { ok: true, data });
5457
+ writeJson3(target, status, { ok: true, data });
4079
5458
  }
4080
5459
  );
4081
5460
  };
4082
5461
  void handle().catch((error) => {
4083
- writeError(response, error, options.logger);
5462
+ writeError2(response, error, options.logger);
4084
5463
  });
4085
5464
  };
4086
5465
  return Object.assign(middleware, {
@@ -4091,18 +5470,30 @@ function createSpotPatchMiddleware(options) {
4091
5470
  }
4092
5471
 
4093
5472
  // src/server/source-registration.ts
4094
- var import_node_crypto11 = require("crypto");
4095
- var import_promises7 = require("fs/promises");
4096
- var import_node_path8 = __toESM(require("path"), 1);
5473
+ var import_node_crypto13 = require("crypto");
5474
+ var import_promises8 = require("fs/promises");
5475
+ var import_node_path9 = __toESM(require("path"), 1);
4097
5476
  var import_compiler = require("@spotpatch/compiler");
5477
+ var import_shared24 = require("@spotpatch/shared");
4098
5478
  var import_zod2 = require("zod");
4099
- var REGISTRATION_BODY_LIMIT_BYTES = 4096;
5479
+ var REGISTRATION_BODY_LIMIT_BYTES = import_shared24.DEFAULT_DATA_FLOW_LIMITS.protocolRequestMaxBytes;
4100
5480
  var REGISTRATION_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{16,128}$/u;
5481
+ var DATA_FLOW_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
4101
5482
  var INTERNAL_SECRET_HEADER = "x-spotpatch-internal";
4102
5483
  var FORBIDDEN_SOURCE_SEGMENTS = /* @__PURE__ */ new Set([".next", "node_modules"]);
4103
5484
  var registrationRequestSchema = import_zod2.z.strictObject({
4104
5485
  epoch: import_zod2.z.string().regex(REGISTRATION_IDENTITY_PATTERN),
4105
- resourcePath: import_zod2.z.string().min(1).max(3072)
5486
+ resourcePath: import_zod2.z.string().min(1).max(3072),
5487
+ dataFlow: import_zod2.z.strictObject({
5488
+ sourceVersion: import_zod2.z.string().regex(DATA_FLOW_IDENTITY_PATTERN),
5489
+ components: import_zod2.z.array(
5490
+ import_zod2.z.strictObject({
5491
+ componentSourceId: import_zod2.z.string().regex(DATA_FLOW_IDENTITY_PATTERN),
5492
+ line: import_zod2.z.number().int().positive().max(1e7),
5493
+ column: import_zod2.z.number().int().positive().max(1e7)
5494
+ })
5495
+ ).max(import_shared24.DEFAULT_DATA_FLOW_LIMITS.sourceMaxComponents)
5496
+ }).optional()
4106
5497
  });
4107
5498
  function getSingleHeader3(request, name) {
4108
5499
  const value = request.headers[name.toLowerCase()];
@@ -4114,16 +5505,16 @@ function identitiesMatch(actual, expected) {
4114
5505
  }
4115
5506
  const actualBytes = Buffer.from(actual);
4116
5507
  const expectedBytes = Buffer.from(expected);
4117
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto11.timingSafeEqual)(actualBytes, expectedBytes);
5508
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto13.timingSafeEqual)(actualBytes, expectedBytes);
4118
5509
  }
4119
5510
  function isWithinRoot(root, candidate) {
4120
- const relative = import_node_path8.default.relative(root, candidate);
4121
- return relative === "" || !relative.startsWith(`..${import_node_path8.default.sep}`) && relative !== ".." && !import_node_path8.default.isAbsolute(relative);
5511
+ const relative = import_node_path9.default.relative(root, candidate);
5512
+ return relative === "" || !relative.startsWith(`..${import_node_path9.default.sep}`) && relative !== ".." && !import_node_path9.default.isAbsolute(relative);
4122
5513
  }
4123
5514
  function hasForbiddenSegment(root, candidate) {
4124
- return import_node_path8.default.relative(root, candidate).split(import_node_path8.default.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
5515
+ return import_node_path9.default.relative(root, candidate).split(import_node_path9.default.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
4125
5516
  }
4126
- function writeJson3(response, statusCode, payload) {
5517
+ function writeJson4(response, statusCode, payload) {
4127
5518
  const body = JSON.stringify(payload);
4128
5519
  response.statusCode = statusCode;
4129
5520
  response.setHeader("Cache-Control", "no-store");
@@ -4143,15 +5534,15 @@ function requestComesFromLoopbackWorker(request) {
4143
5534
  }
4144
5535
  }
4145
5536
  async function resolveAuthorizedSource(root, requestedPath, shouldTransform) {
4146
- if (!import_node_path8.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
5537
+ if (!import_node_path9.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
4147
5538
  return void 0;
4148
5539
  }
4149
5540
  try {
4150
- const sourceStat = await (0, import_promises7.lstat)(requestedPath);
5541
+ const sourceStat = await (0, import_promises8.lstat)(requestedPath);
4151
5542
  if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
4152
5543
  return void 0;
4153
5544
  }
4154
- const resolvedPath = await (0, import_promises7.realpath)(requestedPath);
5545
+ const resolvedPath = await (0, import_promises8.realpath)(requestedPath);
4155
5546
  if (!isWithinRoot(root, resolvedPath) || hasForbiddenSegment(root, resolvedPath) || !shouldTransform(resolvedPath)) {
4156
5547
  return void 0;
4157
5548
  }
@@ -4164,8 +5555,35 @@ async function createSourceRegistrationService(input) {
4164
5555
  if (!REGISTRATION_IDENTITY_PATTERN.test(input.internalSecret) || !REGISTRATION_IDENTITY_PATTERN.test(input.registryEpoch)) {
4165
5556
  throw new TypeError("The source registration identity is invalid.");
4166
5557
  }
4167
- const root = await (0, import_promises7.realpath)(input.root);
5558
+ const root = await (0, import_promises8.realpath)(input.root);
4168
5559
  const sourceFilter = (0, import_compiler.createSourceFilter)(root, input.options);
5560
+ const dataFlowFilter = (0, import_compiler.createDataFlowSourceFilter)(root, input.options);
5561
+ const registrationQueues = /* @__PURE__ */ new Map();
5562
+ async function registerCurrentSource(sourcePath, dataFlow) {
5563
+ if (dataFlow === void 0) {
5564
+ return input.registry.register(sourcePath);
5565
+ }
5566
+ let fileId;
5567
+ const previous = registrationQueues.get(sourcePath) ?? Promise.resolve();
5568
+ const current = previous.catch(() => void 0).then(async () => {
5569
+ const currentSource = await (0, import_promises8.readFile)(sourcePath, "utf8");
5570
+ if ((0, import_compiler.createDataFlowSourceVersion)(currentSource) !== dataFlow.sourceVersion) {
5571
+ return;
5572
+ }
5573
+ fileId = input.registry.registerDataFlowComponents(
5574
+ sourcePath,
5575
+ dataFlow.sourceVersion,
5576
+ dataFlow.components
5577
+ );
5578
+ });
5579
+ registrationQueues.set(sourcePath, current);
5580
+ await current.finally(() => {
5581
+ if (registrationQueues.get(sourcePath) === current) {
5582
+ registrationQueues.delete(sourcePath);
5583
+ }
5584
+ });
5585
+ return fileId;
5586
+ }
4169
5587
  const handler = (request, response) => {
4170
5588
  const handle = async () => {
4171
5589
  const contentType = getSingleHeader3(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
@@ -4173,33 +5591,35 @@ async function createSourceRegistrationService(input) {
4173
5591
  getSingleHeader3(request, INTERNAL_SECRET_HEADER),
4174
5592
  input.internalSecret
4175
5593
  )) {
4176
- writeJson3(response, 403, { ok: false });
5594
+ writeJson4(response, 403, { ok: false });
4177
5595
  return;
4178
5596
  }
4179
5597
  const parsed = registrationRequestSchema.safeParse(
4180
5598
  await readJsonRequestBody(request, REGISTRATION_BODY_LIMIT_BYTES)
4181
5599
  );
4182
5600
  if (!parsed.success || parsed.data.epoch !== input.registryEpoch) {
4183
- writeJson3(response, 400, { ok: false });
5601
+ writeJson4(response, 400, { ok: false });
4184
5602
  return;
4185
5603
  }
4186
5604
  const sourcePath = await resolveAuthorizedSource(
4187
5605
  root,
4188
5606
  parsed.data.resourcePath,
4189
- (absolutePath) => sourceFilter.shouldTransform(absolutePath, "<")
5607
+ (absolutePath) => parsed.data.dataFlow === void 0 ? sourceFilter.shouldTransform(absolutePath, "<") : dataFlowFilter.shouldTransform(absolutePath, "")
4190
5608
  );
4191
5609
  if (sourcePath === void 0) {
4192
- writeJson3(response, 403, { ok: false });
5610
+ writeJson4(response, 403, { ok: false });
4193
5611
  return;
4194
5612
  }
4195
- writeJson3(response, 200, {
4196
- epoch: input.registryEpoch,
4197
- fileId: input.registry.register(sourcePath)
4198
- });
5613
+ const fileId = await registerCurrentSource(sourcePath, parsed.data.dataFlow);
5614
+ if (fileId === void 0) {
5615
+ writeJson4(response, 409, { ok: false });
5616
+ return;
5617
+ }
5618
+ writeJson4(response, 200, { epoch: input.registryEpoch, fileId });
4199
5619
  };
4200
5620
  void handle().catch(() => {
4201
5621
  if (!response.headersSent) {
4202
- writeJson3(response, 400, { ok: false });
5622
+ writeJson4(response, 400, { ok: false });
4203
5623
  } else {
4204
5624
  response.destroy();
4205
5625
  }
@@ -4209,11 +5629,11 @@ async function createSourceRegistrationService(input) {
4209
5629
  }
4210
5630
 
4211
5631
  // src/session/session.ts
4212
- var import_node_crypto12 = require("crypto");
5632
+ var import_node_crypto14 = require("crypto");
4213
5633
  function createSession() {
4214
5634
  return Object.freeze({
4215
- id: (0, import_node_crypto12.randomBytes)(16).toString("base64url"),
4216
- token: (0, import_node_crypto12.randomBytes)(16).toString("base64url")
5635
+ id: (0, import_node_crypto14.randomBytes)(16).toString("base64url"),
5636
+ token: (0, import_node_crypto14.randomBytes)(16).toString("base64url")
4217
5637
  });
4218
5638
  }
4219
5639
 
@@ -4224,6 +5644,7 @@ var OPTION_KEYS = Object.freeze([
4224
5644
  "budget",
4225
5645
  "debug",
4226
5646
  "dataFlow",
5647
+ "contextualAsk",
4227
5648
  "editor",
4228
5649
  "enabled",
4229
5650
  "externalAgent",
@@ -4315,6 +5736,9 @@ function serializeResolvedSpotPatchOptions(options) {
4315
5736
  dataFlow: options.dataFlow.enabled ? Object.freeze({
4316
5737
  runtime: options.dataFlow.runtime
4317
5738
  }) : false,
5739
+ contextualAsk: options.contextualAsk.enabled ? Object.freeze({
5740
+ ...options.contextualAsk.defaultExecutor === void 0 ? {} : { defaultExecutor: options.contextualAsk.defaultExecutor }
5741
+ }) : false,
4318
5742
  editor: options.editor,
4319
5743
  enabled: options.enabled,
4320
5744
  externalAgent: options.externalAgent.enabled,
@@ -4367,6 +5791,33 @@ function parseDataFlow(value) {
4367
5791
  runtime: value.runtime
4368
5792
  });
4369
5793
  }
5794
+ function parseContextualAsk(value) {
5795
+ if (value === false || value === true) return value;
5796
+ if (!isRecord2(value)) {
5797
+ throw new TypeError("The SpotPatch contextual Ask transport is invalid.");
5798
+ }
5799
+ const keys = Object.keys(value);
5800
+ if (keys.length === 0) return Object.freeze({});
5801
+ if (!hasExactKeys(value, ["defaultExecutor"]) || !isRecord2(value.defaultExecutor)) {
5802
+ throw new TypeError("The SpotPatch contextual Ask transport is invalid.");
5803
+ }
5804
+ const preference = value.defaultExecutor;
5805
+ if (preference.kind === "managed-codex" && hasExactKeys(preference, ["kind"])) {
5806
+ return Object.freeze({
5807
+ defaultExecutor: Object.freeze({ kind: "managed-codex" })
5808
+ });
5809
+ }
5810
+ if (preference.kind === "configured-key" && hasExactKeys(preference, ["kind", "modelProfileId", "providerProfileId"]) && typeof preference.providerProfileId === "string" && typeof preference.modelProfileId === "string") {
5811
+ return Object.freeze({
5812
+ defaultExecutor: Object.freeze({
5813
+ kind: "configured-key",
5814
+ providerProfileId: preference.providerProfileId,
5815
+ modelProfileId: preference.modelProfileId
5816
+ })
5817
+ });
5818
+ }
5819
+ throw new TypeError("The SpotPatch contextual Ask transport is invalid.");
5820
+ }
4370
5821
  function parseSerializedSpotPatchOptions(value) {
4371
5822
  if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
4372
5823
  throw new TypeError("The SpotPatch options transport is invalid.");
@@ -4381,6 +5832,7 @@ function parseSerializedSpotPatchOptions(value) {
4381
5832
  budget: parseBudget(value.budget),
4382
5833
  debug: value.debug,
4383
5834
  dataFlow: parseDataFlow(value.dataFlow),
5835
+ contextualAsk: parseContextualAsk(value.contextualAsk),
4384
5836
  editor: value.editor,
4385
5837
  enabled: value.enabled,
4386
5838
  externalAgent: value.externalAgent,
@@ -4399,10 +5851,15 @@ function parseSerializedSpotPatchOptions(value) {
4399
5851
  }
4400
5852
  // Annotate the CommonJS export names for ESM import in node:
4401
5853
  0 && (module.exports = {
5854
+ ContextualAskError,
4402
5855
  DEFAULT_EXCLUDE,
4403
5856
  DEFAULT_OPTIONS,
4404
5857
  applyIntegrationPlan,
5858
+ captureAskReadSnapshot,
5859
+ composeContextualAskExecutors,
4405
5860
  createAgentJobManager,
5861
+ createConfiguredKeyAskExecutors,
5862
+ createContextualAskManager,
4406
5863
  createExternalHandoffService,
4407
5864
  createIntegrationFileChange,
4408
5865
  createRuntimeAiConfig,
@@ -4411,9 +5868,12 @@ function parseSerializedSpotPatchOptions(value) {
4411
5868
  createSourceRegistrationService,
4412
5869
  createSourceRegistry,
4413
5870
  createSpotPatchMiddleware,
5871
+ createWorkspaceActivityCoordinator,
4414
5872
  discoverProjectValidationCheck,
5873
+ handleContextualAskRequest,
4415
5874
  integrationPathExists,
4416
5875
  isLoopbackHostname,
5876
+ matchContextualAskPath,
4417
5877
  parseSerializedSpotPatchOptions,
4418
5878
  readIntegrationFile,
4419
5879
  readJsonRequestBody,