@spotpatch/agent 1.2.3 → 1.2.4

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 CHANGED
@@ -11,6 +11,7 @@ Security boundaries:
11
11
  - no model-controlled arbitrary shell;
12
12
  - no browser-side API keys;
13
13
  - bounded paths, reads, writes, Diff sizes, turns, and tool calls;
14
+ - rejected read paths stay unread and unmodified while the bounded Agent loop can recover through allowed discovery results;
14
15
  - no implicit stash, reset, commit, push, publish, or deployment;
15
16
  - review is the default apply mode.
16
17
 
@@ -22,7 +23,7 @@ Requires Node.js `>=20.19.0`.
22
23
 
23
24
  业务应用应通过 [`@spotpatch/vite`](https://www.npmjs.com/package/@spotpatch/vite) 等框架适配器启用该能力。本包公开发布是为了形成可安装、可版本化的依赖图,不是独立 UI 接入入口。
24
25
 
25
- 安全边界包括:不向模型开放任意 Shell、不把 API Key 放入浏览器、限制路径/读写/Diff/轮次/工具调用,并且不隐式执行 stash、reset、commit、push、发包或部署。默认应用模式必须经过审阅。
26
+ 安全边界包括:不向模型开放任意 Shell、不把 API Key 放入浏览器、限制路径/读写/Diff/轮次/工具调用;只读路径被拒绝时保持零读取、零修改,并允许 Agent 在既有边界内改用合法发现结果继续执行;不隐式执行 stash、reset、commit、push、发包或部署。默认应用模式必须经过审阅。
26
27
 
27
28
  要求 Node.js `>=20.19.0`。
28
29
 
package/dist/index.cjs CHANGED
@@ -1872,7 +1872,7 @@ var AGENT_TOOL_DEFINITIONS = Object.freeze([
1872
1872
  }),
1873
1873
  Object.freeze({
1874
1874
  name: AGENT_TOOL_NAMES.readFile,
1875
- description: "Read a bounded inclusive line range from one allowed UTF-8 text file.",
1875
+ description: "Read a bounded inclusive line range from one allowed UTF-8 text file. Choose paths 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.",
1876
1876
  parameters: Object.freeze({
1877
1877
  type: "object",
1878
1878
  properties: Object.freeze({
@@ -2018,6 +2018,14 @@ function retryableArgumentsRejection() {
2018
2018
  guidance: "No files changed. Retry once with a new tool call ID and only the declared fields and value types."
2019
2019
  });
2020
2020
  }
2021
+ function retryableReadRejection() {
2022
+ return Object.freeze({
2023
+ errorCode: import_shared15.ERROR_CODES.TOOL_PATH_DENIED,
2024
+ retryable: true,
2025
+ reason: "PATH_UNAVAILABLE",
2026
+ 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."
2027
+ });
2028
+ }
2021
2029
  function createAgentToolExecutor(options) {
2022
2030
  const cacheByTurn = /* @__PURE__ */ new Map();
2023
2031
  const fileCatalog = createAgentFileCatalog(options.worktreeRoot);
@@ -2117,7 +2125,15 @@ function createAgentToolExecutor(options) {
2117
2125
  }
2118
2126
  case AGENT_TOOL_NAMES.readFile: {
2119
2127
  const input = parseArguments(readFileSchema, call.arguments);
2120
- const file = await readTextFile(input.path);
2128
+ let file;
2129
+ try {
2130
+ file = await readTextFile(input.path);
2131
+ } catch (error) {
2132
+ if (error instanceof import_shared15.SpotPatchError && error.code === import_shared15.ERROR_CODES.TOOL_PATH_DENIED) {
2133
+ return retryableReadRejection();
2134
+ }
2135
+ throw error;
2136
+ }
2121
2137
  const lines = file.content.split(/\r?\n/u);
2122
2138
  const startLine = input.startLine ?? 1;
2123
2139
  const endLine = input.endLine ?? Math.min(lines.length, startLine + 199);
@@ -2859,6 +2875,7 @@ Follow these rules exactly:
2859
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.
2860
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.
2861
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.
2862
2879
  - Never modify credentials, environment files, lockfiles, generated output, Git metadata, or dependencies.
2863
2880
  - 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.
2864
2881
  - Finish with a concise factual summary after all needed tool calls. Do not include secrets or absolute paths.`;
@@ -3091,7 +3108,7 @@ function isRetryableToolFailure(result) {
3091
3108
  return false;
3092
3109
  }
3093
3110
  const candidate = output;
3094
- return candidate.retryable === true && (candidate.errorCode === import_shared20.ERROR_CODES.PATCH_REJECTED || candidate.errorCode === import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID);
3111
+ return candidate.retryable === true && (candidate.errorCode === import_shared20.ERROR_CODES.PATCH_REJECTED || candidate.errorCode === import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID || candidate.errorCode === import_shared20.ERROR_CODES.TOOL_PATH_DENIED);
3095
3112
  }
3096
3113
  function throwIfCancelled(signal) {
3097
3114
  if (signal.aborted) {