@spotpatch/agent 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.cjs +42 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +42 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1848,7 +1848,7 @@ var AGENT_TOOL_DEFINITIONS = Object.freeze([
|
|
|
1848
1848
|
}),
|
|
1849
1849
|
Object.freeze({
|
|
1850
1850
|
name: AGENT_TOOL_NAMES.readFile,
|
|
1851
|
-
description: "Read a bounded inclusive line range from one allowed UTF-8 text file.",
|
|
1851
|
+
description: "Read a bounded inclusive line range from one allowed UTF-8 text file. Choose an exact path supplied by trusted SpotPatch context or returned by list_files or search_text. A retryable TOOL_PATH_DENIED result means no file was read or changed: do not retry that path; discover an allowed path instead.",
|
|
1852
1852
|
parameters: Object.freeze({
|
|
1853
1853
|
type: "object",
|
|
1854
1854
|
properties: Object.freeze({
|
|
@@ -1902,6 +1902,11 @@ var AGENT_TOOL_DEFINITIONS = Object.freeze([
|
|
|
1902
1902
|
})
|
|
1903
1903
|
})
|
|
1904
1904
|
]);
|
|
1905
|
+
var AGENT_TOOL_DEFINITIONS_WITHOUT_CHECKS = Object.freeze(
|
|
1906
|
+
AGENT_TOOL_DEFINITIONS.filter(
|
|
1907
|
+
(definition) => definition.name !== AGENT_TOOL_NAMES.runCheck
|
|
1908
|
+
)
|
|
1909
|
+
);
|
|
1905
1910
|
|
|
1906
1911
|
// src/tools/tool-executor.ts
|
|
1907
1912
|
var SEARCH_READ_CONCURRENCY = 8;
|
|
@@ -1994,6 +1999,14 @@ function retryableArgumentsRejection() {
|
|
|
1994
1999
|
guidance: "No files changed. Retry once with a new tool call ID and only the declared fields and value types."
|
|
1995
2000
|
});
|
|
1996
2001
|
}
|
|
2002
|
+
function retryableReadRejection() {
|
|
2003
|
+
return Object.freeze({
|
|
2004
|
+
errorCode: ERROR_CODES15.TOOL_PATH_DENIED,
|
|
2005
|
+
retryable: true,
|
|
2006
|
+
reason: "PATH_UNAVAILABLE",
|
|
2007
|
+
guidance: "No file was read or changed. Do not retry the same path. Use list_files or search_text, then read only an allowed path returned by that tool. Protected, external, missing, symlinked, directory, binary, and non-UTF-8 paths are unavailable."
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
1997
2010
|
function createAgentToolExecutor(options) {
|
|
1998
2011
|
const cacheByTurn = /* @__PURE__ */ new Map();
|
|
1999
2012
|
const fileCatalog = createAgentFileCatalog(options.worktreeRoot);
|
|
@@ -2093,7 +2106,15 @@ function createAgentToolExecutor(options) {
|
|
|
2093
2106
|
}
|
|
2094
2107
|
case AGENT_TOOL_NAMES.readFile: {
|
|
2095
2108
|
const input = parseArguments(readFileSchema, call.arguments);
|
|
2096
|
-
|
|
2109
|
+
let file;
|
|
2110
|
+
try {
|
|
2111
|
+
file = await readTextFile(input.path);
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
if (error instanceof SpotPatchError15 && error.code === ERROR_CODES15.TOOL_PATH_DENIED) {
|
|
2114
|
+
return retryableReadRejection();
|
|
2115
|
+
}
|
|
2116
|
+
throw error;
|
|
2117
|
+
}
|
|
2097
2118
|
const lines = file.content.split(/\r?\n/u);
|
|
2098
2119
|
const startLine = input.startLine ?? 1;
|
|
2099
2120
|
const endLine = input.endLine ?? Math.min(lines.length, startLine + 199);
|
|
@@ -2839,6 +2860,7 @@ import {
|
|
|
2839
2860
|
var MAX_PROJECT_CONVENTION_CHARACTERS = 3500;
|
|
2840
2861
|
var MAX_VALIDATION_CHECK_CHARACTERS = 1200;
|
|
2841
2862
|
var MINIMUM_SELECTION_CONTEXT_CHARACTERS = 1024;
|
|
2863
|
+
var VALIDATION_SYSTEM_INSTRUCTION = "- Run each relevant configured check after the final write so failures can be corrected. Do not rerun an unchanged check, and do not claim a check passed unless run_check returned a passed status.\n";
|
|
2842
2864
|
var AGENT_SYSTEM_INSTRUCTIONS = `You are editing code only inside a disposable, isolated Git worktree.
|
|
2843
2865
|
|
|
2844
2866
|
Follow these rules exactly:
|
|
@@ -2853,9 +2875,13 @@ Follow these rules exactly:
|
|
|
2853
2875
|
- 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.
|
|
2854
2876
|
- 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.
|
|
2855
2877
|
- 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.
|
|
2878
|
+
- If read_file returns a retryable TOOL_PATH_DENIED result, no file was read or changed. Do not retry that path. Use list_files or search_text and choose an allowed path returned by the tool; never probe protected, external, generated, credential, environment, or lock files.
|
|
2856
2879
|
- Never modify credentials, environment files, lockfiles, generated output, Git metadata, or dependencies.
|
|
2857
|
-
|
|
2880
|
+
${VALIDATION_SYSTEM_INSTRUCTION.trimEnd()}
|
|
2858
2881
|
- Finish with a concise factual summary after all needed tool calls. Do not include secrets or absolute paths.`;
|
|
2882
|
+
function resolveAgentSystemInstructions(trustedFast) {
|
|
2883
|
+
return trustedFast ? AGENT_SYSTEM_INSTRUCTIONS.replace(VALIDATION_SYSTEM_INSTRUCTION, "") : AGENT_SYSTEM_INSTRUCTIONS;
|
|
2884
|
+
}
|
|
2859
2885
|
function redactedJson(value) {
|
|
2860
2886
|
return JSON.stringify(
|
|
2861
2887
|
value,
|
|
@@ -3047,7 +3073,8 @@ function composeAgentUserPrompt(annotation, maximumCharacters, context = {}) {
|
|
|
3047
3073
|
(target, index) => `Target ${String(index + 1)}:
|
|
3048
3074
|
${redactSensitiveText2(target.instruction.trim())}`
|
|
3049
3075
|
).join("\n\n");
|
|
3050
|
-
const
|
|
3076
|
+
const trustedFastBlock = context.trustedFast ? "\n\nTrusted direct execution is enabled. Start from each target's supplied code.relativePath or source.relativePath. When an exact path is present, do not call list_files first. Read each affected file once unless a write is rejected, make the smallest exact replacement that satisfies the request, and finish immediately after the successful write. No project validation check is available in this mode." : "";
|
|
3077
|
+
const requestBlock = `${requestPrefix}${request}${trustedFastBlock}`;
|
|
3051
3078
|
const checksPrefix = "\n\nConfigured validation checks (IDs and labels only):\n<validation_checks>\n";
|
|
3052
3079
|
const checksSuffix = "\n</validation_checks>";
|
|
3053
3080
|
if (requestBlock.length + contextPrefix.length + suffix.length + MINIMUM_SELECTION_CONTEXT_CHARACTERS > maximumCharacters) {
|
|
@@ -3085,7 +3112,7 @@ function isRetryableToolFailure(result) {
|
|
|
3085
3112
|
return false;
|
|
3086
3113
|
}
|
|
3087
3114
|
const candidate = output;
|
|
3088
|
-
return candidate.retryable === true && (candidate.errorCode === ERROR_CODES19.PATCH_REJECTED || candidate.errorCode === ERROR_CODES19.TOOL_ARGUMENTS_INVALID);
|
|
3115
|
+
return candidate.retryable === true && (candidate.errorCode === ERROR_CODES19.PATCH_REJECTED || candidate.errorCode === ERROR_CODES19.TOOL_ARGUMENTS_INVALID || candidate.errorCode === ERROR_CODES19.TOOL_PATH_DENIED);
|
|
3089
3116
|
}
|
|
3090
3117
|
function throwIfCancelled(signal) {
|
|
3091
3118
|
if (signal.aborted) {
|
|
@@ -3150,6 +3177,8 @@ async function executeToolCalls(calls, turn, executor, callbacks, signal) {
|
|
|
3150
3177
|
return Object.freeze(results);
|
|
3151
3178
|
}
|
|
3152
3179
|
async function executeAgentChange(options) {
|
|
3180
|
+
const trustedFast = options.execution.applyMode === "trusted-auto";
|
|
3181
|
+
const activeChecks = trustedFast ? Object.freeze({}) : options.execution.checks;
|
|
3153
3182
|
const controller = new AbortController();
|
|
3154
3183
|
const unlink = linkSignal(options.signal, controller);
|
|
3155
3184
|
let jobTimedOut = false;
|
|
@@ -3181,7 +3210,7 @@ async function executeAgentChange(options) {
|
|
|
3181
3210
|
})
|
|
3182
3211
|
);
|
|
3183
3212
|
const executor = createAgentToolExecutor({
|
|
3184
|
-
checks:
|
|
3213
|
+
checks: activeChecks,
|
|
3185
3214
|
limits: options.execution.limits,
|
|
3186
3215
|
worktreeRoot: worktree.root,
|
|
3187
3216
|
onCheck(result2) {
|
|
@@ -3197,16 +3226,17 @@ async function executeAgentChange(options) {
|
|
|
3197
3226
|
provider: options.provider,
|
|
3198
3227
|
model: options.model,
|
|
3199
3228
|
credential: options.credential,
|
|
3200
|
-
instructions:
|
|
3229
|
+
instructions: resolveAgentSystemInstructions(trustedFast),
|
|
3201
3230
|
userPrompt: composeAgentUserPrompt(
|
|
3202
3231
|
options.annotation,
|
|
3203
3232
|
options.promptMaxCharacters ?? 16e3,
|
|
3204
3233
|
Object.freeze({
|
|
3205
|
-
checks:
|
|
3206
|
-
projectConventions
|
|
3234
|
+
checks: activeChecks,
|
|
3235
|
+
projectConventions,
|
|
3236
|
+
trustedFast
|
|
3207
3237
|
})
|
|
3208
3238
|
),
|
|
3209
|
-
tools: AGENT_TOOL_DEFINITIONS,
|
|
3239
|
+
tools: trustedFast ? AGENT_TOOL_DEFINITIONS_WITHOUT_CHECKS : AGENT_TOOL_DEFINITIONS,
|
|
3210
3240
|
limits: options.execution.limits,
|
|
3211
3241
|
...options.fetch === void 0 ? {} : { fetch: options.fetch }
|
|
3212
3242
|
});
|
|
@@ -3243,7 +3273,7 @@ async function executeAgentChange(options) {
|
|
|
3243
3273
|
options.callbacks?.onPhase?.(
|
|
3244
3274
|
Object.freeze({
|
|
3245
3275
|
phase: "validating",
|
|
3246
|
-
message: "Validating proposed changes."
|
|
3276
|
+
message: trustedFast ? "Preparing trusted change for direct apply." : "Validating proposed changes."
|
|
3247
3277
|
})
|
|
3248
3278
|
);
|
|
3249
3279
|
const initialChangeSet = await collectAgentChangeSet(
|
|
@@ -3252,7 +3282,7 @@ async function executeAgentChange(options) {
|
|
|
3252
3282
|
options.execution.limits,
|
|
3253
3283
|
controller.signal
|
|
3254
3284
|
);
|
|
3255
|
-
const requiredChecks = initialChangeSet.diff.length === 0 ? [] : Object.values(
|
|
3285
|
+
const requiredChecks = initialChangeSet.diff.length === 0 ? [] : Object.values(activeChecks).filter((check) => check.required);
|
|
3256
3286
|
const finalChecks = [];
|
|
3257
3287
|
let ranFinalCheck = false;
|
|
3258
3288
|
for (const check of requiredChecks) {
|