clinkx 0.2.3 → 0.3.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.
- package/README.md +40 -1
- package/conf/prompts/json-codereviewer.txt +1 -0
- package/conf/prompts/json.txt +1 -0
- package/dist/allowed-roots.d.ts +12 -0
- package/dist/allowed-roots.js +94 -0
- package/dist/allowed-roots.js.map +1 -0
- package/dist/context-builder/anchors.d.ts +11 -0
- package/dist/context-builder/anchors.js +19 -0
- package/dist/context-builder/anchors.js.map +1 -0
- package/dist/context-builder/bundle.d.ts +76 -0
- package/dist/context-builder/bundle.js +3573 -0
- package/dist/context-builder/bundle.js.map +1 -0
- package/dist/context-builder/codemap.d.ts +17 -0
- package/dist/context-builder/codemap.js +301 -0
- package/dist/context-builder/codemap.js.map +1 -0
- package/dist/context-builder/config.d.ts +60 -0
- package/dist/context-builder/config.js +204 -0
- package/dist/context-builder/config.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +182 -0
- package/dist/context-builder/delegate-packet.js +898 -0
- package/dist/context-builder/delegate-packet.js.map +1 -0
- package/dist/context-builder/delegate-reconstruction.d.ts +13 -0
- package/dist/context-builder/delegate-reconstruction.js +220 -0
- package/dist/context-builder/delegate-reconstruction.js.map +1 -0
- package/dist/context-builder/delegate-validation.d.ts +18 -0
- package/dist/context-builder/delegate-validation.js +367 -0
- package/dist/context-builder/delegate-validation.js.map +1 -0
- package/dist/context-builder/delegate.d.ts +24 -0
- package/dist/context-builder/delegate.js +133 -0
- package/dist/context-builder/delegate.js.map +1 -0
- package/dist/context-builder/diagnostics.d.ts +27 -0
- package/dist/context-builder/diagnostics.js +74 -0
- package/dist/context-builder/diagnostics.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +118 -0
- package/dist/context-builder/discovery.js +1829 -0
- package/dist/context-builder/discovery.js.map +1 -0
- package/dist/context-builder/globs.d.ts +14 -0
- package/dist/context-builder/globs.js +175 -0
- package/dist/context-builder/globs.js.map +1 -0
- package/dist/context-builder/handler.d.ts +17 -0
- package/dist/context-builder/handler.js +3848 -0
- package/dist/context-builder/handler.js.map +1 -0
- package/dist/context-builder/path-signals.d.ts +7 -0
- package/dist/context-builder/path-signals.js +66 -0
- package/dist/context-builder/path-signals.js.map +1 -0
- package/dist/context-builder/paths.d.ts +30 -0
- package/dist/context-builder/paths.js +153 -0
- package/dist/context-builder/paths.js.map +1 -0
- package/dist/context-builder/progress.d.ts +15 -0
- package/dist/context-builder/progress.js +70 -0
- package/dist/context-builder/progress.js.map +1 -0
- package/dist/context-builder/receipt.d.ts +89 -0
- package/dist/context-builder/receipt.js +169 -0
- package/dist/context-builder/receipt.js.map +1 -0
- package/dist/context-builder/run-id.d.ts +7 -0
- package/dist/context-builder/run-id.js +31 -0
- package/dist/context-builder/run-id.js.map +1 -0
- package/dist/context-builder/schema.d.ts +431 -0
- package/dist/context-builder/schema.js +293 -0
- package/dist/context-builder/schema.js.map +1 -0
- package/dist/context-builder/secrets.d.ts +13 -0
- package/dist/context-builder/secrets.js +222 -0
- package/dist/context-builder/secrets.js.map +1 -0
- package/dist/context-builder/selection.d.ts +52 -0
- package/dist/context-builder/selection.js +1649 -0
- package/dist/context-builder/selection.js.map +1 -0
- package/dist/context-builder/status.d.ts +45 -0
- package/dist/context-builder/status.js +105 -0
- package/dist/context-builder/status.js.map +1 -0
- package/dist/context-builder/storage.d.ts +85 -0
- package/dist/context-builder/storage.js +746 -0
- package/dist/context-builder/storage.js.map +1 -0
- package/dist/context-builder/token-estimate.d.ts +8 -0
- package/dist/context-builder/token-estimate.js +17 -0
- package/dist/context-builder/token-estimate.js.map +1 -0
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +31 -43
- package/dist/paths.js.map +1 -1
- package/dist/pipeline.d.ts +47 -3
- package/dist/pipeline.js +135 -39
- package/dist/pipeline.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +40 -10
- package/dist/server.js.map +1 -1
- package/package.json +2 -1
- package/dist/artifacts.d.ts +0 -9
- package/dist/artifacts.js +0 -24
- package/dist/artifacts.js.map +0 -1
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
import { link as fsLink, open as fsOpen, mkdir as fsMkdir, readFile as fsReadFile, rename as fsRename, rm as fsRm, stat as fsStat, writeFile as fsWriteFile, } from "node:fs/promises";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { hostname } from "node:os";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
|
+
import { CancellationError, ExecutionError, InvalidParamsError } from "../errors.js";
|
|
6
|
+
import { validateProspectiveChildPath } from "../paths.js";
|
|
7
|
+
import { contextBuilderInternalCapsForManifest, CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS, resolveContextBuilderRunsDir, } from "./config.js";
|
|
8
|
+
import { boundContextBuilderOmissions, boundContextBuilderWarnings, } from "./diagnostics.js";
|
|
9
|
+
import { cloneContextBuilderDelegateReceiptMetadata, CONTEXT_BUILDER_DELEGATE_SCHEMA_VERSION, CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS, CONTEXT_BUILDER_NO_DELEGATE_SCHEMA_VERSION, CONTEXT_BUILDER_SCHEMA_VERSION, isContextBuilderDelegateReceiptMetadata, } from "./receipt.js";
|
|
10
|
+
import { validateCallerRunId } from "./run-id.js";
|
|
11
|
+
import { scanSecretPath } from "./secrets.js";
|
|
12
|
+
export const CONTEXT_BUILDER_ARTIFACT_FILE_NAMES = Object.freeze({
|
|
13
|
+
manifest_json: "manifest.json",
|
|
14
|
+
bundle_md: "bundle.md",
|
|
15
|
+
tree_json: "tree.json",
|
|
16
|
+
search_jsonl: "search.jsonl",
|
|
17
|
+
slices_jsonl: "slices.jsonl",
|
|
18
|
+
codemap_jsonl: "codemap.jsonl",
|
|
19
|
+
summary_json: "summary.json",
|
|
20
|
+
});
|
|
21
|
+
const DEFAULT_STORAGE_OPS = {
|
|
22
|
+
mkdir: fsMkdir,
|
|
23
|
+
writeFile: fsWriteFile,
|
|
24
|
+
readFile: fsReadFile,
|
|
25
|
+
rename: fsRename,
|
|
26
|
+
rm: fsRm,
|
|
27
|
+
open: fsOpen,
|
|
28
|
+
link: fsLink,
|
|
29
|
+
stat: fsStat,
|
|
30
|
+
};
|
|
31
|
+
const CONTEXT_BUILDER_METRIC_TYPES = Object.freeze({
|
|
32
|
+
files_seen: "number",
|
|
33
|
+
files_considered: "number",
|
|
34
|
+
files_selected: "number",
|
|
35
|
+
files_omitted: "number",
|
|
36
|
+
scan_cap_hit: "boolean",
|
|
37
|
+
search_cap_hit: "boolean",
|
|
38
|
+
discovery_byte_cap_hit: "boolean",
|
|
39
|
+
discovery_candidate_cap_hit: "boolean",
|
|
40
|
+
candidate_cap_hit: "boolean",
|
|
41
|
+
bytes_read: "number",
|
|
42
|
+
bytes_written: "number",
|
|
43
|
+
estimated_tokens: "number",
|
|
44
|
+
truncated: "boolean",
|
|
45
|
+
});
|
|
46
|
+
const CONTEXT_BUILDER_INTERNAL_CAP_KEYS = Object.keys(CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS);
|
|
47
|
+
const CONTEXT_BUILDER_MUTATION_LOCK_SUFFIX = ".mutation.lock";
|
|
48
|
+
const CONTEXT_BUILDER_MUTATION_LOCK_ATTEMPTS = 1_000;
|
|
49
|
+
const CONTEXT_BUILDER_MUTATION_LOCK_RETRY_MS = 10;
|
|
50
|
+
const CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS = 10 * 60 * 1_000;
|
|
51
|
+
export function resolveContextBuilderRunPaths(runsDir, runId) {
|
|
52
|
+
validateCallerRunId(runId);
|
|
53
|
+
const resolvedRunsDir = resolve(runsDir);
|
|
54
|
+
const runDir = validateProspectiveChildPath(resolvedRunsDir, runId);
|
|
55
|
+
if (dirname(runDir) !== resolvedRunsDir || basename(runDir) !== runId) {
|
|
56
|
+
throw new InvalidParamsError("context_builder run_id escaped runs directory");
|
|
57
|
+
}
|
|
58
|
+
const artifactDir = join(runDir, "artifacts");
|
|
59
|
+
const artifacts = Object.fromEntries(CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS.map((key) => [
|
|
60
|
+
key,
|
|
61
|
+
key === "manifest_json"
|
|
62
|
+
? join(runDir, CONTEXT_BUILDER_ARTIFACT_FILE_NAMES[key])
|
|
63
|
+
: join(artifactDir, CONTEXT_BUILDER_ARTIFACT_FILE_NAMES[key]),
|
|
64
|
+
]));
|
|
65
|
+
return {
|
|
66
|
+
runId,
|
|
67
|
+
runsDir: resolvedRunsDir,
|
|
68
|
+
runDir,
|
|
69
|
+
artifactDir,
|
|
70
|
+
buildingJson: join(runDir, ".building.json"),
|
|
71
|
+
manifestJson: artifacts.manifest_json,
|
|
72
|
+
artifacts,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export async function createContextBuilderRun(input, ops = DEFAULT_STORAGE_OPS) {
|
|
76
|
+
const runsDir = await resolveContextBuilderRunsDir(input.runsDir);
|
|
77
|
+
const ownerNonce = input.ownerNonce ?? randomBytes(16).toString("hex");
|
|
78
|
+
const paths = {
|
|
79
|
+
...resolveContextBuilderRunPaths(runsDir, input.runId),
|
|
80
|
+
ownerNonce,
|
|
81
|
+
};
|
|
82
|
+
let claimedRunDir = false;
|
|
83
|
+
try {
|
|
84
|
+
await claimContextBuilderRunDir(paths, ops);
|
|
85
|
+
claimedRunDir = true;
|
|
86
|
+
await writeAtomicText(paths.buildingJson, `${JSON.stringify({
|
|
87
|
+
schema_version: CONTEXT_BUILDER_SCHEMA_VERSION,
|
|
88
|
+
run_id: input.runId,
|
|
89
|
+
pid: process.pid,
|
|
90
|
+
hostname: hostname(),
|
|
91
|
+
created_at: input.createdAt,
|
|
92
|
+
owner_nonce: ownerNonce,
|
|
93
|
+
})}\n`, ops);
|
|
94
|
+
await ops.mkdir(paths.artifactDir, { recursive: false, mode: 0o700 });
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (claimedRunDir) {
|
|
98
|
+
const cleanupDecision = await canRemoveIncompleteRunDir(paths, ownerNonce, ops);
|
|
99
|
+
if (cleanupDecision.removable) {
|
|
100
|
+
await (ops.rm ?? fsRm)(paths.runDir, {
|
|
101
|
+
recursive: true,
|
|
102
|
+
force: true,
|
|
103
|
+
}).catch(() => { });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new ExecutionError(`context_builder refused setup cleanup for ${input.runId}: ${cleanupDecision.reason}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return paths;
|
|
112
|
+
}
|
|
113
|
+
export async function writeContextBuilderArtifact(storage, key, content, ops = DEFAULT_STORAGE_OPS) {
|
|
114
|
+
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
115
|
+
await withContextBuilderMutationLock(canonicalStorage, ops, async () => {
|
|
116
|
+
await assertManifestAbsentForMutation(canonicalStorage, ops);
|
|
117
|
+
await writeAtomicText(canonicalStorage.artifacts[key], content, ops, {
|
|
118
|
+
mustNotExistPath: canonicalStorage.manifestJson,
|
|
119
|
+
});
|
|
120
|
+
}, { ownerNonce: canonicalStorage.ownerNonce });
|
|
121
|
+
}
|
|
122
|
+
export async function commitContextBuilderRun(storage, manifest, optionsOrOps = {}, maybeOps = DEFAULT_STORAGE_OPS) {
|
|
123
|
+
const { options, ops } = normalizeCommitArguments(optionsOrOps, maybeOps);
|
|
124
|
+
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
125
|
+
const ownerNonce = options.ownerNonce ?? canonicalStorage.ownerNonce;
|
|
126
|
+
await withContextBuilderMutationLock(canonicalStorage, ops, async () => {
|
|
127
|
+
await assertManifestAbsentForMutation(canonicalStorage, ops);
|
|
128
|
+
await assertRequiredArtifactsReadable(canonicalStorage, ops);
|
|
129
|
+
throwIfCancelledBeforeManifest(options);
|
|
130
|
+
const internalCaps = contextBuilderInternalCapsForManifest(manifest.internal_caps);
|
|
131
|
+
const { delegate, ...manifestWithoutDelegate } = manifest;
|
|
132
|
+
const boundedDelegate = normalizeManifestDelegateMetadata(delegate);
|
|
133
|
+
const completeManifest = {
|
|
134
|
+
...manifestWithoutDelegate,
|
|
135
|
+
schema_version: schemaVersionForManifestDelegate(boundedDelegate),
|
|
136
|
+
run_id: canonicalStorage.runId,
|
|
137
|
+
internal_caps: internalCaps,
|
|
138
|
+
artifacts: { ...canonicalStorage.artifacts },
|
|
139
|
+
warnings: boundContextBuilderWarnings(normalizeContextBuilderWarnings(manifest.warnings), internalCaps),
|
|
140
|
+
omissions: boundContextBuilderOmissions(normalizeContextBuilderOmissions(manifest.omissions), internalCaps),
|
|
141
|
+
...(boundedDelegate == null ? {} : { delegate: boundedDelegate }),
|
|
142
|
+
};
|
|
143
|
+
await writeExclusiveText(canonicalStorage.manifestJson, `${JSON.stringify(completeManifest)}\n`, ops, options.signal === undefined ? {} : { signal: options.signal });
|
|
144
|
+
}, { ownerNonce });
|
|
145
|
+
}
|
|
146
|
+
export async function writeContextBuilderArtifactsAndCommit(storage, contents, manifest, options = {}) {
|
|
147
|
+
const ops = options.ops ?? DEFAULT_STORAGE_OPS;
|
|
148
|
+
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
149
|
+
const ownerNonce = options.ownerNonce ?? canonicalStorage.ownerNonce;
|
|
150
|
+
await withContextBuilderMutationLock(canonicalStorage, ops, async () => {
|
|
151
|
+
await assertManifestAbsentForMutation(canonicalStorage, ops);
|
|
152
|
+
for (const [key, content] of Object.entries(contents)) {
|
|
153
|
+
if (options.signal?.aborted === true) {
|
|
154
|
+
throw new CancellationError();
|
|
155
|
+
}
|
|
156
|
+
await writeAtomicText(canonicalStorage.artifacts[key], content, ops, { mustNotExistPath: canonicalStorage.manifestJson });
|
|
157
|
+
}
|
|
158
|
+
throwIfCancelledBeforeManifest(options.signal === undefined ? {} : { signal: options.signal });
|
|
159
|
+
await assertRequiredArtifactsReadable(canonicalStorage, ops);
|
|
160
|
+
const internalCaps = contextBuilderInternalCapsForManifest(manifest.internal_caps);
|
|
161
|
+
const { delegate, ...manifestWithoutDelegate } = manifest;
|
|
162
|
+
const boundedDelegate = normalizeManifestDelegateMetadata(delegate);
|
|
163
|
+
const completeManifest = {
|
|
164
|
+
...manifestWithoutDelegate,
|
|
165
|
+
schema_version: schemaVersionForManifestDelegate(boundedDelegate),
|
|
166
|
+
run_id: canonicalStorage.runId,
|
|
167
|
+
internal_caps: internalCaps,
|
|
168
|
+
artifacts: { ...canonicalStorage.artifacts },
|
|
169
|
+
warnings: boundContextBuilderWarnings(normalizeContextBuilderWarnings(manifest.warnings), internalCaps),
|
|
170
|
+
omissions: boundContextBuilderOmissions(normalizeContextBuilderOmissions(manifest.omissions), internalCaps),
|
|
171
|
+
...(boundedDelegate == null ? {} : { delegate: boundedDelegate }),
|
|
172
|
+
};
|
|
173
|
+
await writeExclusiveText(canonicalStorage.manifestJson, `${JSON.stringify(completeManifest)}\n`, ops, options.signal === undefined ? {} : { signal: options.signal });
|
|
174
|
+
}, { ownerNonce });
|
|
175
|
+
}
|
|
176
|
+
export async function cleanupContextBuilderIncompleteRun(storage, ownerNonce, ops = DEFAULT_STORAGE_OPS) {
|
|
177
|
+
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
178
|
+
return withContextBuilderMutationLock(canonicalStorage, ops, async () => cleanupContextBuilderIncompleteRunLocked(canonicalStorage, ownerNonce, ops), {
|
|
179
|
+
ownerNonce,
|
|
180
|
+
recoverStaleForCleanup: true,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
async function cleanupContextBuilderIncompleteRunLocked(storage, ownerNonce, ops) {
|
|
184
|
+
if (!await mayRemoveIncompleteRunDir(storage, ownerNonce, ops)) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
await (ops.rm ?? fsRm)(storage.runDir, {
|
|
188
|
+
recursive: true,
|
|
189
|
+
force: true,
|
|
190
|
+
});
|
|
191
|
+
await syncDirectoryBestEffort(storage.runsDir, ops);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
export async function isContextBuilderRunCommitted(runDir, ops = DEFAULT_STORAGE_OPS) {
|
|
195
|
+
try {
|
|
196
|
+
const resolvedRunDir = resolve(runDir);
|
|
197
|
+
const parsed = JSON.parse(await ops.readFile(join(resolvedRunDir, "manifest.json"), "utf8"));
|
|
198
|
+
if (!isValidContextBuilderManifest(parsed, basename(resolvedRunDir))) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
const expectedStorage = resolveContextBuilderRunPaths(dirname(resolvedRunDir), parsed.run_id);
|
|
202
|
+
if (expectedStorage.runDir !== resolvedRunDir) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
if (!artifactPathsExactlyMatch(parsed.artifacts, expectedStorage.artifacts)) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
await assertRequiredArtifactsReadable(expectedStorage, ops);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function canonicalizeContextBuilderStorage(storage) {
|
|
216
|
+
const expected = resolveContextBuilderRunPaths(storage.runsDir, storage.runId);
|
|
217
|
+
if (storage.runDir !== expected.runDir
|
|
218
|
+
|| storage.artifactDir !== expected.artifactDir
|
|
219
|
+
|| storage.buildingJson !== expected.buildingJson
|
|
220
|
+
|| storage.manifestJson !== expected.manifestJson
|
|
221
|
+
|| !artifactPathsExactlyMatch(storage.artifacts, expected.artifacts)) {
|
|
222
|
+
throw new ExecutionError("context_builder storage paths do not match run_id and runs_dir");
|
|
223
|
+
}
|
|
224
|
+
return storage.ownerNonce === undefined
|
|
225
|
+
? expected
|
|
226
|
+
: { ...expected, ownerNonce: storage.ownerNonce };
|
|
227
|
+
}
|
|
228
|
+
async function mayRemoveIncompleteRunDir(storage, ownerNonce, ops) {
|
|
229
|
+
return (await canRemoveIncompleteRunDir(storage, ownerNonce, ops)).removable;
|
|
230
|
+
}
|
|
231
|
+
async function canRemoveIncompleteRunDir(storage, ownerNonce, ops) {
|
|
232
|
+
const resolvedRunsDir = resolve(storage.runsDir);
|
|
233
|
+
const resolvedRunDir = resolve(storage.runDir);
|
|
234
|
+
if (!isPathInsideDirectory(resolvedRunDir, resolvedRunsDir)) {
|
|
235
|
+
return { removable: false, reason: "run directory is outside runs directory" };
|
|
236
|
+
}
|
|
237
|
+
const manifestDecision = await manifestCleanupDecision(storage, ops);
|
|
238
|
+
if (!manifestDecision.removable) {
|
|
239
|
+
return manifestDecision;
|
|
240
|
+
}
|
|
241
|
+
let building;
|
|
242
|
+
try {
|
|
243
|
+
building = JSON.parse(await ops.readFile(storage.buildingJson, "utf8"));
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return { removable: false, reason: "missing marker" };
|
|
247
|
+
}
|
|
248
|
+
if (building === null
|
|
249
|
+
|| typeof building !== "object"
|
|
250
|
+
|| building.owner_nonce !== ownerNonce) {
|
|
251
|
+
return { removable: false, reason: "owner mismatch" };
|
|
252
|
+
}
|
|
253
|
+
return { removable: true };
|
|
254
|
+
}
|
|
255
|
+
async function manifestCleanupDecision(storage, ops) {
|
|
256
|
+
try {
|
|
257
|
+
await ops.readFile(storage.manifestJson, "utf8");
|
|
258
|
+
return { removable: false, reason: "manifest exists" };
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
262
|
+
return { removable: true };
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
removable: false,
|
|
266
|
+
reason: `manifest unreadable: ${nodeErrorCode(error)}`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async function claimContextBuilderRunDir(paths, ops) {
|
|
271
|
+
try {
|
|
272
|
+
await ops.mkdir(paths.runDir, { recursive: false, mode: 0o700 });
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
276
|
+
if (await isContextBuilderRunCommitted(paths.runDir, ops)) {
|
|
277
|
+
throw new InvalidParamsError(`context_builder run_id already exists: ${paths.runId}`);
|
|
278
|
+
}
|
|
279
|
+
throw new ExecutionError(`context_builder run_id conflicts with an incomplete or active run: ${paths.runId}`);
|
|
280
|
+
}
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function throwIfCancelledBeforeManifest(options) {
|
|
285
|
+
if (options.signal?.aborted !== true) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
throw new CancellationError();
|
|
289
|
+
}
|
|
290
|
+
function normalizeCommitArguments(optionsOrOps, maybeOps) {
|
|
291
|
+
if (isStorageOps(optionsOrOps)) {
|
|
292
|
+
return { options: {}, ops: optionsOrOps };
|
|
293
|
+
}
|
|
294
|
+
return { options: optionsOrOps, ops: maybeOps };
|
|
295
|
+
}
|
|
296
|
+
function isStorageOps(value) {
|
|
297
|
+
return ("mkdir" in value
|
|
298
|
+
|| "writeFile" in value
|
|
299
|
+
|| "readFile" in value
|
|
300
|
+
|| "rename" in value
|
|
301
|
+
|| "rm" in value
|
|
302
|
+
|| "open" in value
|
|
303
|
+
|| "link" in value);
|
|
304
|
+
}
|
|
305
|
+
async function withContextBuilderMutationLock(storage, ops, callback, options = {}) {
|
|
306
|
+
const lockPath = mutationLockPathForStorage(storage);
|
|
307
|
+
const lockHandle = await acquireContextBuilderMutationLock(storage, lockPath, ops, options);
|
|
308
|
+
try {
|
|
309
|
+
return await callback();
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
await lockHandle.close().catch(() => { });
|
|
313
|
+
await (ops.rm ?? fsRm)(lockPath, { force: true }).catch(() => { });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function acquireContextBuilderMutationLock(storage, lockPath, ops, options) {
|
|
317
|
+
const open = ops.open ?? fsOpen;
|
|
318
|
+
for (let attempt = 0; attempt < CONTEXT_BUILDER_MUTATION_LOCK_ATTEMPTS; attempt += 1) {
|
|
319
|
+
try {
|
|
320
|
+
const handle = await open(lockPath, "wx", 0o600);
|
|
321
|
+
try {
|
|
322
|
+
await writeMutationLockMetadata(handle, options.ownerNonce);
|
|
323
|
+
return handle;
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
await handle.close().catch(() => { });
|
|
327
|
+
await (ops.rm ?? fsRm)(lockPath, { force: true }).catch(() => { });
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
333
|
+
if (options.recoverStaleForCleanup === true
|
|
334
|
+
&& await breakStaleCleanupMutationLock(storage, lockPath, options.ownerNonce, ops)) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
await delay(CONTEXT_BUILDER_MUTATION_LOCK_RETRY_MS);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
throw new ExecutionError("context_builder timed out waiting for the run mutation lock");
|
|
344
|
+
}
|
|
345
|
+
function mutationLockPathForStorage(storage) {
|
|
346
|
+
validateCallerRunId(storage.runId);
|
|
347
|
+
const resolvedRunsDir = resolve(storage.runsDir);
|
|
348
|
+
const resolvedRunDir = resolve(storage.runDir);
|
|
349
|
+
if (!isPathInsideDirectory(resolvedRunDir, resolvedRunsDir)) {
|
|
350
|
+
throw new ExecutionError("context_builder refused mutation lock outside runs directory");
|
|
351
|
+
}
|
|
352
|
+
return join(resolvedRunsDir, `.${storage.runId}${CONTEXT_BUILDER_MUTATION_LOCK_SUFFIX}`);
|
|
353
|
+
}
|
|
354
|
+
async function writeMutationLockMetadata(handle, ownerNonce) {
|
|
355
|
+
const metadata = {
|
|
356
|
+
pid: process.pid,
|
|
357
|
+
hostname: hostname(),
|
|
358
|
+
created_at: new Date().toISOString(),
|
|
359
|
+
...(ownerNonce == null ? {} : { owner_nonce: ownerNonce }),
|
|
360
|
+
};
|
|
361
|
+
await handle.writeFile(`${JSON.stringify(metadata)}\n`, { encoding: "utf8" });
|
|
362
|
+
}
|
|
363
|
+
async function breakStaleCleanupMutationLock(storage, lockPath, ownerNonce, ops) {
|
|
364
|
+
if (ownerNonce == null) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
const cleanupDecision = await canRemoveIncompleteRunDir(storage, ownerNonce, ops);
|
|
368
|
+
if (!cleanupDecision.removable) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
if (!await isMutationLockOldEnough(lockPath, ops)) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
const metadata = await readMutationLockMetadata(lockPath, ops);
|
|
375
|
+
if (metadata !== "invalid" && !isStaleMutationLockMetadata(metadata)) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
await (ops.rm ?? fsRm)(lockPath, { force: true }).catch(() => { });
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
async function isMutationLockOldEnough(lockPath, ops) {
|
|
382
|
+
const stat = ops.stat ?? fsStat;
|
|
383
|
+
try {
|
|
384
|
+
const lockStat = await stat(lockPath);
|
|
385
|
+
return Date.now() - lockStat.mtimeMs >= CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS;
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async function readMutationLockMetadata(lockPath, ops) {
|
|
392
|
+
let parsed;
|
|
393
|
+
try {
|
|
394
|
+
parsed = JSON.parse(await ops.readFile(lockPath, "utf8"));
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return "invalid";
|
|
398
|
+
}
|
|
399
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
400
|
+
return "invalid";
|
|
401
|
+
}
|
|
402
|
+
const metadata = parsed;
|
|
403
|
+
if (typeof metadata.pid !== "number"
|
|
404
|
+
|| !Number.isInteger(metadata.pid)
|
|
405
|
+
|| typeof metadata.hostname !== "string"
|
|
406
|
+
|| typeof metadata.created_at !== "string") {
|
|
407
|
+
return "invalid";
|
|
408
|
+
}
|
|
409
|
+
if (metadata.owner_nonce !== undefined
|
|
410
|
+
&& typeof metadata.owner_nonce !== "string") {
|
|
411
|
+
return "invalid";
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
pid: metadata.pid,
|
|
415
|
+
hostname: metadata.hostname,
|
|
416
|
+
created_at: metadata.created_at,
|
|
417
|
+
...(metadata.owner_nonce === undefined
|
|
418
|
+
? {}
|
|
419
|
+
: { owner_nonce: metadata.owner_nonce }),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function isStaleMutationLockMetadata(metadata) {
|
|
423
|
+
const createdAtMs = Date.parse(metadata.created_at);
|
|
424
|
+
if (!Number.isFinite(createdAtMs)
|
|
425
|
+
|| Date.now() - createdAtMs < CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
if (metadata.hostname !== hostname()) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
return !isProcessAlive(metadata.pid);
|
|
432
|
+
}
|
|
433
|
+
function isProcessAlive(pid) {
|
|
434
|
+
if (pid <= 0) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
process.kill(pid, 0);
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
return !(isNodeError(error) && error.code === "ESRCH");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async function assertRequiredArtifactsReadable(storage, ops) {
|
|
446
|
+
const missing = [];
|
|
447
|
+
for (const key of CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS) {
|
|
448
|
+
if (key === "manifest_json") {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
await ops.readFile(storage.artifacts[key], "utf8");
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
missing.push(key);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (missing.length > 0) {
|
|
459
|
+
throw new Error(`context_builder cannot commit before required artifacts exist: ${missing.join(", ")}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async function assertManifestAbsentForMutation(storage, ops) {
|
|
463
|
+
try {
|
|
464
|
+
await ops.readFile(storage.manifestJson, "utf8");
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
throw new ExecutionError(`context_builder cannot mutate committed run: manifest unreadable (${nodeErrorCode(error)})`);
|
|
471
|
+
}
|
|
472
|
+
throw new ExecutionError("context_builder cannot mutate committed run: manifest already exists");
|
|
473
|
+
}
|
|
474
|
+
function artifactPathsExactlyMatch(actual, expected) {
|
|
475
|
+
if (!hasExactArtifactKeys(actual)) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
for (const key of CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS) {
|
|
479
|
+
if (actual[key] !== expected[key]) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
function isValidContextBuilderManifest(value, expectedRunId) {
|
|
486
|
+
if (value === null || typeof value !== "object") {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
const manifest = value;
|
|
490
|
+
const hasDelegate = Object.prototype.hasOwnProperty.call(manifest, "delegate");
|
|
491
|
+
const hasValidDelegate = isContextBuilderDelegateReceiptMetadata(manifest.delegate);
|
|
492
|
+
const hasValidVersionedDelegate = (manifest.schema_version === CONTEXT_BUILDER_NO_DELEGATE_SCHEMA_VERSION
|
|
493
|
+
&& !hasDelegate)
|
|
494
|
+
|| (manifest.schema_version === CONTEXT_BUILDER_DELEGATE_SCHEMA_VERSION
|
|
495
|
+
&& hasDelegate
|
|
496
|
+
&& hasValidDelegate);
|
|
497
|
+
return (hasValidVersionedDelegate
|
|
498
|
+
&& manifest.run_id === expectedRunId
|
|
499
|
+
&& (manifest.status === "succeeded" || manifest.status === "partial")
|
|
500
|
+
&& hasRequiredInternalCaps(manifest.internal_caps)
|
|
501
|
+
&& hasRequiredArtifactPaths(manifest.artifacts)
|
|
502
|
+
&& hasRequiredMetrics(manifest.metrics)
|
|
503
|
+
&& Array.isArray(manifest.warnings)
|
|
504
|
+
&& Array.isArray(manifest.omissions));
|
|
505
|
+
}
|
|
506
|
+
function normalizeManifestDelegateMetadata(delegate) {
|
|
507
|
+
if (delegate === undefined) {
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
if (delegate === null || typeof delegate !== "object") {
|
|
511
|
+
throw new ExecutionError("context_builder manifest delegate metadata is invalid");
|
|
512
|
+
}
|
|
513
|
+
const bounded = cloneContextBuilderDelegateReceiptMetadata(delegate);
|
|
514
|
+
if (!isContextBuilderDelegateReceiptMetadata(bounded)) {
|
|
515
|
+
throw new ExecutionError("context_builder manifest delegate metadata is invalid");
|
|
516
|
+
}
|
|
517
|
+
return bounded;
|
|
518
|
+
}
|
|
519
|
+
function schemaVersionForManifestDelegate(delegate) {
|
|
520
|
+
return delegate == null
|
|
521
|
+
? CONTEXT_BUILDER_NO_DELEGATE_SCHEMA_VERSION
|
|
522
|
+
: CONTEXT_BUILDER_DELEGATE_SCHEMA_VERSION;
|
|
523
|
+
}
|
|
524
|
+
function hasRequiredInternalCaps(value) {
|
|
525
|
+
if (value === null || typeof value !== "object") {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
const caps = value;
|
|
529
|
+
return CONTEXT_BUILDER_INTERNAL_CAP_KEYS.every((key) => (typeof caps[key] === "number"));
|
|
530
|
+
}
|
|
531
|
+
function hasRequiredArtifactPaths(value) {
|
|
532
|
+
if (value === null || typeof value !== "object") {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
const artifacts = value;
|
|
536
|
+
return hasExactArtifactKeys(artifacts) && CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS.every((key) => (typeof artifacts[key] === "string"));
|
|
537
|
+
}
|
|
538
|
+
function hasExactArtifactKeys(value) {
|
|
539
|
+
const expectedKeys = new Set(CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS);
|
|
540
|
+
const actualKeys = Object.keys(value);
|
|
541
|
+
return (actualKeys.length === CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS.length
|
|
542
|
+
&& actualKeys.every((key) => expectedKeys.has(key)));
|
|
543
|
+
}
|
|
544
|
+
function hasRequiredMetrics(value) {
|
|
545
|
+
if (value === null || typeof value !== "object") {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
const metrics = value;
|
|
549
|
+
return Object.entries(CONTEXT_BUILDER_METRIC_TYPES).every(([key, expectedType]) => typeof metrics[key] === expectedType);
|
|
550
|
+
}
|
|
551
|
+
async function writeAtomicText(finalPath, content, ops, options = {}) {
|
|
552
|
+
let tempPath = "";
|
|
553
|
+
let handle;
|
|
554
|
+
try {
|
|
555
|
+
const opened = await openExclusiveTempFile(finalPath, ops);
|
|
556
|
+
tempPath = opened.tempPath;
|
|
557
|
+
handle = opened.handle;
|
|
558
|
+
await handle.writeFile(content, { encoding: "utf8" });
|
|
559
|
+
await handle.sync();
|
|
560
|
+
await handle.close();
|
|
561
|
+
handle = undefined;
|
|
562
|
+
if (options.mustNotExistPath != null) {
|
|
563
|
+
await assertPathAbsentForMutation(options.mustNotExistPath, ops);
|
|
564
|
+
}
|
|
565
|
+
await ops.rename(tempPath, finalPath);
|
|
566
|
+
tempPath = "";
|
|
567
|
+
await syncDirectoryBestEffort(dirname(finalPath), ops);
|
|
568
|
+
}
|
|
569
|
+
finally {
|
|
570
|
+
if (handle !== undefined) {
|
|
571
|
+
await handle.close().catch(() => { });
|
|
572
|
+
}
|
|
573
|
+
if (tempPath !== "") {
|
|
574
|
+
await (ops.rm ?? fsRm)(tempPath, { force: true }).catch(() => { });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
async function writeExclusiveText(finalPath, content, ops, options = {}) {
|
|
579
|
+
let tempPath = "";
|
|
580
|
+
let handle;
|
|
581
|
+
try {
|
|
582
|
+
const opened = await openExclusiveTempFile(finalPath, ops);
|
|
583
|
+
tempPath = opened.tempPath;
|
|
584
|
+
handle = opened.handle;
|
|
585
|
+
await handle.writeFile(content, { encoding: "utf8" });
|
|
586
|
+
await handle.sync();
|
|
587
|
+
await handle.close();
|
|
588
|
+
handle = undefined;
|
|
589
|
+
throwIfCancelledBeforeManifest(options);
|
|
590
|
+
await linkNoClobber(tempPath, finalPath, ops);
|
|
591
|
+
await (ops.rm ?? fsRm)(tempPath, { force: true }).catch(() => { });
|
|
592
|
+
tempPath = "";
|
|
593
|
+
await syncDirectoryBestEffort(dirname(finalPath), ops);
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
if (handle !== undefined) {
|
|
597
|
+
await handle.close().catch(() => { });
|
|
598
|
+
}
|
|
599
|
+
if (tempPath !== "") {
|
|
600
|
+
await (ops.rm ?? fsRm)(tempPath, { force: true }).catch(() => { });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
async function linkNoClobber(existingPath, newPath, ops) {
|
|
605
|
+
const link = ops.link ?? fsLink;
|
|
606
|
+
try {
|
|
607
|
+
await link(existingPath, newPath);
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
611
|
+
throw new ExecutionError("context_builder cannot mutate committed run: manifest already exists");
|
|
612
|
+
}
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async function assertPathAbsentForMutation(path, ops) {
|
|
617
|
+
try {
|
|
618
|
+
await ops.readFile(path, "utf8");
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
throw new ExecutionError(`context_builder cannot mutate committed run: manifest unreadable (${nodeErrorCode(error)})`);
|
|
625
|
+
}
|
|
626
|
+
throw new ExecutionError("context_builder cannot mutate committed run: manifest already exists");
|
|
627
|
+
}
|
|
628
|
+
async function openExclusiveTempFile(finalPath, ops) {
|
|
629
|
+
const open = ops.open ?? fsOpen;
|
|
630
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
631
|
+
const tempPath = `${finalPath}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
|
|
632
|
+
try {
|
|
633
|
+
return {
|
|
634
|
+
tempPath,
|
|
635
|
+
handle: await open(tempPath, "wx", 0o600),
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
throw new Error("context_builder failed to create an exclusive temp file");
|
|
646
|
+
}
|
|
647
|
+
async function syncDirectoryBestEffort(path, ops) {
|
|
648
|
+
let handle;
|
|
649
|
+
const open = ops.open ?? fsOpen;
|
|
650
|
+
try {
|
|
651
|
+
handle = await open(path, "r");
|
|
652
|
+
await handle.sync();
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
finally {
|
|
658
|
+
if (handle !== undefined) {
|
|
659
|
+
await handle.close().catch(() => { });
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function isNodeError(error) {
|
|
664
|
+
return error instanceof Error && "code" in error;
|
|
665
|
+
}
|
|
666
|
+
function nodeErrorCode(error) {
|
|
667
|
+
return isNodeError(error) && typeof error.code === "string"
|
|
668
|
+
? error.code
|
|
669
|
+
: "unknown";
|
|
670
|
+
}
|
|
671
|
+
function normalizeContextBuilderWarnings(warnings) {
|
|
672
|
+
return warnings.map((warning) => {
|
|
673
|
+
if (isRecord(warning)) {
|
|
674
|
+
return {
|
|
675
|
+
code: diagnosticText(warning.code, "warning"),
|
|
676
|
+
message: diagnosticText(warning.message, "warning"),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
code: "warning",
|
|
681
|
+
message: diagnosticText(warning, "warning"),
|
|
682
|
+
};
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
function normalizeContextBuilderOmissions(omissions) {
|
|
686
|
+
return omissions.map((omission) => {
|
|
687
|
+
if (isRecord(omission)) {
|
|
688
|
+
const rootIndex = normalizeContextBuilderRootIndex(omission.root_index);
|
|
689
|
+
return {
|
|
690
|
+
...(rootIndex == null ? {} : { root_index: rootIndex }),
|
|
691
|
+
reason: diagnosticText(omission.reason, "omission"),
|
|
692
|
+
message: diagnosticText(omission.message, "omission"),
|
|
693
|
+
...(Array.isArray(omission.paths)
|
|
694
|
+
? { paths: omission.paths.map(normalizeContextBuilderOmissionPath) }
|
|
695
|
+
: {}),
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
return {
|
|
699
|
+
reason: "omission",
|
|
700
|
+
message: diagnosticText(omission, "omission"),
|
|
701
|
+
};
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
function normalizeContextBuilderRootIndex(rootIndex) {
|
|
705
|
+
return typeof rootIndex === "number"
|
|
706
|
+
&& Number.isInteger(rootIndex)
|
|
707
|
+
&& rootIndex >= 0
|
|
708
|
+
? rootIndex
|
|
709
|
+
: undefined;
|
|
710
|
+
}
|
|
711
|
+
function normalizeContextBuilderOmissionPath(path) {
|
|
712
|
+
if (typeof path !== "string") {
|
|
713
|
+
return "[redacted-sensitive-path]";
|
|
714
|
+
}
|
|
715
|
+
const text = diagnosticText(path, "");
|
|
716
|
+
return scanSecretPath(text).length > 0 ? "[redacted-sensitive-path]" : text;
|
|
717
|
+
}
|
|
718
|
+
function diagnosticText(value, fallback) {
|
|
719
|
+
if (typeof value === "string") {
|
|
720
|
+
return value;
|
|
721
|
+
}
|
|
722
|
+
if (value == null) {
|
|
723
|
+
return fallback;
|
|
724
|
+
}
|
|
725
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
726
|
+
return String(value);
|
|
727
|
+
}
|
|
728
|
+
try {
|
|
729
|
+
const serialized = JSON.stringify(value);
|
|
730
|
+
return serialized ?? fallback;
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
return fallback;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function isRecord(value) {
|
|
737
|
+
return value !== null && typeof value === "object";
|
|
738
|
+
}
|
|
739
|
+
async function delay(ms) {
|
|
740
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
741
|
+
}
|
|
742
|
+
function isPathInsideDirectory(candidate, directory) {
|
|
743
|
+
const relation = relative(directory, candidate);
|
|
744
|
+
return relation !== "" && !relation.startsWith("..") && !isAbsolute(relation);
|
|
745
|
+
}
|
|
746
|
+
//# sourceMappingURL=storage.js.map
|