@caupulican/pi-adaptative 0.85.3 → 0.85.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +2 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -5
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +76 -87
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js +3 -0
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.d.ts +2 -0
- package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.js +4 -3
- package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +4 -0
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +2 -0
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/background-tool-task-controller.d.ts +12 -7
- package/dist/core/background-tool-task-controller.d.ts.map +1 -1
- package/dist/core/background-tool-task-controller.js +55 -19
- package/dist/core/background-tool-task-controller.js.map +1 -1
- package/dist/core/bedrock-scope.d.ts +26 -0
- package/dist/core/bedrock-scope.d.ts.map +1 -0
- package/dist/core/bedrock-scope.js +178 -0
- package/dist/core/bedrock-scope.js.map +1 -0
- package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
- package/dist/core/delegation/worker-delegation-controller.js +6 -0
- package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +3 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/foreground-recovery-controller.d.ts +25 -2
- package/dist/core/foreground-recovery-controller.d.ts.map +1 -1
- package/dist/core/foreground-recovery-controller.js +111 -2
- package/dist/core/foreground-recovery-controller.js.map +1 -1
- package/dist/core/foreground-terminal-handoff-controller.d.ts +27 -0
- package/dist/core/foreground-terminal-handoff-controller.d.ts.map +1 -0
- package/dist/core/foreground-terminal-handoff-controller.js +56 -0
- package/dist/core/foreground-terminal-handoff-controller.js.map +1 -0
- package/dist/core/goal-loop-controller.d.ts.map +1 -1
- package/dist/core/goal-loop-controller.js +5 -0
- package/dist/core/goal-loop-controller.js.map +1 -1
- package/dist/core/goals/goal-auto-continue-controller.d.ts +3 -0
- package/dist/core/goals/goal-auto-continue-controller.d.ts.map +1 -1
- package/dist/core/goals/goal-auto-continue-controller.js +28 -7
- package/dist/core/goals/goal-auto-continue-controller.js.map +1 -1
- package/dist/core/human-input-controller.d.ts +9 -0
- package/dist/core/human-input-controller.d.ts.map +1 -1
- package/dist/core/human-input-controller.js +60 -5
- package/dist/core/human-input-controller.js.map +1 -1
- package/dist/core/human-input.d.ts +12 -0
- package/dist/core/human-input.d.ts.map +1 -1
- package/dist/core/human-input.js +59 -12
- package/dist/core/human-input.js.map +1 -1
- package/dist/core/local-runtime-controller.d.ts.map +1 -1
- package/dist/core/local-runtime-controller.js +15 -9
- package/dist/core/local-runtime-controller.js.map +1 -1
- package/dist/core/memory/providers/file-store.d.ts +1 -0
- package/dist/core/memory/providers/file-store.d.ts.map +1 -1
- package/dist/core/memory/providers/file-store.js +252 -125
- package/dist/core/memory/providers/file-store.js.map +1 -1
- package/dist/core/model-capability.d.ts +15 -1
- package/dist/core/model-capability.d.ts.map +1 -1
- package/dist/core/model-capability.js +24 -1
- package/dist/core/model-capability.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +18 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-router-controller.d.ts +6 -6
- package/dist/core/model-router-controller.d.ts.map +1 -1
- package/dist/core/model-router-controller.js +5 -5
- package/dist/core/model-router-controller.js.map +1 -1
- package/dist/core/models/context-sizing.d.ts +1 -0
- package/dist/core/models/context-sizing.d.ts.map +1 -1
- package/dist/core/models/context-sizing.js +4 -1
- package/dist/core/models/context-sizing.js.map +1 -1
- package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
- package/dist/core/models/default-model-suggestions.js +20 -10
- package/dist/core/models/default-model-suggestions.js.map +1 -1
- package/dist/core/models/llamacpp-runtime.d.ts +32 -11
- package/dist/core/models/llamacpp-runtime.d.ts.map +1 -1
- package/dist/core/models/llamacpp-runtime.js +79 -28
- package/dist/core/models/llamacpp-runtime.js.map +1 -1
- package/dist/core/models/local-execution-planner.d.ts +24 -0
- package/dist/core/models/local-execution-planner.d.ts.map +1 -0
- package/dist/core/models/local-execution-planner.js +119 -0
- package/dist/core/models/local-execution-planner.js.map +1 -0
- package/dist/core/models/local-inference-profile.d.ts +34 -0
- package/dist/core/models/local-inference-profile.d.ts.map +1 -0
- package/dist/core/models/local-inference-profile.js +74 -0
- package/dist/core/models/local-inference-profile.js.map +1 -0
- package/dist/core/models/local-runtime.d.ts +23 -4
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +11 -8
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/models/managed-ollama-model.d.ts +26 -0
- package/dist/core/models/managed-ollama-model.d.ts.map +1 -0
- package/dist/core/models/managed-ollama-model.js +59 -0
- package/dist/core/models/managed-ollama-model.js.map +1 -0
- package/dist/core/models/model-ref.js +11 -11
- package/dist/core/models/model-ref.js.map +1 -1
- package/dist/core/models/prism-llamacpp-lifecycle.d.ts +10 -20
- package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -1
- package/dist/core/models/prism-llamacpp-lifecycle.js +11 -31
- package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -1
- package/dist/core/models/prism-llamacpp-server-probe.d.ts +10 -0
- package/dist/core/models/prism-llamacpp-server-probe.d.ts.map +1 -0
- package/dist/core/models/prism-llamacpp-server-probe.js +36 -0
- package/dist/core/models/prism-llamacpp-server-probe.js.map +1 -0
- package/dist/core/models/runtime-arbiter.d.ts +3 -0
- package/dist/core/models/runtime-arbiter.d.ts.map +1 -1
- package/dist/core/models/runtime-arbiter.js +2 -1
- package/dist/core/models/runtime-arbiter.js.map +1 -1
- package/dist/core/research/research-lane-controller.d.ts.map +1 -1
- package/dist/core/research/research-lane-controller.js +14 -5
- package/dist/core/research/research-lane-controller.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +5 -1
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +37 -4
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/scout-controller.d.ts +3 -2
- package/dist/core/scout-controller.d.ts.map +1 -1
- package/dist/core/scout-controller.js +1 -0
- package/dist/core/scout-controller.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +10 -8
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +13 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +59 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +5 -2
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.js +22 -10
- package/dist/core/system-prompt-builder.js.map +1 -1
- package/dist/core/system-prompt.d.ts +3 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +181 -55
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tool-protocol-controller.d.ts.map +1 -1
- package/dist/core/tool-protocol-controller.js +5 -3
- package/dist/core/tool-protocol-controller.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +2 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +9 -1
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +5 -8
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +88 -55
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-intent.d.ts +42 -15
- package/dist/core/tools/file-mutation-intent.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-intent.js +271 -77
- package/dist/core/tools/file-mutation-intent.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/write.d.ts +5 -10
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +92 -73
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.d.ts +6 -0
- package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.js +59 -12
- package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
- package/dist/modes/interactive/local-model-commands.js +51 -8
- package/dist/modes/interactive/local-model-commands.js.map +1 -1
- package/docs/providers.md +34 -10
- package/docs/tool-boundary-performance-roadmap-2026-08-02.md +21 -18
- package/examples/extensions/built-in-tool-renderer.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +1 -1
- package/examples/extensions/prompt-customizer.ts +2 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/ssh.ts +17 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +240 -260
- package/package.json +6 -6
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { constants } from "node:fs";
|
|
3
|
-
import { access, copyFile, lstat, open, stat, unlink } from "node:fs/promises";
|
|
4
|
-
import {
|
|
5
|
-
|
|
2
|
+
import { constants, rmSync } from "node:fs";
|
|
3
|
+
import { access, copyFile, lstat, mkdtemp, open, readFile, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
const DEFAULT_CONTENT_REFERENCE_LIMIT = 64;
|
|
7
|
-
const DEFAULT_INTENT_TTL_MS = 10 * 60 * 1000;
|
|
8
7
|
const DEFAULT_CONTENT_REFERENCE_TTL_MS = 60 * 60 * 1000;
|
|
8
|
+
const DEFAULT_MUTATION_PAYLOAD_LIMIT = 8;
|
|
9
|
+
const DEFAULT_MUTATION_PAYLOAD_BYTE_LIMIT = 16 * 1024 * 1024;
|
|
10
|
+
const DEFAULT_MUTATION_PAYLOAD_TTL_MS = 10 * 60 * 1000;
|
|
9
11
|
const HASH_BUFFER_BYTES = 64 * 1024;
|
|
10
12
|
const CONTENT_REFERENCE_PREFIX = "file-content:";
|
|
13
|
+
const MUTATION_PAYLOAD_REFERENCE_PREFIX = "file-mutation:";
|
|
14
|
+
const LOCAL_MUTATION_PAYLOAD_DIRECTORY_PREFIX = "pi-file-mutation-payloads-";
|
|
15
|
+
const LOCAL_MUTATION_PAYLOAD_PROCESS_BYTE_LIMIT = 64 * 1024 * 1024;
|
|
16
|
+
const LOCAL_MUTATION_PAYLOAD_PROCESS_FILE_LIMIT = 64;
|
|
17
|
+
let localMutationPayloadDirectoryPromise;
|
|
18
|
+
let localMutationPayloadDirectory;
|
|
19
|
+
let localMutationPayloadCleanupRegistered = false;
|
|
20
|
+
let localMutationPayloadOperationTail = Promise.resolve();
|
|
21
|
+
let localMutationPayloadBytes = 0;
|
|
22
|
+
const localMutationPayloadFiles = new Map();
|
|
23
|
+
class MutationPayloadCapacityError extends Error {
|
|
24
|
+
constructor() {
|
|
25
|
+
super("Process mutation payload cache is full.");
|
|
26
|
+
this.name = "MutationPayloadCapacityError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class FileMutationPreflightError extends Error {
|
|
30
|
+
constructor(reason, message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "FileMutationPreflightError";
|
|
33
|
+
this.reason = reason;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
11
36
|
function isMissingPathError(error) {
|
|
12
37
|
return (typeof error === "object" &&
|
|
13
38
|
error !== null &&
|
|
@@ -19,10 +44,6 @@ function errorCode(error) {
|
|
|
19
44
|
? error.code
|
|
20
45
|
: undefined;
|
|
21
46
|
}
|
|
22
|
-
function normalizedPath(path) {
|
|
23
|
-
const absolutePath = resolve(path);
|
|
24
|
-
return process.platform === "win32" ? absolutePath.toLocaleLowerCase("en-US") : absolutePath;
|
|
25
|
-
}
|
|
26
47
|
function positiveBound(value, fallback, label) {
|
|
27
48
|
const resolvedValue = value ?? fallback;
|
|
28
49
|
if (!Number.isSafeInteger(resolvedValue) || resolvedValue < 1) {
|
|
@@ -85,27 +106,95 @@ async function hashLocalFile(path, signal) {
|
|
|
85
106
|
await handle.close();
|
|
86
107
|
}
|
|
87
108
|
}
|
|
109
|
+
async function getLocalMutationPayloadDirectory() {
|
|
110
|
+
localMutationPayloadDirectoryPromise ??= mkdtemp(join(tmpdir(), LOCAL_MUTATION_PAYLOAD_DIRECTORY_PREFIX)).then((path) => {
|
|
111
|
+
localMutationPayloadDirectory = path;
|
|
112
|
+
if (!localMutationPayloadCleanupRegistered) {
|
|
113
|
+
localMutationPayloadCleanupRegistered = true;
|
|
114
|
+
process.once("exit", () => {
|
|
115
|
+
if (localMutationPayloadDirectory) {
|
|
116
|
+
try {
|
|
117
|
+
rmSync(localMutationPayloadDirectory, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Process shutdown cannot safely retry filesystem cleanup.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return path;
|
|
126
|
+
});
|
|
127
|
+
return localMutationPayloadDirectoryPromise;
|
|
128
|
+
}
|
|
129
|
+
async function stageLocalMutationPayload(content, ttlMs) {
|
|
130
|
+
const byteLength = Buffer.byteLength(content, "utf8");
|
|
131
|
+
let stagedPath = "";
|
|
132
|
+
const operation = localMutationPayloadOperationTail.then(async () => {
|
|
133
|
+
for (const [path, record] of localMutationPayloadFiles) {
|
|
134
|
+
if (record.expiresAt > Date.now())
|
|
135
|
+
continue;
|
|
136
|
+
localMutationPayloadFiles.delete(path);
|
|
137
|
+
localMutationPayloadBytes -= record.byteLength;
|
|
138
|
+
try {
|
|
139
|
+
await unlink(path);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (!isMissingPathError(error))
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (localMutationPayloadFiles.size >= LOCAL_MUTATION_PAYLOAD_PROCESS_FILE_LIMIT ||
|
|
147
|
+
localMutationPayloadBytes + byteLength > LOCAL_MUTATION_PAYLOAD_PROCESS_BYTE_LIMIT) {
|
|
148
|
+
throw new MutationPayloadCapacityError();
|
|
149
|
+
}
|
|
150
|
+
stagedPath = join(await getLocalMutationPayloadDirectory(), randomUUID());
|
|
151
|
+
await writeFile(stagedPath, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
152
|
+
localMutationPayloadFiles.set(stagedPath, { byteLength, expiresAt: Date.now() + ttlMs });
|
|
153
|
+
localMutationPayloadBytes += byteLength;
|
|
154
|
+
});
|
|
155
|
+
localMutationPayloadOperationTail = operation.catch(() => { });
|
|
156
|
+
await operation;
|
|
157
|
+
return stagedPath;
|
|
158
|
+
}
|
|
159
|
+
async function removeLocalMutationPayloadOrFile(path) {
|
|
160
|
+
const operation = localMutationPayloadOperationTail.then(async () => {
|
|
161
|
+
const record = localMutationPayloadFiles.get(path);
|
|
162
|
+
if (record !== undefined) {
|
|
163
|
+
localMutationPayloadFiles.delete(path);
|
|
164
|
+
localMutationPayloadBytes -= record.byteLength;
|
|
165
|
+
}
|
|
166
|
+
await unlink(path);
|
|
167
|
+
});
|
|
168
|
+
localMutationPayloadOperationTail = operation.catch(() => { });
|
|
169
|
+
await operation;
|
|
170
|
+
}
|
|
88
171
|
export const localFileMutationIntentOperations = {
|
|
89
172
|
inspect: inspectLocalPath,
|
|
90
173
|
access,
|
|
91
174
|
copyFileExclusive: (sourcePath, targetPath) => copyFile(sourcePath, targetPath, constants.COPYFILE_EXCL),
|
|
92
175
|
hashFile: hashLocalFile,
|
|
93
|
-
|
|
176
|
+
readPayload: (path) => readFile(path, "utf8"),
|
|
177
|
+
removeFile: removeLocalMutationPayloadOrFile,
|
|
178
|
+
stagePayload: stageLocalMutationPayload,
|
|
94
179
|
};
|
|
95
180
|
/**
|
|
96
|
-
* Session-owned authority for
|
|
97
|
-
* It retains only fixed-size metadata; file
|
|
181
|
+
* Session-owned harness authority for file-mutation preflight, stale-target checks,
|
|
182
|
+
* and bounded exact-content references. It retains only fixed-size metadata; file
|
|
183
|
+
* bytes remain owned by the filesystem.
|
|
98
184
|
*/
|
|
99
185
|
export class FileMutationIntentController {
|
|
100
186
|
constructor(options = {}) {
|
|
101
|
-
this.intents = new Map();
|
|
102
|
-
this.intentByPath = new Map();
|
|
103
187
|
this.contentReferences = new Map();
|
|
188
|
+
this.mutationPayloads = new Map();
|
|
189
|
+
this.mutationPayloadBytes = 0;
|
|
190
|
+
this.lastMutationPayloadExpiresAt = 0;
|
|
191
|
+
this.mutationPayloadOperationTail = Promise.resolve();
|
|
104
192
|
this.operations = options.operations ?? localFileMutationIntentOperations;
|
|
105
|
-
this.intentLimit = positiveBound(options.intentLimit, DEFAULT_INTENT_LIMIT, "Intent limit");
|
|
106
193
|
this.contentReferenceLimit = positiveBound(options.contentReferenceLimit, DEFAULT_CONTENT_REFERENCE_LIMIT, "Content reference limit");
|
|
107
|
-
this.intentTtlMs = positiveBound(options.intentTtlMs, DEFAULT_INTENT_TTL_MS, "Intent TTL");
|
|
108
194
|
this.contentReferenceTtlMs = positiveBound(options.contentReferenceTtlMs, DEFAULT_CONTENT_REFERENCE_TTL_MS, "Content reference TTL");
|
|
195
|
+
this.mutationPayloadLimit = positiveBound(options.mutationPayloadLimit, DEFAULT_MUTATION_PAYLOAD_LIMIT, "Mutation payload limit");
|
|
196
|
+
this.mutationPayloadByteLimit = positiveBound(options.mutationPayloadByteLimit, DEFAULT_MUTATION_PAYLOAD_BYTE_LIMIT, "Mutation payload byte limit");
|
|
197
|
+
this.mutationPayloadTtlMs = positiveBound(options.mutationPayloadTtlMs, DEFAULT_MUTATION_PAYLOAD_TTL_MS, "Mutation payload TTL");
|
|
109
198
|
this.now = options.now ?? Date.now;
|
|
110
199
|
}
|
|
111
200
|
async prepare(kind, path, signal, displayPath = path) {
|
|
@@ -116,16 +205,19 @@ export class FileMutationIntentController {
|
|
|
116
205
|
let identity;
|
|
117
206
|
if (kind === "write") {
|
|
118
207
|
const existing = await this.operations.inspect(absolutePath, false);
|
|
119
|
-
if (existing)
|
|
120
|
-
throw new
|
|
208
|
+
if (existing) {
|
|
209
|
+
throw new FileMutationPreflightError("write_collision", `Write collision: ${displayPath} already exists.`);
|
|
210
|
+
}
|
|
121
211
|
await this.assertCreatableParent(absolutePath);
|
|
122
212
|
}
|
|
123
213
|
else {
|
|
124
214
|
const existing = await this.operations.inspect(absolutePath, true);
|
|
125
|
-
if (!existing)
|
|
126
|
-
throw new
|
|
127
|
-
|
|
128
|
-
|
|
215
|
+
if (!existing) {
|
|
216
|
+
throw new FileMutationPreflightError("edit_missing", `Could not edit file: ${displayPath}. Error code: ENOENT.`);
|
|
217
|
+
}
|
|
218
|
+
if (existing.kind !== "file") {
|
|
219
|
+
throw new FileMutationPreflightError("edit_not_file", `Could not edit file: ${displayPath}. Target is not a regular file.`);
|
|
220
|
+
}
|
|
129
221
|
try {
|
|
130
222
|
await this.operations.access(absolutePath, constants.R_OK | constants.W_OK);
|
|
131
223
|
}
|
|
@@ -137,46 +229,11 @@ export class FileMutationIntentController {
|
|
|
137
229
|
}
|
|
138
230
|
if (signal?.aborted)
|
|
139
231
|
throw new Error("Operation aborted");
|
|
140
|
-
|
|
141
|
-
const priorIntentId = this.intentByPath.get(key);
|
|
142
|
-
if (priorIntentId)
|
|
143
|
-
this.deleteIntent(priorIntentId);
|
|
144
|
-
while (this.intents.size >= this.intentLimit) {
|
|
145
|
-
const oldestIntentId = this.intents.keys().next().value;
|
|
146
|
-
if (typeof oldestIntentId !== "string")
|
|
147
|
-
break;
|
|
148
|
-
this.deleteIntent(oldestIntentId);
|
|
149
|
-
}
|
|
150
|
-
const intentId = randomUUID();
|
|
151
|
-
const record = {
|
|
152
|
-
intentId,
|
|
232
|
+
return {
|
|
153
233
|
kind,
|
|
154
234
|
path: absolutePath,
|
|
155
235
|
displayPath,
|
|
156
236
|
...(identity ? { identity } : {}),
|
|
157
|
-
expiresAt: this.now() + this.intentTtlMs,
|
|
158
|
-
};
|
|
159
|
-
this.intents.set(intentId, record);
|
|
160
|
-
this.intentByPath.set(key, intentId);
|
|
161
|
-
return { intentId, kind, path: absolutePath, displayPath };
|
|
162
|
-
}
|
|
163
|
-
consume(intentId, kind, path) {
|
|
164
|
-
this.pruneExpired();
|
|
165
|
-
const record = this.intents.get(intentId);
|
|
166
|
-
if (!record)
|
|
167
|
-
throw new Error("File mutation intent is invalid, expired, or belongs to another session.");
|
|
168
|
-
this.deleteIntent(intentId);
|
|
169
|
-
if (record.kind !== kind)
|
|
170
|
-
throw new Error(`File mutation intent is for ${record.kind}, not ${kind}.`);
|
|
171
|
-
if (normalizedPath(record.path) !== normalizedPath(path)) {
|
|
172
|
-
throw new Error("File mutation intent path does not match the commit path.");
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
intentId: record.intentId,
|
|
176
|
-
kind: record.kind,
|
|
177
|
-
path: record.path,
|
|
178
|
-
displayPath: record.displayPath,
|
|
179
|
-
...(record.identity ? { identity: record.identity } : {}),
|
|
180
237
|
};
|
|
181
238
|
}
|
|
182
239
|
async assertCurrent(lease, signal) {
|
|
@@ -184,16 +241,16 @@ export class FileMutationIntentController {
|
|
|
184
241
|
throw new Error("Operation aborted");
|
|
185
242
|
if (lease.kind === "write") {
|
|
186
243
|
if (await this.operations.inspect(lease.path, false)) {
|
|
187
|
-
throw new
|
|
244
|
+
throw new FileMutationPreflightError("write_collision", `Write collision: ${lease.displayPath} became occupied after preflight; no content was overwritten.`);
|
|
188
245
|
}
|
|
189
246
|
return;
|
|
190
247
|
}
|
|
191
248
|
const current = await this.operations.inspect(lease.path, true);
|
|
192
249
|
if (!current || current.kind !== "file") {
|
|
193
|
-
throw new Error(`Could not edit file: ${lease.displayPath}. The
|
|
250
|
+
throw new Error(`Could not edit file: ${lease.displayPath}. The target no longer exists after preflight.`);
|
|
194
251
|
}
|
|
195
252
|
if (!identitiesMatch(lease.identity, current.identity)) {
|
|
196
|
-
throw new Error(`Could not edit file: ${lease.displayPath}. The file changed
|
|
253
|
+
throw new Error(`Could not edit file: ${lease.displayPath}. The file changed during edit execution; read it again before retrying.`);
|
|
197
254
|
}
|
|
198
255
|
try {
|
|
199
256
|
await this.operations.access(lease.path, constants.R_OK | constants.W_OK);
|
|
@@ -207,7 +264,97 @@ export class FileMutationIntentController {
|
|
|
207
264
|
const digest = createHash("sha256").update(content, "utf8").digest("hex");
|
|
208
265
|
return this.rememberContentDigest(sourcePath, digest, Buffer.byteLength(content, "utf8"));
|
|
209
266
|
}
|
|
267
|
+
async retainMutationPayload(kind, payload) {
|
|
268
|
+
return this.runMutationPayloadOperation(async () => {
|
|
269
|
+
await this.pruneExpiredMutationPayloads();
|
|
270
|
+
const byteLength = Buffer.byteLength(payload, "utf8");
|
|
271
|
+
if (byteLength > this.mutationPayloadByteLimit)
|
|
272
|
+
return undefined;
|
|
273
|
+
while (this.mutationPayloads.size >= this.mutationPayloadLimit ||
|
|
274
|
+
this.mutationPayloadBytes + byteLength > this.mutationPayloadByteLimit) {
|
|
275
|
+
const oldestPayloadRef = this.mutationPayloads.keys().next().value;
|
|
276
|
+
if (typeof oldestPayloadRef !== "string")
|
|
277
|
+
return undefined;
|
|
278
|
+
await this.deleteMutationPayload(oldestPayloadRef, true);
|
|
279
|
+
}
|
|
280
|
+
// Keep insertion and expiry order identical, even if the wall clock moves backward. Expiry
|
|
281
|
+
// pruning can then stop at the first live record instead of rescanning every retained payload.
|
|
282
|
+
const expiresAt = Math.max(this.lastMutationPayloadExpiresAt, this.now() + this.mutationPayloadTtlMs);
|
|
283
|
+
this.lastMutationPayloadExpiresAt = expiresAt;
|
|
284
|
+
let sourcePath;
|
|
285
|
+
try {
|
|
286
|
+
sourcePath = await this.operations.stagePayload(payload, this.mutationPayloadTtlMs);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (error instanceof MutationPayloadCapacityError)
|
|
290
|
+
return undefined;
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
const payloadRef = `${MUTATION_PAYLOAD_REFERENCE_PREFIX}${randomUUID()}`;
|
|
294
|
+
this.mutationPayloads.set(payloadRef, {
|
|
295
|
+
payloadRef,
|
|
296
|
+
kind,
|
|
297
|
+
sourcePath,
|
|
298
|
+
digest: createHash("sha256").update(payload, "utf8").digest("hex"),
|
|
299
|
+
byteLength,
|
|
300
|
+
expiresAt,
|
|
301
|
+
});
|
|
302
|
+
this.mutationPayloadBytes += byteLength;
|
|
303
|
+
return { payloadRef, byteLength };
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
async readMutationPayload(payloadRef, kind, signal) {
|
|
307
|
+
return this.runMutationPayloadOperation(async () => {
|
|
308
|
+
const record = await this.requireMutationPayload(payloadRef, kind, signal);
|
|
309
|
+
const payload = await this.operations.readPayload(record.sourcePath);
|
|
310
|
+
if (createHash("sha256").update(payload, "utf8").digest("hex") !== record.digest) {
|
|
311
|
+
await this.deleteMutationPayload(payloadRef);
|
|
312
|
+
throw new Error("Retained file mutation payload changed after it was cached.");
|
|
313
|
+
}
|
|
314
|
+
return payload;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
async assertMutationPayload(payloadRef, kind, signal) {
|
|
318
|
+
await this.runMutationPayloadOperation(async () => {
|
|
319
|
+
await this.requireVerifiedMutationPayload(payloadRef, kind, signal);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async assertContentReference(contentRef, signal) {
|
|
323
|
+
await this.requireVerifiedContentReference(contentRef, signal);
|
|
324
|
+
}
|
|
325
|
+
async copyMutationPayload(payloadRef, targetPath, signal) {
|
|
326
|
+
return this.runMutationPayloadOperation(async () => {
|
|
327
|
+
const record = await this.requireVerifiedMutationPayload(payloadRef, "write", signal);
|
|
328
|
+
await this.copyVerifiedSource(record, targetPath, signal);
|
|
329
|
+
await this.deleteMutationPayload(payloadRef);
|
|
330
|
+
return this.rememberContentDigest(targetPath, record.digest, record.byteLength);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async discardMutationPayload(payloadRef) {
|
|
334
|
+
await this.runMutationPayloadOperation(() => this.deleteMutationPayload(payloadRef));
|
|
335
|
+
}
|
|
336
|
+
/** Release every payload owned by this controller without touching another controller's files. */
|
|
337
|
+
async dispose() {
|
|
338
|
+
await this.runMutationPayloadOperation(async () => {
|
|
339
|
+
let firstError;
|
|
340
|
+
for (const payloadRef of [...this.mutationPayloads.keys()]) {
|
|
341
|
+
try {
|
|
342
|
+
await this.deleteMutationPayload(payloadRef, true);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
firstError ??= error;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (firstError)
|
|
349
|
+
throw firstError;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
210
352
|
async copyReferencedContent(contentRef, targetPath, signal) {
|
|
353
|
+
const record = await this.requireVerifiedContentReference(contentRef, signal);
|
|
354
|
+
await this.copyVerifiedSource(record, targetPath, signal);
|
|
355
|
+
return this.rememberContentDigest(targetPath, record.digest, record.byteLength);
|
|
356
|
+
}
|
|
357
|
+
async requireVerifiedContentReference(contentRef, signal) {
|
|
211
358
|
this.pruneExpired();
|
|
212
359
|
const record = this.contentReferences.get(contentRef);
|
|
213
360
|
if (!record)
|
|
@@ -215,17 +362,51 @@ export class FileMutationIntentController {
|
|
|
215
362
|
this.touchContentReference(record);
|
|
216
363
|
if (signal?.aborted)
|
|
217
364
|
throw new Error("Operation aborted");
|
|
365
|
+
try {
|
|
366
|
+
await this.verifySource(record, signal, "File content reference source");
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
this.contentReferences.delete(contentRef);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
return record;
|
|
373
|
+
}
|
|
374
|
+
async requireMutationPayload(payloadRef, kind, signal) {
|
|
375
|
+
await this.pruneExpiredMutationPayloads();
|
|
376
|
+
if (signal?.aborted)
|
|
377
|
+
throw new Error("Operation aborted");
|
|
378
|
+
const record = this.mutationPayloads.get(payloadRef);
|
|
379
|
+
if (!record) {
|
|
380
|
+
throw new Error("File mutation payload reference is invalid, expired, or belongs to another session.");
|
|
381
|
+
}
|
|
382
|
+
if (record.kind !== kind) {
|
|
383
|
+
throw new Error(`File mutation payload reference is for ${record.kind}, not ${kind}.`);
|
|
384
|
+
}
|
|
385
|
+
return record;
|
|
386
|
+
}
|
|
387
|
+
async requireVerifiedMutationPayload(payloadRef, kind, signal) {
|
|
388
|
+
const record = await this.requireMutationPayload(payloadRef, kind, signal);
|
|
389
|
+
try {
|
|
390
|
+
await this.verifySource(record, signal, "Retained file mutation payload");
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
await this.deleteMutationPayload(payloadRef);
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
return record;
|
|
397
|
+
}
|
|
398
|
+
async verifySource(record, signal, label) {
|
|
218
399
|
let sourceDigest;
|
|
219
400
|
try {
|
|
220
401
|
sourceDigest = await this.operations.hashFile(record.sourcePath, signal);
|
|
221
402
|
}
|
|
222
403
|
catch (error) {
|
|
223
|
-
throw new Error(
|
|
224
|
-
}
|
|
225
|
-
if (sourceDigest !== record.digest) {
|
|
226
|
-
this.contentReferences.delete(contentRef);
|
|
227
|
-
throw new Error("File content reference source changed after it was cached.");
|
|
404
|
+
throw new Error(`${label} is unavailable: ${String(error)}.`);
|
|
228
405
|
}
|
|
406
|
+
if (sourceDigest !== record.digest)
|
|
407
|
+
throw new Error(`${label} changed after it was cached.`);
|
|
408
|
+
}
|
|
409
|
+
async copyVerifiedSource(record, targetPath, signal) {
|
|
229
410
|
await this.operations.copyFileExclusive(record.sourcePath, targetPath);
|
|
230
411
|
let targetDigest;
|
|
231
412
|
try {
|
|
@@ -239,7 +420,6 @@ export class FileMutationIntentController {
|
|
|
239
420
|
await this.removeFailedCopy(targetPath, new Error("copied bytes did not match the cached digest"));
|
|
240
421
|
throw new Error("File content copy changed while it was being created; the target was removed.");
|
|
241
422
|
}
|
|
242
|
-
return this.rememberContentDigest(targetPath, record.digest, record.byteLength);
|
|
243
423
|
}
|
|
244
424
|
async assertCreatableParent(path) {
|
|
245
425
|
let candidate = dirname(path);
|
|
@@ -301,21 +481,35 @@ export class FileMutationIntentController {
|
|
|
301
481
|
throw new Error(`Copied content verification failed (${String(cause)}), and the newly created target could not be removed: ${String(cleanupError)}.`);
|
|
302
482
|
}
|
|
303
483
|
}
|
|
304
|
-
|
|
305
|
-
const record = this.
|
|
484
|
+
async deleteMutationPayload(payloadRef, strict = false) {
|
|
485
|
+
const record = this.mutationPayloads.get(payloadRef);
|
|
306
486
|
if (!record)
|
|
307
487
|
return;
|
|
308
|
-
this.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
this.
|
|
488
|
+
this.mutationPayloads.delete(payloadRef);
|
|
489
|
+
this.mutationPayloadBytes -= record.byteLength;
|
|
490
|
+
try {
|
|
491
|
+
await this.operations.removeFile(record.sourcePath);
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
if (strict && !isMissingPathError(error))
|
|
495
|
+
throw error;
|
|
496
|
+
}
|
|
312
497
|
}
|
|
313
|
-
|
|
498
|
+
runMutationPayloadOperation(operation) {
|
|
499
|
+
const run = this.mutationPayloadOperationTail.then(operation, operation);
|
|
500
|
+
this.mutationPayloadOperationTail = run.then(() => undefined, () => undefined);
|
|
501
|
+
return run;
|
|
502
|
+
}
|
|
503
|
+
async pruneExpiredMutationPayloads() {
|
|
314
504
|
const now = this.now();
|
|
315
|
-
for (const [
|
|
316
|
-
if (record.expiresAt
|
|
317
|
-
|
|
505
|
+
for (const [payloadRef, record] of this.mutationPayloads) {
|
|
506
|
+
if (record.expiresAt > now)
|
|
507
|
+
break;
|
|
508
|
+
await this.deleteMutationPayload(payloadRef, true);
|
|
318
509
|
}
|
|
510
|
+
}
|
|
511
|
+
pruneExpired() {
|
|
512
|
+
const now = this.now();
|
|
319
513
|
for (const [contentRef, record] of this.contentReferences) {
|
|
320
514
|
if (record.expiresAt <= now)
|
|
321
515
|
this.contentReferences.delete(contentRef);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-mutation-intent.js","sourceRoot":"","sources":["../../../src/core/tools/file-mutation-intent.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { constants } from \"node:fs\";\nimport { access, copyFile, lstat, open, stat, unlink } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\n\nconst DEFAULT_INTENT_LIMIT = 64;\nconst DEFAULT_CONTENT_REFERENCE_LIMIT = 64;\nconst DEFAULT_INTENT_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_CONTENT_REFERENCE_TTL_MS = 60 * 60 * 1000;\nconst HASH_BUFFER_BYTES = 64 * 1024;\nconst CONTENT_REFERENCE_PREFIX = \"file-content:\";\n\nexport type FileMutationKind = \"write\" | \"edit\";\n\nexport interface FilePathIdentity {\n\tdev: string;\n\tino: string;\n\tmode: string;\n\tsize: string;\n\tmtimeMs: string;\n\tctimeMs: string;\n}\n\nexport interface FilePathInspection {\n\tkind: \"file\" | \"directory\" | \"other\";\n\tidentity: FilePathIdentity;\n}\n\nexport interface FileMutationIntentOperations {\n\tinspect(path: string, followSymlinks: boolean): Promise<FilePathInspection | undefined>;\n\taccess(path: string, mode: number): Promise<void>;\n\tcopyFileExclusive(sourcePath: string, targetPath: string): Promise<void>;\n\thashFile(path: string, signal?: AbortSignal): Promise<string>;\n\tremoveFile(path: string): Promise<void>;\n}\n\nexport interface PreparedFileMutation {\n\tintentId: string;\n\tkind: FileMutationKind;\n\tpath: string;\n\tdisplayPath: string;\n}\n\nexport interface FileMutationLease extends PreparedFileMutation {\n\tidentity?: FilePathIdentity;\n}\n\nexport interface FileContentReference {\n\tcontentRef: string;\n\tbyteLength: number;\n}\n\ninterface MutationIntentRecord extends FileMutationLease {\n\texpiresAt: number;\n}\n\ninterface ContentReferenceRecord {\n\tcontentRef: string;\n\tsourcePath: string;\n\tdigest: string;\n\tbyteLength: number;\n\texpiresAt: number;\n}\n\nexport interface FileMutationIntentControllerOptions {\n\toperations?: FileMutationIntentOperations;\n\tintentLimit?: number;\n\tcontentReferenceLimit?: number;\n\tintentTtlMs?: number;\n\tcontentReferenceTtlMs?: number;\n\tnow?: () => number;\n}\n\nfunction isMissingPathError(error: unknown): boolean {\n\treturn (\n\t\ttypeof error === \"object\" &&\n\t\terror !== null &&\n\t\t\"code\" in error &&\n\t\t(error.code === \"ENOENT\" || error.code === \"ENOTDIR\")\n\t);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && typeof error.code === \"string\"\n\t\t? error.code\n\t\t: undefined;\n}\n\nfunction normalizedPath(path: string): string {\n\tconst absolutePath = resolve(path);\n\treturn process.platform === \"win32\" ? absolutePath.toLocaleLowerCase(\"en-US\") : absolutePath;\n}\n\nfunction positiveBound(value: number | undefined, fallback: number, label: string): number {\n\tconst resolvedValue = value ?? fallback;\n\tif (!Number.isSafeInteger(resolvedValue) || resolvedValue < 1) {\n\t\tthrow new TypeError(`${label} must be a positive safe integer.`);\n\t}\n\treturn resolvedValue;\n}\n\nfunction normalizedIdentity(value: {\n\tdev: number | bigint;\n\tino: number | bigint;\n\tmode: number | bigint;\n\tsize: number | bigint;\n\tmtimeMs: number | bigint;\n\tctimeMs: number | bigint;\n}): FilePathIdentity {\n\treturn {\n\t\tdev: String(value.dev),\n\t\tino: String(value.ino),\n\t\tmode: String(value.mode),\n\t\tsize: String(value.size),\n\t\tmtimeMs: String(value.mtimeMs),\n\t\tctimeMs: String(value.ctimeMs),\n\t};\n}\n\nfunction identitiesMatch(left: FilePathIdentity | undefined, right: FilePathIdentity | undefined): boolean {\n\treturn (\n\t\tleft !== undefined &&\n\t\tright !== undefined &&\n\t\tleft.dev === right.dev &&\n\t\tleft.ino === right.ino &&\n\t\tleft.mode === right.mode &&\n\t\tleft.size === right.size &&\n\t\tleft.mtimeMs === right.mtimeMs &&\n\t\tleft.ctimeMs === right.ctimeMs\n\t);\n}\n\nasync function inspectLocalPath(path: string, followSymlinks: boolean): Promise<FilePathInspection | undefined> {\n\ttry {\n\t\tconst result = followSymlinks ? await stat(path, { bigint: true }) : await lstat(path, { bigint: true });\n\t\treturn {\n\t\t\tkind: result.isFile() ? \"file\" : result.isDirectory() ? \"directory\" : \"other\",\n\t\t\tidentity: normalizedIdentity(result),\n\t\t};\n\t} catch (error) {\n\t\tif (isMissingPathError(error)) return undefined;\n\t\tthrow error;\n\t}\n}\n\nasync function hashLocalFile(path: string, signal?: AbortSignal): Promise<string> {\n\tconst handle = await open(path, \"r\");\n\tconst hash = createHash(\"sha256\");\n\tconst buffer = Buffer.allocUnsafe(HASH_BUFFER_BYTES);\n\ttry {\n\t\tlet position = 0;\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst { bytesRead } = await handle.read(buffer, 0, buffer.length, position);\n\t\t\tif (bytesRead === 0) break;\n\t\t\thash.update(buffer.subarray(0, bytesRead));\n\t\t\tposition += bytesRead;\n\t\t}\n\t\treturn hash.digest(\"hex\");\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\nexport const localFileMutationIntentOperations: FileMutationIntentOperations = {\n\tinspect: inspectLocalPath,\n\taccess,\n\tcopyFileExclusive: (sourcePath, targetPath) => copyFile(sourcePath, targetPath, constants.COPYFILE_EXCL),\n\thashFile: hashLocalFile,\n\tremoveFile: unlink,\n};\n\n/**\n * Session-owned authority for two-phase file mutation intents and bounded exact-content references.\n * It retains only fixed-size metadata; file bytes remain owned by the filesystem.\n */\nexport class FileMutationIntentController {\n\tprivate readonly operations: FileMutationIntentOperations;\n\tprivate readonly intentLimit: number;\n\tprivate readonly contentReferenceLimit: number;\n\tprivate readonly intentTtlMs: number;\n\tprivate readonly contentReferenceTtlMs: number;\n\tprivate readonly now: () => number;\n\tprivate readonly intents = new Map<string, MutationIntentRecord>();\n\tprivate readonly intentByPath = new Map<string, string>();\n\tprivate readonly contentReferences = new Map<string, ContentReferenceRecord>();\n\n\tconstructor(options: FileMutationIntentControllerOptions = {}) {\n\t\tthis.operations = options.operations ?? localFileMutationIntentOperations;\n\t\tthis.intentLimit = positiveBound(options.intentLimit, DEFAULT_INTENT_LIMIT, \"Intent limit\");\n\t\tthis.contentReferenceLimit = positiveBound(\n\t\t\toptions.contentReferenceLimit,\n\t\t\tDEFAULT_CONTENT_REFERENCE_LIMIT,\n\t\t\t\"Content reference limit\",\n\t\t);\n\t\tthis.intentTtlMs = positiveBound(options.intentTtlMs, DEFAULT_INTENT_TTL_MS, \"Intent TTL\");\n\t\tthis.contentReferenceTtlMs = positiveBound(\n\t\t\toptions.contentReferenceTtlMs,\n\t\t\tDEFAULT_CONTENT_REFERENCE_TTL_MS,\n\t\t\t\"Content reference TTL\",\n\t\t);\n\t\tthis.now = options.now ?? Date.now;\n\t}\n\n\tasync prepare(\n\t\tkind: FileMutationKind,\n\t\tpath: string,\n\t\tsignal?: AbortSignal,\n\t\tdisplayPath: string = path,\n\t): Promise<PreparedFileMutation> {\n\t\tthis.pruneExpired();\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\tconst absolutePath = resolve(path);\n\t\tlet identity: FilePathIdentity | undefined;\n\t\tif (kind === \"write\") {\n\t\t\tconst existing = await this.operations.inspect(absolutePath, false);\n\t\t\tif (existing) throw new Error(`Write collision: ${displayPath} already exists.`);\n\t\t\tawait this.assertCreatableParent(absolutePath);\n\t\t} else {\n\t\t\tconst existing = await this.operations.inspect(absolutePath, true);\n\t\t\tif (!existing) throw new Error(`Could not edit file: ${displayPath}. Error code: ENOENT.`);\n\t\t\tif (existing.kind !== \"file\")\n\t\t\t\tthrow new Error(`Could not edit file: ${displayPath}. Target is not a regular file.`);\n\t\t\ttry {\n\t\t\t\tawait this.operations.access(absolutePath, constants.R_OK | constants.W_OK);\n\t\t\t} catch (error) {\n\t\t\t\tconst code = errorCode(error);\n\t\t\t\tthrow new Error(`Could not edit file: ${displayPath}. ${code ? `Error code: ${code}` : String(error)}.`);\n\t\t\t}\n\t\t\tidentity = existing.identity;\n\t\t}\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\tconst key = `${kind}\\0${normalizedPath(absolutePath)}`;\n\t\tconst priorIntentId = this.intentByPath.get(key);\n\t\tif (priorIntentId) this.deleteIntent(priorIntentId);\n\t\twhile (this.intents.size >= this.intentLimit) {\n\t\t\tconst oldestIntentId = this.intents.keys().next().value;\n\t\t\tif (typeof oldestIntentId !== \"string\") break;\n\t\t\tthis.deleteIntent(oldestIntentId);\n\t\t}\n\t\tconst intentId = randomUUID();\n\t\tconst record: MutationIntentRecord = {\n\t\t\tintentId,\n\t\t\tkind,\n\t\t\tpath: absolutePath,\n\t\t\tdisplayPath,\n\t\t\t...(identity ? { identity } : {}),\n\t\t\texpiresAt: this.now() + this.intentTtlMs,\n\t\t};\n\t\tthis.intents.set(intentId, record);\n\t\tthis.intentByPath.set(key, intentId);\n\t\treturn { intentId, kind, path: absolutePath, displayPath };\n\t}\n\n\tconsume(intentId: string, kind: FileMutationKind, path: string): FileMutationLease {\n\t\tthis.pruneExpired();\n\t\tconst record = this.intents.get(intentId);\n\t\tif (!record) throw new Error(\"File mutation intent is invalid, expired, or belongs to another session.\");\n\t\tthis.deleteIntent(intentId);\n\t\tif (record.kind !== kind) throw new Error(`File mutation intent is for ${record.kind}, not ${kind}.`);\n\t\tif (normalizedPath(record.path) !== normalizedPath(path)) {\n\t\t\tthrow new Error(\"File mutation intent path does not match the commit path.\");\n\t\t}\n\t\treturn {\n\t\t\tintentId: record.intentId,\n\t\t\tkind: record.kind,\n\t\t\tpath: record.path,\n\t\t\tdisplayPath: record.displayPath,\n\t\t\t...(record.identity ? { identity: record.identity } : {}),\n\t\t};\n\t}\n\n\tasync assertCurrent(lease: FileMutationLease, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\tif (lease.kind === \"write\") {\n\t\t\tif (await this.operations.inspect(lease.path, false)) {\n\t\t\t\tthrow new Error(`Write collision: ${lease.displayPath} already exists; the prepared intent is stale.`);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst current = await this.operations.inspect(lease.path, true);\n\t\tif (!current || current.kind !== \"file\") {\n\t\t\tthrow new Error(`Could not edit file: ${lease.displayPath}. The prepared target no longer exists.`);\n\t\t}\n\t\tif (!identitiesMatch(lease.identity, current.identity)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Could not edit file: ${lease.displayPath}. The file changed after preparation; prepare again.`,\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\tawait this.operations.access(lease.path, constants.R_OK | constants.W_OK);\n\t\t} catch (error) {\n\t\t\tconst code = errorCode(error);\n\t\t\tthrow new Error(`Could not edit file: ${lease.displayPath}. ${code ? `Error code: ${code}` : String(error)}.`);\n\t\t}\n\t}\n\n\trememberContent(sourcePath: string, content: string): FileContentReference {\n\t\tconst digest = createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n\t\treturn this.rememberContentDigest(sourcePath, digest, Buffer.byteLength(content, \"utf8\"));\n\t}\n\n\tasync copyReferencedContent(\n\t\tcontentRef: string,\n\t\ttargetPath: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<FileContentReference> {\n\t\tthis.pruneExpired();\n\t\tconst record = this.contentReferences.get(contentRef);\n\t\tif (!record) throw new Error(\"File content reference is invalid, expired, or belongs to another session.\");\n\t\tthis.touchContentReference(record);\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\tlet sourceDigest: string;\n\t\ttry {\n\t\t\tsourceDigest = await this.operations.hashFile(record.sourcePath, signal);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`File content reference source is unavailable: ${String(error)}.`);\n\t\t}\n\t\tif (sourceDigest !== record.digest) {\n\t\t\tthis.contentReferences.delete(contentRef);\n\t\t\tthrow new Error(\"File content reference source changed after it was cached.\");\n\t\t}\n\n\t\tawait this.operations.copyFileExclusive(record.sourcePath, targetPath);\n\t\tlet targetDigest: string;\n\t\ttry {\n\t\t\ttargetDigest = await this.operations.hashFile(targetPath, signal);\n\t\t} catch (error) {\n\t\t\tawait this.removeFailedCopy(targetPath, error);\n\t\t\tthrow error;\n\t\t}\n\t\tif (targetDigest !== record.digest) {\n\t\t\tawait this.removeFailedCopy(targetPath, new Error(\"copied bytes did not match the cached digest\"));\n\t\t\tthrow new Error(\"File content copy changed while it was being created; the target was removed.\");\n\t\t}\n\t\treturn this.rememberContentDigest(targetPath, record.digest, record.byteLength);\n\t}\n\n\tprivate async assertCreatableParent(path: string): Promise<void> {\n\t\tlet candidate = dirname(path);\n\t\twhile (true) {\n\t\t\tconst inspection = await this.operations.inspect(candidate, true);\n\t\t\tif (inspection) {\n\t\t\t\tif (inspection.kind !== \"directory\") {\n\t\t\t\t\tthrow new Error(`Write parent path is not a directory: ${candidate}.`);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tawait this.operations.access(candidate, constants.W_OK);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst code = errorCode(error);\n\t\t\t\t\tthrow new Error(`Write parent is not writable: ${candidate}${code ? ` (${code})` : \"\"}.`);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst parent = dirname(candidate);\n\t\t\tif (parent === candidate) throw new Error(`No writable parent exists for ${path}.`);\n\t\t\tcandidate = parent;\n\t\t}\n\t}\n\n\tprivate rememberContentDigest(sourcePath: string, digest: string, byteLength: number): FileContentReference {\n\t\tthis.pruneExpired();\n\t\tfor (const existing of this.contentReferences.values()) {\n\t\t\tif (existing.sourcePath === sourcePath && existing.digest === digest) {\n\t\t\t\tthis.touchContentReference(existing);\n\t\t\t\treturn { contentRef: existing.contentRef, byteLength: existing.byteLength };\n\t\t\t}\n\t\t}\n\t\twhile (this.contentReferences.size >= this.contentReferenceLimit) {\n\t\t\tconst oldestContentRef = this.contentReferences.keys().next().value;\n\t\t\tif (typeof oldestContentRef !== \"string\") break;\n\t\t\tthis.contentReferences.delete(oldestContentRef);\n\t\t}\n\t\tconst contentRef = `${CONTENT_REFERENCE_PREFIX}${randomUUID()}`;\n\t\tthis.contentReferences.set(contentRef, {\n\t\t\tcontentRef,\n\t\t\tsourcePath,\n\t\t\tdigest,\n\t\t\tbyteLength,\n\t\t\texpiresAt: this.now() + this.contentReferenceTtlMs,\n\t\t});\n\t\treturn { contentRef, byteLength };\n\t}\n\n\tprivate touchContentReference(record: ContentReferenceRecord): void {\n\t\trecord.expiresAt = this.now() + this.contentReferenceTtlMs;\n\t\tthis.contentReferences.delete(record.contentRef);\n\t\tthis.contentReferences.set(record.contentRef, record);\n\t}\n\n\tprivate async removeFailedCopy(path: string, cause: unknown): Promise<void> {\n\t\ttry {\n\t\t\tawait this.operations.removeFile(path);\n\t\t} catch (cleanupError) {\n\t\t\tthrow new Error(\n\t\t\t\t`Copied content verification failed (${String(cause)}), and the newly created target could not be removed: ${String(cleanupError)}.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate deleteIntent(intentId: string): void {\n\t\tconst record = this.intents.get(intentId);\n\t\tif (!record) return;\n\t\tthis.intents.delete(intentId);\n\t\tconst key = `${record.kind}\\0${normalizedPath(record.path)}`;\n\t\tif (this.intentByPath.get(key) === intentId) this.intentByPath.delete(key);\n\t}\n\n\tprivate pruneExpired(): void {\n\t\tconst now = this.now();\n\t\tfor (const [intentId, record] of this.intents) {\n\t\t\tif (record.expiresAt <= now) this.deleteIntent(intentId);\n\t\t}\n\t\tfor (const [contentRef, record] of this.contentReferences) {\n\t\t\tif (record.expiresAt <= now) this.contentReferences.delete(contentRef);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"file-mutation-intent.js","sourceRoot":"","sources":["../../../src/core/tools/file-mutation-intent.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { constants, rmSync } from \"node:fs\";\nimport { access, copyFile, lstat, mkdtemp, open, readFile, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\n\nconst DEFAULT_CONTENT_REFERENCE_LIMIT = 64;\nconst DEFAULT_CONTENT_REFERENCE_TTL_MS = 60 * 60 * 1000;\nconst DEFAULT_MUTATION_PAYLOAD_LIMIT = 8;\nconst DEFAULT_MUTATION_PAYLOAD_BYTE_LIMIT = 16 * 1024 * 1024;\nconst DEFAULT_MUTATION_PAYLOAD_TTL_MS = 10 * 60 * 1000;\nconst HASH_BUFFER_BYTES = 64 * 1024;\nconst CONTENT_REFERENCE_PREFIX = \"file-content:\";\nconst MUTATION_PAYLOAD_REFERENCE_PREFIX = \"file-mutation:\";\nconst LOCAL_MUTATION_PAYLOAD_DIRECTORY_PREFIX = \"pi-file-mutation-payloads-\";\nconst LOCAL_MUTATION_PAYLOAD_PROCESS_BYTE_LIMIT = 64 * 1024 * 1024;\nconst LOCAL_MUTATION_PAYLOAD_PROCESS_FILE_LIMIT = 64;\n\nlet localMutationPayloadDirectoryPromise: Promise<string> | undefined;\nlet localMutationPayloadDirectory: string | undefined;\nlet localMutationPayloadCleanupRegistered = false;\nlet localMutationPayloadOperationTail: Promise<void> = Promise.resolve();\nlet localMutationPayloadBytes = 0;\nconst localMutationPayloadFiles = new Map<string, { byteLength: number; expiresAt: number }>();\n\nclass MutationPayloadCapacityError extends Error {\n\tconstructor() {\n\t\tsuper(\"Process mutation payload cache is full.\");\n\t\tthis.name = \"MutationPayloadCapacityError\";\n\t}\n}\n\nexport type FileMutationKind = \"write\" | \"edit\";\n\nexport interface FilePathIdentity {\n\tdev: string;\n\tino: string;\n\tmode: string;\n\tsize: string;\n\tmtimeMs: string;\n\tctimeMs: string;\n}\n\nexport interface FilePathInspection {\n\tkind: \"file\" | \"directory\" | \"other\";\n\tidentity: FilePathIdentity;\n}\n\nexport interface FileMutationIntentOperations {\n\tinspect(path: string, followSymlinks: boolean): Promise<FilePathInspection | undefined>;\n\taccess(path: string, mode: number): Promise<void>;\n\tcopyFileExclusive(sourcePath: string, targetPath: string): Promise<void>;\n\thashFile(path: string, signal?: AbortSignal): Promise<string>;\n\treadPayload(path: string): Promise<string>;\n\tremoveFile(path: string): Promise<void>;\n\tstagePayload(content: string, ttlMs: number): Promise<string>;\n}\n\nexport interface FileMutationLease {\n\tkind: FileMutationKind;\n\tpath: string;\n\tdisplayPath: string;\n\tidentity?: FilePathIdentity;\n}\n\nexport interface FileContentReference {\n\tcontentRef: string;\n\tbyteLength: number;\n}\n\nexport interface FileMutationPayloadReference {\n\tpayloadRef: string;\n\tbyteLength: number;\n}\n\nexport type FileMutationPreflightFailureReason = \"write_collision\" | \"edit_missing\" | \"edit_not_file\";\n\nexport class FileMutationPreflightError extends Error {\n\treadonly reason: FileMutationPreflightFailureReason;\n\n\tconstructor(reason: FileMutationPreflightFailureReason, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"FileMutationPreflightError\";\n\t\tthis.reason = reason;\n\t}\n}\n\ninterface ContentReferenceRecord {\n\tcontentRef: string;\n\tsourcePath: string;\n\tdigest: string;\n\tbyteLength: number;\n\texpiresAt: number;\n}\n\ninterface MutationPayloadRecord {\n\tpayloadRef: string;\n\tkind: FileMutationKind;\n\tsourcePath: string;\n\tdigest: string;\n\tbyteLength: number;\n\texpiresAt: number;\n}\n\nexport interface FileMutationIntentControllerOptions {\n\toperations?: FileMutationIntentOperations;\n\tcontentReferenceLimit?: number;\n\tcontentReferenceTtlMs?: number;\n\tmutationPayloadLimit?: number;\n\tmutationPayloadByteLimit?: number;\n\tmutationPayloadTtlMs?: number;\n\tnow?: () => number;\n}\n\nfunction isMissingPathError(error: unknown): boolean {\n\treturn (\n\t\ttypeof error === \"object\" &&\n\t\terror !== null &&\n\t\t\"code\" in error &&\n\t\t(error.code === \"ENOENT\" || error.code === \"ENOTDIR\")\n\t);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && typeof error.code === \"string\"\n\t\t? error.code\n\t\t: undefined;\n}\n\nfunction positiveBound(value: number | undefined, fallback: number, label: string): number {\n\tconst resolvedValue = value ?? fallback;\n\tif (!Number.isSafeInteger(resolvedValue) || resolvedValue < 1) {\n\t\tthrow new TypeError(`${label} must be a positive safe integer.`);\n\t}\n\treturn resolvedValue;\n}\n\nfunction normalizedIdentity(value: {\n\tdev: number | bigint;\n\tino: number | bigint;\n\tmode: number | bigint;\n\tsize: number | bigint;\n\tmtimeMs: number | bigint;\n\tctimeMs: number | bigint;\n}): FilePathIdentity {\n\treturn {\n\t\tdev: String(value.dev),\n\t\tino: String(value.ino),\n\t\tmode: String(value.mode),\n\t\tsize: String(value.size),\n\t\tmtimeMs: String(value.mtimeMs),\n\t\tctimeMs: String(value.ctimeMs),\n\t};\n}\n\nfunction identitiesMatch(left: FilePathIdentity | undefined, right: FilePathIdentity | undefined): boolean {\n\treturn (\n\t\tleft !== undefined &&\n\t\tright !== undefined &&\n\t\tleft.dev === right.dev &&\n\t\tleft.ino === right.ino &&\n\t\tleft.mode === right.mode &&\n\t\tleft.size === right.size &&\n\t\tleft.mtimeMs === right.mtimeMs &&\n\t\tleft.ctimeMs === right.ctimeMs\n\t);\n}\n\nasync function inspectLocalPath(path: string, followSymlinks: boolean): Promise<FilePathInspection | undefined> {\n\ttry {\n\t\tconst result = followSymlinks ? await stat(path, { bigint: true }) : await lstat(path, { bigint: true });\n\t\treturn {\n\t\t\tkind: result.isFile() ? \"file\" : result.isDirectory() ? \"directory\" : \"other\",\n\t\t\tidentity: normalizedIdentity(result),\n\t\t};\n\t} catch (error) {\n\t\tif (isMissingPathError(error)) return undefined;\n\t\tthrow error;\n\t}\n}\n\nasync function hashLocalFile(path: string, signal?: AbortSignal): Promise<string> {\n\tconst handle = await open(path, \"r\");\n\tconst hash = createHash(\"sha256\");\n\tconst buffer = Buffer.allocUnsafe(HASH_BUFFER_BYTES);\n\ttry {\n\t\tlet position = 0;\n\t\twhile (true) {\n\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\tconst { bytesRead } = await handle.read(buffer, 0, buffer.length, position);\n\t\t\tif (bytesRead === 0) break;\n\t\t\thash.update(buffer.subarray(0, bytesRead));\n\t\t\tposition += bytesRead;\n\t\t}\n\t\treturn hash.digest(\"hex\");\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\nasync function getLocalMutationPayloadDirectory(): Promise<string> {\n\tlocalMutationPayloadDirectoryPromise ??= mkdtemp(join(tmpdir(), LOCAL_MUTATION_PAYLOAD_DIRECTORY_PREFIX)).then(\n\t\t(path) => {\n\t\t\tlocalMutationPayloadDirectory = path;\n\t\t\tif (!localMutationPayloadCleanupRegistered) {\n\t\t\t\tlocalMutationPayloadCleanupRegistered = true;\n\t\t\t\tprocess.once(\"exit\", () => {\n\t\t\t\t\tif (localMutationPayloadDirectory) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\trmSync(localMutationPayloadDirectory, { recursive: true, force: true });\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Process shutdown cannot safely retry filesystem cleanup.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn path;\n\t\t},\n\t);\n\treturn localMutationPayloadDirectoryPromise;\n}\n\nasync function stageLocalMutationPayload(content: string, ttlMs: number): Promise<string> {\n\tconst byteLength = Buffer.byteLength(content, \"utf8\");\n\tlet stagedPath = \"\";\n\tconst operation = localMutationPayloadOperationTail.then(async () => {\n\t\tfor (const [path, record] of localMutationPayloadFiles) {\n\t\t\tif (record.expiresAt > Date.now()) continue;\n\t\t\tlocalMutationPayloadFiles.delete(path);\n\t\t\tlocalMutationPayloadBytes -= record.byteLength;\n\t\t\ttry {\n\t\t\t\tawait unlink(path);\n\t\t\t} catch (error) {\n\t\t\t\tif (!isMissingPathError(error)) throw error;\n\t\t\t}\n\t\t}\n\t\tif (\n\t\t\tlocalMutationPayloadFiles.size >= LOCAL_MUTATION_PAYLOAD_PROCESS_FILE_LIMIT ||\n\t\t\tlocalMutationPayloadBytes + byteLength > LOCAL_MUTATION_PAYLOAD_PROCESS_BYTE_LIMIT\n\t\t) {\n\t\t\tthrow new MutationPayloadCapacityError();\n\t\t}\n\t\tstagedPath = join(await getLocalMutationPayloadDirectory(), randomUUID());\n\t\tawait writeFile(stagedPath, content, { encoding: \"utf8\", flag: \"wx\", mode: 0o600 });\n\t\tlocalMutationPayloadFiles.set(stagedPath, { byteLength, expiresAt: Date.now() + ttlMs });\n\t\tlocalMutationPayloadBytes += byteLength;\n\t});\n\tlocalMutationPayloadOperationTail = operation.catch(() => {});\n\tawait operation;\n\treturn stagedPath;\n}\n\nasync function removeLocalMutationPayloadOrFile(path: string): Promise<void> {\n\tconst operation = localMutationPayloadOperationTail.then(async () => {\n\t\tconst record = localMutationPayloadFiles.get(path);\n\t\tif (record !== undefined) {\n\t\t\tlocalMutationPayloadFiles.delete(path);\n\t\t\tlocalMutationPayloadBytes -= record.byteLength;\n\t\t}\n\t\tawait unlink(path);\n\t});\n\tlocalMutationPayloadOperationTail = operation.catch(() => {});\n\tawait operation;\n}\n\nexport const localFileMutationIntentOperations: FileMutationIntentOperations = {\n\tinspect: inspectLocalPath,\n\taccess,\n\tcopyFileExclusive: (sourcePath, targetPath) => copyFile(sourcePath, targetPath, constants.COPYFILE_EXCL),\n\thashFile: hashLocalFile,\n\treadPayload: (path) => readFile(path, \"utf8\"),\n\tremoveFile: removeLocalMutationPayloadOrFile,\n\tstagePayload: stageLocalMutationPayload,\n};\n\n/**\n * Session-owned harness authority for file-mutation preflight, stale-target checks,\n * and bounded exact-content references. It retains only fixed-size metadata; file\n * bytes remain owned by the filesystem.\n */\nexport class FileMutationIntentController {\n\tprivate readonly operations: FileMutationIntentOperations;\n\tprivate readonly contentReferenceLimit: number;\n\tprivate readonly contentReferenceTtlMs: number;\n\tprivate readonly mutationPayloadLimit: number;\n\tprivate readonly mutationPayloadByteLimit: number;\n\tprivate readonly mutationPayloadTtlMs: number;\n\tprivate readonly now: () => number;\n\tprivate readonly contentReferences = new Map<string, ContentReferenceRecord>();\n\tprivate readonly mutationPayloads = new Map<string, MutationPayloadRecord>();\n\tprivate mutationPayloadBytes = 0;\n\tprivate lastMutationPayloadExpiresAt = 0;\n\tprivate mutationPayloadOperationTail: Promise<void> = Promise.resolve();\n\n\tconstructor(options: FileMutationIntentControllerOptions = {}) {\n\t\tthis.operations = options.operations ?? localFileMutationIntentOperations;\n\t\tthis.contentReferenceLimit = positiveBound(\n\t\t\toptions.contentReferenceLimit,\n\t\t\tDEFAULT_CONTENT_REFERENCE_LIMIT,\n\t\t\t\"Content reference limit\",\n\t\t);\n\t\tthis.contentReferenceTtlMs = positiveBound(\n\t\t\toptions.contentReferenceTtlMs,\n\t\t\tDEFAULT_CONTENT_REFERENCE_TTL_MS,\n\t\t\t\"Content reference TTL\",\n\t\t);\n\t\tthis.mutationPayloadLimit = positiveBound(\n\t\t\toptions.mutationPayloadLimit,\n\t\t\tDEFAULT_MUTATION_PAYLOAD_LIMIT,\n\t\t\t\"Mutation payload limit\",\n\t\t);\n\t\tthis.mutationPayloadByteLimit = positiveBound(\n\t\t\toptions.mutationPayloadByteLimit,\n\t\t\tDEFAULT_MUTATION_PAYLOAD_BYTE_LIMIT,\n\t\t\t\"Mutation payload byte limit\",\n\t\t);\n\t\tthis.mutationPayloadTtlMs = positiveBound(\n\t\t\toptions.mutationPayloadTtlMs,\n\t\t\tDEFAULT_MUTATION_PAYLOAD_TTL_MS,\n\t\t\t\"Mutation payload TTL\",\n\t\t);\n\t\tthis.now = options.now ?? Date.now;\n\t}\n\n\tasync prepare(\n\t\tkind: FileMutationKind,\n\t\tpath: string,\n\t\tsignal?: AbortSignal,\n\t\tdisplayPath: string = path,\n\t): Promise<FileMutationLease> {\n\t\tthis.pruneExpired();\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\tconst absolutePath = resolve(path);\n\t\tlet identity: FilePathIdentity | undefined;\n\t\tif (kind === \"write\") {\n\t\t\tconst existing = await this.operations.inspect(absolutePath, false);\n\t\t\tif (existing) {\n\t\t\t\tthrow new FileMutationPreflightError(\"write_collision\", `Write collision: ${displayPath} already exists.`);\n\t\t\t}\n\t\t\tawait this.assertCreatableParent(absolutePath);\n\t\t} else {\n\t\t\tconst existing = await this.operations.inspect(absolutePath, true);\n\t\t\tif (!existing) {\n\t\t\t\tthrow new FileMutationPreflightError(\n\t\t\t\t\t\"edit_missing\",\n\t\t\t\t\t`Could not edit file: ${displayPath}. Error code: ENOENT.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (existing.kind !== \"file\") {\n\t\t\t\tthrow new FileMutationPreflightError(\n\t\t\t\t\t\"edit_not_file\",\n\t\t\t\t\t`Could not edit file: ${displayPath}. Target is not a regular file.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait this.operations.access(absolutePath, constants.R_OK | constants.W_OK);\n\t\t\t} catch (error) {\n\t\t\t\tconst code = errorCode(error);\n\t\t\t\tthrow new Error(`Could not edit file: ${displayPath}. ${code ? `Error code: ${code}` : String(error)}.`);\n\t\t\t}\n\t\t\tidentity = existing.identity;\n\t\t}\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\treturn {\n\t\t\tkind,\n\t\t\tpath: absolutePath,\n\t\t\tdisplayPath,\n\t\t\t...(identity ? { identity } : {}),\n\t\t};\n\t}\n\n\tasync assertCurrent(lease: FileMutationLease, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\tif (lease.kind === \"write\") {\n\t\t\tif (await this.operations.inspect(lease.path, false)) {\n\t\t\t\tthrow new FileMutationPreflightError(\n\t\t\t\t\t\"write_collision\",\n\t\t\t\t\t`Write collision: ${lease.displayPath} became occupied after preflight; no content was overwritten.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst current = await this.operations.inspect(lease.path, true);\n\t\tif (!current || current.kind !== \"file\") {\n\t\t\tthrow new Error(`Could not edit file: ${lease.displayPath}. The target no longer exists after preflight.`);\n\t\t}\n\t\tif (!identitiesMatch(lease.identity, current.identity)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Could not edit file: ${lease.displayPath}. The file changed during edit execution; read it again before retrying.`,\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\tawait this.operations.access(lease.path, constants.R_OK | constants.W_OK);\n\t\t} catch (error) {\n\t\t\tconst code = errorCode(error);\n\t\t\tthrow new Error(`Could not edit file: ${lease.displayPath}. ${code ? `Error code: ${code}` : String(error)}.`);\n\t\t}\n\t}\n\n\trememberContent(sourcePath: string, content: string): FileContentReference {\n\t\tconst digest = createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n\t\treturn this.rememberContentDigest(sourcePath, digest, Buffer.byteLength(content, \"utf8\"));\n\t}\n\n\tasync retainMutationPayload(\n\t\tkind: FileMutationKind,\n\t\tpayload: string,\n\t): Promise<FileMutationPayloadReference | undefined> {\n\t\treturn this.runMutationPayloadOperation(async () => {\n\t\t\tawait this.pruneExpiredMutationPayloads();\n\t\t\tconst byteLength = Buffer.byteLength(payload, \"utf8\");\n\t\t\tif (byteLength > this.mutationPayloadByteLimit) return undefined;\n\t\t\twhile (\n\t\t\t\tthis.mutationPayloads.size >= this.mutationPayloadLimit ||\n\t\t\t\tthis.mutationPayloadBytes + byteLength > this.mutationPayloadByteLimit\n\t\t\t) {\n\t\t\t\tconst oldestPayloadRef = this.mutationPayloads.keys().next().value;\n\t\t\t\tif (typeof oldestPayloadRef !== \"string\") return undefined;\n\t\t\t\tawait this.deleteMutationPayload(oldestPayloadRef, true);\n\t\t\t}\n\n\t\t\t// Keep insertion and expiry order identical, even if the wall clock moves backward. Expiry\n\t\t\t// pruning can then stop at the first live record instead of rescanning every retained payload.\n\t\t\tconst expiresAt = Math.max(this.lastMutationPayloadExpiresAt, this.now() + this.mutationPayloadTtlMs);\n\t\t\tthis.lastMutationPayloadExpiresAt = expiresAt;\n\t\t\tlet sourcePath: string;\n\t\t\ttry {\n\t\t\t\tsourcePath = await this.operations.stagePayload(payload, this.mutationPayloadTtlMs);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof MutationPayloadCapacityError) return undefined;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst payloadRef = `${MUTATION_PAYLOAD_REFERENCE_PREFIX}${randomUUID()}`;\n\t\t\tthis.mutationPayloads.set(payloadRef, {\n\t\t\t\tpayloadRef,\n\t\t\t\tkind,\n\t\t\t\tsourcePath,\n\t\t\t\tdigest: createHash(\"sha256\").update(payload, \"utf8\").digest(\"hex\"),\n\t\t\t\tbyteLength,\n\t\t\t\texpiresAt,\n\t\t\t});\n\t\t\tthis.mutationPayloadBytes += byteLength;\n\t\t\treturn { payloadRef, byteLength };\n\t\t});\n\t}\n\n\tasync readMutationPayload(payloadRef: string, kind: FileMutationKind, signal?: AbortSignal): Promise<string> {\n\t\treturn this.runMutationPayloadOperation(async () => {\n\t\t\tconst record = await this.requireMutationPayload(payloadRef, kind, signal);\n\t\t\tconst payload = await this.operations.readPayload(record.sourcePath);\n\t\t\tif (createHash(\"sha256\").update(payload, \"utf8\").digest(\"hex\") !== record.digest) {\n\t\t\t\tawait this.deleteMutationPayload(payloadRef);\n\t\t\t\tthrow new Error(\"Retained file mutation payload changed after it was cached.\");\n\t\t\t}\n\t\t\treturn payload;\n\t\t});\n\t}\n\n\tasync assertMutationPayload(payloadRef: string, kind: FileMutationKind, signal?: AbortSignal): Promise<void> {\n\t\tawait this.runMutationPayloadOperation(async () => {\n\t\t\tawait this.requireVerifiedMutationPayload(payloadRef, kind, signal);\n\t\t});\n\t}\n\n\tasync assertContentReference(contentRef: string, signal?: AbortSignal): Promise<void> {\n\t\tawait this.requireVerifiedContentReference(contentRef, signal);\n\t}\n\n\tasync copyMutationPayload(\n\t\tpayloadRef: string,\n\t\ttargetPath: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<FileContentReference> {\n\t\treturn this.runMutationPayloadOperation(async () => {\n\t\t\tconst record = await this.requireVerifiedMutationPayload(payloadRef, \"write\", signal);\n\t\t\tawait this.copyVerifiedSource(record, targetPath, signal);\n\t\t\tawait this.deleteMutationPayload(payloadRef);\n\t\t\treturn this.rememberContentDigest(targetPath, record.digest, record.byteLength);\n\t\t});\n\t}\n\n\tasync discardMutationPayload(payloadRef: string): Promise<void> {\n\t\tawait this.runMutationPayloadOperation(() => this.deleteMutationPayload(payloadRef));\n\t}\n\n\t/** Release every payload owned by this controller without touching another controller's files. */\n\tasync dispose(): Promise<void> {\n\t\tawait this.runMutationPayloadOperation(async () => {\n\t\t\tlet firstError: unknown;\n\t\t\tfor (const payloadRef of [...this.mutationPayloads.keys()]) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.deleteMutationPayload(payloadRef, true);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfirstError ??= error;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (firstError) throw firstError;\n\t\t});\n\t}\n\n\tasync copyReferencedContent(\n\t\tcontentRef: string,\n\t\ttargetPath: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<FileContentReference> {\n\t\tconst record = await this.requireVerifiedContentReference(contentRef, signal);\n\t\tawait this.copyVerifiedSource(record, targetPath, signal);\n\t\treturn this.rememberContentDigest(targetPath, record.digest, record.byteLength);\n\t}\n\n\tprivate async requireVerifiedContentReference(\n\t\tcontentRef: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<ContentReferenceRecord> {\n\t\tthis.pruneExpired();\n\t\tconst record = this.contentReferences.get(contentRef);\n\t\tif (!record) throw new Error(\"File content reference is invalid, expired, or belongs to another session.\");\n\t\tthis.touchContentReference(record);\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\ttry {\n\t\t\tawait this.verifySource(record, signal, \"File content reference source\");\n\t\t} catch (error) {\n\t\t\tthis.contentReferences.delete(contentRef);\n\t\t\tthrow error;\n\t\t}\n\t\treturn record;\n\t}\n\n\tprivate async requireMutationPayload(\n\t\tpayloadRef: string,\n\t\tkind: FileMutationKind,\n\t\tsignal?: AbortSignal,\n\t): Promise<MutationPayloadRecord> {\n\t\tawait this.pruneExpiredMutationPayloads();\n\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\tconst record = this.mutationPayloads.get(payloadRef);\n\t\tif (!record) {\n\t\t\tthrow new Error(\"File mutation payload reference is invalid, expired, or belongs to another session.\");\n\t\t}\n\t\tif (record.kind !== kind) {\n\t\t\tthrow new Error(`File mutation payload reference is for ${record.kind}, not ${kind}.`);\n\t\t}\n\t\treturn record;\n\t}\n\n\tprivate async requireVerifiedMutationPayload(\n\t\tpayloadRef: string,\n\t\tkind: FileMutationKind,\n\t\tsignal?: AbortSignal,\n\t): Promise<MutationPayloadRecord> {\n\t\tconst record = await this.requireMutationPayload(payloadRef, kind, signal);\n\t\ttry {\n\t\t\tawait this.verifySource(record, signal, \"Retained file mutation payload\");\n\t\t} catch (error) {\n\t\t\tawait this.deleteMutationPayload(payloadRef);\n\t\t\tthrow error;\n\t\t}\n\t\treturn record;\n\t}\n\n\tprivate async verifySource(\n\t\trecord: Pick<MutationPayloadRecord, \"sourcePath\" | \"digest\">,\n\t\tsignal: AbortSignal | undefined,\n\t\tlabel: string,\n\t): Promise<void> {\n\t\tlet sourceDigest: string;\n\t\ttry {\n\t\t\tsourceDigest = await this.operations.hashFile(record.sourcePath, signal);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`${label} is unavailable: ${String(error)}.`);\n\t\t}\n\t\tif (sourceDigest !== record.digest) throw new Error(`${label} changed after it was cached.`);\n\t}\n\n\tprivate async copyVerifiedSource(\n\t\trecord: Pick<MutationPayloadRecord, \"sourcePath\" | \"digest\">,\n\t\ttargetPath: string,\n\t\tsignal?: AbortSignal,\n\t): Promise<void> {\n\t\tawait this.operations.copyFileExclusive(record.sourcePath, targetPath);\n\t\tlet targetDigest: string;\n\t\ttry {\n\t\t\ttargetDigest = await this.operations.hashFile(targetPath, signal);\n\t\t} catch (error) {\n\t\t\tawait this.removeFailedCopy(targetPath, error);\n\t\t\tthrow error;\n\t\t}\n\t\tif (targetDigest !== record.digest) {\n\t\t\tawait this.removeFailedCopy(targetPath, new Error(\"copied bytes did not match the cached digest\"));\n\t\t\tthrow new Error(\"File content copy changed while it was being created; the target was removed.\");\n\t\t}\n\t}\n\n\tprivate async assertCreatableParent(path: string): Promise<void> {\n\t\tlet candidate = dirname(path);\n\t\twhile (true) {\n\t\t\tconst inspection = await this.operations.inspect(candidate, true);\n\t\t\tif (inspection) {\n\t\t\t\tif (inspection.kind !== \"directory\") {\n\t\t\t\t\tthrow new Error(`Write parent path is not a directory: ${candidate}.`);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tawait this.operations.access(candidate, constants.W_OK);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst code = errorCode(error);\n\t\t\t\t\tthrow new Error(`Write parent is not writable: ${candidate}${code ? ` (${code})` : \"\"}.`);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst parent = dirname(candidate);\n\t\t\tif (parent === candidate) throw new Error(`No writable parent exists for ${path}.`);\n\t\t\tcandidate = parent;\n\t\t}\n\t}\n\n\tprivate rememberContentDigest(sourcePath: string, digest: string, byteLength: number): FileContentReference {\n\t\tthis.pruneExpired();\n\t\tfor (const existing of this.contentReferences.values()) {\n\t\t\tif (existing.sourcePath === sourcePath && existing.digest === digest) {\n\t\t\t\tthis.touchContentReference(existing);\n\t\t\t\treturn { contentRef: existing.contentRef, byteLength: existing.byteLength };\n\t\t\t}\n\t\t}\n\t\twhile (this.contentReferences.size >= this.contentReferenceLimit) {\n\t\t\tconst oldestContentRef = this.contentReferences.keys().next().value;\n\t\t\tif (typeof oldestContentRef !== \"string\") break;\n\t\t\tthis.contentReferences.delete(oldestContentRef);\n\t\t}\n\t\tconst contentRef = `${CONTENT_REFERENCE_PREFIX}${randomUUID()}`;\n\t\tthis.contentReferences.set(contentRef, {\n\t\t\tcontentRef,\n\t\t\tsourcePath,\n\t\t\tdigest,\n\t\t\tbyteLength,\n\t\t\texpiresAt: this.now() + this.contentReferenceTtlMs,\n\t\t});\n\t\treturn { contentRef, byteLength };\n\t}\n\n\tprivate touchContentReference(record: ContentReferenceRecord): void {\n\t\trecord.expiresAt = this.now() + this.contentReferenceTtlMs;\n\t\tthis.contentReferences.delete(record.contentRef);\n\t\tthis.contentReferences.set(record.contentRef, record);\n\t}\n\n\tprivate async removeFailedCopy(path: string, cause: unknown): Promise<void> {\n\t\ttry {\n\t\t\tawait this.operations.removeFile(path);\n\t\t} catch (cleanupError) {\n\t\t\tthrow new Error(\n\t\t\t\t`Copied content verification failed (${String(cause)}), and the newly created target could not be removed: ${String(cleanupError)}.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async deleteMutationPayload(payloadRef: string, strict = false): Promise<void> {\n\t\tconst record = this.mutationPayloads.get(payloadRef);\n\t\tif (!record) return;\n\t\tthis.mutationPayloads.delete(payloadRef);\n\t\tthis.mutationPayloadBytes -= record.byteLength;\n\t\ttry {\n\t\t\tawait this.operations.removeFile(record.sourcePath);\n\t\t} catch (error) {\n\t\t\tif (strict && !isMissingPathError(error)) throw error;\n\t\t}\n\t}\n\n\tprivate runMutationPayloadOperation<T>(operation: () => Promise<T>): Promise<T> {\n\t\tconst run = this.mutationPayloadOperationTail.then(operation, operation);\n\t\tthis.mutationPayloadOperationTail = run.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn run;\n\t}\n\n\tprivate async pruneExpiredMutationPayloads(): Promise<void> {\n\t\tconst now = this.now();\n\t\tfor (const [payloadRef, record] of this.mutationPayloads) {\n\t\t\tif (record.expiresAt > now) break;\n\t\t\tawait this.deleteMutationPayload(payloadRef, true);\n\t\t}\n\t}\n\n\tprivate pruneExpired(): void {\n\t\tconst now = this.now();\n\t\tfor (const [contentRef, record] of this.contentReferences) {\n\t\t\tif (record.expiresAt <= now) this.contentReferences.delete(contentRef);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -4,7 +4,7 @@ export { type BashOperations, type BashSpawnContext, type BashSpawnHook, type Ba
|
|
|
4
4
|
export { CONTEXT_SCOUT_GUIDANCE, type ContextScoutToolDependencies, type ContextScoutToolDetails, type ContextScoutToolInput, createContextScoutToolDefinition, formatScoutResult, } from "./context-scout.ts";
|
|
5
5
|
export { createEditTool, createEditToolDefinition, type EditOperations, type EditToolDetails, type EditToolInput, type EditToolOptions, } from "./edit.ts";
|
|
6
6
|
export { createExtensionifyTool, createExtensionifyToolDefinition, type ExtensionifyInput, type ExtensionifyReport, type ExtensionifyToolDetails, type ExtensionifyToolOptions, } from "./extensionify.ts";
|
|
7
|
-
export { type FileContentReference, FileMutationIntentController, type FileMutationIntentControllerOptions, type FileMutationIntentOperations, type FileMutationKind, type FileMutationLease, type
|
|
7
|
+
export { type FileContentReference, FileMutationIntentController, type FileMutationIntentControllerOptions, type FileMutationIntentOperations, type FileMutationKind, type FileMutationLease, type FileMutationPayloadReference, FileMutationPreflightError, type FileMutationPreflightFailureReason, type FilePathIdentity, type FilePathInspection, localFileMutationIntentOperations, } from "./file-mutation-intent.ts";
|
|
8
8
|
export { withFileMutationQueue } from "./file-mutation-queue.ts";
|
|
9
9
|
export { createFindTool, createFindToolDefinition, type FindOperations, type FindToolDetails, type FindToolInput, type FindToolOptions, } from "./find.ts";
|
|
10
10
|
export { createGrepTool, createGrepToolDefinition, type GrepOperations, type GrepToolDetails, type GrepToolInput, type GrepToolOptions, } from "./grep.ts";
|