@spotpatch/agent 1.1.0 → 1.2.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
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  createOpenAICompatibleProviderSession: () => createOpenAICompatibleProviderSession,
35
35
  createProviderCredential: () => createProviderCredential,
36
36
  executeAgentChange: () => executeAgentChange,
37
+ inspectAgentWorkspace: () => inspectAgentWorkspace,
37
38
  probeProviderCapability: () => probeProviderCapability,
38
39
  resolveProviderCredential: () => resolveProviderCredential,
39
40
  revertPreparedAgentChange: () => revertPreparedAgentChange
@@ -41,7 +42,7 @@ __export(index_exports, {
41
42
  module.exports = __toCommonJS(index_exports);
42
43
 
43
44
  // src/engine/execute-agent-change.ts
44
- var import_shared19 = require("@spotpatch/shared");
45
+ var import_shared20 = require("@spotpatch/shared");
45
46
 
46
47
  // src/provider/openai-compatible-provider.ts
47
48
  var import_shared7 = require("@spotpatch/shared");
@@ -752,7 +753,7 @@ var PROTECTED_FILE_NAMES = /* @__PURE__ */ new Set([
752
753
  "yarn.lock"
753
754
  ]);
754
755
  function deny() {
755
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.TOOL_DENIED);
756
+ throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.TOOL_PATH_DENIED);
756
757
  }
757
758
  function hasControlCharacter(value) {
758
759
  for (let index = 0; index < value.length; index += 1) {
@@ -854,13 +855,13 @@ async function readAgentTextFile(root, relativePath, maximumBytes) {
854
855
  }
855
856
  const bytes = await (0, import_promises2.readFile)(absolutePath);
856
857
  if (bytes.includes(0)) {
857
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_DENIED);
858
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_PATH_DENIED);
858
859
  }
859
860
  let content;
860
861
  try {
861
862
  content = utf8Decoder.decode(bytes);
862
863
  } catch {
863
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_DENIED);
864
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_PATH_DENIED);
864
865
  }
865
866
  return Object.freeze({ content, relativePath, size: metadata.size });
866
867
  }
@@ -873,7 +874,7 @@ function encodeUtf8Text(content, includeByteOrderMark) {
873
874
  }
874
875
  async function writeAgentTextFileIfContentMatches(root, relativePath, expectedContent, nextContent, maximumBytes) {
875
876
  if (nextContent.includes("\0")) {
876
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_DENIED);
877
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_INPUT_INVALID);
877
878
  }
878
879
  const absolutePath = await resolveExistingAgentPath(root, relativePath);
879
880
  const [metadata, currentBytes] = await Promise.all([
@@ -887,7 +888,7 @@ async function writeAgentTextFileIfContentMatches(root, relativePath, expectedCo
887
888
  try {
888
889
  currentContent = utf8Decoder.decode(currentBytes);
889
890
  } catch {
890
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_DENIED);
891
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.TOOL_PATH_DENIED);
891
892
  }
892
893
  if (currentBytes.includes(0) || currentContent !== expectedContent) {
893
894
  throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.PATCH_REJECTED);
@@ -1142,7 +1143,7 @@ async function runConfiguredCheck(options) {
1142
1143
  function requireConfiguredCheck(checkId, checks) {
1143
1144
  const check = checks[checkId];
1144
1145
  if (check === void 0) {
1145
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.TOOL_DENIED);
1146
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.TOOL_INPUT_INVALID);
1146
1147
  }
1147
1148
  return check;
1148
1149
  }
@@ -1331,9 +1332,16 @@ async function applyAgentPatch(worktreeRoot, patch, limits, signal) {
1331
1332
  throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.AGENT_LIMIT_EXCEEDED);
1332
1333
  }
1333
1334
  await Promise.all(
1334
- files.map(
1335
- async (file) => resolveWritableAgentPath(worktreeRoot, file.relativePath)
1336
- )
1335
+ files.map(async (file) => {
1336
+ await resolveWritableAgentPath(worktreeRoot, file.relativePath);
1337
+ if (file.kind !== "added") {
1338
+ await readAgentTextFile(
1339
+ worktreeRoot,
1340
+ file.relativePath,
1341
+ limits.maxReadBytesPerFile
1342
+ );
1343
+ }
1344
+ })
1337
1345
  );
1338
1346
  await runGitCommand({
1339
1347
  cwd: worktreeRoot,
@@ -1447,7 +1455,7 @@ var MAX_DISCOVERED_FILES = 2e4;
1447
1455
  var TEXT_SAMPLE_BYTES = 8192;
1448
1456
  function compileGlob(glob) {
1449
1457
  if (glob.length === 0 || glob.length > 256 || glob.includes("\0") || glob.includes("\\") || glob.startsWith("/") || ["[", "]", "{", "}", "(", ")", "!"].some((character) => glob.includes(character)) || glob.split("/").some((segment) => segment === "..")) {
1450
- throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.TOOL_DENIED);
1458
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.TOOL_INPUT_INVALID);
1451
1459
  }
1452
1460
  let expression = "^";
1453
1461
  for (let index = 0; index < glob.length; index += 1) {
@@ -1686,7 +1694,7 @@ var runCheckSchema = import_zod.z.strictObject({
1686
1694
  checkId: import_zod.z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u)
1687
1695
  });
1688
1696
  function invalidTool() {
1689
- throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.TOOL_DENIED);
1697
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.TOOL_INPUT_INVALID);
1690
1698
  }
1691
1699
  function parseArguments(schema, value) {
1692
1700
  const parsed = schema.safeParse(value);
@@ -1983,67 +1991,339 @@ function createAgentToolExecutor(options) {
1983
1991
  }
1984
1992
 
1985
1993
  // src/worktree/git-worktree.ts
1986
- var import_promises5 = require("fs/promises");
1994
+ var import_promises6 = require("fs/promises");
1995
+ var import_node_crypto3 = require("crypto");
1987
1996
  var import_node_os = __toESM(require("os"), 1);
1997
+ var import_node_path6 = __toESM(require("path"), 1);
1998
+ var import_shared17 = require("@spotpatch/shared");
1999
+
2000
+ // src/worktree/workspace-health.ts
2001
+ var import_promises5 = require("fs/promises");
1988
2002
  var import_node_path5 = __toESM(require("path"), 1);
1989
2003
  var import_shared16 = require("@spotpatch/shared");
1990
- async function assertCleanGitBaseline(options) {
1991
- const root = await (0, import_promises5.realpath)(options.root).catch(() => {
1992
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.WORKTREE_DIRTY);
2004
+ var CONFLICTED_STATUSES = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
2005
+ var OPERATION_MARKERS = Object.freeze([
2006
+ "MERGE_HEAD",
2007
+ "CHERRY_PICK_HEAD",
2008
+ "REVERT_HEAD",
2009
+ "rebase-apply",
2010
+ "rebase-merge"
2011
+ ]);
2012
+ function emptyChanges() {
2013
+ return Object.freeze({
2014
+ staged: 0,
2015
+ unstaged: 0,
2016
+ untracked: 0,
2017
+ conflicted: 0,
2018
+ total: 0
2019
+ });
2020
+ }
2021
+ function blockedHealth(errorCode) {
2022
+ return Object.freeze({
2023
+ state: "blocked",
2024
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
2025
+ changes: emptyChanges(),
2026
+ canIncludeLocalChanges: false,
2027
+ errorCode
1993
2028
  });
1994
- const topLevel = (await runGitCommand({
2029
+ }
2030
+ function parsePorcelainStatus(value) {
2031
+ const records = value.split("\0");
2032
+ const untrackedPaths = [];
2033
+ let staged = 0;
2034
+ let unstaged = 0;
2035
+ let conflicted = 0;
2036
+ let total = 0;
2037
+ for (let index = 0; index < records.length; index += 1) {
2038
+ const record = records[index] ?? "";
2039
+ if (record.length === 0) {
2040
+ continue;
2041
+ }
2042
+ if (record.length < 4 || record[2] !== " ") {
2043
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
2044
+ }
2045
+ const status = record.slice(0, 2);
2046
+ const relativePath = record.slice(3);
2047
+ if (relativePath.length === 0) {
2048
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
2049
+ }
2050
+ total += 1;
2051
+ if (status === "??") {
2052
+ untrackedPaths.push(relativePath);
2053
+ continue;
2054
+ }
2055
+ const indexStatus = status[0] ?? " ";
2056
+ const worktreeStatus = status[1] ?? " ";
2057
+ if (CONFLICTED_STATUSES.has(status) || indexStatus === "U" || worktreeStatus === "U") {
2058
+ conflicted += 1;
2059
+ }
2060
+ if (indexStatus !== " ") {
2061
+ staged += 1;
2062
+ }
2063
+ if (worktreeStatus !== " ") {
2064
+ unstaged += 1;
2065
+ }
2066
+ if (indexStatus === "R" || indexStatus === "C") {
2067
+ index += 1;
2068
+ if ((records[index] ?? "").length === 0) {
2069
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
2070
+ }
2071
+ }
2072
+ }
2073
+ return Object.freeze({
2074
+ changes: Object.freeze({
2075
+ staged,
2076
+ unstaged,
2077
+ untracked: untrackedPaths.length,
2078
+ conflicted,
2079
+ total
2080
+ }),
2081
+ untrackedPaths: Object.freeze(untrackedPaths)
2082
+ });
2083
+ }
2084
+ async function operationInProgress(root, signal) {
2085
+ for (const marker of OPERATION_MARKERS) {
2086
+ const markerPath = (await runGitCommand({
2087
+ cwd: root,
2088
+ args: ["rev-parse", "--git-path", marker],
2089
+ errorCode: import_shared16.ERROR_CODES.WORKTREE_NOT_REPOSITORY,
2090
+ ...signal === void 0 ? {} : { signal }
2091
+ })).trim();
2092
+ if (await (0, import_promises5.lstat)(import_node_path5.default.resolve(root, markerPath)).catch(() => void 0) !== void 0) {
2093
+ return true;
2094
+ }
2095
+ }
2096
+ return false;
2097
+ }
2098
+ async function inspectUntrackedFiles(root, relativePaths) {
2099
+ if (relativePaths.length > import_shared16.AGENT_WORKSPACE_SNAPSHOT_LIMITS.maxUntrackedFiles) {
2100
+ return import_shared16.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE;
2101
+ }
2102
+ let totalBytes = 0;
2103
+ for (const relativePath of relativePaths) {
2104
+ const absolutePath = import_node_path5.default.resolve(root, relativePath);
2105
+ const relative = import_node_path5.default.relative(root, absolutePath);
2106
+ if (relative === ".." || relative.startsWith(`..${import_node_path5.default.sep}`) || import_node_path5.default.isAbsolute(relative)) {
2107
+ return import_shared16.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED;
2108
+ }
2109
+ const metadata = await (0, import_promises5.lstat)(absolutePath).catch(() => void 0);
2110
+ if (metadata === void 0 || !metadata.isFile() || metadata.isSymbolicLink()) {
2111
+ return import_shared16.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED;
2112
+ }
2113
+ totalBytes += metadata.size;
2114
+ if (totalBytes > import_shared16.AGENT_WORKSPACE_SNAPSHOT_LIMITS.maxUntrackedBytes) {
2115
+ return import_shared16.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE;
2116
+ }
2117
+ }
2118
+ return void 0;
2119
+ }
2120
+ async function inspectGitWorkspace(rootValue, signal) {
2121
+ const root = await (0, import_promises5.realpath)(rootValue).catch(() => {
2122
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.WORKTREE_NOT_REPOSITORY);
2123
+ });
2124
+ const topLevelResult = await runRawGitCommand({
1995
2125
  cwd: root,
1996
2126
  args: ["rev-parse", "--show-toplevel"],
1997
- errorCode: import_shared16.ERROR_CODES.WORKTREE_DIRTY,
1998
- ...options.signal === void 0 ? {} : { signal: options.signal }
1999
- })).trim();
2127
+ ...signal === void 0 ? {} : { signal }
2128
+ });
2129
+ if (topLevelResult.exitCode !== 0 || topLevelResult.cancelled || topLevelResult.timedOut) {
2130
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.WORKTREE_NOT_REPOSITORY);
2131
+ }
2132
+ const topLevel = topLevelResult.stdout.trim();
2000
2133
  if (!samePath(root, topLevel)) {
2001
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.WORKTREE_DIRTY);
2134
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.WORKTREE_NOT_REPOSITORY);
2002
2135
  }
2003
2136
  const head = (await runGitCommand({
2004
2137
  cwd: root,
2005
2138
  args: ["rev-parse", "--verify", "HEAD"],
2006
- errorCode: import_shared16.ERROR_CODES.WORKTREE_DIRTY,
2007
- ...options.signal === void 0 ? {} : { signal: options.signal }
2139
+ errorCode: import_shared16.ERROR_CODES.WORKTREE_NOT_REPOSITORY,
2140
+ ...signal === void 0 ? {} : { signal }
2008
2141
  })).trim();
2009
- if (options.expectedHead !== void 0 && head !== options.expectedHead) {
2010
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.APPLY_CONFLICT);
2142
+ const parsed = parsePorcelainStatus(
2143
+ await runGitCommand({
2144
+ cwd: root,
2145
+ args: ["status", "--porcelain=v1", "-z", "--untracked-files=all"],
2146
+ errorCode: import_shared16.ERROR_CODES.WORKTREE_NOT_REPOSITORY,
2147
+ ...signal === void 0 ? {} : { signal }
2148
+ })
2149
+ );
2150
+ const operationActive = await operationInProgress(root, signal);
2151
+ const untrackedError = await inspectUntrackedFiles(root, parsed.untrackedPaths);
2152
+ const errorCode = operationActive ? import_shared16.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS : parsed.changes.conflicted > 0 ? import_shared16.ERROR_CODES.WORKTREE_CONFLICTED : untrackedError;
2153
+ const health = Object.freeze({
2154
+ state: errorCode !== void 0 ? "blocked" : parsed.changes.total === 0 ? "ready" : "consent-required",
2155
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
2156
+ changes: parsed.changes,
2157
+ canIncludeLocalChanges: errorCode === void 0 && parsed.changes.total > 0,
2158
+ ...errorCode === void 0 ? {} : { errorCode }
2159
+ });
2160
+ return Object.freeze({
2161
+ root,
2162
+ head,
2163
+ health,
2164
+ untrackedPaths: parsed.untrackedPaths
2165
+ });
2166
+ }
2167
+ async function inspectAgentWorkspace(root, signal) {
2168
+ try {
2169
+ return (await inspectGitWorkspace(root, signal)).health;
2170
+ } catch (error) {
2171
+ if (error instanceof import_shared16.SpotPatchError) {
2172
+ return blockedHealth(error.code);
2173
+ }
2174
+ return blockedHealth(import_shared16.ERROR_CODES.INTERNAL_ERROR);
2011
2175
  }
2012
- const status = await runGitCommand({
2013
- cwd: root,
2014
- args: ["status", "--porcelain=v1", "-z", "--untracked-files=all"],
2015
- errorCode: import_shared16.ERROR_CODES.WORKTREE_DIRTY,
2016
- ...options.signal === void 0 ? {} : { signal: options.signal }
2176
+ }
2177
+
2178
+ // src/worktree/git-worktree.ts
2179
+ function workspacePath(root, relativePath) {
2180
+ const candidate = import_node_path6.default.resolve(root, relativePath);
2181
+ const relative = import_node_path6.default.relative(root, candidate);
2182
+ if (relative === ".." || relative.startsWith(`..${import_node_path6.default.sep}`) || import_node_path6.default.isAbsolute(relative)) {
2183
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
2184
+ }
2185
+ return candidate;
2186
+ }
2187
+ async function fileDigest(filePath) {
2188
+ return (0, import_node_crypto3.createHash)("sha256").update(await (0, import_promises6.readFile)(filePath)).digest("hex");
2189
+ }
2190
+ async function copyUntrackedFiles(sourceRoot, worktreeRoot, relativePaths) {
2191
+ for (const relativePath of relativePaths) {
2192
+ const sourcePath = workspacePath(sourceRoot, relativePath);
2193
+ const targetPath = workspacePath(worktreeRoot, relativePath);
2194
+ const metadata = await (0, import_promises6.lstat)(sourcePath).catch(() => void 0);
2195
+ if (metadata === void 0 || !metadata.isFile() || metadata.isSymbolicLink()) {
2196
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
2197
+ }
2198
+ await (0, import_promises6.mkdir)(import_node_path6.default.dirname(targetPath), { recursive: true });
2199
+ await (0, import_promises6.copyFile)(sourcePath, targetPath);
2200
+ const [sourceDigest, targetDigest] = await Promise.all([
2201
+ fileDigest(sourcePath),
2202
+ fileDigest(targetPath)
2203
+ ]).catch(() => {
2204
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2205
+ });
2206
+ if (sourceDigest !== targetDigest) {
2207
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2208
+ }
2209
+ }
2210
+ }
2211
+ async function assertUntrackedFilesUnchanged(sourceRoot, worktreeRoot, relativePaths) {
2212
+ for (const relativePath of relativePaths) {
2213
+ const sourcePath = workspacePath(sourceRoot, relativePath);
2214
+ const targetPath = workspacePath(worktreeRoot, relativePath);
2215
+ const [sourceDigest, targetDigest] = await Promise.all([
2216
+ fileDigest(sourcePath),
2217
+ fileDigest(targetPath)
2218
+ ]).catch(() => {
2219
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2220
+ });
2221
+ if (sourceDigest !== targetDigest) {
2222
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2223
+ }
2224
+ }
2225
+ }
2226
+ async function materializeLocalBaseline(sourceRoot, worktreeRoot, expectedHead, untrackedPaths, signal) {
2227
+ const sourceDiff = await runGitCommand({
2228
+ cwd: sourceRoot,
2229
+ args: [
2230
+ "diff",
2231
+ "--binary",
2232
+ "--full-index",
2233
+ "--no-ext-diff",
2234
+ "--no-color",
2235
+ "--no-renames",
2236
+ "HEAD",
2237
+ "--"
2238
+ ],
2239
+ signal,
2240
+ errorCode: import_shared17.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
2017
2241
  });
2018
- if (status.length > 0) {
2019
- throw new import_shared16.SpotPatchError(
2020
- options.expectedHead === void 0 ? import_shared16.ERROR_CODES.WORKTREE_DIRTY : import_shared16.ERROR_CODES.APPLY_CONFLICT
2021
- );
2242
+ if (sourceDiff.length > 0) {
2243
+ await runGitCommand({
2244
+ cwd: worktreeRoot,
2245
+ args: ["apply", "--binary", "--whitespace=nowarn", "-"],
2246
+ stdin: sourceDiff,
2247
+ signal,
2248
+ errorCode: import_shared17.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
2249
+ });
2022
2250
  }
2023
- return Object.freeze({ root, head });
2251
+ await copyUntrackedFiles(sourceRoot, worktreeRoot, untrackedPaths);
2252
+ const confirmation = await inspectGitWorkspace(sourceRoot, signal);
2253
+ const confirmationDiff = await runGitCommand({
2254
+ cwd: sourceRoot,
2255
+ args: [
2256
+ "diff",
2257
+ "--binary",
2258
+ "--full-index",
2259
+ "--no-ext-diff",
2260
+ "--no-color",
2261
+ "--no-renames",
2262
+ "HEAD",
2263
+ "--"
2264
+ ],
2265
+ signal,
2266
+ errorCode: import_shared17.ERROR_CODES.APPLY_CONFLICT
2267
+ });
2268
+ if (confirmation.head !== expectedHead || confirmationDiff !== sourceDiff || confirmation.untrackedPaths.length !== untrackedPaths.length || confirmation.untrackedPaths.some((value, index) => value !== untrackedPaths[index])) {
2269
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2270
+ }
2271
+ await assertUntrackedFilesUnchanged(sourceRoot, worktreeRoot, untrackedPaths);
2272
+ await runGitCommand({
2273
+ cwd: worktreeRoot,
2274
+ args: ["add", "--all"],
2275
+ signal,
2276
+ errorCode: import_shared17.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
2277
+ });
2278
+ await runGitCommand({
2279
+ cwd: worktreeRoot,
2280
+ args: [
2281
+ "-c",
2282
+ "user.name=SpotPatch Agent",
2283
+ "-c",
2284
+ "user.email=spotpatch-agent@example.invalid",
2285
+ "commit",
2286
+ "--quiet",
2287
+ "-m",
2288
+ "SpotPatch local workspace baseline"
2289
+ ],
2290
+ signal,
2291
+ errorCode: import_shared17.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
2292
+ });
2024
2293
  }
2025
2294
  async function defaultTemporaryBase(root) {
2026
- const dependencyDirectory = import_node_path5.default.join(root, "node_modules");
2295
+ const dependencyDirectory = import_node_path6.default.join(root, "node_modules");
2027
2296
  try {
2028
- const stats = await (0, import_promises5.lstat)(dependencyDirectory);
2297
+ const stats = await (0, import_promises6.lstat)(dependencyDirectory);
2029
2298
  if (!stats.isDirectory() || stats.isSymbolicLink()) {
2030
2299
  return import_node_os.default.tmpdir();
2031
2300
  }
2032
- return await (0, import_promises5.realpath)(dependencyDirectory);
2301
+ return await (0, import_promises6.realpath)(dependencyDirectory);
2033
2302
  } catch {
2034
2303
  return import_node_os.default.tmpdir();
2035
2304
  }
2036
2305
  }
2037
2306
  async function createIsolatedGitWorktree(options) {
2038
- const baseline = await assertCleanGitBaseline({
2039
- root: options.root,
2040
- signal: options.signal
2307
+ const workingTreeMode = options.workingTreeMode ?? "require-clean";
2308
+ const inspection = await inspectGitWorkspace(options.root, options.signal);
2309
+ if (inspection.health.state === "blocked") {
2310
+ throw new import_shared17.SpotPatchError(
2311
+ inspection.health.errorCode ?? import_shared17.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
2312
+ );
2313
+ }
2314
+ if (inspection.health.state === "consent-required" && workingTreeMode === "require-clean") {
2315
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.WORKTREE_DIRTY);
2316
+ }
2317
+ const baseline = Object.freeze({
2318
+ root: inspection.root,
2319
+ head: inspection.head,
2320
+ workingTreeMode
2041
2321
  });
2042
2322
  const temporaryBase = options.temporaryBase ?? await defaultTemporaryBase(baseline.root);
2043
- const temporaryDirectory = await (0, import_promises5.mkdtemp)(
2044
- import_node_path5.default.join(temporaryBase, "spotpatch-agent-")
2323
+ const temporaryDirectory = await (0, import_promises6.mkdtemp)(
2324
+ import_node_path6.default.join(temporaryBase, "spotpatch-agent-")
2045
2325
  );
2046
- const worktreePath = import_node_path5.default.join(temporaryDirectory, "worktree");
2326
+ const worktreePath = import_node_path6.default.join(temporaryDirectory, "worktree");
2047
2327
  let registered = false;
2048
2328
  let cleaned = false;
2049
2329
  const cleanup = async () => {
@@ -2058,8 +2338,8 @@ async function createIsolatedGitWorktree(options) {
2058
2338
  timeoutMs: 3e4
2059
2339
  }).catch(() => void 0);
2060
2340
  }
2061
- if (import_node_path5.default.basename(temporaryDirectory).startsWith("spotpatch-agent-")) {
2062
- await (0, import_promises5.rm)(temporaryDirectory, { recursive: true, force: true }).catch(
2341
+ if (import_node_path6.default.basename(temporaryDirectory).startsWith("spotpatch-agent-")) {
2342
+ await (0, import_promises6.rm)(temporaryDirectory, { recursive: true, force: true }).catch(
2063
2343
  () => void 0
2064
2344
  );
2065
2345
  }
@@ -2068,12 +2348,12 @@ async function createIsolatedGitWorktree(options) {
2068
2348
  await runGitCommand({
2069
2349
  cwd: baseline.root,
2070
2350
  args: ["worktree", "add", "--detach", worktreePath, baseline.head],
2071
- errorCode: import_shared16.ERROR_CODES.INTERNAL_ERROR,
2351
+ errorCode: import_shared17.ERROR_CODES.INTERNAL_ERROR,
2072
2352
  signal: options.signal,
2073
2353
  timeoutMs: 3e4
2074
2354
  });
2075
2355
  registered = true;
2076
- const worktreeRoot = await (0, import_promises5.realpath)(worktreePath);
2356
+ const worktreeRoot = await (0, import_promises6.realpath)(worktreePath);
2077
2357
  const actualHead = (await runGitCommand({
2078
2358
  cwd: worktreeRoot,
2079
2359
  args: ["rev-parse", "--verify", "HEAD"],
@@ -2085,7 +2365,16 @@ async function createIsolatedGitWorktree(options) {
2085
2365
  signal: options.signal
2086
2366
  })).trim();
2087
2367
  if (actualHead !== baseline.head || !samePath(actualRoot, worktreeRoot)) {
2088
- throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INTERNAL_ERROR);
2368
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INTERNAL_ERROR);
2369
+ }
2370
+ if (inspection.health.state === "consent-required") {
2371
+ await materializeLocalBaseline(
2372
+ baseline.root,
2373
+ worktreeRoot,
2374
+ baseline.head,
2375
+ inspection.untrackedPaths,
2376
+ options.signal
2377
+ );
2089
2378
  }
2090
2379
  return Object.freeze({ baseline, root: worktreeRoot, cleanup });
2091
2380
  } catch (error) {
@@ -2095,17 +2384,19 @@ async function createIsolatedGitWorktree(options) {
2095
2384
  }
2096
2385
 
2097
2386
  // src/worktree/prepared-change.ts
2098
- var import_node_crypto3 = require("crypto");
2099
- var import_promises6 = require("fs/promises");
2100
- var import_shared17 = require("@spotpatch/shared");
2387
+ var import_node_crypto4 = require("crypto");
2388
+ var import_promises7 = require("fs/promises");
2389
+ var import_shared18 = require("@spotpatch/shared");
2101
2390
  var privateChanges = /* @__PURE__ */ new WeakMap();
2102
2391
  var DELETED_HASH = "<deleted>";
2103
2392
  function createPreparedAgentChange(options) {
2104
2393
  const touchedPaths = Object.freeze(
2105
2394
  options.result.files.map((file) => file.relativePath)
2106
2395
  );
2107
- if (options.expectedHashes.size !== touchedPaths.length || touchedPaths.some((relativePath) => !options.expectedHashes.has(relativePath))) {
2108
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INTERNAL_ERROR);
2396
+ if (options.baselineHashes.size !== touchedPaths.length || options.expectedHashes.size !== touchedPaths.length || touchedPaths.some(
2397
+ (relativePath) => !options.baselineHashes.has(relativePath) || !options.expectedHashes.has(relativePath)
2398
+ )) {
2399
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INTERNAL_ERROR);
2109
2400
  }
2110
2401
  const change = Object.freeze({
2111
2402
  kind: "prepared-agent-change",
@@ -2115,6 +2406,7 @@ function createPreparedAgentChange(options) {
2115
2406
  });
2116
2407
  privateChanges.set(change, {
2117
2408
  baselineHead: options.baselineHead,
2409
+ baselineHashes: new Map(options.baselineHashes),
2118
2410
  diff: options.result.diff,
2119
2411
  expectedHashes: new Map(options.expectedHashes),
2120
2412
  root: options.root,
@@ -2126,28 +2418,30 @@ function createPreparedAgentChange(options) {
2126
2418
  function requirePrivateChange(change) {
2127
2419
  const privateChange = privateChanges.get(change);
2128
2420
  if (privateChange === void 0) {
2129
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INTERNAL_ERROR);
2421
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INTERNAL_ERROR);
2130
2422
  }
2131
2423
  return privateChange;
2132
2424
  }
2133
- async function currentHead(root) {
2134
- return (await runGitCommand({
2135
- cwd: root,
2136
- args: ["rev-parse", "--verify", "HEAD"],
2137
- errorCode: import_shared17.ERROR_CODES.APPLY_CONFLICT
2138
- })).trim();
2425
+ async function assertWorkspaceOperationSafe(root, expectedHead) {
2426
+ const inspection = await inspectGitWorkspace(root).catch(() => {
2427
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
2428
+ });
2429
+ const blockingOperation = inspection.health.errorCode === import_shared18.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS || inspection.health.errorCode === import_shared18.ERROR_CODES.WORKTREE_CONFLICTED;
2430
+ if (inspection.head !== expectedHead || blockingOperation) {
2431
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
2432
+ }
2139
2433
  }
2140
2434
  async function fileHash(root, relativePath) {
2141
2435
  const normalized = assertAgentPathAllowed(relativePath);
2142
2436
  const absolutePath = await resolveWritableAgentPath(root, normalized);
2143
- const metadata = await (0, import_promises6.lstat)(absolutePath).catch(() => void 0);
2437
+ const metadata = await (0, import_promises7.lstat)(absolutePath).catch(() => void 0);
2144
2438
  if (metadata === void 0) {
2145
2439
  return DELETED_HASH;
2146
2440
  }
2147
2441
  if (!metadata.isFile() || metadata.isSymbolicLink()) {
2148
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2442
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
2149
2443
  }
2150
- return (0, import_node_crypto3.createHash)("sha256").update(await (0, import_promises6.readFile)(absolutePath)).digest("hex");
2444
+ return (0, import_node_crypto4.createHash)("sha256").update(await (0, import_promises7.readFile)(absolutePath)).digest("hex");
2151
2445
  }
2152
2446
  async function captureAgentFileHashes(root, paths) {
2153
2447
  const entries = await Promise.all(
@@ -2163,34 +2457,38 @@ function hashesMatch(expected, actual) {
2163
2457
  async function applyPreparedAgentChange(change) {
2164
2458
  const privateChange = requirePrivateChange(change);
2165
2459
  if (privateChange.state !== "prepared" || !change.validationPassed || privateChange.diff.length === 0) {
2166
- throw new import_shared17.SpotPatchError(
2167
- change.validationPassed ? import_shared17.ERROR_CODES.APPLY_CONFLICT : import_shared17.ERROR_CODES.VALIDATION_FAILED
2460
+ throw new import_shared18.SpotPatchError(
2461
+ change.validationPassed ? import_shared18.ERROR_CODES.APPLY_CONFLICT : import_shared18.ERROR_CODES.VALIDATION_FAILED
2168
2462
  );
2169
2463
  }
2170
2464
  privateChange.state = "applying";
2171
2465
  try {
2172
- await assertCleanGitBaseline({
2173
- root: privateChange.root,
2174
- expectedHead: privateChange.baselineHead
2175
- });
2466
+ await assertWorkspaceOperationSafe(privateChange.root, privateChange.baselineHead);
2467
+ const currentHashes = await captureAgentFileHashes(
2468
+ privateChange.root,
2469
+ privateChange.touchedPaths
2470
+ );
2471
+ if (!hashesMatch(privateChange.baselineHashes, currentHashes)) {
2472
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
2473
+ }
2176
2474
  await runGitCommand({
2177
2475
  cwd: privateChange.root,
2178
2476
  args: ["apply", "--check", "--whitespace=error-all", "-"],
2179
2477
  stdin: privateChange.diff,
2180
- errorCode: import_shared17.ERROR_CODES.APPLY_CONFLICT
2478
+ errorCode: import_shared18.ERROR_CODES.APPLY_CONFLICT
2181
2479
  });
2182
2480
  await runGitCommand({
2183
2481
  cwd: privateChange.root,
2184
2482
  args: ["apply", "--whitespace=error-all", "-"],
2185
2483
  stdin: privateChange.diff,
2186
- errorCode: import_shared17.ERROR_CODES.APPLY_CONFLICT
2484
+ errorCode: import_shared18.ERROR_CODES.APPLY_CONFLICT
2187
2485
  });
2188
2486
  const appliedHashes = await captureAgentFileHashes(
2189
2487
  privateChange.root,
2190
2488
  privateChange.touchedPaths
2191
2489
  );
2192
2490
  if (!hashesMatch(privateChange.expectedHashes, appliedHashes)) {
2193
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2491
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
2194
2492
  }
2195
2493
  privateChange.appliedHashes = appliedHashes;
2196
2494
  privateChange.state = "applied";
@@ -2202,34 +2500,39 @@ async function applyPreparedAgentChange(change) {
2202
2500
  async function revertPreparedAgentChange(change) {
2203
2501
  const privateChange = requirePrivateChange(change);
2204
2502
  if (privateChange.state !== "applied" || privateChange.appliedHashes === void 0) {
2205
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2503
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
2206
2504
  }
2207
2505
  privateChange.state = "reverting";
2208
2506
  try {
2209
- if (await currentHead(privateChange.root) !== privateChange.baselineHead) {
2210
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2211
- }
2507
+ await assertWorkspaceOperationSafe(privateChange.root, privateChange.baselineHead);
2212
2508
  const currentHashes = await captureAgentFileHashes(
2213
2509
  privateChange.root,
2214
2510
  privateChange.touchedPaths
2215
2511
  );
2216
2512
  for (const [relativePath, expectedHash] of privateChange.appliedHashes) {
2217
2513
  if (currentHashes.get(relativePath) !== expectedHash) {
2218
- throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.APPLY_CONFLICT);
2514
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
2219
2515
  }
2220
2516
  }
2221
2517
  await runGitCommand({
2222
2518
  cwd: privateChange.root,
2223
2519
  args: ["apply", "--reverse", "--check", "--whitespace=error-all", "-"],
2224
2520
  stdin: privateChange.diff,
2225
- errorCode: import_shared17.ERROR_CODES.APPLY_CONFLICT
2521
+ errorCode: import_shared18.ERROR_CODES.APPLY_CONFLICT
2226
2522
  });
2227
2523
  await runGitCommand({
2228
2524
  cwd: privateChange.root,
2229
2525
  args: ["apply", "--reverse", "--whitespace=error-all", "-"],
2230
2526
  stdin: privateChange.diff,
2231
- errorCode: import_shared17.ERROR_CODES.APPLY_CONFLICT
2527
+ errorCode: import_shared18.ERROR_CODES.APPLY_CONFLICT
2232
2528
  });
2529
+ const revertedHashes = await captureAgentFileHashes(
2530
+ privateChange.root,
2531
+ privateChange.touchedPaths
2532
+ );
2533
+ if (!hashesMatch(privateChange.baselineHashes, revertedHashes)) {
2534
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
2535
+ }
2233
2536
  privateChange.state = "reverted";
2234
2537
  } catch (error) {
2235
2538
  privateChange.state = "applied";
@@ -2238,7 +2541,7 @@ async function revertPreparedAgentChange(change) {
2238
2541
  }
2239
2542
 
2240
2543
  // src/engine/agent-prompt.ts
2241
- var import_shared18 = require("@spotpatch/shared");
2544
+ var import_shared19 = require("@spotpatch/shared");
2242
2545
  var AGENT_SYSTEM_INSTRUCTIONS = `You are editing code only inside a disposable, isolated Git worktree.
2243
2546
 
2244
2547
  Follow these rules exactly:
@@ -2255,7 +2558,7 @@ Follow these rules exactly:
2255
2558
  function redactedJson(value) {
2256
2559
  return JSON.stringify(
2257
2560
  value,
2258
- (_key, item) => typeof item === "string" ? (0, import_shared18.redactSensitiveText)(item) : item,
2561
+ (_key, item) => typeof item === "string" ? (0, import_shared19.redactSensitiveText)(item) : item,
2259
2562
  2
2260
2563
  );
2261
2564
  }
@@ -2332,7 +2635,7 @@ function createBoundedTarget(target, maximumCharacters) {
2332
2635
  function composeBoundedContext(annotation, maximumCharacters) {
2333
2636
  const page = Object.freeze({
2334
2637
  ...annotation.page,
2335
- url: (0, import_shared18.sanitizeUrl)(annotation.page.url, "http://spotpatch.invalid")
2638
+ url: (0, import_shared19.sanitizeUrl)(annotation.page.url, "http://spotpatch.invalid")
2336
2639
  });
2337
2640
  const fixedCharacters = redactedJson({
2338
2641
  page,
@@ -2388,7 +2691,7 @@ function composeAgentUserPrompt(annotation, maximumCharacters) {
2388
2691
  const minimumContextCharacters = 1024;
2389
2692
  const request = annotation.targets.map(
2390
2693
  (target, index) => `Target ${String(index + 1)}:
2391
- ${(0, import_shared18.redactSensitiveText)(target.instruction.trim())}`
2694
+ ${(0, import_shared19.redactSensitiveText)(target.instruction.trim())}`
2392
2695
  ).join("\n\n");
2393
2696
  const prefix = `${requestPrefix}${request}${contextPrefix}`;
2394
2697
  if (prefix.length + suffix.length + minimumContextCharacters > maximumCharacters) {
@@ -2408,11 +2711,11 @@ function isRetryableToolFailure(result) {
2408
2711
  return false;
2409
2712
  }
2410
2713
  const candidate = output;
2411
- return candidate.errorCode === import_shared19.ERROR_CODES.PATCH_REJECTED && candidate.retryable === true;
2714
+ return candidate.errorCode === import_shared20.ERROR_CODES.PATCH_REJECTED && candidate.retryable === true;
2412
2715
  }
2413
2716
  function throwIfCancelled(signal) {
2414
2717
  if (signal.aborted) {
2415
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.AGENT_CANCELLED);
2718
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.AGENT_CANCELLED);
2416
2719
  }
2417
2720
  }
2418
2721
  function linkSignal(source, target) {
@@ -2450,6 +2753,7 @@ async function executeAgentChange(options) {
2450
2753
  worktree = await createIsolatedGitWorktree({
2451
2754
  root: options.root,
2452
2755
  signal: controller.signal,
2756
+ workingTreeMode: options.workingTreeMode ?? "require-clean",
2453
2757
  ...options.temporaryBase === void 0 ? {} : { temporaryBase: options.temporaryBase }
2454
2758
  });
2455
2759
  options.callbacks?.onPhase?.(
@@ -2491,7 +2795,7 @@ async function executeAgentChange(options) {
2491
2795
  }
2492
2796
  toolCallCount += response.toolCalls.length;
2493
2797
  if (toolCallCount > options.execution.limits.maxToolCalls) {
2494
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.AGENT_LIMIT_EXCEEDED);
2798
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED);
2495
2799
  }
2496
2800
  const results = [];
2497
2801
  for (const call of response.toolCalls) {
@@ -2526,7 +2830,7 @@ async function executeAgentChange(options) {
2526
2830
  pendingResults = Object.freeze(results);
2527
2831
  }
2528
2832
  if (summary === void 0) {
2529
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.AGENT_LIMIT_EXCEEDED);
2833
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED);
2530
2834
  }
2531
2835
  options.callbacks?.onPhase?.(
2532
2836
  Object.freeze({
@@ -2559,7 +2863,7 @@ async function executeAgentChange(options) {
2559
2863
  controller.signal
2560
2864
  );
2561
2865
  if (afterCheck.diff !== initialChangeSet.diff) {
2562
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.VALIDATION_FAILED);
2866
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.VALIDATION_FAILED);
2563
2867
  }
2564
2868
  }
2565
2869
  const validationPassed = finalChecks.every((check) => check.status === "passed");
@@ -2575,9 +2879,14 @@ async function executeAgentChange(options) {
2575
2879
  worktree.root,
2576
2880
  initialChangeSet.touchedPaths
2577
2881
  );
2882
+ const baselineHashes = await captureAgentFileHashes(
2883
+ worktree.baseline.root,
2884
+ initialChangeSet.touchedPaths
2885
+ );
2578
2886
  return createPreparedAgentChange({
2579
2887
  autoApplyEligible,
2580
2888
  baselineHead: worktree.baseline.head,
2889
+ baselineHashes,
2581
2890
  expectedHashes,
2582
2891
  result,
2583
2892
  root: worktree.baseline.root,
@@ -2585,15 +2894,15 @@ async function executeAgentChange(options) {
2585
2894
  });
2586
2895
  } catch (error) {
2587
2896
  if (options.signal.aborted) {
2588
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.AGENT_CANCELLED);
2897
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.AGENT_CANCELLED);
2589
2898
  }
2590
2899
  if (hasJobTimedOut()) {
2591
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.AGENT_LIMIT_EXCEEDED);
2900
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED);
2592
2901
  }
2593
- if (error instanceof import_shared19.SpotPatchError) {
2902
+ if (error instanceof import_shared20.SpotPatchError) {
2594
2903
  throw error;
2595
2904
  }
2596
- throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INTERNAL_ERROR);
2905
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INTERNAL_ERROR);
2597
2906
  } finally {
2598
2907
  clearTimeout(timeout);
2599
2908
  unlink();
@@ -2602,13 +2911,13 @@ async function executeAgentChange(options) {
2602
2911
  }
2603
2912
 
2604
2913
  // src/provider/capability-probe.ts
2605
- var import_shared20 = require("@spotpatch/shared");
2914
+ var import_shared21 = require("@spotpatch/shared");
2606
2915
  var PROBE_TOOL_NAME = "spotpatch_capability_probe";
2607
2916
  var PROBE_TOKEN = "spotpatch-ready-v1";
2608
2917
  async function probeProviderCapability(options) {
2609
2918
  const model = options.provider.models[options.modelProfileId];
2610
2919
  if (model === void 0) {
2611
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED);
2920
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.MODEL_NOT_ALLOWED);
2612
2921
  }
2613
2922
  const credential = options.credential ?? resolveProviderCredential(options.provider.apiKeyEnv, options.environment);
2614
2923
  const session = createOpenAICompatibleProviderSession({
@@ -2637,7 +2946,7 @@ async function probeProviderCapability(options) {
2637
2946
  const first = await session.next(void 0, options.signal);
2638
2947
  const probeCall = first.toolCalls[0];
2639
2948
  if (first.toolCalls.length !== 1 || probeCall?.name !== PROBE_TOOL_NAME || probeCall.arguments.token !== PROBE_TOKEN) {
2640
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED);
2949
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED);
2641
2950
  }
2642
2951
  const second = await session.next(
2643
2952
  Object.freeze([
@@ -2649,7 +2958,7 @@ async function probeProviderCapability(options) {
2649
2958
  options.signal
2650
2959
  );
2651
2960
  if (second.toolCalls.length !== 0 || second.finalText.trim().length === 0) {
2652
- throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED);
2961
+ throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED);
2653
2962
  }
2654
2963
  return Object.freeze({
2655
2964
  providerProfileId: options.provider.id,
@@ -2672,6 +2981,7 @@ async function probeProviderCapability(options) {
2672
2981
  createOpenAICompatibleProviderSession,
2673
2982
  createProviderCredential,
2674
2983
  executeAgentChange,
2984
+ inspectAgentWorkspace,
2675
2985
  probeProviderCapability,
2676
2986
  resolveProviderCredential,
2677
2987
  revertPreparedAgentChange