@poolzin/pool-bot 2026.3.4 → 2026.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/assets/pool-bot-icon-dark.png +0 -0
  3. package/assets/pool-bot-logo-1.png +0 -0
  4. package/assets/pool-bot-mascot.png +0 -0
  5. package/dist/agents/pi-embedded-runner/tool-result-truncation.js +62 -7
  6. package/dist/agents/poolbot-tools.js +12 -0
  7. package/dist/agents/session-write-lock.js +93 -8
  8. package/dist/agents/tools/pdf-native-providers.js +102 -0
  9. package/dist/agents/tools/pdf-tool.helpers.js +86 -0
  10. package/dist/agents/tools/pdf-tool.js +508 -0
  11. package/dist/build-info.json +3 -3
  12. package/dist/cron/normalize.js +3 -0
  13. package/dist/cron/service/jobs.js +48 -0
  14. package/dist/gateway/protocol/schema/cron.js +3 -0
  15. package/dist/gateway/server-channels.js +99 -14
  16. package/dist/gateway/server-cron.js +89 -0
  17. package/dist/gateway/server-health-probes.js +55 -0
  18. package/dist/gateway/server-http.js +5 -0
  19. package/dist/hooks/bundled/session-memory/handler.js +8 -2
  20. package/dist/infra/abort-signal.js +12 -0
  21. package/dist/infra/boundary-file-read.js +118 -0
  22. package/dist/infra/boundary-path.js +594 -0
  23. package/dist/infra/file-identity.js +12 -0
  24. package/dist/infra/fs-safe.js +377 -12
  25. package/dist/infra/hardlink-guards.js +30 -0
  26. package/dist/infra/json-utf8-bytes.js +8 -0
  27. package/dist/infra/net/fetch-guard.js +63 -13
  28. package/dist/infra/net/proxy-env.js +17 -0
  29. package/dist/infra/net/ssrf.js +74 -272
  30. package/dist/infra/path-alias-guards.js +21 -0
  31. package/dist/infra/path-guards.js +13 -1
  32. package/dist/infra/ports-probe.js +19 -0
  33. package/dist/infra/prototype-keys.js +4 -0
  34. package/dist/infra/restart-stale-pids.js +254 -0
  35. package/dist/infra/safe-open-sync.js +71 -0
  36. package/dist/infra/secure-random.js +7 -0
  37. package/dist/media/ffmpeg-limits.js +4 -0
  38. package/dist/media/input-files.js +6 -2
  39. package/dist/media/temp-files.js +12 -0
  40. package/dist/memory/embedding-chunk-limits.js +5 -2
  41. package/dist/memory/embeddings-ollama.js +91 -138
  42. package/dist/memory/embeddings-remote-fetch.js +11 -10
  43. package/dist/memory/embeddings.js +25 -9
  44. package/dist/memory/manager-embedding-ops.js +1 -1
  45. package/dist/memory/post-json.js +23 -0
  46. package/dist/memory/qmd-manager.js +272 -77
  47. package/dist/memory/remote-http.js +33 -0
  48. package/dist/plugin-sdk/windows-spawn.js +214 -0
  49. package/dist/shared/net/ip-test-fixtures.js +1 -0
  50. package/dist/shared/net/ip.js +303 -0
  51. package/dist/shared/net/ipv4.js +8 -11
  52. package/dist/shared/pid-alive.js +59 -2
  53. package/dist/test-helpers/ssrf.js +13 -0
  54. package/dist/tui/tui.js +9 -4
  55. package/dist/utils/fetch-timeout.js +12 -1
  56. package/docs/adr/003-feature-gap-analysis.md +112 -0
  57. package/package.json +10 -4
@@ -0,0 +1,594 @@
1
+ import fs from "node:fs";
2
+ import fsp from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { isNotFoundPathError, isPathInside } from "./path-guards.js";
6
+ export const BOUNDARY_PATH_ALIAS_POLICIES = {
7
+ strict: Object.freeze({
8
+ allowFinalSymlinkForUnlink: false,
9
+ allowFinalHardlinkForUnlink: false,
10
+ }),
11
+ unlinkTarget: Object.freeze({
12
+ allowFinalSymlinkForUnlink: true,
13
+ allowFinalHardlinkForUnlink: true,
14
+ }),
15
+ };
16
+ export async function resolveBoundaryPath(params) {
17
+ const rootPath = path.resolve(params.rootPath);
18
+ const absolutePath = path.resolve(params.absolutePath);
19
+ const rootCanonicalPath = params.rootCanonicalPath
20
+ ? path.resolve(params.rootCanonicalPath)
21
+ : await resolvePathViaExistingAncestor(rootPath);
22
+ const context = createBoundaryResolutionContext({
23
+ resolveParams: params,
24
+ rootPath,
25
+ absolutePath,
26
+ rootCanonicalPath,
27
+ outsideLexicalCanonicalPath: await resolveOutsideLexicalCanonicalPathAsync({
28
+ rootPath,
29
+ absolutePath,
30
+ }),
31
+ });
32
+ const outsideResult = await resolveOutsideBoundaryPathAsync({
33
+ boundaryLabel: params.boundaryLabel,
34
+ context,
35
+ });
36
+ if (outsideResult) {
37
+ return outsideResult;
38
+ }
39
+ return resolveBoundaryPathLexicalAsync({
40
+ params,
41
+ absolutePath: context.absolutePath,
42
+ rootPath: context.rootPath,
43
+ rootCanonicalPath: context.rootCanonicalPath,
44
+ });
45
+ }
46
+ export function resolveBoundaryPathSync(params) {
47
+ const rootPath = path.resolve(params.rootPath);
48
+ const absolutePath = path.resolve(params.absolutePath);
49
+ const rootCanonicalPath = params.rootCanonicalPath
50
+ ? path.resolve(params.rootCanonicalPath)
51
+ : resolvePathViaExistingAncestorSync(rootPath);
52
+ const context = createBoundaryResolutionContext({
53
+ resolveParams: params,
54
+ rootPath,
55
+ absolutePath,
56
+ rootCanonicalPath,
57
+ outsideLexicalCanonicalPath: resolveOutsideLexicalCanonicalPathSync({
58
+ rootPath,
59
+ absolutePath,
60
+ }),
61
+ });
62
+ const outsideResult = resolveOutsideBoundaryPathSync({
63
+ boundaryLabel: params.boundaryLabel,
64
+ context,
65
+ });
66
+ if (outsideResult) {
67
+ return outsideResult;
68
+ }
69
+ return resolveBoundaryPathLexicalSync({
70
+ params,
71
+ absolutePath: context.absolutePath,
72
+ rootPath: context.rootPath,
73
+ rootCanonicalPath: context.rootCanonicalPath,
74
+ });
75
+ }
76
+ function isPromiseLike(value) {
77
+ return Boolean(value &&
78
+ (typeof value === "object" || typeof value === "function") &&
79
+ "then" in value &&
80
+ typeof value.then === "function");
81
+ }
82
+ function createLexicalTraversalState(params) {
83
+ const relative = path.relative(params.rootPath, params.absolutePath);
84
+ return {
85
+ segments: relative.split(path.sep).filter(Boolean),
86
+ allowFinalSymlink: params.params.policy?.allowFinalSymlinkForUnlink === true,
87
+ canonicalCursor: params.rootCanonicalPath,
88
+ lexicalCursor: params.rootPath,
89
+ preserveFinalSymlink: false,
90
+ };
91
+ }
92
+ function assertLexicalCursorInsideBoundary(params) {
93
+ assertInsideBoundary({
94
+ boundaryLabel: params.params.boundaryLabel,
95
+ rootCanonicalPath: params.rootCanonicalPath,
96
+ candidatePath: params.candidatePath,
97
+ absolutePath: params.absolutePath,
98
+ });
99
+ }
100
+ function applyMissingSuffixToCanonicalCursor(params) {
101
+ const missingSuffix = params.state.segments.slice(params.missingFromIndex);
102
+ params.state.canonicalCursor = path.resolve(params.state.canonicalCursor, ...missingSuffix);
103
+ assertLexicalCursorInsideBoundary({
104
+ params: params.params,
105
+ rootCanonicalPath: params.rootCanonicalPath,
106
+ candidatePath: params.state.canonicalCursor,
107
+ absolutePath: params.absolutePath,
108
+ });
109
+ }
110
+ function advanceCanonicalCursorForSegment(params) {
111
+ params.state.canonicalCursor = path.resolve(params.state.canonicalCursor, params.segment);
112
+ assertLexicalCursorInsideBoundary({
113
+ params: params.params,
114
+ rootCanonicalPath: params.rootCanonicalPath,
115
+ candidatePath: params.state.canonicalCursor,
116
+ absolutePath: params.absolutePath,
117
+ });
118
+ }
119
+ function finalizeLexicalResolution(params) {
120
+ assertLexicalCursorInsideBoundary({
121
+ params: params.params,
122
+ rootCanonicalPath: params.rootCanonicalPath,
123
+ candidatePath: params.state.canonicalCursor,
124
+ absolutePath: params.absolutePath,
125
+ });
126
+ return buildResolvedBoundaryPath({
127
+ absolutePath: params.absolutePath,
128
+ canonicalPath: params.state.canonicalCursor,
129
+ rootPath: params.rootPath,
130
+ rootCanonicalPath: params.rootCanonicalPath,
131
+ kind: params.kind,
132
+ });
133
+ }
134
+ function handleLexicalLstatFailure(params) {
135
+ if (!isNotFoundPathError(params.error)) {
136
+ return false;
137
+ }
138
+ applyMissingSuffixToCanonicalCursor({
139
+ state: params.state,
140
+ missingFromIndex: params.missingFromIndex,
141
+ rootCanonicalPath: params.rootCanonicalPath,
142
+ params: params.resolveParams,
143
+ absolutePath: params.absolutePath,
144
+ });
145
+ return true;
146
+ }
147
+ function handleLexicalStatReadFailure(params) {
148
+ if (handleLexicalLstatFailure({
149
+ error: params.error,
150
+ state: params.state,
151
+ missingFromIndex: params.missingFromIndex,
152
+ rootCanonicalPath: params.rootCanonicalPath,
153
+ resolveParams: params.resolveParams,
154
+ absolutePath: params.absolutePath,
155
+ })) {
156
+ return null;
157
+ }
158
+ throw params.error;
159
+ }
160
+ function handleLexicalStatDisposition(params) {
161
+ if (!params.isSymbolicLink) {
162
+ advanceCanonicalCursorForSegment({
163
+ state: params.state,
164
+ segment: params.segment,
165
+ rootCanonicalPath: params.rootCanonicalPath,
166
+ params: params.resolveParams,
167
+ absolutePath: params.absolutePath,
168
+ });
169
+ return "continue";
170
+ }
171
+ if (params.state.allowFinalSymlink && params.isLast) {
172
+ params.state.preserveFinalSymlink = true;
173
+ advanceCanonicalCursorForSegment({
174
+ state: params.state,
175
+ segment: params.segment,
176
+ rootCanonicalPath: params.rootCanonicalPath,
177
+ params: params.resolveParams,
178
+ absolutePath: params.absolutePath,
179
+ });
180
+ return "break";
181
+ }
182
+ return "resolve-link";
183
+ }
184
+ function applyResolvedSymlinkHop(params) {
185
+ if (!isPathInside(params.rootCanonicalPath, params.linkCanonical)) {
186
+ throw symlinkEscapeError({
187
+ boundaryLabel: params.boundaryLabel,
188
+ rootCanonicalPath: params.rootCanonicalPath,
189
+ symlinkPath: params.state.lexicalCursor,
190
+ });
191
+ }
192
+ params.state.canonicalCursor = params.linkCanonical;
193
+ params.state.lexicalCursor = params.linkCanonical;
194
+ }
195
+ function readLexicalStat(params) {
196
+ try {
197
+ const stat = params.read(params.state.lexicalCursor);
198
+ if (isPromiseLike(stat)) {
199
+ return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure({ ...params, error }));
200
+ }
201
+ return stat;
202
+ }
203
+ catch (error) {
204
+ return handleLexicalStatReadFailure({ ...params, error });
205
+ }
206
+ }
207
+ function resolveAndApplySymlinkHop(params) {
208
+ const linkCanonical = params.resolveLinkCanonical(params.state.lexicalCursor);
209
+ if (isPromiseLike(linkCanonical)) {
210
+ return Promise.resolve(linkCanonical).then((value) => applyResolvedSymlinkHop({
211
+ state: params.state,
212
+ linkCanonical: value,
213
+ rootCanonicalPath: params.rootCanonicalPath,
214
+ boundaryLabel: params.boundaryLabel,
215
+ }));
216
+ }
217
+ applyResolvedSymlinkHop({
218
+ state: params.state,
219
+ linkCanonical,
220
+ rootCanonicalPath: params.rootCanonicalPath,
221
+ boundaryLabel: params.boundaryLabel,
222
+ });
223
+ }
224
+ function* iterateLexicalTraversal(state) {
225
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
226
+ const segment = state.segments[idx] ?? "";
227
+ const isLast = idx === state.segments.length - 1;
228
+ state.lexicalCursor = path.join(state.lexicalCursor, segment);
229
+ yield { idx, segment, isLast };
230
+ }
231
+ }
232
+ async function resolveBoundaryPathLexicalAsync(params) {
233
+ const state = createLexicalTraversalState(params);
234
+ const sharedStepParams = {
235
+ state,
236
+ rootCanonicalPath: params.rootCanonicalPath,
237
+ resolveParams: params.params,
238
+ absolutePath: params.absolutePath,
239
+ };
240
+ for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
241
+ const stat = await readLexicalStat({
242
+ ...sharedStepParams,
243
+ missingFromIndex: idx,
244
+ read: (cursor) => fsp.lstat(cursor),
245
+ });
246
+ if (!stat) {
247
+ break;
248
+ }
249
+ const disposition = handleLexicalStatDisposition({
250
+ ...sharedStepParams,
251
+ isSymbolicLink: stat.isSymbolicLink(),
252
+ segment,
253
+ isLast,
254
+ });
255
+ if (disposition === "continue") {
256
+ continue;
257
+ }
258
+ if (disposition === "break") {
259
+ break;
260
+ }
261
+ await resolveAndApplySymlinkHop({
262
+ state,
263
+ rootCanonicalPath: params.rootCanonicalPath,
264
+ boundaryLabel: params.params.boundaryLabel,
265
+ resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor),
266
+ });
267
+ }
268
+ const kind = await getPathKind(params.absolutePath, state.preserveFinalSymlink);
269
+ return finalizeLexicalResolution({
270
+ ...params,
271
+ state,
272
+ kind,
273
+ });
274
+ }
275
+ function resolveBoundaryPathLexicalSync(params) {
276
+ const state = createLexicalTraversalState(params);
277
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
278
+ const segment = state.segments[idx] ?? "";
279
+ const isLast = idx === state.segments.length - 1;
280
+ state.lexicalCursor = path.join(state.lexicalCursor, segment);
281
+ const maybeStat = readLexicalStat({
282
+ state,
283
+ missingFromIndex: idx,
284
+ rootCanonicalPath: params.rootCanonicalPath,
285
+ resolveParams: params.params,
286
+ absolutePath: params.absolutePath,
287
+ read: (cursor) => fs.lstatSync(cursor),
288
+ });
289
+ if (isPromiseLike(maybeStat)) {
290
+ throw new Error("Unexpected async lexical stat");
291
+ }
292
+ const stat = maybeStat;
293
+ if (!stat) {
294
+ break;
295
+ }
296
+ const disposition = handleLexicalStatDisposition({
297
+ state,
298
+ isSymbolicLink: stat.isSymbolicLink(),
299
+ segment,
300
+ isLast,
301
+ rootCanonicalPath: params.rootCanonicalPath,
302
+ resolveParams: params.params,
303
+ absolutePath: params.absolutePath,
304
+ });
305
+ if (disposition === "continue") {
306
+ continue;
307
+ }
308
+ if (disposition === "break") {
309
+ break;
310
+ }
311
+ const maybeApplied = resolveAndApplySymlinkHop({
312
+ state,
313
+ rootCanonicalPath: params.rootCanonicalPath,
314
+ boundaryLabel: params.params.boundaryLabel,
315
+ resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor),
316
+ });
317
+ if (isPromiseLike(maybeApplied)) {
318
+ throw new Error("Unexpected async symlink resolution");
319
+ }
320
+ }
321
+ const kind = getPathKindSync(params.absolutePath, state.preserveFinalSymlink);
322
+ return finalizeLexicalResolution({
323
+ ...params,
324
+ state,
325
+ kind,
326
+ });
327
+ }
328
+ function resolveCanonicalOutsideLexicalPath(params) {
329
+ return params.outsideLexicalCanonicalPath ?? params.absolutePath;
330
+ }
331
+ function createBoundaryResolutionContext(params) {
332
+ const lexicalInside = isPathInside(params.rootPath, params.absolutePath);
333
+ const canonicalOutsideLexicalPath = resolveCanonicalOutsideLexicalPath({
334
+ absolutePath: params.absolutePath,
335
+ outsideLexicalCanonicalPath: params.outsideLexicalCanonicalPath,
336
+ });
337
+ assertLexicalBoundaryOrCanonicalAlias({
338
+ skipLexicalRootCheck: params.resolveParams.skipLexicalRootCheck,
339
+ lexicalInside,
340
+ canonicalOutsideLexicalPath,
341
+ rootCanonicalPath: params.rootCanonicalPath,
342
+ boundaryLabel: params.resolveParams.boundaryLabel,
343
+ rootPath: params.rootPath,
344
+ absolutePath: params.absolutePath,
345
+ });
346
+ return {
347
+ rootPath: params.rootPath,
348
+ absolutePath: params.absolutePath,
349
+ rootCanonicalPath: params.rootCanonicalPath,
350
+ lexicalInside,
351
+ canonicalOutsideLexicalPath,
352
+ };
353
+ }
354
+ async function resolveOutsideBoundaryPathAsync(params) {
355
+ if (params.context.lexicalInside) {
356
+ return null;
357
+ }
358
+ const kind = await getPathKind(params.context.absolutePath, false);
359
+ return buildOutsideLexicalBoundaryPath({
360
+ boundaryLabel: params.boundaryLabel,
361
+ rootCanonicalPath: params.context.rootCanonicalPath,
362
+ absolutePath: params.context.absolutePath,
363
+ canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
364
+ rootPath: params.context.rootPath,
365
+ kind,
366
+ });
367
+ }
368
+ function resolveOutsideBoundaryPathSync(params) {
369
+ if (params.context.lexicalInside) {
370
+ return null;
371
+ }
372
+ const kind = getPathKindSync(params.context.absolutePath, false);
373
+ return buildOutsideLexicalBoundaryPath({
374
+ boundaryLabel: params.boundaryLabel,
375
+ rootCanonicalPath: params.context.rootCanonicalPath,
376
+ absolutePath: params.context.absolutePath,
377
+ canonicalOutsideLexicalPath: params.context.canonicalOutsideLexicalPath,
378
+ rootPath: params.context.rootPath,
379
+ kind,
380
+ });
381
+ }
382
+ async function resolveOutsideLexicalCanonicalPathAsync(params) {
383
+ if (isPathInside(params.rootPath, params.absolutePath)) {
384
+ return undefined;
385
+ }
386
+ return await resolvePathViaExistingAncestor(params.absolutePath);
387
+ }
388
+ function resolveOutsideLexicalCanonicalPathSync(params) {
389
+ if (isPathInside(params.rootPath, params.absolutePath)) {
390
+ return undefined;
391
+ }
392
+ return resolvePathViaExistingAncestorSync(params.absolutePath);
393
+ }
394
+ function buildOutsideLexicalBoundaryPath(params) {
395
+ assertInsideBoundary({
396
+ boundaryLabel: params.boundaryLabel,
397
+ rootCanonicalPath: params.rootCanonicalPath,
398
+ candidatePath: params.canonicalOutsideLexicalPath,
399
+ absolutePath: params.absolutePath,
400
+ });
401
+ return buildResolvedBoundaryPath({
402
+ absolutePath: params.absolutePath,
403
+ canonicalPath: params.canonicalOutsideLexicalPath,
404
+ rootPath: params.rootPath,
405
+ rootCanonicalPath: params.rootCanonicalPath,
406
+ kind: params.kind,
407
+ });
408
+ }
409
+ function assertLexicalBoundaryOrCanonicalAlias(params) {
410
+ if (params.skipLexicalRootCheck || params.lexicalInside) {
411
+ return;
412
+ }
413
+ if (isPathInside(params.rootCanonicalPath, params.canonicalOutsideLexicalPath)) {
414
+ return;
415
+ }
416
+ throw pathEscapeError({
417
+ boundaryLabel: params.boundaryLabel,
418
+ rootPath: params.rootPath,
419
+ absolutePath: params.absolutePath,
420
+ });
421
+ }
422
+ function buildResolvedBoundaryPath(params) {
423
+ return {
424
+ absolutePath: params.absolutePath,
425
+ canonicalPath: params.canonicalPath,
426
+ rootPath: params.rootPath,
427
+ rootCanonicalPath: params.rootCanonicalPath,
428
+ relativePath: relativeInsideRoot(params.rootCanonicalPath, params.canonicalPath),
429
+ exists: params.kind.exists,
430
+ kind: params.kind.kind,
431
+ };
432
+ }
433
+ export async function resolvePathViaExistingAncestor(targetPath) {
434
+ const normalized = path.resolve(targetPath);
435
+ let cursor = normalized;
436
+ const missingSuffix = [];
437
+ while (!isFilesystemRoot(cursor) && !(await pathExists(cursor))) {
438
+ missingSuffix.unshift(path.basename(cursor));
439
+ const parent = path.dirname(cursor);
440
+ if (parent === cursor) {
441
+ break;
442
+ }
443
+ cursor = parent;
444
+ }
445
+ if (!(await pathExists(cursor))) {
446
+ return normalized;
447
+ }
448
+ try {
449
+ const resolvedAncestor = path.resolve(await fsp.realpath(cursor));
450
+ if (missingSuffix.length === 0) {
451
+ return resolvedAncestor;
452
+ }
453
+ return path.resolve(resolvedAncestor, ...missingSuffix);
454
+ }
455
+ catch {
456
+ return normalized;
457
+ }
458
+ }
459
+ export function resolvePathViaExistingAncestorSync(targetPath) {
460
+ const normalized = path.resolve(targetPath);
461
+ let cursor = normalized;
462
+ const missingSuffix = [];
463
+ while (!isFilesystemRoot(cursor) && !fs.existsSync(cursor)) {
464
+ missingSuffix.unshift(path.basename(cursor));
465
+ const parent = path.dirname(cursor);
466
+ if (parent === cursor) {
467
+ break;
468
+ }
469
+ cursor = parent;
470
+ }
471
+ if (!fs.existsSync(cursor)) {
472
+ return normalized;
473
+ }
474
+ try {
475
+ // Keep sync behavior aligned with async (`fsp.realpath`) to avoid
476
+ // platform-specific canonical alias drift (notably on Windows).
477
+ const resolvedAncestor = path.resolve(fs.realpathSync(cursor));
478
+ if (missingSuffix.length === 0) {
479
+ return resolvedAncestor;
480
+ }
481
+ return path.resolve(resolvedAncestor, ...missingSuffix);
482
+ }
483
+ catch {
484
+ return normalized;
485
+ }
486
+ }
487
+ async function getPathKind(absolutePath, preserveFinalSymlink) {
488
+ try {
489
+ const stat = preserveFinalSymlink
490
+ ? await fsp.lstat(absolutePath)
491
+ : await fsp.stat(absolutePath);
492
+ return { exists: true, kind: toResolvedKind(stat) };
493
+ }
494
+ catch (error) {
495
+ if (isNotFoundPathError(error)) {
496
+ return { exists: false, kind: "missing" };
497
+ }
498
+ throw error;
499
+ }
500
+ }
501
+ function getPathKindSync(absolutePath, preserveFinalSymlink) {
502
+ try {
503
+ const stat = preserveFinalSymlink ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
504
+ return { exists: true, kind: toResolvedKind(stat) };
505
+ }
506
+ catch (error) {
507
+ if (isNotFoundPathError(error)) {
508
+ return { exists: false, kind: "missing" };
509
+ }
510
+ throw error;
511
+ }
512
+ }
513
+ function toResolvedKind(stat) {
514
+ if (stat.isFile()) {
515
+ return "file";
516
+ }
517
+ if (stat.isDirectory()) {
518
+ return "directory";
519
+ }
520
+ if (stat.isSymbolicLink()) {
521
+ return "symlink";
522
+ }
523
+ return "other";
524
+ }
525
+ function relativeInsideRoot(rootPath, targetPath) {
526
+ const relative = path.relative(path.resolve(rootPath), path.resolve(targetPath));
527
+ if (!relative || relative === ".") {
528
+ return "";
529
+ }
530
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
531
+ return "";
532
+ }
533
+ return relative;
534
+ }
535
+ function assertInsideBoundary(params) {
536
+ if (isPathInside(params.rootCanonicalPath, params.candidatePath)) {
537
+ return;
538
+ }
539
+ throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
540
+ }
541
+ function pathEscapeError(params) {
542
+ return new Error(`Path escapes ${params.boundaryLabel} (${shortPath(params.rootPath)}): ${shortPath(params.absolutePath)}`);
543
+ }
544
+ function symlinkEscapeError(params) {
545
+ return new Error(`Symlink escapes ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.symlinkPath)}`);
546
+ }
547
+ function shortPath(value) {
548
+ const home = os.homedir();
549
+ if (value.startsWith(home)) {
550
+ return `~${value.slice(home.length)}`;
551
+ }
552
+ return value;
553
+ }
554
+ function isFilesystemRoot(candidate) {
555
+ return path.parse(candidate).root === candidate;
556
+ }
557
+ async function pathExists(targetPath) {
558
+ try {
559
+ await fsp.lstat(targetPath);
560
+ return true;
561
+ }
562
+ catch (error) {
563
+ if (isNotFoundPathError(error)) {
564
+ return false;
565
+ }
566
+ throw error;
567
+ }
568
+ }
569
+ async function resolveSymlinkHopPath(symlinkPath) {
570
+ try {
571
+ return path.resolve(await fsp.realpath(symlinkPath));
572
+ }
573
+ catch (error) {
574
+ if (!isNotFoundPathError(error)) {
575
+ throw error;
576
+ }
577
+ const linkTarget = await fsp.readlink(symlinkPath);
578
+ const linkAbsolute = path.resolve(path.dirname(symlinkPath), linkTarget);
579
+ return resolvePathViaExistingAncestor(linkAbsolute);
580
+ }
581
+ }
582
+ function resolveSymlinkHopPathSync(symlinkPath) {
583
+ try {
584
+ return path.resolve(fs.realpathSync(symlinkPath));
585
+ }
586
+ catch (error) {
587
+ if (!isNotFoundPathError(error)) {
588
+ throw error;
589
+ }
590
+ const linkTarget = fs.readlinkSync(symlinkPath);
591
+ const linkAbsolute = path.resolve(path.dirname(symlinkPath), linkTarget);
592
+ return resolvePathViaExistingAncestorSync(linkAbsolute);
593
+ }
594
+ }
@@ -0,0 +1,12 @@
1
+ function isZero(value) {
2
+ return value === 0 || value === 0n;
3
+ }
4
+ export function sameFileIdentity(left, right, platform = process.platform) {
5
+ if (left.ino !== right.ino) {
6
+ return false;
7
+ }
8
+ if (left.dev === right.dev) {
9
+ return true;
10
+ }
11
+ return platform === "win32" && (isZero(left.dev) || isZero(right.dev));
12
+ }