@hheei/pi-apply-patch 0.0.0-stage → 0.1.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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -2
  3. package/THIRD_PARTY_NOTICES.md +3 -0
  4. package/dist/apply-patch/executor.d.ts +15 -0
  5. package/dist/apply-patch/executor.js +407 -0
  6. package/dist/apply-patch/fs.d.ts +37 -0
  7. package/dist/apply-patch/fs.js +186 -0
  8. package/dist/apply-patch/glyph.d.ts +8 -0
  9. package/dist/apply-patch/glyph.js +23 -0
  10. package/dist/apply-patch/index.d.ts +6 -0
  11. package/dist/apply-patch/index.js +5 -0
  12. package/dist/apply-patch/jsdiff-matching.d.ts +13 -0
  13. package/dist/apply-patch/jsdiff-matching.js +613 -0
  14. package/dist/apply-patch/jsdiff-worker.d.ts +1 -0
  15. package/dist/apply-patch/jsdiff-worker.js +18 -0
  16. package/dist/apply-patch/jsdiff.d.ts +9 -0
  17. package/dist/apply-patch/jsdiff.js +219 -0
  18. package/dist/apply-patch/outcome.d.ts +84 -0
  19. package/dist/apply-patch/outcome.js +1 -0
  20. package/dist/apply-patch/parser.d.ts +85 -0
  21. package/dist/apply-patch/parser.js +486 -0
  22. package/dist/apply-patch/paths.d.ts +5 -0
  23. package/dist/apply-patch/paths.js +23 -0
  24. package/dist/apply-patch/policy.d.ts +11 -0
  25. package/dist/apply-patch/policy.js +44 -0
  26. package/dist/apply-patch/presentation.d.ts +7 -0
  27. package/dist/apply-patch/presentation.js +127 -0
  28. package/dist/apply-patch/settings.d.ts +5 -0
  29. package/dist/apply-patch/settings.js +33 -0
  30. package/dist/apply-patch/tool.d.ts +18 -0
  31. package/dist/apply-patch/tool.js +178 -0
  32. package/dist/apply-patch/view.d.ts +34 -0
  33. package/dist/apply-patch/view.js +256 -0
  34. package/dist/apply-patch-tool.d.ts +1 -0
  35. package/dist/apply-patch-tool.js +1 -0
  36. package/dist/extension.d.ts +2 -0
  37. package/dist/extension.js +22 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.js +1 -0
  40. package/package.json +57 -5
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hheei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,9 @@
1
- # Temporary Holding Version
1
+ # @hheei/pi-apply-patch
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Standalone `apply_patch` extension for Pi. Install with `pi install npm:@hheei/pi-apply-patch`. The tool is registered with `defaultActive: false`; enable it through your loadout.
4
+
5
+ Owns V4A parsing, pure JavaScript jsdiff worker matching, per-path publishing, partial outcomes and the `applyPatch` group in `ext_settings.json`. Confirmed edits persist after failure or cancellation. File mutation locks are shared with `edit` and `write` through ext-core.
6
+
7
+ No file tools, Bash, Python or UI extension is required. Optional UI rendering comes through ext-core; native tool text remains available without it. Worker code is included in `dist`.
8
+
9
+ See [architecture](../../docs/architecture/apply-patch.md) and [tool packages](../../docs/ext-tools/README.md).
@@ -0,0 +1,3 @@
1
+ # Third-Party Notices
2
+
3
+ No third-party code is vendored in this package.
@@ -0,0 +1,15 @@
1
+ import { type PatchFs } from "./fs.js";
2
+ import type { ApplyPatchInWorkspaceResult, ApplyPatchProgress } from "./outcome.js";
3
+ import { type V4aPatch } from "./parser.js";
4
+ import type { ApplyPatchPolicy } from "./policy.js";
5
+ export interface ApplyPatchInWorkspaceOptions {
6
+ readonly workspaceRoot: string;
7
+ readonly patch: string;
8
+ readonly parsedPatch?: V4aPatch;
9
+ readonly policy: ApplyPatchPolicy;
10
+ readonly signal?: AbortSignal;
11
+ readonly onProgress?: (progress: ApplyPatchProgress) => void;
12
+ readonly fs?: PatchFs;
13
+ readonly lockKey?: string;
14
+ }
15
+ export declare function applyPatchInWorkspace(options: ApplyPatchInWorkspaceOptions): Promise<ApplyPatchInWorkspaceResult>;
@@ -0,0 +1,407 @@
1
+ import { acquireMutationLock, errorMessage } from "@hheei/pi-ext-core";
2
+ import { APPLY_PATCH_MAX_FILE_SIZE, createLocalPatchFs, FsTransportError, fileTooLarge, gcAgentPatchTemps, publishPreparedFile, sftpTimeoutMs, } from "./fs.js";
3
+ import { runJsDiffUpdate } from "./jsdiff.js";
4
+ import { findV4aPatchConflicts, operationTouchedPaths, parseV4aPatch, } from "./parser.js";
5
+ import { assertPatchPath } from "./paths.js";
6
+ const SNAPSHOT_MAX_LINES = 150;
7
+ class PatchUpdateError extends Error {
8
+ diagnostics;
9
+ constructor(message, diagnostics) {
10
+ super(message);
11
+ this.diagnostics = diagnostics;
12
+ }
13
+ }
14
+ function lineCount(content) {
15
+ if (content === undefined || content.length === 0)
16
+ return 0;
17
+ const text = typeof content === "string" ? content : new TextDecoder().decode(content);
18
+ const lines = text.split(/\r\n|\n|\r/);
19
+ return lines.at(-1) === "" ? lines.length - 1 : lines.length;
20
+ }
21
+ function hunkDelta(hunks) {
22
+ let addedLines = 0;
23
+ let removedLines = 0;
24
+ for (const hunk of hunks)
25
+ for (const line of hunk.lines)
26
+ if (line.kind === "add")
27
+ addedLines += 1;
28
+ else if (line.kind === "remove")
29
+ removedLines += 1;
30
+ return { addedLines, removedLines };
31
+ }
32
+ function plannedDelta(operation, content) {
33
+ if (operation.kind === "add")
34
+ return { addedLines: lineCount(operation.content), removedLines: 0 };
35
+ if (operation.kind === "delete")
36
+ return { addedLines: 0, removedLines: lineCount(content) };
37
+ return hunkDelta(operation.hunks);
38
+ }
39
+ function progressTotals(operations) {
40
+ const paths = new Set();
41
+ let addedLines = 0;
42
+ let removedLines = 0;
43
+ for (const operation of operations) {
44
+ paths.add(operation.path);
45
+ if (operation.status === "applied" ||
46
+ operation.status === "fuzzy" ||
47
+ operation.status === "partial") {
48
+ addedLines += operation.addedLines;
49
+ removedLines += operation.removedLines;
50
+ }
51
+ }
52
+ return { files: paths.size, addedLines, removedLines };
53
+ }
54
+ function splitTextLines(content) {
55
+ const lines = new TextDecoder().decode(content).split(/\r\n|\n|\r/);
56
+ if (lines.at(-1) === "")
57
+ lines.pop();
58
+ return Object.freeze(lines);
59
+ }
60
+ function fileSnapshot(path, before, after) {
61
+ return Object.freeze({
62
+ path,
63
+ hunkIndex: 1,
64
+ startLine: 1,
65
+ afterStartLine: 1,
66
+ before: Object.freeze((before === undefined ? [] : splitTextLines(before)).slice(0, SNAPSHOT_MAX_LINES)),
67
+ after: Object.freeze((after === undefined ? [] : splitTextLines(after)).slice(0, SNAPSHOT_MAX_LINES)),
68
+ });
69
+ }
70
+ function initialRejections(patch) {
71
+ const rejected = [];
72
+ const indices = new Set();
73
+ for (const conflict of findV4aPatchConflicts(patch)) {
74
+ const operationIndices = Object.freeze([...conflict.operationIndices]);
75
+ for (const index of operationIndices)
76
+ indices.add(index);
77
+ rejected.push(Object.freeze({
78
+ operationIndices,
79
+ paths: Object.freeze([conflict.path]),
80
+ error: conflict.message,
81
+ diagnostics: Object.freeze([]),
82
+ }));
83
+ }
84
+ return { rejected, indices };
85
+ }
86
+ function rejection(index, paths, error, diagnostics = []) {
87
+ return Object.freeze({
88
+ operationIndices: Object.freeze([index]),
89
+ paths: Object.freeze([...paths]),
90
+ error: errorMessage(error),
91
+ diagnostics: Object.freeze([...diagnostics]),
92
+ });
93
+ }
94
+ async function prepareUpdateBytes(fs, operation, policy, signal) {
95
+ const followed = await fs.statFollow(operation.path, signal);
96
+ if (followed.kind === "missing")
97
+ throw new Error(`Patch source does not exist: ${operation.path}`);
98
+ if (followed.kind !== "file")
99
+ throw new Error(`Patch path is not a regular file: ${operation.path}`);
100
+ if (followed.size > APPLY_PATCH_MAX_FILE_SIZE)
101
+ throw fileTooLarge(followed.size, operation.path);
102
+ const before = await fs.readFollow(operation.path, signal, sftpTimeoutMs(followed.size));
103
+ if (before.length > APPLY_PATCH_MAX_FILE_SIZE)
104
+ throw fileTooLarge(before.length, operation.path);
105
+ const stage = await runJsDiffUpdate({
106
+ before,
107
+ operation,
108
+ fuzzFactor: policy.fuzzFactor,
109
+ ...(signal === undefined ? {} : { signal }),
110
+ });
111
+ if (operation.hunks.length > 0 && stage.outcomes.length === 0)
112
+ throw new PatchUpdateError("One or more update hunks failed", stage.rejected);
113
+ if (stage.after.length > APPLY_PATCH_MAX_FILE_SIZE)
114
+ throw fileTooLarge(stage.after.length, operation.moveTo ?? operation.path);
115
+ return { bytes: stage.after, before, mode: followed.mode, stage };
116
+ }
117
+ function sourceChanged(path) {
118
+ return new Error(`Patch source changed during patch preparation: ${path}`);
119
+ }
120
+ async function revalidateSourceBaseline(fs, path, before, signal) {
121
+ const meta = await fs.statFollow(path, signal);
122
+ if (meta.kind !== "file" || meta.size > APPLY_PATCH_MAX_FILE_SIZE)
123
+ throw sourceChanged(path);
124
+ const current = await fs.readFollow(path, signal, sftpTimeoutMs(meta.size));
125
+ if (current.length > APPLY_PATCH_MAX_FILE_SIZE || Buffer.compare(current, before) !== 0)
126
+ throw sourceChanged(path);
127
+ }
128
+ async function revalidateDeleteBaseline(fs, path, meta, before, signal) {
129
+ const current = await fs.lstat(path, signal);
130
+ if (current.kind !== meta.kind || current.size !== meta.size || current.mode !== meta.mode)
131
+ throw sourceChanged(path);
132
+ if (before !== undefined)
133
+ await revalidateSourceBaseline(fs, path, before, signal);
134
+ }
135
+ async function applyAdd(fs, index, operation, signal) {
136
+ const existing = await fs.lstat(operation.path, signal);
137
+ if (existing.kind !== "missing")
138
+ throw new Error(`Patch add target already exists: ${operation.path}`);
139
+ const bytes = Buffer.from(operation.content, "utf8");
140
+ if (bytes.length > APPLY_PATCH_MAX_FILE_SIZE)
141
+ throw fileTooLarge(bytes.length, operation.path);
142
+ await publishPreparedFile(fs, operation.path, bytes, undefined, false, signal);
143
+ return Object.freeze({
144
+ operationIndex: index,
145
+ kind: "add",
146
+ paths: Object.freeze([operation.path]),
147
+ outcomes: Object.freeze([]),
148
+ snapshots: Object.freeze([fileSnapshot(operation.path, undefined, bytes)]),
149
+ });
150
+ }
151
+ async function applyDelete(fs, index, operation, signal) {
152
+ const meta = await fs.lstat(operation.path, signal);
153
+ if (meta.kind === "missing")
154
+ throw new Error(`Patch source does not exist: ${operation.path}`);
155
+ if (meta.kind === "directory")
156
+ throw new Error(`Patch path is not a regular file: ${operation.path}`);
157
+ if (meta.kind === "file" && meta.size > APPLY_PATCH_MAX_FILE_SIZE)
158
+ throw fileTooLarge(meta.size, operation.path);
159
+ const before = meta.kind === "file"
160
+ ? await fs.readFollow(operation.path, signal, sftpTimeoutMs(meta.size))
161
+ : undefined;
162
+ if (before !== undefined && before.length > APPLY_PATCH_MAX_FILE_SIZE)
163
+ throw fileTooLarge(before.length, operation.path);
164
+ await revalidateDeleteBaseline(fs, operation.path, meta, before, signal);
165
+ await fs.unlink(operation.path, signal);
166
+ return {
167
+ outcome: Object.freeze({
168
+ operationIndex: index,
169
+ kind: "delete",
170
+ paths: Object.freeze([operation.path]),
171
+ outcomes: Object.freeze([]),
172
+ snapshots: Object.freeze([fileSnapshot(operation.path, before, undefined)]),
173
+ }),
174
+ before,
175
+ };
176
+ }
177
+ async function applyUpdate(fs, index, operation, policy, signal) {
178
+ const prepared = await prepareUpdateBytes(fs, operation, policy, signal);
179
+ const dest = operation.moveTo ?? (await fs.followLeaf(operation.path, signal));
180
+ if (operation.moveTo !== undefined) {
181
+ const destMeta = await fs.lstat(operation.moveTo, signal);
182
+ if (destMeta.kind !== "missing")
183
+ throw new Error(`Patch move destination already exists: ${operation.moveTo}`);
184
+ }
185
+ await publishPreparedFile(fs, dest, prepared.bytes, prepared.mode, operation.moveTo === undefined, signal, () => revalidateSourceBaseline(fs, operation.path, prepared.before, signal));
186
+ const destPaths = operation.moveTo === undefined ? [operation.path] : [operation.moveTo];
187
+ const outcome = Object.freeze({
188
+ operationIndex: index,
189
+ kind: "update",
190
+ paths: Object.freeze(destPaths),
191
+ outcomes: Object.freeze([...prepared.stage.outcomes]),
192
+ snapshots: Object.freeze([...prepared.stage.snapshots]),
193
+ });
194
+ const result = {
195
+ outcome,
196
+ mode: prepared.stage.mode,
197
+ rejectedHunks: prepared.stage.rejected,
198
+ prepared: prepared.stage,
199
+ };
200
+ if (operation.moveTo === undefined)
201
+ return result;
202
+ try {
203
+ await revalidateSourceBaseline(fs, operation.path, prepared.before, signal);
204
+ await fs.unlink(operation.path, signal);
205
+ return {
206
+ ...result,
207
+ outcome: Object.freeze({
208
+ ...outcome,
209
+ paths: Object.freeze([operation.path, operation.moveTo]),
210
+ }),
211
+ };
212
+ }
213
+ catch (sourceError) {
214
+ return {
215
+ ...result,
216
+ sourceError,
217
+ sourceUnknown: sourceError instanceof FsTransportError && sourceError.phase !== "write",
218
+ };
219
+ }
220
+ }
221
+ export async function applyPatchInWorkspace(options) {
222
+ options.signal?.throwIfAborted();
223
+ const fs = options.fs ?? createLocalPatchFs(options.workspaceRoot);
224
+ const patch = options.parsedPatch ?? parseV4aPatch(options.patch);
225
+ for (const operation of patch.operations)
226
+ for (const path of operationTouchedPaths(operation))
227
+ assertPatchPath(path);
228
+ const initial = initialRejections(patch);
229
+ const rejected = [...initial.rejected];
230
+ const unconfirmed = [];
231
+ const notApplied = [];
232
+ const applied = [];
233
+ const rejectedIndices = new Set(initial.indices);
234
+ let exactUpdateCount = 0;
235
+ let fuzzyUpdateCount = 0;
236
+ let halt;
237
+ const progressOperations = patch.operations.map((operation, index) => ({
238
+ operationIndex: index,
239
+ kind: operation.kind,
240
+ path: operation.kind === "update" ? (operation.moveTo ?? operation.path) : operation.path,
241
+ ...plannedDelta(operation, undefined),
242
+ status: rejectedIndices.has(index) ? "rejected" : "pending",
243
+ }));
244
+ const setStatus = (index, status, outcome, update, partialReason, prepared) => {
245
+ const current = progressOperations[index];
246
+ if (current === undefined)
247
+ throw new Error(`Missing patch progress operation: ${index}`);
248
+ progressOperations[index] = Object.freeze({
249
+ ...current,
250
+ ...(update === undefined
251
+ ? {}
252
+ : update.kind !== "update" || prepared === undefined
253
+ ? plannedDelta(update, undefined)
254
+ : { addedLines: prepared.addedLines, removedLines: prepared.removedLines }),
255
+ status,
256
+ ...(status === "partial" && update !== undefined && outcome !== undefined
257
+ ? {
258
+ appliedHunks: outcome.outcomes.length,
259
+ totalHunks: update.hunks.length,
260
+ ...(partialReason === undefined ? {} : { partialReason }),
261
+ }
262
+ : {}),
263
+ });
264
+ };
265
+ const emit = (stage) => {
266
+ const totals = progressTotals(progressOperations);
267
+ options.onProgress?.(Object.freeze({
268
+ stage,
269
+ ...totals,
270
+ operations: Object.freeze([...progressOperations]),
271
+ }));
272
+ };
273
+ const markNotApplied = (index, operation) => {
274
+ setStatus(index, "not_applied");
275
+ notApplied.push(rejection(index, operationTouchedPaths(operation), "NotApplied"));
276
+ };
277
+ const release = await acquireMutationLock(options.lockKey ?? fs.scope, options.signal);
278
+ try {
279
+ await gcAgentPatchTemps(fs, patch.operations.flatMap((operation) => operationTouchedPaths(operation).map((path) => {
280
+ const separator = path.lastIndexOf("/");
281
+ return separator < 0 ? "." : path.slice(0, separator);
282
+ })), options.signal);
283
+ emit("parsed");
284
+ for (const [index, operation] of patch.operations.entries()) {
285
+ if (rejectedIndices.has(index))
286
+ continue;
287
+ if (halt !== undefined) {
288
+ markNotApplied(index, operation);
289
+ continue;
290
+ }
291
+ if (options.signal?.aborted) {
292
+ if (applied.length === 0 && unconfirmed.length === 0)
293
+ throw options.signal.reason ?? new Error("aborted");
294
+ halt = "not_applied";
295
+ markNotApplied(index, operation);
296
+ continue;
297
+ }
298
+ try {
299
+ if (operation.kind === "add") {
300
+ const outcome = await applyAdd(fs, index, operation, options.signal);
301
+ applied.push(outcome);
302
+ setStatus(index, "applied", outcome);
303
+ }
304
+ else if (operation.kind === "delete") {
305
+ const deleted = await applyDelete(fs, index, operation, options.signal);
306
+ applied.push(deleted.outcome);
307
+ const current = progressOperations[index];
308
+ if (current !== undefined) {
309
+ progressOperations[index] = Object.freeze({
310
+ ...current,
311
+ ...plannedDelta(operation, deleted.before),
312
+ status: "applied",
313
+ });
314
+ }
315
+ }
316
+ else {
317
+ const published = await applyUpdate(fs, index, operation, options.policy, options.signal);
318
+ if (published.mode === "exact")
319
+ exactUpdateCount += 1;
320
+ if (published.mode === "fuzzy")
321
+ fuzzyUpdateCount += 1;
322
+ applied.push(published.outcome);
323
+ const partialReason = published.rejectedHunks.length === 0
324
+ ? undefined
325
+ : published.rejectedHunks[0]?.kind === "context_not_found"
326
+ ? "context not found"
327
+ : "exact context is ambiguous";
328
+ if (published.rejectedHunks.length > 0)
329
+ rejected.push(rejection(index, operationTouchedPaths(operation), "One or more update hunks failed", published.rejectedHunks));
330
+ if (published.sourceError !== undefined) {
331
+ if (published.sourceUnknown === true) {
332
+ unconfirmed.push(rejection(index, [operation.path], published.sourceError));
333
+ setStatus(index, "unconfirmed", published.outcome, operation, undefined, published.prepared);
334
+ halt = "unconfirmed";
335
+ }
336
+ else {
337
+ rejected.push(rejection(index, [operation.path], published.sourceError));
338
+ setStatus(index, "partial", published.outcome, operation, partialReason ?? String(published.sourceError), published.prepared);
339
+ }
340
+ }
341
+ else {
342
+ setStatus(index, partialReason === undefined
343
+ ? published.mode === "fuzzy"
344
+ ? "fuzzy"
345
+ : "applied"
346
+ : "partial", published.outcome, operation, partialReason, published.prepared);
347
+ }
348
+ }
349
+ }
350
+ catch (error) {
351
+ if (options.signal?.aborted && applied.length === 0 && unconfirmed.length === 0)
352
+ throw options.signal.reason ?? error;
353
+ if (error instanceof FsTransportError) {
354
+ const bucket = error.phase === "write" ? notApplied : unconfirmed;
355
+ const status = error.phase === "write" ? "not_applied" : "unconfirmed";
356
+ bucket.push(rejection(index, operationTouchedPaths(operation), error));
357
+ setStatus(index, status);
358
+ halt = status;
359
+ }
360
+ else if (error instanceof PatchUpdateError) {
361
+ rejected.push(rejection(index, operationTouchedPaths(operation), error, error.diagnostics));
362
+ rejectedIndices.add(index);
363
+ setStatus(index, "rejected");
364
+ }
365
+ else if (options.signal?.aborted) {
366
+ notApplied.push(rejection(index, operationTouchedPaths(operation), error));
367
+ setStatus(index, "not_applied");
368
+ halt = "not_applied";
369
+ }
370
+ else {
371
+ rejected.push(rejection(index, operationTouchedPaths(operation), error));
372
+ rejectedIndices.add(index);
373
+ setStatus(index, "rejected");
374
+ }
375
+ }
376
+ emit("publishing");
377
+ await new Promise((resolve) => setImmediate(resolve));
378
+ }
379
+ if (halt !== undefined) {
380
+ for (const [index, operation] of patch.operations.entries()) {
381
+ if (progressOperations[index]?.status !== "pending")
382
+ continue;
383
+ markNotApplied(index, operation);
384
+ }
385
+ }
386
+ emit("done");
387
+ const totals = progressTotals(progressOperations);
388
+ return Object.freeze({
389
+ changedPaths: Object.freeze([
390
+ ...new Set(applied.flatMap((operation) => [...operation.paths])),
391
+ ]),
392
+ addedLines: totals.addedLines,
393
+ removedLines: totals.removedLines,
394
+ operations: Object.freeze([...progressOperations]),
395
+ operationCount: patch.operations.length,
396
+ exactUpdateCount,
397
+ fuzzyUpdateCount,
398
+ applied: Object.freeze(applied),
399
+ rejected: Object.freeze(rejected),
400
+ unconfirmed: Object.freeze(unconfirmed),
401
+ notApplied: Object.freeze(notApplied),
402
+ });
403
+ }
404
+ finally {
405
+ await release();
406
+ }
407
+ }
@@ -0,0 +1,37 @@
1
+ export declare const APPLY_PATCH_MAX_FILE_SIZE: number;
2
+ export declare const SFTP_SMALL_BYTES = 1048576;
3
+ export declare const SFTP_SMALL_TIMEOUT_MS = 30000;
4
+ export declare const SFTP_LARGE_TIMEOUT_MS = 60000;
5
+ export type EntryKind = "missing" | "file" | "symlink" | "directory" | "other";
6
+ export interface EntryMeta {
7
+ readonly kind: EntryKind;
8
+ readonly size: number;
9
+ readonly mode?: number;
10
+ }
11
+ export type PublishPhase = "write" | "replace" | "remove";
12
+ export declare class FsTransportError extends Error {
13
+ readonly phase: PublishPhase;
14
+ readonly timedOut: boolean;
15
+ constructor(phase: PublishPhase, timedOut: boolean, message: string);
16
+ }
17
+ export interface PatchFs {
18
+ readonly scope: string;
19
+ lstat(path: string, signal?: AbortSignal): Promise<EntryMeta>;
20
+ statFollow(path: string, signal?: AbortSignal): Promise<EntryMeta>;
21
+ followLeaf(path: string, signal?: AbortSignal): Promise<string>;
22
+ readFollow(path: string, signal?: AbortSignal, timeoutMs?: number): Promise<Uint8Array>;
23
+ mkdirp(path: string, signal?: AbortSignal): Promise<void>;
24
+ writeAtomic(path: string, data: Uint8Array, mode: number | undefined, signal?: AbortSignal): Promise<void>;
25
+ renameNew(from: string, to: string, signal?: AbortSignal): Promise<void>;
26
+ replace(from: string, to: string, signal?: AbortSignal): Promise<void>;
27
+ unlink(path: string, signal?: AbortSignal): Promise<void>;
28
+ list(path: string, signal?: AbortSignal): Promise<readonly string[]>;
29
+ }
30
+ export declare function sftpTimeoutMs(bytes: number): number;
31
+ export declare function agentPatchTempName(base: string): string;
32
+ export declare function isAgentPatchTemp(name: string): boolean;
33
+ export declare function fileTooLarge(size: number, path: string): Error;
34
+ /** Installs fully prepared bytes through one sibling temporary file. */
35
+ export declare function publishPreparedFile(fs: PatchFs, path: string, data: Uint8Array, mode: number | undefined, replaceExisting: boolean, signal?: AbortSignal, beforePublish?: () => Promise<void>): Promise<void>;
36
+ export declare function createLocalPatchFs(workspaceRoot: string): PatchFs;
37
+ export declare function gcAgentPatchTemps(fs: PatchFs, directories: readonly string[], signal?: AbortSignal): Promise<void>;