@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.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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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.
|
|
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.
|
|
2007
|
-
...
|
|
2139
|
+
errorCode: import_shared16.ERROR_CODES.WORKTREE_NOT_REPOSITORY,
|
|
2140
|
+
...signal === void 0 ? {} : { signal }
|
|
2008
2141
|
})).trim();
|
|
2009
|
-
|
|
2010
|
-
|
|
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
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
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 (
|
|
2019
|
-
|
|
2020
|
-
|
|
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
|
-
|
|
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 =
|
|
2295
|
+
const dependencyDirectory = import_node_path6.default.join(root, "node_modules");
|
|
2027
2296
|
try {
|
|
2028
|
-
const stats = await (0,
|
|
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,
|
|
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
|
|
2039
|
-
|
|
2040
|
-
|
|
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,
|
|
2044
|
-
|
|
2323
|
+
const temporaryDirectory = await (0, import_promises6.mkdtemp)(
|
|
2324
|
+
import_node_path6.default.join(temporaryBase, "spotpatch-agent-")
|
|
2045
2325
|
);
|
|
2046
|
-
const worktreePath =
|
|
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 (
|
|
2062
|
-
await (0,
|
|
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:
|
|
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,
|
|
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
|
|
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
|
|
2099
|
-
var
|
|
2100
|
-
var
|
|
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.
|
|
2108
|
-
|
|
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
|
|
2421
|
+
throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INTERNAL_ERROR);
|
|
2130
2422
|
}
|
|
2131
2423
|
return privateChange;
|
|
2132
2424
|
}
|
|
2133
|
-
async function
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
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,
|
|
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
|
|
2442
|
+
throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
|
|
2149
2443
|
}
|
|
2150
|
-
return (0,
|
|
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
|
|
2167
|
-
change.validationPassed ?
|
|
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
|
|
2173
|
-
|
|
2174
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
2503
|
+
throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.APPLY_CONFLICT);
|
|
2206
2504
|
}
|
|
2207
2505
|
privateChange.state = "reverting";
|
|
2208
2506
|
try {
|
|
2209
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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 ===
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2897
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.AGENT_CANCELLED);
|
|
2589
2898
|
}
|
|
2590
2899
|
if (hasJobTimedOut()) {
|
|
2591
|
-
throw new
|
|
2900
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED);
|
|
2592
2901
|
}
|
|
2593
|
-
if (error instanceof
|
|
2902
|
+
if (error instanceof import_shared20.SpotPatchError) {
|
|
2594
2903
|
throw error;
|
|
2595
2904
|
}
|
|
2596
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|