@spotpatch/dev-server 0.2.0 → 0.3.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
@@ -32,19 +32,25 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  DEFAULT_EXCLUDE: () => DEFAULT_EXCLUDE,
34
34
  DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
35
+ applyIntegrationPlan: () => applyIntegrationPlan,
35
36
  createAgentJobManager: () => createAgentJobManager,
37
+ createIntegrationFileChange: () => createIntegrationFileChange,
36
38
  createRuntimeAiConfig: () => createRuntimeAiConfig,
37
39
  createSession: () => createSession,
38
40
  createSourceRegistrationService: () => createSourceRegistrationService,
39
41
  createSourceRegistry: () => createSourceRegistry,
40
42
  createSpotPatchMiddleware: () => createSpotPatchMiddleware,
43
+ discoverProjectValidationCheck: () => discoverProjectValidationCheck,
44
+ integrationPathExists: () => integrationPathExists,
41
45
  isLoopbackHostname: () => isLoopbackHostname,
42
46
  parseSerializedSpotPatchOptions: () => parseSerializedSpotPatchOptions,
47
+ readIntegrationFile: () => readIntegrationFile,
43
48
  readJsonRequestBody: () => readJsonRequestBody,
44
49
  readRuntimeBootstrap: () => readRuntimeBootstrap,
45
50
  resolveCredentialEnvironment: () => resolveCredentialEnvironment,
46
51
  resolveEnvironmentAiConfiguration: () => resolveEnvironmentAiConfiguration,
47
52
  resolveOptions: () => resolveOptions,
53
+ resolveProjectOptions: () => resolveProjectOptions,
48
54
  resolveRuntimeBootstrapOptions: () => resolveRuntimeBootstrapOptions,
49
55
  serializeResolvedSpotPatchOptions: () => serializeResolvedSpotPatchOptions
50
56
  });
@@ -322,7 +328,7 @@ function createAgentJobManager(options) {
322
328
  transition(job, "completed", "No source changes were proposed.");
323
329
  return;
324
330
  }
325
- const shouldApplyDirectly = options.ai.execution.applyMode === "auto" && preparedChange.autoApplyEligible || options.ai.execution.applyMode === "trusted-auto" && job.trustedFastModeConsent;
331
+ const shouldApplyDirectly = job.applyMode === "auto" && preparedChange.autoApplyEligible || job.applyMode === "trusted-auto" && job.trustedFastModeConsent;
326
332
  if (shouldApplyDirectly) {
327
333
  try {
328
334
  await applyChange(job, preparedChange);
@@ -398,8 +404,11 @@ function createAgentJobManager(options) {
398
404
  if (closed) {
399
405
  throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.AI_DISABLED);
400
406
  }
401
- const trustedFastModeConfigured = options.ai.execution.applyMode === "trusted-auto";
402
- if (trustedFastModeConfigured !== (request.trustedFastModeConsent === true)) {
407
+ const configuredApplyMode = options.ai.execution.applyMode;
408
+ const requestedApplyMode = request.applyMode ?? (request.trustedFastModeConsent === true ? "trusted-auto" : configuredApplyMode);
409
+ const applyModeAllowed = configuredApplyMode === "trusted-auto" ? requestedApplyMode === "review" || requestedApplyMode === "trusted-auto" : requestedApplyMode === configuredApplyMode;
410
+ const trustedConsentMatches = requestedApplyMode === "trusted-auto" === (request.trustedFastModeConsent === true);
411
+ if (!applyModeAllowed || !trustedConsentMatches) {
403
412
  throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.INVALID_REQUEST);
404
413
  }
405
414
  if (hasActiveJob()) {
@@ -420,6 +429,7 @@ function createAgentJobManager(options) {
420
429
  const timestamp = dependencies.now();
421
430
  const job = {
422
431
  annotation: request.annotation,
432
+ applyMode: requestedApplyMode,
423
433
  controller: new AbortController(),
424
434
  createdAt: timestamp,
425
435
  credential: selection.credential,
@@ -591,6 +601,171 @@ function resolveEnvironmentAiConfiguration(environment) {
591
601
  });
592
602
  }
593
603
 
604
+ // src/integration/file-plan.ts
605
+ var import_node_crypto2 = require("crypto");
606
+ var import_promises = require("fs/promises");
607
+ var import_node_path = __toESM(require("path"), 1);
608
+ function isMissingPathError(error) {
609
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
610
+ }
611
+ function isPathWithin(root, target) {
612
+ const relative = import_node_path.default.relative(root, target);
613
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
614
+ }
615
+ function relativePathWithin(root, target) {
616
+ const relative = import_node_path.default.relative(root, target);
617
+ if (relative.length === 0 || !isPathWithin(root, target)) {
618
+ throw new Error("SpotPatch init refuses to modify a path outside the app root.");
619
+ }
620
+ return relative.split(import_node_path.default.sep).join("/");
621
+ }
622
+ async function integrationPathExists(absolutePath) {
623
+ try {
624
+ await (0, import_promises.access)(absolutePath);
625
+ return true;
626
+ } catch {
627
+ return false;
628
+ }
629
+ }
630
+ async function readIntegrationFile(absolutePath) {
631
+ const metadata = await (0, import_promises.lstat)(absolutePath);
632
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
633
+ throw new Error(
634
+ `SpotPatch refuses to modify the non-regular file ${import_node_path.default.basename(absolutePath)}.`
635
+ );
636
+ }
637
+ return (0, import_promises.readFile)(absolutePath, "utf8");
638
+ }
639
+ function createIntegrationFileChange(appRoot, absolutePath, nextContent, previousContent) {
640
+ if (previousContent === nextContent) {
641
+ return void 0;
642
+ }
643
+ const root = import_node_path.default.resolve(appRoot);
644
+ const target = import_node_path.default.resolve(absolutePath);
645
+ return Object.freeze({
646
+ absolutePath: target,
647
+ nextContent,
648
+ ...previousContent === void 0 ? {} : { previousContent },
649
+ relativePath: relativePathWithin(root, target)
650
+ });
651
+ }
652
+ function temporaryPath(absolutePath, label) {
653
+ return import_node_path.default.join(
654
+ import_node_path.default.dirname(absolutePath),
655
+ `.${import_node_path.default.basename(absolutePath)}.spotpatch-${label}-${String(process.pid)}-${(0, import_node_crypto2.randomBytes)(8).toString("hex")}`
656
+ );
657
+ }
658
+ async function writeAtomic(absolutePath, content, mode) {
659
+ await (0, import_promises.mkdir)(import_node_path.default.dirname(absolutePath), { recursive: true });
660
+ const stagedPath = temporaryPath(absolutePath, "stage");
661
+ try {
662
+ await (0, import_promises.writeFile)(stagedPath, content, { encoding: "utf8", flag: "wx", mode });
663
+ await (0, import_promises.rename)(stagedPath, absolutePath);
664
+ } catch (error) {
665
+ await (0, import_promises.unlink)(stagedPath).catch(() => void 0);
666
+ throw error;
667
+ }
668
+ }
669
+ async function rollbackChange(change) {
670
+ const currentContent = await readIntegrationFile(change.absolutePath);
671
+ if (currentContent !== change.nextContent) {
672
+ throw new Error(
673
+ `SpotPatch init cannot restore ${change.relativePath} because it changed during initialization.`
674
+ );
675
+ }
676
+ if (change.previousContent === void 0) {
677
+ await (0, import_promises.unlink)(change.absolutePath);
678
+ return;
679
+ }
680
+ const mode = (await (0, import_promises.stat)(change.absolutePath)).mode & 511;
681
+ await writeAtomic(change.absolutePath, change.previousContent, mode);
682
+ }
683
+ async function assertSafeTarget(appRoot, realAppRoot, change) {
684
+ const target = import_node_path.default.resolve(change.absolutePath);
685
+ const relativePath = relativePathWithin(appRoot, target);
686
+ if (target !== change.absolutePath || relativePath !== change.relativePath || import_node_path.default.dirname(target) === target) {
687
+ throw new Error("SpotPatch init received an invalid integration file plan.");
688
+ }
689
+ let targetMetadata;
690
+ try {
691
+ targetMetadata = await (0, import_promises.lstat)(target);
692
+ } catch (error) {
693
+ if (!isMissingPathError(error)) {
694
+ throw error;
695
+ }
696
+ }
697
+ if (targetMetadata?.isSymbolicLink()) {
698
+ throw new Error(
699
+ `SpotPatch refuses to modify the symbolic link ${change.relativePath}.`
700
+ );
701
+ }
702
+ const containmentAnchor = await (0, import_promises.realpath)(
703
+ targetMetadata === void 0 ? import_node_path.default.dirname(target) : target
704
+ );
705
+ if (!isPathWithin(realAppRoot, containmentAnchor)) {
706
+ throw new Error("SpotPatch init refuses to modify a path outside the app root.");
707
+ }
708
+ }
709
+ async function assertCurrentBaseline(change) {
710
+ if (change.previousContent === void 0) {
711
+ try {
712
+ await (0, import_promises.lstat)(change.absolutePath);
713
+ } catch (error) {
714
+ if (isMissingPathError(error)) {
715
+ return;
716
+ }
717
+ throw error;
718
+ }
719
+ throw new Error(
720
+ `SpotPatch init cannot create ${change.relativePath} because it now exists.`
721
+ );
722
+ }
723
+ const currentContent = await readIntegrationFile(change.absolutePath);
724
+ if (currentContent !== change.previousContent) {
725
+ throw new Error(
726
+ `SpotPatch init cannot update ${change.relativePath} because it changed after the preview.`
727
+ );
728
+ }
729
+ }
730
+ async function applyIntegrationPlan(plan) {
731
+ if (plan.changes.length === 0) {
732
+ return;
733
+ }
734
+ const appRoot = import_node_path.default.resolve(plan.appRoot);
735
+ const realAppRoot = await (0, import_promises.realpath)(appRoot);
736
+ const targets = /* @__PURE__ */ new Set();
737
+ for (const change of plan.changes) {
738
+ if (targets.has(change.absolutePath)) {
739
+ throw new Error("SpotPatch init received duplicate integration file changes.");
740
+ }
741
+ targets.add(change.absolutePath);
742
+ await assertSafeTarget(appRoot, realAppRoot, change);
743
+ await assertCurrentBaseline(change);
744
+ }
745
+ const applied = [];
746
+ try {
747
+ for (const change of plan.changes) {
748
+ await assertCurrentBaseline(change);
749
+ const mode = change.previousContent === void 0 ? 384 : (await (0, import_promises.stat)(change.absolutePath)).mode & 511;
750
+ await writeAtomic(change.absolutePath, change.nextContent, mode);
751
+ applied.push(change);
752
+ }
753
+ } catch (error) {
754
+ const rollbackResults = await Promise.allSettled(
755
+ applied.reverse().map(rollbackChange)
756
+ );
757
+ if (rollbackResults.some((result) => result.status === "rejected")) {
758
+ throw new Error(
759
+ "SpotPatch init failed and could not completely restore the previous files.",
760
+ { cause: error }
761
+ );
762
+ }
763
+ throw new Error("SpotPatch init failed; all written files were restored.", {
764
+ cause: error
765
+ });
766
+ }
767
+ }
768
+
594
769
  // src/options.ts
595
770
  var import_shared2 = require("@spotpatch/shared");
596
771
  var import_zod = require("zod");
@@ -916,6 +1091,9 @@ function assertPositiveBudget(budget) {
916
1091
  }
917
1092
  }
918
1093
  function resolveOptions(options = {}, environmentAi) {
1094
+ if (options.trustedFastMode !== void 0 && typeof options.trustedFastMode !== "boolean") {
1095
+ throw new RangeError("SpotPatch trustedFastMode must be a boolean.");
1096
+ }
919
1097
  const budget = Object.freeze({
920
1098
  ...DEFAULT_OPTIONS.budget,
921
1099
  ...options.budget
@@ -955,17 +1133,179 @@ function resolveOptions(options = {}, environmentAi) {
955
1133
  return Object.freeze(resolved);
956
1134
  }
957
1135
 
1136
+ // src/project-validation.ts
1137
+ var import_node_child_process = require("child_process");
1138
+ var import_promises2 = require("fs/promises");
1139
+ var import_node_module = require("module");
1140
+ var import_node_path2 = __toESM(require("path"), 1);
1141
+ var import_node_util = require("util");
1142
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
1143
+ var TYPESCRIPT_CHECK_ID = "spotpatch-typecheck";
1144
+ var TYPESCRIPT_CHECK_LABEL = "TypeScript";
1145
+ function isRecord(value) {
1146
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1147
+ }
1148
+ async function isRegularFile(absolutePath) {
1149
+ try {
1150
+ const metadata = await (0, import_promises2.lstat)(absolutePath);
1151
+ return metadata.isFile() && !metadata.isSymbolicLink();
1152
+ } catch {
1153
+ return false;
1154
+ }
1155
+ }
1156
+ async function readManifest(appRoot) {
1157
+ const manifestPath = import_node_path2.default.join(appRoot, "package.json");
1158
+ if (!await isRegularFile(manifestPath)) {
1159
+ return void 0;
1160
+ }
1161
+ try {
1162
+ const value = JSON.parse(await (0, import_promises2.readFile)(manifestPath, "utf8"));
1163
+ return isRecord(value) ? value : void 0;
1164
+ } catch {
1165
+ return void 0;
1166
+ }
1167
+ }
1168
+ function declaresTypeScript(manifest) {
1169
+ return [
1170
+ manifest.dependencies,
1171
+ manifest.devDependencies,
1172
+ manifest.peerDependencies
1173
+ ].some(
1174
+ (dependencies) => isRecord(dependencies) && typeof dependencies.typescript === "string"
1175
+ );
1176
+ }
1177
+ async function findGitRoot(appRoot) {
1178
+ try {
1179
+ const result = await execFileAsync("git", ["rev-parse", "--show-toplevel"], {
1180
+ cwd: appRoot,
1181
+ encoding: "utf8",
1182
+ timeout: 5e3,
1183
+ windowsHide: true
1184
+ });
1185
+ const root = await (0, import_promises2.realpath)(result.stdout.trim());
1186
+ const relative = import_node_path2.default.relative(root, appRoot);
1187
+ if (relative === "" || !relative.startsWith(`..${import_node_path2.default.sep}`) && relative !== ".." && !import_node_path2.default.isAbsolute(relative)) {
1188
+ return root;
1189
+ }
1190
+ } catch {
1191
+ return void 0;
1192
+ }
1193
+ return void 0;
1194
+ }
1195
+ async function resolveTypeScriptCli(appRoot) {
1196
+ const resolveFromApplication = (0, import_node_module.createRequire)(import_node_path2.default.join(appRoot, "package.json"));
1197
+ try {
1198
+ const packagePath = resolveFromApplication.resolve("typescript/package.json");
1199
+ const cliPath = import_node_path2.default.join(import_node_path2.default.dirname(packagePath), "bin", "tsc");
1200
+ await (0, import_promises2.access)(cliPath);
1201
+ return await (0, import_promises2.realpath)(cliPath);
1202
+ } catch {
1203
+ return void 0;
1204
+ }
1205
+ }
1206
+ function portableRelativePath(from, to) {
1207
+ return import_node_path2.default.relative(from, to).split(import_node_path2.default.sep).join("/");
1208
+ }
1209
+ async function discoverProjectValidationCheck(options) {
1210
+ const appRoot = await (0, import_promises2.realpath)(options.appRoot);
1211
+ const tsconfigPath = import_node_path2.default.join(appRoot, "tsconfig.json");
1212
+ const [manifest, projectRoot, hasTsconfig] = await Promise.all([
1213
+ readManifest(appRoot),
1214
+ findGitRoot(appRoot),
1215
+ isRegularFile(tsconfigPath)
1216
+ ]);
1217
+ if (manifest === void 0 || projectRoot === void 0 || !hasTsconfig || !declaresTypeScript(manifest)) {
1218
+ return void 0;
1219
+ }
1220
+ const cliPath = await resolveTypeScriptCli(appRoot);
1221
+ if (cliPath === void 0) {
1222
+ return void 0;
1223
+ }
1224
+ const projectPath = portableRelativePath(projectRoot, tsconfigPath);
1225
+ if (projectPath.length === 0 || projectPath.startsWith("../")) {
1226
+ return void 0;
1227
+ }
1228
+ return Object.freeze({
1229
+ id: TYPESCRIPT_CHECK_ID,
1230
+ label: TYPESCRIPT_CHECK_LABEL,
1231
+ command: process.execPath,
1232
+ args: Object.freeze([
1233
+ cliPath,
1234
+ "--noEmit",
1235
+ "--pretty",
1236
+ "false",
1237
+ "--project",
1238
+ projectPath
1239
+ ]),
1240
+ required: true,
1241
+ timeoutMs: options.timeoutMs
1242
+ });
1243
+ }
1244
+
1245
+ // src/project-options.ts
1246
+ function hasRequiredCheck(ai) {
1247
+ return Object.values(ai.execution.checks).some((check) => check.required);
1248
+ }
1249
+ function availableCheckId(checks, preferred) {
1250
+ if (checks[preferred] === void 0) {
1251
+ return preferred;
1252
+ }
1253
+ let suffix = 2;
1254
+ while (checks[`${preferred}-${String(suffix)}`] !== void 0) {
1255
+ suffix += 1;
1256
+ }
1257
+ return `${preferred}-${String(suffix)}`;
1258
+ }
1259
+ async function resolveProjectOptions(input) {
1260
+ const userOptions = input.options ?? {};
1261
+ const resolved = resolveOptions(userOptions, input.environmentAi);
1262
+ if (!userOptions.trustedFastMode || resolved.ai === false) {
1263
+ return resolved;
1264
+ }
1265
+ if (resolved.ai.execution.applyMode === "auto") {
1266
+ throw new RangeError(
1267
+ "SpotPatch trustedFastMode cannot be combined with applyMode auto."
1268
+ );
1269
+ }
1270
+ let checks = resolved.ai.execution.checks;
1271
+ if (!hasRequiredCheck(resolved.ai)) {
1272
+ const discovered = await discoverProjectValidationCheck({
1273
+ appRoot: input.appRoot,
1274
+ timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
1275
+ });
1276
+ if (discovered === void 0) {
1277
+ throw new RangeError(
1278
+ "SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
1279
+ );
1280
+ }
1281
+ const id = availableCheckId(checks, discovered.id);
1282
+ checks = Object.freeze({
1283
+ ...checks,
1284
+ [id]: Object.freeze({ ...discovered, id })
1285
+ });
1286
+ }
1287
+ const ai = Object.freeze({
1288
+ ...resolved.ai,
1289
+ execution: Object.freeze({
1290
+ ...resolved.ai.execution,
1291
+ applyMode: "trusted-auto",
1292
+ checks
1293
+ })
1294
+ });
1295
+ return Object.freeze({ ...resolved, ai });
1296
+ }
1297
+
958
1298
  // src/registry/source-registry.ts
959
- var import_node_path = __toESM(require("path"), 1);
1299
+ var import_node_path3 = __toESM(require("path"), 1);
960
1300
 
961
1301
  // src/registry/source-id.ts
962
- var import_node_crypto2 = require("crypto");
1302
+ var import_node_crypto3 = require("crypto");
963
1303
  var SOURCE_ID_BYTES = 8;
964
- var createRandomSourceId = () => (0, import_node_crypto2.randomBytes)(SOURCE_ID_BYTES).toString("base64url");
1304
+ var createRandomSourceId = () => (0, import_node_crypto3.randomBytes)(SOURCE_ID_BYTES).toString("base64url");
965
1305
 
966
1306
  // src/registry/source-registry.ts
967
1307
  function normalizeAbsolutePath(absolutePath) {
968
- return import_node_path.default.normalize(import_node_path.default.resolve(absolutePath));
1308
+ return import_node_path3.default.normalize(import_node_path3.default.resolve(absolutePath));
969
1309
  }
970
1310
  function createSourceRegistry(options = {}) {
971
1311
  const createId = options.createId ?? createRandomSourceId;
@@ -1003,13 +1343,13 @@ var import_shared10 = require("@spotpatch/shared");
1003
1343
  var import_shared7 = require("@spotpatch/shared");
1004
1344
 
1005
1345
  // src/server/agent-request.ts
1006
- var import_promises3 = require("fs/promises");
1007
- var import_node_path4 = __toESM(require("path"), 1);
1346
+ var import_promises5 = require("fs/promises");
1347
+ var import_node_path6 = __toESM(require("path"), 1);
1008
1348
  var import_shared5 = require("@spotpatch/shared");
1009
1349
 
1010
1350
  // src/server/source-context.ts
1011
- var import_promises2 = require("fs/promises");
1012
- var import_node_path3 = __toESM(require("path"), 1);
1351
+ var import_promises4 = require("fs/promises");
1352
+ var import_node_path5 = __toESM(require("path"), 1);
1013
1353
  var import_shared4 = require("@spotpatch/shared");
1014
1354
 
1015
1355
  // src/server/extract-code-context.ts
@@ -1239,8 +1579,8 @@ function extractCodeContext(options) {
1239
1579
  }
1240
1580
 
1241
1581
  // src/server/source-file.ts
1242
- var import_promises = require("fs/promises");
1243
- var import_node_path2 = __toESM(require("path"), 1);
1582
+ var import_promises3 = require("fs/promises");
1583
+ var import_node_path4 = __toESM(require("path"), 1);
1244
1584
  var import_shared3 = require("@spotpatch/shared");
1245
1585
 
1246
1586
  // src/server/constants.ts
@@ -1258,8 +1598,8 @@ async function assertInsideRoot(root, candidate) {
1258
1598
  let realCandidate;
1259
1599
  try {
1260
1600
  [realRoot, realCandidate] = await Promise.all([
1261
- (0, import_promises.realpath)(root),
1262
- (0, import_promises.realpath)(candidate)
1601
+ (0, import_promises3.realpath)(root),
1602
+ (0, import_promises3.realpath)(candidate)
1263
1603
  ]);
1264
1604
  } catch (error) {
1265
1605
  if (isMissingFileError(error)) {
@@ -1269,8 +1609,8 @@ async function assertInsideRoot(root, candidate) {
1269
1609
  }
1270
1610
  throw error;
1271
1611
  }
1272
- const relative = import_node_path2.default.relative(realRoot, realCandidate);
1273
- const outside = relative.startsWith(`..${import_node_path2.default.sep}`) || relative === ".." || import_node_path2.default.isAbsolute(relative);
1612
+ const relative = import_node_path4.default.relative(realRoot, realCandidate);
1613
+ const outside = relative.startsWith(`..${import_node_path4.default.sep}`) || relative === ".." || import_node_path4.default.isAbsolute(relative);
1274
1614
  if (outside) {
1275
1615
  throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
1276
1616
  }
@@ -1282,12 +1622,12 @@ async function resolveSourceFile(options) {
1282
1622
  throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
1283
1623
  }
1284
1624
  const sourcePath = await assertInsideRoot(options.root, registeredPath);
1285
- if (!ALLOWED_EXTENSIONS.has(import_node_path2.default.extname(sourcePath).toLowerCase())) {
1625
+ if (!ALLOWED_EXTENSIONS.has(import_node_path4.default.extname(sourcePath).toLowerCase())) {
1286
1626
  throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
1287
1627
  }
1288
1628
  let sourceStat;
1289
1629
  try {
1290
- sourceStat = await (0, import_promises.stat)(sourcePath);
1630
+ sourceStat = await (0, import_promises3.stat)(sourcePath);
1291
1631
  } catch (error) {
1292
1632
  if (isMissingFileError(error)) {
1293
1633
  throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
@@ -1307,7 +1647,7 @@ async function resolveSourceFile(options) {
1307
1647
 
1308
1648
  // src/server/source-context.ts
1309
1649
  function toDisplayPath(root, sourcePath) {
1310
- return import_node_path3.default.relative(root, sourcePath).split(import_node_path3.default.sep).join("/");
1650
+ return import_node_path5.default.relative(root, sourcePath).split(import_node_path5.default.sep).join("/");
1311
1651
  }
1312
1652
  async function readSourceContext(options) {
1313
1653
  const sourcePath = await resolveSourceFile({
@@ -1317,7 +1657,7 @@ async function readSourceContext(options) {
1317
1657
  });
1318
1658
  let source;
1319
1659
  try {
1320
- source = await (0, import_promises2.readFile)(sourcePath, "utf8");
1660
+ source = await (0, import_promises4.readFile)(sourcePath, "utf8");
1321
1661
  } catch (error) {
1322
1662
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
1323
1663
  throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
@@ -1330,11 +1670,11 @@ async function readSourceContext(options) {
1330
1670
  if (options.request.line > lines.length) {
1331
1671
  throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
1332
1672
  }
1333
- const extension = import_node_path3.default.extname(sourcePath).toLowerCase();
1673
+ const extension = import_node_path5.default.extname(sourcePath).toLowerCase();
1334
1674
  return extractCodeContext({
1335
1675
  source,
1336
1676
  sourcePath,
1337
- relativePath: toDisplayPath(await (0, import_promises2.realpath)(options.root), sourcePath),
1677
+ relativePath: toDisplayPath(await (0, import_promises4.realpath)(options.root), sourcePath),
1338
1678
  language: extension === ".tsx" ? "tsx" : "jsx",
1339
1679
  line: options.request.line,
1340
1680
  column: options.request.column,
@@ -1370,7 +1710,7 @@ async function authorizeSourceRef(source, registry, root) {
1370
1710
  registry,
1371
1711
  root
1372
1712
  });
1373
- const relativePath = import_node_path4.default.relative(await (0, import_promises3.realpath)(root), sourcePath).split(import_node_path4.default.sep).join("/");
1713
+ const relativePath = import_node_path6.default.relative(await (0, import_promises5.realpath)(root), sourcePath).split(import_node_path6.default.sep).join("/");
1374
1714
  if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
1375
1715
  throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
1376
1716
  }
@@ -1475,6 +1815,7 @@ async function authorizeAgentJobRequest(input) {
1475
1815
  });
1476
1816
  return Object.freeze({
1477
1817
  annotation,
1818
+ ...input.request.applyMode === void 0 ? {} : { applyMode: input.request.applyMode },
1478
1819
  providerProfileId: input.request.providerProfileId,
1479
1820
  modelProfileId: input.request.modelProfileId,
1480
1821
  providerDataConsent: true,
@@ -1541,21 +1882,21 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
1541
1882
  "reverted",
1542
1883
  "failed"
1543
1884
  ]);
1544
- function matchAgentRequestPath(path6) {
1545
- if (path6 === import_shared7.SPOTPATCH_ENDPOINTS.agentCapability) {
1885
+ function matchAgentRequestPath(path8) {
1886
+ if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentCapability) {
1546
1887
  return Object.freeze({ kind: "capability" });
1547
1888
  }
1548
- if (path6 === import_shared7.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
1889
+ if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
1549
1890
  return Object.freeze({ kind: "workspace-health" });
1550
1891
  }
1551
- if (path6 === import_shared7.SPOTPATCH_ENDPOINTS.agentJobs) {
1892
+ if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentJobs) {
1552
1893
  return Object.freeze({ kind: "create-job" });
1553
1894
  }
1554
1895
  const prefix = `${import_shared7.SPOTPATCH_ENDPOINTS.agentJobs}/`;
1555
- if (!path6.startsWith(prefix)) {
1896
+ if (!path8.startsWith(prefix)) {
1556
1897
  return void 0;
1557
1898
  }
1558
- const segments = path6.slice(prefix.length).split("/");
1899
+ const segments = path8.slice(prefix.length).split("/");
1559
1900
  const jobId = segments[0];
1560
1901
  const action = segments[1];
1561
1902
  if (segments.length !== 2 || jobId === void 0 || !AGENT_JOB_ID_PATTERN.test(jobId) || action === void 0 || !AGENT_JOB_ACTIONS.has(action)) {
@@ -1725,7 +2066,7 @@ async function handleAgentRequest(request, response, options, route, writeSucces
1725
2066
  }
1726
2067
 
1727
2068
  // src/server/editor.ts
1728
- var import_node_child_process = require("child_process");
2069
+ var import_node_child_process2 = require("child_process");
1729
2070
  var import_launch_editor = __toESM(require("launch-editor"), 1);
1730
2071
  var EDITOR_STARTUP_GRACE_MS = 300;
1731
2072
  function normalizedEditorEnvironment(environment) {
@@ -1752,7 +2093,7 @@ function editorCommand(editor) {
1752
2093
  var DEFAULT_DEPENDENCIES2 = Object.freeze({
1753
2094
  environment: process.env,
1754
2095
  fallbackLauncher: import_launch_editor.default,
1755
- processSpawner: (command, arguments_, options) => (0, import_node_child_process.spawn)(command, [...arguments_], options),
2096
+ processSpawner: (command, arguments_, options) => (0, import_node_child_process2.spawn)(command, [...arguments_], options),
1756
2097
  startupGraceMs: EDITOR_STARTUP_GRACE_MS
1757
2098
  });
1758
2099
  function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
@@ -1810,7 +2151,7 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
1810
2151
  var launchConfiguredEditor = createEditorLauncher();
1811
2152
 
1812
2153
  // src/server/request-security.ts
1813
- var import_node_crypto3 = require("crypto");
2154
+ var import_node_crypto4 = require("crypto");
1814
2155
  var import_node_net = require("net");
1815
2156
  var import_shared8 = require("@spotpatch/shared");
1816
2157
  function getSingleHeader(request, name) {
@@ -1823,7 +2164,7 @@ function tokensMatch(actual, expected) {
1823
2164
  }
1824
2165
  const actualBytes = Buffer.from(actual);
1825
2166
  const expectedBytes = Buffer.from(expected);
1826
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto3.timingSafeEqual)(actualBytes, expectedBytes);
2167
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto4.timingSafeEqual)(actualBytes, expectedBytes);
1827
2168
  }
1828
2169
  function isLoopbackHostname(hostname) {
1829
2170
  const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
@@ -2078,14 +2419,14 @@ async function handleOpenEditor(request, options) {
2078
2419
  function createSpotPatchMiddleware(options) {
2079
2420
  const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
2080
2421
  return (request, response, next) => {
2081
- const path6 = requestPath(request);
2082
- const agentRoute = matchAgentRequestPath(path6);
2083
- if (path6 !== import_shared10.SPOTPATCH_ENDPOINTS.sourceContext && path6 !== import_shared10.SPOTPATCH_ENDPOINTS.openEditor && agentRoute === void 0 && !path6.startsWith(`${import_shared10.SPOTPATCH_API_BASE}/`)) {
2422
+ const path8 = requestPath(request);
2423
+ const agentRoute = matchAgentRequestPath(path8);
2424
+ if (path8 !== import_shared10.SPOTPATCH_ENDPOINTS.sourceContext && path8 !== import_shared10.SPOTPATCH_ENDPOINTS.openEditor && agentRoute === void 0 && !path8.startsWith(`${import_shared10.SPOTPATCH_API_BASE}/`)) {
2084
2425
  next();
2085
2426
  return;
2086
2427
  }
2087
2428
  const handle = async () => {
2088
- if (path6 === import_shared10.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
2429
+ if (path8 === import_shared10.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
2089
2430
  const data = await readRuntimeBootstrap(
2090
2431
  request,
2091
2432
  bootstrap
@@ -2097,7 +2438,7 @@ function createSpotPatchMiddleware(options) {
2097
2438
  allowLan: options.options.allowLan,
2098
2439
  sessionToken: options.session.token
2099
2440
  });
2100
- if (path6 === import_shared10.SPOTPATCH_ENDPOINTS.sourceContext) {
2441
+ if (path8 === import_shared10.SPOTPATCH_ENDPOINTS.sourceContext) {
2101
2442
  if (request.method !== "POST") {
2102
2443
  throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
2103
2444
  }
@@ -2105,7 +2446,7 @@ function createSpotPatchMiddleware(options) {
2105
2446
  writeJson(response, 200, { ok: true, data });
2106
2447
  return;
2107
2448
  }
2108
- if (path6 === import_shared10.SPOTPATCH_ENDPOINTS.openEditor) {
2449
+ if (path8 === import_shared10.SPOTPATCH_ENDPOINTS.openEditor) {
2109
2450
  if (request.method !== "POST") {
2110
2451
  throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
2111
2452
  }
@@ -2133,9 +2474,9 @@ function createSpotPatchMiddleware(options) {
2133
2474
  }
2134
2475
 
2135
2476
  // src/server/source-registration.ts
2136
- var import_node_crypto4 = require("crypto");
2137
- var import_promises4 = require("fs/promises");
2138
- var import_node_path5 = __toESM(require("path"), 1);
2477
+ var import_node_crypto5 = require("crypto");
2478
+ var import_promises6 = require("fs/promises");
2479
+ var import_node_path7 = __toESM(require("path"), 1);
2139
2480
  var import_compiler = require("@spotpatch/compiler");
2140
2481
  var import_zod2 = require("zod");
2141
2482
  var REGISTRATION_BODY_LIMIT_BYTES = 4096;
@@ -2156,14 +2497,14 @@ function identitiesMatch(actual, expected) {
2156
2497
  }
2157
2498
  const actualBytes = Buffer.from(actual);
2158
2499
  const expectedBytes = Buffer.from(expected);
2159
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto4.timingSafeEqual)(actualBytes, expectedBytes);
2500
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto5.timingSafeEqual)(actualBytes, expectedBytes);
2160
2501
  }
2161
2502
  function isWithinRoot(root, candidate) {
2162
- const relative = import_node_path5.default.relative(root, candidate);
2163
- return relative === "" || !relative.startsWith(`..${import_node_path5.default.sep}`) && relative !== ".." && !import_node_path5.default.isAbsolute(relative);
2503
+ const relative = import_node_path7.default.relative(root, candidate);
2504
+ return relative === "" || !relative.startsWith(`..${import_node_path7.default.sep}`) && relative !== ".." && !import_node_path7.default.isAbsolute(relative);
2164
2505
  }
2165
2506
  function hasForbiddenSegment(root, candidate) {
2166
- return import_node_path5.default.relative(root, candidate).split(import_node_path5.default.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
2507
+ return import_node_path7.default.relative(root, candidate).split(import_node_path7.default.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
2167
2508
  }
2168
2509
  function writeJson2(response, statusCode, payload) {
2169
2510
  const body = JSON.stringify(payload);
@@ -2185,15 +2526,15 @@ function requestComesFromLoopbackWorker(request) {
2185
2526
  }
2186
2527
  }
2187
2528
  async function resolveAuthorizedSource(root, requestedPath, shouldTransform) {
2188
- if (!import_node_path5.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
2529
+ if (!import_node_path7.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
2189
2530
  return void 0;
2190
2531
  }
2191
2532
  try {
2192
- const sourceStat = await (0, import_promises4.lstat)(requestedPath);
2533
+ const sourceStat = await (0, import_promises6.lstat)(requestedPath);
2193
2534
  if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
2194
2535
  return void 0;
2195
2536
  }
2196
- const resolvedPath = await (0, import_promises4.realpath)(requestedPath);
2537
+ const resolvedPath = await (0, import_promises6.realpath)(requestedPath);
2197
2538
  if (!isWithinRoot(root, resolvedPath) || hasForbiddenSegment(root, resolvedPath) || !shouldTransform(resolvedPath)) {
2198
2539
  return void 0;
2199
2540
  }
@@ -2206,7 +2547,7 @@ async function createSourceRegistrationService(input) {
2206
2547
  if (!REGISTRATION_IDENTITY_PATTERN.test(input.internalSecret) || !REGISTRATION_IDENTITY_PATTERN.test(input.registryEpoch)) {
2207
2548
  throw new TypeError("The source registration identity is invalid.");
2208
2549
  }
2209
- const root = await (0, import_promises4.realpath)(input.root);
2550
+ const root = await (0, import_promises6.realpath)(input.root);
2210
2551
  const sourceFilter = (0, import_compiler.createSourceFilter)(root, input.options);
2211
2552
  const handler = (request, response) => {
2212
2553
  const handle = async () => {
@@ -2251,11 +2592,11 @@ async function createSourceRegistrationService(input) {
2251
2592
  }
2252
2593
 
2253
2594
  // src/session/session.ts
2254
- var import_node_crypto5 = require("crypto");
2595
+ var import_node_crypto6 = require("crypto");
2255
2596
  function createSession() {
2256
2597
  return Object.freeze({
2257
- id: (0, import_node_crypto5.randomBytes)(16).toString("base64url"),
2258
- token: (0, import_node_crypto5.randomBytes)(16).toString("base64url")
2598
+ id: (0, import_node_crypto6.randomBytes)(16).toString("base64url"),
2599
+ token: (0, import_node_crypto6.randomBytes)(16).toString("base64url")
2259
2600
  });
2260
2601
  }
2261
2602
 
@@ -2283,7 +2624,7 @@ var BUDGET_KEYS = Object.freeze([
2283
2624
  "maxComponentDepth"
2284
2625
  ]);
2285
2626
  var REGEXP_FLAGS_PATTERN = /^(?!.*(.).*\1)[dgimsuvy]*$/u;
2286
- function isRecord(value) {
2627
+ function isRecord2(value) {
2287
2628
  return typeof value === "object" && value !== null && !Array.isArray(value);
2288
2629
  }
2289
2630
  function hasExactKeys(value, keys) {
@@ -2368,7 +2709,7 @@ function parseFilterList(value) {
2368
2709
  }
2369
2710
  return Object.freeze(
2370
2711
  value.map((entry) => {
2371
- if (!isRecord(entry)) {
2712
+ if (!isRecord2(entry)) {
2372
2713
  throw new TypeError("The SpotPatch filter transport is invalid.");
2373
2714
  }
2374
2715
  if (entry.kind === "string" && hasExactKeys(entry, ["kind", "value"]) && typeof entry.value === "string" && entry.value.length > 0 && entry.value.length <= 1024 && !entry.value.includes("\0")) {
@@ -2386,7 +2727,7 @@ function parseFilterList(value) {
2386
2727
  );
2387
2728
  }
2388
2729
  function parseBudget(value) {
2389
- if (!isRecord(value) || !hasExactKeys(value, BUDGET_KEYS)) {
2730
+ if (!isRecord2(value) || !hasExactKeys(value, BUDGET_KEYS)) {
2390
2731
  throw new TypeError("The SpotPatch budget transport is invalid.");
2391
2732
  }
2392
2733
  const budget = Object.fromEntries(
@@ -2395,10 +2736,10 @@ function parseBudget(value) {
2395
2736
  return Object.freeze(budget);
2396
2737
  }
2397
2738
  function parseSerializedSpotPatchOptions(value) {
2398
- if (!isRecord(value) || !hasExactKeys(value, OPTION_KEYS)) {
2739
+ if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
2399
2740
  throw new TypeError("The SpotPatch options transport is invalid.");
2400
2741
  }
2401
- if (typeof value.enabled !== "boolean" || typeof value.redact !== "boolean" || typeof value.allowLan !== "boolean" || typeof value.debug !== "boolean" || typeof value.shortcut !== "string" || typeof value.maxTargets !== "number" || typeof value.editor !== "string" || typeof value.locale !== "string" || value.ai !== false && !isRecord(value.ai)) {
2742
+ if (typeof value.enabled !== "boolean" || typeof value.redact !== "boolean" || typeof value.allowLan !== "boolean" || typeof value.debug !== "boolean" || typeof value.shortcut !== "string" || typeof value.maxTargets !== "number" || typeof value.editor !== "string" || typeof value.locale !== "string" || value.ai !== false && !isRecord2(value.ai)) {
2402
2743
  throw new TypeError("The SpotPatch options transport is invalid.");
2403
2744
  }
2404
2745
  try {
@@ -2426,19 +2767,25 @@ function parseSerializedSpotPatchOptions(value) {
2426
2767
  0 && (module.exports = {
2427
2768
  DEFAULT_EXCLUDE,
2428
2769
  DEFAULT_OPTIONS,
2770
+ applyIntegrationPlan,
2429
2771
  createAgentJobManager,
2772
+ createIntegrationFileChange,
2430
2773
  createRuntimeAiConfig,
2431
2774
  createSession,
2432
2775
  createSourceRegistrationService,
2433
2776
  createSourceRegistry,
2434
2777
  createSpotPatchMiddleware,
2778
+ discoverProjectValidationCheck,
2779
+ integrationPathExists,
2435
2780
  isLoopbackHostname,
2436
2781
  parseSerializedSpotPatchOptions,
2782
+ readIntegrationFile,
2437
2783
  readJsonRequestBody,
2438
2784
  readRuntimeBootstrap,
2439
2785
  resolveCredentialEnvironment,
2440
2786
  resolveEnvironmentAiConfiguration,
2441
2787
  resolveOptions,
2788
+ resolveProjectOptions,
2442
2789
  resolveRuntimeBootstrapOptions,
2443
2790
  serializeResolvedSpotPatchOptions
2444
2791
  });