@spotpatch/dev-server 0.8.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.");
@@ -2477,6 +2533,9 @@ function createSourceRegistry(options = {}) {
2477
2533
  resolve(fileId) {
2478
2534
  return idToPath.get(fileId);
2479
2535
  },
2536
+ findRegisteredId(absolutePath) {
2537
+ return pathToId.get(normalizeAbsolutePath(absolutePath));
2538
+ },
2480
2539
  resolveDataFlowComponent(componentSourceId) {
2481
2540
  return componentAnchors.get(componentSourceId);
2482
2541
  },
@@ -2489,179 +2548,1478 @@ function createSourceRegistry(options = {}) {
2489
2548
  });
2490
2549
  }
2491
2550
 
2492
- // src/server/middleware.ts
2493
- var import_shared20 = require("@spotpatch/shared");
2494
-
2495
- // src/external-handoff/browser-http.ts
2496
- var import_shared13 = require("@spotpatch/shared");
2497
-
2498
- // src/server/annotation-authorizer.ts
2499
- var import_promises6 = require("fs/promises");
2500
- var import_node_path7 = __toESM(require("path"), 1);
2501
- var import_shared12 = require("@spotpatch/shared");
2502
-
2503
- // src/server/source-context.ts
2504
- var import_promises5 = require("fs/promises");
2505
- var import_node_path6 = __toESM(require("path"), 1);
2551
+ // src/contextual-ask/manager.ts
2552
+ var import_node_crypto10 = require("crypto");
2506
2553
  var import_shared11 = require("@spotpatch/shared");
2507
2554
 
2508
- // src/server/extract-code-context.ts
2509
- var import_oxc_parser = require("oxc-parser");
2510
- function isComponentName(name) {
2511
- return /^[A-Z]/u.test(name);
2512
- }
2513
- function unwrapTypeExpression(expression) {
2514
- switch (expression.type) {
2515
- case "TSAsExpression":
2516
- case "TSSatisfiesExpression":
2517
- case "TSTypeAssertion":
2518
- case "TSNonNullExpression":
2519
- case "TSInstantiationExpression":
2520
- return unwrapTypeExpression(expression.expression);
2521
- default:
2522
- return expression;
2523
- }
2524
- }
2525
- function calleeName(expression) {
2526
- const unwrapped = unwrapTypeExpression(expression);
2527
- if (unwrapped.type === "Identifier") {
2528
- 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;
2529
2563
  }
2530
- if (unwrapped.type === "MemberExpression" && !unwrapped.computed) {
2531
- 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 });
2532
2569
  }
2533
- return void 0;
2570
+ return new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", { cause: error });
2534
2571
  }
2535
- function isFunctionExpression(expression) {
2536
- const unwrapped = unwrapTypeExpression(expression);
2537
- 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);
2538
2618
  }
2539
- function isSupportedComponentInitializer(expression) {
2540
- const unwrapped = unwrapTypeExpression(expression);
2541
- if (isFunctionExpression(unwrapped)) {
2542
- return true;
2543
- }
2544
- if (unwrapped.type !== "CallExpression") {
2545
- 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");
2546
2623
  }
2547
- const name = calleeName(unwrapped.callee);
2548
- if (name !== "memo" && name !== "forwardRef") {
2549
- 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");
2550
2629
  }
2551
- const firstArgument = unwrapped.arguments[0];
2552
- return firstArgument !== void 0 && firstArgument.type !== "SpreadElement" && (isFunctionExpression(firstArgument) || isSupportedComponentInitializer(firstArgument));
2630
+ return normalized;
2553
2631
  }
2554
- function variableComponent(node) {
2555
- if (node.id.type !== "Identifier" || !isComponentName(node.id.name) || node.init === null || !isSupportedComponentInitializer(node.init)) {
2556
- 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
+ }
2557
2641
  }
2558
- return Object.freeze({ start: node.start, end: node.end, name: node.id.name });
2559
- }
2560
- function functionComponent(node) {
2561
- 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;
2562
2642
  }
2563
- function isReactComponentSuperclass(expression) {
2564
- if (expression === null) {
2565
- 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");
2566
2650
  }
2567
- const unwrapped = unwrapTypeExpression(expression);
2568
- if (unwrapped.type === "Identifier") {
2569
- 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);
2570
2680
  }
2571
- return unwrapped.type === "MemberExpression" && !unwrapped.computed && unwrapped.object.type === "Identifier" && unwrapped.object.name === "React" && (unwrapped.property.name === "Component" || unwrapped.property.name === "PureComponent");
2572
2681
  }
2573
- function classComponent(node) {
2574
- 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";
2575
2684
  }
2576
- function selectedOffset(source, line, column) {
2577
- const lines = source.split(/\r?\n/u);
2578
- if (line < 1 || line > lines.length) {
2579
- 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
+ );
2580
2726
  }
2581
- const lineStart = lines.slice(0, line - 1).reduce((total, value) => total + value.length + 1, 0);
2582
- const lineLength = lines[line - 1]?.length ?? 0;
2583
- 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()];
2584
2759
  }
2585
- function findComponentSpan(options) {
2586
- const offset = selectedOffset(options.source, options.line, options.column);
2587
- if (offset === void 0) {
2588
- 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();
2589
2768
  }
2590
- let parseResult;
2591
2769
  try {
2592
- parseResult = (0, import_oxc_parser.parseSync)(options.sourcePath, options.source, {
2593
- 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
+ }
2594
2788
  });
2789
+ visitor.visit(parsed.program);
2595
2790
  } catch {
2596
- return void 0;
2791
+ return [];
2597
2792
  }
2598
- if (parseResult.errors.length > 0) {
2599
- 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
+ }
2600
2907
  }
2601
- const jsxNodes = [];
2602
- const components = [];
2603
- const visitor = new import_oxc_parser.Visitor({
2604
- JSXElement(node) {
2605
- 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;
2606
2935
  },
2607
- JSXFragment(node) {
2608
- 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
+ });
2609
2949
  },
2610
- FunctionDeclaration(node) {
2611
- const candidate = functionComponent(node);
2612
- if (candidate !== void 0) {
2613
- 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
+ }
2614
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);
2615
2985
  },
2616
- VariableDeclarator(node) {
2617
- const candidate = variableComponent(node);
2618
- if (candidate !== void 0) {
2619
- 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
+ }
2620
2994
  }
2995
+ return false;
2621
2996
  },
2622
- ClassDeclaration(node) {
2623
- const candidate = classComponent(node);
2624
- if (candidate !== void 0) {
2625
- 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");
2626
3001
  }
3002
+ return Object.freeze({ ...publicSourceEntry(source), startLine, endLine });
2627
3003
  }
2628
3004
  });
2629
- visitor.visit(parseResult.program);
2630
- const selectedJsx = jsxNodes.filter((node) => node.start <= offset && node.end >= offset).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
2631
- if (selectedJsx === void 0) {
2632
- return void 0;
2633
- }
2634
- return components.filter(
2635
- (component) => component.start <= selectedJsx.start && component.end >= selectedJsx.end
2636
- ).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
2637
- }
2638
- function lineAtOffset(source, offset) {
2639
- let line = 1;
2640
- for (let index = 0; index < offset; index += 1) {
2641
- if (source[index] === "\n") {
2642
- line += 1;
2643
- }
2644
- }
2645
- return line;
2646
3005
  }
2647
- function componentRange(source, component) {
2648
- return Object.freeze({
2649
- startLine: lineAtOffset(source, component.start),
2650
- 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
+ );
2651
3054
  });
2652
3055
  }
2653
- function truncateSelectedLine(line, column, maxCharacters) {
2654
- if (line.length <= maxCharacters) {
2655
- return line;
2656
- }
2657
- if (maxCharacters === 1) {
2658
- 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);
2659
3067
  }
2660
- const contentCharacters = maxCharacters - 2;
2661
- const desiredStart = Math.max(0, column - 1 - Math.floor(contentCharacters / 2));
2662
- const start = Math.min(desiredStart, line.length - contentCharacters);
2663
- const end = start + contentCharacters;
2664
- 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(
2665
4023
  0,
2666
4024
  maxCharacters
2667
4025
  );
@@ -2732,9 +4090,9 @@ function extractCodeContext(options) {
2732
4090
  }
2733
4091
 
2734
4092
  // src/server/source-file.ts
2735
- var import_promises4 = require("fs/promises");
2736
- var import_node_path5 = __toESM(require("path"), 1);
2737
- 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");
2738
4096
  var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
2739
4097
  function isMissingFileError(error) {
2740
4098
  return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
@@ -2744,56 +4102,56 @@ async function assertInsideRoot(root, candidate) {
2744
4102
  let realCandidate;
2745
4103
  try {
2746
4104
  [realRoot, realCandidate] = await Promise.all([
2747
- (0, import_promises4.realpath)(root),
2748
- (0, import_promises4.realpath)(candidate)
4105
+ (0, import_promises5.realpath)(root),
4106
+ (0, import_promises5.realpath)(candidate)
2749
4107
  ]);
2750
4108
  } catch (error) {
2751
4109
  if (isMissingFileError(error)) {
2752
- 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, {
2753
4111
  cause: error
2754
4112
  });
2755
4113
  }
2756
4114
  throw error;
2757
4115
  }
2758
- const relative = import_node_path5.default.relative(realRoot, realCandidate);
2759
- 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);
2760
4118
  if (outside) {
2761
- 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);
2762
4120
  }
2763
4121
  return realCandidate;
2764
4122
  }
2765
4123
  async function resolveSourceFile(options) {
2766
4124
  const registeredPath = options.registry.resolve(options.fileId);
2767
4125
  if (registeredPath === void 0) {
2768
- 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);
2769
4127
  }
2770
4128
  const sourcePath = await assertInsideRoot(options.root, registeredPath);
2771
- if (!ALLOWED_EXTENSIONS.has(import_node_path5.default.extname(sourcePath).toLowerCase())) {
2772
- 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);
2773
4131
  }
2774
4132
  let sourceStat;
2775
4133
  try {
2776
- sourceStat = await (0, import_promises4.stat)(sourcePath);
4134
+ sourceStat = await (0, import_promises5.stat)(sourcePath);
2777
4135
  } catch (error) {
2778
4136
  if (isMissingFileError(error)) {
2779
- 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, {
2780
4138
  cause: error
2781
4139
  });
2782
4140
  }
2783
4141
  throw error;
2784
4142
  }
2785
4143
  if (!sourceStat.isFile()) {
2786
- 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);
2787
4145
  }
2788
4146
  if (sourceStat.size > MAX_SOURCE_FILE_BYTES) {
2789
- 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);
2790
4148
  }
2791
4149
  return sourcePath;
2792
4150
  }
2793
4151
 
2794
4152
  // src/server/source-context.ts
2795
4153
  function toDisplayPath(root, sourcePath) {
2796
- 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("/");
2797
4155
  }
2798
4156
  async function readSourceContext(options) {
2799
4157
  const sourcePath = await resolveSourceFile({
@@ -2803,10 +4161,10 @@ async function readSourceContext(options) {
2803
4161
  });
2804
4162
  let source;
2805
4163
  try {
2806
- source = await (0, import_promises5.readFile)(sourcePath, "utf8");
4164
+ source = await (0, import_promises6.readFile)(sourcePath, "utf8");
2807
4165
  } catch (error) {
2808
4166
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
2809
- 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, {
2810
4168
  cause: error
2811
4169
  });
2812
4170
  }
@@ -2814,13 +4172,13 @@ async function readSourceContext(options) {
2814
4172
  }
2815
4173
  const lines = source.split(/\r?\n/);
2816
4174
  if (options.request.line > lines.length) {
2817
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
4175
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2818
4176
  }
2819
- const extension = import_node_path6.default.extname(sourcePath).toLowerCase();
4177
+ const extension = import_node_path7.default.extname(sourcePath).toLowerCase();
2820
4178
  return extractCodeContext({
2821
4179
  source,
2822
4180
  sourcePath,
2823
- relativePath: toDisplayPath(await (0, import_promises5.realpath)(options.root), sourcePath),
4181
+ relativePath: toDisplayPath(await (0, import_promises6.realpath)(options.root), sourcePath),
2824
4182
  language: extension === ".tsx" ? "tsx" : "jsx",
2825
4183
  line: options.request.line,
2826
4184
  column: options.request.column,
@@ -2832,9 +4190,9 @@ async function readSourceContext(options) {
2832
4190
  // src/server/annotation-authorizer.ts
2833
4191
  function sanitizePageContext(page) {
2834
4192
  return Object.freeze({
2835
- url: (0, import_shared12.sanitizeUrl)(page.url, page.url),
2836
- pathname: (0, import_shared12.redactSensitiveText)(page.pathname),
2837
- title: (0, import_shared12.redactSensitiveText)(page.title),
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),
2838
4196
  viewportWidth: page.viewportWidth,
2839
4197
  viewportHeight: page.viewportHeight,
2840
4198
  devicePixelRatio: page.devicePixelRatio
@@ -2853,18 +4211,18 @@ function compactSourceRef(source) {
2853
4211
  async function authorizeSourceRef(source, registry, root) {
2854
4212
  const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
2855
4213
  if (markerOrigin && (source.fileId === void 0 || source.line === void 0 || source.column === void 0)) {
2856
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4214
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2857
4215
  }
2858
4216
  if (source.fileId === void 0) {
2859
4217
  if (source.origin === "none" && source.relativePath !== void 0) {
2860
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4218
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2861
4219
  }
2862
4220
  return compactSourceRef(source);
2863
4221
  }
2864
4222
  const sourcePath = await resolveSourceFile({ fileId: source.fileId, registry, root });
2865
- 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("/");
2866
4224
  if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
2867
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4225
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2868
4226
  }
2869
4227
  return Object.freeze({ ...compactSourceRef(source), relativePath });
2870
4228
  }
@@ -2900,7 +4258,7 @@ async function authorizeTarget(target, input) {
2900
4258
  maxLines: input.options.budget.maxCodeLines
2901
4259
  });
2902
4260
  if (marker === void 0 && target.code !== void 0) {
2903
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4261
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2904
4262
  }
2905
4263
  const code = marker === void 0 ? void 0 : await readSourceContext({
2906
4264
  request: marker,
@@ -2910,7 +4268,7 @@ async function authorizeTarget(target, input) {
2910
4268
  maxLines: input.options.budget.maxCodeLines
2911
4269
  });
2912
4270
  if (target.code !== void 0 && target.code.relativePath !== code?.relativePath) {
2913
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4271
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2914
4272
  }
2915
4273
  return Object.freeze({
2916
4274
  instruction: target.instruction,
@@ -2945,11 +4303,11 @@ async function authorizeTarget(target, input) {
2945
4303
  async function authorizeAnnotation(input) {
2946
4304
  const requestedTargets = input.annotation.targets;
2947
4305
  if (requestedTargets.length > input.options.maxTargets) {
2948
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4306
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2949
4307
  }
2950
4308
  const identities = requestedTargets.map(targetIdentity);
2951
4309
  if (new Set(identities).size !== identities.length) {
2952
- throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
4310
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2953
4311
  }
2954
4312
  const targets = Object.freeze(
2955
4313
  await Promise.all(requestedTargets.map((target) => authorizeTarget(target, input)))
@@ -2965,30 +4323,30 @@ async function authorizeAnnotation(input) {
2965
4323
  }
2966
4324
 
2967
4325
  // src/external-handoff/browser-http.ts
2968
- function matchExternalHandoffBrowserPath(path9) {
2969
- if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffCapability) return "capability";
2970
- if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffPublish) return "publish";
2971
- if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffStatus) return "status";
2972
- if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery) {
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) {
2973
4331
  return "resolve-delivery";
2974
4332
  }
2975
4333
  return void 0;
2976
4334
  }
2977
4335
  function requireService(options) {
2978
4336
  if (!options.options.externalAgent.enabled || options.service === void 0) {
2979
- 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);
2980
4338
  }
2981
4339
  return options.service;
2982
4340
  }
2983
4341
  function remapAuthorizationError(error) {
2984
- if (error instanceof import_shared13.SpotPatchError) {
2985
- if (error.code === import_shared13.ERROR_CODES.SOURCE_NOT_FOUND || error.code === import_shared13.ERROR_CODES.SOURCE_OUTSIDE_ROOT || error.code === import_shared13.ERROR_CODES.SOURCE_TOO_LARGE) {
2986
- throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_SOURCE_STALE, void 0, {
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, {
2987
4345
  cause: error
2988
4346
  });
2989
4347
  }
2990
- if (error.code === import_shared13.ERROR_CODES.INVALID_REQUEST) {
2991
- throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_VALIDATION_FAILED, void 0, {
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, {
2992
4350
  cause: error
2993
4351
  });
2994
4352
  }
@@ -2997,38 +4355,38 @@ function remapAuthorizationError(error) {
2997
4355
  }
2998
4356
  async function handleExternalHandoffBrowserRequest(request, response, route, options, writeSuccess) {
2999
4357
  if (request.method !== "POST") {
3000
- throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
4358
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
3001
4359
  }
3002
4360
  const service = requireService(options);
3003
4361
  if (route === "capability") {
3004
- const parsed2 = import_shared13.externalHandoffCapabilityRequestSchema.safeParse(
4362
+ const parsed2 = import_shared16.externalHandoffCapabilityRequestSchema.safeParse(
3005
4363
  await readJsonRequestBody(request)
3006
4364
  );
3007
- 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);
3008
4366
  writeSuccess(response, 200, service.capability());
3009
4367
  return;
3010
4368
  }
3011
4369
  if (route === "status") {
3012
- const parsed2 = import_shared13.externalHandoffStatusRequestSchema.safeParse(
4370
+ const parsed2 = import_shared16.externalHandoffStatusRequestSchema.safeParse(
3013
4371
  await readJsonRequestBody(request)
3014
4372
  );
3015
- 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);
3016
4374
  writeSuccess(response, 200, service.status(parsed2.data.cursor));
3017
4375
  return;
3018
4376
  }
3019
4377
  if (route === "resolve-delivery") {
3020
- const parsed2 = import_shared13.externalHandoffResolveDeliveryRequestSchema.safeParse(
4378
+ const parsed2 = import_shared16.externalHandoffResolveDeliveryRequestSchema.safeParse(
3021
4379
  await readJsonRequestBody(request)
3022
4380
  );
3023
- 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);
3024
4382
  writeSuccess(response, 200, service.resolveDelivery(parsed2.data.cursor));
3025
4383
  return;
3026
4384
  }
3027
- const parsed = import_shared13.externalHandoffPublishRequestSchema.safeParse(
3028
- 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)
3029
4387
  );
3030
4388
  if (!parsed.success) {
3031
- 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);
3032
4390
  }
3033
4391
  const result = await service.publish(parsed.data, async (annotation) => {
3034
4392
  try {
@@ -3046,41 +4404,41 @@ async function handleExternalHandoffBrowserRequest(request, response, route, opt
3046
4404
  }
3047
4405
 
3048
4406
  // src/external-agent/browser-http.ts
3049
- var import_shared14 = require("@spotpatch/shared");
3050
- function matchExternalAgentBrowserPath(path9) {
3051
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlStatus) return "status";
3052
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlConnect) return "connect";
3053
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlDisconnect) {
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) {
3054
4412
  return "disconnect";
3055
4413
  }
3056
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlCancel) return "cancel";
3057
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentEvents) return "events";
3058
- if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentResult) return "result";
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";
3059
4417
  return void 0;
3060
4418
  }
3061
4419
  function writeEvent(response, event) {
3062
- response.write(`${JSON.stringify(import_shared14.externalAgentEventSchema.parse(event))}
4420
+ response.write(`${JSON.stringify(import_shared17.externalAgentEventSchema.parse(event))}
3063
4421
  `);
3064
4422
  }
3065
4423
  function createExternalAgentBrowserController(port) {
3066
4424
  const streams = /* @__PURE__ */ new Set();
3067
4425
  let disposed = false;
3068
4426
  const handleEvents = async (request, response) => {
3069
- const parsed = import_shared14.externalAgentEventsRequestSchema.safeParse(
4427
+ const parsed = import_shared17.externalAgentEventsRequestSchema.safeParse(
3070
4428
  await readJsonRequestBody(request)
3071
4429
  );
3072
4430
  if (!parsed.success) {
3073
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4431
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3074
4432
  }
3075
- if (disposed || streams.size >= import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
3076
- 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);
3077
4435
  }
3078
4436
  response.statusCode = 200;
3079
4437
  response.setHeader("Cache-Control", "no-store");
3080
4438
  response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
3081
4439
  response.setHeader("X-Content-Type-Options", "nosniff");
3082
4440
  streams.add(response);
3083
- const initial = import_shared14.externalAgentControlStatusSchema.parse(port.getStatus());
4441
+ const initial = import_shared17.externalAgentControlStatusSchema.parse(port.getStatus());
3084
4442
  writeEvent(response, { type: "status", data: initial });
3085
4443
  const unsubscribe = port.subscribe((status) => {
3086
4444
  if (!response.destroyed && status.sequence > (parsed.data.afterSequence ?? -1)) {
@@ -3096,7 +4454,7 @@ function createExternalAgentBrowserController(port) {
3096
4454
  emittedAt: (/* @__PURE__ */ new Date()).toISOString()
3097
4455
  });
3098
4456
  }
3099
- }, import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
4457
+ }, import_shared17.EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
3100
4458
  heartbeat.unref();
3101
4459
  await new Promise((resolve) => {
3102
4460
  const close = () => {
@@ -3112,29 +4470,29 @@ function createExternalAgentBrowserController(port) {
3112
4470
  return Object.freeze({
3113
4471
  async handle(request, response, route, writeSuccess) {
3114
4472
  if (request.method !== "POST" || disposed) {
3115
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
4473
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
3116
4474
  }
3117
4475
  if (route === "events") {
3118
4476
  await handleEvents(request, response);
3119
4477
  return;
3120
4478
  }
3121
4479
  if (route === "status") {
3122
- const parsed2 = import_shared14.externalAgentControlStatusRequestSchema.safeParse(
4480
+ const parsed2 = import_shared17.externalAgentControlStatusRequestSchema.safeParse(
3123
4481
  await readJsonRequestBody(request)
3124
4482
  );
3125
- 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);
3126
4484
  writeSuccess(
3127
4485
  response,
3128
4486
  200,
3129
- import_shared14.externalAgentControlStatusSchema.parse(port.getStatus())
4487
+ import_shared17.externalAgentControlStatusSchema.parse(port.getStatus())
3130
4488
  );
3131
4489
  return;
3132
4490
  }
3133
4491
  if (route === "connect") {
3134
- const parsed2 = import_shared14.externalAgentControlConnectRequestSchema.safeParse(
4492
+ const parsed2 = import_shared17.externalAgentControlConnectRequestSchema.safeParse(
3135
4493
  await readJsonRequestBody(request)
3136
4494
  );
3137
- 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);
3138
4496
  const controller = new AbortController();
3139
4497
  response.once("close", () => {
3140
4498
  if (!response.writableEnded) controller.abort("browser-disconnected");
@@ -3142,45 +4500,45 @@ function createExternalAgentBrowserController(port) {
3142
4500
  writeSuccess(
3143
4501
  response,
3144
4502
  200,
3145
- import_shared14.externalAgentControlStatusSchema.parse(
4503
+ import_shared17.externalAgentControlStatusSchema.parse(
3146
4504
  await port.connect(parsed2.data, controller.signal)
3147
4505
  )
3148
4506
  );
3149
4507
  return;
3150
4508
  }
3151
4509
  if (route === "disconnect") {
3152
- const parsed2 = import_shared14.externalAgentControlDisconnectRequestSchema.safeParse(
4510
+ const parsed2 = import_shared17.externalAgentControlDisconnectRequestSchema.safeParse(
3153
4511
  await readJsonRequestBody(request)
3154
4512
  );
3155
- 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);
3156
4514
  writeSuccess(
3157
4515
  response,
3158
4516
  200,
3159
- import_shared14.externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
4517
+ import_shared17.externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
3160
4518
  );
3161
4519
  return;
3162
4520
  }
3163
4521
  if (route === "cancel") {
3164
- const parsed2 = import_shared14.externalAgentControlCancelRequestSchema.safeParse(
4522
+ const parsed2 = import_shared17.externalAgentControlCancelRequestSchema.safeParse(
3165
4523
  await readJsonRequestBody(request)
3166
4524
  );
3167
- 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);
3168
4526
  writeSuccess(
3169
4527
  response,
3170
4528
  200,
3171
- import_shared14.externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
4529
+ import_shared17.externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
3172
4530
  );
3173
4531
  return;
3174
4532
  }
3175
- const parsed = import_shared14.externalAgentResultRequestSchema.safeParse(
4533
+ const parsed = import_shared17.externalAgentResultRequestSchema.safeParse(
3176
4534
  await readJsonRequestBody(request)
3177
4535
  );
3178
- 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);
3179
4537
  const result = port.getResult(parsed.data.revision);
3180
4538
  if (result === void 0) {
3181
- 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);
3182
4540
  }
3183
- writeSuccess(response, 200, import_shared14.externalAgentManagedResultSchema.parse(result));
4541
+ writeSuccess(response, 200, import_shared17.externalAgentManagedResultSchema.parse(result));
3184
4542
  },
3185
4543
  dispose() {
3186
4544
  if (disposed) return;
@@ -3192,10 +4550,10 @@ function createExternalAgentBrowserController(port) {
3192
4550
  }
3193
4551
 
3194
4552
  // src/server/agent-http.ts
3195
- var import_shared16 = require("@spotpatch/shared");
4553
+ var import_shared19 = require("@spotpatch/shared");
3196
4554
 
3197
4555
  // src/server/agent-request.ts
3198
- var import_shared15 = require("@spotpatch/shared");
4556
+ var import_shared18 = require("@spotpatch/shared");
3199
4557
  async function authorizeAgentJobRequest(input) {
3200
4558
  const annotation = await authorizeAnnotation({
3201
4559
  annotation: input.request.annotation,
@@ -3231,21 +4589,21 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
3231
4589
  "reverted",
3232
4590
  "failed"
3233
4591
  ]);
3234
- function matchAgentRequestPath(path9) {
3235
- if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentCapability) {
4592
+ function matchAgentRequestPath(path10) {
4593
+ if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentCapability) {
3236
4594
  return Object.freeze({ kind: "capability" });
3237
4595
  }
3238
- if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
4596
+ if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
3239
4597
  return Object.freeze({ kind: "workspace-health" });
3240
4598
  }
3241
- if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentJobs) {
4599
+ if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentJobs) {
3242
4600
  return Object.freeze({ kind: "create-job" });
3243
4601
  }
3244
- const prefix = `${import_shared16.SPOTPATCH_ENDPOINTS.agentJobs}/`;
3245
- if (!path9.startsWith(prefix)) {
4602
+ const prefix = `${import_shared19.SPOTPATCH_ENDPOINTS.agentJobs}/`;
4603
+ if (!path10.startsWith(prefix)) {
3246
4604
  return void 0;
3247
4605
  }
3248
- const segments = path9.slice(prefix.length).split("/");
4606
+ const segments = path10.slice(prefix.length).split("/");
3249
4607
  const jobId = segments[0];
3250
4608
  const action = segments[1];
3251
4609
  if (segments.length !== 2 || jobId === void 0 || !AGENT_JOB_ID_PATTERN.test(jobId) || action === void 0 || !AGENT_JOB_ACTIONS.has(action)) {
@@ -3259,7 +4617,7 @@ function matchAgentRequestPath(path9) {
3259
4617
  }
3260
4618
  function requireAgentManager(options) {
3261
4619
  if (options.agentManager === void 0 || options.options.ai === false) {
3262
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.AI_DISABLED);
4620
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.AI_DISABLED);
3263
4621
  }
3264
4622
  return options.agentManager;
3265
4623
  }
@@ -3312,13 +4670,13 @@ function streamAgentJobEvents(response, manager, jobId) {
3312
4670
  }
3313
4671
  async function handleCapability(request, response, options, writeSuccess) {
3314
4672
  if (request.method !== "POST") {
3315
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4673
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3316
4674
  }
3317
- const parsed = import_shared16.agentCapabilityRequestSchema.safeParse(
4675
+ const parsed = import_shared19.agentCapabilityRequestSchema.safeParse(
3318
4676
  await readJsonRequestBody(request)
3319
4677
  );
3320
4678
  if (!parsed.success) {
3321
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4679
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3322
4680
  }
3323
4681
  const controller = new AbortController();
3324
4682
  const abort = () => {
@@ -3337,13 +4695,13 @@ async function handleCapability(request, response, options, writeSuccess) {
3337
4695
  }
3338
4696
  async function handleCreateJob(request, response, options, writeSuccess) {
3339
4697
  if (request.method !== "POST") {
3340
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4698
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3341
4699
  }
3342
- const parsed = import_shared16.agentJobCreateRequestSchema.safeParse(
4700
+ const parsed = import_shared19.agentJobCreateRequestSchema.safeParse(
3343
4701
  await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
3344
4702
  );
3345
4703
  if (!parsed.success) {
3346
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4704
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3347
4705
  }
3348
4706
  const authorizedRequest = await authorizeAgentJobRequest({
3349
4707
  request: parsed.data,
@@ -3356,13 +4714,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
3356
4714
  }
3357
4715
  async function handleWorkspaceHealth(request, response, options, writeSuccess) {
3358
4716
  if (request.method !== "POST") {
3359
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4717
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3360
4718
  }
3361
- const parsed = import_shared16.agentWorkspaceHealthRequestSchema.safeParse(
4719
+ const parsed = import_shared19.agentWorkspaceHealthRequestSchema.safeParse(
3362
4720
  await readJsonRequestBody(request)
3363
4721
  );
3364
4722
  if (!parsed.success) {
3365
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4723
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3366
4724
  }
3367
4725
  const controller = new AbortController();
3368
4726
  const abort = () => {
@@ -3379,13 +4737,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
3379
4737
  async function handleJobAction(request, response, options, route, writeSuccess) {
3380
4738
  const manager = requireAgentManager(options);
3381
4739
  if (request.method !== "POST") {
3382
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4740
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3383
4741
  }
3384
- const parsed = import_shared16.agentJobActionRequestSchema.safeParse(
4742
+ const parsed = import_shared19.agentJobActionRequestSchema.safeParse(
3385
4743
  await readJsonRequestBody(request)
3386
4744
  );
3387
4745
  if (!parsed.success) {
3388
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
4746
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
3389
4747
  }
3390
4748
  if (route.action === "events") {
3391
4749
  streamAgentJobEvents(response, manager, route.jobId);
@@ -3439,13 +4797,13 @@ function detectIntegratedEditor(environment) {
3439
4797
  function editorCommand(editor) {
3440
4798
  return editor === "cursor" ? "cursor" : "code";
3441
4799
  }
3442
- var DEFAULT_DEPENDENCIES2 = Object.freeze({
4800
+ var DEFAULT_DEPENDENCIES3 = Object.freeze({
3443
4801
  environment: process.env,
3444
4802
  fallbackLauncher: import_launch_editor.default,
3445
4803
  processSpawner: (command, arguments_, options) => (0, import_node_child_process2.spawn)(command, [...arguments_], options),
3446
4804
  startupGraceMs: EDITOR_STARTUP_GRACE_MS
3447
4805
  });
3448
- function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
4806
+ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES3) {
3449
4807
  return (target, configuredEditor) => {
3450
4808
  const integratedEditor = detectIntegratedEditor(dependencies.environment);
3451
4809
  const resolvedEditor = configuredEditor === "auto" ? integratedEditor : configuredEditor;
@@ -3500,27 +4858,27 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
3500
4858
  var launchConfiguredEditor = createEditorLauncher();
3501
4859
 
3502
4860
  // src/server/data-flow-http.ts
3503
- var import_node_crypto9 = require("crypto");
4861
+ var import_node_crypto11 = require("crypto");
3504
4862
  var import_analyzer = require("@spotpatch/analyzer");
3505
- var import_shared17 = require("@spotpatch/shared");
4863
+ var import_shared20 = require("@spotpatch/shared");
3506
4864
  function envelopeBytes(report) {
3507
4865
  return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
3508
4866
  }
3509
4867
  function limitDataFlowReportToBytes(report, maximumBytes) {
3510
- const structurallyLimited = (0, import_shared17.limitDataFlowReportCollections)(report);
4868
+ const structurallyLimited = (0, import_shared20.limitDataFlowReportCollections)(report);
3511
4869
  if (envelopeBytes(structurallyLimited) <= maximumBytes) {
3512
4870
  return structurallyLimited;
3513
4871
  }
3514
- let limited = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
4872
+ let limited = (0, import_shared20.limitDataFlowReportCollections)(structurallyLimited, {
3515
4873
  forceTruncation: true,
3516
4874
  maximumDependencies: 0,
3517
4875
  truncatedBy: "bytes"
3518
4876
  });
3519
4877
  if (envelopeBytes(limited) > maximumBytes) {
3520
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INTERNAL_ERROR);
4878
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INTERNAL_ERROR);
3521
4879
  }
3522
4880
  for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
3523
- const candidate = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
4881
+ const candidate = (0, import_shared20.limitDataFlowReportCollections)(structurallyLimited, {
3524
4882
  forceTruncation: true,
3525
4883
  maximumDependencies,
3526
4884
  truncatedBy: "bytes"
@@ -3546,7 +4904,7 @@ async function analyzeTarget(request, analyzer, options) {
3546
4904
  request.componentSourceId
3547
4905
  );
3548
4906
  if (anchor?.sourceVersion !== request.sourceVersion) {
3549
- 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);
3550
4908
  }
3551
4909
  return anchor;
3552
4910
  }
@@ -3563,7 +4921,7 @@ async function analyzeTarget(request, analyzer, options) {
3563
4921
  column: resolvedRequest.column
3564
4922
  });
3565
4923
  if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
3566
- 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);
3567
4925
  }
3568
4926
  return limitDataFlowReportToBytes(
3569
4927
  report,
@@ -3572,31 +4930,31 @@ async function analyzeTarget(request, analyzer, options) {
3572
4930
  }
3573
4931
  function requireAnalyzer(analyzer) {
3574
4932
  if (analyzer === void 0) {
3575
- 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);
3576
4934
  }
3577
4935
  return analyzer;
3578
4936
  }
3579
4937
  async function handleComponentDataFlowReport(request, analyzer, options) {
3580
- const parsed = import_shared17.dataFlowComponentReportRequestSchema.safeParse(
4938
+ const parsed = import_shared20.dataFlowComponentReportRequestSchema.safeParse(
3581
4939
  await readJsonRequestBody(
3582
4940
  request,
3583
4941
  options.options.dataFlow.limits.protocolRequestMaxBytes
3584
4942
  )
3585
4943
  );
3586
4944
  if (!parsed.success) {
3587
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
4945
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3588
4946
  }
3589
4947
  return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
3590
4948
  }
3591
4949
  async function handlePageDataFlowReport(request, analyzer, options) {
3592
- const parsed = import_shared17.dataFlowPageReportRequestSchema.safeParse(
4950
+ const parsed = import_shared20.dataFlowPageReportRequestSchema.safeParse(
3593
4951
  await readJsonRequestBody(
3594
4952
  request,
3595
4953
  options.options.dataFlow.limits.protocolRequestMaxBytes
3596
4954
  )
3597
4955
  );
3598
4956
  if (!parsed.success) {
3599
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
4957
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
3600
4958
  }
3601
4959
  const activeAnalyzer = requireAnalyzer(analyzer);
3602
4960
  const componentReports = await Promise.all(
@@ -3620,10 +4978,10 @@ async function handlePageDataFlowReport(request, analyzer, options) {
3620
4978
  const analyzedVersions = new Set(
3621
4979
  componentReports.flatMap((report2) => report2.baseline.analyzedSourceVersions)
3622
4980
  );
3623
- 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)}`;
3624
4982
  const complete = componentReports.every((report2) => report2.completeness.complete);
3625
4983
  const report = Object.freeze({
3626
- schemaVersion: import_shared17.DATA_FLOW_SCHEMA_VERSION,
4984
+ schemaVersion: import_shared20.DATA_FLOW_SCHEMA_VERSION,
3627
4985
  reportId,
3628
4986
  baseline: Object.freeze({
3629
4987
  registryEpoch: options.session.id,
@@ -3667,9 +5025,9 @@ async function handlePageDataFlowReport(request, analyzer, options) {
3667
5025
  }
3668
5026
 
3669
5027
  // src/server/request-security.ts
3670
- var import_node_crypto10 = require("crypto");
5028
+ var import_node_crypto12 = require("crypto");
3671
5029
  var import_node_net = require("net");
3672
- var import_shared18 = require("@spotpatch/shared");
5030
+ var import_shared21 = require("@spotpatch/shared");
3673
5031
  function getSingleHeader(request, name) {
3674
5032
  const value = request.headers[name.toLowerCase()];
3675
5033
  return Array.isArray(value) ? value[0] : value;
@@ -3680,7 +5038,7 @@ function tokensMatch2(actual, expected) {
3680
5038
  }
3681
5039
  const actualBytes = Buffer.from(actual);
3682
5040
  const expectedBytes = Buffer.from(expected);
3683
- 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);
3684
5042
  }
3685
5043
  function isLoopbackHostname(hostname) {
3686
5044
  const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
@@ -3713,33 +5071,42 @@ function parseOrigin(value) {
3713
5071
  return void 0;
3714
5072
  }
3715
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
+ }
3716
5077
  function assertRequestAuthorized(request, options) {
3717
- const actualToken = getSingleHeader(request, import_shared18.SPOTPATCH_TOKEN_HEADER);
5078
+ const actualToken = getSingleHeader(request, import_shared21.SPOTPATCH_TOKEN_HEADER);
3718
5079
  if (!tokensMatch2(actualToken, options.sessionToken)) {
3719
- throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_TOKEN);
5080
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.INVALID_TOKEN);
3720
5081
  }
3721
5082
  const hostHeader = getSingleHeader(request, "host");
3722
5083
  const originHeader = getSingleHeader(request, "origin");
3723
5084
  const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
3724
5085
  const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
3725
- if (host === void 0 || origin === void 0) {
3726
- 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);
3727
5088
  }
3728
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
+ }
3729
5096
  const originIsLoopback = isLoopbackHostname(origin.hostname);
3730
5097
  if (!options.allowLan) {
3731
5098
  if (!hostIsLoopback || !originIsLoopback) {
3732
- 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);
3733
5100
  }
3734
5101
  return;
3735
5102
  }
3736
5103
  if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
3737
- 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);
3738
5105
  }
3739
5106
  }
3740
5107
 
3741
5108
  // src/server/runtime-bootstrap.ts
3742
- var import_shared19 = require("@spotpatch/shared");
5109
+ var import_shared22 = require("@spotpatch/shared");
3743
5110
  function getSingleHeader2(request, name) {
3744
5111
  const value = request.headers[name.toLowerCase()];
3745
5112
  return Array.isArray(value) ? value[0] : value;
@@ -3754,7 +5121,7 @@ function resolveRuntimeBootstrapOptions(options) {
3754
5121
  if (expectedOrigin.origin !== options.expectedOrigin || expectedOrigin.protocol !== "http:" || !isLoopbackHostname(expectedOrigin.hostname)) {
3755
5122
  throw new TypeError("The SpotPatch bootstrap origin must be a loopback origin.");
3756
5123
  }
3757
- const parsedConfig = import_shared19.runtimeConfigSchema.safeParse(options.runtimeConfig);
5124
+ const parsedConfig = import_shared22.runtimeConfigSchema.safeParse(options.runtimeConfig);
3758
5125
  if (!parsedConfig.success) {
3759
5126
  throw new TypeError("The SpotPatch Runtime configuration is invalid.");
3760
5127
  }
@@ -3766,7 +5133,7 @@ function resolveRuntimeBootstrapOptions(options) {
3766
5133
  function assertRuntimeBootstrapRequest(request, expectedOrigin) {
3767
5134
  const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
3768
5135
  if (request.method !== "POST" || contentType !== "application/json") {
3769
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
5136
+ throw new import_shared22.SpotPatchError(import_shared22.ERROR_CODES.INVALID_REQUEST);
3770
5137
  }
3771
5138
  const host = getSingleHeader2(request, "host");
3772
5139
  let hostIsLoopback = false;
@@ -3778,152 +5145,152 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
3778
5145
  }
3779
5146
  }
3780
5147
  if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
3781
- 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);
3782
5149
  }
3783
5150
  }
3784
5151
  async function readRuntimeBootstrap(request, options) {
3785
5152
  assertRuntimeBootstrapRequest(request, options.expectedOrigin);
3786
- const parsedBody = import_shared19.runtimeBootstrapRequestSchema.safeParse(
5153
+ const parsedBody = import_shared22.runtimeBootstrapRequestSchema.safeParse(
3787
5154
  await readJsonRequestBody(request)
3788
5155
  );
3789
5156
  if (!parsedBody.success) {
3790
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
5157
+ throw new import_shared22.SpotPatchError(import_shared22.ERROR_CODES.INVALID_REQUEST);
3791
5158
  }
3792
5159
  return options.runtimeConfig;
3793
5160
  }
3794
5161
 
3795
5162
  // src/server/middleware.ts
3796
- var STATUS_BY_ERROR = Object.freeze({
3797
- [import_shared20.ERROR_CODES.INVALID_REQUEST]: 400,
3798
- [import_shared20.ERROR_CODES.INVALID_TOKEN]: 401,
3799
- [import_shared20.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
3800
- [import_shared20.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
3801
- [import_shared20.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
3802
- [import_shared20.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
3803
- [import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
3804
- [import_shared20.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
3805
- [import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
3806
- [import_shared20.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
3807
- [import_shared20.ERROR_CODES.AI_DISABLED]: 404,
3808
- [import_shared20.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
3809
- [import_shared20.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
3810
- [import_shared20.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
3811
- [import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
3812
- [import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
3813
- [import_shared20.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
3814
- [import_shared20.ERROR_CODES.AGENT_BUSY]: 409,
3815
- [import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
3816
- [import_shared20.ERROR_CODES.AGENT_CANCELLED]: 409,
3817
- [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: 404,
3818
- [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
3819
- [import_shared20.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: 422,
3820
- [import_shared20.ERROR_CODES.HANDOFF_SOURCE_STALE]: 409,
3821
- [import_shared20.ERROR_CODES.HANDOFF_NOT_FOUND]: 404,
3822
- [import_shared20.ERROR_CODES.HANDOFF_EXPIRED]: 410,
3823
- [import_shared20.ERROR_CODES.HANDOFF_CURSOR_INVALID]: 409,
3824
- [import_shared20.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: 413,
3825
- [import_shared20.ERROR_CODES.BRIDGE_UNAUTHORIZED]: 401,
3826
- [import_shared20.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: 409,
3827
- [import_shared20.ERROR_CODES.BRIDGE_BUSY]: 429,
3828
- [import_shared20.ERROR_CODES.EXTERNAL_AGENT_BUSY]: 409,
3829
- [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: 409,
3830
- [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
3831
- [import_shared20.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: 409,
3832
- [import_shared20.ERROR_CODES.SESSION_NOT_FOUND]: 404,
3833
- [import_shared20.ERROR_CODES.SESSION_AMBIGUOUS]: 409,
3834
- [import_shared20.ERROR_CODES.SESSION_CLOSED]: 410,
3835
- [import_shared20.ERROR_CODES.WORKTREE_DIRTY]: 409,
3836
- [import_shared20.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
3837
- [import_shared20.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
3838
- [import_shared20.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
3839
- [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
3840
- [import_shared20.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
3841
- [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
3842
- [import_shared20.ERROR_CODES.TOOL_DENIED]: 403,
3843
- [import_shared20.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
3844
- [import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
3845
- [import_shared20.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
3846
- [import_shared20.ERROR_CODES.TOOL_PATH_DENIED]: 403,
3847
- [import_shared20.ERROR_CODES.PATCH_REJECTED]: 422,
3848
- [import_shared20.ERROR_CODES.VALIDATION_FAILED]: 422,
3849
- [import_shared20.ERROR_CODES.APPLY_CONFLICT]: 409,
3850
- [import_shared20.ERROR_CODES.INTERNAL_ERROR]: 500
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
3851
5218
  });
3852
- var PUBLIC_MESSAGES = Object.freeze({
3853
- [import_shared20.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
3854
- [import_shared20.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
3855
- [import_shared20.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
3856
- [import_shared20.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
3857
- [import_shared20.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
3858
- [import_shared20.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
3859
- [import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
3860
- [import_shared20.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
3861
- [import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
3862
- [import_shared20.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
3863
- [import_shared20.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
3864
- [import_shared20.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
3865
- [import_shared20.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
3866
- [import_shared20.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
3867
- [import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
3868
- [import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
3869
- [import_shared20.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
3870
- [import_shared20.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
3871
- [import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
3872
- [import_shared20.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
3873
- [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
3874
- [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
3875
- [import_shared20.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
3876
- [import_shared20.ERROR_CODES.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
3877
- [import_shared20.ERROR_CODES.HANDOFF_NOT_FOUND]: "No current handoff is available.",
3878
- [import_shared20.ERROR_CODES.HANDOFF_EXPIRED]: "The handoff has expired.",
3879
- [import_shared20.ERROR_CODES.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
3880
- [import_shared20.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
3881
- [import_shared20.ERROR_CODES.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
3882
- [import_shared20.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
3883
- [import_shared20.ERROR_CODES.BRIDGE_BUSY]: "The local bridge is busy.",
3884
- [import_shared20.ERROR_CODES.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
3885
- [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
3886
- [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
3887
- [import_shared20.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
3888
- [import_shared20.ERROR_CODES.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
3889
- [import_shared20.ERROR_CODES.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
3890
- [import_shared20.ERROR_CODES.SESSION_CLOSED]: "The SpotPatch session has closed.",
3891
- [import_shared20.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
3892
- [import_shared20.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
3893
- [import_shared20.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
3894
- [import_shared20.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
3895
- [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
3896
- [import_shared20.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
3897
- [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
3898
- [import_shared20.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
3899
- [import_shared20.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
3900
- [import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
3901
- [import_shared20.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
3902
- [import_shared20.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
3903
- [import_shared20.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
3904
- [import_shared20.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
3905
- [import_shared20.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
3906
- [import_shared20.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
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."
3907
5274
  });
3908
- function writeJson2(response, status, payload) {
5275
+ function writeJson3(response, status, payload) {
3909
5276
  response.statusCode = status;
3910
5277
  response.setHeader("Cache-Control", "no-store");
3911
5278
  response.setHeader("Content-Type", "application/json; charset=utf-8");
3912
5279
  response.end(JSON.stringify(payload));
3913
5280
  }
3914
5281
  function asSpotPatchError(error) {
3915
- 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 });
3916
5283
  }
3917
- function writeError(response, error, logger) {
5284
+ function writeError2(response, error, logger) {
3918
5285
  const normalized = asSpotPatchError(error);
3919
- if (normalized.code === import_shared20.ERROR_CODES.INTERNAL_ERROR) {
5286
+ if (normalized.code === import_shared23.ERROR_CODES.INTERNAL_ERROR) {
3920
5287
  logger?.warn("[spotpatch:server] Internal request failure.");
3921
5288
  }
3922
- writeJson2(response, STATUS_BY_ERROR[normalized.code], {
5289
+ writeJson3(response, STATUS_BY_ERROR2[normalized.code], {
3923
5290
  ok: false,
3924
5291
  error: {
3925
5292
  code: normalized.code,
3926
- message: PUBLIC_MESSAGES[normalized.code]
5293
+ message: PUBLIC_MESSAGES2[normalized.code]
3927
5294
  }
3928
5295
  });
3929
5296
  }
@@ -3935,11 +5302,11 @@ function requestPath(request) {
3935
5302
  }
3936
5303
  }
3937
5304
  async function handleSourceContext(request, options) {
3938
- const parsed = import_shared20.sourceContextRequestSchema.safeParse(
5305
+ const parsed = import_shared23.sourceContextRequestSchema.safeParse(
3939
5306
  await readJsonRequestBody(request)
3940
5307
  );
3941
5308
  if (!parsed.success) {
3942
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5309
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
3943
5310
  }
3944
5311
  return readSourceContext({
3945
5312
  request: parsed.data,
@@ -3950,9 +5317,9 @@ async function handleSourceContext(request, options) {
3950
5317
  });
3951
5318
  }
3952
5319
  async function handleOpenEditor(request, options) {
3953
- const parsed = import_shared20.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
5320
+ const parsed = import_shared23.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3954
5321
  if (!parsed.success) {
3955
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5322
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
3956
5323
  }
3957
5324
  const body = parsed.data;
3958
5325
  const sourcePath = await resolveSourceFile({
@@ -3969,7 +5336,7 @@ async function handleOpenEditor(request, options) {
3969
5336
  options.logger?.warn(
3970
5337
  `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
3971
5338
  );
3972
- 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, {
3973
5340
  cause: error
3974
5341
  });
3975
5342
  }
@@ -3979,73 +5346,84 @@ function createSpotPatchMiddleware(options) {
3979
5346
  const dataFlowAnalyzer = createDataFlowAnalyzer(options);
3980
5347
  const externalAgentController = options.externalAgentControl === void 0 ? void 0 : createExternalAgentBrowserController(options.externalAgentControl);
3981
5348
  const middleware = (request, response, next) => {
3982
- const path9 = requestPath(request);
3983
- const agentRoute = matchAgentRequestPath(path9);
3984
- const externalAgentRoute = matchExternalAgentBrowserPath(path9);
3985
- const externalHandoffRoute = matchExternalHandoffBrowserPath(path9);
3986
- if (path9 !== import_shared20.SPOTPATCH_ENDPOINTS.sourceContext && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.openEditor && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport && agentRoute === void 0 && externalAgentRoute === void 0 && externalHandoffRoute === void 0 && !path9.startsWith(`${import_shared20.SPOTPATCH_API_BASE}/`)) {
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}/`)) {
3987
5355
  next();
3988
5356
  return;
3989
5357
  }
3990
5358
  const handle = async () => {
3991
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
5359
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
3992
5360
  const data = await readRuntimeBootstrap(
3993
5361
  request,
3994
5362
  bootstrap
3995
5363
  );
3996
- writeJson2(response, 200, { ok: true, data });
5364
+ writeJson3(response, 200, { ok: true, data });
3997
5365
  return;
3998
5366
  }
3999
5367
  assertRequestAuthorized(request, {
4000
5368
  allowLan: options.options.allowLan,
4001
5369
  sessionToken: options.session.token
4002
5370
  });
4003
- 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) {
4004
5382
  if (request.method !== "POST") {
4005
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5383
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4006
5384
  }
4007
5385
  const data = await handleSourceContext(request, options);
4008
- writeJson2(response, 200, { ok: true, data });
5386
+ writeJson3(response, 200, { ok: true, data });
4009
5387
  return;
4010
5388
  }
4011
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.openEditor) {
5389
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.openEditor) {
4012
5390
  if (request.method !== "POST") {
4013
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5391
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4014
5392
  }
4015
5393
  const data = await handleOpenEditor(request, options);
4016
- writeJson2(response, 200, { ok: true, data });
5394
+ writeJson3(response, 200, { ok: true, data });
4017
5395
  return;
4018
5396
  }
4019
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
5397
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
4020
5398
  if (request.method !== "POST") {
4021
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5399
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4022
5400
  }
4023
5401
  const data = await handleComponentDataFlowReport(
4024
5402
  request,
4025
5403
  dataFlowAnalyzer,
4026
5404
  options
4027
5405
  );
4028
- writeJson2(response, 200, { ok: true, data });
5406
+ writeJson3(response, 200, { ok: true, data });
4029
5407
  return;
4030
5408
  }
4031
- if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
5409
+ if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
4032
5410
  if (request.method !== "POST") {
4033
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5411
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4034
5412
  }
4035
5413
  const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
4036
- writeJson2(response, 200, { ok: true, data });
5414
+ writeJson3(response, 200, { ok: true, data });
4037
5415
  return;
4038
5416
  }
4039
5417
  if (externalAgentRoute !== void 0) {
4040
5418
  if (!options.options.externalAgent.enabled || externalAgentController === void 0) {
4041
- 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);
4042
5420
  }
4043
5421
  await externalAgentController.handle(
4044
5422
  request,
4045
5423
  response,
4046
5424
  externalAgentRoute,
4047
5425
  (target, status, data) => {
4048
- writeJson2(target, status, { ok: true, data });
5426
+ writeJson3(target, status, { ok: true, data });
4049
5427
  }
4050
5428
  );
4051
5429
  return;
@@ -4062,13 +5440,13 @@ function createSpotPatchMiddleware(options) {
4062
5440
  ...options.externalHandoffService === void 0 ? {} : { service: options.externalHandoffService }
4063
5441
  },
4064
5442
  (target, status, data) => {
4065
- writeJson2(target, status, { ok: true, data });
5443
+ writeJson3(target, status, { ok: true, data });
4066
5444
  }
4067
5445
  );
4068
5446
  return;
4069
5447
  }
4070
5448
  if (agentRoute === void 0) {
4071
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
5449
+ throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
4072
5450
  }
4073
5451
  await handleAgentRequest(
4074
5452
  request,
@@ -4076,12 +5454,12 @@ function createSpotPatchMiddleware(options) {
4076
5454
  options,
4077
5455
  agentRoute,
4078
5456
  (target, status, data) => {
4079
- writeJson2(target, status, { ok: true, data });
5457
+ writeJson3(target, status, { ok: true, data });
4080
5458
  }
4081
5459
  );
4082
5460
  };
4083
5461
  void handle().catch((error) => {
4084
- writeError(response, error, options.logger);
5462
+ writeError2(response, error, options.logger);
4085
5463
  });
4086
5464
  };
4087
5465
  return Object.assign(middleware, {
@@ -4092,13 +5470,13 @@ function createSpotPatchMiddleware(options) {
4092
5470
  }
4093
5471
 
4094
5472
  // src/server/source-registration.ts
4095
- var import_node_crypto11 = require("crypto");
4096
- var import_promises7 = require("fs/promises");
4097
- 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);
4098
5476
  var import_compiler = require("@spotpatch/compiler");
4099
- var import_shared21 = require("@spotpatch/shared");
5477
+ var import_shared24 = require("@spotpatch/shared");
4100
5478
  var import_zod2 = require("zod");
4101
- var REGISTRATION_BODY_LIMIT_BYTES = import_shared21.DEFAULT_DATA_FLOW_LIMITS.protocolRequestMaxBytes;
5479
+ var REGISTRATION_BODY_LIMIT_BYTES = import_shared24.DEFAULT_DATA_FLOW_LIMITS.protocolRequestMaxBytes;
4102
5480
  var REGISTRATION_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{16,128}$/u;
4103
5481
  var DATA_FLOW_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
4104
5482
  var INTERNAL_SECRET_HEADER = "x-spotpatch-internal";
@@ -4114,7 +5492,7 @@ var registrationRequestSchema = import_zod2.z.strictObject({
4114
5492
  line: import_zod2.z.number().int().positive().max(1e7),
4115
5493
  column: import_zod2.z.number().int().positive().max(1e7)
4116
5494
  })
4117
- ).max(import_shared21.DEFAULT_DATA_FLOW_LIMITS.sourceMaxComponents)
5495
+ ).max(import_shared24.DEFAULT_DATA_FLOW_LIMITS.sourceMaxComponents)
4118
5496
  }).optional()
4119
5497
  });
4120
5498
  function getSingleHeader3(request, name) {
@@ -4127,16 +5505,16 @@ function identitiesMatch(actual, expected) {
4127
5505
  }
4128
5506
  const actualBytes = Buffer.from(actual);
4129
5507
  const expectedBytes = Buffer.from(expected);
4130
- 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);
4131
5509
  }
4132
5510
  function isWithinRoot(root, candidate) {
4133
- const relative = import_node_path8.default.relative(root, candidate);
4134
- 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);
4135
5513
  }
4136
5514
  function hasForbiddenSegment(root, candidate) {
4137
- 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));
4138
5516
  }
4139
- function writeJson3(response, statusCode, payload) {
5517
+ function writeJson4(response, statusCode, payload) {
4140
5518
  const body = JSON.stringify(payload);
4141
5519
  response.statusCode = statusCode;
4142
5520
  response.setHeader("Cache-Control", "no-store");
@@ -4156,15 +5534,15 @@ function requestComesFromLoopbackWorker(request) {
4156
5534
  }
4157
5535
  }
4158
5536
  async function resolveAuthorizedSource(root, requestedPath, shouldTransform) {
4159
- if (!import_node_path8.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
5537
+ if (!import_node_path9.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
4160
5538
  return void 0;
4161
5539
  }
4162
5540
  try {
4163
- const sourceStat = await (0, import_promises7.lstat)(requestedPath);
5541
+ const sourceStat = await (0, import_promises8.lstat)(requestedPath);
4164
5542
  if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
4165
5543
  return void 0;
4166
5544
  }
4167
- const resolvedPath = await (0, import_promises7.realpath)(requestedPath);
5545
+ const resolvedPath = await (0, import_promises8.realpath)(requestedPath);
4168
5546
  if (!isWithinRoot(root, resolvedPath) || hasForbiddenSegment(root, resolvedPath) || !shouldTransform(resolvedPath)) {
4169
5547
  return void 0;
4170
5548
  }
@@ -4177,7 +5555,7 @@ async function createSourceRegistrationService(input) {
4177
5555
  if (!REGISTRATION_IDENTITY_PATTERN.test(input.internalSecret) || !REGISTRATION_IDENTITY_PATTERN.test(input.registryEpoch)) {
4178
5556
  throw new TypeError("The source registration identity is invalid.");
4179
5557
  }
4180
- const root = await (0, import_promises7.realpath)(input.root);
5558
+ const root = await (0, import_promises8.realpath)(input.root);
4181
5559
  const sourceFilter = (0, import_compiler.createSourceFilter)(root, input.options);
4182
5560
  const dataFlowFilter = (0, import_compiler.createDataFlowSourceFilter)(root, input.options);
4183
5561
  const registrationQueues = /* @__PURE__ */ new Map();
@@ -4188,7 +5566,7 @@ async function createSourceRegistrationService(input) {
4188
5566
  let fileId;
4189
5567
  const previous = registrationQueues.get(sourcePath) ?? Promise.resolve();
4190
5568
  const current = previous.catch(() => void 0).then(async () => {
4191
- const currentSource = await (0, import_promises7.readFile)(sourcePath, "utf8");
5569
+ const currentSource = await (0, import_promises8.readFile)(sourcePath, "utf8");
4192
5570
  if ((0, import_compiler.createDataFlowSourceVersion)(currentSource) !== dataFlow.sourceVersion) {
4193
5571
  return;
4194
5572
  }
@@ -4213,14 +5591,14 @@ async function createSourceRegistrationService(input) {
4213
5591
  getSingleHeader3(request, INTERNAL_SECRET_HEADER),
4214
5592
  input.internalSecret
4215
5593
  )) {
4216
- writeJson3(response, 403, { ok: false });
5594
+ writeJson4(response, 403, { ok: false });
4217
5595
  return;
4218
5596
  }
4219
5597
  const parsed = registrationRequestSchema.safeParse(
4220
5598
  await readJsonRequestBody(request, REGISTRATION_BODY_LIMIT_BYTES)
4221
5599
  );
4222
5600
  if (!parsed.success || parsed.data.epoch !== input.registryEpoch) {
4223
- writeJson3(response, 400, { ok: false });
5601
+ writeJson4(response, 400, { ok: false });
4224
5602
  return;
4225
5603
  }
4226
5604
  const sourcePath = await resolveAuthorizedSource(
@@ -4229,19 +5607,19 @@ async function createSourceRegistrationService(input) {
4229
5607
  (absolutePath) => parsed.data.dataFlow === void 0 ? sourceFilter.shouldTransform(absolutePath, "<") : dataFlowFilter.shouldTransform(absolutePath, "")
4230
5608
  );
4231
5609
  if (sourcePath === void 0) {
4232
- writeJson3(response, 403, { ok: false });
5610
+ writeJson4(response, 403, { ok: false });
4233
5611
  return;
4234
5612
  }
4235
5613
  const fileId = await registerCurrentSource(sourcePath, parsed.data.dataFlow);
4236
5614
  if (fileId === void 0) {
4237
- writeJson3(response, 409, { ok: false });
5615
+ writeJson4(response, 409, { ok: false });
4238
5616
  return;
4239
5617
  }
4240
- writeJson3(response, 200, { epoch: input.registryEpoch, fileId });
5618
+ writeJson4(response, 200, { epoch: input.registryEpoch, fileId });
4241
5619
  };
4242
5620
  void handle().catch(() => {
4243
5621
  if (!response.headersSent) {
4244
- writeJson3(response, 400, { ok: false });
5622
+ writeJson4(response, 400, { ok: false });
4245
5623
  } else {
4246
5624
  response.destroy();
4247
5625
  }
@@ -4251,11 +5629,11 @@ async function createSourceRegistrationService(input) {
4251
5629
  }
4252
5630
 
4253
5631
  // src/session/session.ts
4254
- var import_node_crypto12 = require("crypto");
5632
+ var import_node_crypto14 = require("crypto");
4255
5633
  function createSession() {
4256
5634
  return Object.freeze({
4257
- id: (0, import_node_crypto12.randomBytes)(16).toString("base64url"),
4258
- 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")
4259
5637
  });
4260
5638
  }
4261
5639
 
@@ -4266,6 +5644,7 @@ var OPTION_KEYS = Object.freeze([
4266
5644
  "budget",
4267
5645
  "debug",
4268
5646
  "dataFlow",
5647
+ "contextualAsk",
4269
5648
  "editor",
4270
5649
  "enabled",
4271
5650
  "externalAgent",
@@ -4357,6 +5736,9 @@ function serializeResolvedSpotPatchOptions(options) {
4357
5736
  dataFlow: options.dataFlow.enabled ? Object.freeze({
4358
5737
  runtime: options.dataFlow.runtime
4359
5738
  }) : false,
5739
+ contextualAsk: options.contextualAsk.enabled ? Object.freeze({
5740
+ ...options.contextualAsk.defaultExecutor === void 0 ? {} : { defaultExecutor: options.contextualAsk.defaultExecutor }
5741
+ }) : false,
4360
5742
  editor: options.editor,
4361
5743
  enabled: options.enabled,
4362
5744
  externalAgent: options.externalAgent.enabled,
@@ -4409,6 +5791,33 @@ function parseDataFlow(value) {
4409
5791
  runtime: value.runtime
4410
5792
  });
4411
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
+ }
4412
5821
  function parseSerializedSpotPatchOptions(value) {
4413
5822
  if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
4414
5823
  throw new TypeError("The SpotPatch options transport is invalid.");
@@ -4423,6 +5832,7 @@ function parseSerializedSpotPatchOptions(value) {
4423
5832
  budget: parseBudget(value.budget),
4424
5833
  debug: value.debug,
4425
5834
  dataFlow: parseDataFlow(value.dataFlow),
5835
+ contextualAsk: parseContextualAsk(value.contextualAsk),
4426
5836
  editor: value.editor,
4427
5837
  enabled: value.enabled,
4428
5838
  externalAgent: value.externalAgent,
@@ -4441,10 +5851,15 @@ function parseSerializedSpotPatchOptions(value) {
4441
5851
  }
4442
5852
  // Annotate the CommonJS export names for ESM import in node:
4443
5853
  0 && (module.exports = {
5854
+ ContextualAskError,
4444
5855
  DEFAULT_EXCLUDE,
4445
5856
  DEFAULT_OPTIONS,
4446
5857
  applyIntegrationPlan,
5858
+ captureAskReadSnapshot,
5859
+ composeContextualAskExecutors,
4447
5860
  createAgentJobManager,
5861
+ createConfiguredKeyAskExecutors,
5862
+ createContextualAskManager,
4448
5863
  createExternalHandoffService,
4449
5864
  createIntegrationFileChange,
4450
5865
  createRuntimeAiConfig,
@@ -4453,9 +5868,12 @@ function parseSerializedSpotPatchOptions(value) {
4453
5868
  createSourceRegistrationService,
4454
5869
  createSourceRegistry,
4455
5870
  createSpotPatchMiddleware,
5871
+ createWorkspaceActivityCoordinator,
4456
5872
  discoverProjectValidationCheck,
5873
+ handleContextualAskRequest,
4457
5874
  integrationPathExists,
4458
5875
  isLoopbackHostname,
5876
+ matchContextualAskPath,
4459
5877
  parseSerializedSpotPatchOptions,
4460
5878
  readIntegrationFile,
4461
5879
  readJsonRequestBody,