test: refresh module graph and Web pins for apply_patch
Regenerate docs/module-graph for the fs-patch package, add apply_patch to the shipped Web composition tool list, and refresh the Web tool-schema, system-prompt, and collapse-timing pins that CI compares against the default tool set. Cover the symbolic-link preflight refusal through a provider lstat double and the atomic-write parent failure through a regular-file blocker, so Windows coverage no longer depends on POSIX-only tests.
This commit is contained in:
1 parent
1f14903f28
commit
7fab3cf976
12 files changed
+157
-3
No files matched your search
@@ -473,6 +473,7 @@ function assertLeanChildRecord(agent: Agent): void {
|
||||
* Agent Note owns the rationale and its sources.
|
||||
*/
|
||||
const EXPECTED_TOOLS = [
|
||||
'apply_patch',
|
||||
'ask_user_question',
|
||||
'bash',
|
||||
'create_goal',
|
||||
|
||||
@@ -61,6 +61,7 @@ flowchart TD
|
||||
pkg_fs["fs"]
|
||||
pkg_fs_local["fs-local"]
|
||||
pkg_fs_observation_policy["fs-observation-policy"]
|
||||
pkg_fs_patch["fs-patch"]
|
||||
pkg_fs_sandbox["fs-sandbox"]
|
||||
pkg_tool_fs["tool-fs"]
|
||||
pkg_tool_fs_search["tool-fs-search"]
|
||||
@@ -1475,6 +1476,7 @@ flowchart TD
|
||||
| [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | — |
|
||||
| [`llm`](../packages/llm/llm) | `llm` | — |
|
||||
| [`scope`](../packages/core/scope) | `core` | — |
|
||||
| [`fs-patch`](../packages/fs/fs-patch) | `fs` | — |
|
||||
| [`api-gateway`](../packages/api/gateway) | `api` | — |
|
||||
| [`api-job-controller`](../packages/api/job-controller) | `api` | — |
|
||||
| [`api-remote-access`](../packages/api/remote-access) | `api` | — |
|
||||
|
||||
@@ -63,6 +63,7 @@ flowchart TD
|
||||
pkg_fs["fs"]
|
||||
pkg_fs_local["fs-local"]
|
||||
pkg_fs_observation_policy["fs-observation-policy"]
|
||||
pkg_fs_patch["fs-patch"]
|
||||
pkg_fs_sandbox["fs-sandbox"]
|
||||
pkg_tool_fs["tool-fs"]
|
||||
pkg_tool_fs_search["tool-fs-search"]
|
||||
@@ -1477,6 +1478,7 @@ flowchart TD
|
||||
| [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | — |
|
||||
| [`llm`](../packages/llm/llm) | `llm` | — |
|
||||
| [`scope`](../packages/core/scope) | `core` | — |
|
||||
| [`fs-patch`](../packages/fs/fs-patch) | `fs` | — |
|
||||
| [`api-gateway`](../packages/api/gateway) | `api` | — |
|
||||
| [`api-job-controller`](../packages/api/job-controller) | `api` | — |
|
||||
| [`api-remote-access`](../packages/api/remote-access) | `api` | — |
|
||||
|
||||
@@ -1015,6 +1015,16 @@ describe('atomic write failures', () => {
|
||||
} finally { await chmod(locked, 0o755) }
|
||||
})
|
||||
|
||||
it('maps a parent path blocked by a regular file to FS_IO_ERROR on every platform', async () => {
|
||||
const blocker = join(dir, 'blocker')
|
||||
await writeFile(blocker, 'file')
|
||||
const target = join(blocker, 'child')
|
||||
await expect(writeFileAtomic(target, 'content', undefined, undefined)).rejects.toMatchObject({
|
||||
code: 'FS_IO_ERROR', message: expect.stringContaining(`cannot write "${target}": `) as string, cause: expect.any(Error) as Error,
|
||||
})
|
||||
expect(await readFile(blocker, 'utf8')).toBe('file')
|
||||
})
|
||||
|
||||
it.each([new Error('provider defect'), Object.assign(new Error('invalid errno'), { code: 1 }), 'provider defect'])(
|
||||
'preserves unexpected failures during staging: %s', async (failure) => {
|
||||
const file = join(dir, 'a')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Whole-patch admission rejects all writes while preserving prior observation state. */
|
||||
import { mkdir, symlink, writeFile } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { FsError } from '@deepseek-ai/dsh-fs'
|
||||
import { FsError, FsVersion } from '@deepseek-ai/dsh-fs'
|
||||
import type { FsErrorCode } from '@deepseek-ai/dsh-fs'
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import { add, move, patch, remove, setup, update } from './harness.ts'
|
||||
@@ -52,6 +52,19 @@ it.each([
|
||||
expect(await t.editIntent('a')).toEqual({ version: await t.version('a') })
|
||||
})
|
||||
|
||||
it.each(['delete', 'move'] as const)('rejects a %s source the provider reports as a symbolic link', async (kind) => {
|
||||
const t = await setup()
|
||||
await t.file('a', 'old\n')
|
||||
vi.spyOn(t.fs, 'lstat').mockResolvedValue({ type: 'symlink', version: FsVersion('link') })
|
||||
const a = t.record(kind, 'a', 0, 2, kind === 'move' ? 'b' : undefined)
|
||||
expect(await t.run(patch(kind === 'delete' ? remove('a') : move('a', 'b')))).toEqual({ phase: 'preflight', refusals: [
|
||||
{ operation: a, failure: { code: 'PATCH_SYMLINK_SOURCE',
|
||||
message: `cannot ${kind} "${a.path}": path is a symbolic link — operate on its target path instead` } },
|
||||
] })
|
||||
expect(await t.bytes('a')).toBe('old\n')
|
||||
expect(t.observations).toEqual([])
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32').each(['delete', 'move'] as const)('rejects a symbolic-link %s source without changing its target', async (kind) => {
|
||||
const t = await setup()
|
||||
await t.file('a', 'old\n')
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- button "Copy"
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [expanded]
|
||||
- button "Tools updated 33 added"
|
||||
- button "Tools updated 34 added"
|
||||
- button "Think Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". I'll now reply with DONE as instructed."
|
||||
- paragraph: DONE
|
||||
- button "Copy"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
- button "Copy"
|
||||
- status: Completed
|
||||
- button "Completed in {{duration}}" [disabled] [expanded]
|
||||
- button "Tools updated 33 added"
|
||||
- button "Tools updated 34 added"
|
||||
- button "Think Both files have been read. a.txt contains \"alpha\" and b.txt contains \"beta\". I'll now reply with DONE as instructed."
|
||||
- paragraph: DONE
|
||||
- button "Copy"
|
||||
|
||||
@@ -12,6 +12,8 @@ Read an existing file before overwriting it with write (the default fs-observati
|
||||
|
||||
Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session.
|
||||
|
||||
Prefer apply_patch over separate edit calls for changes across files or separated regions. Read a file before updating, deleting, or moving it (the default fs-observation-policy requires it), unless you just created or edited it in this session. Operations run in order, stop at the first failure, and are not rolled back; re-read affected files before retrying when needed.
|
||||
|
||||
Use the glob tool — not shell find — to discover files by path pattern.
|
||||
|
||||
Use the grep tool — not shell grep or rg — to search file contents. Use read on a matched file when you need surrounding context.
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
{
|
||||
"initial": [
|
||||
{
|
||||
"name": "apply_patch",
|
||||
"description": "Create, update, delete, or move UTF-8 text files with one Codex-style patch; prefer it over multiple edit calls when a change spans several files or separated regions.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"patch": {
|
||||
"type": "string",
|
||||
"description": "Codex patch: start with *** Begin Patch and end with *** End Patch. Use *** Add File: path with + lines; *** Update File: path, optionally followed by *** Move to: path, with @@ or @@ context and space-prefixed context, + added, or - removed lines; *** End of File anchors the last hunk to the file end. Use *** Delete File: path to delete. Paths are relative to the Session working directory. Read existing files before updating, deleting, or moving them."
|
||||
},
|
||||
"sandbox_permissions": {
|
||||
"type": "string",
|
||||
"description": "The narrowest wider sandbox mode for a one-shot retry of the exact operation the sandbox just denied; the retry asks the user for approval.",
|
||||
"enum": [
|
||||
"workspace-write",
|
||||
"danger-full-access"
|
||||
]
|
||||
},
|
||||
"justification": {
|
||||
"type": "string",
|
||||
"description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. Use the language of the user’s current request."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"patch"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ask_user_question",
|
||||
"description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding.",
|
||||
|
||||
@@ -14,6 +14,8 @@ Read an existing file before overwriting it with write (the default fs-observati
|
||||
|
||||
Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session.
|
||||
|
||||
Prefer apply_patch over separate edit calls for changes across files or separated regions. Read a file before updating, deleting, or moving it (the default fs-observation-policy requires it), unless you just created or edited it in this session. Operations run in order, stop at the first failure, and are not rolled back; re-read affected files before retrying when needed.
|
||||
|
||||
Use the glob tool — not shell find — to discover files by path pattern.
|
||||
|
||||
Use the grep tool — not shell grep or rg — to search file contents. Use read on a matched file when you need surrounding context.
|
||||
@@ -47,6 +49,15 @@ Program-only SDK bindings:
|
||||
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
|
||||
|
||||
interface ToolArgsMap {
|
||||
/** Create, update, delete, or move UTF-8 text files with one Codex-style patch; prefer it over multiple edit calls when a change spans several files or separated regions. */
|
||||
apply_patch: {
|
||||
/** Codex patch: start with *** Begin Patch and end with *** End Patch. Use *** Add File: path with + lines; *** Update File: path, optionally followed by *** Move to: path, with @@ or @@ context and space-prefixed context, + added, or - removed lines; *** End of File anchors the last hunk to the file end. Use *** Delete File: path to delete. Paths are relative to the Session working directory. Read existing files before updating, deleting, or moving them. */
|
||||
patch: string;
|
||||
/** The narrowest wider sandbox mode for a one-shot retry of the exact operation the sandbox just denied; the retry asks the user for approval. */
|
||||
sandbox_permissions?: "workspace-write" | "danger-full-access";
|
||||
/** Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. Use the language of the user’s current request. */
|
||||
justification?: string;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. */
|
||||
ask_user_question: {
|
||||
/** Questions to ask the user before continuing. */
|
||||
@@ -375,6 +386,27 @@ interface ToolArgsMap {
|
||||
}
|
||||
|
||||
interface ToolOutputMap {
|
||||
apply_patch: {
|
||||
phase: "commit";
|
||||
cancelled: boolean;
|
||||
operations: ({
|
||||
index: number;
|
||||
kind: "add" | "update" | "delete" | "move";
|
||||
path: string;
|
||||
destination?: string;
|
||||
patchLine: number;
|
||||
status: "completed" | "failed" | "unknown" | "skipped";
|
||||
code?: string;
|
||||
message?: string;
|
||||
hunkLine?: number;
|
||||
changes?: ({
|
||||
path: string;
|
||||
before: string | null;
|
||||
after: string | null;
|
||||
})[];
|
||||
})[];
|
||||
affectedPaths: string[];
|
||||
};
|
||||
ask_user_question: {
|
||||
answers: {
|
||||
id: string;
|
||||
|
||||
@@ -14,6 +14,8 @@ Read an existing file before overwriting it with write (the default fs-observati
|
||||
|
||||
Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session.
|
||||
|
||||
Prefer apply_patch over separate edit calls for changes across files or separated regions. Read a file before updating, deleting, or moving it (the default fs-observation-policy requires it), unless you just created or edited it in this session. Operations run in order, stop at the first failure, and are not rolled back; re-read affected files before retrying when needed.
|
||||
|
||||
Use the glob tool — not shell find — to discover files by path pattern.
|
||||
|
||||
Use the grep tool — not shell grep or rg — to search file contents. Use read on a matched file when you need surrounding context.
|
||||
@@ -47,6 +49,15 @@ Program-only SDK bindings:
|
||||
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
|
||||
|
||||
interface ToolArgsMap {
|
||||
/** Create, update, delete, or move UTF-8 text files with one Codex-style patch; prefer it over multiple edit calls when a change spans several files or separated regions. */
|
||||
apply_patch: {
|
||||
/** Codex patch: start with *** Begin Patch and end with *** End Patch. Use *** Add File: path with + lines; *** Update File: path, optionally followed by *** Move to: path, with @@ or @@ context and space-prefixed context, + added, or - removed lines; *** End of File anchors the last hunk to the file end. Use *** Delete File: path to delete. Paths are relative to the Session working directory. Read existing files before updating, deleting, or moving them. */
|
||||
patch: string;
|
||||
/** The narrowest wider sandbox mode for a one-shot retry of the exact operation the sandbox just denied; the retry asks the user for approval. */
|
||||
sandbox_permissions?: "workspace-write" | "danger-full-access";
|
||||
/** Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. Use the language of the user’s current request. */
|
||||
justification?: string;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. */
|
||||
ask_user_question: {
|
||||
/** Questions to ask the user before continuing. */
|
||||
@@ -383,6 +394,27 @@ interface ToolArgsMap {
|
||||
}
|
||||
|
||||
interface ToolOutputMap {
|
||||
apply_patch: {
|
||||
phase: "commit";
|
||||
cancelled: boolean;
|
||||
operations: ({
|
||||
index: number;
|
||||
kind: "add" | "update" | "delete" | "move";
|
||||
path: string;
|
||||
destination?: string;
|
||||
patchLine: number;
|
||||
status: "completed" | "failed" | "unknown" | "skipped";
|
||||
code?: string;
|
||||
message?: string;
|
||||
hunkLine?: number;
|
||||
changes?: ({
|
||||
path: string;
|
||||
before: string | null;
|
||||
after: string | null;
|
||||
})[];
|
||||
})[];
|
||||
affectedPaths: string[];
|
||||
};
|
||||
ask_user_question: {
|
||||
answers: {
|
||||
id: string;
|
||||
|
||||
@@ -14,6 +14,8 @@ Read an existing file before overwriting it with write (the default fs-observati
|
||||
|
||||
Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session.
|
||||
|
||||
Prefer apply_patch over separate edit calls for changes across files or separated regions. Read a file before updating, deleting, or moving it (the default fs-observation-policy requires it), unless you just created or edited it in this session. Operations run in order, stop at the first failure, and are not rolled back; re-read affected files before retrying when needed.
|
||||
|
||||
Use the glob tool — not shell find — to discover files by path pattern.
|
||||
|
||||
Use the grep tool — not shell grep or rg — to search file contents. Use read on a matched file when you need surrounding context.
|
||||
@@ -47,6 +49,15 @@ Program-only SDK bindings:
|
||||
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
|
||||
|
||||
interface ToolArgsMap {
|
||||
/** Create, update, delete, or move UTF-8 text files with one Codex-style patch; prefer it over multiple edit calls when a change spans several files or separated regions. */
|
||||
apply_patch: {
|
||||
/** Codex patch: start with *** Begin Patch and end with *** End Patch. Use *** Add File: path with + lines; *** Update File: path, optionally followed by *** Move to: path, with @@ or @@ context and space-prefixed context, + added, or - removed lines; *** End of File anchors the last hunk to the file end. Use *** Delete File: path to delete. Paths are relative to the Session working directory. Read existing files before updating, deleting, or moving them. */
|
||||
patch: string;
|
||||
/** The narrowest wider sandbox mode for a one-shot retry of the exact operation the sandbox just denied; the retry asks the user for approval. */
|
||||
sandbox_permissions?: "workspace-write" | "danger-full-access";
|
||||
/** Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. Use the language of the user’s current request. */
|
||||
justification?: string;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. */
|
||||
ask_user_question: {
|
||||
/** Questions to ask the user before continuing. */
|
||||
@@ -366,6 +377,27 @@ interface ToolArgsMap {
|
||||
}
|
||||
|
||||
interface ToolOutputMap {
|
||||
apply_patch: {
|
||||
phase: "commit";
|
||||
cancelled: boolean;
|
||||
operations: ({
|
||||
index: number;
|
||||
kind: "add" | "update" | "delete" | "move";
|
||||
path: string;
|
||||
destination?: string;
|
||||
patchLine: number;
|
||||
status: "completed" | "failed" | "unknown" | "skipped";
|
||||
code?: string;
|
||||
message?: string;
|
||||
hunkLine?: number;
|
||||
changes?: ({
|
||||
path: string;
|
||||
before: string | null;
|
||||
after: string | null;
|
||||
})[];
|
||||
})[];
|
||||
affectedPaths: string[];
|
||||
};
|
||||
ask_user_question: {
|
||||
answers: {
|
||||
id: string;
|
||||
|
||||
Reference in new issue
Block a user