@spotpatch/dev-server 0.1.2 → 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.js CHANGED
@@ -280,7 +280,8 @@ function createAgentJobManager(options) {
280
280
  transition(job, "completed", "No source changes were proposed.");
281
281
  return;
282
282
  }
283
- if (options.ai.execution.applyMode === "auto" && preparedChange.autoApplyEligible) {
283
+ const shouldApplyDirectly = job.applyMode === "auto" && preparedChange.autoApplyEligible || job.applyMode === "trusted-auto" && job.trustedFastModeConsent;
284
+ if (shouldApplyDirectly) {
284
285
  try {
285
286
  await applyChange(job, preparedChange);
286
287
  } catch {
@@ -355,6 +356,13 @@ function createAgentJobManager(options) {
355
356
  if (closed) {
356
357
  throw new SpotPatchError(ERROR_CODES.AI_DISABLED);
357
358
  }
359
+ const configuredApplyMode = options.ai.execution.applyMode;
360
+ const requestedApplyMode = request.applyMode ?? (request.trustedFastModeConsent === true ? "trusted-auto" : configuredApplyMode);
361
+ const applyModeAllowed = configuredApplyMode === "trusted-auto" ? requestedApplyMode === "review" || requestedApplyMode === "trusted-auto" : requestedApplyMode === configuredApplyMode;
362
+ const trustedConsentMatches = requestedApplyMode === "trusted-auto" === (request.trustedFastModeConsent === true);
363
+ if (!applyModeAllowed || !trustedConsentMatches) {
364
+ throw new SpotPatchError(ERROR_CODES.INVALID_REQUEST);
365
+ }
358
366
  if (hasActiveJob()) {
359
367
  throw new SpotPatchError(ERROR_CODES.AGENT_BUSY);
360
368
  }
@@ -373,6 +381,7 @@ function createAgentJobManager(options) {
373
381
  const timestamp = dependencies.now();
374
382
  const job = {
375
383
  annotation: request.annotation,
384
+ applyMode: requestedApplyMode,
376
385
  controller: new AbortController(),
377
386
  createdAt: timestamp,
378
387
  credential: selection.credential,
@@ -388,6 +397,7 @@ function createAgentJobManager(options) {
388
397
  runPromise: void 0,
389
398
  sequence: 0,
390
399
  status: "queued",
400
+ trustedFastModeConsent: request.trustedFastModeConsent === true,
391
401
  updatedAt: timestamp,
392
402
  workingTreeMode: request.workingTreeMode
393
403
  };
@@ -543,8 +553,184 @@ function resolveEnvironmentAiConfiguration(environment) {
543
553
  });
544
554
  }
545
555
 
556
+ // src/integration/file-plan.ts
557
+ import { randomBytes as randomBytes2 } from "crypto";
558
+ import {
559
+ access,
560
+ lstat,
561
+ mkdir,
562
+ readFile,
563
+ realpath,
564
+ rename,
565
+ stat,
566
+ unlink,
567
+ writeFile
568
+ } from "fs/promises";
569
+ import path from "path";
570
+ function isMissingPathError(error) {
571
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
572
+ }
573
+ function isPathWithin(root, target) {
574
+ const relative = path.relative(root, target);
575
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
576
+ }
577
+ function relativePathWithin(root, target) {
578
+ const relative = path.relative(root, target);
579
+ if (relative.length === 0 || !isPathWithin(root, target)) {
580
+ throw new Error("SpotPatch init refuses to modify a path outside the app root.");
581
+ }
582
+ return relative.split(path.sep).join("/");
583
+ }
584
+ async function integrationPathExists(absolutePath) {
585
+ try {
586
+ await access(absolutePath);
587
+ return true;
588
+ } catch {
589
+ return false;
590
+ }
591
+ }
592
+ async function readIntegrationFile(absolutePath) {
593
+ const metadata = await lstat(absolutePath);
594
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
595
+ throw new Error(
596
+ `SpotPatch refuses to modify the non-regular file ${path.basename(absolutePath)}.`
597
+ );
598
+ }
599
+ return readFile(absolutePath, "utf8");
600
+ }
601
+ function createIntegrationFileChange(appRoot, absolutePath, nextContent, previousContent) {
602
+ if (previousContent === nextContent) {
603
+ return void 0;
604
+ }
605
+ const root = path.resolve(appRoot);
606
+ const target = path.resolve(absolutePath);
607
+ return Object.freeze({
608
+ absolutePath: target,
609
+ nextContent,
610
+ ...previousContent === void 0 ? {} : { previousContent },
611
+ relativePath: relativePathWithin(root, target)
612
+ });
613
+ }
614
+ function temporaryPath(absolutePath, label) {
615
+ return path.join(
616
+ path.dirname(absolutePath),
617
+ `.${path.basename(absolutePath)}.spotpatch-${label}-${String(process.pid)}-${randomBytes2(8).toString("hex")}`
618
+ );
619
+ }
620
+ async function writeAtomic(absolutePath, content, mode) {
621
+ await mkdir(path.dirname(absolutePath), { recursive: true });
622
+ const stagedPath = temporaryPath(absolutePath, "stage");
623
+ try {
624
+ await writeFile(stagedPath, content, { encoding: "utf8", flag: "wx", mode });
625
+ await rename(stagedPath, absolutePath);
626
+ } catch (error) {
627
+ await unlink(stagedPath).catch(() => void 0);
628
+ throw error;
629
+ }
630
+ }
631
+ async function rollbackChange(change) {
632
+ const currentContent = await readIntegrationFile(change.absolutePath);
633
+ if (currentContent !== change.nextContent) {
634
+ throw new Error(
635
+ `SpotPatch init cannot restore ${change.relativePath} because it changed during initialization.`
636
+ );
637
+ }
638
+ if (change.previousContent === void 0) {
639
+ await unlink(change.absolutePath);
640
+ return;
641
+ }
642
+ const mode = (await stat(change.absolutePath)).mode & 511;
643
+ await writeAtomic(change.absolutePath, change.previousContent, mode);
644
+ }
645
+ async function assertSafeTarget(appRoot, realAppRoot, change) {
646
+ const target = path.resolve(change.absolutePath);
647
+ const relativePath = relativePathWithin(appRoot, target);
648
+ if (target !== change.absolutePath || relativePath !== change.relativePath || path.dirname(target) === target) {
649
+ throw new Error("SpotPatch init received an invalid integration file plan.");
650
+ }
651
+ let targetMetadata;
652
+ try {
653
+ targetMetadata = await lstat(target);
654
+ } catch (error) {
655
+ if (!isMissingPathError(error)) {
656
+ throw error;
657
+ }
658
+ }
659
+ if (targetMetadata?.isSymbolicLink()) {
660
+ throw new Error(
661
+ `SpotPatch refuses to modify the symbolic link ${change.relativePath}.`
662
+ );
663
+ }
664
+ const containmentAnchor = await realpath(
665
+ targetMetadata === void 0 ? path.dirname(target) : target
666
+ );
667
+ if (!isPathWithin(realAppRoot, containmentAnchor)) {
668
+ throw new Error("SpotPatch init refuses to modify a path outside the app root.");
669
+ }
670
+ }
671
+ async function assertCurrentBaseline(change) {
672
+ if (change.previousContent === void 0) {
673
+ try {
674
+ await lstat(change.absolutePath);
675
+ } catch (error) {
676
+ if (isMissingPathError(error)) {
677
+ return;
678
+ }
679
+ throw error;
680
+ }
681
+ throw new Error(
682
+ `SpotPatch init cannot create ${change.relativePath} because it now exists.`
683
+ );
684
+ }
685
+ const currentContent = await readIntegrationFile(change.absolutePath);
686
+ if (currentContent !== change.previousContent) {
687
+ throw new Error(
688
+ `SpotPatch init cannot update ${change.relativePath} because it changed after the preview.`
689
+ );
690
+ }
691
+ }
692
+ async function applyIntegrationPlan(plan) {
693
+ if (plan.changes.length === 0) {
694
+ return;
695
+ }
696
+ const appRoot = path.resolve(plan.appRoot);
697
+ const realAppRoot = await realpath(appRoot);
698
+ const targets = /* @__PURE__ */ new Set();
699
+ for (const change of plan.changes) {
700
+ if (targets.has(change.absolutePath)) {
701
+ throw new Error("SpotPatch init received duplicate integration file changes.");
702
+ }
703
+ targets.add(change.absolutePath);
704
+ await assertSafeTarget(appRoot, realAppRoot, change);
705
+ await assertCurrentBaseline(change);
706
+ }
707
+ const applied = [];
708
+ try {
709
+ for (const change of plan.changes) {
710
+ await assertCurrentBaseline(change);
711
+ const mode = change.previousContent === void 0 ? 384 : (await stat(change.absolutePath)).mode & 511;
712
+ await writeAtomic(change.absolutePath, change.nextContent, mode);
713
+ applied.push(change);
714
+ }
715
+ } catch (error) {
716
+ const rollbackResults = await Promise.allSettled(
717
+ applied.reverse().map(rollbackChange)
718
+ );
719
+ if (rollbackResults.some((result) => result.status === "rejected")) {
720
+ throw new Error(
721
+ "SpotPatch init failed and could not completely restore the previous files.",
722
+ { cause: error }
723
+ );
724
+ }
725
+ throw new Error("SpotPatch init failed; all written files were restored.", {
726
+ cause: error
727
+ });
728
+ }
729
+ }
730
+
546
731
  // src/options.ts
547
732
  import {
733
+ AGENT_APPLY_MODES,
548
734
  DEFAULT_AGENT_LIMITS,
549
735
  MAX_ANNOTATION_TARGETS,
550
736
  SPOTPATCH_EDITOR_PREFERENCES,
@@ -625,7 +811,7 @@ var aiOptionsSchema = z.strictObject({
625
811
  defaultProvider: z.string(),
626
812
  execution: z.strictObject({
627
813
  isolation: z.literal("git-worktree").optional(),
628
- applyMode: z.enum(["review", "auto"]).optional(),
814
+ applyMode: z.enum(AGENT_APPLY_MODES).optional(),
629
815
  checks: z.record(z.string(), agentCheckSchema).optional(),
630
816
  limits: agentLimitsSchema
631
817
  }).optional()
@@ -820,8 +1006,8 @@ function resolveAiOptions(options) {
820
1006
  const limits = resolveLimits(validated.execution?.limits);
821
1007
  const checks = resolveChecks(validated.execution?.checks, limits.checkTimeoutMs);
822
1008
  const applyMode = validated.execution?.applyMode ?? "review";
823
- if (applyMode === "auto" && !Object.values(checks).some((check) => check.required)) {
824
- throw new RangeError("SpotPatch AI auto mode requires a required check.");
1009
+ if (applyMode !== "review" && !Object.values(checks).some((check) => check.required)) {
1010
+ throw new RangeError(`SpotPatch AI ${applyMode} mode requires a required check.`);
825
1011
  }
826
1012
  const providers = resolveProviders(validated.providers);
827
1013
  if (!(validated.defaultProvider in providers)) {
@@ -873,6 +1059,9 @@ function assertPositiveBudget(budget) {
873
1059
  }
874
1060
  }
875
1061
  function resolveOptions(options = {}, environmentAi) {
1062
+ if (options.trustedFastMode !== void 0 && typeof options.trustedFastMode !== "boolean") {
1063
+ throw new RangeError("SpotPatch trustedFastMode must be a boolean.");
1064
+ }
876
1065
  const budget = Object.freeze({
877
1066
  ...DEFAULT_OPTIONS.budget,
878
1067
  ...options.budget
@@ -912,17 +1101,179 @@ function resolveOptions(options = {}, environmentAi) {
912
1101
  return Object.freeze(resolved);
913
1102
  }
914
1103
 
1104
+ // src/project-validation.ts
1105
+ import { execFile } from "child_process";
1106
+ import { access as access2, lstat as lstat2, readFile as readFile2, realpath as realpath2 } from "fs/promises";
1107
+ import { createRequire } from "module";
1108
+ import path2 from "path";
1109
+ import { promisify } from "util";
1110
+ var execFileAsync = promisify(execFile);
1111
+ var TYPESCRIPT_CHECK_ID = "spotpatch-typecheck";
1112
+ var TYPESCRIPT_CHECK_LABEL = "TypeScript";
1113
+ function isRecord(value) {
1114
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1115
+ }
1116
+ async function isRegularFile(absolutePath) {
1117
+ try {
1118
+ const metadata = await lstat2(absolutePath);
1119
+ return metadata.isFile() && !metadata.isSymbolicLink();
1120
+ } catch {
1121
+ return false;
1122
+ }
1123
+ }
1124
+ async function readManifest(appRoot) {
1125
+ const manifestPath = path2.join(appRoot, "package.json");
1126
+ if (!await isRegularFile(manifestPath)) {
1127
+ return void 0;
1128
+ }
1129
+ try {
1130
+ const value = JSON.parse(await readFile2(manifestPath, "utf8"));
1131
+ return isRecord(value) ? value : void 0;
1132
+ } catch {
1133
+ return void 0;
1134
+ }
1135
+ }
1136
+ function declaresTypeScript(manifest) {
1137
+ return [
1138
+ manifest.dependencies,
1139
+ manifest.devDependencies,
1140
+ manifest.peerDependencies
1141
+ ].some(
1142
+ (dependencies) => isRecord(dependencies) && typeof dependencies.typescript === "string"
1143
+ );
1144
+ }
1145
+ async function findGitRoot(appRoot) {
1146
+ try {
1147
+ const result = await execFileAsync("git", ["rev-parse", "--show-toplevel"], {
1148
+ cwd: appRoot,
1149
+ encoding: "utf8",
1150
+ timeout: 5e3,
1151
+ windowsHide: true
1152
+ });
1153
+ const root = await realpath2(result.stdout.trim());
1154
+ const relative = path2.relative(root, appRoot);
1155
+ if (relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative)) {
1156
+ return root;
1157
+ }
1158
+ } catch {
1159
+ return void 0;
1160
+ }
1161
+ return void 0;
1162
+ }
1163
+ async function resolveTypeScriptCli(appRoot) {
1164
+ const resolveFromApplication = createRequire(path2.join(appRoot, "package.json"));
1165
+ try {
1166
+ const packagePath = resolveFromApplication.resolve("typescript/package.json");
1167
+ const cliPath = path2.join(path2.dirname(packagePath), "bin", "tsc");
1168
+ await access2(cliPath);
1169
+ return await realpath2(cliPath);
1170
+ } catch {
1171
+ return void 0;
1172
+ }
1173
+ }
1174
+ function portableRelativePath(from, to) {
1175
+ return path2.relative(from, to).split(path2.sep).join("/");
1176
+ }
1177
+ async function discoverProjectValidationCheck(options) {
1178
+ const appRoot = await realpath2(options.appRoot);
1179
+ const tsconfigPath = path2.join(appRoot, "tsconfig.json");
1180
+ const [manifest, projectRoot, hasTsconfig] = await Promise.all([
1181
+ readManifest(appRoot),
1182
+ findGitRoot(appRoot),
1183
+ isRegularFile(tsconfigPath)
1184
+ ]);
1185
+ if (manifest === void 0 || projectRoot === void 0 || !hasTsconfig || !declaresTypeScript(manifest)) {
1186
+ return void 0;
1187
+ }
1188
+ const cliPath = await resolveTypeScriptCli(appRoot);
1189
+ if (cliPath === void 0) {
1190
+ return void 0;
1191
+ }
1192
+ const projectPath = portableRelativePath(projectRoot, tsconfigPath);
1193
+ if (projectPath.length === 0 || projectPath.startsWith("../")) {
1194
+ return void 0;
1195
+ }
1196
+ return Object.freeze({
1197
+ id: TYPESCRIPT_CHECK_ID,
1198
+ label: TYPESCRIPT_CHECK_LABEL,
1199
+ command: process.execPath,
1200
+ args: Object.freeze([
1201
+ cliPath,
1202
+ "--noEmit",
1203
+ "--pretty",
1204
+ "false",
1205
+ "--project",
1206
+ projectPath
1207
+ ]),
1208
+ required: true,
1209
+ timeoutMs: options.timeoutMs
1210
+ });
1211
+ }
1212
+
1213
+ // src/project-options.ts
1214
+ function hasRequiredCheck(ai) {
1215
+ return Object.values(ai.execution.checks).some((check) => check.required);
1216
+ }
1217
+ function availableCheckId(checks, preferred) {
1218
+ if (checks[preferred] === void 0) {
1219
+ return preferred;
1220
+ }
1221
+ let suffix = 2;
1222
+ while (checks[`${preferred}-${String(suffix)}`] !== void 0) {
1223
+ suffix += 1;
1224
+ }
1225
+ return `${preferred}-${String(suffix)}`;
1226
+ }
1227
+ async function resolveProjectOptions(input) {
1228
+ const userOptions = input.options ?? {};
1229
+ const resolved = resolveOptions(userOptions, input.environmentAi);
1230
+ if (!userOptions.trustedFastMode || resolved.ai === false) {
1231
+ return resolved;
1232
+ }
1233
+ if (resolved.ai.execution.applyMode === "auto") {
1234
+ throw new RangeError(
1235
+ "SpotPatch trustedFastMode cannot be combined with applyMode auto."
1236
+ );
1237
+ }
1238
+ let checks = resolved.ai.execution.checks;
1239
+ if (!hasRequiredCheck(resolved.ai)) {
1240
+ const discovered = await discoverProjectValidationCheck({
1241
+ appRoot: input.appRoot,
1242
+ timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
1243
+ });
1244
+ if (discovered === void 0) {
1245
+ throw new RangeError(
1246
+ "SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
1247
+ );
1248
+ }
1249
+ const id = availableCheckId(checks, discovered.id);
1250
+ checks = Object.freeze({
1251
+ ...checks,
1252
+ [id]: Object.freeze({ ...discovered, id })
1253
+ });
1254
+ }
1255
+ const ai = Object.freeze({
1256
+ ...resolved.ai,
1257
+ execution: Object.freeze({
1258
+ ...resolved.ai.execution,
1259
+ applyMode: "trusted-auto",
1260
+ checks
1261
+ })
1262
+ });
1263
+ return Object.freeze({ ...resolved, ai });
1264
+ }
1265
+
915
1266
  // src/registry/source-registry.ts
916
- import path from "path";
1267
+ import path3 from "path";
917
1268
 
918
1269
  // src/registry/source-id.ts
919
- import { randomBytes as randomBytes2 } from "crypto";
1270
+ import { randomBytes as randomBytes3 } from "crypto";
920
1271
  var SOURCE_ID_BYTES = 8;
921
- var createRandomSourceId = () => randomBytes2(SOURCE_ID_BYTES).toString("base64url");
1272
+ var createRandomSourceId = () => randomBytes3(SOURCE_ID_BYTES).toString("base64url");
922
1273
 
923
1274
  // src/registry/source-registry.ts
924
1275
  function normalizeAbsolutePath(absolutePath) {
925
- return path.normalize(path.resolve(absolutePath));
1276
+ return path3.normalize(path3.resolve(absolutePath));
926
1277
  }
927
1278
  function createSourceRegistry(options = {}) {
928
1279
  const createId = options.createId ?? createRandomSourceId;
@@ -975,16 +1326,16 @@ import {
975
1326
  } from "@spotpatch/shared";
976
1327
 
977
1328
  // src/server/agent-request.ts
978
- import { realpath as realpath3 } from "fs/promises";
979
- import path4 from "path";
1329
+ import { realpath as realpath5 } from "fs/promises";
1330
+ import path6 from "path";
980
1331
  import {
981
1332
  ERROR_CODES as ERROR_CODES4,
982
1333
  SpotPatchError as SpotPatchError4
983
1334
  } from "@spotpatch/shared";
984
1335
 
985
1336
  // src/server/source-context.ts
986
- import { readFile, realpath as realpath2 } from "fs/promises";
987
- import path3 from "path";
1337
+ import { readFile as readFile3, realpath as realpath4 } from "fs/promises";
1338
+ import path5 from "path";
988
1339
  import {
989
1340
  ERROR_CODES as ERROR_CODES3,
990
1341
  SpotPatchError as SpotPatchError3
@@ -1220,8 +1571,8 @@ function extractCodeContext(options) {
1220
1571
  }
1221
1572
 
1222
1573
  // src/server/source-file.ts
1223
- import { realpath, stat } from "fs/promises";
1224
- import path2 from "path";
1574
+ import { realpath as realpath3, stat as stat2 } from "fs/promises";
1575
+ import path4 from "path";
1225
1576
  import { ERROR_CODES as ERROR_CODES2, SpotPatchError as SpotPatchError2 } from "@spotpatch/shared";
1226
1577
 
1227
1578
  // src/server/constants.ts
@@ -1239,8 +1590,8 @@ async function assertInsideRoot(root, candidate) {
1239
1590
  let realCandidate;
1240
1591
  try {
1241
1592
  [realRoot, realCandidate] = await Promise.all([
1242
- realpath(root),
1243
- realpath(candidate)
1593
+ realpath3(root),
1594
+ realpath3(candidate)
1244
1595
  ]);
1245
1596
  } catch (error) {
1246
1597
  if (isMissingFileError(error)) {
@@ -1250,8 +1601,8 @@ async function assertInsideRoot(root, candidate) {
1250
1601
  }
1251
1602
  throw error;
1252
1603
  }
1253
- const relative = path2.relative(realRoot, realCandidate);
1254
- const outside = relative.startsWith(`..${path2.sep}`) || relative === ".." || path2.isAbsolute(relative);
1604
+ const relative = path4.relative(realRoot, realCandidate);
1605
+ const outside = relative.startsWith(`..${path4.sep}`) || relative === ".." || path4.isAbsolute(relative);
1255
1606
  if (outside) {
1256
1607
  throw new SpotPatchError2(ERROR_CODES2.SOURCE_OUTSIDE_ROOT);
1257
1608
  }
@@ -1263,12 +1614,12 @@ async function resolveSourceFile(options) {
1263
1614
  throw new SpotPatchError2(ERROR_CODES2.SOURCE_NOT_FOUND);
1264
1615
  }
1265
1616
  const sourcePath = await assertInsideRoot(options.root, registeredPath);
1266
- if (!ALLOWED_EXTENSIONS.has(path2.extname(sourcePath).toLowerCase())) {
1617
+ if (!ALLOWED_EXTENSIONS.has(path4.extname(sourcePath).toLowerCase())) {
1267
1618
  throw new SpotPatchError2(ERROR_CODES2.SOURCE_NOT_FOUND);
1268
1619
  }
1269
1620
  let sourceStat;
1270
1621
  try {
1271
- sourceStat = await stat(sourcePath);
1622
+ sourceStat = await stat2(sourcePath);
1272
1623
  } catch (error) {
1273
1624
  if (isMissingFileError(error)) {
1274
1625
  throw new SpotPatchError2(ERROR_CODES2.SOURCE_NOT_FOUND, void 0, {
@@ -1288,7 +1639,7 @@ async function resolveSourceFile(options) {
1288
1639
 
1289
1640
  // src/server/source-context.ts
1290
1641
  function toDisplayPath(root, sourcePath) {
1291
- return path3.relative(root, sourcePath).split(path3.sep).join("/");
1642
+ return path5.relative(root, sourcePath).split(path5.sep).join("/");
1292
1643
  }
1293
1644
  async function readSourceContext(options) {
1294
1645
  const sourcePath = await resolveSourceFile({
@@ -1298,7 +1649,7 @@ async function readSourceContext(options) {
1298
1649
  });
1299
1650
  let source;
1300
1651
  try {
1301
- source = await readFile(sourcePath, "utf8");
1652
+ source = await readFile3(sourcePath, "utf8");
1302
1653
  } catch (error) {
1303
1654
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
1304
1655
  throw new SpotPatchError3(ERROR_CODES3.SOURCE_NOT_FOUND, void 0, {
@@ -1311,11 +1662,11 @@ async function readSourceContext(options) {
1311
1662
  if (options.request.line > lines.length) {
1312
1663
  throw new SpotPatchError3(ERROR_CODES3.INVALID_REQUEST);
1313
1664
  }
1314
- const extension = path3.extname(sourcePath).toLowerCase();
1665
+ const extension = path5.extname(sourcePath).toLowerCase();
1315
1666
  return extractCodeContext({
1316
1667
  source,
1317
1668
  sourcePath,
1318
- relativePath: toDisplayPath(await realpath2(options.root), sourcePath),
1669
+ relativePath: toDisplayPath(await realpath4(options.root), sourcePath),
1319
1670
  language: extension === ".tsx" ? "tsx" : "jsx",
1320
1671
  line: options.request.line,
1321
1672
  column: options.request.column,
@@ -1351,7 +1702,7 @@ async function authorizeSourceRef(source, registry, root) {
1351
1702
  registry,
1352
1703
  root
1353
1704
  });
1354
- const relativePath = path4.relative(await realpath3(root), sourcePath).split(path4.sep).join("/");
1705
+ const relativePath = path6.relative(await realpath5(root), sourcePath).split(path6.sep).join("/");
1355
1706
  if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
1356
1707
  throw new SpotPatchError4(ERROR_CODES4.INVALID_REQUEST);
1357
1708
  }
@@ -1456,9 +1807,11 @@ async function authorizeAgentJobRequest(input) {
1456
1807
  });
1457
1808
  return Object.freeze({
1458
1809
  annotation,
1810
+ ...input.request.applyMode === void 0 ? {} : { applyMode: input.request.applyMode },
1459
1811
  providerProfileId: input.request.providerProfileId,
1460
1812
  modelProfileId: input.request.modelProfileId,
1461
1813
  providerDataConsent: true,
1814
+ ...input.request.trustedFastModeConsent === true ? { trustedFastModeConsent: true } : {},
1462
1815
  workingTreeMode: input.request.workingTreeMode
1463
1816
  });
1464
1817
  }
@@ -1521,21 +1874,21 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
1521
1874
  "reverted",
1522
1875
  "failed"
1523
1876
  ]);
1524
- function matchAgentRequestPath(path6) {
1525
- if (path6 === SPOTPATCH_ENDPOINTS.agentCapability) {
1877
+ function matchAgentRequestPath(path8) {
1878
+ if (path8 === SPOTPATCH_ENDPOINTS.agentCapability) {
1526
1879
  return Object.freeze({ kind: "capability" });
1527
1880
  }
1528
- if (path6 === SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
1881
+ if (path8 === SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
1529
1882
  return Object.freeze({ kind: "workspace-health" });
1530
1883
  }
1531
- if (path6 === SPOTPATCH_ENDPOINTS.agentJobs) {
1884
+ if (path8 === SPOTPATCH_ENDPOINTS.agentJobs) {
1532
1885
  return Object.freeze({ kind: "create-job" });
1533
1886
  }
1534
1887
  const prefix = `${SPOTPATCH_ENDPOINTS.agentJobs}/`;
1535
- if (!path6.startsWith(prefix)) {
1888
+ if (!path8.startsWith(prefix)) {
1536
1889
  return void 0;
1537
1890
  }
1538
- const segments = path6.slice(prefix.length).split("/");
1891
+ const segments = path8.slice(prefix.length).split("/");
1539
1892
  const jobId = segments[0];
1540
1893
  const action = segments[1];
1541
1894
  if (segments.length !== 2 || jobId === void 0 || !AGENT_JOB_ID_PATTERN.test(jobId) || action === void 0 || !AGENT_JOB_ACTIONS.has(action)) {
@@ -2063,14 +2416,14 @@ async function handleOpenEditor(request, options) {
2063
2416
  function createSpotPatchMiddleware(options) {
2064
2417
  const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
2065
2418
  return (request, response, next) => {
2066
- const path6 = requestPath(request);
2067
- const agentRoute = matchAgentRequestPath(path6);
2068
- if (path6 !== SPOTPATCH_ENDPOINTS2.sourceContext && path6 !== SPOTPATCH_ENDPOINTS2.openEditor && agentRoute === void 0 && !path6.startsWith(`${SPOTPATCH_API_BASE}/`)) {
2419
+ const path8 = requestPath(request);
2420
+ const agentRoute = matchAgentRequestPath(path8);
2421
+ if (path8 !== SPOTPATCH_ENDPOINTS2.sourceContext && path8 !== SPOTPATCH_ENDPOINTS2.openEditor && agentRoute === void 0 && !path8.startsWith(`${SPOTPATCH_API_BASE}/`)) {
2069
2422
  next();
2070
2423
  return;
2071
2424
  }
2072
2425
  const handle = async () => {
2073
- if (path6 === SPOTPATCH_ENDPOINTS2.bootstrap && bootstrap !== void 0) {
2426
+ if (path8 === SPOTPATCH_ENDPOINTS2.bootstrap && bootstrap !== void 0) {
2074
2427
  const data = await readRuntimeBootstrap(
2075
2428
  request,
2076
2429
  bootstrap
@@ -2082,7 +2435,7 @@ function createSpotPatchMiddleware(options) {
2082
2435
  allowLan: options.options.allowLan,
2083
2436
  sessionToken: options.session.token
2084
2437
  });
2085
- if (path6 === SPOTPATCH_ENDPOINTS2.sourceContext) {
2438
+ if (path8 === SPOTPATCH_ENDPOINTS2.sourceContext) {
2086
2439
  if (request.method !== "POST") {
2087
2440
  throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2088
2441
  }
@@ -2090,7 +2443,7 @@ function createSpotPatchMiddleware(options) {
2090
2443
  writeJson(response, 200, { ok: true, data });
2091
2444
  return;
2092
2445
  }
2093
- if (path6 === SPOTPATCH_ENDPOINTS2.openEditor) {
2446
+ if (path8 === SPOTPATCH_ENDPOINTS2.openEditor) {
2094
2447
  if (request.method !== "POST") {
2095
2448
  throw new SpotPatchError9(ERROR_CODES9.INVALID_REQUEST);
2096
2449
  }
@@ -2119,8 +2472,8 @@ function createSpotPatchMiddleware(options) {
2119
2472
 
2120
2473
  // src/server/source-registration.ts
2121
2474
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
2122
- import { lstat, realpath as realpath4 } from "fs/promises";
2123
- import path5 from "path";
2475
+ import { lstat as lstat3, realpath as realpath6 } from "fs/promises";
2476
+ import path7 from "path";
2124
2477
  import { createSourceFilter } from "@spotpatch/compiler";
2125
2478
  import { z as z2 } from "zod";
2126
2479
  var REGISTRATION_BODY_LIMIT_BYTES = 4096;
@@ -2144,11 +2497,11 @@ function identitiesMatch(actual, expected) {
2144
2497
  return actualBytes.byteLength === expectedBytes.byteLength && timingSafeEqual2(actualBytes, expectedBytes);
2145
2498
  }
2146
2499
  function isWithinRoot(root, candidate) {
2147
- const relative = path5.relative(root, candidate);
2148
- return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== ".." && !path5.isAbsolute(relative);
2500
+ const relative = path7.relative(root, candidate);
2501
+ return relative === "" || !relative.startsWith(`..${path7.sep}`) && relative !== ".." && !path7.isAbsolute(relative);
2149
2502
  }
2150
2503
  function hasForbiddenSegment(root, candidate) {
2151
- return path5.relative(root, candidate).split(path5.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
2504
+ return path7.relative(root, candidate).split(path7.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
2152
2505
  }
2153
2506
  function writeJson2(response, statusCode, payload) {
2154
2507
  const body = JSON.stringify(payload);
@@ -2170,15 +2523,15 @@ function requestComesFromLoopbackWorker(request) {
2170
2523
  }
2171
2524
  }
2172
2525
  async function resolveAuthorizedSource(root, requestedPath, shouldTransform) {
2173
- if (!path5.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
2526
+ if (!path7.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
2174
2527
  return void 0;
2175
2528
  }
2176
2529
  try {
2177
- const sourceStat = await lstat(requestedPath);
2530
+ const sourceStat = await lstat3(requestedPath);
2178
2531
  if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
2179
2532
  return void 0;
2180
2533
  }
2181
- const resolvedPath = await realpath4(requestedPath);
2534
+ const resolvedPath = await realpath6(requestedPath);
2182
2535
  if (!isWithinRoot(root, resolvedPath) || hasForbiddenSegment(root, resolvedPath) || !shouldTransform(resolvedPath)) {
2183
2536
  return void 0;
2184
2537
  }
@@ -2191,7 +2544,7 @@ async function createSourceRegistrationService(input) {
2191
2544
  if (!REGISTRATION_IDENTITY_PATTERN.test(input.internalSecret) || !REGISTRATION_IDENTITY_PATTERN.test(input.registryEpoch)) {
2192
2545
  throw new TypeError("The source registration identity is invalid.");
2193
2546
  }
2194
- const root = await realpath4(input.root);
2547
+ const root = await realpath6(input.root);
2195
2548
  const sourceFilter = createSourceFilter(root, input.options);
2196
2549
  const handler = (request, response) => {
2197
2550
  const handle = async () => {
@@ -2236,11 +2589,11 @@ async function createSourceRegistrationService(input) {
2236
2589
  }
2237
2590
 
2238
2591
  // src/session/session.ts
2239
- import { randomBytes as randomBytes3 } from "crypto";
2592
+ import { randomBytes as randomBytes4 } from "crypto";
2240
2593
  function createSession() {
2241
2594
  return Object.freeze({
2242
- id: randomBytes3(16).toString("base64url"),
2243
- token: randomBytes3(16).toString("base64url")
2595
+ id: randomBytes4(16).toString("base64url"),
2596
+ token: randomBytes4(16).toString("base64url")
2244
2597
  });
2245
2598
  }
2246
2599
 
@@ -2268,7 +2621,7 @@ var BUDGET_KEYS = Object.freeze([
2268
2621
  "maxComponentDepth"
2269
2622
  ]);
2270
2623
  var REGEXP_FLAGS_PATTERN = /^(?!.*(.).*\1)[dgimsuvy]*$/u;
2271
- function isRecord(value) {
2624
+ function isRecord2(value) {
2272
2625
  return typeof value === "object" && value !== null && !Array.isArray(value);
2273
2626
  }
2274
2627
  function hasExactKeys(value, keys) {
@@ -2353,7 +2706,7 @@ function parseFilterList(value) {
2353
2706
  }
2354
2707
  return Object.freeze(
2355
2708
  value.map((entry) => {
2356
- if (!isRecord(entry)) {
2709
+ if (!isRecord2(entry)) {
2357
2710
  throw new TypeError("The SpotPatch filter transport is invalid.");
2358
2711
  }
2359
2712
  if (entry.kind === "string" && hasExactKeys(entry, ["kind", "value"]) && typeof entry.value === "string" && entry.value.length > 0 && entry.value.length <= 1024 && !entry.value.includes("\0")) {
@@ -2371,7 +2724,7 @@ function parseFilterList(value) {
2371
2724
  );
2372
2725
  }
2373
2726
  function parseBudget(value) {
2374
- if (!isRecord(value) || !hasExactKeys(value, BUDGET_KEYS)) {
2727
+ if (!isRecord2(value) || !hasExactKeys(value, BUDGET_KEYS)) {
2375
2728
  throw new TypeError("The SpotPatch budget transport is invalid.");
2376
2729
  }
2377
2730
  const budget = Object.fromEntries(
@@ -2380,10 +2733,10 @@ function parseBudget(value) {
2380
2733
  return Object.freeze(budget);
2381
2734
  }
2382
2735
  function parseSerializedSpotPatchOptions(value) {
2383
- if (!isRecord(value) || !hasExactKeys(value, OPTION_KEYS)) {
2736
+ if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
2384
2737
  throw new TypeError("The SpotPatch options transport is invalid.");
2385
2738
  }
2386
- 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)) {
2739
+ 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)) {
2387
2740
  throw new TypeError("The SpotPatch options transport is invalid.");
2388
2741
  }
2389
2742
  try {
@@ -2410,19 +2763,25 @@ function parseSerializedSpotPatchOptions(value) {
2410
2763
  export {
2411
2764
  DEFAULT_EXCLUDE,
2412
2765
  DEFAULT_OPTIONS,
2766
+ applyIntegrationPlan,
2413
2767
  createAgentJobManager,
2768
+ createIntegrationFileChange,
2414
2769
  createRuntimeAiConfig,
2415
2770
  createSession,
2416
2771
  createSourceRegistrationService,
2417
2772
  createSourceRegistry,
2418
2773
  createSpotPatchMiddleware,
2774
+ discoverProjectValidationCheck,
2775
+ integrationPathExists,
2419
2776
  isLoopbackHostname,
2420
2777
  parseSerializedSpotPatchOptions,
2778
+ readIntegrationFile,
2421
2779
  readJsonRequestBody,
2422
2780
  readRuntimeBootstrap,
2423
2781
  resolveCredentialEnvironment,
2424
2782
  resolveEnvironmentAiConfiguration,
2425
2783
  resolveOptions,
2784
+ resolveProjectOptions,
2426
2785
  resolveRuntimeBootstrapOptions,
2427
2786
  serializeResolvedSpotPatchOptions
2428
2787
  };