@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 +407 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +418 -94
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/engine/execute-agent-change.ts
|
|
2
2
|
import {
|
|
3
|
-
ERROR_CODES as
|
|
4
|
-
SpotPatchError as
|
|
3
|
+
ERROR_CODES as ERROR_CODES19,
|
|
4
|
+
SpotPatchError as SpotPatchError19
|
|
5
5
|
} from "@spotpatch/shared";
|
|
6
6
|
|
|
7
7
|
// src/provider/openai-compatible-provider.ts
|
|
@@ -716,7 +716,7 @@ var PROTECTED_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
|
716
716
|
"yarn.lock"
|
|
717
717
|
]);
|
|
718
718
|
function deny() {
|
|
719
|
-
throw new SpotPatchError8(ERROR_CODES8.
|
|
719
|
+
throw new SpotPatchError8(ERROR_CODES8.TOOL_PATH_DENIED);
|
|
720
720
|
}
|
|
721
721
|
function hasControlCharacter(value) {
|
|
722
722
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -821,13 +821,13 @@ async function readAgentTextFile(root, relativePath, maximumBytes) {
|
|
|
821
821
|
}
|
|
822
822
|
const bytes = await readFile(absolutePath);
|
|
823
823
|
if (bytes.includes(0)) {
|
|
824
|
-
throw new SpotPatchError9(ERROR_CODES9.
|
|
824
|
+
throw new SpotPatchError9(ERROR_CODES9.TOOL_PATH_DENIED);
|
|
825
825
|
}
|
|
826
826
|
let content;
|
|
827
827
|
try {
|
|
828
828
|
content = utf8Decoder.decode(bytes);
|
|
829
829
|
} catch {
|
|
830
|
-
throw new SpotPatchError9(ERROR_CODES9.
|
|
830
|
+
throw new SpotPatchError9(ERROR_CODES9.TOOL_PATH_DENIED);
|
|
831
831
|
}
|
|
832
832
|
return Object.freeze({ content, relativePath, size: metadata.size });
|
|
833
833
|
}
|
|
@@ -840,7 +840,7 @@ function encodeUtf8Text(content, includeByteOrderMark) {
|
|
|
840
840
|
}
|
|
841
841
|
async function writeAgentTextFileIfContentMatches(root, relativePath, expectedContent, nextContent, maximumBytes) {
|
|
842
842
|
if (nextContent.includes("\0")) {
|
|
843
|
-
throw new SpotPatchError9(ERROR_CODES9.
|
|
843
|
+
throw new SpotPatchError9(ERROR_CODES9.TOOL_INPUT_INVALID);
|
|
844
844
|
}
|
|
845
845
|
const absolutePath = await resolveExistingAgentPath(root, relativePath);
|
|
846
846
|
const [metadata, currentBytes] = await Promise.all([
|
|
@@ -854,7 +854,7 @@ async function writeAgentTextFileIfContentMatches(root, relativePath, expectedCo
|
|
|
854
854
|
try {
|
|
855
855
|
currentContent = utf8Decoder.decode(currentBytes);
|
|
856
856
|
} catch {
|
|
857
|
-
throw new SpotPatchError9(ERROR_CODES9.
|
|
857
|
+
throw new SpotPatchError9(ERROR_CODES9.TOOL_PATH_DENIED);
|
|
858
858
|
}
|
|
859
859
|
if (currentBytes.includes(0) || currentContent !== expectedContent) {
|
|
860
860
|
throw new SpotPatchError9(ERROR_CODES9.PATCH_REJECTED);
|
|
@@ -1113,7 +1113,7 @@ async function runConfiguredCheck(options) {
|
|
|
1113
1113
|
function requireConfiguredCheck(checkId, checks) {
|
|
1114
1114
|
const check = checks[checkId];
|
|
1115
1115
|
if (check === void 0) {
|
|
1116
|
-
throw new SpotPatchError10(ERROR_CODES10.
|
|
1116
|
+
throw new SpotPatchError10(ERROR_CODES10.TOOL_INPUT_INVALID);
|
|
1117
1117
|
}
|
|
1118
1118
|
return check;
|
|
1119
1119
|
}
|
|
@@ -1308,9 +1308,16 @@ async function applyAgentPatch(worktreeRoot, patch, limits, signal) {
|
|
|
1308
1308
|
throw new SpotPatchError13(ERROR_CODES13.AGENT_LIMIT_EXCEEDED);
|
|
1309
1309
|
}
|
|
1310
1310
|
await Promise.all(
|
|
1311
|
-
files.map(
|
|
1312
|
-
|
|
1313
|
-
|
|
1311
|
+
files.map(async (file) => {
|
|
1312
|
+
await resolveWritableAgentPath(worktreeRoot, file.relativePath);
|
|
1313
|
+
if (file.kind !== "added") {
|
|
1314
|
+
await readAgentTextFile(
|
|
1315
|
+
worktreeRoot,
|
|
1316
|
+
file.relativePath,
|
|
1317
|
+
limits.maxReadBytesPerFile
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
})
|
|
1314
1321
|
);
|
|
1315
1322
|
await runGitCommand({
|
|
1316
1323
|
cwd: worktreeRoot,
|
|
@@ -1424,7 +1431,7 @@ var MAX_DISCOVERED_FILES = 2e4;
|
|
|
1424
1431
|
var TEXT_SAMPLE_BYTES = 8192;
|
|
1425
1432
|
function compileGlob(glob) {
|
|
1426
1433
|
if (glob.length === 0 || glob.length > 256 || glob.includes("\0") || glob.includes("\\") || glob.startsWith("/") || ["[", "]", "{", "}", "(", ")", "!"].some((character) => glob.includes(character)) || glob.split("/").some((segment) => segment === "..")) {
|
|
1427
|
-
throw new SpotPatchError14(ERROR_CODES14.
|
|
1434
|
+
throw new SpotPatchError14(ERROR_CODES14.TOOL_INPUT_INVALID);
|
|
1428
1435
|
}
|
|
1429
1436
|
let expression = "^";
|
|
1430
1437
|
for (let index = 0; index < glob.length; index += 1) {
|
|
@@ -1663,7 +1670,7 @@ var runCheckSchema = z.strictObject({
|
|
|
1663
1670
|
checkId: z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u)
|
|
1664
1671
|
});
|
|
1665
1672
|
function invalidTool() {
|
|
1666
|
-
throw new SpotPatchError15(ERROR_CODES15.
|
|
1673
|
+
throw new SpotPatchError15(ERROR_CODES15.TOOL_INPUT_INVALID);
|
|
1667
1674
|
}
|
|
1668
1675
|
function parseArguments(schema, value) {
|
|
1669
1676
|
const parsed = schema.safeParse(value);
|
|
@@ -1960,67 +1967,354 @@ function createAgentToolExecutor(options) {
|
|
|
1960
1967
|
}
|
|
1961
1968
|
|
|
1962
1969
|
// src/worktree/git-worktree.ts
|
|
1963
|
-
import {
|
|
1970
|
+
import {
|
|
1971
|
+
copyFile,
|
|
1972
|
+
lstat as lstat4,
|
|
1973
|
+
mkdir,
|
|
1974
|
+
mkdtemp,
|
|
1975
|
+
readFile as readFile2,
|
|
1976
|
+
realpath as realpath3,
|
|
1977
|
+
rm as rm2
|
|
1978
|
+
} from "fs/promises";
|
|
1979
|
+
import { createHash as createHash2 } from "crypto";
|
|
1964
1980
|
import os from "os";
|
|
1981
|
+
import path6 from "path";
|
|
1982
|
+
import {
|
|
1983
|
+
ERROR_CODES as ERROR_CODES17,
|
|
1984
|
+
SpotPatchError as SpotPatchError17
|
|
1985
|
+
} from "@spotpatch/shared";
|
|
1986
|
+
|
|
1987
|
+
// src/worktree/workspace-health.ts
|
|
1988
|
+
import { lstat as lstat3, realpath as realpath2 } from "fs/promises";
|
|
1965
1989
|
import path5 from "path";
|
|
1966
|
-
import {
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1990
|
+
import {
|
|
1991
|
+
AGENT_WORKSPACE_SNAPSHOT_LIMITS,
|
|
1992
|
+
ERROR_CODES as ERROR_CODES16,
|
|
1993
|
+
SpotPatchError as SpotPatchError16
|
|
1994
|
+
} from "@spotpatch/shared";
|
|
1995
|
+
var CONFLICTED_STATUSES = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
1996
|
+
var OPERATION_MARKERS = Object.freeze([
|
|
1997
|
+
"MERGE_HEAD",
|
|
1998
|
+
"CHERRY_PICK_HEAD",
|
|
1999
|
+
"REVERT_HEAD",
|
|
2000
|
+
"rebase-apply",
|
|
2001
|
+
"rebase-merge"
|
|
2002
|
+
]);
|
|
2003
|
+
function emptyChanges() {
|
|
2004
|
+
return Object.freeze({
|
|
2005
|
+
staged: 0,
|
|
2006
|
+
unstaged: 0,
|
|
2007
|
+
untracked: 0,
|
|
2008
|
+
conflicted: 0,
|
|
2009
|
+
total: 0
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
function blockedHealth(errorCode) {
|
|
2013
|
+
return Object.freeze({
|
|
2014
|
+
state: "blocked",
|
|
2015
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2016
|
+
changes: emptyChanges(),
|
|
2017
|
+
canIncludeLocalChanges: false,
|
|
2018
|
+
errorCode
|
|
1970
2019
|
});
|
|
1971
|
-
|
|
2020
|
+
}
|
|
2021
|
+
function parsePorcelainStatus(value) {
|
|
2022
|
+
const records = value.split("\0");
|
|
2023
|
+
const untrackedPaths = [];
|
|
2024
|
+
let staged = 0;
|
|
2025
|
+
let unstaged = 0;
|
|
2026
|
+
let conflicted = 0;
|
|
2027
|
+
let total = 0;
|
|
2028
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
2029
|
+
const record = records[index] ?? "";
|
|
2030
|
+
if (record.length === 0) {
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
if (record.length < 4 || record[2] !== " ") {
|
|
2034
|
+
throw new SpotPatchError16(ERROR_CODES16.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
|
|
2035
|
+
}
|
|
2036
|
+
const status = record.slice(0, 2);
|
|
2037
|
+
const relativePath = record.slice(3);
|
|
2038
|
+
if (relativePath.length === 0) {
|
|
2039
|
+
throw new SpotPatchError16(ERROR_CODES16.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
|
|
2040
|
+
}
|
|
2041
|
+
total += 1;
|
|
2042
|
+
if (status === "??") {
|
|
2043
|
+
untrackedPaths.push(relativePath);
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
const indexStatus = status[0] ?? " ";
|
|
2047
|
+
const worktreeStatus = status[1] ?? " ";
|
|
2048
|
+
if (CONFLICTED_STATUSES.has(status) || indexStatus === "U" || worktreeStatus === "U") {
|
|
2049
|
+
conflicted += 1;
|
|
2050
|
+
}
|
|
2051
|
+
if (indexStatus !== " ") {
|
|
2052
|
+
staged += 1;
|
|
2053
|
+
}
|
|
2054
|
+
if (worktreeStatus !== " ") {
|
|
2055
|
+
unstaged += 1;
|
|
2056
|
+
}
|
|
2057
|
+
if (indexStatus === "R" || indexStatus === "C") {
|
|
2058
|
+
index += 1;
|
|
2059
|
+
if ((records[index] ?? "").length === 0) {
|
|
2060
|
+
throw new SpotPatchError16(ERROR_CODES16.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return Object.freeze({
|
|
2065
|
+
changes: Object.freeze({
|
|
2066
|
+
staged,
|
|
2067
|
+
unstaged,
|
|
2068
|
+
untracked: untrackedPaths.length,
|
|
2069
|
+
conflicted,
|
|
2070
|
+
total
|
|
2071
|
+
}),
|
|
2072
|
+
untrackedPaths: Object.freeze(untrackedPaths)
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
async function operationInProgress(root, signal) {
|
|
2076
|
+
for (const marker of OPERATION_MARKERS) {
|
|
2077
|
+
const markerPath = (await runGitCommand({
|
|
2078
|
+
cwd: root,
|
|
2079
|
+
args: ["rev-parse", "--git-path", marker],
|
|
2080
|
+
errorCode: ERROR_CODES16.WORKTREE_NOT_REPOSITORY,
|
|
2081
|
+
...signal === void 0 ? {} : { signal }
|
|
2082
|
+
})).trim();
|
|
2083
|
+
if (await lstat3(path5.resolve(root, markerPath)).catch(() => void 0) !== void 0) {
|
|
2084
|
+
return true;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return false;
|
|
2088
|
+
}
|
|
2089
|
+
async function inspectUntrackedFiles(root, relativePaths) {
|
|
2090
|
+
if (relativePaths.length > AGENT_WORKSPACE_SNAPSHOT_LIMITS.maxUntrackedFiles) {
|
|
2091
|
+
return ERROR_CODES16.WORKTREE_LOCAL_CHANGES_TOO_LARGE;
|
|
2092
|
+
}
|
|
2093
|
+
let totalBytes = 0;
|
|
2094
|
+
for (const relativePath of relativePaths) {
|
|
2095
|
+
const absolutePath = path5.resolve(root, relativePath);
|
|
2096
|
+
const relative = path5.relative(root, absolutePath);
|
|
2097
|
+
if (relative === ".." || relative.startsWith(`..${path5.sep}`) || path5.isAbsolute(relative)) {
|
|
2098
|
+
return ERROR_CODES16.WORKTREE_UNTRACKED_UNSUPPORTED;
|
|
2099
|
+
}
|
|
2100
|
+
const metadata = await lstat3(absolutePath).catch(() => void 0);
|
|
2101
|
+
if (metadata === void 0 || !metadata.isFile() || metadata.isSymbolicLink()) {
|
|
2102
|
+
return ERROR_CODES16.WORKTREE_UNTRACKED_UNSUPPORTED;
|
|
2103
|
+
}
|
|
2104
|
+
totalBytes += metadata.size;
|
|
2105
|
+
if (totalBytes > AGENT_WORKSPACE_SNAPSHOT_LIMITS.maxUntrackedBytes) {
|
|
2106
|
+
return ERROR_CODES16.WORKTREE_LOCAL_CHANGES_TOO_LARGE;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
return void 0;
|
|
2110
|
+
}
|
|
2111
|
+
async function inspectGitWorkspace(rootValue, signal) {
|
|
2112
|
+
const root = await realpath2(rootValue).catch(() => {
|
|
2113
|
+
throw new SpotPatchError16(ERROR_CODES16.WORKTREE_NOT_REPOSITORY);
|
|
2114
|
+
});
|
|
2115
|
+
const topLevelResult = await runRawGitCommand({
|
|
1972
2116
|
cwd: root,
|
|
1973
2117
|
args: ["rev-parse", "--show-toplevel"],
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2118
|
+
...signal === void 0 ? {} : { signal }
|
|
2119
|
+
});
|
|
2120
|
+
if (topLevelResult.exitCode !== 0 || topLevelResult.cancelled || topLevelResult.timedOut) {
|
|
2121
|
+
throw new SpotPatchError16(ERROR_CODES16.WORKTREE_NOT_REPOSITORY);
|
|
2122
|
+
}
|
|
2123
|
+
const topLevel = topLevelResult.stdout.trim();
|
|
1977
2124
|
if (!samePath(root, topLevel)) {
|
|
1978
|
-
throw new SpotPatchError16(ERROR_CODES16.
|
|
2125
|
+
throw new SpotPatchError16(ERROR_CODES16.WORKTREE_NOT_REPOSITORY);
|
|
1979
2126
|
}
|
|
1980
2127
|
const head = (await runGitCommand({
|
|
1981
2128
|
cwd: root,
|
|
1982
2129
|
args: ["rev-parse", "--verify", "HEAD"],
|
|
1983
|
-
errorCode: ERROR_CODES16.
|
|
1984
|
-
...
|
|
2130
|
+
errorCode: ERROR_CODES16.WORKTREE_NOT_REPOSITORY,
|
|
2131
|
+
...signal === void 0 ? {} : { signal }
|
|
1985
2132
|
})).trim();
|
|
1986
|
-
|
|
1987
|
-
|
|
2133
|
+
const parsed = parsePorcelainStatus(
|
|
2134
|
+
await runGitCommand({
|
|
2135
|
+
cwd: root,
|
|
2136
|
+
args: ["status", "--porcelain=v1", "-z", "--untracked-files=all"],
|
|
2137
|
+
errorCode: ERROR_CODES16.WORKTREE_NOT_REPOSITORY,
|
|
2138
|
+
...signal === void 0 ? {} : { signal }
|
|
2139
|
+
})
|
|
2140
|
+
);
|
|
2141
|
+
const operationActive = await operationInProgress(root, signal);
|
|
2142
|
+
const untrackedError = await inspectUntrackedFiles(root, parsed.untrackedPaths);
|
|
2143
|
+
const errorCode = operationActive ? ERROR_CODES16.WORKTREE_OPERATION_IN_PROGRESS : parsed.changes.conflicted > 0 ? ERROR_CODES16.WORKTREE_CONFLICTED : untrackedError;
|
|
2144
|
+
const health = Object.freeze({
|
|
2145
|
+
state: errorCode !== void 0 ? "blocked" : parsed.changes.total === 0 ? "ready" : "consent-required",
|
|
2146
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2147
|
+
changes: parsed.changes,
|
|
2148
|
+
canIncludeLocalChanges: errorCode === void 0 && parsed.changes.total > 0,
|
|
2149
|
+
...errorCode === void 0 ? {} : { errorCode }
|
|
2150
|
+
});
|
|
2151
|
+
return Object.freeze({
|
|
2152
|
+
root,
|
|
2153
|
+
head,
|
|
2154
|
+
health,
|
|
2155
|
+
untrackedPaths: parsed.untrackedPaths
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
async function inspectAgentWorkspace(root, signal) {
|
|
2159
|
+
try {
|
|
2160
|
+
return (await inspectGitWorkspace(root, signal)).health;
|
|
2161
|
+
} catch (error) {
|
|
2162
|
+
if (error instanceof SpotPatchError16) {
|
|
2163
|
+
return blockedHealth(error.code);
|
|
2164
|
+
}
|
|
2165
|
+
return blockedHealth(ERROR_CODES16.INTERNAL_ERROR);
|
|
1988
2166
|
}
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// src/worktree/git-worktree.ts
|
|
2170
|
+
function workspacePath(root, relativePath) {
|
|
2171
|
+
const candidate = path6.resolve(root, relativePath);
|
|
2172
|
+
const relative = path6.relative(root, candidate);
|
|
2173
|
+
if (relative === ".." || relative.startsWith(`..${path6.sep}`) || path6.isAbsolute(relative)) {
|
|
2174
|
+
throw new SpotPatchError17(ERROR_CODES17.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
|
|
2175
|
+
}
|
|
2176
|
+
return candidate;
|
|
2177
|
+
}
|
|
2178
|
+
async function fileDigest(filePath) {
|
|
2179
|
+
return createHash2("sha256").update(await readFile2(filePath)).digest("hex");
|
|
2180
|
+
}
|
|
2181
|
+
async function copyUntrackedFiles(sourceRoot, worktreeRoot, relativePaths) {
|
|
2182
|
+
for (const relativePath of relativePaths) {
|
|
2183
|
+
const sourcePath = workspacePath(sourceRoot, relativePath);
|
|
2184
|
+
const targetPath = workspacePath(worktreeRoot, relativePath);
|
|
2185
|
+
const metadata = await lstat4(sourcePath).catch(() => void 0);
|
|
2186
|
+
if (metadata === void 0 || !metadata.isFile() || metadata.isSymbolicLink()) {
|
|
2187
|
+
throw new SpotPatchError17(ERROR_CODES17.WORKTREE_LOCAL_CHANGES_UNSUPPORTED);
|
|
2188
|
+
}
|
|
2189
|
+
await mkdir(path6.dirname(targetPath), { recursive: true });
|
|
2190
|
+
await copyFile(sourcePath, targetPath);
|
|
2191
|
+
const [sourceDigest, targetDigest] = await Promise.all([
|
|
2192
|
+
fileDigest(sourcePath),
|
|
2193
|
+
fileDigest(targetPath)
|
|
2194
|
+
]).catch(() => {
|
|
2195
|
+
throw new SpotPatchError17(ERROR_CODES17.APPLY_CONFLICT);
|
|
2196
|
+
});
|
|
2197
|
+
if (sourceDigest !== targetDigest) {
|
|
2198
|
+
throw new SpotPatchError17(ERROR_CODES17.APPLY_CONFLICT);
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
async function assertUntrackedFilesUnchanged(sourceRoot, worktreeRoot, relativePaths) {
|
|
2203
|
+
for (const relativePath of relativePaths) {
|
|
2204
|
+
const sourcePath = workspacePath(sourceRoot, relativePath);
|
|
2205
|
+
const targetPath = workspacePath(worktreeRoot, relativePath);
|
|
2206
|
+
const [sourceDigest, targetDigest] = await Promise.all([
|
|
2207
|
+
fileDigest(sourcePath),
|
|
2208
|
+
fileDigest(targetPath)
|
|
2209
|
+
]).catch(() => {
|
|
2210
|
+
throw new SpotPatchError17(ERROR_CODES17.APPLY_CONFLICT);
|
|
2211
|
+
});
|
|
2212
|
+
if (sourceDigest !== targetDigest) {
|
|
2213
|
+
throw new SpotPatchError17(ERROR_CODES17.APPLY_CONFLICT);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
async function materializeLocalBaseline(sourceRoot, worktreeRoot, expectedHead, untrackedPaths, signal) {
|
|
2218
|
+
const sourceDiff = await runGitCommand({
|
|
2219
|
+
cwd: sourceRoot,
|
|
2220
|
+
args: [
|
|
2221
|
+
"diff",
|
|
2222
|
+
"--binary",
|
|
2223
|
+
"--full-index",
|
|
2224
|
+
"--no-ext-diff",
|
|
2225
|
+
"--no-color",
|
|
2226
|
+
"--no-renames",
|
|
2227
|
+
"HEAD",
|
|
2228
|
+
"--"
|
|
2229
|
+
],
|
|
2230
|
+
signal,
|
|
2231
|
+
errorCode: ERROR_CODES17.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
|
|
1994
2232
|
});
|
|
1995
|
-
if (
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2233
|
+
if (sourceDiff.length > 0) {
|
|
2234
|
+
await runGitCommand({
|
|
2235
|
+
cwd: worktreeRoot,
|
|
2236
|
+
args: ["apply", "--binary", "--whitespace=nowarn", "-"],
|
|
2237
|
+
stdin: sourceDiff,
|
|
2238
|
+
signal,
|
|
2239
|
+
errorCode: ERROR_CODES17.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
|
|
2240
|
+
});
|
|
1999
2241
|
}
|
|
2000
|
-
|
|
2242
|
+
await copyUntrackedFiles(sourceRoot, worktreeRoot, untrackedPaths);
|
|
2243
|
+
const confirmation = await inspectGitWorkspace(sourceRoot, signal);
|
|
2244
|
+
const confirmationDiff = await runGitCommand({
|
|
2245
|
+
cwd: sourceRoot,
|
|
2246
|
+
args: [
|
|
2247
|
+
"diff",
|
|
2248
|
+
"--binary",
|
|
2249
|
+
"--full-index",
|
|
2250
|
+
"--no-ext-diff",
|
|
2251
|
+
"--no-color",
|
|
2252
|
+
"--no-renames",
|
|
2253
|
+
"HEAD",
|
|
2254
|
+
"--"
|
|
2255
|
+
],
|
|
2256
|
+
signal,
|
|
2257
|
+
errorCode: ERROR_CODES17.APPLY_CONFLICT
|
|
2258
|
+
});
|
|
2259
|
+
if (confirmation.head !== expectedHead || confirmationDiff !== sourceDiff || confirmation.untrackedPaths.length !== untrackedPaths.length || confirmation.untrackedPaths.some((value, index) => value !== untrackedPaths[index])) {
|
|
2260
|
+
throw new SpotPatchError17(ERROR_CODES17.APPLY_CONFLICT);
|
|
2261
|
+
}
|
|
2262
|
+
await assertUntrackedFilesUnchanged(sourceRoot, worktreeRoot, untrackedPaths);
|
|
2263
|
+
await runGitCommand({
|
|
2264
|
+
cwd: worktreeRoot,
|
|
2265
|
+
args: ["add", "--all"],
|
|
2266
|
+
signal,
|
|
2267
|
+
errorCode: ERROR_CODES17.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
|
|
2268
|
+
});
|
|
2269
|
+
await runGitCommand({
|
|
2270
|
+
cwd: worktreeRoot,
|
|
2271
|
+
args: [
|
|
2272
|
+
"-c",
|
|
2273
|
+
"user.name=SpotPatch Agent",
|
|
2274
|
+
"-c",
|
|
2275
|
+
"user.email=spotpatch-agent@example.invalid",
|
|
2276
|
+
"commit",
|
|
2277
|
+
"--quiet",
|
|
2278
|
+
"-m",
|
|
2279
|
+
"SpotPatch local workspace baseline"
|
|
2280
|
+
],
|
|
2281
|
+
signal,
|
|
2282
|
+
errorCode: ERROR_CODES17.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
|
|
2283
|
+
});
|
|
2001
2284
|
}
|
|
2002
2285
|
async function defaultTemporaryBase(root) {
|
|
2003
|
-
const dependencyDirectory =
|
|
2286
|
+
const dependencyDirectory = path6.join(root, "node_modules");
|
|
2004
2287
|
try {
|
|
2005
|
-
const stats = await
|
|
2288
|
+
const stats = await lstat4(dependencyDirectory);
|
|
2006
2289
|
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
2007
2290
|
return os.tmpdir();
|
|
2008
2291
|
}
|
|
2009
|
-
return await
|
|
2292
|
+
return await realpath3(dependencyDirectory);
|
|
2010
2293
|
} catch {
|
|
2011
2294
|
return os.tmpdir();
|
|
2012
2295
|
}
|
|
2013
2296
|
}
|
|
2014
2297
|
async function createIsolatedGitWorktree(options) {
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2298
|
+
const workingTreeMode = options.workingTreeMode ?? "require-clean";
|
|
2299
|
+
const inspection = await inspectGitWorkspace(options.root, options.signal);
|
|
2300
|
+
if (inspection.health.state === "blocked") {
|
|
2301
|
+
throw new SpotPatchError17(
|
|
2302
|
+
inspection.health.errorCode ?? ERROR_CODES17.WORKTREE_LOCAL_CHANGES_UNSUPPORTED
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2305
|
+
if (inspection.health.state === "consent-required" && workingTreeMode === "require-clean") {
|
|
2306
|
+
throw new SpotPatchError17(ERROR_CODES17.WORKTREE_DIRTY);
|
|
2307
|
+
}
|
|
2308
|
+
const baseline = Object.freeze({
|
|
2309
|
+
root: inspection.root,
|
|
2310
|
+
head: inspection.head,
|
|
2311
|
+
workingTreeMode
|
|
2018
2312
|
});
|
|
2019
2313
|
const temporaryBase = options.temporaryBase ?? await defaultTemporaryBase(baseline.root);
|
|
2020
2314
|
const temporaryDirectory = await mkdtemp(
|
|
2021
|
-
|
|
2315
|
+
path6.join(temporaryBase, "spotpatch-agent-")
|
|
2022
2316
|
);
|
|
2023
|
-
const worktreePath =
|
|
2317
|
+
const worktreePath = path6.join(temporaryDirectory, "worktree");
|
|
2024
2318
|
let registered = false;
|
|
2025
2319
|
let cleaned = false;
|
|
2026
2320
|
const cleanup = async () => {
|
|
@@ -2035,7 +2329,7 @@ async function createIsolatedGitWorktree(options) {
|
|
|
2035
2329
|
timeoutMs: 3e4
|
|
2036
2330
|
}).catch(() => void 0);
|
|
2037
2331
|
}
|
|
2038
|
-
if (
|
|
2332
|
+
if (path6.basename(temporaryDirectory).startsWith("spotpatch-agent-")) {
|
|
2039
2333
|
await rm2(temporaryDirectory, { recursive: true, force: true }).catch(
|
|
2040
2334
|
() => void 0
|
|
2041
2335
|
);
|
|
@@ -2045,12 +2339,12 @@ async function createIsolatedGitWorktree(options) {
|
|
|
2045
2339
|
await runGitCommand({
|
|
2046
2340
|
cwd: baseline.root,
|
|
2047
2341
|
args: ["worktree", "add", "--detach", worktreePath, baseline.head],
|
|
2048
|
-
errorCode:
|
|
2342
|
+
errorCode: ERROR_CODES17.INTERNAL_ERROR,
|
|
2049
2343
|
signal: options.signal,
|
|
2050
2344
|
timeoutMs: 3e4
|
|
2051
2345
|
});
|
|
2052
2346
|
registered = true;
|
|
2053
|
-
const worktreeRoot = await
|
|
2347
|
+
const worktreeRoot = await realpath3(worktreePath);
|
|
2054
2348
|
const actualHead = (await runGitCommand({
|
|
2055
2349
|
cwd: worktreeRoot,
|
|
2056
2350
|
args: ["rev-parse", "--verify", "HEAD"],
|
|
@@ -2062,7 +2356,16 @@ async function createIsolatedGitWorktree(options) {
|
|
|
2062
2356
|
signal: options.signal
|
|
2063
2357
|
})).trim();
|
|
2064
2358
|
if (actualHead !== baseline.head || !samePath(actualRoot, worktreeRoot)) {
|
|
2065
|
-
throw new
|
|
2359
|
+
throw new SpotPatchError17(ERROR_CODES17.INTERNAL_ERROR);
|
|
2360
|
+
}
|
|
2361
|
+
if (inspection.health.state === "consent-required") {
|
|
2362
|
+
await materializeLocalBaseline(
|
|
2363
|
+
baseline.root,
|
|
2364
|
+
worktreeRoot,
|
|
2365
|
+
baseline.head,
|
|
2366
|
+
inspection.untrackedPaths,
|
|
2367
|
+
options.signal
|
|
2368
|
+
);
|
|
2066
2369
|
}
|
|
2067
2370
|
return Object.freeze({ baseline, root: worktreeRoot, cleanup });
|
|
2068
2371
|
} catch (error) {
|
|
@@ -2072,17 +2375,19 @@ async function createIsolatedGitWorktree(options) {
|
|
|
2072
2375
|
}
|
|
2073
2376
|
|
|
2074
2377
|
// src/worktree/prepared-change.ts
|
|
2075
|
-
import { createHash as
|
|
2076
|
-
import { lstat as
|
|
2077
|
-
import { ERROR_CODES as
|
|
2378
|
+
import { createHash as createHash3 } from "crypto";
|
|
2379
|
+
import { lstat as lstat5, readFile as readFile3 } from "fs/promises";
|
|
2380
|
+
import { ERROR_CODES as ERROR_CODES18, SpotPatchError as SpotPatchError18 } from "@spotpatch/shared";
|
|
2078
2381
|
var privateChanges = /* @__PURE__ */ new WeakMap();
|
|
2079
2382
|
var DELETED_HASH = "<deleted>";
|
|
2080
2383
|
function createPreparedAgentChange(options) {
|
|
2081
2384
|
const touchedPaths = Object.freeze(
|
|
2082
2385
|
options.result.files.map((file) => file.relativePath)
|
|
2083
2386
|
);
|
|
2084
|
-
if (options.
|
|
2085
|
-
|
|
2387
|
+
if (options.baselineHashes.size !== touchedPaths.length || options.expectedHashes.size !== touchedPaths.length || touchedPaths.some(
|
|
2388
|
+
(relativePath) => !options.baselineHashes.has(relativePath) || !options.expectedHashes.has(relativePath)
|
|
2389
|
+
)) {
|
|
2390
|
+
throw new SpotPatchError18(ERROR_CODES18.INTERNAL_ERROR);
|
|
2086
2391
|
}
|
|
2087
2392
|
const change = Object.freeze({
|
|
2088
2393
|
kind: "prepared-agent-change",
|
|
@@ -2092,6 +2397,7 @@ function createPreparedAgentChange(options) {
|
|
|
2092
2397
|
});
|
|
2093
2398
|
privateChanges.set(change, {
|
|
2094
2399
|
baselineHead: options.baselineHead,
|
|
2400
|
+
baselineHashes: new Map(options.baselineHashes),
|
|
2095
2401
|
diff: options.result.diff,
|
|
2096
2402
|
expectedHashes: new Map(options.expectedHashes),
|
|
2097
2403
|
root: options.root,
|
|
@@ -2103,28 +2409,30 @@ function createPreparedAgentChange(options) {
|
|
|
2103
2409
|
function requirePrivateChange(change) {
|
|
2104
2410
|
const privateChange = privateChanges.get(change);
|
|
2105
2411
|
if (privateChange === void 0) {
|
|
2106
|
-
throw new
|
|
2412
|
+
throw new SpotPatchError18(ERROR_CODES18.INTERNAL_ERROR);
|
|
2107
2413
|
}
|
|
2108
2414
|
return privateChange;
|
|
2109
2415
|
}
|
|
2110
|
-
async function
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2416
|
+
async function assertWorkspaceOperationSafe(root, expectedHead) {
|
|
2417
|
+
const inspection = await inspectGitWorkspace(root).catch(() => {
|
|
2418
|
+
throw new SpotPatchError18(ERROR_CODES18.APPLY_CONFLICT);
|
|
2419
|
+
});
|
|
2420
|
+
const blockingOperation = inspection.health.errorCode === ERROR_CODES18.WORKTREE_OPERATION_IN_PROGRESS || inspection.health.errorCode === ERROR_CODES18.WORKTREE_CONFLICTED;
|
|
2421
|
+
if (inspection.head !== expectedHead || blockingOperation) {
|
|
2422
|
+
throw new SpotPatchError18(ERROR_CODES18.APPLY_CONFLICT);
|
|
2423
|
+
}
|
|
2116
2424
|
}
|
|
2117
2425
|
async function fileHash(root, relativePath) {
|
|
2118
2426
|
const normalized = assertAgentPathAllowed(relativePath);
|
|
2119
2427
|
const absolutePath = await resolveWritableAgentPath(root, normalized);
|
|
2120
|
-
const metadata = await
|
|
2428
|
+
const metadata = await lstat5(absolutePath).catch(() => void 0);
|
|
2121
2429
|
if (metadata === void 0) {
|
|
2122
2430
|
return DELETED_HASH;
|
|
2123
2431
|
}
|
|
2124
2432
|
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
2125
|
-
throw new
|
|
2433
|
+
throw new SpotPatchError18(ERROR_CODES18.APPLY_CONFLICT);
|
|
2126
2434
|
}
|
|
2127
|
-
return
|
|
2435
|
+
return createHash3("sha256").update(await readFile3(absolutePath)).digest("hex");
|
|
2128
2436
|
}
|
|
2129
2437
|
async function captureAgentFileHashes(root, paths) {
|
|
2130
2438
|
const entries = await Promise.all(
|
|
@@ -2140,34 +2448,38 @@ function hashesMatch(expected, actual) {
|
|
|
2140
2448
|
async function applyPreparedAgentChange(change) {
|
|
2141
2449
|
const privateChange = requirePrivateChange(change);
|
|
2142
2450
|
if (privateChange.state !== "prepared" || !change.validationPassed || privateChange.diff.length === 0) {
|
|
2143
|
-
throw new
|
|
2144
|
-
change.validationPassed ?
|
|
2451
|
+
throw new SpotPatchError18(
|
|
2452
|
+
change.validationPassed ? ERROR_CODES18.APPLY_CONFLICT : ERROR_CODES18.VALIDATION_FAILED
|
|
2145
2453
|
);
|
|
2146
2454
|
}
|
|
2147
2455
|
privateChange.state = "applying";
|
|
2148
2456
|
try {
|
|
2149
|
-
await
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2457
|
+
await assertWorkspaceOperationSafe(privateChange.root, privateChange.baselineHead);
|
|
2458
|
+
const currentHashes = await captureAgentFileHashes(
|
|
2459
|
+
privateChange.root,
|
|
2460
|
+
privateChange.touchedPaths
|
|
2461
|
+
);
|
|
2462
|
+
if (!hashesMatch(privateChange.baselineHashes, currentHashes)) {
|
|
2463
|
+
throw new SpotPatchError18(ERROR_CODES18.APPLY_CONFLICT);
|
|
2464
|
+
}
|
|
2153
2465
|
await runGitCommand({
|
|
2154
2466
|
cwd: privateChange.root,
|
|
2155
2467
|
args: ["apply", "--check", "--whitespace=error-all", "-"],
|
|
2156
2468
|
stdin: privateChange.diff,
|
|
2157
|
-
errorCode:
|
|
2469
|
+
errorCode: ERROR_CODES18.APPLY_CONFLICT
|
|
2158
2470
|
});
|
|
2159
2471
|
await runGitCommand({
|
|
2160
2472
|
cwd: privateChange.root,
|
|
2161
2473
|
args: ["apply", "--whitespace=error-all", "-"],
|
|
2162
2474
|
stdin: privateChange.diff,
|
|
2163
|
-
errorCode:
|
|
2475
|
+
errorCode: ERROR_CODES18.APPLY_CONFLICT
|
|
2164
2476
|
});
|
|
2165
2477
|
const appliedHashes = await captureAgentFileHashes(
|
|
2166
2478
|
privateChange.root,
|
|
2167
2479
|
privateChange.touchedPaths
|
|
2168
2480
|
);
|
|
2169
2481
|
if (!hashesMatch(privateChange.expectedHashes, appliedHashes)) {
|
|
2170
|
-
throw new
|
|
2482
|
+
throw new SpotPatchError18(ERROR_CODES18.APPLY_CONFLICT);
|
|
2171
2483
|
}
|
|
2172
2484
|
privateChange.appliedHashes = appliedHashes;
|
|
2173
2485
|
privateChange.state = "applied";
|
|
@@ -2179,34 +2491,39 @@ async function applyPreparedAgentChange(change) {
|
|
|
2179
2491
|
async function revertPreparedAgentChange(change) {
|
|
2180
2492
|
const privateChange = requirePrivateChange(change);
|
|
2181
2493
|
if (privateChange.state !== "applied" || privateChange.appliedHashes === void 0) {
|
|
2182
|
-
throw new
|
|
2494
|
+
throw new SpotPatchError18(ERROR_CODES18.APPLY_CONFLICT);
|
|
2183
2495
|
}
|
|
2184
2496
|
privateChange.state = "reverting";
|
|
2185
2497
|
try {
|
|
2186
|
-
|
|
2187
|
-
throw new SpotPatchError17(ERROR_CODES17.APPLY_CONFLICT);
|
|
2188
|
-
}
|
|
2498
|
+
await assertWorkspaceOperationSafe(privateChange.root, privateChange.baselineHead);
|
|
2189
2499
|
const currentHashes = await captureAgentFileHashes(
|
|
2190
2500
|
privateChange.root,
|
|
2191
2501
|
privateChange.touchedPaths
|
|
2192
2502
|
);
|
|
2193
2503
|
for (const [relativePath, expectedHash] of privateChange.appliedHashes) {
|
|
2194
2504
|
if (currentHashes.get(relativePath) !== expectedHash) {
|
|
2195
|
-
throw new
|
|
2505
|
+
throw new SpotPatchError18(ERROR_CODES18.APPLY_CONFLICT);
|
|
2196
2506
|
}
|
|
2197
2507
|
}
|
|
2198
2508
|
await runGitCommand({
|
|
2199
2509
|
cwd: privateChange.root,
|
|
2200
2510
|
args: ["apply", "--reverse", "--check", "--whitespace=error-all", "-"],
|
|
2201
2511
|
stdin: privateChange.diff,
|
|
2202
|
-
errorCode:
|
|
2512
|
+
errorCode: ERROR_CODES18.APPLY_CONFLICT
|
|
2203
2513
|
});
|
|
2204
2514
|
await runGitCommand({
|
|
2205
2515
|
cwd: privateChange.root,
|
|
2206
2516
|
args: ["apply", "--reverse", "--whitespace=error-all", "-"],
|
|
2207
2517
|
stdin: privateChange.diff,
|
|
2208
|
-
errorCode:
|
|
2518
|
+
errorCode: ERROR_CODES18.APPLY_CONFLICT
|
|
2209
2519
|
});
|
|
2520
|
+
const revertedHashes = await captureAgentFileHashes(
|
|
2521
|
+
privateChange.root,
|
|
2522
|
+
privateChange.touchedPaths
|
|
2523
|
+
);
|
|
2524
|
+
if (!hashesMatch(privateChange.baselineHashes, revertedHashes)) {
|
|
2525
|
+
throw new SpotPatchError18(ERROR_CODES18.APPLY_CONFLICT);
|
|
2526
|
+
}
|
|
2210
2527
|
privateChange.state = "reverted";
|
|
2211
2528
|
} catch (error) {
|
|
2212
2529
|
privateChange.state = "applied";
|
|
@@ -2388,11 +2705,11 @@ function isRetryableToolFailure(result) {
|
|
|
2388
2705
|
return false;
|
|
2389
2706
|
}
|
|
2390
2707
|
const candidate = output;
|
|
2391
|
-
return candidate.errorCode ===
|
|
2708
|
+
return candidate.errorCode === ERROR_CODES19.PATCH_REJECTED && candidate.retryable === true;
|
|
2392
2709
|
}
|
|
2393
2710
|
function throwIfCancelled(signal) {
|
|
2394
2711
|
if (signal.aborted) {
|
|
2395
|
-
throw new
|
|
2712
|
+
throw new SpotPatchError19(ERROR_CODES19.AGENT_CANCELLED);
|
|
2396
2713
|
}
|
|
2397
2714
|
}
|
|
2398
2715
|
function linkSignal(source, target) {
|
|
@@ -2430,6 +2747,7 @@ async function executeAgentChange(options) {
|
|
|
2430
2747
|
worktree = await createIsolatedGitWorktree({
|
|
2431
2748
|
root: options.root,
|
|
2432
2749
|
signal: controller.signal,
|
|
2750
|
+
workingTreeMode: options.workingTreeMode ?? "require-clean",
|
|
2433
2751
|
...options.temporaryBase === void 0 ? {} : { temporaryBase: options.temporaryBase }
|
|
2434
2752
|
});
|
|
2435
2753
|
options.callbacks?.onPhase?.(
|
|
@@ -2471,7 +2789,7 @@ async function executeAgentChange(options) {
|
|
|
2471
2789
|
}
|
|
2472
2790
|
toolCallCount += response.toolCalls.length;
|
|
2473
2791
|
if (toolCallCount > options.execution.limits.maxToolCalls) {
|
|
2474
|
-
throw new
|
|
2792
|
+
throw new SpotPatchError19(ERROR_CODES19.AGENT_LIMIT_EXCEEDED);
|
|
2475
2793
|
}
|
|
2476
2794
|
const results = [];
|
|
2477
2795
|
for (const call of response.toolCalls) {
|
|
@@ -2506,7 +2824,7 @@ async function executeAgentChange(options) {
|
|
|
2506
2824
|
pendingResults = Object.freeze(results);
|
|
2507
2825
|
}
|
|
2508
2826
|
if (summary === void 0) {
|
|
2509
|
-
throw new
|
|
2827
|
+
throw new SpotPatchError19(ERROR_CODES19.AGENT_LIMIT_EXCEEDED);
|
|
2510
2828
|
}
|
|
2511
2829
|
options.callbacks?.onPhase?.(
|
|
2512
2830
|
Object.freeze({
|
|
@@ -2539,7 +2857,7 @@ async function executeAgentChange(options) {
|
|
|
2539
2857
|
controller.signal
|
|
2540
2858
|
);
|
|
2541
2859
|
if (afterCheck.diff !== initialChangeSet.diff) {
|
|
2542
|
-
throw new
|
|
2860
|
+
throw new SpotPatchError19(ERROR_CODES19.VALIDATION_FAILED);
|
|
2543
2861
|
}
|
|
2544
2862
|
}
|
|
2545
2863
|
const validationPassed = finalChecks.every((check) => check.status === "passed");
|
|
@@ -2555,9 +2873,14 @@ async function executeAgentChange(options) {
|
|
|
2555
2873
|
worktree.root,
|
|
2556
2874
|
initialChangeSet.touchedPaths
|
|
2557
2875
|
);
|
|
2876
|
+
const baselineHashes = await captureAgentFileHashes(
|
|
2877
|
+
worktree.baseline.root,
|
|
2878
|
+
initialChangeSet.touchedPaths
|
|
2879
|
+
);
|
|
2558
2880
|
return createPreparedAgentChange({
|
|
2559
2881
|
autoApplyEligible,
|
|
2560
2882
|
baselineHead: worktree.baseline.head,
|
|
2883
|
+
baselineHashes,
|
|
2561
2884
|
expectedHashes,
|
|
2562
2885
|
result,
|
|
2563
2886
|
root: worktree.baseline.root,
|
|
@@ -2565,15 +2888,15 @@ async function executeAgentChange(options) {
|
|
|
2565
2888
|
});
|
|
2566
2889
|
} catch (error) {
|
|
2567
2890
|
if (options.signal.aborted) {
|
|
2568
|
-
throw new
|
|
2891
|
+
throw new SpotPatchError19(ERROR_CODES19.AGENT_CANCELLED);
|
|
2569
2892
|
}
|
|
2570
2893
|
if (hasJobTimedOut()) {
|
|
2571
|
-
throw new
|
|
2894
|
+
throw new SpotPatchError19(ERROR_CODES19.AGENT_LIMIT_EXCEEDED);
|
|
2572
2895
|
}
|
|
2573
|
-
if (error instanceof
|
|
2896
|
+
if (error instanceof SpotPatchError19) {
|
|
2574
2897
|
throw error;
|
|
2575
2898
|
}
|
|
2576
|
-
throw new
|
|
2899
|
+
throw new SpotPatchError19(ERROR_CODES19.INTERNAL_ERROR);
|
|
2577
2900
|
} finally {
|
|
2578
2901
|
clearTimeout(timeout);
|
|
2579
2902
|
unlink();
|
|
@@ -2583,15 +2906,15 @@ async function executeAgentChange(options) {
|
|
|
2583
2906
|
|
|
2584
2907
|
// src/provider/capability-probe.ts
|
|
2585
2908
|
import {
|
|
2586
|
-
ERROR_CODES as
|
|
2587
|
-
SpotPatchError as
|
|
2909
|
+
ERROR_CODES as ERROR_CODES20,
|
|
2910
|
+
SpotPatchError as SpotPatchError20
|
|
2588
2911
|
} from "@spotpatch/shared";
|
|
2589
2912
|
var PROBE_TOOL_NAME = "spotpatch_capability_probe";
|
|
2590
2913
|
var PROBE_TOKEN = "spotpatch-ready-v1";
|
|
2591
2914
|
async function probeProviderCapability(options) {
|
|
2592
2915
|
const model = options.provider.models[options.modelProfileId];
|
|
2593
2916
|
if (model === void 0) {
|
|
2594
|
-
throw new
|
|
2917
|
+
throw new SpotPatchError20(ERROR_CODES20.MODEL_NOT_ALLOWED);
|
|
2595
2918
|
}
|
|
2596
2919
|
const credential = options.credential ?? resolveProviderCredential(options.provider.apiKeyEnv, options.environment);
|
|
2597
2920
|
const session = createOpenAICompatibleProviderSession({
|
|
@@ -2620,7 +2943,7 @@ async function probeProviderCapability(options) {
|
|
|
2620
2943
|
const first = await session.next(void 0, options.signal);
|
|
2621
2944
|
const probeCall = first.toolCalls[0];
|
|
2622
2945
|
if (first.toolCalls.length !== 1 || probeCall?.name !== PROBE_TOOL_NAME || probeCall.arguments.token !== PROBE_TOKEN) {
|
|
2623
|
-
throw new
|
|
2946
|
+
throw new SpotPatchError20(ERROR_CODES20.MODEL_TOOL_CALL_UNSUPPORTED);
|
|
2624
2947
|
}
|
|
2625
2948
|
const second = await session.next(
|
|
2626
2949
|
Object.freeze([
|
|
@@ -2632,7 +2955,7 @@ async function probeProviderCapability(options) {
|
|
|
2632
2955
|
options.signal
|
|
2633
2956
|
);
|
|
2634
2957
|
if (second.toolCalls.length !== 0 || second.finalText.trim().length === 0) {
|
|
2635
|
-
throw new
|
|
2958
|
+
throw new SpotPatchError20(ERROR_CODES20.MODEL_TOOL_CALL_UNSUPPORTED);
|
|
2636
2959
|
}
|
|
2637
2960
|
return Object.freeze({
|
|
2638
2961
|
providerProfileId: options.provider.id,
|
|
@@ -2654,6 +2977,7 @@ export {
|
|
|
2654
2977
|
createOpenAICompatibleProviderSession,
|
|
2655
2978
|
createProviderCredential,
|
|
2656
2979
|
executeAgentChange,
|
|
2980
|
+
inspectAgentWorkspace,
|
|
2657
2981
|
probeProviderCapability,
|
|
2658
2982
|
resolveProviderCredential,
|
|
2659
2983
|
revertPreparedAgentChange
|