jishushell 0.4.24 → 0.5.15

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 (281) hide show
  1. package/INSTALL-NOTICE +11 -0
  2. package/apps/anythingllm-container.yaml +287 -0
  3. package/apps/browserless-chromium-container.yaml +90 -0
  4. package/apps/filebrowser-container.yaml +163 -0
  5. package/apps/hermes-container.yaml +36 -2
  6. package/apps/ollama-binary.yaml +91 -90
  7. package/apps/ollama-cpu-container.yaml +8 -1
  8. package/apps/ollama-with-hollama-binary.yaml +91 -90
  9. package/apps/openclaw-binary.yaml +38 -1
  10. package/apps/openclaw-container.yaml +45 -2
  11. package/apps/openclaw-with-ollama-container.yaml +11 -2
  12. package/apps/openclaw-with-searxng-container.yaml +26 -2
  13. package/apps/openwebui-container.yaml +45 -1
  14. package/apps/playwright-container.yaml +7 -1
  15. package/apps/searxng-container.yaml +58 -7
  16. package/apps/weknora-container.yaml +471 -0
  17. package/dist/cli/app.js +79 -9
  18. package/dist/cli/app.js.map +1 -1
  19. package/dist/cli/doctor.d.ts +12 -12
  20. package/dist/cli/doctor.js +242 -55
  21. package/dist/cli/doctor.js.map +1 -1
  22. package/dist/cli/llm.d.ts +4 -3
  23. package/dist/cli/llm.js +4 -3
  24. package/dist/cli/llm.js.map +1 -1
  25. package/dist/cli/panel.d.ts +6 -5
  26. package/dist/cli/panel.js +10 -9
  27. package/dist/cli/panel.js.map +1 -1
  28. package/dist/config.d.ts +19 -0
  29. package/dist/config.js +99 -1
  30. package/dist/config.js.map +1 -1
  31. package/dist/control.d.ts +7 -6
  32. package/dist/control.js +7 -6
  33. package/dist/control.js.map +1 -1
  34. package/dist/install.js +3 -3
  35. package/dist/install.js.map +1 -1
  36. package/dist/routes/agent-apps.d.ts +1 -1
  37. package/dist/routes/agent-apps.js +1 -1
  38. package/dist/routes/apps.js +44 -11
  39. package/dist/routes/apps.js.map +1 -1
  40. package/dist/routes/auth.js +5 -2
  41. package/dist/routes/auth.js.map +1 -1
  42. package/dist/routes/backup.js +64 -11
  43. package/dist/routes/backup.js.map +1 -1
  44. package/dist/routes/external-mounts.d.ts +17 -0
  45. package/dist/routes/external-mounts.js +73 -0
  46. package/dist/routes/external-mounts.js.map +1 -0
  47. package/dist/routes/file-mounts.d.ts +13 -0
  48. package/dist/routes/file-mounts.js +90 -0
  49. package/dist/routes/file-mounts.js.map +1 -0
  50. package/dist/routes/files-organize.d.ts +28 -0
  51. package/dist/routes/files-organize.js +167 -0
  52. package/dist/routes/files-organize.js.map +1 -0
  53. package/dist/routes/files.d.ts +31 -0
  54. package/dist/routes/files.js +321 -0
  55. package/dist/routes/files.js.map +1 -0
  56. package/dist/routes/instances.js +826 -17
  57. package/dist/routes/instances.js.map +1 -1
  58. package/dist/routes/internal.d.ts +2 -0
  59. package/dist/routes/internal.js +59 -0
  60. package/dist/routes/internal.js.map +1 -0
  61. package/dist/routes/llm.js +24 -35
  62. package/dist/routes/llm.js.map +1 -1
  63. package/dist/routes/setup.js +10 -10
  64. package/dist/routes/setup.js.map +1 -1
  65. package/dist/routes/system.js +1 -1
  66. package/dist/routes/system.js.map +1 -1
  67. package/dist/routes/webdav.d.ts +17 -0
  68. package/dist/routes/webdav.js +114 -0
  69. package/dist/routes/webdav.js.map +1 -0
  70. package/dist/server.d.ts +9 -0
  71. package/dist/server.js +751 -20
  72. package/dist/server.js.map +1 -1
  73. package/dist/services/agent-apps/catalog.js +4 -3
  74. package/dist/services/agent-apps/catalog.js.map +1 -1
  75. package/dist/services/agent-apps/index.d.ts +1 -1
  76. package/dist/services/agent-apps/index.js +1 -1
  77. package/dist/services/agent-apps/installers/adapter.d.ts +1 -1
  78. package/dist/services/agent-apps/installers/adapter.js +1 -1
  79. package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
  80. package/dist/services/agent-apps/installers/shell-script.js +3 -3
  81. package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
  82. package/dist/services/agent-apps/types.d.ts +2 -2
  83. package/dist/services/agent-apps/types.js +1 -1
  84. package/dist/services/app/app-compiler.d.ts +1 -1
  85. package/dist/services/app/app-compiler.js +5 -5
  86. package/dist/services/app/app-compiler.js.map +1 -1
  87. package/dist/services/app/app-manager.d.ts +25 -1
  88. package/dist/services/app/app-manager.js +829 -150
  89. package/dist/services/app/app-manager.js.map +1 -1
  90. package/dist/services/app/custom-manager.js.map +1 -1
  91. package/dist/services/app/hermes-agent-manager.js +7 -4
  92. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  93. package/dist/services/app/ollama-manager.js +1 -1
  94. package/dist/services/app/ollama-manager.js.map +1 -1
  95. package/dist/services/app/openclaw-manager.js +20 -3
  96. package/dist/services/app/openclaw-manager.js.map +1 -1
  97. package/dist/services/app/platform-transform.d.ts +32 -0
  98. package/dist/services/app/platform-transform.js +65 -0
  99. package/dist/services/app/platform-transform.js.map +1 -0
  100. package/dist/services/app/provide-resolver.d.ts +29 -0
  101. package/dist/services/app/provide-resolver.js +112 -0
  102. package/dist/services/app/provide-resolver.js.map +1 -0
  103. package/dist/services/app-passwords.d.ts +61 -0
  104. package/dist/services/app-passwords.js +173 -0
  105. package/dist/services/app-passwords.js.map +1 -0
  106. package/dist/services/backup-manager.d.ts +11 -0
  107. package/dist/services/backup-manager.js +177 -4
  108. package/dist/services/backup-manager.js.map +1 -1
  109. package/dist/services/capability-endpoint-validator.d.ts +41 -0
  110. package/dist/services/capability-endpoint-validator.js +104 -0
  111. package/dist/services/capability-endpoint-validator.js.map +1 -0
  112. package/dist/services/capability-health.d.ts +16 -0
  113. package/dist/services/capability-health.js +121 -0
  114. package/dist/services/capability-health.js.map +1 -0
  115. package/dist/services/capability-registry.d.ts +106 -0
  116. package/dist/services/capability-registry.js +313 -0
  117. package/dist/services/capability-registry.js.map +1 -0
  118. package/dist/services/connection-apply.d.ts +91 -0
  119. package/dist/services/connection-apply.js +475 -0
  120. package/dist/services/connection-apply.js.map +1 -0
  121. package/dist/services/connection-resolver.d.ts +65 -0
  122. package/dist/services/connection-resolver.js +281 -0
  123. package/dist/services/connection-resolver.js.map +1 -0
  124. package/dist/services/connection-transactor.d.ts +39 -0
  125. package/dist/services/connection-transactor.js +351 -0
  126. package/dist/services/connection-transactor.js.map +1 -0
  127. package/dist/services/external-mounts.d.ts +40 -0
  128. package/dist/services/external-mounts.js +187 -0
  129. package/dist/services/external-mounts.js.map +1 -0
  130. package/dist/services/files-manager.d.ts +252 -0
  131. package/dist/services/files-manager.js +1075 -0
  132. package/dist/services/files-manager.js.map +1 -0
  133. package/dist/services/files-mounts.d.ts +42 -0
  134. package/dist/services/files-mounts.js +207 -0
  135. package/dist/services/files-mounts.js.map +1 -0
  136. package/dist/services/instance-manager.d.ts +13 -0
  137. package/dist/services/instance-manager.js +138 -46
  138. package/dist/services/instance-manager.js.map +1 -1
  139. package/dist/services/llm-proxy/index.d.ts +16 -2
  140. package/dist/services/llm-proxy/index.js +48 -44
  141. package/dist/services/llm-proxy/index.js.map +1 -1
  142. package/dist/services/llm-proxy/probe.d.ts +6 -0
  143. package/dist/services/llm-proxy/probe.js +85 -0
  144. package/dist/services/llm-proxy/probe.js.map +1 -0
  145. package/dist/services/llm-proxy/ssrf.d.ts +1 -0
  146. package/dist/services/llm-proxy/ssrf.js +24 -9
  147. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  148. package/dist/services/nomad-manager.d.ts +4 -0
  149. package/dist/services/nomad-manager.js +428 -35
  150. package/dist/services/nomad-manager.js.map +1 -1
  151. package/dist/services/organize/applier.d.ts +46 -0
  152. package/dist/services/organize/applier.js +218 -0
  153. package/dist/services/organize/applier.js.map +1 -0
  154. package/dist/services/organize/rules.d.ts +57 -0
  155. package/dist/services/organize/rules.js +286 -0
  156. package/dist/services/organize/rules.js.map +1 -0
  157. package/dist/services/organize/scanner.d.ts +50 -0
  158. package/dist/services/organize/scanner.js +366 -0
  159. package/dist/services/organize/scanner.js.map +1 -0
  160. package/dist/services/organize/store.d.ts +14 -0
  161. package/dist/services/organize/store.js +82 -0
  162. package/dist/services/organize/store.js.map +1 -0
  163. package/dist/services/panel-manager.js +20 -1
  164. package/dist/services/panel-manager.js.map +1 -1
  165. package/dist/services/process-manager.js +4 -3
  166. package/dist/services/process-manager.js.map +1 -1
  167. package/dist/services/runtime/adapters/hermes.d.ts +30 -1
  168. package/dist/services/runtime/adapters/hermes.js +219 -6
  169. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  170. package/dist/services/runtime/adapters/openclaw-mcporter.d.ts +45 -0
  171. package/dist/services/runtime/adapters/openclaw-mcporter.js +108 -0
  172. package/dist/services/runtime/adapters/openclaw-mcporter.js.map +1 -0
  173. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  174. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  175. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  176. package/dist/services/runtime/adapters/openclaw.d.ts +177 -0
  177. package/dist/services/runtime/adapters/openclaw.js +1171 -11
  178. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  179. package/dist/services/runtime/instance.d.ts +1 -1
  180. package/dist/services/runtime/instance.js +1 -1
  181. package/dist/services/runtime/instance.js.map +1 -1
  182. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  183. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  184. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  185. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  186. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  187. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  188. package/dist/services/runtime/mcp-shims/firewall.d.ts +26 -0
  189. package/dist/services/runtime/mcp-shims/firewall.js +129 -0
  190. package/dist/services/runtime/mcp-shims/firewall.js.map +1 -0
  191. package/dist/services/runtime/mcp-shims/searxng-shim.d.ts +27 -0
  192. package/dist/services/runtime/mcp-shims/searxng-shim.js +125 -0
  193. package/dist/services/runtime/mcp-shims/searxng-shim.js.map +1 -0
  194. package/dist/services/runtime/mcp-shims/write-mcp-entry.d.ts +83 -0
  195. package/dist/services/runtime/mcp-shims/write-mcp-entry.js +127 -0
  196. package/dist/services/runtime/mcp-shims/write-mcp-entry.js.map +1 -0
  197. package/dist/services/runtime/migrations.d.ts +8 -0
  198. package/dist/services/runtime/migrations.js +100 -0
  199. package/dist/services/runtime/migrations.js.map +1 -1
  200. package/dist/services/runtime/types.d.ts +46 -0
  201. package/dist/services/setup-manager.js +99 -24
  202. package/dist/services/setup-manager.js.map +1 -1
  203. package/dist/services/suggestions.d.ts +27 -0
  204. package/dist/services/suggestions.js +133 -0
  205. package/dist/services/suggestions.js.map +1 -0
  206. package/dist/services/task-registry.js +4 -2
  207. package/dist/services/task-registry.js.map +1 -1
  208. package/dist/services/telemetry/device-fingerprint.d.ts +1 -1
  209. package/dist/services/telemetry/device-fingerprint.js +1 -1
  210. package/dist/services/types-shim.d.ts +16 -0
  211. package/dist/services/types-shim.js +2 -0
  212. package/dist/services/types-shim.js.map +1 -0
  213. package/dist/services/webdav/server.d.ts +24 -0
  214. package/dist/services/webdav/server.js +420 -0
  215. package/dist/services/webdav/server.js.map +1 -0
  216. package/dist/services/webdav/xml-builder.d.ts +73 -0
  217. package/dist/services/webdav/xml-builder.js +156 -0
  218. package/dist/services/webdav/xml-builder.js.map +1 -0
  219. package/dist/services/workspace-builder.d.ts +29 -0
  220. package/dist/services/workspace-builder.js +188 -0
  221. package/dist/services/workspace-builder.js.map +1 -0
  222. package/dist/types.d.ts +231 -1
  223. package/dist/utils/instance-lock.d.ts +22 -0
  224. package/dist/utils/instance-lock.js +48 -0
  225. package/dist/utils/instance-lock.js.map +1 -0
  226. package/dist/utils/path-locks.d.ts +30 -0
  227. package/dist/utils/path-locks.js +63 -0
  228. package/dist/utils/path-locks.js.map +1 -0
  229. package/dist/utils/path-safety.d.ts +41 -0
  230. package/dist/utils/path-safety.js +119 -0
  231. package/dist/utils/path-safety.js.map +1 -0
  232. package/dist/utils/safe-json.js +55 -22
  233. package/dist/utils/safe-json.js.map +1 -1
  234. package/dist/utils/safe-write.d.ts +24 -0
  235. package/dist/utils/safe-write.js +82 -0
  236. package/dist/utils/safe-write.js.map +1 -0
  237. package/install/jishu-install.sh +323 -27
  238. package/install/jishu-uninstall.sh +353 -20
  239. package/package.json +18 -1
  240. package/public/assets/Dashboard-BdWPtroF.js +1 -0
  241. package/public/assets/{HermesChatPanel-mFSureyc.js → HermesChatPanel-B_2HlVBQ.js} +1 -1
  242. package/public/assets/HermesConfigForm-DVlhg3WV.js +4 -0
  243. package/public/assets/{InitPassword-CVA8wQA6.js → InitPassword-D7glTExX.js} +1 -1
  244. package/public/assets/InstanceDetail-CxSy2cpe.js +92 -0
  245. package/public/assets/{Login-BWsZH2mu.js → Login-Cfr5c2sv.js} +1 -1
  246. package/public/assets/NewInstance-BIYDmJis.js +1 -0
  247. package/public/assets/ProviderRecommendations-BuRnvRcI.js +1 -0
  248. package/public/assets/Settings-Cc-tYBil.js +1 -0
  249. package/public/assets/Setup-lGZEk5jq.js +1 -0
  250. package/public/assets/{WeixinLoginPanel-CnjR8xMu.js → WeixinLoginPanel-CoGqzxeV.js} +2 -2
  251. package/public/assets/index-87IJXG-w.css +1 -0
  252. package/public/assets/index-BZc5zH7u.js +19 -0
  253. package/public/assets/providers-DtNXh9JD.js +1 -0
  254. package/public/assets/registry-BWnkJgZ1.js +2 -0
  255. package/public/assets/{usePolling-Do5Erqm_.js → usePolling-CwwT9KrC.js} +1 -1
  256. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-y9V7Sfuu.js} +1 -1
  257. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-BWrEVJVb.js} +6 -6
  258. package/public/index.html +4 -4
  259. package/scripts/check-app-spec.mjs +457 -0
  260. package/scripts/check-i18n.mjs +154 -0
  261. package/scripts/check-new-file-tests.mjs +230 -0
  262. package/scripts/check-quarantine-expiry.mjs +105 -0
  263. package/scripts/perf/README.md +49 -0
  264. package/scripts/perf/auth.js +99 -0
  265. package/scripts/perf/config.js +63 -0
  266. package/scripts/perf/instances.js +143 -0
  267. package/scripts/perf/proxy.js +96 -0
  268. package/scripts/run.sh +4 -4
  269. package/scripts/smoke/files-w1.sh +142 -0
  270. package/scripts/smoke-backend.mjs +122 -0
  271. package/scripts/smoke-post-publish.mjs +346 -0
  272. package/public/assets/Dashboard-B-JoOjBQ.js +0 -1
  273. package/public/assets/HermesConfigForm-DvR05LK1.js +0 -4
  274. package/public/assets/InstanceDetail-DcZW2QGO.js +0 -91
  275. package/public/assets/NewInstance-BCIrAd86.js +0 -1
  276. package/public/assets/Settings-xkDcduFz.js +0 -1
  277. package/public/assets/Setup-Cfuwj4gV.js +0 -1
  278. package/public/assets/index-CPhVFEsx.css +0 -1
  279. package/public/assets/index-DQsM6Joa.js +0 -19
  280. package/public/assets/providers-V-vwrExZ.js +0 -1
  281. package/public/assets/registry-B4UFJdpA.js +0 -2
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Suggestions engine — scans every app instance for unbound `requires`
3
+ * slots that *could* be filled from the current capability registry, and
4
+ * emits one suggestion per (consumer, slot, candidate). The Dashboard
5
+ * polls this so the user gets a low-friction "you can connect X to Y"
6
+ * prompt without having to dig into each instance.
7
+ *
8
+ * Dismissals are stored on the consumer's `instance.json.dismissedSuggestions`
9
+ * (per-slot, 30-day TTL).
10
+ */
11
+ import * as capabilityRegistry from "./capability-registry.js";
12
+ import * as instanceManager from "./instance-manager.js";
13
+ import { getApp } from "./app/app-manager.js";
14
+ import { resolveConnections } from "./connection-resolver.js";
15
+ import { loadCapabilitySpecForLegacyInstance } from "./runtime/migrations.js";
16
+ import { safeReadJson, safeWriteJson } from "../utils/safe-json.js";
17
+ const DISMISS_TTL_MS = 30 * 24 * 60 * 60 * 1000;
18
+ export function computeSuggestions() {
19
+ const out = [];
20
+ const allInstances = instanceManager.listInstances();
21
+ for (const inst of allInstances) {
22
+ // App-installed instances (yaml apps from the Apps page) carry an
23
+ // `app_id` and resolve their spec via the registry. Legacy instances
24
+ // (hermes / openclaw created via the classic NewInstance flow) have
25
+ // no `app_id`; their `requires` come from the matching yaml template
26
+ // synthesized in-memory by `loadCapabilitySpecForLegacyInstance`.
27
+ // Without this branch, brand-new hermes / openclaw instances never
28
+ // surface suggestions even when running candidates exist.
29
+ const appId = inst.app_id;
30
+ let spec = null;
31
+ if (appId) {
32
+ const appData = getApp(appId);
33
+ if (!appData)
34
+ continue;
35
+ spec = appData.spec;
36
+ }
37
+ else {
38
+ spec = loadCapabilitySpecForLegacyInstance(inst);
39
+ if (!spec)
40
+ continue;
41
+ }
42
+ const persisted = (inst.connections ?? {});
43
+ const dismissed = (inst.dismissedSuggestions ?? []);
44
+ const dismissedSlots = new Set(dismissed
45
+ .filter((d) => new Date(d.until).getTime() > Date.now())
46
+ .map((d) => d.slot));
47
+ let pending = [];
48
+ try {
49
+ const r = resolveConnections(spec, { connections: persisted }, "preCreate");
50
+ pending = r.pending;
51
+ }
52
+ catch {
53
+ continue;
54
+ }
55
+ for (const p of pending) {
56
+ if (dismissedSlots.has(p.slot))
57
+ continue;
58
+ const candidates = enumerateCandidates(p.capability);
59
+ // Same agent-on-agent guard as the Connections-tab dropdown: hermes
60
+ // and openclaw consuming `llm` should never be offered another
61
+ // agent's `llm-agent` (would chain agent → agent → real model).
62
+ const consumerAgentType = String(inst?.agentType ?? "");
63
+ const consumerIsAgent = consumerAgentType === "hermes" || consumerAgentType === "openclaw";
64
+ const running = candidates.filter((e) => {
65
+ if (e.status !== "running")
66
+ return false;
67
+ if (e.instanceId === inst.id)
68
+ return false;
69
+ if (consumerIsAgent && p.capability === "llm" && e.capability === "llm-agent")
70
+ return false;
71
+ return true;
72
+ });
73
+ if (running.length === 0)
74
+ continue;
75
+ // Single candidate → suggest it; multiple → suggest the running ones.
76
+ const top = running[0];
77
+ out.push({
78
+ id: `${inst.id}::${p.slot}::${top.instanceId}::${top.capability}`,
79
+ consumerInstanceId: inst.id,
80
+ consumerName: inst.name ?? inst.id,
81
+ appId: appId ?? "",
82
+ slot: p.slot,
83
+ capability: p.capability,
84
+ candidate: {
85
+ providerId: top.instanceId,
86
+ capability: top.capability,
87
+ name: top.name,
88
+ protocol: top.protocol,
89
+ status: top.status,
90
+ hostPort: top.hostPort,
91
+ path: top.path,
92
+ },
93
+ reason: p.reason,
94
+ });
95
+ }
96
+ }
97
+ return out;
98
+ }
99
+ function enumerateCandidates(capability) {
100
+ const isCategoryToken = ["llm", "search", "browser", "mcp"].includes(capability);
101
+ if (!isCategoryToken) {
102
+ return capabilityRegistry.listProviders(capability);
103
+ }
104
+ const file = capabilityRegistry.snapshot();
105
+ const out = [];
106
+ for (const [cap, list] of Object.entries(file.providersByCapability ?? {})) {
107
+ if (cap.startsWith(capability + "-") || cap === capability) {
108
+ out.push(...list);
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+ /**
114
+ * Mark a suggestion as dismissed. Persisted on the consumer instance so
115
+ * subsequent computeSuggestions() invocations skip it for the TTL window.
116
+ *
117
+ * `id` follows the format `${consumerInstanceId}::${slot}::...` — the
118
+ * leading two segments are enough to identify the slot to dismiss.
119
+ */
120
+ export async function dismissSuggestion(id) {
121
+ const parts = id.split("::");
122
+ if (parts.length < 2)
123
+ throw new Error(`Invalid suggestion id: ${id}`);
124
+ const [consumerInstanceId, slot] = parts;
125
+ const path = instanceManager.instanceMetaPath(consumerInstanceId);
126
+ const cur = (safeReadJson(path, `instance:${consumerInstanceId}`) ?? {});
127
+ const list = Array.isArray(cur.dismissedSuggestions) ? cur.dismissedSuggestions : [];
128
+ const filtered = list.filter((d) => d.slot !== slot);
129
+ filtered.push({ slot, until: new Date(Date.now() + DISMISS_TTL_MS).toISOString() });
130
+ cur.dismissedSuggestions = filtered;
131
+ safeWriteJson(path, cur);
132
+ }
133
+ //# sourceMappingURL=suggestions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggestions.js","sourceRoot":"","sources":["../../src/services/suggestions.ts"],"names":[],"mappings":"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"}
@@ -15,13 +15,15 @@ const MAX_TASK_EVENTS = 500;
15
15
  setInterval(() => {
16
16
  const now = Date.now();
17
17
  for (const [id, task] of tasks) {
18
- if (task.status !== "running" && now - parseInt(id.split("-").pop() || "0") > TASK_MAX_AGE) {
18
+ const parts = id.split("-");
19
+ const createdAt = Number(parts[parts.length - 2] || 0);
20
+ if (task.status !== "running" && Number.isFinite(createdAt) && now - createdAt > TASK_MAX_AGE) {
19
21
  tasks.delete(id);
20
22
  }
21
23
  }
22
24
  }, 60_000).unref();
23
25
  export function createTask(name) {
24
- const id = `${name}-${Date.now()}`;
26
+ const id = `${name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
25
27
  const task = { id, name, status: "running", events: [], listeners: new Set() };
26
28
  tasks.set(id, task);
27
29
  return task;
@@ -1 +1 @@
1
- {"version":3,"file":"task-registry.js","sourceRoot":"","sources":["../../src/services/task-registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task-registry.js","sourceRoot":"","sources":["../../src/services/task-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA4BH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;AACtC,MAAM,YAAY,GAAG,OAAO,CAAC;AAC7B,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,WAAW,CAAC,GAAG,EAAE;IACf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,GAAG,GAAG,SAAS,GAAG,YAAY,EAAE,CAAC;YAC9F,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;AAEnB,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,EAAE,GAAG,GAAG,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IAC7E,MAAM,IAAI,GAAS,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;IACrF,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAU,EAAE,KAAgB;IACnD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACzC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACtC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;KACzB,CAAC;AACJ,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,eAAe,CAAC,UAAmB;IACjD,MAAM,MAAM,GAAwC,EAAE,CAAC;IACvD,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,SAAS;QACxC,IAAI,UAAU,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAS;QAC9D,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAU,EAAE,QAAoC;IAC5E,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC/C,CAAC"}
@@ -2,7 +2,7 @@
2
2
  * Cross-platform device fingerprint generation.
3
3
  *
4
4
  * Supported platforms:
5
- * - Linux: Jetson Orin/Thor, RK3588, CIX P1, 辉羲 R1, Raspberry Pi
5
+ * - Linux: Jetson Orin/Thor, RK3588, CIX P1, Huixi R1, Raspberry Pi
6
6
  * - macOS: Mac Mini, Mac Studio
7
7
  *
8
8
  * Fingerprint = SHA-256(machine_id_or_uuid + mac)
@@ -2,7 +2,7 @@
2
2
  * Cross-platform device fingerprint generation.
3
3
  *
4
4
  * Supported platforms:
5
- * - Linux: Jetson Orin/Thor, RK3588, CIX P1, 辉羲 R1, Raspberry Pi
5
+ * - Linux: Jetson Orin/Thor, RK3588, CIX P1, Huixi R1, Raspberry Pi
6
6
  * - macOS: Mac Mini, Mac Studio
7
7
  *
8
8
  * Fingerprint = SHA-256(machine_id_or_uuid + mac)
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Lightweight `AppInstance` shim exported from a stable module path so PR 4
3
+ * apply hooks don't need to import from app-manager.ts (which would create
4
+ * a circular dependency: app-manager → connection-apply → app-manager).
5
+ */
6
+ import type { InstanceConnections } from "../types.js";
7
+ export interface AppInstance {
8
+ id: string;
9
+ name?: string;
10
+ description?: string;
11
+ app_id?: string;
12
+ connections?: InstanceConnections;
13
+ "connections-env"?: Record<string, string>;
14
+ schemaVersion?: number;
15
+ [key: string]: unknown;
16
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-shim.js","sourceRoot":"","sources":["../../src/services/types-shim.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ import type { FastifyRequest, FastifyReply } from "fastify";
2
+ import { FilesManager } from "../files-manager.js";
3
+ import { type AppPassword } from "../app-passwords.js";
4
+ export interface WebdavContext {
5
+ filesManager: FilesManager;
6
+ /** WebDAV root URL prefix, e.g. "/webdav" — no trailing slash. */
7
+ mountPrefix: string;
8
+ /** Authenticated app password (already verified by auth middleware). */
9
+ authedAs: AppPassword;
10
+ }
11
+ /**
12
+ * Strip the mount prefix from req.url and decode each segment, then
13
+ * normalize: leading / trailing slash removed, "." collapsed. Returns
14
+ * the files/-relative path the rest of the WebDAV server uses.
15
+ */
16
+ export declare function extractRel(reqUrl: string, mountPrefix: string): string;
17
+ export declare function handleOptions(_ctx: WebdavContext, _req: FastifyRequest, reply: FastifyReply): Promise<void>;
18
+ export declare function handlePropfind(ctx: WebdavContext, req: FastifyRequest, reply: FastifyReply): Promise<void>;
19
+ export declare function handleGet(ctx: WebdavContext, req: FastifyRequest, reply: FastifyReply, bodyless: boolean): Promise<void>;
20
+ export declare function handlePut(ctx: WebdavContext, req: FastifyRequest, reply: FastifyReply): Promise<void>;
21
+ export declare function handleDelete(ctx: WebdavContext, req: FastifyRequest, reply: FastifyReply): Promise<void>;
22
+ export declare function handleMkcol(ctx: WebdavContext, req: FastifyRequest, reply: FastifyReply): Promise<void>;
23
+ export declare function handleMove(ctx: WebdavContext, req: FastifyRequest, reply: FastifyReply): Promise<void>;
24
+ export declare function handleCopy(ctx: WebdavContext, req: FastifyRequest, reply: FastifyReply): Promise<void>;
@@ -0,0 +1,420 @@
1
+ /**
2
+ * WebDAV server logic for the AI NAS (M3 W4 PR-3).
3
+ *
4
+ * Implements a focused subset of RFC 4918 — the methods iOS Files,
5
+ * macOS Finder, Windows Explorer, and FX (Android) actually issue:
6
+ *
7
+ * OPTIONS capability advertisement
8
+ * PROPFIND list (Depth: 0 / 1)
9
+ * GET stream a file
10
+ * HEAD same headers as GET, no body
11
+ * PUT upload (octet-stream pass-through; overwrites are normal)
12
+ * DELETE soft-delete to .trash via FilesManager
13
+ * MKCOL create directory
14
+ * MOVE rename / move within the WebDAV root
15
+ * COPY shallow file copy (no recursive copy in this PR)
16
+ *
17
+ * Locking (LOCK / UNLOCK / PROPPATCH) is intentionally NOT implemented;
18
+ * we return 501 — clients fall back to optimistic concurrency.
19
+ *
20
+ * All request paths are validated through the FilesManager surface so
21
+ * we share path-safety, symlink defense, and the audit log with the
22
+ * /api/files routes. Authentication runs upstream (see routes/webdav.ts).
23
+ */
24
+ import * as fs from "node:fs";
25
+ import * as path from "node:path";
26
+ import { FilesError, } from "../files-manager.js";
27
+ import { buildMultistatus, parseDepth, } from "./xml-builder.js";
28
+ import { isAllowed } from "../app-passwords.js";
29
+ import { resolveSafe } from "../../utils/path-safety.js";
30
+ import { atomicWriteStream } from "../../utils/safe-write.js";
31
+ import { withPathLock } from "../../utils/path-locks.js";
32
+ import { FILES_ROOT } from "../../config.js";
33
+ // ── Path extraction ────────────────────────────────
34
+ /**
35
+ * Strip the mount prefix from req.url and decode each segment, then
36
+ * normalize: leading / trailing slash removed, "." collapsed. Returns
37
+ * the files/-relative path the rest of the WebDAV server uses.
38
+ */
39
+ export function extractRel(reqUrl, mountPrefix) {
40
+ const cleanPrefix = mountPrefix.replace(/\/+$/, "");
41
+ let urlPath = reqUrl;
42
+ // Strip query string — WebDAV doesn't use it
43
+ const q = urlPath.indexOf("?");
44
+ if (q >= 0)
45
+ urlPath = urlPath.slice(0, q);
46
+ if (urlPath.startsWith(cleanPrefix))
47
+ urlPath = urlPath.slice(cleanPrefix.length);
48
+ if (urlPath.startsWith("/"))
49
+ urlPath = urlPath.slice(1);
50
+ if (urlPath.endsWith("/"))
51
+ urlPath = urlPath.slice(0, -1);
52
+ // Decode each segment so `%E7%AC%94%E8%AE%B0` becomes `笔记`
53
+ return urlPath
54
+ .split("/")
55
+ .map((s) => {
56
+ try {
57
+ return decodeURIComponent(s);
58
+ }
59
+ catch {
60
+ return s;
61
+ }
62
+ })
63
+ .filter((s) => s !== "")
64
+ .join("/");
65
+ }
66
+ // ── Auth scope check ───────────────────────────────
67
+ function checkScope(ctx, rel, needsWrite) {
68
+ return isAllowed(ctx.authedAs, rel, needsWrite);
69
+ }
70
+ function send403(reply, reason) {
71
+ reply
72
+ .code(403)
73
+ .header("Content-Type", "text/plain; charset=utf-8")
74
+ .send(`forbidden: ${reason ?? "scope mismatch"}`);
75
+ }
76
+ // ── OPTIONS ────────────────────────────────────────
77
+ export async function handleOptions(_ctx, _req, reply) {
78
+ reply
79
+ .code(200)
80
+ .header("Allow", "OPTIONS, GET, HEAD, PUT, DELETE, PROPFIND, MKCOL, MOVE, COPY")
81
+ .header("DAV", "1")
82
+ .header("MS-Author-Via", "DAV") // Windows Explorer compatibility
83
+ .send();
84
+ }
85
+ // ── PROPFIND ───────────────────────────────────────
86
+ export async function handlePropfind(ctx, req, reply) {
87
+ const rel = extractRel(req.url, ctx.mountPrefix);
88
+ const scope = checkScope(ctx, rel, false);
89
+ if (!scope.allowed)
90
+ return send403(reply, scope.reason);
91
+ const depth = parseDepth(req.headers.depth);
92
+ const targetAbs = resolveSafe(FILES_ROOT, rel);
93
+ let stat;
94
+ try {
95
+ const ls = fs.lstatSync(targetAbs);
96
+ if (ls.isSymbolicLink()) {
97
+ reply.code(403).send("symlinks not exposed via WebDAV");
98
+ return;
99
+ }
100
+ stat = fs.statSync(targetAbs);
101
+ }
102
+ catch (e) {
103
+ if (e?.code === "ENOENT") {
104
+ reply.code(404).send();
105
+ return;
106
+ }
107
+ throw e;
108
+ }
109
+ const resources = [
110
+ statToResource(rel, stat, basename(rel) || "files"),
111
+ ];
112
+ if (depth >= 1 && stat.isDirectory()) {
113
+ let entries;
114
+ try {
115
+ entries = fs.readdirSync(targetAbs);
116
+ }
117
+ catch {
118
+ entries = [];
119
+ }
120
+ for (const name of entries) {
121
+ if (name.startsWith("."))
122
+ continue; // hidden / .trash never exposed
123
+ const childAbs = path.join(targetAbs, name);
124
+ let childStat;
125
+ try {
126
+ const ls = fs.lstatSync(childAbs);
127
+ if (ls.isSymbolicLink())
128
+ continue;
129
+ childStat = fs.statSync(childAbs);
130
+ }
131
+ catch {
132
+ continue;
133
+ }
134
+ const childRel = rel ? `${rel}/${name}` : name;
135
+ const childScope = checkScope(ctx, childRel, false);
136
+ if (!childScope.allowed)
137
+ continue; // hide outside-scope children
138
+ resources.push(statToResource(childRel, childStat, name));
139
+ }
140
+ }
141
+ const xml = buildMultistatus(ctx.mountPrefix, resources);
142
+ reply
143
+ .code(207)
144
+ .header("Content-Type", 'application/xml; charset="utf-8"')
145
+ .header("DAV", "1")
146
+ .send(xml);
147
+ }
148
+ function statToResource(rel, stat, displayName) {
149
+ const isDir = stat.isDirectory();
150
+ return {
151
+ pathRel: rel,
152
+ displayName,
153
+ isDir,
154
+ size: isDir ? 0 : stat.size,
155
+ mtime: Math.floor(stat.mtimeMs / 1000),
156
+ etag: `W/"${stat.size}-${Math.floor(stat.mtimeMs)}"`,
157
+ mime: isDir ? undefined : guessMime(displayName),
158
+ };
159
+ }
160
+ function basename(rel) {
161
+ if (!rel)
162
+ return "";
163
+ const idx = rel.lastIndexOf("/");
164
+ return idx === -1 ? rel : rel.slice(idx + 1);
165
+ }
166
+ const MIME_BY_EXT = {
167
+ ".txt": "text/plain; charset=utf-8",
168
+ ".md": "text/markdown; charset=utf-8",
169
+ ".json": "application/json",
170
+ ".pdf": "application/pdf",
171
+ ".jpg": "image/jpeg",
172
+ ".jpeg": "image/jpeg",
173
+ ".png": "image/png",
174
+ ".webp": "image/webp",
175
+ ".gif": "image/gif",
176
+ ".mp4": "video/mp4",
177
+ ".mov": "video/quicktime",
178
+ ".mp3": "audio/mpeg",
179
+ ".zip": "application/zip",
180
+ };
181
+ function guessMime(name) {
182
+ const ext = path.extname(name).toLowerCase();
183
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
184
+ }
185
+ // ── GET / HEAD ─────────────────────────────────────
186
+ export async function handleGet(ctx, req, reply, bodyless) {
187
+ const rel = extractRel(req.url, ctx.mountPrefix);
188
+ const scope = checkScope(ctx, rel, false);
189
+ if (!scope.allowed)
190
+ return send403(reply, scope.reason);
191
+ if (rel === "") {
192
+ reply.code(403).send("cannot GET the WebDAV root");
193
+ return;
194
+ }
195
+ try {
196
+ const r = await ctx.filesManager.readStream(rel);
197
+ reply
198
+ .header("Content-Type", r.mime)
199
+ .header("Content-Length", String(r.size))
200
+ .header("ETag", r.etag)
201
+ .header("Last-Modified", new Date(r.mtime * 1000).toUTCString())
202
+ .header("Accept-Ranges", "bytes");
203
+ if (bodyless) {
204
+ reply.code(200).send();
205
+ }
206
+ else {
207
+ reply.code(200).send(r.openStream());
208
+ }
209
+ }
210
+ catch (e) {
211
+ sendFilesError(reply, e);
212
+ }
213
+ }
214
+ // ── PUT ────────────────────────────────────────────
215
+ export async function handlePut(ctx, req, reply) {
216
+ const rel = extractRel(req.url, ctx.mountPrefix);
217
+ const scope = checkScope(ctx, rel, true);
218
+ if (!scope.allowed)
219
+ return send403(reply, scope.reason);
220
+ if (rel === "") {
221
+ reply.code(405).send("cannot PUT the WebDAV root");
222
+ return;
223
+ }
224
+ // Content-Length is mandatory for the same reason as /api/files.
225
+ const cl = req.headers["content-length"];
226
+ const clNum = typeof cl === "string" ? Number.parseInt(cl, 10) : Number.NaN;
227
+ if (!Number.isFinite(clNum) || clNum < 0) {
228
+ reply.code(411).send("Content-Length required");
229
+ return;
230
+ }
231
+ const body = req.raw;
232
+ if (!body) {
233
+ reply.code(400).send("missing body");
234
+ return;
235
+ }
236
+ try {
237
+ // WebDAV PUT default-overwrites; matches RFC 4918 §9.7.
238
+ const result = await ctx.filesManager.writeStream(rel, body, {
239
+ overwrite: true,
240
+ expectedSize: clNum,
241
+ });
242
+ // 201 if newly created, 204 if overwritten — FilesManager doesn't
243
+ // currently distinguish; default to 201 (Finder accepts both).
244
+ reply
245
+ .code(201)
246
+ .header("ETag", result.etag)
247
+ .header("Content-Length", "0")
248
+ .send();
249
+ }
250
+ catch (e) {
251
+ sendFilesError(reply, e);
252
+ }
253
+ }
254
+ // ── DELETE ─────────────────────────────────────────
255
+ export async function handleDelete(ctx, req, reply) {
256
+ const rel = extractRel(req.url, ctx.mountPrefix);
257
+ const scope = checkScope(ctx, rel, true);
258
+ if (!scope.allowed)
259
+ return send403(reply, scope.reason);
260
+ if (rel === "") {
261
+ reply.code(405).send("cannot DELETE the WebDAV root");
262
+ return;
263
+ }
264
+ try {
265
+ await ctx.filesManager.remove(rel);
266
+ reply.code(204).send();
267
+ }
268
+ catch (e) {
269
+ sendFilesError(reply, e);
270
+ }
271
+ }
272
+ // ── MKCOL ──────────────────────────────────────────
273
+ export async function handleMkcol(ctx, req, reply) {
274
+ const rel = extractRel(req.url, ctx.mountPrefix);
275
+ const scope = checkScope(ctx, rel, true);
276
+ if (!scope.allowed)
277
+ return send403(reply, scope.reason);
278
+ if (rel === "") {
279
+ reply.code(405).send("cannot MKCOL the WebDAV root");
280
+ return;
281
+ }
282
+ // RFC 4918 §9.3: MKCOL must reject a request with a body.
283
+ const cl = Number.parseInt(req.headers["content-length"], 10);
284
+ if (Number.isFinite(cl) && cl > 0) {
285
+ reply.code(415).send("MKCOL must not include a body");
286
+ return;
287
+ }
288
+ try {
289
+ await ctx.filesManager.mkdir(rel);
290
+ reply.code(201).send();
291
+ }
292
+ catch (e) {
293
+ sendFilesError(reply, e);
294
+ }
295
+ }
296
+ // ── MOVE ───────────────────────────────────────────
297
+ export async function handleMove(ctx, req, reply) {
298
+ const fromRel = extractRel(req.url, ctx.mountPrefix);
299
+ const toRel = parseDestinationHeader(req, ctx.mountPrefix);
300
+ if (toRel === null) {
301
+ reply.code(400).send("invalid Destination header");
302
+ return;
303
+ }
304
+ const fromScope = checkScope(ctx, fromRel, true);
305
+ if (!fromScope.allowed)
306
+ return send403(reply, fromScope.reason);
307
+ const toScope = checkScope(ctx, toRel, true);
308
+ if (!toScope.allowed)
309
+ return send403(reply, toScope.reason);
310
+ const overwrite = String(req.headers.overwrite ?? "T")
311
+ .trim()
312
+ .toUpperCase() !== "F";
313
+ try {
314
+ await ctx.filesManager.move(fromRel, toRel, overwrite);
315
+ // 201 if the destination was newly created, 204 if overwritten.
316
+ // Without knowing pre-state we default to 201 (clients accept it).
317
+ reply.code(201).header("Content-Length", "0").send();
318
+ }
319
+ catch (e) {
320
+ sendFilesError(reply, e);
321
+ }
322
+ }
323
+ // ── COPY ───────────────────────────────────────────
324
+ export async function handleCopy(ctx, req, reply) {
325
+ const fromRel = extractRel(req.url, ctx.mountPrefix);
326
+ const toRel = parseDestinationHeader(req, ctx.mountPrefix);
327
+ if (toRel === null) {
328
+ reply.code(400).send("invalid Destination header");
329
+ return;
330
+ }
331
+ const fromScope = checkScope(ctx, fromRel, false);
332
+ if (!fromScope.allowed)
333
+ return send403(reply, fromScope.reason);
334
+ const toScope = checkScope(ctx, toRel, true);
335
+ if (!toScope.allowed)
336
+ return send403(reply, toScope.reason);
337
+ const overwrite = String(req.headers.overwrite ?? "T")
338
+ .trim()
339
+ .toUpperCase() !== "F";
340
+ // For W4 we only support file copy (not recursive directory copy).
341
+ // Finder relies primarily on MOVE; recursive COPY is rare. Returning
342
+ // 502 Bad Gateway with a clear body lets clients fall back.
343
+ const fromAbs = resolveSafe(FILES_ROOT, fromRel);
344
+ let stat;
345
+ try {
346
+ stat = fs.statSync(fromAbs);
347
+ }
348
+ catch {
349
+ reply.code(404).send();
350
+ return;
351
+ }
352
+ if (stat.isDirectory()) {
353
+ reply
354
+ .code(502)
355
+ .send("recursive directory COPY is not supported in this build");
356
+ return;
357
+ }
358
+ const toAbs = resolveSafe(FILES_ROOT, toRel);
359
+ try {
360
+ if (!overwrite && fs.existsSync(toAbs)) {
361
+ reply.code(412).send();
362
+ return;
363
+ }
364
+ fs.mkdirSync(path.dirname(toAbs), { recursive: true });
365
+ // Stream copy via atomicWriteStream so we get tmp+rename atomicity
366
+ await withPathLock(toAbs, async () => {
367
+ await atomicWriteStream(toAbs, fs.createReadStream(fromAbs));
368
+ });
369
+ reply.code(201).header("Content-Length", "0").send();
370
+ }
371
+ catch (e) {
372
+ sendFilesError(reply, e);
373
+ }
374
+ }
375
+ function parseDestinationHeader(req, mountPrefix) {
376
+ const raw = req.headers.destination;
377
+ if (typeof raw !== "string" || !raw)
378
+ return null;
379
+ // Destination may be a full URL (https://host/webdav/x) or a path-only
380
+ // value. We strip everything up to and including the mount prefix.
381
+ let dest = raw;
382
+ try {
383
+ const u = new URL(raw);
384
+ dest = u.pathname;
385
+ }
386
+ catch {
387
+ // Not a full URL — assume path-only
388
+ }
389
+ const cleanPrefix = mountPrefix.replace(/\/+$/, "");
390
+ if (!dest.startsWith(cleanPrefix))
391
+ return null;
392
+ let rest = dest.slice(cleanPrefix.length);
393
+ if (rest.startsWith("/"))
394
+ rest = rest.slice(1);
395
+ if (rest.endsWith("/"))
396
+ rest = rest.slice(0, -1);
397
+ return rest
398
+ .split("/")
399
+ .map((s) => {
400
+ try {
401
+ return decodeURIComponent(s);
402
+ }
403
+ catch {
404
+ return s;
405
+ }
406
+ })
407
+ .filter((s) => s !== "")
408
+ .join("/");
409
+ }
410
+ // ── Error mapping ──────────────────────────────────
411
+ function sendFilesError(reply, e) {
412
+ if (e instanceof FilesError) {
413
+ reply.code(e.httpStatus).send(e.message);
414
+ return;
415
+ }
416
+ const err = e;
417
+ console.error("[webdav] unexpected:", err.message);
418
+ reply.code(500).send(err.message ?? "internal error");
419
+ }
420
+ //# sourceMappingURL=server.js.map