sliccy 6.155.0 → 6.156.1

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 (176) hide show
  1. package/README.md +8 -1
  2. package/dist/node-server/hostfs.d.ts +6 -1
  3. package/dist/node-server/hostfs.js +32 -0
  4. package/dist/ui/.vite/manifest.json +201 -201
  5. package/dist/ui/assets/CLAUDE-D8aRy6Y3.js +1 -0
  6. package/dist/ui/assets/{SKILL-C947OHCv.js → SKILL-CkA461kX.js} +1 -1
  7. package/dist/ui/assets/{account-store-FUhuBOfI.js → account-store-BlFA99H0.js} +1 -1
  8. package/dist/ui/assets/{account-store-BL68LrFT.js → account-store-DlBJje5p.js} +1 -1
  9. package/dist/ui/assets/{adobe-BaHLL9uH.js → adobe-Bb0HwmGi.js} +1 -1
  10. package/dist/ui/assets/{adobe-DNHcSDh3.js → adobe-Cf_urjYb.js} +1 -1
  11. package/dist/ui/assets/{agent-bridge-Vz3IOG0A.js → agent-bridge-DoFAHPGJ.js} +1 -1
  12. package/dist/ui/assets/{agent-panels-CPSe8uIz.js → agent-panels-CvyqP3Ii.js} +1 -1
  13. package/dist/ui/assets/{apply-layout-G6vw7FBS.js → apply-layout-lpl82E_y.js} +1 -1
  14. package/dist/ui/assets/{apps-CLm3AutR.js → apps-Cx1ihs6S.js} +1 -1
  15. package/dist/ui/assets/{auto-mount-table-BDyGIOIb.js → auto-mount-table-Bj59hmjV.js} +1 -1
  16. package/dist/ui/assets/{azure-openai--n3vcQWx.js → azure-openai-Bvik-NC0.js} +1 -1
  17. package/dist/ui/assets/{azure-openai-N9FaZSuP.js → azure-openai-Cc3KvQoJ.js} +1 -1
  18. package/dist/ui/assets/backend-hostfs-De2Zrx7a.js +1 -0
  19. package/dist/ui/assets/backend-hostfs-n_0O1LVt.js +1 -0
  20. package/dist/ui/assets/{browser-BxGToOGy.js → browser-CyOAI9cj.js} +3 -3
  21. package/dist/ui/assets/{budget-usage-source-C6vq4Afr.js → budget-usage-source-DJCk4Eol.js} +1 -1
  22. package/dist/ui/assets/{bunny-probe-Dk0BdUab.js → bunny-probe-BVjp9oI9.js} +1 -1
  23. package/dist/ui/assets/{bunny-run-DtVSvgJ8.js → bunny-run-E1D0_zTi.js} +1 -1
  24. package/dist/ui/assets/{cdp-C7C40sWI.js → cdp-B3b4fMbQ.js} +1 -1
  25. package/dist/ui/assets/{cerebras-B4z1b7WX.js → cerebras-CY_ZE7G8.js} +1 -1
  26. package/dist/ui/assets/{cerebras-BecGzlaM.js → cerebras-Dv9W7f69.js} +1 -1
  27. package/dist/ui/assets/{composer-speech-uDoOk80y.js → composer-speech-lUexnB3v.js} +1 -1
  28. package/dist/ui/assets/{connect-surface-CkYbO0v8.js → connect-surface-CYgHpfHt.js} +1 -1
  29. package/dist/ui/assets/{cost-command-BzdZ_evm.js → cost-command-XzcQb5EI.js} +1 -1
  30. package/dist/ui/assets/{directed-approval-DdGH8Fat.js → directed-approval-HCtoBBOI.js} +1 -1
  31. package/dist/ui/assets/{dist-CtH2hxRt.js → dist-0AIPb7H_.js} +9 -9
  32. package/dist/ui/assets/{dist-VS1DwNvU.js → dist-xOOa94QU.js} +10 -10
  33. package/dist/ui/assets/{ensure-speech-assets-DOu9EHnU.js → ensure-speech-assets-BfysPtlZ.js} +1 -1
  34. package/dist/ui/assets/{extension-ops-B05mAXsq.js → extension-ops-DcLzcAwt.js} +1 -1
  35. package/dist/ui/assets/{fast-path-CfBU0M0X.js → fast-path-CbwpSWFJ.js} +1 -1
  36. package/dist/ui/assets/{frozen-archive-writer--a4fMghf.js → frozen-archive-writer-Df5805od.js} +1 -1
  37. package/dist/ui/assets/fs-B5sS6_5I.js +2 -0
  38. package/dist/ui/assets/fs-C88g915Q.js +1 -0
  39. package/dist/ui/assets/{github-FRM6nHar.js → github-Bujm8ahG.js} +1 -1
  40. package/dist/ui/assets/github-HJvu_gac.js +2 -0
  41. package/dist/ui/assets/{github-auth-DgdBb7y7.js → github-auth-C641hcY1.js} +1 -1
  42. package/dist/ui/assets/{github-copilot-D6XJxicV.js → github-copilot-DbNXg7Dn.js} +1 -1
  43. package/dist/ui/assets/{github-copilot-CNY2SweM.js → github-copilot-ca4PJied.js} +1 -1
  44. package/dist/ui/assets/{github-install-PNSinMst.js → github-install-QZbYCNzS.js} +1 -1
  45. package/dist/ui/assets/{github-zip-BCXSTlT0.js → github-zip-DQai7euS.js} +1 -1
  46. package/dist/ui/assets/{hear-DAaE31Ez.js → hear-Co1rqszy.js} +1 -1
  47. package/dist/ui/assets/{help-HkZtBCNF.js → help-HaCQg57w.js} +1 -1
  48. package/dist/ui/assets/installer-3ZT0rXO0.js +4 -0
  49. package/dist/ui/assets/isomorphic-git-DRUhmZ5u.js +84 -0
  50. package/dist/ui/assets/{js-realm-worker-Bp1ohy2N.js → js-realm-worker-5lZp3zuB.js} +1 -1
  51. package/dist/ui/assets/kernel-worker-CdtrnwDB.js +4948 -0
  52. package/dist/ui/assets/{live-session-snapshot-CpwRZg6K.js → live-session-snapshot-BP6rxBrE.js} +1 -1
  53. package/dist/ui/assets/{local-llm-BL_WYiZZ.js → local-llm-CP69uiQv.js} +1 -1
  54. package/dist/ui/assets/{local-llm-BKrrjR68.js → local-llm-i_pLphdz.js} +1 -1
  55. package/dist/ui/assets/{main-DW-YS69h.js → main-B6KvjwxA.js} +3 -3
  56. package/dist/ui/assets/{main-cherry-B0AQVdvI.js → main-cherry-Bq-MY7_2.js} +1 -1
  57. package/dist/ui/assets/{memory-curation-seam-CD-ZRKzr.js → memory-curation-seam-DaXa0jXx.js} +1 -1
  58. package/dist/ui/assets/{mount-DlOEB7Cf.js → mount-BzmsDs-Q.js} +2 -2
  59. package/dist/ui/assets/{mount-igEB5Saa.js → mount-YnCWqYIR.js} +3 -3
  60. package/dist/ui/assets/{mount-bridge-client-DQaM1wCj.js → mount-bridge-client-DphjpUJQ.js} +1 -1
  61. package/dist/ui/assets/{mount-bridge-client-DWY67yip.js → mount-bridge-client-wfM0sGs5.js} +1 -1
  62. package/dist/ui/assets/{mount-index-D9w_zMSm.js → mount-index-DLLlduW0.js} +1 -1
  63. package/dist/ui/assets/{mount-recovery-WObwrrJB.js → mount-recovery-WIBwiZyX.js} +1 -1
  64. package/dist/ui/assets/mv-DUh51uEl.js +2 -0
  65. package/dist/ui/assets/{new-session-BlbJzLQE.js → new-session-CL_CInjb.js} +2 -2
  66. package/dist/ui/assets/{oauth-bootstrap-DHGSi_vM.js → oauth-bootstrap-p-eVw2wO.js} +2 -2
  67. package/dist/ui/assets/{onboarding-orchestrator-ILCQMWeH.js → onboarding-orchestrator-Qp_wc9Wi.js} +1 -1
  68. package/dist/ui/assets/{openai-codex-ByOwob9t.js → openai-codex-BpnvhpGf.js} +1 -1
  69. package/dist/ui/assets/{openai-codex-zHgcCFMl.js → openai-codex-SDOR649L.js} +1 -1
  70. package/dist/ui/assets/{openrouter-CvwSbwg3.js → openrouter-BSWGMR9L.js} +1 -1
  71. package/dist/ui/assets/{openrouter-ooroqv_7.js → openrouter-Csr12jOI.js} +1 -1
  72. package/dist/ui/assets/{openrouter-free-DAXjSSCv.js → openrouter-free-DRMsAIo_.js} +1 -1
  73. package/dist/ui/assets/{openrouter-free-Bw5mRfKy.js → openrouter-free-lmqj7iWQ.js} +1 -1
  74. package/dist/ui/assets/{openrouter-oauth-CsQP_eqX.js → openrouter-oauth-C4wVAVP0.js} +1 -1
  75. package/dist/ui/assets/{openrouter-oauth-DKr2TOOt.js → openrouter-oauth-DHX_fpIm.js} +1 -1
  76. package/dist/ui/assets/opfs-mount-B4BizFOR.js +2 -0
  77. package/dist/ui/assets/opfs-mount-BdgSw31f.js +2 -0
  78. package/dist/ui/assets/{panel-rpc-handlers-g22Ng2gw.js → panel-rpc-handlers-B-q4ku-f.js} +2 -2
  79. package/dist/ui/assets/{panelize-shell-25ZtCwHW.js → panelize-shell-DY2g5UXp.js} +1 -1
  80. package/dist/ui/assets/{pi-edit-execution-jG5wiCuj.js → pi-edit-execution-D9yRA2Fe.js} +1 -1
  81. package/dist/ui/assets/{probe-info-B9GqJE41.js → probe-info-CjsoArwT.js} +1 -1
  82. package/dist/ui/assets/provider-BwgylKFb.js +2 -0
  83. package/dist/ui/assets/{provider-iX6700HG.js → provider-Uec7ton3.js} +1 -1
  84. package/dist/ui/assets/provider-settings-D_IE6rMQ.js +1 -0
  85. package/dist/ui/assets/provider-store-access-DEDLOyhJ.js +1 -0
  86. package/dist/ui/assets/provider-store-access-DfqSh8G6.js +1 -0
  87. package/dist/ui/assets/{providers-DjUwvFlY.js → providers-CKwuqmbn.js} +2 -2
  88. package/dist/ui/assets/{providers-DAeL97z_.js → providers-CSPNJM6o.js} +1 -1
  89. package/dist/ui/assets/{quick-llm-CM0bq0Rp.js → quick-llm-Dl0Eh3p1.js} +1 -1
  90. package/dist/ui/assets/{recovery-screen-C2-cTzHe.js → recovery-screen-BIO2d0_k.js} +1 -1
  91. package/dist/ui/assets/{remote-sprinkle-vfs-BY9A-xsx.js → remote-sprinkle-vfs-C_-TfGu3.js} +1 -1
  92. package/dist/ui/assets/{remote-vfs-client-Dt3CesOI.js → remote-vfs-client-CEL8mUQ9.js} +1 -1
  93. package/dist/ui/assets/rename-via-fs-ckVY1Ljv.js +1 -0
  94. package/dist/ui/assets/{run-Cv106rZx.js → run-B7bNDge8.js} +2 -2
  95. package/dist/ui/assets/{run-CgQOlMuy.js → run-BAXE1Fgn.js} +2 -2
  96. package/dist/ui/assets/run-BbzzVjlq.js +13 -0
  97. package/dist/ui/assets/{run-BZq150_-.js → run-CFSW3zNE.js} +2 -2
  98. package/dist/ui/assets/{run-XqJwbPb4.js → run-CWenWZRy.js} +2 -2
  99. package/dist/ui/assets/{run-BmBPc42l.js → run-DZd7hQTI.js} +1 -1
  100. package/dist/ui/assets/{run-2dG-kPkW.js → run-enknJJEk.js} +1 -1
  101. package/dist/ui/assets/{run-wZKGk8f1.js → run-r5Uh0A1S.js} +1 -1
  102. package/dist/ui/assets/same-file-identity-C_JKwIh_.js +1 -0
  103. package/dist/ui/assets/{session-command-memory-CkF1Wu0O.js → session-command-memory-CzJDf01Z.js} +2 -2
  104. package/dist/ui/assets/{session-jsonl-eMNv3Qki.js → session-jsonl-BwqaD3jQ.js} +1 -1
  105. package/dist/ui/assets/{session-jsonl-BUM105sQ.js → session-jsonl-CjA27CdZ.js} +1 -1
  106. package/dist/ui/assets/{session-search-index-D2VtAM_Q.js → session-search-index-D6zcG95Z.js} +1 -1
  107. package/dist/ui/assets/{setup-feature-flags-remote-CYaTMUGI.js → setup-feature-flags-remote-DlXGCipy.js} +1 -1
  108. package/dist/ui/assets/shebang-exec-hint-BISPom3l.js +2 -0
  109. package/dist/ui/assets/sidecar-repair-CWpX6fJD.js +1 -0
  110. package/dist/ui/assets/{silent-renew-backoff-CMcARSwp.js → silent-renew-backoff-CTA85brT.js} +1 -1
  111. package/dist/ui/assets/{skills-BpO1a28c.js → skills-DMAKVN61.js} +1 -1
  112. package/dist/ui/assets/slicc-version-C_H-MY6l.js +1 -0
  113. package/dist/ui/assets/{snapshot-features-j6dx6n8x.js → snapshot-features-82ReCjaH.js} +1 -1
  114. package/dist/ui/assets/{snapshot-CUbnB6HL.js → snapshot-zhet1-8H.js} +1 -1
  115. package/dist/ui/assets/{soundscape-BswqsuUI.js → soundscape-lSRZkZE6.js} +1 -1
  116. package/dist/ui/assets/{speak-BjUpeuE_.js → speak-Cm1HeYCz.js} +1 -1
  117. package/dist/ui/assets/{sprinkle-manager--VjyaGhT.js → sprinkle-manager-CPuK8KeD.js} +1 -1
  118. package/dist/ui/assets/{sprinkle-renderer-DST9wDsJ.js → sprinkle-renderer-CF7WMbPy.js} +1 -1
  119. package/dist/ui/assets/src-D-Mukt_a.js +575 -0
  120. package/dist/ui/assets/{state-D4U_z-EK.js → state-C9R7OoLj.js} +5 -5
  121. package/dist/ui/assets/{store-B8MYU0Oj.js → store-Dpd801Yg.js} +1 -1
  122. package/dist/ui/assets/{store-DChdcXsg.js → store-wkip2iVX.js} +1 -1
  123. package/dist/ui/assets/{sudo-Zjjvq0o1.js → sudo-CtswsF2u.js} +1 -1
  124. package/dist/ui/assets/{sync-dialog-CQpxesIG.js → sync-dialog-BSpy8EFx.js} +1 -1
  125. package/dist/ui/assets/{transformers-env-Bfs2yK-G.js → transformers-env-kaOi2ojg.js} +1 -1
  126. package/dist/ui/assets/{tray-sidecar-DoJ0KYqF.js → tray-sidecar-DsOY4QHT.js} +1 -1
  127. package/dist/ui/assets/types-Bgy4zvr3.js +1 -0
  128. package/dist/ui/assets/{upgrade-detection-BJO1N35I.js → upgrade-detection-vrOmiqqS.js} +1 -1
  129. package/dist/ui/assets/{upskill-lick-skip-ETmK7gKp.js → upskill-lick-skip-xfV4fwlQ.js} +1 -1
  130. package/dist/ui/assets/{voice-reply-CxLeD2UU.js → voice-reply-CH76grIE.js} +1 -1
  131. package/dist/ui/assets/{wc-attach-BqTZtL4F.js → wc-attach-3nXTzstV.js} +2 -2
  132. package/dist/ui/assets/{wc-extension-C_P8Z5_f.js → wc-extension-Bt_trMqK.js} +2 -2
  133. package/dist/ui/assets/{wc-follower-w1PZlnC3.js → wc-follower-DuNcMcEQ.js} +3 -3
  134. package/dist/ui/assets/{wc-follower-oauth-CqVmKcTQ.js → wc-follower-oauth-B7gPBiE9.js} +1 -1
  135. package/dist/ui/assets/{wc-gelatiere-ErLykEqr.js → wc-gelatiere-DNYiiwtq.js} +1 -1
  136. package/dist/ui/assets/{wc-live-Kx0jpvxz.js → wc-live-r5pjjgYB.js} +5 -5
  137. package/dist/ui/assets/{wc-nav-DMxSQWF8.js → wc-nav-CE55zqTz.js} +2 -2
  138. package/dist/ui/assets/{wc-onboarding-W0FpgTat.js → wc-onboarding-DwuXDHj6.js} +2 -2
  139. package/dist/ui/assets/{wc-placeholder-CI7UKO4Z.js → wc-placeholder-Bk0Qt_En.js} +3 -3
  140. package/dist/ui/assets/{wc-secret-request-DaAszAFd.js → wc-secret-request-CVKjjntX.js} +1 -1
  141. package/dist/ui/assets/{wc-settings-B-FsPgww.js → wc-settings-qSUcx77z.js} +3 -3
  142. package/dist/ui/assets/wc-shell-CAp8GPMx.js +125 -0
  143. package/dist/ui/assets/{wc-shortcut-config-B5nHTfK6.js → wc-shortcut-config-Cy4oz3Dh.js} +1 -1
  144. package/dist/ui/assets/{wc-shortcuts-Cbk5hDbX.js → wc-shortcuts-BibvziEB.js} +1 -1
  145. package/dist/ui/assets/{wc-sprinkles-DnBxgn1Q.js → wc-sprinkles-CZtWuFtI.js} +2 -2
  146. package/dist/ui/assets/{wc-transcript-export-7tvwdvKC.js → wc-transcript-export-DOkXWZlo.js} +2 -2
  147. package/dist/ui/assets/{wc-tray-DwUCXdrJ.js → wc-tray-CbFgvHkE.js} +4 -4
  148. package/dist/ui/assets/welcome-detection-DU4qkHlr.js +1 -0
  149. package/dist/ui/assets/{whisper-session-DoxUb6hK.js → whisper-session-Bgp21V01.js} +2 -2
  150. package/dist/ui/assets/{writable-vfs-client-C4pTXDFX.js → writable-vfs-client-CuLQlJK0.js} +1 -1
  151. package/dist/ui/assets/{xai-grok-CIVOagKi.js → xai-grok-BVfIqjo2.js} +1 -1
  152. package/dist/ui/assets/{xai-grok-BHDMfFJ3.js → xai-grok-CPv2tY1e.js} +1 -1
  153. package/dist/ui/index.html +3 -3
  154. package/dist/ui/packages/webapp/index.html +3 -3
  155. package/package.json +2 -1
  156. package/dist/ui/assets/CLAUDE-C4hj5z8T.js +0 -34
  157. package/dist/ui/assets/backend-hostfs-BH8uwRZG.js +0 -1
  158. package/dist/ui/assets/backend-hostfs-BJU2QL9q.js +0 -1
  159. package/dist/ui/assets/dist-BCg-GYyx.js +0 -2
  160. package/dist/ui/assets/dist-ByarBBEi.js +0 -2
  161. package/dist/ui/assets/fs-BOdMXcsi.js +0 -2
  162. package/dist/ui/assets/fs-CL93DUyC.js +0 -1
  163. package/dist/ui/assets/github-CQwdOEdJ.js +0 -2
  164. package/dist/ui/assets/installer-D4WxHb-C.js +0 -4
  165. package/dist/ui/assets/kernel-worker-dGdJLKhc.js +0 -5022
  166. package/dist/ui/assets/provider-B0ZPEVNo.js +0 -2
  167. package/dist/ui/assets/provider-settings-jhJRRTGA.js +0 -1
  168. package/dist/ui/assets/provider-store-access-BtES6Ln2.js +0 -1
  169. package/dist/ui/assets/provider-store-access-z34_fP_u.js +0 -1
  170. package/dist/ui/assets/sidecar-repair-CIHuOk7H.js +0 -1
  171. package/dist/ui/assets/sidecar-repair-Dyo_uNW9.js +0 -1
  172. package/dist/ui/assets/slicc-version-pkH5Cy8d.js +0 -1
  173. package/dist/ui/assets/src-DAuBIYcJ.js +0 -575
  174. package/dist/ui/assets/types-Dgm4REV3.js +0 -1
  175. package/dist/ui/assets/wc-shell-Bqj2G2_K.js +0 -125
  176. package/dist/ui/assets/welcome-detection-BePZ1gj8.js +0 -1
package/README.md CHANGED
@@ -42,10 +42,14 @@ Once you're in, you can:
42
42
 
43
43
  > Status: active working prototype. The macOS app is the easiest way in today; the extension has been submitted to the Chrome Web Store.
44
44
 
45
+ Scoop shells bound supported filesystem traversals and command buffers. See the
46
+ [shell work limits](docs/shell-reference.md#scoop-filesystem-work-limits) for limits
47
+ and command coverage.
48
+
45
49
  ## Why SLICC is different
46
50
 
47
51
  - **Browser-native, not browser-adjacent.** The agent runtime lives in the browser, and the agent can act on the same browser it lives in. A great mix of power and containment. If you don't like what the AI does, close the browser tab and it's over.
48
- - **A real shell environment.** Many browser agents are constrained by the tools provided to them. SLICC has an almost-too-real shell with commands like `git`, "`node`", `python`, `playwright`, built-in.
52
+ - **A real shell environment.** Many browser agents are constrained by the tools provided to them. SLICC has an almost-too-real shell with commands like `git`, "`node`", `python`, `playwright`, built-in. Text files use UTF-8 by default; binary pipelines and explicit file encodings preserve their bytes.
49
53
  - **UI on the fly.** SLICC can generate rich user interfaces on the fly. These can be small visualizations in a chat response, or full-blown web applications that run in a sidebar, or even a separate tab.
50
54
  - **Built around Skills.** Agents don't suffer from missing capabilities, they suffer from skill issues. SLICC can install native skills into `/workspace/skills`, and it also discovers compatible `.agents` / `.claude` skills read-only across the reachable VFS.
51
55
  - **More than a coding panel.** Coding is one strong use case, but SLICC is built for practical browser work too: authenticated web apps, repetitive tab work, content operations, debugging, research, and automation.
@@ -280,6 +284,9 @@ SLICC would not have been possible without the pioneering inspiration of [OpenCl
280
284
 
281
285
  ## Development and deeper docs
282
286
 
287
+ Developers can verify native browser storage with `npm run test:opfs` and trial the
288
+ [optional OPFS preload setting](docs/filesystem-preload.md).
289
+
283
290
  If you want to go deeper, the detailed docs live here:
284
291
 
285
292
  - [Development guide](docs/development.md)
@@ -57,7 +57,7 @@
57
57
  * HTML and the webapp cannot rethrow a faithful `FsError`.
58
58
  */
59
59
  import type { ErrorRequestHandler, Express, Response } from 'express';
60
- import { createReadStream } from 'fs';
60
+ import { createReadStream, type Stats } from 'fs';
61
61
  import type { HostMountMapping } from './runtime-flags.js';
62
62
  /**
63
63
  * Cap on a WHOLE-FILE `GET /api/hostfs/read` (and on `PUT /write`). It exists
@@ -174,6 +174,11 @@ export declare function isHostFsStableBodyRequest(req: {
174
174
  * passed through untouched.
175
175
  */
176
176
  export declare const hostFsBodyErrorHandler: ErrorRequestHandler;
177
+ /**
178
+ * True when `a` and `b` are the same file (device + inode), not merely
179
+ * similar strings. Path comparison misses the APFS case / NFC-NFD collision.
180
+ */
181
+ export declare function sameHostFileIdentity(a: Stats, b: Stats): boolean;
177
182
  /**
178
183
  * Register the /api/hostfs routes for the resolved mount roots. The route
179
184
  * surface mirrors `MountBackend` 1:1 (webapp `backend-hostfs.ts`):
@@ -307,6 +307,7 @@ function statIdentity(s) {
307
307
  return {
308
308
  ctime: s.ctimeMs,
309
309
  ino: Number(s.ino),
310
+ dev: Number(s.dev),
310
311
  uid: s.uid,
311
312
  gid: s.gid,
312
313
  mode: s.mode,
@@ -515,7 +516,34 @@ async function mkdirOp(target) {
515
516
  await mkdir(target, { recursive: true });
516
517
  return { ok: true };
517
518
  }
519
+ /**
520
+ * True when `a` and `b` are the same file (device + inode), not merely
521
+ * similar strings. Path comparison misses the APFS case / NFC-NFD collision.
522
+ */
523
+ export function sameHostFileIdentity(a, b) {
524
+ return BigInt(a.dev) === BigInt(b.dev) && BigInt(a.ino) === BigInt(b.ino);
525
+ }
526
+ /**
527
+ * POSIX `rename(2)`: if both operands name the same directory entry (or
528
+ * hard links to the same inode), succeed and do nothing.
529
+ *
530
+ * Node's `fs.rename` on APFS is not that no-op — a case-only or NFD→NFC
531
+ * rename rewrites the catalog name. A copy-then-unlink rename is worse:
532
+ * `open(dest, O_TRUNC)` zeros the only inode, then the subsequent read of
533
+ * source copies 0 bytes, and `readdir` still shows the original name
534
+ * (#3107). Compare identity before calling `rename`.
535
+ */
518
536
  async function renameOp(from, to) {
537
+ try {
538
+ const fromStat = await lstat(from);
539
+ const toStat = await lstat(to);
540
+ if (sameHostFileIdentity(fromStat, toStat))
541
+ return { ok: true, noop: true };
542
+ }
543
+ catch (err) {
544
+ if (errnoCode(err) !== 'ENOENT')
545
+ throw err;
546
+ }
519
547
  await rename(from, to);
520
548
  return { ok: true };
521
549
  }
@@ -644,6 +672,10 @@ export function registerHostFsRoutes(app, roots) {
644
672
  }
645
673
  await mkdir(dirname(target), { recursive: true });
646
674
  const body = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
675
+ // The body is fully buffered before this open. `createWriteStream`
676
+ // would O_TRUNC dest on open, before any byte arrived — the
677
+ // copy-then-unlink truncation in #3107. writeFile also truncates,
678
+ // but only with the complete payload already in hand.
647
679
  // Write-then-rename would break hardlinks and xattrs on user files;
648
680
  // plain writeFile matches what local tools do.
649
681
  await writeFile(target, body);