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,351 @@
1
+ /**
2
+ * connection-transactor — PUT /connections atomic 5-step flow (§10.3).
3
+ *
4
+ * 1. Validate — resolveConnections runtime mode + per-binding checks
5
+ * 2. Snapshot — capture instance.json plus the side-effect files that
6
+ * persist hooks may write through: provider.env (LLM
7
+ * proxy-upstream), mcporter.json (MCP),
8
+ * openclaw.json (LLM proxy-upstream + adapter
9
+ * applyConnectionEnv), and Hermes config.yaml / .env.
10
+ * Snapshot now covers OpenClaw (V2 + legacy),
11
+ * Hermes (V2 + legacy), and generic app-dir consumers.
12
+ * 3. Apply hooks — invoke PERSIST_HOOKS for each resolved binding in
13
+ * deterministic order (default-env → search → browser
14
+ * → llm → mcp). On any failure, jump to step 5.
15
+ * 4. Persist — read-modify-write instance.json: replace `connections`
16
+ * map only, keep other fields (notably the
17
+ * `connections-env` written by step 3 hooks for generic
18
+ * apps without an adapter).
19
+ * 5. Rollback — reverse-order recovery from snapshots: restore each
20
+ * side-effect file (or unlink if it didn't exist
21
+ * pre-apply) before restoring instance.json.
22
+ *
23
+ * Returns either { ok: true, resolved, pending } or a structured error.
24
+ */
25
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
26
+ import { dirname, join } from "node:path";
27
+ import { INSTANCES_DIR, APPS_DIR } from "../config.js";
28
+ import { resolveConnections, ConnectionError } from "./connection-resolver.js";
29
+ import { PERSIST_HOOKS, UNPERSIST_HOOKS } from "./connection-apply.js";
30
+ import { categoryFromProviderCapability } from "./connection-resolver.js";
31
+ import * as capabilityRegistry from "./capability-registry.js";
32
+ import { withInstanceLock } from "../utils/instance-lock.js";
33
+ const APPLY_ORDER = ["default", "search", "browser", "llm", "mcp"];
34
+ export async function applyConnections(input) {
35
+ // Serialize against startApp/stopApp/restartApp on the same consumer
36
+ // instance. Without this, a concurrent stop can flip a bound provider
37
+ // to status:stopped between Validate (step 1) and Apply (step 3),
38
+ // leaving instance.json + side-effect files inconsistent.
39
+ return withInstanceLock(input.instance.id, () => applyConnectionsImpl(input));
40
+ }
41
+ async function applyConnectionsImpl(input) {
42
+ const { instance, spec, newConnections, saveInstanceJson, readInstanceJson, adapter } = input;
43
+ // ── Step 1: Validate ──────────────────────────────────────────────────
44
+ // Resolve the *new* connections in runtime mode so missing-required /
45
+ // ambiguous / invalid-binding all surface as ConnectionError before we
46
+ // touch any state.
47
+ const candidate = {
48
+ ...instance,
49
+ connections: newConnections,
50
+ };
51
+ const { resolved } = resolveConnections(spec, candidate, "runtime");
52
+ // ── Step 1d: validateCapabilityEndpoint per binding (§12) ─────────────
53
+ // Catches registry tampering / drift before any persist hook fires:
54
+ // - Fresh re-resolve must match the registry entry (host/port/path)
55
+ // - Provider host must be on loopback or LAN
56
+ // - Protocol must be in the category whitelist
57
+ // Only applies to entries whose provider spec we can resolve; legacy
58
+ // instances without a synthesized spec degrade gracefully (skip).
59
+ await validateResolvedEndpoints(resolved);
60
+ // ── Step 2: Snapshot ──────────────────────────────────────────────────
61
+ // 覆盖 OpenClaw (V2+legacy)、Hermes (V2+legacy)、generic app-dir 三类
62
+ // consumer 的具体写入路径;详见 snapshotSideEffectFiles。
63
+ const snapshot = {
64
+ instanceJson: await readInstanceJson(instance.id),
65
+ files: snapshotSideEffectFiles(instance.id),
66
+ };
67
+ // ── Step 2b: Compute shrunk slots ─────────────────────────────────────
68
+ // PERSIST_HOOKS only sees slots that *currently* have a binding. When
69
+ // the user removes a binding (sets the slot to null or drops it from the
70
+ // map) we need to undo the durable state the prior persist hook wrote —
71
+ // otherwise unbinding LLM_ENDPOINT leaves x-jishushell.proxy.upstream
72
+ // pointing at the gone provider, etc. `shrunkSlots` enumerates exactly
73
+ // those: present-and-non-null in the on-disk connections, missing or
74
+ // null in `newConnections`.
75
+ const previousConnections = (snapshot.instanceJson?.connections ?? {});
76
+ const shrunkSlots = [];
77
+ for (const [slot, prior] of Object.entries(previousConnections)) {
78
+ if (prior == null)
79
+ continue; // already empty — nothing to undo
80
+ const next = newConnections?.[slot];
81
+ if (next != null)
82
+ continue; // still bound (or rebound) — persist hook handles it
83
+ const req = spec.requires?.find((r) => r.inject_as === slot);
84
+ const cap = req?.capability ?? "";
85
+ const fromToken = cap && (UNPERSIST_HOOKS[cap] ? cap : null);
86
+ const fromProvide = !fromToken ? categoryFromProviderCapability(cap) : null;
87
+ shrunkSlots.push({ slot, category: fromToken ?? fromProvide ?? "default" });
88
+ }
89
+ // ── Step 3: Apply hooks ───────────────────────────────────────────────
90
+ const ctx = makeApplyContext({ adapter, saveInstanceJson });
91
+ const appliedHooks = [];
92
+ try {
93
+ // 3a: unpersist removed slots BEFORE persisting new ones — if the user
94
+ // swapped one LLM provider for another, persist's overwrite already
95
+ // handles it; this loop only fires for slots that genuinely went
96
+ // away, so it's safe to run first. Failures here are logged and
97
+ // swallowed so a stale env var or stale upstream config doesn't
98
+ // block legitimate new bindings; the rollback step still recovers
99
+ // the full snapshot if a later persist throws.
100
+ for (const { slot, category } of shrunkSlots) {
101
+ const hook = UNPERSIST_HOOKS[category];
102
+ if (!hook)
103
+ continue;
104
+ try {
105
+ await hook(candidate, slot, ctx);
106
+ }
107
+ catch (e) {
108
+ console.warn(`[connection-transactor] unpersist ${category}/${slot} for ${instance.id} failed: ${e?.message ?? e}`);
109
+ }
110
+ }
111
+ for (const category of APPLY_ORDER) {
112
+ const hook = PERSIST_HOOKS[category];
113
+ if (!hook)
114
+ continue;
115
+ for (const binding of resolved) {
116
+ if (binding.category !== category)
117
+ continue;
118
+ await hook(candidate, binding, ctx);
119
+ appliedHooks.push(binding);
120
+ }
121
+ }
122
+ }
123
+ catch (e) {
124
+ await rollback(input, snapshot);
125
+ if (e instanceof ConnectionError)
126
+ throw e;
127
+ throw new ConnectionError("CONNECTION_APPLY_FAILED", 500, `Connection apply failed: ${e?.message ?? e}`, { applied: appliedHooks.map((b) => b.slot) });
128
+ }
129
+ // ── Step 4: Persist connections ───────────────────────────────────────
130
+ // Read-modify-write so we don't clobber `connections-env` that step 3
131
+ // hooks may have written for generic apps without an adapter.
132
+ try {
133
+ await saveInstanceJson(instance.id, (cur) => ({
134
+ ...cur,
135
+ connections: newConnections,
136
+ }));
137
+ }
138
+ catch (e) {
139
+ await rollback(input, snapshot);
140
+ throw new ConnectionError("CONNECTION_PERSIST_FAILED", 500, `Failed to persist connections: ${e?.message ?? e}`);
141
+ }
142
+ return { resolved };
143
+ }
144
+ function makeApplyContext(opts) {
145
+ return {
146
+ registry: capabilityRegistry,
147
+ adapter: opts.adapter,
148
+ async writeConnectionEnv(instance, env) {
149
+ if (Object.keys(env).length === 0)
150
+ return;
151
+ if (opts.adapter && typeof opts.adapter.applyConnectionEnv === "function") {
152
+ await opts.adapter.applyConnectionEnv(instance.id, env);
153
+ return;
154
+ }
155
+ // Generic container app — persist into instance.json["connections-env"].
156
+ await opts.saveInstanceJson(instance.id, (cur) => ({
157
+ ...cur,
158
+ "connections-env": {
159
+ ...(cur["connections-env"] ?? {}),
160
+ ...env,
161
+ },
162
+ }));
163
+ },
164
+ };
165
+ }
166
+ /**
167
+ * Test-only export so unit tests can exercise the snapshot/restore path
168
+ * without spinning up the full transactor. Production code should not
169
+ * import these names directly.
170
+ */
171
+ export const __testing__ = {
172
+ snapshotSideEffectFiles: (instanceId) => snapshotSideEffectFiles(instanceId),
173
+ applyFileSnapshots: (snapshots) => applyFileSnapshots(snapshots),
174
+ validateResolvedEndpoints: (resolved) => validateResolvedEndpoints(resolved),
175
+ };
176
+ /**
177
+ * Walk every resolved binding and validate each provider entry's endpoint
178
+ * via `validateCapabilityEndpoint` (§12). Looks the provider spec up
179
+ * either through the app-manager (app-installed providers) or through the
180
+ * legacy synthesizer (instance-backed providers). When neither lookup
181
+ * yields a spec, the entry is left un-validated rather than rejected —
182
+ * the design's protection target is registry tampering after legitimate
183
+ * registration, and there's no spec to compare against in the missing case.
184
+ */
185
+ async function validateResolvedEndpoints(resolved) {
186
+ if (resolved.length === 0)
187
+ return;
188
+ const { validateCapabilityEndpoint, findProviderProvide } = await import("./capability-endpoint-validator.js");
189
+ const { getApp } = await import("./app/app-manager.js");
190
+ const { loadCapabilitySpecForLegacyInstance } = await import("./runtime/migrations.js");
191
+ const legacyInstanceManager = await import("./instance-manager.js");
192
+ for (const binding of resolved) {
193
+ const category = binding.category;
194
+ for (const entry of binding.entries) {
195
+ // Look up the provider spec. Two paths:
196
+ // 1. App-installed provider — `getApp(entry.instanceId).spec`
197
+ // 2. Legacy instance-backed provider — synthesize from agent template
198
+ let providerSpec = null;
199
+ let isLegacySynthetic = false;
200
+ const appRecord = getApp(entry.instanceId);
201
+ if (appRecord) {
202
+ providerSpec = appRecord.spec;
203
+ }
204
+ else {
205
+ const meta = legacyInstanceManager.getInstance(entry.instanceId);
206
+ providerSpec = loadCapabilitySpecForLegacyInstance(meta);
207
+ isLegacySynthetic = !!providerSpec;
208
+ }
209
+ if (!providerSpec) {
210
+ // No spec we can validate against AND no live instance to synthesize
211
+ // one from. The registry entry is orphaned — treat as a hard reject
212
+ // so a tampered registry can't sneak past the validator simply by
213
+ // pointing at an instanceId that no longer exists.
214
+ const { ConnectionError } = await import("./connection-resolver.js");
215
+ throw new ConnectionError("INVALID_CAPABILITY_ENDPOINT", 400, `Provider '${entry.instanceId}' has no installed app or live instance — registry entry is orphaned`, {
216
+ providerInstanceId: entry.instanceId,
217
+ capability: entry.capability,
218
+ reason: "provider-spec-missing",
219
+ });
220
+ }
221
+ const provide = findProviderProvide(providerSpec, entry.capability);
222
+ if (!provide) {
223
+ if (isLegacySynthetic) {
224
+ // Legacy synthetic specs are minimal (id/name/requires/provides
225
+ // only). If the agent template hasn't declared this capability
226
+ // there's nothing to validate against — degrade rather than
227
+ // reject so we don't break working bindings on older instances.
228
+ continue;
229
+ }
230
+ // App-installed provider whose spec doesn't declare this capability.
231
+ // Either the registry was tampered to forge a capability the spec
232
+ // doesn't expose, or the spec drifted out of sync. Either way the
233
+ // safe move is to reject — apply hooks would otherwise consume a
234
+ // registry-supplied host/port/protocol with no spec confirmation.
235
+ const { ConnectionError } = await import("./connection-resolver.js");
236
+ throw new ConnectionError("INVALID_CAPABILITY_ENDPOINT", 400, `Provider '${entry.instanceId}' app spec does not declare capability '${entry.capability}'`, {
237
+ providerInstanceId: entry.instanceId,
238
+ capability: entry.capability,
239
+ reason: "capability-not-in-spec",
240
+ });
241
+ }
242
+ validateCapabilityEndpoint(providerSpec, entry.instanceId, provide, entry, category);
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * Capture raw bytes of files that PERSIST_HOOKS may write through. Used to
248
+ * roll back partial mutations when a later hook throws. Files that did not
249
+ * exist at snapshot time are recorded with `bytes: null` and unlinked on
250
+ * rollback so we don't leave behind half-written state.
251
+ *
252
+ * 覆盖三类 consumer 的所有已知写入路径:
253
+ * - OpenClaw:provider.env / openclaw.json / mcporter.json(V2 + legacy)
254
+ * - Hermes:agent-home/config.yaml + agent-home/.env(V2 + legacy)
255
+ * - Generic app-dir:apps/<id>/instance.json
256
+ *
257
+ * 路径前缀来自 INSTANCES_DIR / APPS_DIR(均读 JISHUSHELL_HOME 环境变量),
258
+ * 不存在的路径记为 bytes:null,回滚时做 unlink 而非写入。
259
+ */
260
+ function snapshotSideEffectFiles(instanceId) {
261
+ const legacyHome = join(INSTANCES_DIR, instanceId);
262
+ const v2Home = join(APPS_DIR, instanceId);
263
+ const candidates = [
264
+ // ── Per-instance secret bag (LLM proxy-upstream) ──
265
+ join(legacyHome, "provider.env"),
266
+ // ── OpenClaw-native config (V2 + legacy layouts) ──
267
+ join(legacyHome, "openclaw-home", ".openclaw", "openclaw.json"),
268
+ join(v2Home, "openclaw-home", ".openclaw", "openclaw.json"),
269
+ // ── MCP server registry consumed by the OpenClaw runtime ──
270
+ join(legacyHome, "openclaw-home", ".openclaw", "workspace", "config", "mcporter.json"),
271
+ join(v2Home, "openclaw-home", ".openclaw", "workspace", "config", "mcporter.json"),
272
+ // ── Hermes adapter writes (V2 layout): config.yaml + .env via
273
+ // applyConnectionEnv. Without these in the snapshot, a partial MCP
274
+ // apply for the search slot leaves a half-written config.yaml when
275
+ // a later LLM hook fails. Path matches `resolveHermesPaths`.
276
+ join(v2Home, "agent-home", "config.yaml"),
277
+ join(v2Home, "agent-home", ".env"),
278
+ // ── Hermes legacy layout (pre-V2 instances) ──
279
+ join(legacyHome, "agent-home", "config.yaml"),
280
+ join(legacyHome, "agent-home", ".env"),
281
+ // ── Generic app-dir consumer (e.g. OpenWebUI) — instance.json
282
+ // holds the persisted `connections-env` map written by the
283
+ // openai-env hook for non-adapter apps. Already covered by
284
+ // `snapshot.instanceJson`, but listed here as an extra safety net
285
+ // so a partial direct write at apps/<id>/instance.json (outside
286
+ // the saveInstanceJson code path) still rolls back. ──
287
+ join(v2Home, "instance.json"),
288
+ ];
289
+ const out = [];
290
+ for (const path of candidates) {
291
+ try {
292
+ out.push({ path, bytes: existsSync(path) ? readFileSync(path) : null });
293
+ }
294
+ catch (e) {
295
+ if (e.code === "ENOENT") {
296
+ // File disappeared between existsSync and readFileSync (TOCTOU) — treat as absent.
297
+ out.push({ path, bytes: null });
298
+ }
299
+ else {
300
+ console.warn(`[connection-transactor] snapshot read failed for ${path}: ${e?.message ?? e}`);
301
+ out.push({ path, bytes: null, readError: true });
302
+ }
303
+ }
304
+ }
305
+ return out;
306
+ }
307
+ /**
308
+ * Restore each file to its snapshotted bytes. Files whose snapshot recorded
309
+ * `bytes: null` (didn't exist pre-apply) are unlinked. Errors are logged
310
+ * per-file so a single failed restore doesn't block the rest.
311
+ */
312
+ function applyFileSnapshots(snapshots) {
313
+ for (const file of snapshots) {
314
+ if (file.readError) {
315
+ console.warn(`[connection-transactor] skipping rollback for ${file.path} (snapshot read had failed)`);
316
+ continue;
317
+ }
318
+ try {
319
+ if (file.bytes === null) {
320
+ if (existsSync(file.path))
321
+ unlinkSync(file.path);
322
+ }
323
+ else {
324
+ try {
325
+ mkdirSync(dirname(file.path), { recursive: true });
326
+ }
327
+ catch { /* dir likely exists */ }
328
+ writeFileSync(file.path, file.bytes);
329
+ }
330
+ }
331
+ catch (e) {
332
+ console.error(`[connection-transactor] rollback file restore failed for ${file.path}: ${e?.message ?? e}`);
333
+ }
334
+ }
335
+ }
336
+ async function rollback(input, snapshot) {
337
+ // Restore side-effect files first so their state is consistent before the
338
+ // instance.json `connections` map is rewound. Failures are logged but not
339
+ // propagated — at this point the caller has already seen a CONNECTION_*
340
+ // error and we want to give every later restore step a chance to run.
341
+ applyFileSnapshots(snapshot.files);
342
+ try {
343
+ // Single read-modify-write that overlays the snapshot on the current
344
+ // instance.json — atomic rollback for the single-file state.
345
+ await input.saveInstanceJson(input.instance.id, () => snapshot.instanceJson);
346
+ }
347
+ catch (e) {
348
+ console.error(`[connection-transactor] rollback failed: ${e?.message ?? e}`);
349
+ }
350
+ }
351
+ //# sourceMappingURL=connection-transactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-transactor.js","sourceRoot":"","sources":["../../src/services/connection-transactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE/E,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEvE,OAAO,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,kBAAkB,MAAM,0BAA0B,CAAC;AAG/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAI7D,MAAM,WAAW,GAAe,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAiC/E,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAsB;IAC3D,qEAAqE;IACrE,sEAAsE;IACtE,kEAAkE;IAClE,0DAA0D;IAC1D,OAAO,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,KAAsB;IACxD,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAE9F,yEAAyE;IACzE,sEAAsE;IACtE,uEAAuE;IACvE,mBAAmB;IACnB,MAAM,SAAS,GAAgB;QAC7B,GAAG,QAAQ;QACX,WAAW,EAAE,cAAc;KAC5B,CAAC;IACF,MAAM,EAAE,QAAQ,EAAE,GAAG,kBAAkB,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IAEpE,yEAAyE;IACzE,oEAAoE;IACpE,sEAAsE;IACtE,+CAA+C;IAC/C,iDAAiD;IACjD,qEAAqE;IACrE,kEAAkE;IAClE,MAAM,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IAE1C,yEAAyE;IACzE,gEAAgE;IAChE,+CAA+C;IAC/C,MAAM,QAAQ,GAAuB;QACnC,YAAY,EAAE,MAAM,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjD,KAAK,EAAE,uBAAuB,CAAC,QAAQ,CAAC,EAAE,CAAC;KAC5C,CAAC;IAEF,yEAAyE;IACzE,sEAAsE;IACtE,yEAAyE;IACzE,wEAAwE;IACxE,sEAAsE;IACtE,uEAAuE;IACvE,qEAAqE;IACrE,4BAA4B;IAC5B,MAAM,mBAAmB,GAAG,CAAC,QAAQ,CAAC,YAAY,EAAE,WAAW,IAAI,EAAE,CAA4B,CAAC;IAClG,MAAM,WAAW,GAA8C,EAAE,CAAC;IAClE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAChE,IAAI,KAAK,IAAI,IAAI;YAAE,SAAS,CAAC,kCAAkC;QAC/D,MAAM,IAAI,GAAI,cAA0C,EAAE,CAAC,IAAI,CAAC,CAAC;QACjE,IAAI,IAAI,IAAI,IAAI;YAAE,SAAS,CAAC,qDAAqD;QACjF,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,GAAG,EAAE,UAAU,IAAI,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7D,MAAM,WAAW,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,8BAA8B,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5E,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,IAAI,WAAW,IAAI,SAAS,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,yEAAyE;IACzE,MAAM,GAAG,GAAG,gBAAgB,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC5D,MAAM,YAAY,GAAyB,EAAE,CAAC;IAC9C,IAAI,CAAC;QACH,uEAAuE;QACvE,wEAAwE;QACxE,qEAAqE;QACrE,oEAAoE;QACpE,oEAAoE;QACpE,sEAAsE;QACtE,mDAAmD;QACnD,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,WAAW,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;YACvC,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACnC,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBAChB,OAAO,CAAC,IAAI,CACV,qCAAqC,QAAQ,IAAI,IAAI,QAAQ,QAAQ,CAAC,EAAE,YAAY,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CACtG,CAAC;YACJ,CAAC;QACH,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,WAAW,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;YACrC,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;oBAAE,SAAS;gBAC5C,MAAM,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;gBACpC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,MAAM,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAChC,IAAI,CAAC,YAAY,eAAe;YAAE,MAAM,CAAC,CAAC;QAC1C,MAAM,IAAI,eAAe,CACvB,yBAAyB,EACzB,GAAG,EACH,4BAA4B,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,EAC7C,EAAE,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAC7C,CAAC;IACJ,CAAC;IAED,yEAAyE;IACzE,sEAAsE;IACtE,8DAA8D;IAC9D,IAAI,CAAC;QACH,MAAM,gBAAgB,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC5C,GAAG,GAAG;YACN,WAAW,EAAE,cAAc;SAC5B,CAAC,CAAC,CAAC;IACN,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,MAAM,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAChC,MAAM,IAAI,eAAe,CACvB,2BAA2B,EAC3B,GAAG,EACH,kCAAkC,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CACpD,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAGzB;IACC,OAAO;QACL,QAAQ,EAAE,kBAAkB;QAC5B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,KAAK,CAAC,kBAAkB,CAAC,QAAQ,EAAE,GAAG;YACpC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAC1C,IAAI,IAAI,CAAC,OAAO,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,UAAU,EAAE,CAAC;gBAC1E,MAAM,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YACD,yEAAyE;YACzE,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACjD,GAAG,GAAG;gBACN,iBAAiB,EAAE;oBACjB,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;oBACjC,GAAG,GAAG;iBACP;aACF,CAAC,CAAC,CAAC;QACN,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,uBAAuB,EAAE,CAAC,UAAkB,EAAE,EAAE,CAAC,uBAAuB,CAAC,UAAU,CAAC;IACpF,kBAAkB,EAAE,CAAC,SAAyB,EAAE,EAAE,CAAC,kBAAkB,CAAC,SAAS,CAAC;IAChF,yBAAyB,EAAE,CAAC,QAA8B,EAAE,EAAE,CAAC,yBAAyB,CAAC,QAAQ,CAAC;CACnG,CAAC;AAEF;;;;;;;;GAQG;AACH,KAAK,UAAU,yBAAyB,CAAC,QAA8B;IACrE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAElC,MAAM,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CACtE,oCAAoC,CACrC,CAAC;IACF,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;IACxD,MAAM,EAAE,mCAAmC,EAAE,GAAG,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC;IACxF,MAAM,qBAAqB,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;IAEpE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAI,OAAO,CAAC,QAA6D,CAAC;QACxF,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpC,wCAAwC;YACxC,gEAAgE;YAChE,wEAAwE;YACxE,IAAI,YAAY,GAAQ,IAAI,CAAC;YAC7B,IAAI,iBAAiB,GAAG,KAAK,CAAC;YAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC3C,IAAI,SAAS,EAAE,CAAC;gBACd,YAAY,GAAG,SAAS,CAAC,IAAI,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,GAAG,qBAAqB,CAAC,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBACjE,YAAY,GAAG,mCAAmC,CAAC,IAAI,CAAC,CAAC;gBACzD,iBAAiB,GAAG,CAAC,CAAC,YAAY,CAAC;YACrC,CAAC;YAED,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,qEAAqE;gBACrE,oEAAoE;gBACpE,kEAAkE;gBAClE,mDAAmD;gBACnD,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;gBACrE,MAAM,IAAI,eAAe,CACvB,6BAA6B,EAC7B,GAAG,EACH,aAAa,KAAK,CAAC,UAAU,sEAAsE,EACnG;oBACE,kBAAkB,EAAE,KAAK,CAAC,UAAU;oBACpC,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,MAAM,EAAE,uBAAuB;iBAChC,CACF,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAG,mBAAmB,CAAC,YAAY,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;YACpE,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,IAAI,iBAAiB,EAAE,CAAC;oBACtB,gEAAgE;oBAChE,+DAA+D;oBAC/D,4DAA4D;oBAC5D,gEAAgE;oBAChE,SAAS;gBACX,CAAC;gBACD,qEAAqE;gBACrE,kEAAkE;gBAClE,kEAAkE;gBAClE,iEAAiE;gBACjE,kEAAkE;gBAClE,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;gBACrE,MAAM,IAAI,eAAe,CACvB,6BAA6B,EAC7B,GAAG,EACH,aAAa,KAAK,CAAC,UAAU,2CAA2C,KAAK,CAAC,UAAU,GAAG,EAC3F;oBACE,kBAAkB,EAAE,KAAK,CAAC,UAAU;oBACpC,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,MAAM,EAAE,wBAAwB;iBACjC,CACF,CAAC;YACJ,CAAC;YACD,0BAA0B,CAAC,YAAY,EAAE,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,uBAAuB,CAAC,UAAkB;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG;QACjB,qDAAqD;QACrD,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC;QAChC,qDAAqD;QACrD,IAAI,CAAC,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,eAAe,CAAC;QAC/D,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,eAAe,CAAC;QAC3D,6DAA6D;QAC7D,IAAI,CAAC,UAAU,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,CAAC;QACtF,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,CAAC;QAClF,+DAA+D;QAC/D,sEAAsE;QACtE,sEAAsE;QACtE,gEAAgE;QAChE,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,aAAa,CAAC;QACzC,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC;QAClC,gDAAgD;QAChD,IAAI,CAAC,UAAU,EAAE,YAAY,EAAE,aAAa,CAAC;QAC7C,IAAI,CAAC,UAAU,EAAE,YAAY,EAAE,MAAM,CAAC;QACtC,+DAA+D;QAC/D,8DAA8D;QAC9D,8DAA8D;QAC9D,qEAAqE;QACrE,mEAAmE;QACnE,0DAA0D;QAC1D,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC;KAC9B,CAAC;IACF,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACnD,mFAAmF;gBACnF,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC,oDAAoD,IAAI,KAAK,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC7F,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,SAAyB;IACnD,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,iDAAiD,IAAI,CAAC,IAAI,6BAA6B,CAAC,CAAC;YACtG,SAAS;QACX,CAAC;QACD,IAAI,CAAC;YACH,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBACxB,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;oBAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnD,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC;oBACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrD,CAAC;gBAAC,MAAM,CAAC,CAAC,uBAAuB,CAAC,CAAC;gBACnC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,4DAA4D,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7G,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,KAAsB,EAAE,QAA4B;IAC1E,0EAA0E;IAC1E,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEnC,IAAI,CAAC;QACH,qEAAqE;QACrE,6DAA6D;QAC7D,MAAM,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC/E,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC"}
@@ -0,0 +1,40 @@
1
+ export interface ExternalMount {
2
+ alias: string;
3
+ host_path: string;
4
+ mode: "ro" | "rw";
5
+ description?: string;
6
+ }
7
+ export interface MountValidationError {
8
+ field: string;
9
+ message: string;
10
+ }
11
+ export interface MountValidationResult {
12
+ ok: boolean;
13
+ errors: MountValidationError[];
14
+ warnings: string[];
15
+ }
16
+ /**
17
+ * Validate a single mount candidate. Returns {ok, errors, warnings};
18
+ * warnings flag sensitive paths (e.g. ~/.ssh) but do NOT block the
19
+ * caller — that call is the user's policy decision.
20
+ */
21
+ export declare function validateMount(m: Partial<ExternalMount>, existing?: ExternalMount[]): MountValidationResult;
22
+ /**
23
+ * Resolve a files/-relative path that MIGHT start with an external
24
+ * mount alias to its true host filesystem path. Returns null when the
25
+ * path doesn't begin with a registered alias — caller falls back to
26
+ * `FILES_ROOT/<rel>`.
27
+ *
28
+ * Symlink defense and path-safety MUST run on the returned absolute
29
+ * path against `mount.host_path` (not FILES_ROOT) — caller's job.
30
+ */
31
+ export declare function resolveAcrossMounts(rel: string, mounts: ExternalMount[]): {
32
+ abs: string;
33
+ mount: ExternalMount;
34
+ remainder: string;
35
+ } | null;
36
+ /**
37
+ * Returns true if the given files/-relative path lands inside a
38
+ * read-only mount (any operation that mutates must reject early).
39
+ */
40
+ export declare function isReadOnlyTarget(rel: string, mounts: ExternalMount[]): boolean;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * External mounts — let any host directory appear as a virtual subtree
3
+ * inside ~/.jishushell/files/ (M3 W4 PR-4).
4
+ *
5
+ * Design choice: virtualize at the FilesManager layer rather than
6
+ * planting a symlink under FILES_ROOT.
7
+ *
8
+ * 1. The W1 symlink-defense rejects ALL symlinks anywhere in the
9
+ * path chain. We don't want to whitelist symlinks generally;
10
+ * the alias check must be code-explicit.
11
+ * 2. Symlinks behave inconsistently across docker bind mounts,
12
+ * WebDAV clients, and macOS APFS firmlinks. A virtual layer
13
+ * sidesteps the entire mess.
14
+ * 3. Listing the FILES_ROOT directory cannot include a symlink
15
+ * that points outside (the listing helper hides them); a
16
+ * virtual entry is just a synthesized DirEntry.
17
+ *
18
+ * The user-visible model:
19
+ * files/ ← real POSIX root
20
+ * ├── notes/ ← real subdirectory
21
+ * ├── photos/ ← real subdirectory
22
+ * └── Documents/ ← VIRTUAL — host_path = /Users/me/Documents
23
+ *
24
+ * From every layer's perspective (panel UI, /api/files/*, WebDAV,
25
+ * Agent drive shim), Documents/ behaves like any other directory.
26
+ * Mode "ro" is enforced uniformly.
27
+ *
28
+ * Sensitive paths flagged by sanity-check (caller can override):
29
+ * /etc, /proc, /sys, /dev, /var/log, ~/.ssh, ~/.aws, ~/.gnupg,
30
+ * anywhere inside JISHUSHELL_HOME (would create recursion)
31
+ */
32
+ import * as fs from "node:fs";
33
+ import * as path from "node:path";
34
+ import { homedir } from "node:os";
35
+ import { JISHUSHELL_HOME, FILES_ROOT } from "../config.js";
36
+ const ALIAS_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,63}$/;
37
+ const DENY_PREFIXES = [
38
+ "/etc",
39
+ "/proc",
40
+ "/sys",
41
+ "/dev",
42
+ "/run",
43
+ "/boot",
44
+ "/var/log",
45
+ "/var/lib",
46
+ ];
47
+ const SUSPICIOUS_HIDDEN = [".ssh", ".aws", ".gnupg", ".config/gh"];
48
+ /**
49
+ * Reserved aliases that cannot be used because they collide with
50
+ * panel-internal directories or W1.5 / W2 conventions.
51
+ */
52
+ const RESERVED_ALIASES = new Set([
53
+ ".trash",
54
+ "agent-data",
55
+ "legacy",
56
+ "skills",
57
+ "memory",
58
+ ".knowledge",
59
+ ]);
60
+ /**
61
+ * Validate a single mount candidate. Returns {ok, errors, warnings};
62
+ * warnings flag sensitive paths (e.g. ~/.ssh) but do NOT block the
63
+ * caller — that call is the user's policy decision.
64
+ */
65
+ export function validateMount(m, existing = []) {
66
+ const errors = [];
67
+ const warnings = [];
68
+ // alias
69
+ if (!m.alias || typeof m.alias !== "string") {
70
+ errors.push({ field: "alias", message: "required string" });
71
+ }
72
+ else if (!ALIAS_RE.test(m.alias)) {
73
+ errors.push({
74
+ field: "alias",
75
+ message: "must match [a-zA-Z0-9_.-]+ (no slashes), <= 64 chars",
76
+ });
77
+ }
78
+ else if (RESERVED_ALIASES.has(m.alias)) {
79
+ errors.push({ field: "alias", message: `"${m.alias}" is a reserved alias` });
80
+ }
81
+ else if (existing.some((e) => e.alias === m.alias)) {
82
+ errors.push({ field: "alias", message: `"${m.alias}" already in use` });
83
+ }
84
+ // mode
85
+ if (m.mode !== "ro" && m.mode !== "rw") {
86
+ errors.push({ field: "mode", message: 'must be "ro" or "rw"' });
87
+ }
88
+ // host_path
89
+ if (!m.host_path || typeof m.host_path !== "string") {
90
+ errors.push({ field: "host_path", message: "required absolute path" });
91
+ }
92
+ else if (!path.isAbsolute(m.host_path)) {
93
+ errors.push({ field: "host_path", message: "must be absolute" });
94
+ }
95
+ else {
96
+ // Resolve any ~ expansion the caller forgot
97
+ const resolved = m.host_path.startsWith("~")
98
+ ? path.join(homedir(), m.host_path.slice(1))
99
+ : path.resolve(m.host_path);
100
+ if (!fs.existsSync(resolved)) {
101
+ errors.push({
102
+ field: "host_path",
103
+ message: `path does not exist: ${resolved}`,
104
+ });
105
+ }
106
+ else {
107
+ try {
108
+ const stat = fs.lstatSync(resolved);
109
+ if (stat.isSymbolicLink()) {
110
+ errors.push({
111
+ field: "host_path",
112
+ message: "must be a real directory, not a symlink",
113
+ });
114
+ }
115
+ else if (!stat.isDirectory()) {
116
+ errors.push({
117
+ field: "host_path",
118
+ message: "must be a directory",
119
+ });
120
+ }
121
+ }
122
+ catch (e) {
123
+ errors.push({
124
+ field: "host_path",
125
+ message: `cannot stat: ${e.message}`,
126
+ });
127
+ }
128
+ // Recursion guard: must not be inside FILES_ROOT or JISHUSHELL_HOME
129
+ if (resolved === FILES_ROOT ||
130
+ resolved.startsWith(FILES_ROOT + path.sep) ||
131
+ resolved === JISHUSHELL_HOME ||
132
+ resolved.startsWith(JISHUSHELL_HOME + path.sep)) {
133
+ errors.push({
134
+ field: "host_path",
135
+ message: "cannot mount a path inside ~/.jishushell — would cause recursion",
136
+ });
137
+ }
138
+ // Sensitive prefix warnings (do not block)
139
+ for (const prefix of DENY_PREFIXES) {
140
+ if (resolved === prefix || resolved.startsWith(prefix + "/")) {
141
+ warnings.push(`host_path is under ${prefix} — system directory, mode=ro is strongly recommended`);
142
+ break;
143
+ }
144
+ }
145
+ for (const hidden of SUSPICIOUS_HIDDEN) {
146
+ if (resolved.includes(`/${hidden}`) || resolved.includes(`\\${hidden}`)) {
147
+ warnings.push(`host_path contains "${hidden}" — likely holds credentials, mode=ro recommended`);
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ return { ok: errors.length === 0, errors, warnings };
154
+ }
155
+ /**
156
+ * Resolve a files/-relative path that MIGHT start with an external
157
+ * mount alias to its true host filesystem path. Returns null when the
158
+ * path doesn't begin with a registered alias — caller falls back to
159
+ * `FILES_ROOT/<rel>`.
160
+ *
161
+ * Symlink defense and path-safety MUST run on the returned absolute
162
+ * path against `mount.host_path` (not FILES_ROOT) — caller's job.
163
+ */
164
+ export function resolveAcrossMounts(rel, mounts) {
165
+ if (!rel)
166
+ return null;
167
+ const slash = rel.indexOf("/");
168
+ const first = slash < 0 ? rel : rel.slice(0, slash);
169
+ const remainder = slash < 0 ? "" : rel.slice(slash + 1);
170
+ const m = mounts.find((x) => x.alias === first);
171
+ if (!m)
172
+ return null;
173
+ const expanded = m.host_path.startsWith("~")
174
+ ? path.join(homedir(), m.host_path.slice(1))
175
+ : path.resolve(m.host_path);
176
+ const abs = remainder ? path.join(expanded, remainder) : expanded;
177
+ return { abs, mount: m, remainder };
178
+ }
179
+ /**
180
+ * Returns true if the given files/-relative path lands inside a
181
+ * read-only mount (any operation that mutates must reject early).
182
+ */
183
+ export function isReadOnlyTarget(rel, mounts) {
184
+ const m = resolveAcrossMounts(rel, mounts);
185
+ return !!(m && m.mount.mode === "ro");
186
+ }
187
+ //# sourceMappingURL=external-mounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-mounts.js","sourceRoot":"","sources":["../../src/services/external-mounts.ts"],"names":[],"mappings":"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"}