@spotpatch/agent 1.2.0 → 1.2.2
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/README.md +31 -8
- package/dist/index.cjs +78 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +78 -19
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -29,9 +29,27 @@ function parseJsonRecord(value) {
|
|
|
29
29
|
}
|
|
30
30
|
function parseToolArguments(value) {
|
|
31
31
|
if (typeof value !== "string") {
|
|
32
|
-
throw new SpotPatchError(ERROR_CODES.
|
|
32
|
+
throw new SpotPatchError(ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
33
|
+
}
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(value);
|
|
37
|
+
} catch {
|
|
38
|
+
throw new SpotPatchError(ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(parsed)) {
|
|
41
|
+
throw new SpotPatchError(ERROR_CODES.TOOL_ARGUMENTS_INVALID);
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
function assertUniqueToolCallIds(calls) {
|
|
46
|
+
const ids = /* @__PURE__ */ new Set();
|
|
47
|
+
for (const call of calls) {
|
|
48
|
+
if (ids.has(call.id)) {
|
|
49
|
+
throw new SpotPatchError(ERROR_CODES.TOOL_CALL_ID_CONFLICT);
|
|
50
|
+
}
|
|
51
|
+
ids.add(call.id);
|
|
33
52
|
}
|
|
34
|
-
return parseJsonRecord(value);
|
|
35
53
|
}
|
|
36
54
|
function requireString(record, field) {
|
|
37
55
|
const value = record[field];
|
|
@@ -41,6 +59,7 @@ function requireString(record, field) {
|
|
|
41
59
|
return value;
|
|
42
60
|
}
|
|
43
61
|
function validateToolResults(pendingCalls, results) {
|
|
62
|
+
assertUniqueToolCallIds(pendingCalls);
|
|
44
63
|
if (pendingCalls.length === 0) {
|
|
45
64
|
if (results !== void 0 && results.length > 0) {
|
|
46
65
|
throw new SpotPatchError(ERROR_CODES.INTERNAL_ERROR);
|
|
@@ -441,6 +460,7 @@ function parseChatEvents(events) {
|
|
|
441
460
|
}
|
|
442
461
|
const finalText = content.join("");
|
|
443
462
|
const toolCalls = finalizeToolCalls(calls);
|
|
463
|
+
assertUniqueToolCallIds(toolCalls);
|
|
444
464
|
if (toolCalls.length === 0 && finalText.trim().length === 0) {
|
|
445
465
|
throw new SpotPatchError5(ERROR_CODES5.PROVIDER_PROTOCOL_UNSUPPORTED);
|
|
446
466
|
}
|
|
@@ -537,7 +557,7 @@ function collectFunctionCall(item, calls) {
|
|
|
537
557
|
});
|
|
538
558
|
const existing = calls.get(id);
|
|
539
559
|
if (existing !== void 0 && (existing.name !== call.name || JSON.stringify(existing.arguments) !== JSON.stringify(call.arguments))) {
|
|
540
|
-
throw new SpotPatchError6(ERROR_CODES6.
|
|
560
|
+
throw new SpotPatchError6(ERROR_CODES6.TOOL_CALL_ID_CONFLICT);
|
|
541
561
|
}
|
|
542
562
|
calls.set(id, call);
|
|
543
563
|
}
|
|
@@ -840,7 +860,7 @@ function encodeUtf8Text(content, includeByteOrderMark) {
|
|
|
840
860
|
}
|
|
841
861
|
async function writeAgentTextFileIfContentMatches(root, relativePath, expectedContent, nextContent, maximumBytes) {
|
|
842
862
|
if (nextContent.includes("\0")) {
|
|
843
|
-
throw new SpotPatchError9(ERROR_CODES9.
|
|
863
|
+
throw new SpotPatchError9(ERROR_CODES9.TOOL_ARGUMENTS_INVALID);
|
|
844
864
|
}
|
|
845
865
|
const absolutePath = await resolveExistingAgentPath(root, relativePath);
|
|
846
866
|
const [metadata, currentBytes] = await Promise.all([
|
|
@@ -1036,7 +1056,10 @@ function minimalProcessEnvironment() {
|
|
|
1036
1056
|
"LANG",
|
|
1037
1057
|
"LC_ALL"
|
|
1038
1058
|
];
|
|
1039
|
-
const environment = {
|
|
1059
|
+
const environment = {
|
|
1060
|
+
CI: "1",
|
|
1061
|
+
NO_COLOR: "1"
|
|
1062
|
+
};
|
|
1040
1063
|
for (const name of allowedNames) {
|
|
1041
1064
|
const value = process.env[name];
|
|
1042
1065
|
if (value !== void 0) {
|
|
@@ -1113,7 +1136,7 @@ async function runConfiguredCheck(options) {
|
|
|
1113
1136
|
function requireConfiguredCheck(checkId, checks) {
|
|
1114
1137
|
const check = checks[checkId];
|
|
1115
1138
|
if (check === void 0) {
|
|
1116
|
-
throw new SpotPatchError10(ERROR_CODES10.
|
|
1139
|
+
throw new SpotPatchError10(ERROR_CODES10.TOOL_ARGUMENTS_INVALID);
|
|
1117
1140
|
}
|
|
1118
1141
|
return check;
|
|
1119
1142
|
}
|
|
@@ -1431,7 +1454,7 @@ var MAX_DISCOVERED_FILES = 2e4;
|
|
|
1431
1454
|
var TEXT_SAMPLE_BYTES = 8192;
|
|
1432
1455
|
function compileGlob(glob) {
|
|
1433
1456
|
if (glob.length === 0 || glob.length > 256 || glob.includes("\0") || glob.includes("\\") || glob.startsWith("/") || ["[", "]", "{", "}", "(", ")", "!"].some((character) => glob.includes(character)) || glob.split("/").some((segment) => segment === "..")) {
|
|
1434
|
-
throw new SpotPatchError14(ERROR_CODES14.
|
|
1457
|
+
throw new SpotPatchError14(ERROR_CODES14.TOOL_ARGUMENTS_INVALID);
|
|
1435
1458
|
}
|
|
1436
1459
|
let expression = "^";
|
|
1437
1460
|
for (let index = 0; index < glob.length; index += 1) {
|
|
@@ -1670,7 +1693,7 @@ var runCheckSchema = z.strictObject({
|
|
|
1670
1693
|
checkId: z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u)
|
|
1671
1694
|
});
|
|
1672
1695
|
function invalidTool() {
|
|
1673
|
-
throw new SpotPatchError15(ERROR_CODES15.
|
|
1696
|
+
throw new SpotPatchError15(ERROR_CODES15.TOOL_ARGUMENTS_INVALID);
|
|
1674
1697
|
}
|
|
1675
1698
|
function parseArguments(schema, value) {
|
|
1676
1699
|
const parsed = schema.safeParse(value);
|
|
@@ -1727,8 +1750,16 @@ function retryableWriteRejection(reason, guidance) {
|
|
|
1727
1750
|
guidance
|
|
1728
1751
|
});
|
|
1729
1752
|
}
|
|
1753
|
+
function retryableArgumentsRejection() {
|
|
1754
|
+
return Object.freeze({
|
|
1755
|
+
errorCode: ERROR_CODES15.TOOL_ARGUMENTS_INVALID,
|
|
1756
|
+
retryable: true,
|
|
1757
|
+
reason: "ARGUMENTS_DO_NOT_MATCH_CONTRACT",
|
|
1758
|
+
guidance: "No files changed. Retry once with a new tool call ID and only the declared fields and value types."
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1730
1761
|
function createAgentToolExecutor(options) {
|
|
1731
|
-
const
|
|
1762
|
+
const cacheByTurn = /* @__PURE__ */ new Map();
|
|
1732
1763
|
const touchedPaths = /* @__PURE__ */ new Set();
|
|
1733
1764
|
const executeUncached = async (call, signal) => {
|
|
1734
1765
|
switch (call.name) {
|
|
@@ -1944,20 +1975,32 @@ function createAgentToolExecutor(options) {
|
|
|
1944
1975
|
}
|
|
1945
1976
|
};
|
|
1946
1977
|
return Object.freeze({
|
|
1947
|
-
async execute(call, signal) {
|
|
1978
|
+
async execute(call, scope, signal) {
|
|
1979
|
+
if (!Number.isSafeInteger(scope.turn) || scope.turn < 1) {
|
|
1980
|
+
throw new SpotPatchError15(ERROR_CODES15.INTERNAL_ERROR);
|
|
1981
|
+
}
|
|
1982
|
+
const turnCache = cacheByTurn.get(scope.turn) ?? /* @__PURE__ */ new Map();
|
|
1983
|
+
cacheByTurn.set(scope.turn, turnCache);
|
|
1948
1984
|
const signature = `${call.name}\0${JSON.stringify(call.arguments)}`;
|
|
1949
|
-
const cached =
|
|
1985
|
+
const cached = turnCache.get(call.id);
|
|
1950
1986
|
if (cached !== void 0) {
|
|
1951
1987
|
if (cached.signature !== signature) {
|
|
1952
|
-
|
|
1988
|
+
throw new SpotPatchError15(ERROR_CODES15.TOOL_CALL_ID_CONFLICT);
|
|
1953
1989
|
}
|
|
1954
1990
|
return cached.result;
|
|
1955
1991
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
output
|
|
1959
|
-
})
|
|
1960
|
-
|
|
1992
|
+
let output;
|
|
1993
|
+
try {
|
|
1994
|
+
output = await executeUncached(call, signal);
|
|
1995
|
+
} catch (error) {
|
|
1996
|
+
if (error instanceof SpotPatchError15 && error.code === ERROR_CODES15.TOOL_ARGUMENTS_INVALID) {
|
|
1997
|
+
output = retryableArgumentsRejection();
|
|
1998
|
+
} else {
|
|
1999
|
+
throw error;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
const result = Object.freeze({ toolCallId: call.id, output });
|
|
2003
|
+
turnCache.set(call.id, Object.freeze({ signature, result }));
|
|
1961
2004
|
return result;
|
|
1962
2005
|
},
|
|
1963
2006
|
touchedPaths() {
|
|
@@ -2546,6 +2589,7 @@ Follow these rules exactly:
|
|
|
2546
2589
|
- Use apply_patch only when creating or deleting a file, or when the change cannot be expressed as one exact replacement. apply_patch accepts only a raw canonical unified Git diff.
|
|
2547
2590
|
- Every patch must begin with 'diff --git a/<path> b/<path>', include matching '--- a/<path>' and '+++ b/<path>' headers and valid '@@' hunks. Send only the raw diff: no Markdown fences, prose, shell commands, or '*** Begin Patch' markers.
|
|
2548
2591
|
- If a write tool returns a retryable PATCH_REJECTED result, no file changed. Follow its guidance, re-read the current file, and retry once with a new tool call ID.
|
|
2592
|
+
- If any tool returns a retryable TOOL_ARGUMENTS_INVALID result, no file changed. Retry once with a new tool call ID using only the declared fields and value types.
|
|
2549
2593
|
- Never modify credentials, environment files, lockfiles, generated output, Git metadata, or dependencies.
|
|
2550
2594
|
- Do not claim a check passed unless run_check returned a passed status.
|
|
2551
2595
|
- Finish with a concise factual summary after all needed tool calls. Do not include secrets or absolute paths.`;
|
|
@@ -2562,6 +2606,12 @@ function sliceText(value, maximum) {
|
|
|
2562
2606
|
function createBoundedTarget(target, maximumCharacters) {
|
|
2563
2607
|
const detailBudget = Math.max(192, maximumCharacters - 420);
|
|
2564
2608
|
const bounded = {
|
|
2609
|
+
...target.page === void 0 ? {} : {
|
|
2610
|
+
page: Object.freeze({
|
|
2611
|
+
...target.page,
|
|
2612
|
+
url: sanitizeUrl(target.page.url, "http://spotpatch.invalid")
|
|
2613
|
+
})
|
|
2614
|
+
},
|
|
2565
2615
|
source: target.source,
|
|
2566
2616
|
react: Object.freeze({
|
|
2567
2617
|
supported: target.react.supported,
|
|
@@ -2705,7 +2755,7 @@ function isRetryableToolFailure(result) {
|
|
|
2705
2755
|
return false;
|
|
2706
2756
|
}
|
|
2707
2757
|
const candidate = output;
|
|
2708
|
-
return candidate.errorCode === ERROR_CODES19.PATCH_REJECTED
|
|
2758
|
+
return candidate.retryable === true && (candidate.errorCode === ERROR_CODES19.PATCH_REJECTED || candidate.errorCode === ERROR_CODES19.TOOL_ARGUMENTS_INVALID);
|
|
2709
2759
|
}
|
|
2710
2760
|
function throwIfCancelled(signal) {
|
|
2711
2761
|
if (signal.aborted) {
|
|
@@ -2781,8 +2831,10 @@ async function executeAgentChange(options) {
|
|
|
2781
2831
|
let summary;
|
|
2782
2832
|
let toolCallCount = 0;
|
|
2783
2833
|
for (let turn = 0; turn < options.execution.limits.maxTurns; turn += 1) {
|
|
2834
|
+
const turnNumber = turn + 1;
|
|
2784
2835
|
throwIfCancelled(controller.signal);
|
|
2785
2836
|
const response = await session.next(pendingResults, controller.signal);
|
|
2837
|
+
assertUniqueToolCallIds(response.toolCalls);
|
|
2786
2838
|
if (response.toolCalls.length === 0) {
|
|
2787
2839
|
summary = response.finalText.trim().slice(0, options.execution.limits.maxToolOutputCharacters);
|
|
2788
2840
|
break;
|
|
@@ -2795,16 +2847,22 @@ async function executeAgentChange(options) {
|
|
|
2795
2847
|
for (const call of response.toolCalls) {
|
|
2796
2848
|
options.callbacks?.onTool?.(
|
|
2797
2849
|
Object.freeze({
|
|
2850
|
+
turn: turnNumber,
|
|
2798
2851
|
toolCallId: call.id,
|
|
2799
2852
|
toolName: call.name,
|
|
2800
2853
|
state: "started"
|
|
2801
2854
|
})
|
|
2802
2855
|
);
|
|
2803
2856
|
try {
|
|
2804
|
-
const result2 = await executor.execute(
|
|
2857
|
+
const result2 = await executor.execute(
|
|
2858
|
+
call,
|
|
2859
|
+
Object.freeze({ turn: turnNumber }),
|
|
2860
|
+
controller.signal
|
|
2861
|
+
);
|
|
2805
2862
|
results.push(result2);
|
|
2806
2863
|
options.callbacks?.onTool?.(
|
|
2807
2864
|
Object.freeze({
|
|
2865
|
+
turn: turnNumber,
|
|
2808
2866
|
toolCallId: call.id,
|
|
2809
2867
|
toolName: call.name,
|
|
2810
2868
|
state: isRetryableToolFailure(result2) ? "failed" : "succeeded"
|
|
@@ -2813,6 +2871,7 @@ async function executeAgentChange(options) {
|
|
|
2813
2871
|
} catch (error) {
|
|
2814
2872
|
options.callbacks?.onTool?.(
|
|
2815
2873
|
Object.freeze({
|
|
2874
|
+
turn: turnNumber,
|
|
2816
2875
|
toolCallId: call.id,
|
|
2817
2876
|
toolName: call.name,
|
|
2818
2877
|
state: "failed"
|