@j0hanz/filesystem-mcp 2.1.5 → 2.1.7
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/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -13
- package/dist/cli.js.map +1 -1
- package/dist/core/diff.d.ts +6 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +21 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/errors.d.ts +0 -20
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -60
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +18 -2
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +34 -6
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fs.d.ts +1 -2
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +3 -22
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +1 -6
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +0 -9
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +2 -4
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +3 -4
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +1 -6
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path.d.ts +4 -8
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +13 -26
- package/dist/core/path.js.map +1 -1
- package/dist/core/primitives.d.ts +8 -2
- package/dist/core/primitives.d.ts.map +1 -1
- package/dist/core/primitives.js +10 -2
- package/dist/core/primitives.js.map +1 -1
- package/dist/core/read.d.ts +0 -15
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +12 -41
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +1 -16
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +7 -41
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +39 -3
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +72 -17
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +7 -15
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +0 -1
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +0 -1
- package/dist/core/store.js.map +1 -1
- package/dist/index.js +1 -8
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +13 -28
- package/dist/prompts.js.map +1 -1
- package/dist/tools/batch.d.ts +5 -78
- package/dist/tools/batch.d.ts.map +1 -1
- package/dist/tools/batch.js +6 -115
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +8 -18
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +1 -4
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +1 -22
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete-file.d.ts.map +1 -1
- package/dist/tools/delete-file.js +6 -6
- package/dist/tools/delete-file.js.map +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +3 -10
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +35 -72
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list.js +2 -1
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +162 -116
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts.map +1 -1
- package/dist/tools/patch.js +5 -23
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +20 -21
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-in-files.d.ts.map +1 -1
- package/dist/tools/replace-in-files.js +33 -51
- package/dist/tools/replace-in-files.js.map +1 -1
- package/dist/tools/search-content.d.ts.map +1 -1
- package/dist/tools/search-content.js +10 -31
- package/dist/tools/search-content.js.map +1 -1
- package/dist/tools/search-files.d.ts.map +1 -1
- package/dist/tools/search-files.js +1 -2
- package/dist/tools/search-files.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +4 -3
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.js +1 -1
- package/dist/transport/http-policy.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/move.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { isInputRequiredResult } from '@modelcontextprotocol/server';
|
|
2
|
-
import { basename, dirname, resolve
|
|
2
|
+
import { basename, dirname, resolve } from 'node:path';
|
|
3
3
|
import * as z from 'zod/v4';
|
|
4
|
+
import { processInParallel } from '../core/concurrency.js';
|
|
4
5
|
import { ErrorCode, FsError, isFsError, isNodeError, Problem, rethrowIfAborted, } from '../core/errors.js';
|
|
5
6
|
import { joinRoster, pathLabel } from '../core/fmt.js';
|
|
6
7
|
import { destExists } from '../core/fs.js';
|
|
7
|
-
import { readAcceptedChoice } from '../core/input-required.js';
|
|
8
|
+
import { choiceInput, pendingRoundTrip, readAcceptedChoice } from '../core/input-required.js';
|
|
8
9
|
import { IS_CASE_INSENSITIVE_FS, isPathInsideDirectory, isSamePath } from '../core/path-utils.js';
|
|
9
10
|
import { defaultFalseBoolean, PerFileErrorSchema, RequiredPath } from '../core/schema.js';
|
|
10
|
-
import {
|
|
11
|
+
import { PARALLEL_CONCURRENCY } from '../core/util.js';
|
|
11
12
|
import { defineTool } from './define.js';
|
|
12
13
|
const MoveItemSchema = z.strictObject({
|
|
13
14
|
source: RequiredPath.describe('Path of the file or directory to move or copy'),
|
|
@@ -38,64 +39,61 @@ const MoveOutputSchema = z.strictObject({
|
|
|
38
39
|
.optional()
|
|
39
40
|
.describe('Destinations skipped because the user chose Skip'),
|
|
40
41
|
});
|
|
42
|
+
const VERB = { move: 'Move', copy: 'Copy' };
|
|
41
43
|
/**
|
|
42
|
-
* Phase 1 (no mutation): validate source and destination, reject self-
|
|
43
|
-
*
|
|
44
|
-
* whether this
|
|
45
|
-
* the old flow created the destination's parent before asking, which
|
|
46
|
-
* the filesystem before a confirmation; that now waits for phase 2 (R14).
|
|
44
|
+
* Phase 1 (no mutation): validate source and destination, reject self-targets
|
|
45
|
+
* and transfers into a subdirectory of the source, and stat the destination to
|
|
46
|
+
* decide whether this pair needs an overwrite confirmation. No `mkdir` happens
|
|
47
|
+
* here — the old flow created the destination's parent before asking, which
|
|
48
|
+
* mutated the filesystem before a confirmation; that now waits for phase 2 (R14).
|
|
49
|
+
*
|
|
50
|
+
* `overwrite` is copy-only — move has no confirmation bypass — so a move always
|
|
51
|
+
* passes false.
|
|
47
52
|
*/
|
|
48
|
-
async function
|
|
49
|
-
let
|
|
50
|
-
let
|
|
51
|
-
try {
|
|
52
|
-
({ renamePath, realPath } = await validateMoveSource(move.source, fs));
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
return { status: 'fail', failure: pairFailure(move, error) };
|
|
56
|
-
}
|
|
53
|
+
async function planTransfer(op, pair, fs, overwrite) {
|
|
54
|
+
let realSource;
|
|
55
|
+
let opSource;
|
|
57
56
|
let validDest;
|
|
58
57
|
try {
|
|
59
|
-
|
|
58
|
+
({ realSource, opSource } = await validateTransferSource(op, pair.source, fs));
|
|
59
|
+
validDest = await fs.pathGuard.validatePathForWrite(pair.destination);
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
|
-
return { status: 'fail', failure: pairFailure(
|
|
62
|
+
return { status: 'fail', failure: pairFailure(pair, error) };
|
|
63
63
|
}
|
|
64
|
-
// Comparisons run on the resolved source; only the fs call
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
const resolvedSource = resolve(
|
|
64
|
+
// Comparisons run on the resolved source; only the fs call in phase 2 uses
|
|
65
|
+
// opSource. validatePathForWrite resolves the destination through a symlink
|
|
66
|
+
// too, so both sides of every check must be resolved to match.
|
|
67
|
+
const resolvedSource = resolve(realSource);
|
|
68
68
|
const resolvedDest = resolve(validDest);
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const isSelf = resolvedSource === resolvedDest;
|
|
70
|
+
const isCaseOnlyRename = !isSelf && isSamePath(resolvedSource, resolvedDest);
|
|
71
|
+
// A copy onto a case-only variant of its own source is the same file, so it
|
|
72
|
+
// is a no-op; the same rename is real work for move and proceeds.
|
|
73
|
+
if (isSelf || (isCaseOnlyRename && op === 'copy')) {
|
|
71
74
|
return { status: 'noop' };
|
|
72
75
|
}
|
|
73
|
-
|
|
74
|
-
const normalizedSource = IS_CASE_INSENSITIVE_FS
|
|
75
|
-
? (resolvedSource + sep).toLowerCase()
|
|
76
|
-
: resolvedSource + sep;
|
|
77
|
-
if (normalizedDest.startsWith(normalizedSource)) {
|
|
76
|
+
if (!isCaseOnlyRename && isPathInsideDirectory(resolvedSource, resolvedDest)) {
|
|
78
77
|
return {
|
|
79
78
|
status: 'fail',
|
|
80
|
-
failure: pairFailure(
|
|
79
|
+
failure: pairFailure(pair, new FsError(ErrorCode.INVALID_INPUT, `Cannot ${op} a directory into its own subdirectory`, pair.source)),
|
|
81
80
|
};
|
|
82
81
|
}
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const pending = !isCaseOnlyRename && destExistedOriginally;
|
|
82
|
+
const destExistedOriginally = !isCaseOnlyRename && (await destExists(fs, validDest, op));
|
|
83
|
+
const pending = destExistedOriginally && !overwrite;
|
|
86
84
|
return {
|
|
87
85
|
status: 'plan',
|
|
88
|
-
plan: { pair
|
|
86
|
+
plan: { pair, opSource, validDest, isCaseOnlyRename, destExistedOriginally, pending },
|
|
89
87
|
};
|
|
90
88
|
}
|
|
91
89
|
/**
|
|
92
|
-
* Phase 2 (mutation): confirm an overwrite if needed,
|
|
93
|
-
*
|
|
94
|
-
* A declined/missing confirmation throws `CANCELLED`, collected as a
|
|
95
|
-
* failure by the caller. A destination that appeared between plan and
|
|
96
|
-
* (created during the confirmation gap) also fails closed.
|
|
90
|
+
* Phase 2 (mutation): confirm an overwrite if needed, TOCTOU-check the
|
|
91
|
+
* destination, create its parent, then rename (with cross-device fallback) or
|
|
92
|
+
* copy. A declined/missing confirmation throws `CANCELLED`, collected as a
|
|
93
|
+
* per-pair failure by the caller. A destination that appeared between plan and
|
|
94
|
+
* mutation (created during the confirmation gap) also fails closed.
|
|
97
95
|
*/
|
|
98
|
-
async function
|
|
96
|
+
async function executeTransfer(op, plan, ctx, pendingSorted) {
|
|
99
97
|
if (plan.pending) {
|
|
100
98
|
const key = `confirm_${pendingSorted.indexOf(plan.validDest)}`;
|
|
101
99
|
const choice = readAcceptedChoice(ctx.inputResponses, key);
|
|
@@ -103,78 +101,130 @@ async function executeMove(plan, ctx, pendingSorted) {
|
|
|
103
101
|
return { skipped: plan.pair.destination };
|
|
104
102
|
}
|
|
105
103
|
if (choice !== 'overwrite') {
|
|
106
|
-
throw new FsError(ErrorCode.CANCELLED,
|
|
104
|
+
throw new FsError(ErrorCode.CANCELLED, `${VERB[op]} cancelled: overwrite of "${plan.pair.destination}" was declined or missing`, plan.pair.destination);
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
// TOCTOU check before any mutation: a destination that did not exist when
|
|
110
108
|
// planned but exists now was created during the confirmation gap.
|
|
111
|
-
if (!plan.isCaseOnlyRename
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
109
|
+
if (!plan.isCaseOnlyRename &&
|
|
110
|
+
!plan.destExistedOriginally &&
|
|
111
|
+
(await destExists(ctx.fs, plan.validDest, op))) {
|
|
112
|
+
throw new FsError(ErrorCode.CANCELLED, `${VERB[op]} cancelled: destination "${plan.pair.destination}" was created during confirmation.`, plan.pair.destination);
|
|
116
113
|
}
|
|
117
114
|
await ctx.fs.mkdir(dirname(plan.validDest), { recursive: true });
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
async function planCopy(copy, fs, overwrite) {
|
|
123
|
-
let realSource;
|
|
124
|
-
try {
|
|
125
|
-
realSource = await fs.pathGuard.validateExistingPath(copy.source);
|
|
126
|
-
}
|
|
127
|
-
catch (error) {
|
|
128
|
-
return { status: 'fail', failure: pairFailure(copy, error) };
|
|
115
|
+
if (op === 'move') {
|
|
116
|
+
await performRenameWithFallback(plan.opSource, plan.validDest, ctx.fs, plan.pair.source);
|
|
117
|
+
ctx.log?.('info', `move: ${plan.pair.source} -> ${plan.pair.destination}`, 'move');
|
|
129
118
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
119
|
+
else {
|
|
120
|
+
await ctx.fs.cp(plan.opSource, plan.validDest, {
|
|
121
|
+
recursive: true,
|
|
122
|
+
verbatimSymlinks: true,
|
|
123
|
+
preserveTimestamps: true,
|
|
124
|
+
force: true,
|
|
125
|
+
});
|
|
133
126
|
}
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
return { value: { from: plan.opSource, to: plan.validDest } };
|
|
128
|
+
}
|
|
129
|
+
function pairFailure(pair, error) {
|
|
130
|
+
return {
|
|
131
|
+
source: pair.source,
|
|
132
|
+
destination: pair.destination,
|
|
133
|
+
error: Problem.fromUnknown(error, ErrorCode.UNKNOWN, pair.source),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The source→destination sibling of `runOverPaths`: plan every pair, fail closed
|
|
138
|
+
* on a duplicated destination, round-trip the overwrite confirmations as one
|
|
139
|
+
* set, then execute what survived in parallel. `copy` and `move` differ only in
|
|
140
|
+
* their `plan` and `execute` callbacks.
|
|
141
|
+
*/
|
|
142
|
+
async function runTransfers(items, ctx, op, overwrite) {
|
|
143
|
+
const { results: planned, errors: planErrors } = await processInParallel(items, (pair) => planTransfer(op, pair, ctx.fs, overwrite), PARALLEL_CONCURRENCY, ctx.signal);
|
|
144
|
+
// plan callbacks are fail-closed (they return { status: 'fail' }, never
|
|
145
|
+
// throw), so planErrors should always be empty. Surface the first rather
|
|
146
|
+
// than silently dropping the item if a future plan callback violates that.
|
|
147
|
+
const firstPlanError = planErrors[0];
|
|
148
|
+
if (firstPlanError)
|
|
149
|
+
throw firstPlanError.error;
|
|
150
|
+
const failures = [];
|
|
151
|
+
const candidates = [];
|
|
152
|
+
for (const { value } of planned) {
|
|
153
|
+
if (value.status === 'fail')
|
|
154
|
+
failures.push(value.failure);
|
|
155
|
+
else if (value.status === 'plan')
|
|
156
|
+
candidates.push(value.plan);
|
|
157
|
+
// 'noop' (self-copy / self-move) is silently skipped.
|
|
136
158
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
159
|
+
// Two sources targeting the same destination in one batch would otherwise
|
|
160
|
+
// collapse to a single shared overwrite confirmation and let the second
|
|
161
|
+
// one silently clobber the first's freshly-written content. Fail closed:
|
|
162
|
+
// only the first plan per destination proceeds; later ones targeting the
|
|
163
|
+
// same destination are reported as a per-item failure.
|
|
164
|
+
const seenDest = new Set();
|
|
165
|
+
const ready = [];
|
|
166
|
+
for (const candidate of candidates) {
|
|
167
|
+
const destKey = IS_CASE_INSENSITIVE_FS
|
|
168
|
+
? candidate.validDest.toLowerCase()
|
|
169
|
+
: candidate.validDest;
|
|
170
|
+
if (seenDest.has(destKey)) {
|
|
171
|
+
failures.push(pairFailure(candidate.pair, new FsError(ErrorCode.INVALID_INPUT, `${VERB[op]} cancelled: another entry in this batch already targets destination "${candidate.pair.destination}"`, candidate.pair.destination)));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
seenDest.add(destKey);
|
|
175
|
+
ready.push(candidate);
|
|
140
176
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
177
|
+
const pendingSorted = ready
|
|
178
|
+
.filter((p) => p.pending)
|
|
179
|
+
.map((p) => p.validDest)
|
|
180
|
+
.sort();
|
|
181
|
+
if (pendingSorted.length > 0) {
|
|
182
|
+
// Round 1 returns input_required; a retry whose verified state does not
|
|
183
|
+
// bind this overwrite set throws (R9) via `pendingRoundTrip`.
|
|
184
|
+
const round = await pendingRoundTrip({
|
|
185
|
+
op,
|
|
186
|
+
pending: pendingSorted,
|
|
187
|
+
requestState: ctx.requestState,
|
|
188
|
+
clientCapabilities: ctx.clientCapabilities,
|
|
189
|
+
buildInputs: (dests) => dests.map((dest, i) => choiceInput(`confirm_${i}`, op === 'move'
|
|
190
|
+
? `"${dest}" already exists. Overwrite it?`
|
|
191
|
+
: `Destination "${dest}" already exists. Overwrite it?`, [
|
|
192
|
+
{ value: 'overwrite', title: 'Overwrite' },
|
|
193
|
+
{ value: 'skip', title: 'Skip' },
|
|
194
|
+
])),
|
|
195
|
+
});
|
|
196
|
+
if (round !== undefined)
|
|
197
|
+
return round;
|
|
146
198
|
}
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
199
|
+
const total = ready.length;
|
|
200
|
+
let completed = 0;
|
|
201
|
+
const tick = () => {
|
|
202
|
+
completed += 1;
|
|
203
|
+
ctx.onProgress?.({ current: completed, total });
|
|
152
204
|
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const key = `confirm_${pendingSorted.indexOf(plan.validDest)}`;
|
|
157
|
-
const choice = readAcceptedChoice(ctx.inputResponses, key);
|
|
158
|
-
if (choice === 'skip') {
|
|
159
|
-
return { skipped: plan.pair.destination };
|
|
205
|
+
const { results: execResults, errors: execErrors } = await processInParallel(ready, async (readyPlan) => {
|
|
206
|
+
try {
|
|
207
|
+
return await executeTransfer(op, readyPlan, ctx, pendingSorted);
|
|
160
208
|
}
|
|
161
|
-
|
|
162
|
-
|
|
209
|
+
finally {
|
|
210
|
+
tick();
|
|
163
211
|
}
|
|
212
|
+
}, PARALLEL_CONCURRENCY, ctx.signal);
|
|
213
|
+
for (const { error, index } of execErrors) {
|
|
214
|
+
rethrowIfAborted(error);
|
|
215
|
+
const failed = ready[index];
|
|
216
|
+
if (failed)
|
|
217
|
+
failures.push(pairFailure(failed.pair, error));
|
|
164
218
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
219
|
+
const results = [];
|
|
220
|
+
const skipped = [];
|
|
221
|
+
for (const { value } of execResults) {
|
|
222
|
+
if ('skipped' in value)
|
|
223
|
+
skipped.push(value.skipped);
|
|
224
|
+
else
|
|
225
|
+
results.push(value.value);
|
|
169
226
|
}
|
|
170
|
-
|
|
171
|
-
await ctx.fs.cp(plan.realSource, plan.validDest, {
|
|
172
|
-
recursive: true,
|
|
173
|
-
verbatimSymlinks: true,
|
|
174
|
-
preserveTimestamps: true,
|
|
175
|
-
force: true,
|
|
176
|
-
});
|
|
177
|
-
return { value: { from: plan.realSource, to: plan.validDest } };
|
|
227
|
+
return { results, skipped, failures };
|
|
178
228
|
}
|
|
179
229
|
/**
|
|
180
230
|
* Two-phase move: pre-check every move (no mutation) to build the overwrite
|
|
@@ -184,17 +234,10 @@ async function executeCopy(plan, ctx, pendingSorted, overwrite) {
|
|
|
184
234
|
* an accepted overwrite. Non-pending moves proceed alongside them.
|
|
185
235
|
*/
|
|
186
236
|
async function handleMove(args, ctx) {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
execute: (plan, pendingSorted) => executeCopy(plan, ctx, pendingSorted, args.overwrite),
|
|
192
|
-
})
|
|
193
|
-
: await runOverPairs(args.moves, ctx, {
|
|
194
|
-
op: 'move',
|
|
195
|
-
plan: (move) => planMove(move, ctx.fs),
|
|
196
|
-
execute: (plan, pendingSorted) => executeMove(plan, ctx, pendingSorted),
|
|
197
|
-
});
|
|
237
|
+
const op = args.copy ? 'copy' : 'move';
|
|
238
|
+
// `overwrite` is copy-only; move has no confirmation bypass.
|
|
239
|
+
const overwrite = args.copy && args.overwrite;
|
|
240
|
+
const outcome = await runTransfers(args.moves, ctx, op, overwrite);
|
|
198
241
|
if (isInputRequiredResult(outcome))
|
|
199
242
|
return outcome;
|
|
200
243
|
const { results, skipped, failures } = outcome;
|
|
@@ -204,7 +247,7 @@ async function handleMove(args, ctx) {
|
|
|
204
247
|
...(skipped.length > 0 ? { skipped } : {}),
|
|
205
248
|
};
|
|
206
249
|
}
|
|
207
|
-
function buildSummary(verb, results, failures, skipped
|
|
250
|
+
function buildSummary(verb, results, failures, skipped) {
|
|
208
251
|
const successCount = results.length;
|
|
209
252
|
const failCount = failures.length;
|
|
210
253
|
if (failCount === 0 && skipped.length === 0 && successCount === 1) {
|
|
@@ -231,18 +274,19 @@ function buildSummary(verb, results, failures, skipped = []) {
|
|
|
231
274
|
* itself, or moving a symlink would rename the file it points at and leave the
|
|
232
275
|
* link dangling. Every collision check must instead compare the resolved
|
|
233
276
|
* target, or a link moved onto itself (or onto its own target) reads as a real
|
|
234
|
-
* move and renames the link over that target, destroying it.
|
|
277
|
+
* move and renames the link over that target, destroying it. A copy reads
|
|
278
|
+
* through the link, so both views are the resolved one.
|
|
235
279
|
*/
|
|
236
|
-
async function
|
|
280
|
+
async function validateTransferSource(op, source, fs) {
|
|
237
281
|
try {
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
return {
|
|
282
|
+
const realSource = await fs.pathGuard.validateExistingPath(source);
|
|
283
|
+
const opSource = op === 'move' ? await fs.pathGuard.validatePathForDelete(source) : realSource;
|
|
284
|
+
return { realSource, opSource };
|
|
241
285
|
}
|
|
242
286
|
catch (error) {
|
|
243
287
|
if (isFsError(error))
|
|
244
288
|
throw error;
|
|
245
|
-
throw new FsError(ErrorCode.ACCESS_DENIED,
|
|
289
|
+
throw new FsError(ErrorCode.ACCESS_DENIED, `${VERB[op]} failed for ${source}`, source);
|
|
246
290
|
}
|
|
247
291
|
}
|
|
248
292
|
async function performRenameWithFallback(validSource, validDest, fsOps, originalSource) {
|
|
@@ -321,7 +365,9 @@ export const MOVE = defineTool({
|
|
|
321
365
|
return {
|
|
322
366
|
structured: output,
|
|
323
367
|
text: buildSummary(args.copy ? 'copy' : 'move', output.moves, failures, skipped),
|
|
324
|
-
|
|
368
|
+
// Every requested pair failed: no move, no copy, no user-chosen skip - the
|
|
369
|
+
// call did nothing. A skip is work the caller asked for, so it counts.
|
|
370
|
+
isError: failures.length > 0 && output.moves.length + skipped.length === 0,
|
|
325
371
|
};
|
|
326
372
|
},
|
|
327
373
|
});
|
package/dist/tools/move.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move.js","sourceRoot":"","sources":["../../src/tools/move.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InputRequiredResult } from '@modelcontextprotocol/server';\nimport { isInputRequiredResult } from '@modelcontextprotocol/server';\n\nimport { basename, dirname, resolve, sep } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport {\n ErrorCode,\n FsError,\n isFsError,\n isNodeError,\n Problem,\n rethrowIfAborted,\n} from '../core/errors.js';\nimport { joinRoster, pathLabel } from '../core/fmt.js';\nimport { destExists } from '../core/fs.js';\nimport type { GuardedFileSystem } from '../core/fs.js';\nimport { readAcceptedChoice } from '../core/input-required.js';\nimport { IS_CASE_INSENSITIVE_FS, isPathInsideDirectory, isSamePath } from '../core/path-utils.js';\nimport { defaultFalseBoolean, PerFileErrorSchema, RequiredPath } from '../core/schema.js';\nimport type { PairExecResult, PairPlanResult } from './batch.js';\nimport { isTotalFailure, pairFailure, runOverPairs } from './batch.js';\nimport type { ToolCtx } from './define.js';\nimport { defineTool } from './define.js';\n\nconst MoveItemSchema = z.strictObject({\n source: RequiredPath.describe('Path of the file or directory to move or copy'),\n destination: RequiredPath.describe('Destination path'),\n});\n\nconst MoveItemResultSchema = z.strictObject({\n from: z.string().describe('Resolved absolute source path'),\n to: z.string().describe('Resolved absolute destination path'),\n});\n\nconst MoveInputSchema = z.strictObject({\n moves: z.array(MoveItemSchema).min(1).max(100).describe('Operations to perform (max 100)'),\n copy: defaultFalseBoolean('Copy instead of move; sources are left in place'),\n overwrite: defaultFalseBoolean(\n 'Copy mode only: overwrite existing destinations without confirmation',\n ),\n});\n\nconst MoveFailureItemSchema = z.strictObject({\n source: z.string().describe('Source path that could not be moved'),\n destination: z.string().describe('Intended destination path for the failed move'),\n error: PerFileErrorSchema,\n});\n\ntype MoveFailureItem = z.infer<typeof MoveFailureItemSchema>;\n\nconst MoveOutputSchema = z.strictObject({\n moves: z.array(MoveItemResultSchema).describe('Successfully completed operations'),\n failures: z\n .array(MoveFailureItemSchema)\n .optional()\n .describe('Operations that failed with per-item error details'),\n skipped: z\n .array(z.string())\n .optional()\n .describe('Destinations skipped because the user chose Skip'),\n});\n\ntype MoveItemResult = z.infer<typeof MoveItemResultSchema>;\n\n/** A move pre-checked up to the point a confirmation decision is needed. */\ninterface MovePlan {\n pair: { source: string; destination: string };\n renamePath: string;\n validDest: string;\n isCaseOnlyRename: boolean;\n /** Whether the destination existed when planned (drives the TOCTOU guard). */\n destExistedOriginally: boolean;\n /** Destination exists and is not a self/case-only move → needs overwrite confirmation. */\n pending: boolean;\n}\n\n/**\n * Phase 1 (no mutation): validate source and destination, reject self-moves and\n * moves into a subdirectory of the source, and stat the destination to decide\n * whether this move needs an overwrite confirmation. No `mkdir` happens here —\n * the old flow created the destination's parent before asking, which mutated\n * the filesystem before a confirmation; that now waits for phase 2 (R14).\n */\nasync function planMove(\n move: { source: string; destination: string },\n fs: ToolCtx['fs'],\n): Promise<PairPlanResult<MovePlan>> {\n let renamePath: string;\n let realPath: string;\n try {\n ({ renamePath, realPath } = await validateMoveSource(move.source, fs));\n } catch (error) {\n return { status: 'fail', failure: pairFailure(move, error) };\n }\n\n let validDest: string;\n try {\n validDest = await fs.pathGuard.validatePathForWrite(move.destination);\n } catch (error) {\n return { status: 'fail', failure: pairFailure(move, error) };\n }\n\n // Comparisons run on the resolved source; only the fs call below uses\n // renamePath. validatePathForWrite resolves the destination through a\n // symlink too, so both sides of every check must be resolved to match.\n const resolvedSource = resolve(realPath);\n const resolvedDest = resolve(validDest);\n\n if (resolvedSource === resolvedDest) {\n // Self-move — silently skip.\n return { status: 'noop' };\n }\n\n const normalizedDest = IS_CASE_INSENSITIVE_FS ? resolvedDest.toLowerCase() : resolvedDest;\n const normalizedSource = IS_CASE_INSENSITIVE_FS\n ? (resolvedSource + sep).toLowerCase()\n : resolvedSource + sep;\n\n if (normalizedDest.startsWith(normalizedSource)) {\n return {\n status: 'fail',\n failure: pairFailure(\n move,\n new FsError(\n ErrorCode.INVALID_INPUT,\n 'Cannot move a directory into its own subdirectory',\n move.source,\n ),\n ),\n };\n }\n\n const isCaseOnlyRename = isSamePath(resolvedSource, resolvedDest);\n const destExistedOriginally = !isCaseOnlyRename && (await destExists(fs, validDest, 'move'));\n\n const pending = !isCaseOnlyRename && destExistedOriginally;\n return {\n status: 'plan',\n plan: { pair: move, renamePath, validDest, isCaseOnlyRename, destExistedOriginally, pending },\n };\n}\n\n/**\n * Phase 2 (mutation): confirm an overwrite if needed, create the destination's\n * parent, TOCTOU-check the destination, then rename (with cross-device fallback).\n * A declined/missing confirmation throws `CANCELLED`, collected as a per-move\n * failure by the caller. A destination that appeared between plan and rename\n * (created during the confirmation gap) also fails closed.\n */\nasync function executeMove(\n plan: MovePlan,\n ctx: Pick<ToolCtx, 'fs' | 'signal' | 'inputResponses' | 'log'>,\n pendingSorted: readonly string[],\n): Promise<PairExecResult<MoveItemResult>> {\n if (plan.pending) {\n const key = `confirm_${pendingSorted.indexOf(plan.validDest)}`;\n const choice = readAcceptedChoice(ctx.inputResponses, key);\n if (choice === 'skip') {\n return { skipped: plan.pair.destination };\n }\n if (choice !== 'overwrite') {\n throw new FsError(\n ErrorCode.CANCELLED,\n `Move cancelled: overwrite of \"${plan.pair.destination}\" was declined or missing`,\n plan.pair.destination,\n );\n }\n }\n\n // TOCTOU check before any mutation: a destination that did not exist when\n // planned but exists now was created during the confirmation gap.\n if (!plan.isCaseOnlyRename) {\n const existsNow = await destExists(ctx.fs, plan.validDest, 'move');\n if (existsNow && !plan.destExistedOriginally) {\n throw new FsError(\n ErrorCode.CANCELLED,\n `Move cancelled: destination \"${plan.pair.destination}\" was created during confirmation.`,\n plan.pair.destination,\n );\n }\n }\n\n await ctx.fs.mkdir(dirname(plan.validDest), { recursive: true });\n\n await performRenameWithFallback(plan.renamePath, plan.validDest, ctx.fs, plan.pair.source);\n ctx.log?.('info', `move: ${plan.pair.source} -> ${plan.pair.destination}`, 'move');\n return { value: { from: plan.renamePath, to: plan.validDest } };\n}\n\n/** A copy pre-checked up to the point a confirmation decision is needed. */\ninterface CopyPlan {\n pair: { source: string; destination: string };\n realSource: string;\n validDest: string;\n destExistedOriginally: boolean;\n pending: boolean;\n}\n\nasync function planCopy(\n copy: { source: string; destination: string },\n fs: ToolCtx['fs'],\n overwrite: boolean,\n): Promise<PairPlanResult<CopyPlan>> {\n let realSource: string;\n try {\n realSource = await fs.pathGuard.validateExistingPath(copy.source);\n } catch (error) {\n return { status: 'fail', failure: pairFailure(copy, error) };\n }\n\n let validDest: string;\n try {\n validDest = await fs.pathGuard.validatePathForWrite(copy.destination);\n } catch (error) {\n return { status: 'fail', failure: pairFailure(copy, error) };\n }\n\n if (isSamePath(realSource, validDest)) {\n // Self-copy — noop\n return { status: 'noop' };\n }\n\n if (isPathInsideDirectory(realSource, validDest)) {\n return {\n status: 'fail',\n failure: pairFailure(\n copy,\n new FsError(\n ErrorCode.INVALID_INPUT,\n 'Cannot copy a directory into its own subdirectory',\n copy.source,\n ),\n ),\n };\n }\n\n const destExistedOriginally = await destExists(fs, validDest, 'copy');\n\n const pending = destExistedOriginally && !overwrite;\n return {\n status: 'plan',\n plan: { pair: copy, realSource, validDest, destExistedOriginally, pending },\n };\n}\n\nasync function executeCopy(\n plan: CopyPlan,\n ctx: Pick<ToolCtx, 'fs' | 'signal' | 'inputResponses'>,\n pendingSorted: readonly string[],\n overwrite: boolean,\n): Promise<PairExecResult<MoveItemResult>> {\n if (plan.pending && !overwrite) {\n const key = `confirm_${pendingSorted.indexOf(plan.validDest)}`;\n const choice = readAcceptedChoice(ctx.inputResponses, key);\n if (choice === 'skip') {\n return { skipped: plan.pair.destination };\n }\n if (choice !== 'overwrite') {\n throw new FsError(\n ErrorCode.CANCELLED,\n `Copy cancelled: overwrite of \"${plan.pair.destination}\" was declined or missing`,\n plan.pair.destination,\n );\n }\n }\n\n // TOCTOU check before any mutation: a destination that did not exist when\n // planned but exists now was created during the confirmation gap.\n if ((await destExists(ctx.fs, plan.validDest, 'copy')) && !plan.destExistedOriginally) {\n throw new FsError(\n ErrorCode.CANCELLED,\n `Copy cancelled: destination \"${plan.pair.destination}\" was created during confirmation.`,\n plan.pair.destination,\n );\n }\n\n await ctx.fs.mkdir(dirname(plan.validDest), { recursive: true });\n\n await ctx.fs.cp(plan.realSource, plan.validDest, {\n recursive: true,\n verbatimSymlinks: true,\n preserveTimestamps: true,\n force: true,\n });\n\n return { value: { from: plan.realSource, to: plan.validDest } };\n}\n\n/**\n * Two-phase move: pre-check every move (no mutation) to build the overwrite\n * pending set; if any move is pending and the round carries no verified\n * `requestState`, return `input_required` moving nothing (R14). On retry, R9\n * checks the state binds this move set, then each pending move proceeds only on\n * an accepted overwrite. Non-pending moves proceed alongside them.\n */\nasync function handleMove(\n args: z.infer<typeof MoveInputSchema>,\n ctx: ToolCtx,\n): Promise<z.infer<typeof MoveOutputSchema> | InputRequiredResult> {\n const outcome = args.copy\n ? await runOverPairs(args.moves, ctx, {\n op: 'copy',\n plan: (pair) => planCopy(pair, ctx.fs, args.overwrite),\n execute: (plan, pendingSorted) => executeCopy(plan, ctx, pendingSorted, args.overwrite),\n })\n : await runOverPairs(args.moves, ctx, {\n op: 'move',\n plan: (move) => planMove(move, ctx.fs),\n execute: (plan, pendingSorted) => executeMove(plan, ctx, pendingSorted),\n });\n if (isInputRequiredResult(outcome)) return outcome;\n\n const { results, skipped, failures } = outcome;\n\n return {\n moves: results,\n ...(failures.length > 0 ? { failures } : {}),\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n}\n\nfunction buildSummary(\n verb: 'move' | 'copy',\n results: readonly MoveItemResult[],\n failures: readonly MoveFailureItem[],\n skipped: readonly string[] = [],\n): string {\n const successCount = results.length;\n const failCount = failures.length;\n if (failCount === 0 && skipped.length === 0 && successCount === 1) {\n const result = results[0];\n if (result) {\n return `${verb}: ${pathLabel(result.from)} → ${pathLabel(result.to)}`;\n }\n }\n // Name each pair. \"move: 2 items\" left the caller to open structuredContent\n // to learn which two, and a partial failure was unreadable without it.\n // Skipped destinations are an outcome the user chose, not a failure — name\n // them, or an all-skipped call reads as \"copy: nothing\".\n const tokens = [\n ...results.map((r) => `${pathLabel(r.from)} → ${pathLabel(r.to)}`),\n ...skipped.map((dest) => `${pathLabel(dest)} SKIPPED`),\n ];\n const parts = [`${verb}: ${joinRoster(tokens) || 'nothing'}`];\n if (failCount > 0) parts.push(`${String(failCount)} failed`);\n return parts.join(' · ');\n}\n\ninterface MoveSource {\n /** Symlink preserved — the path actually handed to rename/cp/rm. */\n renamePath: string;\n /** Symlink resolved — the identity used for every same-target comparison. */\n realPath: string;\n}\n\n/**\n * A move needs two views of its source. Renaming must operate on the link\n * itself, or moving a symlink would rename the file it points at and leave the\n * link dangling. Every collision check must instead compare the resolved\n * target, or a link moved onto itself (or onto its own target) reads as a real\n * move and renames the link over that target, destroying it.\n */\nasync function validateMoveSource(source: string, fs: ToolCtx['fs']): Promise<MoveSource> {\n try {\n const realPath = await fs.pathGuard.validateExistingPath(source);\n const renamePath = await fs.pathGuard.validatePathForDelete(source);\n return { renamePath, realPath };\n } catch (error) {\n if (isFsError(error)) throw error;\n throw new FsError(ErrorCode.ACCESS_DENIED, `Move failed for ${source}`, source);\n }\n}\n\nasync function performRenameWithFallback(\n validSource: string,\n validDest: string,\n fsOps: Pick<GuardedFileSystem, 'rename' | 'cp' | 'rm'>,\n originalSource: string,\n): Promise<void> {\n try {\n await fsOps.rename(validSource, validDest);\n } catch (error: unknown) {\n rethrowIfAborted(error);\n\n if (!isNodeError(error) || error.code !== 'EXDEV') {\n throw error;\n }\n\n // EXDEV: cross-device rename. Copy then remove, preserving symlinks and\n // timestamps so link semantics survive the move.\n let copied = false;\n try {\n await fsOps.cp(validSource, validDest, {\n recursive: true,\n verbatimSymlinks: true,\n preserveTimestamps: true,\n });\n copied = true;\n await fsOps.rm(validSource, { recursive: true, force: true });\n } catch (copyOrRemoveError) {\n rethrowIfAborted(copyOrRemoveError);\n if (copied) {\n // cp succeeded but rm failed: the destination already holds a complete\n // copy and the source remains. Surface the rm error as the cause so the\n // caller can recover (clean up the duplicate) instead of a silent generic\n // failure that hides the partial completion.\n const baseProblem = Problem.fromUnknown(\n copyOrRemoveError,\n ErrorCode.UNKNOWN,\n originalSource,\n );\n throw new FsError(\n baseProblem.code,\n `Cross-device move of ${originalSource}: copy succeeded but source removal failed (destination holds a copy): ${baseProblem.message}`,\n originalSource,\n copyOrRemoveError,\n );\n }\n throw copyOrRemoveError;\n }\n }\n}\n\nexport const MOVE = defineTool({\n name: 'move',\n title: 'Move or Copy Files',\n description:\n 'Move, rename, or copy files and directories to explicit destination paths (max 100 operations per call). ' +\n 'Pass moves: [{ source, destination }] — there is no single-pair form. ' +\n 'Parent directories are created automatically. Set copy=true to copy instead of move (sources are kept). ' +\n 'An existing destination prompts the user to confirm the overwrite, so the call returns without moving ' +\n 'anything until that confirmation comes back; copy=true with overwrite=true skips the prompt, move has no ' +\n 'such bypass. Self-moves are silently skipped.',\n input: MoveInputSchema,\n output: MoveOutputSchema,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n progress: (args) => {\n const label = args.copy ? 'Copy' : 'Move';\n if (args.moves.length === 1) {\n const move = args.moves[0];\n return {\n label,\n subject: `${basename(move?.source ?? '')} → ${basename(move?.destination ?? '')}`,\n };\n }\n return { label, subject: `${String(args.moves.length)} files` };\n },\n defaultErrorCode: ErrorCode.UNKNOWN,\n accessPaths: (args) => args.moves.flatMap((m) => [m.source, m.destination]),\n run: async (args, ctx) => {\n const output = await handleMove(args, ctx);\n // input_required is a return value, not a completed call: surface it\n // verbatim so the executor short-circuits before building a CallToolResult.\n if (isInputRequiredResult(output)) return output;\n const failures = output.failures ?? [];\n const skipped = output.skipped ?? [];\n return {\n structured: output,\n text: buildSummary(args.copy ? 'copy' : 'move', output.moves, failures, skipped),\n isError: isTotalFailure({ results: output.moves, skipped, failures }),\n };\n },\n});\n"]}
|
|
1
|
+
{"version":3,"file":"move.js","sourceRoot":"","sources":["../../src/tools/move.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InputRequiredResult } from '@modelcontextprotocol/server';\nimport { isInputRequiredResult } from '@modelcontextprotocol/server';\n\nimport { basename, dirname, resolve } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { processInParallel } from '../core/concurrency.js';\nimport {\n ErrorCode,\n FsError,\n isFsError,\n isNodeError,\n Problem,\n rethrowIfAborted,\n} from '../core/errors.js';\nimport { joinRoster, pathLabel } from '../core/fmt.js';\nimport { destExists } from '../core/fs.js';\nimport type { GuardedFileSystem } from '../core/fs.js';\nimport { choiceInput, pendingRoundTrip, readAcceptedChoice } from '../core/input-required.js';\nimport { IS_CASE_INSENSITIVE_FS, isPathInsideDirectory, isSamePath } from '../core/path-utils.js';\nimport { defaultFalseBoolean, PerFileErrorSchema, RequiredPath } from '../core/schema.js';\nimport { PARALLEL_CONCURRENCY } from '../core/util.js';\nimport type { ToolCtx } from './define.js';\nimport { defineTool } from './define.js';\n\nconst MoveItemSchema = z.strictObject({\n source: RequiredPath.describe('Path of the file or directory to move or copy'),\n destination: RequiredPath.describe('Destination path'),\n});\n\nconst MoveItemResultSchema = z.strictObject({\n from: z.string().describe('Resolved absolute source path'),\n to: z.string().describe('Resolved absolute destination path'),\n});\n\nconst MoveInputSchema = z.strictObject({\n moves: z.array(MoveItemSchema).min(1).max(100).describe('Operations to perform (max 100)'),\n copy: defaultFalseBoolean('Copy instead of move; sources are left in place'),\n overwrite: defaultFalseBoolean(\n 'Copy mode only: overwrite existing destinations without confirmation',\n ),\n});\n\nconst MoveFailureItemSchema = z.strictObject({\n source: z.string().describe('Source path that could not be moved'),\n destination: z.string().describe('Intended destination path for the failed move'),\n error: PerFileErrorSchema,\n});\n\ntype MoveFailureItem = z.infer<typeof MoveFailureItemSchema>;\n\nconst MoveOutputSchema = z.strictObject({\n moves: z.array(MoveItemResultSchema).describe('Successfully completed operations'),\n failures: z\n .array(MoveFailureItemSchema)\n .optional()\n .describe('Operations that failed with per-item error details'),\n skipped: z\n .array(z.string())\n .optional()\n .describe('Destinations skipped because the user chose Skip'),\n});\n\ntype MoveItemResult = z.infer<typeof MoveItemResultSchema>;\n\ntype PairOp = 'move' | 'copy';\n\nconst VERB: Readonly<Record<PairOp, string>> = { move: 'Move', copy: 'Copy' };\n\n/** A move or copy pre-checked up to the point a confirmation decision is needed. */\ninterface TransferPlan {\n pair: { source: string; destination: string };\n /**\n * The path the mutation runs on: the link itself for a move, the resolved\n * target for a copy. Every collision check uses the resolved source instead\n * — see {@link validateTransferSource}.\n */\n opSource: string;\n validDest: string;\n /**\n * A rename that only changes the destination's case. Move must perform it —\n * it is real work — so it is exempt from the destination-exists checks that\n * would otherwise read the source itself as an existing destination. Never\n * true for a copy, which treats a case-only target as a self-copy and skips.\n */\n isCaseOnlyRename: boolean;\n /** Whether the destination existed when planned (drives the TOCTOU guard). */\n destExistedOriginally: boolean;\n /** Destination exists and is not a self/case-only target → needs overwrite confirmation. */\n pending: boolean;\n}\n\n/**\n * Phase 1 (no mutation): validate source and destination, reject self-targets\n * and transfers into a subdirectory of the source, and stat the destination to\n * decide whether this pair needs an overwrite confirmation. No `mkdir` happens\n * here — the old flow created the destination's parent before asking, which\n * mutated the filesystem before a confirmation; that now waits for phase 2 (R14).\n *\n * `overwrite` is copy-only — move has no confirmation bypass — so a move always\n * passes false.\n */\nasync function planTransfer(\n op: PairOp,\n pair: { source: string; destination: string },\n fs: ToolCtx['fs'],\n overwrite: boolean,\n): Promise<TransferPlanResult> {\n let realSource: string;\n let opSource: string;\n let validDest: string;\n try {\n ({ realSource, opSource } = await validateTransferSource(op, pair.source, fs));\n validDest = await fs.pathGuard.validatePathForWrite(pair.destination);\n } catch (error) {\n return { status: 'fail', failure: pairFailure(pair, error) };\n }\n\n // Comparisons run on the resolved source; only the fs call in phase 2 uses\n // opSource. validatePathForWrite resolves the destination through a symlink\n // too, so both sides of every check must be resolved to match.\n const resolvedSource = resolve(realSource);\n const resolvedDest = resolve(validDest);\n\n const isSelf = resolvedSource === resolvedDest;\n const isCaseOnlyRename = !isSelf && isSamePath(resolvedSource, resolvedDest);\n\n // A copy onto a case-only variant of its own source is the same file, so it\n // is a no-op; the same rename is real work for move and proceeds.\n if (isSelf || (isCaseOnlyRename && op === 'copy')) {\n return { status: 'noop' };\n }\n\n if (!isCaseOnlyRename && isPathInsideDirectory(resolvedSource, resolvedDest)) {\n return {\n status: 'fail',\n failure: pairFailure(\n pair,\n new FsError(\n ErrorCode.INVALID_INPUT,\n `Cannot ${op} a directory into its own subdirectory`,\n pair.source,\n ),\n ),\n };\n }\n\n const destExistedOriginally = !isCaseOnlyRename && (await destExists(fs, validDest, op));\n const pending = destExistedOriginally && !overwrite;\n\n return {\n status: 'plan',\n plan: { pair, opSource, validDest, isCaseOnlyRename, destExistedOriginally, pending },\n };\n}\n\n/**\n * Phase 2 (mutation): confirm an overwrite if needed, TOCTOU-check the\n * destination, create its parent, then rename (with cross-device fallback) or\n * copy. A declined/missing confirmation throws `CANCELLED`, collected as a\n * per-pair failure by the caller. A destination that appeared between plan and\n * mutation (created during the confirmation gap) also fails closed.\n */\nasync function executeTransfer(\n op: PairOp,\n plan: TransferPlan,\n ctx: Pick<ToolCtx, 'fs' | 'signal' | 'inputResponses' | 'log'>,\n pendingSorted: readonly string[],\n): Promise<TransferExecResult> {\n if (plan.pending) {\n const key = `confirm_${pendingSorted.indexOf(plan.validDest)}`;\n const choice = readAcceptedChoice(ctx.inputResponses, key);\n if (choice === 'skip') {\n return { skipped: plan.pair.destination };\n }\n if (choice !== 'overwrite') {\n throw new FsError(\n ErrorCode.CANCELLED,\n `${VERB[op]} cancelled: overwrite of \"${plan.pair.destination}\" was declined or missing`,\n plan.pair.destination,\n );\n }\n }\n\n // TOCTOU check before any mutation: a destination that did not exist when\n // planned but exists now was created during the confirmation gap.\n if (\n !plan.isCaseOnlyRename &&\n !plan.destExistedOriginally &&\n (await destExists(ctx.fs, plan.validDest, op))\n ) {\n throw new FsError(\n ErrorCode.CANCELLED,\n `${VERB[op]} cancelled: destination \"${plan.pair.destination}\" was created during confirmation.`,\n plan.pair.destination,\n );\n }\n\n await ctx.fs.mkdir(dirname(plan.validDest), { recursive: true });\n\n if (op === 'move') {\n await performRenameWithFallback(plan.opSource, plan.validDest, ctx.fs, plan.pair.source);\n ctx.log?.('info', `move: ${plan.pair.source} -> ${plan.pair.destination}`, 'move');\n } else {\n await ctx.fs.cp(plan.opSource, plan.validDest, {\n recursive: true,\n verbatimSymlinks: true,\n preserveTimestamps: true,\n force: true,\n });\n }\n return { value: { from: plan.opSource, to: plan.validDest } };\n}\n\ntype TransferPlanResult =\n | { status: 'fail'; failure: MoveFailureItem }\n | { status: 'noop' }\n | { status: 'plan'; plan: TransferPlan };\n\n/** A discriminated union, so the fold below narrows without a generic cast. */\ntype TransferExecResult = { readonly skipped: string } | { readonly value: MoveItemResult };\n\nfunction pairFailure(\n pair: { source: string; destination: string },\n error: unknown,\n): MoveFailureItem {\n return {\n source: pair.source,\n destination: pair.destination,\n error: Problem.fromUnknown(error, ErrorCode.UNKNOWN, pair.source),\n };\n}\n\n/**\n * The source→destination sibling of `runOverPaths`: plan every pair, fail closed\n * on a duplicated destination, round-trip the overwrite confirmations as one\n * set, then execute what survived in parallel. `copy` and `move` differ only in\n * their `plan` and `execute` callbacks.\n */\nasync function runTransfers(\n items: readonly z.infer<typeof MoveItemSchema>[],\n ctx: ToolCtx,\n op: PairOp,\n overwrite: boolean,\n): Promise<\n | { results: MoveItemResult[]; skipped: string[]; failures: MoveFailureItem[] }\n | InputRequiredResult\n> {\n const { results: planned, errors: planErrors } = await processInParallel(\n items,\n (pair) => planTransfer(op, pair, ctx.fs, overwrite),\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n // plan callbacks are fail-closed (they return { status: 'fail' }, never\n // throw), so planErrors should always be empty. Surface the first rather\n // than silently dropping the item if a future plan callback violates that.\n const firstPlanError = planErrors[0];\n if (firstPlanError) throw firstPlanError.error;\n\n const failures: MoveFailureItem[] = [];\n const candidates: TransferPlan[] = [];\n for (const { value } of planned) {\n if (value.status === 'fail') failures.push(value.failure);\n else if (value.status === 'plan') candidates.push(value.plan);\n // 'noop' (self-copy / self-move) is silently skipped.\n }\n\n // Two sources targeting the same destination in one batch would otherwise\n // collapse to a single shared overwrite confirmation and let the second\n // one silently clobber the first's freshly-written content. Fail closed:\n // only the first plan per destination proceeds; later ones targeting the\n // same destination are reported as a per-item failure.\n const seenDest = new Set<string>();\n const ready: TransferPlan[] = [];\n for (const candidate of candidates) {\n const destKey = IS_CASE_INSENSITIVE_FS\n ? candidate.validDest.toLowerCase()\n : candidate.validDest;\n if (seenDest.has(destKey)) {\n failures.push(\n pairFailure(\n candidate.pair,\n new FsError(\n ErrorCode.INVALID_INPUT,\n `${VERB[op]} cancelled: another entry in this batch already targets destination \"${candidate.pair.destination}\"`,\n candidate.pair.destination,\n ),\n ),\n );\n continue;\n }\n seenDest.add(destKey);\n ready.push(candidate);\n }\n\n const pendingSorted = ready\n .filter((p) => p.pending)\n .map((p) => p.validDest)\n .sort();\n if (pendingSorted.length > 0) {\n // Round 1 returns input_required; a retry whose verified state does not\n // bind this overwrite set throws (R9) via `pendingRoundTrip`.\n const round = await pendingRoundTrip({\n op,\n pending: pendingSorted,\n requestState: ctx.requestState,\n clientCapabilities: ctx.clientCapabilities,\n buildInputs: (dests) =>\n dests.map((dest, i) =>\n choiceInput(\n `confirm_${i}`,\n op === 'move'\n ? `\"${dest}\" already exists. Overwrite it?`\n : `Destination \"${dest}\" already exists. Overwrite it?`,\n [\n { value: 'overwrite', title: 'Overwrite' },\n { value: 'skip', title: 'Skip' },\n ],\n ),\n ),\n });\n if (round !== undefined) return round;\n }\n\n const total = ready.length;\n let completed = 0;\n const tick = (): void => {\n completed += 1;\n ctx.onProgress?.({ current: completed, total });\n };\n const { results: execResults, errors: execErrors } = await processInParallel(\n ready,\n async (readyPlan) => {\n try {\n return await executeTransfer(op, readyPlan, ctx, pendingSorted);\n } finally {\n tick();\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n for (const { error, index } of execErrors) {\n rethrowIfAborted(error);\n const failed = ready[index];\n if (failed) failures.push(pairFailure(failed.pair, error));\n }\n\n const results: MoveItemResult[] = [];\n const skipped: string[] = [];\n for (const { value } of execResults) {\n if ('skipped' in value) skipped.push(value.skipped);\n else results.push(value.value);\n }\n\n return { results, skipped, failures };\n}\n\n/**\n * Two-phase move: pre-check every move (no mutation) to build the overwrite\n * pending set; if any move is pending and the round carries no verified\n * `requestState`, return `input_required` moving nothing (R14). On retry, R9\n * checks the state binds this move set, then each pending move proceeds only on\n * an accepted overwrite. Non-pending moves proceed alongside them.\n */\nasync function handleMove(\n args: z.infer<typeof MoveInputSchema>,\n ctx: ToolCtx,\n): Promise<z.infer<typeof MoveOutputSchema> | InputRequiredResult> {\n const op: PairOp = args.copy ? 'copy' : 'move';\n // `overwrite` is copy-only; move has no confirmation bypass.\n const overwrite = args.copy && args.overwrite;\n const outcome = await runTransfers(args.moves, ctx, op, overwrite);\n if (isInputRequiredResult(outcome)) return outcome;\n\n const { results, skipped, failures } = outcome;\n\n return {\n moves: results,\n ...(failures.length > 0 ? { failures } : {}),\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n}\n\nfunction buildSummary(\n verb: 'move' | 'copy',\n results: readonly MoveItemResult[],\n failures: readonly MoveFailureItem[],\n skipped: readonly string[],\n): string {\n const successCount = results.length;\n const failCount = failures.length;\n if (failCount === 0 && skipped.length === 0 && successCount === 1) {\n const result = results[0];\n if (result) {\n return `${verb}: ${pathLabel(result.from)} → ${pathLabel(result.to)}`;\n }\n }\n // Name each pair. \"move: 2 items\" left the caller to open structuredContent\n // to learn which two, and a partial failure was unreadable without it.\n // Skipped destinations are an outcome the user chose, not a failure — name\n // them, or an all-skipped call reads as \"copy: nothing\".\n const tokens = [\n ...results.map((r) => `${pathLabel(r.from)} → ${pathLabel(r.to)}`),\n ...skipped.map((dest) => `${pathLabel(dest)} SKIPPED`),\n ];\n const parts = [`${verb}: ${joinRoster(tokens) || 'nothing'}`];\n if (failCount > 0) parts.push(`${String(failCount)} failed`);\n return parts.join(' · ');\n}\n\ninterface TransferSource {\n /** Symlink resolved — the identity used for every same-target comparison. */\n realSource: string;\n /** The path the mutation runs on: symlink preserved for move, resolved for copy. */\n opSource: string;\n}\n\n/**\n * A move needs two views of its source. Renaming must operate on the link\n * itself, or moving a symlink would rename the file it points at and leave the\n * link dangling. Every collision check must instead compare the resolved\n * target, or a link moved onto itself (or onto its own target) reads as a real\n * move and renames the link over that target, destroying it. A copy reads\n * through the link, so both views are the resolved one.\n */\nasync function validateTransferSource(\n op: PairOp,\n source: string,\n fs: ToolCtx['fs'],\n): Promise<TransferSource> {\n try {\n const realSource = await fs.pathGuard.validateExistingPath(source);\n const opSource = op === 'move' ? await fs.pathGuard.validatePathForDelete(source) : realSource;\n return { realSource, opSource };\n } catch (error) {\n if (isFsError(error)) throw error;\n throw new FsError(ErrorCode.ACCESS_DENIED, `${VERB[op]} failed for ${source}`, source);\n }\n}\n\nasync function performRenameWithFallback(\n validSource: string,\n validDest: string,\n fsOps: Pick<GuardedFileSystem, 'rename' | 'cp' | 'rm'>,\n originalSource: string,\n): Promise<void> {\n try {\n await fsOps.rename(validSource, validDest);\n } catch (error: unknown) {\n rethrowIfAborted(error);\n\n if (!isNodeError(error) || error.code !== 'EXDEV') {\n throw error;\n }\n\n // EXDEV: cross-device rename. Copy then remove, preserving symlinks and\n // timestamps so link semantics survive the move.\n let copied = false;\n try {\n await fsOps.cp(validSource, validDest, {\n recursive: true,\n verbatimSymlinks: true,\n preserveTimestamps: true,\n });\n copied = true;\n await fsOps.rm(validSource, { recursive: true, force: true });\n } catch (copyOrRemoveError) {\n rethrowIfAborted(copyOrRemoveError);\n if (copied) {\n // cp succeeded but rm failed: the destination already holds a complete\n // copy and the source remains. Surface the rm error as the cause so the\n // caller can recover (clean up the duplicate) instead of a silent generic\n // failure that hides the partial completion.\n const baseProblem = Problem.fromUnknown(\n copyOrRemoveError,\n ErrorCode.UNKNOWN,\n originalSource,\n );\n throw new FsError(\n baseProblem.code,\n `Cross-device move of ${originalSource}: copy succeeded but source removal failed (destination holds a copy): ${baseProblem.message}`,\n originalSource,\n copyOrRemoveError,\n );\n }\n throw copyOrRemoveError;\n }\n }\n}\n\nexport const MOVE = defineTool({\n name: 'move',\n title: 'Move or Copy Files',\n description:\n 'Move, rename, or copy files and directories to explicit destination paths (max 100 operations per call). ' +\n 'Pass moves: [{ source, destination }] — there is no single-pair form. ' +\n 'Parent directories are created automatically. Set copy=true to copy instead of move (sources are kept). ' +\n 'An existing destination prompts the user to confirm the overwrite, so the call returns without moving ' +\n 'anything until that confirmation comes back; copy=true with overwrite=true skips the prompt, move has no ' +\n 'such bypass. Self-moves are silently skipped.',\n input: MoveInputSchema,\n output: MoveOutputSchema,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n progress: (args) => {\n const label = args.copy ? 'Copy' : 'Move';\n if (args.moves.length === 1) {\n const move = args.moves[0];\n return {\n label,\n subject: `${basename(move?.source ?? '')} → ${basename(move?.destination ?? '')}`,\n };\n }\n return { label, subject: `${String(args.moves.length)} files` };\n },\n defaultErrorCode: ErrorCode.UNKNOWN,\n accessPaths: (args) => args.moves.flatMap((m) => [m.source, m.destination]),\n run: async (args, ctx) => {\n const output = await handleMove(args, ctx);\n // input_required is a return value, not a completed call: surface it\n // verbatim so the executor short-circuits before building a CallToolResult.\n if (isInputRequiredResult(output)) return output;\n const failures = output.failures ?? [];\n const skipped = output.skipped ?? [];\n return {\n structured: output,\n text: buildSummary(args.copy ? 'copy' : 'move', output.moves, failures, skipped),\n // Every requested pair failed: no move, no copy, no user-chosen skip - the\n // call did nothing. A skip is work the caller asked for, so it counts.\n isError: failures.length > 0 && output.moves.length + skipped.length === 0,\n };\n },\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../../src/tools/patch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../../src/tools/patch.ts"],"names":[],"mappings":"AA0JA,eAAO,MAAM,KAAK,mCAsBhB,CAAC"}
|
package/dist/tools/patch.js
CHANGED
|
@@ -2,9 +2,7 @@ import { basename } from 'node:path';
|
|
|
2
2
|
import * as z from 'zod/v4';
|
|
3
3
|
import { applyPatch, parsePatch } from 'diff';
|
|
4
4
|
import { ErrorCode, FsError } from '../core/errors.js';
|
|
5
|
-
import {
|
|
6
|
-
import { detectMimeFromContent } from '../core/mime.js';
|
|
7
|
-
import { countLines } from '../core/read.js';
|
|
5
|
+
import { buildWrittenFileMeta } from '../core/file-uri.js';
|
|
8
6
|
import { defaultFalseBoolean, FileKind, IsoDateTime, NonNegInt, RequiredPath, } from '../core/schema.js';
|
|
9
7
|
import { defineTool } from './define.js';
|
|
10
8
|
const PatchInputSchema = z.strictObject({
|
|
@@ -27,22 +25,6 @@ const PatchOutputSchema = z.strictObject({
|
|
|
27
25
|
linesRemoved: NonNegInt.describe('Number of lines removed by the patch'),
|
|
28
26
|
diff: z.string().optional().describe('Unified diff preview (present only in dryRun mode)'),
|
|
29
27
|
});
|
|
30
|
-
function buildPatchMeta(validPath, patched, resourceStore) {
|
|
31
|
-
const bytesWritten = Buffer.byteLength(patched, 'utf-8');
|
|
32
|
-
const mimeInfo = detectMimeFromContent(validPath, patched);
|
|
33
|
-
const resourceUri = buildFileResourceUri(validPath);
|
|
34
|
-
const link = resourceStore !== undefined
|
|
35
|
-
? buildFileResourceLink(validPath, mimeInfo.mimeType, bytesWritten)
|
|
36
|
-
: undefined;
|
|
37
|
-
return {
|
|
38
|
-
size: bytesWritten,
|
|
39
|
-
lineCount: countLines(patched),
|
|
40
|
-
mimeType: mimeInfo.mimeType,
|
|
41
|
-
kind: mimeInfo.kind,
|
|
42
|
-
resourceUri,
|
|
43
|
-
link,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
28
|
// Hunk lines are the raw unified lines with their +/-/ leading prefix (no
|
|
47
29
|
// +++/--- file headers inside hunks), so the first char is a reliable marker.
|
|
48
30
|
function countAddedRemoved(parsedPatch) {
|
|
@@ -87,7 +69,7 @@ async function handlePatch(args, ctx) {
|
|
|
87
69
|
// follow-up `read` when the caller actually wants the bytes.
|
|
88
70
|
const summaryText = `patch: ${basename(args.path)} +${String(linesAdded)} -${String(linesRemoved)}`;
|
|
89
71
|
if (args.dryRun) {
|
|
90
|
-
const meta =
|
|
72
|
+
const meta = buildWrittenFileMeta(validPath, patched, ctx.resourceStore);
|
|
91
73
|
return {
|
|
92
74
|
structured: {
|
|
93
75
|
path: validPath,
|
|
@@ -104,7 +86,7 @@ async function handlePatch(args, ctx) {
|
|
|
104
86
|
// A dry run exists to be previewed, so it keeps returning the patched
|
|
105
87
|
// text; only the write path summarizes, where the bytes are on disk.
|
|
106
88
|
text: patched,
|
|
107
|
-
...(meta.
|
|
89
|
+
...(meta.resourceLink !== undefined ? { resources: [meta.resourceLink] } : {}),
|
|
108
90
|
};
|
|
109
91
|
}
|
|
110
92
|
await ctx.fs.writeFile(args.path, patched, { encoding: 'utf-8', signal: ctx.signal });
|
|
@@ -113,7 +95,7 @@ async function handlePatch(args, ctx) {
|
|
|
113
95
|
// writer it may reflect that writer's mtime while `size`/content come from this
|
|
114
96
|
// patch's atomic write. The file content itself is always consistent.
|
|
115
97
|
const { stats: fileStats } = await ctx.fs.stat(args.path, { signal: ctx.signal });
|
|
116
|
-
const meta =
|
|
98
|
+
const meta = buildWrittenFileMeta(validPath, patched, ctx.resourceStore);
|
|
117
99
|
return {
|
|
118
100
|
structured: {
|
|
119
101
|
path: validPath,
|
|
@@ -127,7 +109,7 @@ async function handlePatch(args, ctx) {
|
|
|
127
109
|
linesRemoved,
|
|
128
110
|
},
|
|
129
111
|
text: summaryText,
|
|
130
|
-
...(meta.
|
|
112
|
+
...(meta.resourceLink !== undefined ? { resources: [meta.resourceLink] } : {}),
|
|
131
113
|
};
|
|
132
114
|
}
|
|
133
115
|
export const PATCH = defineTool({
|