@openclaw/signal 2026.10.2 → 2026.10.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 (113) hide show
  1. package/dist/.setup/{api-BzvUhsz9.mjs → api-CfFaBHKY.mjs} +8 -8
  2. package/dist/.setup/{api-BYvpXSbI.mjs → api-oTYg1sqh.mjs} +8 -8
  3. package/dist/.setup/{approval-handler.runtime-DftlFeEB.mjs → approval-handler.runtime-DzZCg6An.mjs} +2 -3
  4. package/dist/.setup/{approval-reactions-BHIX1jwY.mjs → approval-reactions-POBtr9RL.mjs} +31 -57
  5. package/dist/.setup/{channel-E_23H6I6.mjs → channel-v_OVXflu.mjs} +22 -25
  6. package/dist/.setup/{client-AXcDteBZ.mjs → client-DrPmUInx.mjs} +53 -58
  7. package/dist/.setup/{client-adapter-c_O1ouxZ.mjs → client-adapter-C25_QRp1.mjs} +1 -1
  8. package/dist/.setup/{install-signal-cli-ChvjzM6X.mjs → install-signal-cli-hYVLYGXe.mjs} +4 -26
  9. package/dist/.setup/{monitor-CG20ucVU.mjs → monitor-B2dIc7fW.mjs} +35 -51
  10. package/dist/.setup/{probe-2LTiStRi.mjs → probe-D6n3Nkud.mjs} +2 -2
  11. package/dist/.setup/{reaction-runtime-api-xqwxFZ2S.mjs → reaction-runtime-api-BoF_xKQe.mjs} +1 -1
  12. package/dist/.setup/{reaction-targets-TGHqhdoa.mjs → reaction-targets-Bw8pIemQ.mjs} +1 -1
  13. package/dist/.setup/{send-CAlJlhsT.mjs → send-Da8lyyyC.mjs} +10 -11
  14. package/dist/.setup/{setup-surface-BpW9dJZY.mjs → setup-surface-D1IPnECU.mjs} +2 -2
  15. package/dist/.setup/{transport-detection-CQSEftdN.mjs → transport-detection-C7tIMlYW.mjs} +2 -2
  16. package/dist/.setup/{transport-probes.runtime-DwXEHnXr.mjs → transport-probes.runtime-CE-YN8v-.mjs} +1 -1
  17. package/dist/api.js +8 -8
  18. package/dist/channel-plugin-api.js +1 -1
  19. package/dist/contract-api.js +2 -2
  20. package/dist/reaction-runtime-api.js +1 -1
  21. package/dist/setup-entry.js +1 -1
  22. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +35 -0
  23. package/node_modules/@openclaw/fs-safe/README.md +1 -1
  24. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +1 -1
  25. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -1
  26. package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
  27. package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +4 -4
  28. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  29. package/node_modules/@openclaw/fs-safe/dist/archive-read.js +5 -4
  30. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
  31. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +22 -38
  32. package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.d.ts +17 -0
  33. package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +117 -0
  34. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +8 -5
  35. package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +27 -50
  36. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-acquire.d.ts +3 -0
  37. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-acquire.js +57 -11
  38. package/node_modules/@openclaw/fs-safe/dist/guarded-mutation.js +5 -5
  39. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +52 -5
  40. package/node_modules/@openclaw/fs-safe/dist/guest.js +1 -1
  41. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +1 -0
  42. package/node_modules/@openclaw/fs-safe/dist/native-operations.d.ts +0 -1
  43. package/node_modules/@openclaw/fs-safe/dist/native-operations.js +22 -26
  44. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +5 -1
  45. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +4 -1
  46. package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +2 -1
  47. package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +6 -1
  48. package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +2 -2
  49. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +34 -7
  50. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +1 -4
  51. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +6 -0
  52. package/node_modules/@openclaw/fs-safe/dist/root-create-input.d.ts +15 -1
  53. package/node_modules/@openclaw/fs-safe/dist/root-create-input.js +4 -0
  54. package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +1 -1
  55. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +0 -1
  56. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +6 -12
  57. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +15 -15
  58. package/node_modules/@openclaw/fs-safe/dist/root-paths.js +1 -1
  59. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +4 -4
  60. package/node_modules/@openclaw/fs-safe/dist/root-write-verification.d.ts +10 -1
  61. package/node_modules/@openclaw/fs-safe/dist/root-write-verification.js +20 -0
  62. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +19 -7
  63. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +25 -3
  64. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission.d.ts +2 -0
  65. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.d.ts +14 -0
  66. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +187 -0
  67. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +15 -1
  68. package/node_modules/@openclaw/fs-safe/dist/temp-directory-diagnostics.d.ts +10 -0
  69. package/node_modules/@openclaw/fs-safe/dist/temp-directory-diagnostics.js +29 -0
  70. package/node_modules/@openclaw/fs-safe/dist/temp-target.js +1 -1
  71. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +6 -6
  72. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +4 -4
  73. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +17 -16
  74. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +20 -28
  75. package/node_modules/@openclaw/fs-safe/dist/walk.js +2 -13
  76. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +2 -2
  77. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +3 -1
  78. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +3 -9
  79. package/node_modules/@openclaw/fs-safe/dist/watch.js +2 -3
  80. package/node_modules/@openclaw/fs-safe/docs/advanced.md +1 -1
  81. package/node_modules/@openclaw/fs-safe/docs/archive.md +3 -0
  82. package/node_modules/@openclaw/fs-safe/docs/atomic.md +11 -3
  83. package/node_modules/@openclaw/fs-safe/docs/contributing.md +1 -1
  84. package/node_modules/@openclaw/fs-safe/docs/copy.md +48 -0
  85. package/node_modules/@openclaw/fs-safe/docs/guest.md +21 -4
  86. package/node_modules/@openclaw/fs-safe/docs/path-scope.md +4 -0
  87. package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +46 -8
  88. package/node_modules/@openclaw/fs-safe/docs/temp.md +12 -0
  89. package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
  90. package/node_modules/@openclaw/fs-safe/package.json +15 -15
  91. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  92. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  93. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  94. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  95. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  96. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  97. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  98. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  99. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  100. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  101. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  102. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  103. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  104. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  105. package/node_modules/ws/lib/websocket.js +17 -3
  106. package/node_modules/ws/package.json +1 -1
  107. package/package.json +6 -6
  108. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-arbitration.d.ts +0 -17
  109. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-arbitration.js +0 -66
  110. package/node_modules/@openclaw/fs-safe/dist/publish-file-native.d.ts +0 -4
  111. package/node_modules/@openclaw/fs-safe/dist/publish-file-native.js +0 -35
  112. package/node_modules/@openclaw/fs-safe/dist/root-move-preflight.d.ts +0 -7
  113. package/node_modules/@openclaw/fs-safe/dist/root-move-preflight.js +0 -16
@@ -3,14 +3,14 @@ import { i as normalizeSignalTransportHost } from "./transport-url-CAC0JcuI.mjs"
3
3
  import { i as resolveSignalAccount, o as resolveSignalReplyToMode } from "./accounts-DMtj2E-4.mjs";
4
4
  import { n as normalizeSignalMessagingTarget } from "./normalize-D7kSomXt.mjs";
5
5
  import { a as normalizeSignalAllowRecipient, c as resolveSignalSender, i as isSignalSenderAllowed, l as looksLikeUuid, n as formatSignalSenderDisplay, o as resolveSignalPeerId, r as formatSignalSenderId, s as resolveSignalRecipient, t as formatSignalPairingIdLine } from "./identity-B3JyPqGO.mjs";
6
- import { _ as resolveSignalReactionLevel, m as materializeSignalPresentationFallback, p as registerSignalReplyContext, v as formatSignalMediaText, x as isSignalNativeApprovalHandlerConfigured } from "./channel-E_23H6I6.mjs";
7
- import { a as prepareSignalSocketPath, t as SignalSseRejectionError } from "./client-AXcDteBZ.mjs";
8
- import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-c_O1ouxZ.mjs";
9
- import { n as sendReactionSignal } from "./reaction-runtime-api-xqwxFZ2S.mjs";
6
+ import { _ as resolveSignalReactionLevel, m as materializeSignalPresentationFallback, p as registerSignalReplyContext, v as formatSignalMediaText, x as isSignalNativeApprovalHandlerConfigured } from "./channel-v_OVXflu.mjs";
7
+ import { a as prepareSignalSocketPath, t as SignalSseRejectionError } from "./client-DrPmUInx.mjs";
8
+ import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-C25_QRp1.mjs";
9
+ import { n as sendReactionSignal } from "./reaction-runtime-api-BoF_xKQe.mjs";
10
10
  import { n as getSignalRuntime, t as getOptionalSignalRuntime } from "./runtime-8sxUF217.mjs";
11
- import { i as maybeResolveSignalApprovalReaction, s as resolveSignalApprovalConversationKey, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-BHIX1jwY.mjs";
12
- import { n as registerSignalReactionTargetsForDeliveredPayload, r as maybeResolveSignalQuestionReaction } from "./reaction-targets-TGHqhdoa.mjs";
13
- import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-CAlJlhsT.mjs";
11
+ import { i as maybeResolveSignalApprovalReaction, s as resolveSignalApprovalConversationKey, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-POBtr9RL.mjs";
12
+ import { n as registerSignalReactionTargetsForDeliveredPayload, r as maybeResolveSignalQuestionReaction } from "./reaction-targets-Bw8pIemQ.mjs";
13
+ import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-Da8lyyyC.mjs";
14
14
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
15
15
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
16
16
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
@@ -18,7 +18,7 @@ import { asPositiveSafeInteger, isRecord, normalizeNullableString, normalizeOpti
18
18
  import path from "node:path";
19
19
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
20
20
  import { normalizeE164, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
21
- import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, readAgentRunTerminalOutcome, resolveEnvelopeFormatOptions, resolveInboundDebounceMs, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toHistoryMediaEntries, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
21
+ import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, readAgentRunTerminalOutcome, resolveInboundDebounceMs, resolveInboundMentionDecision, resolveInboundSessionEnvelopeContextAsync, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toHistoryMediaEntries, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
22
22
  import { canonicalizeBase64, detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
23
23
  import { resolvePromptHistoryLimit } from "openclaw/plugin-sdk/number-runtime";
24
24
  import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
@@ -36,13 +36,12 @@ import { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runti
36
36
  import { createInterface } from "node:readline";
37
37
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
38
38
  import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
39
- import { DEFAULT_EMOJIS, DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
39
+ import { DEFAULT_EMOJIS, createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
40
40
  import { defineStableChannelIngressIdentity, fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
41
41
  import { isControlCommandMessage } from "openclaw/plugin-sdk/command-detection";
42
42
  import { createInternalHookEvent, fireAndForgetHook, toInternalMessageReceivedContext, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
43
43
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
44
44
  import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
45
- import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
46
45
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
47
46
  import { upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
48
47
  import { listChatCommands, maybeResolveTextAlias, normalizeCommandBody } from "openclaw/plugin-sdk/command-auth-native";
@@ -77,8 +76,8 @@ async function waitForSignalDaemonReady(params) {
77
76
  await (params.waitForTransportReadyFn ?? waitForTransportReady)({
78
77
  label: "signal daemon",
79
78
  timeoutMs: Math.max(0, params.startupDeadlineMs - Date.now()),
80
- logAfterMs: params.logAfterMs,
81
- logIntervalMs: params.logIntervalMs,
79
+ logAfterMs: 1e4,
80
+ logIntervalMs: 1e4,
82
81
  pollIntervalMs: 150,
83
82
  abortSignal: params.abortSignal,
84
83
  runtime: params.runtime,
@@ -201,9 +200,7 @@ function spawnSignalDaemon(opts) {
201
200
  } else error(`signal-cli process error: ${String(err)}`);
202
201
  });
203
202
  return {
204
- pid: child.pid ?? void 0,
205
203
  exited: exitedPromise,
206
- isExited: () => exited,
207
204
  stop: () => {
208
205
  if (exited) return Promise.resolve();
209
206
  if (stopPromise) return stopPromise;
@@ -328,11 +325,6 @@ function normalizeSignalUuidEntry(entry) {
328
325
  if (lower.startsWith("uuid:")) return signalStripped.slice(5).trim() || null;
329
326
  return looksLikeUuid(signalStripped) ? signalStripped : null;
330
327
  }
331
- function normalizeSignalPhoneEntry(entry) {
332
- const parsed = strippedSignalEntry(entry);
333
- if (!parsed) return null;
334
- return normalizeSignalAllowRecipient(parsed.trimmed) ?? null;
335
- }
336
328
  const signalIngressIdentity = defineStableChannelIngressIdentity({
337
329
  key: "stable",
338
330
  normalizeEntry: () => null,
@@ -340,7 +332,7 @@ const signalIngressIdentity = defineStableChannelIngressIdentity({
340
332
  {
341
333
  key: "phone",
342
334
  kind: "phone",
343
- normalizeEntry: normalizeSignalPhoneEntry,
335
+ normalizeEntry: (entry) => normalizeSignalAllowRecipient(entry) ?? null,
344
336
  normalizeSubject: (value) => value,
345
337
  sensitivity: "pii"
346
338
  },
@@ -461,7 +453,7 @@ function resolveSignalInboundConversationKey(accountId, entry) {
461
453
  }
462
454
  function isSignalActiveRunControlText(text) {
463
455
  if (isAbortRequestText(text)) return true;
464
- const normalizedBody = normalizeCommandBody(text.trim());
456
+ const normalizedBody = normalizeCommandBody(text);
465
457
  const alias = maybeResolveTextAlias(normalizedBody);
466
458
  if (!alias) return false;
467
459
  const command = listChatCommands().find((entry) => entry.textAliases.some((candidate) => candidate.trim().toLowerCase() === alias));
@@ -699,7 +691,6 @@ function createSignalEventHandler(deps) {
699
691
  accountId: deps.accountId,
700
692
  groups: resolveChannelGroups(deps.cfg, "signal", deps.accountId)
701
693
  });
702
- const statusReactionTiming = deps.statusReactionTiming ?? DEFAULT_TIMING;
703
694
  const activeEnqueueEntries = /* @__PURE__ */ new WeakSet();
704
695
  async function handleSignalInboundMessage(entry) {
705
696
  const { cfg } = entry;
@@ -718,10 +709,9 @@ function createSignalEventHandler(deps) {
718
709
  groupId: entry.groupId,
719
710
  senderPeerId: entry.senderPeerId
720
711
  });
721
- const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId });
722
- const envelopeOptions = resolveEnvelopeFormatOptions(cfg);
723
- const previousTimestamp = readSessionUpdatedAt({
724
- storePath,
712
+ const { envelopeOptions, previousTimestamp } = await resolveInboundSessionEnvelopeContextAsync({
713
+ cfg,
714
+ agentId: route.agentId,
725
715
  sessionKey: route.sessionKey
726
716
  });
727
717
  const body = formatInboundEnvelope({
@@ -858,7 +848,6 @@ function createSignalEventHandler(deps) {
858
848
  } },
859
849
  initialEmoji: ackReaction,
860
850
  emojis: { stallHard: DEFAULT_EMOJIS.stallSoft },
861
- timing: statusReactionTiming,
862
851
  onError: (err) => {
863
852
  logAckFailure({
864
853
  log: logVerbose,
@@ -1066,30 +1055,27 @@ function createSignalEventHandler(deps) {
1066
1055
  await settle();
1067
1056
  return;
1068
1057
  }
1069
- if (entries.length === 1) {
1070
- await handleSignalInboundMessage({
1058
+ let inbound = last;
1059
+ if (entries.length > 1) {
1060
+ const combinedText = entries.map((entry) => entry.bodyText).filter(Boolean).join("\n");
1061
+ const combinedCommandBody = entries.map((entry) => entry.commandBody).filter(Boolean).join("\n");
1062
+ if (!combinedText.trim()) {
1063
+ await settle();
1064
+ return;
1065
+ }
1066
+ inbound = {
1071
1067
  ...last,
1072
- channelIngress,
1073
- turnAdoptionLifecycle: lifecycle
1074
- });
1075
- await settle();
1076
- return;
1077
- }
1078
- const combinedText = entries.map((entry) => entry.bodyText).filter(Boolean).join("\n");
1079
- const combinedCommandBody = entries.map((entry) => entry.commandBody).filter(Boolean).join("\n");
1080
- if (!combinedText.trim()) {
1081
- await settle();
1082
- return;
1068
+ bodyText: combinedText,
1069
+ commandBody: combinedCommandBody,
1070
+ isBatched: true,
1071
+ nativeReplyBody: last.nativeReplyBody ?? last.bodyText,
1072
+ media: entries.flatMap((entry) => entry.media ?? [])
1073
+ };
1083
1074
  }
1084
1075
  await handleSignalInboundMessage({
1085
- ...last,
1086
- bodyText: combinedText,
1087
- commandBody: combinedCommandBody,
1088
- turnAdoptionLifecycle: lifecycle,
1089
- isBatched: true,
1090
- nativeReplyBody: last.nativeReplyBody ?? last.bodyText,
1091
- media: entries.flatMap((entry) => entry.media ?? []),
1092
- channelIngress
1076
+ ...inbound,
1077
+ channelIngress,
1078
+ turnAdoptionLifecycle: lifecycle
1093
1079
  });
1094
1080
  await settle();
1095
1081
  }
@@ -1872,7 +1858,7 @@ async function deliverReplies(params) {
1872
1858
  } : {}
1873
1859
  });
1874
1860
  replyPlan.markSent();
1875
- const messageId = typeof result?.messageId === "string" && result.messageId.trim() ? result.messageId.trim() : null;
1861
+ const messageId = result.messageId.trim();
1876
1862
  if (messageId) deliveryResults.push({
1877
1863
  channel: "signal",
1878
1864
  messageId,
@@ -1992,8 +1978,6 @@ async function monitorSignalProvider(opts = {}) {
1992
1978
  baseUrl,
1993
1979
  abortSignal: daemonLifecycle.abortSignal,
1994
1980
  startupDeadlineMs: startupDeadline,
1995
- logAfterMs: 1e4,
1996
- logIntervalMs: 1e4,
1997
1981
  runtime,
1998
1982
  waitForTransportReadyFn
1999
1983
  });
@@ -1,6 +1,6 @@
1
1
  import { o as __exportAll } from "./doctor-contract-api-CGS80vmV.mjs";
2
- import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-c_O1ouxZ.mjs";
3
- import { t as detectSignalTransport } from "./transport-detection-CQSEftdN.mjs";
2
+ import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-C25_QRp1.mjs";
3
+ import { t as detectSignalTransport } from "./transport-detection-C7tIMlYW.mjs";
4
4
  import { normalizeOptionalString, normalizeTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { runChannelProbe } from "openclaw/plugin-sdk/text-utility-runtime";
6
6
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
@@ -1,6 +1,6 @@
1
1
  import { i as resolveSignalAccount } from "./accounts-DMtj2E-4.mjs";
2
2
  import { r as normalizeSignalReactionRecipient } from "./normalize-D7kSomXt.mjs";
3
- import { n as signalRpcRequest } from "./client-adapter-c_O1ouxZ.mjs";
3
+ import { n as signalRpcRequest } from "./client-adapter-C25_QRp1.mjs";
4
4
  import { t as resolveSignalRpcContext } from "./rpc-context-BAXcX7dI.mjs";
5
5
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
6
6
  //#region extensions/signal/src/send-reactions.ts
@@ -1,6 +1,6 @@
1
1
  import { o as __exportAll } from "./doctor-contract-api-CGS80vmV.mjs";
2
2
  import { i as resolveSignalDeliveredConversationKey } from "./approval-auth-BITw96pZ.mjs";
3
- import { c as resolveSignalApprovalTargetAuthorKeys, o as registerSignalApprovalReactionTargetForDeliveredPayload } from "./approval-reactions-BHIX1jwY.mjs";
3
+ import { c as resolveSignalApprovalTargetAuthorKeys, o as registerSignalApprovalReactionTargetForDeliveredPayload } from "./approval-reactions-POBtr9RL.mjs";
4
4
  import { createQuestionReactionTargetStore, questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
5
5
  import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
6
6
  //#region extensions/signal/src/question-reactions.ts
@@ -1,9 +1,9 @@
1
1
  import { o as __exportAll } from "./doctor-contract-api-CGS80vmV.mjs";
2
2
  import { i as resolveSignalAccount } from "./accounts-DMtj2E-4.mjs";
3
3
  import { n as normalizeSignalMessagingTarget } from "./normalize-D7kSomXt.mjs";
4
- import { p as registerSignalReplyContext, y as markdownToSignalText } from "./channel-E_23H6I6.mjs";
5
- import { o as isSignalQuoteMetadataRejection } from "./client-AXcDteBZ.mjs";
6
- import { n as signalRpcRequest } from "./client-adapter-c_O1ouxZ.mjs";
4
+ import { p as registerSignalReplyContext, y as markdownToSignalText } from "./channel-v_OVXflu.mjs";
5
+ import { o as isSignalQuoteMetadataRejection } from "./client-DrPmUInx.mjs";
6
+ import { n as signalRpcRequest } from "./client-adapter-C25_QRp1.mjs";
7
7
  import { t as resolveSignalRpcContext } from "./rpc-context-BAXcX7dI.mjs";
8
8
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
9
9
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
@@ -33,7 +33,7 @@ function assertSignalRecipientDelivery(result, target) {
33
33
  if (failures.length === 0 || target.type === "group" && hasSuccessfulRecipient) return;
34
34
  throw new Error(`Signal send failed for ${failures.length} recipient${failures.length === 1 ? "" : "s"}: ${[...new Set(failures)].join(", ")}`);
35
35
  }
36
- async function resolveSignalRpcAccountInfo(opts) {
36
+ function resolveSignalRpcAccountInfo(opts) {
37
37
  const cfg = requireRuntimeConfig(opts.cfg, "Signal RPC account resolution");
38
38
  return resolveSignalAccount({
39
39
  cfg,
@@ -43,14 +43,13 @@ async function resolveSignalRpcAccountInfo(opts) {
43
43
  function parseTarget(raw) {
44
44
  const value = normalizeSignalMessagingTarget(raw);
45
45
  if (!value) throw new Error("Signal recipient is required");
46
- const normalized = normalizeLowercaseStringOrEmpty(value);
47
- if (normalized.startsWith("group:")) return {
46
+ if (value.startsWith("group:")) return {
48
47
  type: "group",
49
- groupId: value.slice(6).trim()
48
+ groupId: value.slice(6)
50
49
  };
51
- if (normalized.startsWith("username:")) return {
50
+ if (value.startsWith("username:")) return {
52
51
  type: "username",
53
- username: value.slice(9).trim()
52
+ username: value.slice(9)
54
53
  };
55
54
  return {
56
55
  type: "recipient",
@@ -208,7 +207,7 @@ async function sendMessageSignal(to, text, opts) {
208
207
  };
209
208
  }
210
209
  async function sendTypingSignal(to, opts) {
211
- const accountInfo = await resolveSignalRpcAccountInfo(opts);
210
+ const accountInfo = resolveSignalRpcAccountInfo(opts);
212
211
  const { baseUrl, account } = resolveSignalRpcContext(opts, accountInfo);
213
212
  const target = parseTarget(to);
214
213
  if (target.type === "username") return false;
@@ -224,7 +223,7 @@ async function sendTypingSignal(to, opts) {
224
223
  }
225
224
  async function sendReadReceiptSignal(to, targetTimestamp, opts) {
226
225
  if (!Number.isFinite(targetTimestamp) || targetTimestamp <= 0) return false;
227
- const accountInfo = await resolveSignalRpcAccountInfo(opts);
226
+ const accountInfo = resolveSignalRpcAccountInfo(opts);
228
227
  const { baseUrl, account } = resolveSignalRpcContext(opts, accountInfo);
229
228
  const target = parseTarget(to);
230
229
  if (target.type !== "recipient") return false;
@@ -1,6 +1,6 @@
1
1
  import { i as resolveSignalAccount, n as listSignalAccountIds } from "./accounts-DMtj2E-4.mjs";
2
- import { c as signalNumberTextInput, i as createSignalCliPathTextInput, o as signalCompletionNote, s as signalDmPolicy } from "./channel-E_23H6I6.mjs";
3
- import { r as installSignalCli } from "./install-signal-cli-ChvjzM6X.mjs";
2
+ import { c as signalNumberTextInput, i as createSignalCliPathTextInput, o as signalCompletionNote, s as signalDmPolicy } from "./channel-v_OVXflu.mjs";
3
+ import { r as installSignalCli } from "./install-signal-cli-hYVLYGXe.mjs";
4
4
  import { createDetectedBinaryStatus, createSetupTranslator, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
5
5
  import { detectBinary } from "openclaw/plugin-sdk/setup-tools";
6
6
  //#region extensions/signal/src/setup-surface.ts
@@ -6,10 +6,10 @@ const DEFAULT_PROBE_TIMEOUT_MS = 1e4;
6
6
  async function detectSignalTransport(params) {
7
7
  const url = normalizeSignalTransportUrl(params.url);
8
8
  const timeoutMs = params.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
9
- const probes = params.probeNative && params.probeContainer ? void 0 : await import("./transport-probes.runtime-DwXEHnXr.mjs");
9
+ const probes = params.probeNative && params.probeContainer ? void 0 : await import("./transport-probes.runtime-CE-YN8v-.mjs");
10
10
  const probeNative = params.probeNative ?? probes?.nativeCheck;
11
11
  const probeContainer = params.probeContainer ?? probes?.containerCheck;
12
- if (!probeNative || !probeContainer) throw new Error("Signal transport probes are unavailable");
12
+ if (!probeNative || !probeContainer) throw new Error("Signal transport checks are unavailable");
13
13
  const [native, container] = await Promise.all([probeNative(url, timeoutMs).catch(() => ({ ok: false })), probeContainer(url, timeoutMs, params.account).catch(() => ({ ok: false }))]);
14
14
  if (native.ok) return {
15
15
  kind: "external-native",
@@ -1,2 +1,2 @@
1
- import { n as signalCheck, s as containerCheck } from "./client-AXcDteBZ.mjs";
1
+ import { n as signalCheck, s as containerCheck } from "./client-DrPmUInx.mjs";
2
2
  export { containerCheck, signalCheck as nativeCheck };
package/dist/api.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./.setup/accounts-DMtj2E-4.mjs";
2
2
  import { n as normalizeSignalMessagingTarget, t as looksLikeSignalTargetId } from "./.setup/normalize-D7kSomXt.mjs";
3
3
  import { a as normalizeSignalAllowRecipient, c as resolveSignalSender, i as isSignalSenderAllowed, l as looksLikeUuid, n as formatSignalSenderDisplay, o as resolveSignalPeerId, r as formatSignalSenderId, s as resolveSignalRecipient, t as formatSignalPairingIdLine } from "./.setup/identity-B3JyPqGO.mjs";
4
- import { _ as resolveSignalReactionLevel, a as normalizeSignalAccountInput, b as markdownToSignalTextChunks, d as probeSignalTransport, f as writeSignalAccountTransport, g as signalMessageActions, h as resolveSignalOutboundTarget, t as signalPlugin, u as prepareSignalManagedNativeTransport, y as markdownToSignalText } from "./.setup/channel-E_23H6I6.mjs";
5
- import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-xqwxFZ2S.mjs";
6
- import { t as detectSignalTransport } from "./.setup/transport-detection-CQSEftdN.mjs";
7
- import { t as signalSetupPlugin } from "./.setup/api-BYvpXSbI.mjs";
8
- import { r as installSignalCli } from "./.setup/install-signal-cli-ChvjzM6X.mjs";
9
- import { t as monitorSignalProvider } from "./.setup/monitor-CG20ucVU.mjs";
10
- import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./.setup/send-CAlJlhsT.mjs";
11
- import { t as probeSignal } from "./.setup/probe-2LTiStRi.mjs";
4
+ import { _ as resolveSignalReactionLevel, a as normalizeSignalAccountInput, b as markdownToSignalTextChunks, d as probeSignalTransport, f as writeSignalAccountTransport, g as signalMessageActions, h as resolveSignalOutboundTarget, t as signalPlugin, u as prepareSignalManagedNativeTransport, y as markdownToSignalText } from "./.setup/channel-v_OVXflu.mjs";
5
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-BoF_xKQe.mjs";
6
+ import { t as detectSignalTransport } from "./.setup/transport-detection-C7tIMlYW.mjs";
7
+ import { t as signalSetupPlugin } from "./.setup/api-oTYg1sqh.mjs";
8
+ import { r as installSignalCli } from "./.setup/install-signal-cli-hYVLYGXe.mjs";
9
+ import { t as monitorSignalProvider } from "./.setup/monitor-B2dIc7fW.mjs";
10
+ import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./.setup/send-Da8lyyyC.mjs";
11
+ import { t as probeSignal } from "./.setup/probe-D6n3Nkud.mjs";
12
12
  export { detectSignalTransport, formatSignalPairingIdLine, formatSignalSenderDisplay, formatSignalSenderId, installSignalCli, isSignalSenderAllowed, listEnabledSignalAccounts, listSignalAccountIds, looksLikeSignalTargetId, looksLikeUuid, markdownToSignalText, markdownToSignalTextChunks, monitorSignalProvider, normalizeSignalAccountInput, normalizeSignalAllowRecipient, normalizeSignalMessagingTarget, prepareSignalManagedNativeTransport, probeSignal, probeSignalTransport, removeReactionSignal, resolveDefaultSignalAccountId, resolveSignalAccount, resolveSignalOutboundTarget, resolveSignalPeerId, resolveSignalReactionLevel, resolveSignalRecipient, resolveSignalSender, sendMessageSignal, sendReactionSignal, sendReadReceiptSignal, sendTypingSignal, signalMessageActions, signalPlugin, signalSetupPlugin, writeSignalAccountTransport };
@@ -1,2 +1,2 @@
1
- import { t as signalPlugin } from "./.setup/channel-E_23H6I6.mjs";
1
+ import { t as signalPlugin } from "./.setup/channel-v_OVXflu.mjs";
2
2
  export { signalPlugin };
@@ -1,4 +1,4 @@
1
1
  import { n as normalizeSignalMessagingTarget, r as normalizeSignalReactionRecipient, t as looksLikeSignalTargetId } from "./.setup/normalize-D7kSomXt.mjs";
2
2
  import { i as isSignalSenderAllowed } from "./.setup/identity-B3JyPqGO.mjs";
3
- import { a as looksLikeArchive, i as installSignalCliFromRelease, n as extractSignalCliArchive, o as pickAsset, r as installSignalCli, t as downloadToFile } from "./.setup/install-signal-cli-ChvjzM6X.mjs";
4
- export { downloadToFile, extractSignalCliArchive, installSignalCli, installSignalCliFromRelease, isSignalSenderAllowed, looksLikeArchive, looksLikeSignalTargetId, normalizeSignalMessagingTarget, normalizeSignalReactionRecipient, pickAsset };
3
+ import { i as installSignalCliFromRelease, n as extractSignalCliArchive, r as installSignalCli, t as downloadToFile } from "./.setup/install-signal-cli-hYVLYGXe.mjs";
4
+ export { downloadToFile, extractSignalCliArchive, installSignalCli, installSignalCliFromRelease, isSignalSenderAllowed, looksLikeSignalTargetId, normalizeSignalMessagingTarget, normalizeSignalReactionRecipient };
@@ -1,2 +1,2 @@
1
- import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-xqwxFZ2S.mjs";
1
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./.setup/reaction-runtime-api-BoF_xKQe.mjs";
2
2
  export { removeReactionSignal, sendReactionSignal };
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  var setup_entry_default = defineBundledChannelSetupEntry({
5
5
  importMetaUrl: import.meta.url,
6
6
  plugin: {
7
- specifier: fileURLToPath(new URL(".setup/api-BzvUhsz9.mjs", import.meta.url).href),
7
+ specifier: fileURLToPath(new URL(".setup/api-CfFaBHKY.mjs", import.meta.url).href),
8
8
  exportName: "signalSetupPlugin"
9
9
  }
10
10
  });
@@ -2,6 +2,41 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.25.0 - 2026-10-08
6
+
7
+ ### Highlights
8
+
9
+ - **Faster synchronous plugin captures:** 16% faster than pre-change main on a measured 6,000-file tree; the new `createRootFileCopyBatchSync` API on `advanced` also reuses directory admissions across copies. ([#878](https://github.com/openclaw/fs-safe/pull/878), [#849](https://github.com/openclaw/fs-safe/pull/849))
10
+ - **Guest workspace publication:** works on gVisor and other Linux filesystems that reject `RENAME_NOREPLACE`. ([#876](https://github.com/openclaw/fs-safe/pull/876))
11
+ - **Root-backed sidecar locks:** native asynchronous release and exit cleanup no longer strand locks after their Root moves. ([#879](https://github.com/openclaw/fs-safe/pull/879))
12
+ - **Guest cross-device moves:** preserve the admitted file permissions. ([#869](https://github.com/openclaw/fs-safe/pull/869))
13
+
14
+ ### Features
15
+
16
+ - **Guarded copy batches:** `createRootFileCopyBatchSync` on `advanced` reuses directory admissions across synchronous copies on canonical POSIX paths while rechecking ancestor and file identities for every copy; single-file copies also reuse admission within the operation. ([#849](https://github.com/openclaw/fs-safe/pull/849))
17
+
18
+ ### Fixes
19
+
20
+ - **Root-backed file locks:** native asynchronous release and exit cleanup follow the created lock's retained identity after its Root moves, preserving replacement files and reporting ownership mismatches. ([#879](https://github.com/openclaw/fs-safe/pull/879)) Thanks @axiom-ncis.
21
+ - **Temp directory diagnostics:** name rejected roots and ancestors, explain ownership, mode, or symlink failures, and provide safe recovery guidance, including for root-run appliances. Thanks @joequant. ([#877](https://github.com/openclaw/fs-safe/pull/877))
22
+ - **Guest directory publication:** publish staged workspaces when Linux filesystems reject `RENAME_NOREPLACE`, with checked file link/unlink fallback and directory rename that can only replace a concurrently created empty directory. ([#876](https://github.com/openclaw/fs-safe/pull/876)) Thanks @valkyriweb.
23
+ - **Guest cross-device moves:** preserve the admitted source file's permissions instead of leaving the destination with a broader default mode. ([#869](https://github.com/openclaw/fs-safe/pull/869))
24
+ - **Move receipts:** publication receipts capture the source identity after synchronous authorization callbacks return, so a source replaced during authorization is reported accurately. ([#868](https://github.com/openclaw/fs-safe/pull/868))
25
+ - **Atomic copy fallback:** report a failed restoration as a failure even when the underlying error carries a restored receipt. ([#851](https://github.com/openclaw/fs-safe/pull/851))
26
+ - **Archive read limits:** keep the validated entry-read byte limit fixed when callers mutate the options during a read. ([#852](https://github.com/openclaw/fs-safe/pull/852))
27
+ - **Watch on macOS:** reconcile notifications for changes to the watched Root itself immediately instead of waiting for the periodic scan. ([#858](https://github.com/openclaw/fs-safe/pull/858))
28
+ - **`pathScope` tilde names:** keep literal `~` directory names inside scoped file and existing-path lookups instead of expanding them. ([#856](https://github.com/openclaw/fs-safe/pull/856))
29
+
30
+ ### Performance
31
+
32
+ - **Guarded file copies:** reuse checked canonical POSIX roots during initial path admission, reducing repeated path-resolution work in synchronous plugin source captures without changing per-file identity or race checks.
33
+ - **Root walks:** reuse normalized directory prefixes, cutting a 50k-file walk by 5.6% and its sampled allocation by about 9% while keeping every filesystem and identity check. ([#870](https://github.com/openclaw/fs-safe/pull/870))
34
+ - **Atomic writes:** non-durable async POSIX writes skip a redundant mode change when the staged file already has the requested permissions. ([#871](https://github.com/openclaw/fs-safe/pull/871))
35
+ - **Guarded mutations:** fewer promise allocations around synchronous guard checks, with directory identity checks unchanged. ([#857](https://github.com/openclaw/fs-safe/pull/857))
36
+ - **Archive publication:** fewer promise allocations in archive guard checks, without removing identity or containment checks. ([#864](https://github.com/openclaw/fs-safe/pull/864))
37
+ - **Watch:** Linux and Windows watch passes skip macOS stream-path preparation. ([#860](https://github.com/openclaw/fs-safe/pull/860))
38
+ - **Permission diagnostics:** bound escaping work for large error messages without changing the displayed excerpts. ([#859](https://github.com/openclaw/fs-safe/pull/859))
39
+
5
40
  ## 0.24.2 - 2026-10-07
6
41
 
7
42
  ### Fixes
@@ -22,7 +22,7 @@ await fs.write("../escape.txt", "x"); // throws FsSafeError("outside-
22
22
 
23
23
  That's the whole pitch. `root()` is the product; the rest of the package — JSON stores, atomic writes, secret files, archive extraction, temp workspaces — is supporting cast for the same boundary.
24
24
 
25
- Full docs and reference at **[fs-safe.io](https://fs-safe.io)**.
25
+ Full docs and reference at **[fs-safe.io](https://fs-safe.io)**. For synchronous plugin captures, [guarded file copies and batches](docs/copy.md#synchronous-guarded-file-copies) return owned destination descriptors while preserving per-file checks.
26
26
 
27
27
  ## Contents
28
28
 
@@ -1,4 +1,4 @@
1
- export { copyRootFileSync, type CopyRootFileSyncOptions, type CopiedRootFileSync } from "./copy-root-file-sync.js";
1
+ export { copyRootFileSync, createRootFileCopyBatchSync, type RootFileCopyBatchSync, type CopyRootFileSyncOptions, type CopiedRootFileSync } from "./copy-root-file-sync.js";
2
2
  export { createAsyncLock } from "./async-lock.js";
3
3
  export { createDirectory, createDirectorySync, createFileSync, type CreateDirectoryOptions, type CreateFileOptions, type OwnedFileDescriptorSync, } from "./create.js";
4
4
  export { copyFileHandle, copyFileDescriptorSync, type CopyFileHandleOptions } from "./file-handle-transfer.js";
@@ -1,4 +1,4 @@
1
- export { copyRootFileSync } from "./copy-root-file-sync.js";
1
+ export { copyRootFileSync, createRootFileCopyBatchSync } from "./copy-root-file-sync.js";
2
2
  // Advanced composition surface. These exports are less stable than the focused
3
3
  // public subpaths; prefer root/json/store/temp/archive unless you are building a
4
4
  // higher-level primitive.
@@ -65,7 +65,7 @@ export async function finalizeArchivePublication(params) {
65
65
  const check = () => params.deadline?.check();
66
66
  const assertGuards = async (guards) => {
67
67
  for (const guard of [params.destinationGuard, ...guards, params.sourceGuard]) {
68
- await assertDirectoryIdentityGuard(guard);
68
+ assertDirectoryIdentityGuard(guard);
69
69
  check();
70
70
  }
71
71
  };
@@ -98,15 +98,15 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
98
98
  assertSourceDirectory(ancestor);
99
99
  };
100
100
  const assertGuards = async () => {
101
- await assertDirectoryIdentityGuard(destinationGuard);
101
+ assertDirectoryIdentityGuard(destinationGuard);
102
102
  check();
103
103
  for (const ancestor of ancestors) {
104
- await assertDirectoryIdentityGuard(ancestor.guard);
104
+ assertDirectoryIdentityGuard(ancestor.guard);
105
105
  check();
106
106
  await ancestor.owner.verify(check);
107
107
  check();
108
108
  }
109
- await assertDirectoryIdentityGuard(sourceGuard);
109
+ assertDirectoryIdentityGuard(sourceGuard);
110
110
  check();
111
111
  };
112
112
  const walk = async (sourceDir) => {
@@ -184,7 +184,7 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
184
184
  await owner.apply(mode, { check, beforeChmod: async () => {
185
185
  await assertGuards();
186
186
  assertSourceAncestors();
187
- await assertDirectoryIdentityGuard(guard);
187
+ assertDirectoryIdentityGuard(guard);
188
188
  check();
189
189
  await assertResolvedInsideDestination({
190
190
  destinationRealDir, targetPath: destinationPath, originalPath,
@@ -213,7 +213,8 @@ async function readNativeBufferEntry(native, buffer, kind, requested, displayPat
213
213
  }
214
214
  }
215
215
  export async function readArchiveEntry(archivePath, entryPath, options) {
216
- if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
216
+ const maxBytes = options.maxBytes;
217
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
217
218
  throw new RangeError("maxBytes must be a non-negative safe integer");
218
219
  }
219
220
  const kind = options.kind ?? resolveArchiveKind(archivePath);
@@ -228,7 +229,7 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
228
229
  admitZipBuffer(buffer, resolveExtractLimits(), entry => { zipEntries.push(entry); });
229
230
  const native = getNativeBinding();
230
231
  if (native)
231
- return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, options.maxBytes, zipEntries);
232
- return kind === "zip" ? await readZipEntry(buffer, requestedEntry, options.maxBytes, zipEntries)
233
- : await readTarEntry(buffer, requestedEntry, options.maxBytes, kind);
232
+ return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, maxBytes, zipEntries);
233
+ return kind === "zip" ? await readZipEntry(buffer, requestedEntry, maxBytes, zipEntries)
234
+ : await readTarEntry(buffer, requestedEntry, maxBytes, kind);
234
235
  }
@@ -5,7 +5,7 @@ import { ArchiveSecurityError } from "./archive-errors.js";
5
5
  export type ArchiveDirectoryGuard = AsyncDirectoryGuard<BigIntStats>;
6
6
  export declare function createArchiveSymlinkTraversalError(originalPath: string): ArchiveSecurityError;
7
7
  export declare function createDirectoryIdentityGuard(dir: string): Promise<ArchiveDirectoryGuard>;
8
- export declare function assertDirectoryIdentityGuard(guard: ArchiveDirectoryGuard): Promise<void>;
8
+ export declare function assertDirectoryIdentityGuard(guard: ArchiveDirectoryGuard): void;
9
9
  export declare function prepareArchiveDestinationGuard(destDir: string): Promise<ArchiveDirectoryGuard>;
10
10
  export declare function prepareArchiveDestinationDir(destDir: string): Promise<string>;
11
11
  export declare function assertResolvedInsideDestination(params: {
@@ -1,7 +1,7 @@
1
1
  import fsSync, {} from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
4
+ import { assertDirectoryGuard, createAsyncDirectoryGuard, } from "./directory-guard.js";
5
5
  import { ownExtractionDestinationMutation, } from "./archive-deadline.js";
6
6
  import { ArchiveSecurityError, } from "./archive-errors.js";
7
7
  import { FsSafeError } from "./errors.js";
@@ -38,9 +38,9 @@ export async function createDirectoryIdentityGuard(dir) {
38
38
  throw err;
39
39
  }
40
40
  }
41
- export async function assertDirectoryIdentityGuard(guard) {
41
+ export function assertDirectoryIdentityGuard(guard) {
42
42
  try {
43
- await assertAsyncDirectoryGuard(guard);
43
+ assertDirectoryGuard(guard, "native");
44
44
  }
45
45
  catch (err) {
46
46
  if (err instanceof FsSafeError || isNotFoundPathError(err)) {
@@ -200,7 +200,7 @@ async function prepareOutputPath(params, assertGuards, privateWorkingMode = fals
200
200
  if (existingDestinationGuard && assertGuards)
201
201
  await assertGuards();
202
202
  else {
203
- await assertDirectoryIdentityGuard(destinationGuard);
203
+ assertDirectoryIdentityGuard(destinationGuard);
204
204
  checkExtractionDeadline(params.deadline);
205
205
  await assertGuards?.();
206
206
  }
@@ -213,46 +213,30 @@ async function prepareOutputPath(params, assertGuards, privateWorkingMode = fals
213
213
  originalPath: params.originalPath,
214
214
  });
215
215
  checkExtractionDeadline(params.deadline);
216
- if (params.isDirectory) {
217
- await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", params.outPath);
216
+ const relativePath = params.isDirectory ? relPath : path.posix.dirname(relPath);
217
+ if (params.isDirectory || relativePath !== ".") {
218
+ const directoryPath = params.isDirectory ? params.outPath : path.dirname(params.outPath);
219
+ await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", directoryPath);
218
220
  checkExtractionDeadline(params.deadline);
219
221
  await ownExtractionDestinationMutation(params.deadline, async () => {
220
222
  await assertOutputGuards();
221
223
  checkExtractionDeadline(params.deadline);
222
- await mkdirArchiveOutput({
223
- targetRoot,
224
- relativePath: relPath,
225
- originalPath: params.originalPath,
226
- });
227
- checkExtractionDeadline(params.deadline);
228
- await assertDirectoryIdentityGuard(destinationGuard);
224
+ await mkdirArchiveOutput({ targetRoot, relativePath, originalPath: params.originalPath });
229
225
  checkExtractionDeadline(params.deadline);
230
- await assertResolvedInsideDestination({
231
- destinationRealDir: params.destinationRealDir,
232
- targetPath: params.outPath,
233
- originalPath: params.originalPath,
234
- });
235
- checkExtractionDeadline(params.deadline);
236
- });
237
- return;
238
- }
239
- const parentRel = path.posix.dirname(relPath);
240
- if (parentRel !== ".") {
241
- await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("mkdir", path.dirname(params.outPath));
242
- checkExtractionDeadline(params.deadline);
243
- await ownExtractionDestinationMutation(params.deadline, async () => {
244
- await assertOutputGuards();
245
- checkExtractionDeadline(params.deadline);
246
- await mkdirArchiveOutput({
247
- targetRoot,
248
- relativePath: parentRel,
249
- originalPath: params.originalPath,
250
- });
251
- checkExtractionDeadline(params.deadline);
252
- await assertDirectoryIdentityGuard(destinationGuard);
226
+ assertDirectoryIdentityGuard(destinationGuard);
253
227
  checkExtractionDeadline(params.deadline);
228
+ if (params.isDirectory) {
229
+ await assertResolvedInsideDestination({
230
+ destinationRealDir: params.destinationRealDir,
231
+ targetPath: params.outPath,
232
+ originalPath: params.originalPath,
233
+ });
234
+ checkExtractionDeadline(params.deadline);
235
+ }
254
236
  });
255
237
  }
238
+ if (params.isDirectory)
239
+ return;
256
240
  await assertResolvedInsideDestination({
257
241
  destinationRealDir: params.destinationRealDir,
258
242
  targetPath: path.dirname(params.outPath),
@@ -292,12 +276,12 @@ export async function withStagedArchiveDestination(params) {
292
276
  const stagingGuard = await createDirectoryIdentityGuard(stagingDir);
293
277
  try {
294
278
  await fs.chmod(stagingDir, ARCHIVE_STAGING_MODE).catch(() => undefined);
295
- await assertDirectoryIdentityGuard(stagingGuard);
279
+ assertDirectoryIdentityGuard(stagingGuard);
296
280
  return await params.run(stagingDir);
297
281
  }
298
282
  finally {
299
283
  try {
300
- await assertDirectoryIdentityGuard(stagingGuard);
284
+ assertDirectoryIdentityGuard(stagingGuard);
301
285
  await fs.rm(stagingDir, { recursive: true, force: true }).catch(() => undefined);
302
286
  }
303
287
  catch {