@openclaw/signal 2026.11.3 → 2026.11.5-beta.2

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 (179) hide show
  1. package/dist/.setup/{accounts-B0Pba1-q.mjs → accounts-enJKL3ci.mjs} +4 -8
  2. package/dist/.setup/api-CrasQEKI.mjs +14 -0
  3. package/dist/.setup/{api-CbN3mye4.mjs → api-DljZsfUn.mjs} +9 -11
  4. package/dist/.setup/{approval-auth-DhUKkSkA.mjs → approval-auth-B_5Ytx8K.mjs} +7 -11
  5. package/dist/.setup/{approval-handler.runtime-CsEcBZoc.mjs → approval-handler.runtime-0RbVmTxx.mjs} +7 -8
  6. package/dist/.setup/{approval-reactions-JsqCEOHK.mjs → approval-reactions-CvqVSSx8.mjs} +16 -24
  7. package/dist/.setup/{channel-CM-rLLPN.mjs → channel-ByJj8fSh.mjs} +417 -372
  8. package/dist/.setup/{client-Duwqbgqi.mjs → client-CesmEjRc.mjs} +170 -186
  9. package/dist/.setup/{config-schema-DvxZ18SD.mjs → config-schema-BCmW0BLB.mjs} +2 -5
  10. package/dist/.setup/{doctor-contract-api-BIdVW0PF.mjs → doctor-contract-api-CcLwIhsD.mjs} +88 -95
  11. package/dist/.setup/{identity-B3JyPqGO.mjs → identity-vKtR3DrM.mjs} +39 -2
  12. package/dist/.setup/{install-signal-cli-hYVLYGXe.mjs → install-signal-cli-DvLDZfpP.mjs} +95 -109
  13. package/dist/.setup/{transport-url-CAC0JcuI.mjs → legacy-transport-D_cZC3bS.mjs} +18 -1
  14. package/dist/.setup/{monitor-C6u1JV2C.mjs → monitor-Cf5sihwl.mjs} +166 -239
  15. package/dist/.setup/{probe-D_93QCe0.mjs → probe-Caq5Y4kA.mjs} +3 -3
  16. package/dist/.setup/{send-CM7-016e.mjs → send-BJuOWLky.mjs} +34 -66
  17. package/dist/.setup/{setup-surface-DvKLwYrl.mjs → setup-surface-DQ1yFZmY.mjs} +3 -3
  18. package/dist/.setup/{transport-detection-CmlgOybe.mjs → transport-detection-DvR6X_UQ.mjs} +3 -3
  19. package/dist/.setup/{transport-policy-B6C_LogW.mjs → transport-policy-zIR3lRUj.mjs} +1 -1
  20. package/dist/.setup/transport-probes.runtime-CoH6UFtc.mjs +1 -0
  21. package/dist/.source-module-urls/b02c526df8b90083105b63e5b51d5c8bbfe02676df72f6be8dd8f825817b32b9.json +1 -0
  22. package/dist/api.js +9 -11
  23. package/dist/channel-config-api.js +1 -2
  24. package/dist/channel-plugin-api.js +1 -2
  25. package/dist/contract-api.js +3 -4
  26. package/dist/doctor-contract-api.js +1 -2
  27. package/dist/runtime-api.js +1 -2
  28. package/dist/setup-entry.js +1 -1
  29. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +94 -0
  30. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  31. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
  32. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +3 -1
  33. package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +2 -2
  34. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +8 -1
  35. package/node_modules/@openclaw/fs-safe/dist/file-open-error.d.ts +2 -0
  36. package/node_modules/@openclaw/fs-safe/dist/file-open-error.js +14 -0
  37. package/node_modules/@openclaw/fs-safe/dist/file-sync.d.ts +2 -1
  38. package/node_modules/@openclaw/fs-safe/dist/file-sync.js +6 -1
  39. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
  40. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +1 -0
  41. package/node_modules/@openclaw/fs-safe/dist/native-config.js +14 -3
  42. package/node_modules/@openclaw/fs-safe/dist/native-operations.js +2 -0
  43. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +9 -2
  44. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +40 -32
  45. package/node_modules/@openclaw/fs-safe/dist/output.js +6 -0
  46. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
  47. package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -0
  48. package/node_modules/@openclaw/fs-safe/dist/path.js +5 -1
  49. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +25 -2
  50. package/node_modules/@openclaw/fs-safe/dist/publication-handle.d.ts +4 -0
  51. package/node_modules/@openclaw/fs-safe/dist/publication-handle.js +32 -0
  52. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.d.ts +2 -1
  53. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +19 -9
  54. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +23 -24
  55. package/node_modules/@openclaw/fs-safe/dist/publish-rename-fallback.d.ts +21 -0
  56. package/node_modules/@openclaw/fs-safe/dist/publish-rename-fallback.js +102 -0
  57. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +2 -0
  58. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +3 -1
  59. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +8 -5
  60. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +3 -0
  61. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -1
  62. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +19 -9
  63. package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +3 -3
  64. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.d.ts +2 -2
  65. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +27 -13
  66. package/node_modules/@openclaw/fs-safe/dist/root-path.js +2 -2
  67. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.d.ts +3 -0
  68. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +18 -3
  69. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +1 -1
  70. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +68 -21
  71. package/node_modules/@openclaw/fs-safe/dist/watch-io-error.d.ts +2 -0
  72. package/node_modules/@openclaw/fs-safe/dist/watch-io-error.js +27 -0
  73. package/node_modules/@openclaw/fs-safe/dist/watch-io-worker.d.ts +1 -0
  74. package/node_modules/@openclaw/fs-safe/dist/watch-io-worker.js +93 -0
  75. package/node_modules/@openclaw/fs-safe/dist/watch-io.d.ts +47 -0
  76. package/node_modules/@openclaw/fs-safe/dist/watch-io.js +236 -0
  77. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +1 -1
  78. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +3 -3
  79. package/node_modules/@openclaw/fs-safe/dist/watch-probe.d.ts +16 -0
  80. package/node_modules/@openclaw/fs-safe/dist/watch-probe.js +135 -0
  81. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +1 -1
  82. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +86 -24
  83. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +4 -0
  84. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +78 -16
  85. package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +2 -0
  86. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +23 -0
  87. package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +3 -1
  88. package/node_modules/@openclaw/fs-safe/dist/watch.js +60 -13
  89. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +1 -1
  90. package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +1 -0
  91. package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
  92. package/node_modules/@openclaw/fs-safe/docs/advanced.md +5 -2
  93. package/node_modules/@openclaw/fs-safe/docs/compiler-qualification.md +392 -0
  94. package/node_modules/@openclaw/fs-safe/docs/config.md +7 -1
  95. package/node_modules/@openclaw/fs-safe/docs/contributing.md +25 -5
  96. package/node_modules/@openclaw/fs-safe/docs/copy.md +10 -0
  97. package/node_modules/@openclaw/fs-safe/docs/durability.md +9 -3
  98. package/node_modules/@openclaw/fs-safe/docs/errors.md +13 -0
  99. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -4
  100. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +6 -4
  101. package/node_modules/@openclaw/fs-safe/docs/output.md +4 -2
  102. package/node_modules/@openclaw/fs-safe/docs/path.md +5 -2
  103. package/node_modules/@openclaw/fs-safe/docs/permissions.md +15 -0
  104. package/node_modules/@openclaw/fs-safe/docs/security-model.md +9 -0
  105. package/node_modules/@openclaw/fs-safe/docs/staged-file.md +2 -0
  106. package/node_modules/@openclaw/fs-safe/docs/testing.md +1 -1
  107. package/node_modules/@openclaw/fs-safe/docs/watch.md +62 -5
  108. package/node_modules/@openclaw/fs-safe/docs/writing.md +6 -1
  109. package/node_modules/@openclaw/fs-safe/package.json +18 -16
  110. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  111. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  112. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  113. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  114. package/node_modules/@openclaw/fs-safe-freebsd-arm64/fs-safe-native.node +0 -0
  115. package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +1 -1
  116. package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
  117. package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +1 -1
  118. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  119. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  120. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  121. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  122. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  123. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  124. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  125. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  126. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/fs-safe-native.node +0 -0
  127. package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +1 -1
  128. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  129. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  130. package/node_modules/zod/package.json +1 -1
  131. package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
  132. package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
  133. package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
  134. package/node_modules/zod/src/v4/core/compile.ts +9 -0
  135. package/node_modules/zod/src/v4/core/schemas.ts +35 -1
  136. package/node_modules/zod/src/v4/core/util.ts +13 -0
  137. package/node_modules/zod/src/v4/core/versions.ts +1 -1
  138. package/node_modules/zod/v4/core/compile.cjs +8 -0
  139. package/node_modules/zod/v4/core/compile.js +8 -0
  140. package/node_modules/zod/v4/core/schemas.cjs +30 -0
  141. package/node_modules/zod/v4/core/schemas.d.cts +3 -1
  142. package/node_modules/zod/v4/core/schemas.d.ts +3 -1
  143. package/node_modules/zod/v4/core/schemas.js +30 -0
  144. package/node_modules/zod/v4/core/util.cjs +15 -0
  145. package/node_modules/zod/v4/core/util.d.cts +2 -0
  146. package/node_modules/zod/v4/core/util.d.ts +2 -0
  147. package/node_modules/zod/v4/core/util.js +14 -0
  148. package/node_modules/zod/v4/core/versions.cjs +1 -1
  149. package/node_modules/zod/v4/core/versions.js +1 -1
  150. package/openclaw.plugin.json +0 -30
  151. package/package.json +6 -8
  152. package/dist/.setup/api-CJBtD3lT.mjs +0 -16
  153. package/dist/.setup/client-adapter-Chnbch_z.mjs +0 -30
  154. package/dist/.setup/legacy-transport-QIpaMNST.mjs +0 -18
  155. package/dist/.setup/normalize-D7kSomXt.mjs +0 -39
  156. package/dist/.setup/reaction-runtime-api-mdO4k0yv.mjs +0 -65
  157. package/dist/.setup/reaction-targets-BgPLDHBf.mjs +0 -72
  158. package/dist/.setup/rpc-context-BAXcX7dI.mjs +0 -13
  159. package/dist/.setup/transport-probes.runtime-D-xIMRcn.mjs +0 -2
  160. package/dist/reaction-runtime-api.js +0 -2
  161. package/node_modules/ws/LICENSE +0 -20
  162. package/node_modules/ws/README.md +0 -548
  163. package/node_modules/ws/browser.js +0 -8
  164. package/node_modules/ws/index.js +0 -22
  165. package/node_modules/ws/lib/buffer-util.js +0 -131
  166. package/node_modules/ws/lib/constants.js +0 -19
  167. package/node_modules/ws/lib/event-target.js +0 -292
  168. package/node_modules/ws/lib/extension.js +0 -203
  169. package/node_modules/ws/lib/limiter.js +0 -55
  170. package/node_modules/ws/lib/permessage-deflate.js +0 -530
  171. package/node_modules/ws/lib/receiver.js +0 -743
  172. package/node_modules/ws/lib/sender.js +0 -607
  173. package/node_modules/ws/lib/stream.js +0 -161
  174. package/node_modules/ws/lib/subprotocol.js +0 -62
  175. package/node_modules/ws/lib/validation.js +0 -152
  176. package/node_modules/ws/lib/websocket-server.js +0 -562
  177. package/node_modules/ws/lib/websocket.js +0 -1421
  178. package/node_modules/ws/package.json +0 -74
  179. package/node_modules/ws/wrapper.mjs +0 -21
@@ -51,6 +51,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
53
  import fs from "node:fs";
54
+ import { annotateReadOnlyOpenError } from "./file-open-error.js";
54
55
  import { randomUUID } from "node:crypto";
55
56
  import fsAsync from "node:fs/promises";
56
57
  import path from "node:path";
@@ -487,6 +488,7 @@ export async function tryOpenCreateRootNative(params) {
487
488
  catch (error) {
488
489
  if (hasNodeErrorCode(error, "EEXIST") && typeof error === "object" && error !== null)
489
490
  nativeCreateCollisions.add(error);
491
+ annotateReadOnlyOpenError(error, params.target);
490
492
  throw error;
491
493
  }
492
494
  let rawOpen = true;
@@ -2,8 +2,10 @@ import { type RootContext } from "./root-context.js";
2
2
  import { type RootDirectoryObservationGuard } from "./root-directory-list.js";
3
3
  import { type ExactStatIdentity } from "./stat-observation.js";
4
4
  import type { DirEntry } from "./types.js";
5
+ import type { RootEntryObservation } from "./root-directory-list-types.js";
5
6
  /** One literal entry lookup under an operation-local admitted parent; never follow the leaf. */
6
- export declare function lookupRootDirectoryEntry(root: RootContext, guard: RootDirectoryObservationGuard, name: string): Promise<{
7
+ export declare function lookupRootDirectoryEntry(root: RootContext, guard: RootDirectoryObservationGuard, name: string, exactMetadata?: boolean): Promise<{
7
8
  entry: DirEntry;
8
9
  identity: ExactStatIdentity;
10
+ observation?: RootEntryObservation;
9
11
  } | undefined>;
@@ -1,12 +1,13 @@
1
- import fs from "node:fs";
1
+ import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { isNotFoundPathError } from "./path.js";
5
5
  import { assertValidRootRelativePath } from "./root-context.js";
6
6
  import { assertRootDirectoryObservationGuard, pathStatFromStats } from "./root-directory-list.js";
7
7
  import { inspectStatObservationSync } from "./stat-observation.js";
8
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
8
9
  /** One literal entry lookup under an operation-local admitted parent; never follow the leaf. */
9
- export async function lookupRootDirectoryEntry(root, guard, name) {
10
+ export async function lookupRootDirectoryEntry(root, guard, name, exactMetadata = false) {
10
11
  if (!name || name === "." || name === ".." || path.basename(name) !== name || path.isAbsolute(name)) {
11
12
  throw new FsSafeError("invalid-path", "directory lookup requires one literal name");
12
13
  }
@@ -14,10 +15,12 @@ export async function lookupRootDirectoryEntry(root, guard, name) {
14
15
  await assertRootDirectoryObservationGuard(root, guard);
15
16
  try {
16
17
  const pathname = path.join(guard.realPath, name);
17
- const observed = inspectStatObservationSync(bigint => bigint
18
- ? fs.lstatSync(pathname, { bigint: true }) : fs.lstatSync(pathname));
18
+ const exact = exactMetadata ? inspectFileIdentitySync(() => fs.lstatSync(pathname, { bigint: true })) : undefined;
19
+ const observed = exact ? { stat: exact, identity: { dev: exact.dev, ino: exact.ino } }
20
+ : inspectStatObservationSync(bigint => bigint ? fs.lstatSync(pathname, { bigint: true }) : fs.lstatSync(pathname));
19
21
  await assertRootDirectoryObservationGuard(root, guard);
20
- return { entry: pathStatFromStats(observed.stat, name), identity: observed.identity };
22
+ return { entry: pathStatFromStats(observed.stat, name), identity: observed.identity,
23
+ ...(exactMetadata ? { observation: observed.stat } : {}) };
21
24
  }
22
25
  catch (error) {
23
26
  await assertRootDirectoryObservationGuard(root, guard);
@@ -1,14 +1,17 @@
1
1
  import type { ExactStatIdentity } from "./stat-observation.js";
2
+ import type { BigIntStats } from "node:fs";
2
3
  import type { DirEntry } from "./types.js";
3
4
  /** Strings only: cached spellings never replace an observation or its identity fences. */
4
5
  export type RootDirectoryListingPaths = {
5
6
  directory: string;
6
7
  names: Map<string, string>;
7
8
  };
9
+ export type RootEntryObservation = Pick<BigIntStats, "dev" | "ino" | "mode" | "nlink" | "size" | "mtimeNs" | "ctimeNs">;
8
10
  export type RootDirectoryListingResult = {
9
11
  kind: "entry";
10
12
  entry: DirEntry;
11
13
  identity?: ExactStatIdentity;
14
+ observation?: RootEntryObservation;
12
15
  } | {
13
16
  kind: "limit";
14
17
  name: string;
@@ -1,5 +1,6 @@
1
1
  import type { BigIntStats, Stats } from "node:fs";
2
2
  import { type AsyncDirectoryGuard } from "./directory-guard.js";
3
+ import { type NativeDirectoryObservationBackend } from "./native-directory-observation.js";
3
4
  import { type RootContext } from "./root-context.js";
4
5
  import type { RootPathDirectoryObservationGuard, RootPathObservationReceipt, RootPathParentObservationReceipt } from "./root-path.js";
5
6
  import type { DirEntry, PathStat } from "./types.js";
@@ -8,7 +9,7 @@ export declare function pathStatFromStats(stat: Stats | BigIntStats): PathStat;
8
9
  export declare function pathStatFromStats(stat: Stats | BigIntStats, name: string): DirEntry;
9
10
  export type RootDirectoryObservationGuard = AsyncDirectoryGuard<BigIntStats>;
10
11
  export declare function createRootDirectoryObservationGuard(root: RootContext, directory: string): Promise<RootDirectoryObservationGuard>;
11
- export declare function assertRootDirectoryObservationGuard(root: RootContext, guard: RootDirectoryObservationGuard | RootPathDirectoryObservationGuard): Promise<void>;
12
+ export declare function assertRootDirectoryObservationGuard(root: RootContext, guard: RootDirectoryObservationGuard | RootPathDirectoryObservationGuard, backend?: NativeDirectoryObservationBackend): Promise<void>;
12
13
  /**
13
14
  * Close an operation-local traversal receipt with fresh exact observations.
14
15
  * Descendant canonical paths are admitted while the receipt is created. The
@@ -6,14 +6,14 @@ import path from "node:path";
6
6
  import { setImmediate as yieldToEventLoop } from "node:timers/promises";
7
7
  import { assertAsyncDirectoryGuard, assertDirectoryObservationGuardSync, assertDirectoryObservationSync, createAsyncDirectoryGuard, extendDirectoryObservationGuard, } from "./directory-guard.js";
8
8
  import { FsSafeError } from "./errors.js";
9
- import { inspectNativeDirectoryObservation, isNativeDirectoryObservationGuard, } from "./native-directory-observation.js";
9
+ import { inspectNativeDirectoryObservation, getNativeDirectoryObservationBackend, isNativeDirectoryObservationGuard, } from "./native-directory-observation.js";
10
10
  import { isNotFoundPathError } from "./path.js";
11
11
  import { assertRootIdentityCurrent } from "./root-context.js";
12
12
  import { errorCauseOptions, rootPathChangedError } from "./root-errors.js";
13
13
  import { admitPathInsideRoot } from "./root-boundary.js";
14
14
  import { createSuppressedError } from "./suppressed-error.js";
15
15
  import { realpathSync } from "./realpath.js";
16
- import { inspectStatObservationSync } from "./stat-observation.js";
16
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
17
17
  import { getFsSafeTestHooks } from "./test-hooks.js";
18
18
  const METADATA_BATCH_SIZE = 32;
19
19
  function directoryEntryName(bytes) {
@@ -77,7 +77,7 @@ function directoryChangedError(error) {
77
77
  return error;
78
78
  return new FsSafeError("path-mismatch", "directory changed during operation", errorCauseOptions(error));
79
79
  }
80
- export async function assertRootDirectoryObservationGuard(root, guard) {
80
+ export async function assertRootDirectoryObservationGuard(root, guard, backend) {
81
81
  if (isNativeDirectoryObservationGuard(guard)) {
82
82
  throw new FsSafeError("path-mismatch", "native directory observation cannot be revalidated");
83
83
  }
@@ -86,7 +86,9 @@ export async function assertRootDirectoryObservationGuard(root, guard) {
86
86
  identity.dev === root.rootIdentity.dev && identity.ino === root.rootIdentity.ino;
87
87
  if (guardPinsRoot) {
88
88
  try {
89
- if ("identity" in guard)
89
+ if (backend && "identity" in guard)
90
+ assertReceiptDirectoryGuardSync(root, guard, backend);
91
+ else if ("identity" in guard)
90
92
  assertDirectoryObservationGuardSync(guard);
91
93
  else
92
94
  await assertAsyncDirectoryGuard(guard);
@@ -109,7 +111,9 @@ export async function assertRootDirectoryObservationGuard(root, guard) {
109
111
  await assertRootIdentityCurrent(root);
110
112
  }
111
113
  try {
112
- if ("identity" in guard)
114
+ if (backend && "identity" in guard)
115
+ assertReceiptDirectoryGuardSync(root, guard, backend);
116
+ else if ("identity" in guard)
113
117
  assertDirectoryObservationGuardSync(guard);
114
118
  else
115
119
  await assertAsyncDirectoryGuard(guard);
@@ -263,12 +267,15 @@ export async function openRootDirectoryListing(root, directory, options, receipt
263
267
  identity: { dev: admitted.stat.dev, ino: admitted.stat.ino },
264
268
  }, admitted.dir, admitted.realPath);
265
269
  }
270
+ // Windows watch listings keep every fence while sharing one handle for its
271
+ // identity/path queries. POSIX pathname checks are cheaper than this helper.
272
+ const backend = getNativeDirectoryObservationBackend(options.exactIdentity === true && process.platform === "win32");
266
273
  const assertCurrent = async () => {
267
274
  options.signal?.throwIfAborted();
268
275
  if (receipt)
269
276
  assertRootPathObservationReceiptCurrent(root, receipt);
270
277
  else
271
- await assertRootDirectoryObservationGuard(root, guard);
278
+ await assertRootDirectoryObservationGuard(root, guard, backend);
272
279
  options.signal?.throwIfAborted();
273
280
  };
274
281
  const paths = { directory: guard.realPath, names: new Map() };
@@ -430,8 +437,10 @@ export async function openRootDirectoryListing(root, directory, options, receipt
430
437
  paths.names.set(name, pathname);
431
438
  let observed;
432
439
  try {
433
- observed = options.exactIdentity ? inspectStatObservationSync(bigint => bigint
434
- ? fsSync.lstatSync(pathname, { bigint: true }) : fsSync.lstatSync(pathname)) : { stat: fsSync.lstatSync(pathname, { bigint: true }) };
440
+ const stat = options.exactIdentity
441
+ ? inspectFileIdentitySync(() => fsSync.lstatSync(pathname, { bigint: true }))
442
+ : fsSync.lstatSync(pathname, { bigint: true });
443
+ observed = { stat, ...(options.exactIdentity ? { identity: { dev: stat.dev, ino: stat.ino } } : {}) };
435
444
  }
436
445
  catch (error) {
437
446
  await assertCurrent();
@@ -440,7 +449,8 @@ export async function openRootDirectoryListing(root, directory, options, receipt
440
449
  throw error;
441
450
  }
442
451
  await assertCurrent();
443
- return { kind: "entry", entry: pathStatFromStats(observed.stat, name), identity: observed.identity };
452
+ return { kind: "entry", entry: pathStatFromStats(observed.stat, name), identity: observed.identity,
453
+ ...(options.exactIdentity ? { observation: observed.stat } : {}) };
444
454
  }
445
455
  }
446
456
  catch (error) {
@@ -31,9 +31,9 @@ export function observeCanonicalRoot(ioFs, rootPath) {
31
31
  const stat = inspectCanonicalRoot(ioFs, rootPath);
32
32
  const identity = Object.freeze({ dev: stat.dev, ino: stat.ino });
33
33
  let observedPath = rootPath;
34
- if (process.platform === "win32" && ioFs === fs) {
35
- // Native leaf resolution expands Windows short names. Bind that root
36
- // spelling before traversal without replacing the observed identity.
34
+ if (ioFs === fs) {
35
+ // Match native leaf resolution (including case and Windows short names)
36
+ // without replacing the already observed root identity.
37
37
  observedPath = realpathSync.native(pathForWindowsFilesystem(rootPath));
38
38
  if (observedPath !== rootPath)
39
39
  inspectCanonicalRoot(ioFs, observedPath, identity);
@@ -8,5 +8,5 @@ export declare function rawPathRelativeToCanonicalRoot(candidate: string, rootCa
8
8
  export declare function resolvePathViaExistingAncestor(targetPath: string): Promise<string>;
9
9
  export declare function resolvePathViaExistingAncestorSync(targetPath: string): string;
10
10
  export type RootPathMetadataMode = "native" | "ordinary";
11
- export declare function resolveExistingAncestor(targetPath: string, mode: RootPathMetadataMode): string;
12
- export declare function resolveSymlinkHopPath(symlinkPath: string, mode: RootPathMetadataMode, rejectUnresolved?: boolean): string;
11
+ export declare function resolveExistingAncestor(targetPath: string, mode: RootPathMetadataMode, preserveRawSegments?: boolean, remainingHops?: number): string;
12
+ export declare function resolveSymlinkHopPath(symlinkPath: string, mode: RootPathMetadataMode, rejectUnresolved?: boolean, remainingHops?: number): string;
@@ -104,14 +104,15 @@ function pathExistsSync(targetPath) {
104
104
  export function resolvePathViaExistingAncestorSync(targetPath) {
105
105
  return resolveExistingAncestor(targetPath, "ordinary");
106
106
  }
107
- export function resolveExistingAncestor(targetPath, mode) {
108
- const exists = mode === "native" ? pathExists : pathExistsSync;
109
- const canonicalize = mode === "native" ? realpathSync.native : realpathSync;
107
+ export function resolveExistingAncestor(targetPath, mode, preserveRawSegments = false, remainingHops = 64) {
108
+ const exists = mode === "native" || preserveRawSegments ? pathExists : pathExistsSync;
109
+ const canonicalize = realpathSync.native;
110
110
  assertNoWindowsPathAlias(targetPath);
111
111
  const normalized = resolvePathPreservingWindowsRoot(targetPath);
112
112
  assertNoWindowsPathAlias(normalized);
113
- let cursor = normalized;
114
- const filesystemRoot = path.parse(normalized).root;
113
+ const candidate = preserveRawSegments ? absolutePathWithRawSegments(targetPath) : normalized;
114
+ let cursor = candidate;
115
+ const filesystemRoot = path.parse(candidate).root;
115
116
  while (cursor !== filesystemRoot &&
116
117
  !exists(cursor)) {
117
118
  cursor = path.dirname(cursor);
@@ -123,22 +124,30 @@ export function resolveExistingAncestor(targetPath, mode) {
123
124
  try {
124
125
  rawResolvedAncestor = canonicalize(pathForWindowsFilesystem(cursor));
125
126
  }
126
- catch {
127
- return normalized;
127
+ catch (error) {
128
+ if (!preserveRawSegments)
129
+ return normalized;
130
+ if (!isNotFoundPathError(error))
131
+ throw error;
132
+ rawResolvedAncestor = resolveSymlinkHopPath(cursor, mode, false, remainingHops);
128
133
  }
129
134
  assertNoWindowsPathAlias(rawResolvedAncestor);
130
135
  const resolvedAncestor = resolvePathPreservingWindowsRoot(rawResolvedAncestor);
131
136
  assertNoWindowsPathAlias(resolvedAncestor);
132
137
  // Keep a leading suffix separator relative to the canonical ancestor.
133
- const resolved = cursor === normalized
138
+ const suffix = candidate.slice(cursor.length);
139
+ if (preserveRawSegments && suffix.split(path.sep).includes("..")) {
140
+ throw new FsSafeError("symlink", "symlink target has an unresolved parent component");
141
+ }
142
+ const resolved = cursor === candidate
134
143
  ? resolvedAncestor
135
- : path.resolve(resolvedAncestor, `.${path.sep}${normalized.slice(cursor.length)}`);
144
+ : path.resolve(resolvedAncestor, `.${path.sep}${suffix}`);
136
145
  assertNoWindowsPathAlias(resolved);
137
146
  return resolved;
138
147
  }
139
- export function resolveSymlinkHopPath(symlinkPath, mode, rejectUnresolved) {
148
+ export function resolveSymlinkHopPath(symlinkPath, mode, rejectUnresolved, remainingHops = 64) {
140
149
  try {
141
- const canonicalize = mode === "native" ? realpathSync.native : realpathSync;
150
+ const canonicalize = realpathSync.native;
142
151
  const rawRealPath = canonicalize(symlinkPath);
143
152
  assertNoWindowsPathAlias(rawRealPath, "filesystem", "resolved symlink path uses a Windows filesystem namespace alias");
144
153
  const realPath = resolvePathPreservingWindowsRoot(rawRealPath);
@@ -151,11 +160,16 @@ export function resolveSymlinkHopPath(symlinkPath, mode, rejectUnresolved) {
151
160
  }
152
161
  if (!isNotFoundPathError(error))
153
162
  throw error;
163
+ if (remainingHops <= 0)
164
+ throw new FsSafeError("symlink", "too many unresolved symlink hops");
154
165
  const linkTarget = fs.readlinkSync(symlinkPath);
155
166
  assertNoWindowsPathAlias(linkTarget, "filesystem", "symlink target uses a Windows filesystem namespace alias");
156
167
  const linkDir = resolvePathPreservingWindowsRoot(path.dirname(symlinkPath));
157
- const targetPath = resolvePathFromBasePreservingWindowsRoot(linkDir, linkTarget);
158
- const resolved = resolveExistingAncestor(targetPath, mode);
168
+ // Keep parent segments until the kernel has resolved the existing prefix.
169
+ const targetPath = path.isAbsolute(linkTarget) ? linkTarget
170
+ : path.parse(linkTarget).root ? resolvePathFromBasePreservingWindowsRoot(linkDir, linkTarget)
171
+ : `${linkDir}${path.sep}${linkTarget}`;
172
+ const resolved = resolveExistingAncestor(targetPath, mode, true, remainingHops - 1);
159
173
  assertNoWindowsPathAlias(resolved, "filesystem", "resolved symlink path uses a Windows filesystem namespace alias");
160
174
  return resolved;
161
175
  }
@@ -255,8 +255,8 @@ function assertResolvedLinkDirectory(pathname, isLast) {
255
255
  const stat = fs.statSync(pathForWindowsFilesystem(pathname));
256
256
  assertDirectoryBeforeMoreSegments(stat, pathname, isLast);
257
257
  }
258
- // Both public APIs use synchronous metadata; only canonicalization and ancestor
259
- // existence differ. Promise wrappers preserve the asynchronous rejection API.
258
+ // Both public APIs use kernel canonicalization and synchronous metadata; only
259
+ // ancestor existence differs. Promise wrappers preserve asynchronous rejection.
260
260
  function traverseRootPath(params, { mode = "native", observation: observationOutput, observeRoot, removalReceipts }) {
261
261
  const input = captureValidRootPathInputs(params);
262
262
  const rawAbsolutePath = absolutePathWithRawSegments(input.absolutePath);
@@ -1,7 +1,10 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import { type AsyncDirectoryGuard } from "./directory-guard.js";
1
3
  export declare function resolveCallbackTempPath(workspaceDir: string, component: string): string;
2
4
  export declare function writeCallbackSibling<T>(params: {
3
5
  tempDir: string;
4
6
  tempName: string;
7
+ rootBoundary?: AsyncDirectoryGuard<BigIntStats>;
5
8
  write: (tempPath: string) => Promise<T>;
6
9
  producerIsolation?: "private-directory";
7
10
  resolveFinalPath: (result: T) => string;
@@ -13,6 +13,7 @@ import { cachedNoReplaceDevice, isNoReplaceUnavailable } from "./native-noreplac
13
13
  import { handoffPrivateProducerFile, } from "./private-producer-handoff.js";
14
14
  import { resolveReadOpenFlags } from "./read-open-flags.js";
15
15
  import { rootFromDirectoryGuard } from "./root-impl.js";
16
+ import { admitPathInsideRoot } from "./root-boundary.js";
16
17
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
17
18
  import { registerTempPathForExit } from "./temp-cleanup.js";
18
19
  import { createOwnedTempFile } from "./temp-target.js";
@@ -160,10 +161,22 @@ export async function writeCallbackSibling(params) {
160
161
  const maxBytes = params.maxBytes;
161
162
  const syncTempFile = params.syncTempFile;
162
163
  const syncParentDir = params.syncParentDir;
164
+ const rootBoundary = params.rootBoundary;
163
165
  const guard = await createAsyncDirectoryGuard(parent, { bigint: true });
164
- const assertParent = () => assertDirectoryIdentitySync(parent, {
165
- dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath,
166
- });
166
+ const assertParent = () => {
167
+ assertDirectoryIdentitySync(parent, {
168
+ dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath,
169
+ });
170
+ if (rootBoundary) {
171
+ assertDirectoryIdentitySync(rootBoundary.dir, {
172
+ dev: rootBoundary.stat.dev, ino: rootBoundary.stat.ino, realPath: rootBoundary.realPath,
173
+ });
174
+ if (!admitPathInsideRoot({
175
+ rootPath: rootBoundary.realPath, candidatePath: guard.realPath, rootIdentity: rootBoundary.stat,
176
+ }))
177
+ throw new FsSafeError("outside-workspace", "sibling parent resolves outside the root");
178
+ }
179
+ };
167
180
  let handle;
168
181
  let identity;
169
182
  let unregister;
@@ -181,6 +194,8 @@ export async function writeCallbackSibling(params) {
181
194
  }
182
195
  };
183
196
  try {
197
+ if (rootBoundary)
198
+ assertParent();
184
199
  let result;
185
200
  if (producerIsolation === "private-directory") {
186
201
  const isolated = await writeIsolatedProducer({
@@ -9,7 +9,7 @@ export declare function selectedWatchChildren(scopes: readonly WatchScope[], dir
9
9
  /** A folded directory can contain a missing scope component, unlike a nameless child. */
10
10
  export declare function selectedWatchSubtree(scopes: readonly WatchScope[], directory: string): boolean;
11
11
  export declare function nativeChanges(scopes: readonly WatchScope[], snapshot: WatchSnapshot | undefined, batch: NativeWatchBatch, limit?: number, after?: WatchSnapshot): WatchChange[] | undefined;
12
- export declare function changedEntries(before: WatchSnapshot | undefined, after: WatchSnapshot, limit: number): WatchChange[] | undefined;
12
+ export declare function changedEntries(before: WatchSnapshot | undefined, after: WatchSnapshot, limit: number): Promise<WatchChange[] | undefined>;
13
13
  /** Backend names never establish authority to publish a pathname. */
14
14
  export declare function guardedHintChanges(scopes: readonly WatchScope[], before: WatchSnapshot | undefined, after: WatchSnapshot, hints: readonly WatchChange[] | undefined, observed: readonly WatchChange[] | undefined, limit: number): WatchChange[] | undefined;
15
15
  /** Resolve native spelling aliases without treating case folding as identity. */
@@ -1,9 +1,9 @@
1
1
  import path from "node:path";
2
2
  import { FsSafeError } from "./errors.js";
3
3
  import { isNotFoundPathError } from "./path.js";
4
- import { assertRootIdentityCurrent, resolvePathInRoot } from "./root-context.js";
5
- import { createRootDirectoryObservationGuard, assertRootDirectoryObservationGuard } from "./root-directory-list.js";
6
- import { lookupRootDirectoryEntry } from "./root-directory-entry.js";
4
+ import {} from "./root-context.js";
5
+ import {} from "./root-directory-list.js";
6
+ import { watchRootCurrent as assertRootIdentityCurrent, watchResolve as resolvePathInRoot, watchGuard as createRootDirectoryObservationGuard, watchAssert as assertRootDirectoryObservationGuard, watchLookup as lookupRootDirectoryEntry, watchYield } from "./watch-io.js";
7
7
  function below(parent, child) {
8
8
  return parent === "" ? child !== "" : child.startsWith(parent + path.sep);
9
9
  }
@@ -86,7 +86,7 @@ export function nativeChanges(scopes, snapshot, batch, limit = 256, after) {
86
86
  }
87
87
  return [...result.values()];
88
88
  }
89
- export function changedEntries(before, after, limit) {
89
+ export async function changedEntries(before, after, limit) {
90
90
  if (!before)
91
91
  return undefined;
92
92
  const changes = [];
@@ -102,12 +102,17 @@ export function changedEntries(before, after, limit) {
102
102
  changes.push(Object.freeze({ path: name, type: sameFile ? "content" : "structural" }));
103
103
  return true;
104
104
  };
105
- for (const name of before.entries.keys())
105
+ const checkpoint = watchYield();
106
+ for (const name of before.entries.keys()) {
107
+ await checkpoint();
106
108
  if (!compare(name))
107
109
  return undefined;
108
- for (const name of after.entries.keys())
110
+ }
111
+ for (const name of after.entries.keys()) {
112
+ await checkpoint();
109
113
  if (!before.entries.has(name) && !compare(name))
110
114
  return undefined;
115
+ }
111
116
  return changes;
112
117
  }
113
118
  /** Backend names never establish authority to publish a pathname. */
@@ -116,14 +121,19 @@ export function guardedHintChanges(scopes, before, after, hints, observed, limit
116
121
  return undefined;
117
122
  const result = new Map(observed.map(change => [change.path, change]));
118
123
  for (const hint of hints) {
119
- // Drop unobserved non-target names only under a parent with the same guarded
120
- // identity in both passes; otherwise a stale/misdirected watch erases detail.
124
+ // A consumer can observe a transient child between our scans. Invalidate
125
+ // its admitted parent without publishing an unobserved backend spelling.
121
126
  if (!before?.entries.has(hint.path) && !after.entries.has(hint.path) && !scopes.some(scope => scope.path === hint.path)) {
122
127
  const parent = path.dirname(hint.path);
123
128
  const directory = parent === "." ? "" : parent;
124
129
  const left = before?.directories.get(directory), right = after.directories.get(directory);
125
- if (left && right && left.dev === right.dev && left.ino === right.ino)
130
+ if (left && right && left.dev === right.dev && left.ino === right.ino) {
131
+ for (const change of scopedChanges(scopes, { path: directory, type: "structural" })) {
132
+ if (!addChange(result, change, limit))
133
+ return undefined;
134
+ }
126
135
  continue;
136
+ }
127
137
  return undefined;
128
138
  }
129
139
  // Equal snapshots cannot exclude an intermediate change and restoration (ABA).
@@ -140,11 +150,18 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
140
150
  return undefined;
141
151
  if (scheduling && !batch.hints.length)
142
152
  return undefined;
153
+ const checkpoint = watchYield();
143
154
  const result = new Map();
144
- const candidates = new Map([...before?.targets ?? [], ...after.targets, ...after.directories]);
155
+ const candidates = new Map();
156
+ for (const entries of [before?.targets, after.targets, after.directories])
157
+ for (const [name, identity] of entries ?? []) {
158
+ candidates.set(name, identity);
159
+ await checkpoint();
160
+ }
145
161
  if (scheduling) {
146
162
  const identities = new Map();
147
163
  for (const { dev, ino } of candidates.values()) {
164
+ await checkpoint();
148
165
  let inodes = identities.get(dev);
149
166
  if (!inodes)
150
167
  identities.set(dev, inodes = new Set());
@@ -173,9 +190,11 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
173
190
  };
174
191
  if (scheduling) {
175
192
  // A sibling hint cannot hide a replaced scope anchor or its identity chain.
176
- for (const name of after.directories.keys())
193
+ for (const name of after.directories.keys()) {
194
+ await checkpoint();
177
195
  if (scopes.some(scope => !name || scope.path === name || scope.path.startsWith(name + path.sep)))
178
196
  await admittedParent(name);
197
+ }
179
198
  for (const scope of scopes) {
180
199
  const anchor = after.scopeAnchors?.get(scope.path);
181
200
  if (!anchor)
@@ -206,6 +225,7 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
206
225
  return undefined;
207
226
  const guard = await admittedParent(hint.directory);
208
227
  for (const [name, identity] of candidates) {
228
+ await checkpoint();
209
229
  if (identity.dev === guard.stat.dev && identity.ino === guard.stat.ino && selectedWatchSubtree(scopes, name))
210
230
  return undefined;
211
231
  }
@@ -225,22 +245,48 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
225
245
  // Native recursion observes one Root handle and may report descendants of
226
246
  // unselected/entry-only directories. Admit a parent alias by exact identity,
227
247
  // not by lowercasing, and do not turn unselected descendants into events.
228
- try {
229
- guard = await admittedParent(parent);
230
- }
231
- catch (error) {
232
- await assertRootIdentityCurrent(root);
233
- if (scheduling)
248
+ for (let attempt = 0;; attempt++) {
249
+ const retained = guards.has(parent);
250
+ try {
251
+ guard = await admittedParent(parent);
252
+ break;
253
+ }
254
+ catch (error) {
255
+ await assertRootIdentityCurrent(root);
256
+ // An earlier hint may already depend on this selected-alias guard.
257
+ if (scheduling || retained)
258
+ throw error;
259
+ guards.delete(parent);
260
+ if (isNotFoundPathError(error) || (error instanceof FsSafeError && ["not-found", "path-alias", "outside-workspace", "symlink", "not-file"].includes(error.code)))
261
+ break;
262
+ // A yielded admission may race an ordinary sibling deletion. Fresh
263
+ // admission can establish absence; persistent ambiguity still fails.
264
+ // Windows also reports EPERM while an unrelated directory is delete-pending.
265
+ const failure = error;
266
+ const failedRealpath = failure?.syscall === "realpath" &&
267
+ (failure.code === "EBADF" || failure.code === "EPERM");
268
+ if (attempt < 2 && (failedRealpath || error instanceof FsSafeError && error.code === "path-mismatch"))
269
+ continue;
234
270
  throw error;
235
- if (isNotFoundPathError(error) || (error instanceof FsSafeError && ["not-found", "path-alias", "outside-workspace", "symlink", "not-file"].includes(error.code)))
236
- continue;
237
- throw error;
271
+ }
272
+ }
273
+ if (!guard)
274
+ continue;
275
+ let admitted;
276
+ for (const entry of after.directories) {
277
+ await checkpoint();
278
+ if (entry[1].dev === guard.stat.dev && entry[1].ino === guard.stat.ino) {
279
+ admitted = entry;
280
+ break;
281
+ }
238
282
  }
239
- const admitted = [...after.directories].find(([, identity]) => identity.dev === guard.stat.dev && identity.ino === guard.stat.ino);
240
283
  if (!admitted) {
241
284
  // A newly created directory may contain selected entries absent from the baseline.
242
285
  if (scheduling)
243
286
  return undefined;
287
+ // The guarded observation proved this parent is not a selected alias.
288
+ // No result depends on it; do not extend its lifetime through sibling churn.
289
+ guards.delete(hint.directory);
244
290
  continue;
245
291
  }
246
292
  [parent, expected] = admitted;
@@ -288,6 +334,7 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
288
334
  if (scheduling && found.entry.isSymbolicLink)
289
335
  return undefined;
290
336
  for (const [relative, identity] of candidates) {
337
+ await checkpoint();
291
338
  if (!relative || identity.dev !== found.identity.dev || identity.ino !== found.identity.ino)
292
339
  continue;
293
340
  if ((path.dirname(relative) === "." ? "" : path.dirname(relative)) !== parent) {
@@ -0,0 +1,2 @@
1
+ export declare function encodeWatchError(error: unknown): unknown;
2
+ export declare function decodeWatchError(value: unknown): unknown;
@@ -0,0 +1,27 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { createSuppressedError } from "./suppressed-error.js";
3
+ export function encodeWatchError(error) {
4
+ if (!(error instanceof Error))
5
+ return error;
6
+ const combined = error;
7
+ const syscall = error.syscall;
8
+ return {
9
+ name: error.name, message: error.message, code: error.code,
10
+ ...(typeof syscall === "string" ? { syscall } : {}),
11
+ ...(error instanceof FsSafeError ? { details: error.details } : {}),
12
+ cause: error.cause === undefined ? undefined : encodeWatchError(error.cause),
13
+ ...(error.name === "SuppressedError" ? { error: encodeWatchError(combined.error), suppressed: encodeWatchError(combined.suppressed) } : {}),
14
+ };
15
+ }
16
+ export function decodeWatchError(value) {
17
+ if (!value || typeof value !== "object" || !("message" in value) || typeof value.message !== "string")
18
+ return value;
19
+ const error = value;
20
+ if (error.name === "SuppressedError")
21
+ return createSuppressedError(decodeWatchError(error.error), decodeWatchError(error.suppressed), error.message);
22
+ const cause = error.cause === undefined ? undefined : decodeWatchError(error.cause);
23
+ if (error.name === "FsSafeError")
24
+ return new FsSafeError(error.code, error.message, { cause, details: error.details });
25
+ return Object.assign(new Error(error.message, { cause }), { name: error.name, code: error.code,
26
+ ...(typeof error.syscall === "string" ? { syscall: error.syscall } : {}) });
27
+ }