jishushell 0.4.30 → 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 (182) hide show
  1. package/apps/anythingllm-container.yaml +287 -0
  2. package/apps/browserless-chromium-container.yaml +18 -6
  3. package/apps/filebrowser-container.yaml +163 -0
  4. package/apps/openclaw-binary.yaml +8 -0
  5. package/apps/openclaw-container.yaml +9 -1
  6. package/apps/openclaw-with-searxng-container.yaml +4 -0
  7. package/apps/searxng-container.yaml +5 -4
  8. package/apps/weknora-container.yaml +471 -0
  9. package/dist/cli/panel.js.map +1 -1
  10. package/dist/config.d.ts +19 -0
  11. package/dist/config.js +99 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/install.js +3 -3
  14. package/dist/install.js.map +1 -1
  15. package/dist/routes/auth.js +2 -2
  16. package/dist/routes/auth.js.map +1 -1
  17. package/dist/routes/backup.js +64 -11
  18. package/dist/routes/backup.js.map +1 -1
  19. package/dist/routes/external-mounts.d.ts +17 -0
  20. package/dist/routes/external-mounts.js +73 -0
  21. package/dist/routes/external-mounts.js.map +1 -0
  22. package/dist/routes/file-mounts.d.ts +13 -0
  23. package/dist/routes/file-mounts.js +90 -0
  24. package/dist/routes/file-mounts.js.map +1 -0
  25. package/dist/routes/files-organize.d.ts +28 -0
  26. package/dist/routes/files-organize.js +167 -0
  27. package/dist/routes/files-organize.js.map +1 -0
  28. package/dist/routes/files.d.ts +31 -0
  29. package/dist/routes/files.js +321 -0
  30. package/dist/routes/files.js.map +1 -0
  31. package/dist/routes/instances.js +45 -7
  32. package/dist/routes/instances.js.map +1 -1
  33. package/dist/routes/internal.d.ts +2 -0
  34. package/dist/routes/internal.js +59 -0
  35. package/dist/routes/internal.js.map +1 -0
  36. package/dist/routes/setup.js +9 -9
  37. package/dist/routes/setup.js.map +1 -1
  38. package/dist/routes/system.js +1 -1
  39. package/dist/routes/system.js.map +1 -1
  40. package/dist/routes/webdav.d.ts +17 -0
  41. package/dist/routes/webdav.js +114 -0
  42. package/dist/routes/webdav.js.map +1 -0
  43. package/dist/server.js +341 -3
  44. package/dist/server.js.map +1 -1
  45. package/dist/services/app/app-compiler.d.ts +1 -1
  46. package/dist/services/app/app-compiler.js +5 -5
  47. package/dist/services/app/app-compiler.js.map +1 -1
  48. package/dist/services/app/app-manager.d.ts +1 -0
  49. package/dist/services/app/app-manager.js +172 -41
  50. package/dist/services/app/app-manager.js.map +1 -1
  51. package/dist/services/app/custom-manager.js.map +1 -1
  52. package/dist/services/app/hermes-agent-manager.js +1 -0
  53. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  54. package/dist/services/app/ollama-manager.js +1 -1
  55. package/dist/services/app/ollama-manager.js.map +1 -1
  56. package/dist/services/app/openclaw-manager.js +20 -3
  57. package/dist/services/app/openclaw-manager.js.map +1 -1
  58. package/dist/services/app/platform-transform.d.ts +32 -0
  59. package/dist/services/app/platform-transform.js +65 -0
  60. package/dist/services/app/platform-transform.js.map +1 -0
  61. package/dist/services/app-passwords.d.ts +61 -0
  62. package/dist/services/app-passwords.js +173 -0
  63. package/dist/services/app-passwords.js.map +1 -0
  64. package/dist/services/backup-manager.d.ts +11 -0
  65. package/dist/services/backup-manager.js +177 -4
  66. package/dist/services/backup-manager.js.map +1 -1
  67. package/dist/services/connection-apply.d.ts +2 -0
  68. package/dist/services/connection-apply.js +55 -1
  69. package/dist/services/connection-apply.js.map +1 -1
  70. package/dist/services/connection-resolver.js +1 -1
  71. package/dist/services/connection-resolver.js.map +1 -1
  72. package/dist/services/connection-transactor.d.ts +2 -0
  73. package/dist/services/connection-transactor.js +12 -2
  74. package/dist/services/connection-transactor.js.map +1 -1
  75. package/dist/services/external-mounts.d.ts +40 -0
  76. package/dist/services/external-mounts.js +187 -0
  77. package/dist/services/external-mounts.js.map +1 -0
  78. package/dist/services/files-manager.d.ts +252 -0
  79. package/dist/services/files-manager.js +1075 -0
  80. package/dist/services/files-manager.js.map +1 -0
  81. package/dist/services/files-mounts.d.ts +42 -0
  82. package/dist/services/files-mounts.js +207 -0
  83. package/dist/services/files-mounts.js.map +1 -0
  84. package/dist/services/instance-manager.js +1 -23
  85. package/dist/services/instance-manager.js.map +1 -1
  86. package/dist/services/llm-proxy/index.js.map +1 -1
  87. package/dist/services/llm-proxy/ssrf.js +6 -2
  88. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  89. package/dist/services/nomad-manager.d.ts +4 -0
  90. package/dist/services/nomad-manager.js +53 -19
  91. package/dist/services/nomad-manager.js.map +1 -1
  92. package/dist/services/organize/applier.d.ts +46 -0
  93. package/dist/services/organize/applier.js +218 -0
  94. package/dist/services/organize/applier.js.map +1 -0
  95. package/dist/services/organize/rules.d.ts +57 -0
  96. package/dist/services/organize/rules.js +286 -0
  97. package/dist/services/organize/rules.js.map +1 -0
  98. package/dist/services/organize/scanner.d.ts +50 -0
  99. package/dist/services/organize/scanner.js +366 -0
  100. package/dist/services/organize/scanner.js.map +1 -0
  101. package/dist/services/organize/store.d.ts +14 -0
  102. package/dist/services/organize/store.js +82 -0
  103. package/dist/services/organize/store.js.map +1 -0
  104. package/dist/services/panel-manager.js +20 -1
  105. package/dist/services/panel-manager.js.map +1 -1
  106. package/dist/services/process-manager.js +3 -2
  107. package/dist/services/process-manager.js.map +1 -1
  108. package/dist/services/runtime/adapters/hermes.js +1 -1
  109. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  110. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  111. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  112. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  113. package/dist/services/runtime/adapters/openclaw.d.ts +90 -0
  114. package/dist/services/runtime/adapters/openclaw.js +957 -45
  115. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  116. package/dist/services/runtime/instance.d.ts +1 -1
  117. package/dist/services/runtime/instance.js +1 -1
  118. package/dist/services/runtime/instance.js.map +1 -1
  119. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  120. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  121. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  122. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  123. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  124. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  125. package/dist/services/runtime/types.d.ts +31 -0
  126. package/dist/services/setup-manager.js +93 -18
  127. package/dist/services/setup-manager.js.map +1 -1
  128. package/dist/services/suggestions.js.map +1 -1
  129. package/dist/services/webdav/server.d.ts +24 -0
  130. package/dist/services/webdav/server.js +420 -0
  131. package/dist/services/webdav/server.js.map +1 -0
  132. package/dist/services/webdav/xml-builder.d.ts +73 -0
  133. package/dist/services/webdav/xml-builder.js +156 -0
  134. package/dist/services/webdav/xml-builder.js.map +1 -0
  135. package/dist/services/workspace-builder.d.ts +29 -0
  136. package/dist/services/workspace-builder.js +188 -0
  137. package/dist/services/workspace-builder.js.map +1 -0
  138. package/dist/types.d.ts +60 -0
  139. package/dist/utils/path-locks.d.ts +30 -0
  140. package/dist/utils/path-locks.js +63 -0
  141. package/dist/utils/path-locks.js.map +1 -0
  142. package/dist/utils/path-safety.d.ts +41 -0
  143. package/dist/utils/path-safety.js +119 -0
  144. package/dist/utils/path-safety.js.map +1 -0
  145. package/dist/utils/safe-write.d.ts +24 -0
  146. package/dist/utils/safe-write.js +82 -0
  147. package/dist/utils/safe-write.js.map +1 -0
  148. package/package.json +16 -1
  149. package/public/assets/Dashboard-BdWPtroF.js +1 -0
  150. package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-B_2HlVBQ.js} +1 -1
  151. package/public/assets/{HermesConfigForm-anDnwUp_.js → HermesConfigForm-DVlhg3WV.js} +2 -2
  152. package/public/assets/{InitPassword-ZU9_-hDr.js → InitPassword-D7glTExX.js} +1 -1
  153. package/public/assets/InstanceDetail-CxSy2cpe.js +92 -0
  154. package/public/assets/{Login-BItXqYAJ.js → Login-Cfr5c2sv.js} +1 -1
  155. package/public/assets/NewInstance-BIYDmJis.js +1 -0
  156. package/public/assets/{ProviderRecommendations-DFYj7Fb6.js → ProviderRecommendations-BuRnvRcI.js} +1 -1
  157. package/public/assets/{Settings-Bttc6QmM.js → Settings-Cc-tYBil.js} +1 -1
  158. package/public/assets/{Setup-Bsxx1zgj.js → Setup-lGZEk5jq.js} +1 -1
  159. package/public/assets/{WeixinLoginPanel-DPZpAKgO.js → WeixinLoginPanel-CoGqzxeV.js} +2 -2
  160. package/public/assets/index-87IJXG-w.css +1 -0
  161. package/public/assets/index-BZc5zH7u.js +19 -0
  162. package/public/assets/{registry-5s2UB6is.js → registry-BWnkJgZ1.js} +2 -2
  163. package/public/assets/{usePolling-Do5Erqm_.js → usePolling-CwwT9KrC.js} +1 -1
  164. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-y9V7Sfuu.js} +1 -1
  165. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-BWrEVJVb.js} +6 -6
  166. package/public/index.html +4 -4
  167. package/scripts/check-app-spec.mjs +18 -4
  168. package/scripts/check-new-file-tests.mjs +230 -0
  169. package/scripts/check-quarantine-expiry.mjs +105 -0
  170. package/scripts/perf/README.md +49 -0
  171. package/scripts/perf/auth.js +99 -0
  172. package/scripts/perf/config.js +63 -0
  173. package/scripts/perf/instances.js +143 -0
  174. package/scripts/perf/proxy.js +96 -0
  175. package/scripts/smoke/files-w1.sh +142 -0
  176. package/scripts/smoke-backend.mjs +122 -0
  177. package/scripts/smoke-post-publish.mjs +346 -0
  178. package/public/assets/Dashboard-rkWp-CXd.js +0 -1
  179. package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
  180. package/public/assets/NewInstance-BousE6kY.js +0 -1
  181. package/public/assets/index-8xZy1z5k.css +0 -1
  182. package/public/assets/index-Dw3HhUYE.js +0 -19
@@ -0,0 +1,489 @@
1
+ /**
2
+ * Drive MCP shim — exposes the panel's file + organize API to the
3
+ * agent inside the OpenClaw container (M1 W1.6).
4
+ *
5
+ * Why a shim, not a direct MCP server in panel:
6
+ * - Panel and agent run in different processes (and often different
7
+ * containers). Stdio MCP is the cheapest contract — the container
8
+ * already runs node, and `mcporter.json` already wires shim
9
+ * stdio servers via `command/args`.
10
+ * - The shim translates JSON-RPC tool calls into HTTP calls back
11
+ * to the panel via `host.docker.internal:8090`, authenticated
12
+ * with the per-host internal token (`X-Jishushell-Internal-Token`)
13
+ * plus the per-instance id (`X-Jishushell-Instance`).
14
+ *
15
+ * Tool surface (matches panel REST one-to-one):
16
+ * drive_list → GET /api/files
17
+ * drive_read_preview → GET /api/files/preview
18
+ * drive_quota → GET /api/files/quota
19
+ * drive_mkdir → POST /api/files/mkdir
20
+ * drive_organize_scan → POST /api/files/organize/scan
21
+ * drive_organize_apply → PUT /api/files/organize/batches/:id/apply
22
+ * drive_organize_revert → POST /api/files/organize/batches/:id/revert
23
+ * drive_search → GET /api/files/search
24
+ * drive_get_meta → GET /api/files/meta
25
+ * drive_set_meta → PUT /api/files/meta
26
+ * drive_reindex → POST /api/files/reindex
27
+ * drive_resolve_local_path → GET /api/files/resolve (v0.4 — IM-plugin bridge)
28
+ * drive_write_binary → PUT /api/files (v0.4 — base64 body)
29
+ *
30
+ * The user-facing chat experience this enables:
31
+ * user: "整理一下 inbox 里的发票"
32
+ * agent → drive_organize_scan({ path: "inbox" })
33
+ * agent → drive_organize_apply({ batch_id, selected_ids: [...] })
34
+ * agent: "✅ 整理了 8 张发票到 finance/2026/05/, 30 秒内可撤销"
35
+ *
36
+ * Stdio framing: NDJSON JSON-RPC 2.0 (MCP standard). Pure Node
37
+ * builtins — no npm install needed inside the runtime image.
38
+ */
39
+ export const DRIVE_MCP_SHIM_VERSION = "0.4.0";
40
+ export const DRIVE_MCP_SHIM_SOURCE = `#!/usr/bin/env node
41
+ // jishushell-drive-shim — generated, do not edit by hand.
42
+ // Source: src/services/runtime/mcp-shims/drive-shim.ts
43
+ import { createInterface } from "node:readline";
44
+
45
+ // Per-instance values are baked in by the adapter at install time via
46
+ // substituteDriveShimPlaceholders(). OpenClaw scrubs env when spawning MCP
47
+ // subprocesses, so the env-based fallback is unreliable in production —
48
+ // the literal substitution path below is the source of truth. Env vars
49
+ // are kept as a backstop for hand-running the shim during development.
50
+ const PANEL_URL_LIT = "__JS_PANEL_URL__";
51
+ const TOKEN_LIT = "__JS_TOKEN__";
52
+ const INSTANCE_ID_LIT = "__JS_INSTANCE_ID__";
53
+ const PANEL_URL = (PANEL_URL_LIT.startsWith("__JS_") ? (process.env.JISHUSHELL_PANEL_URL || "http://host.docker.internal:8090") : PANEL_URL_LIT).replace(/\\/+$/, "");
54
+ const TOKEN = TOKEN_LIT.startsWith("__JS_") ? (process.env.JISHUSHELL_INTERNAL_TOKEN || "") : TOKEN_LIT;
55
+ const INSTANCE_ID = INSTANCE_ID_LIT.startsWith("__JS_") ? (process.env.JISHUSHELL_INSTANCE_ID || "") : INSTANCE_ID_LIT;
56
+
57
+ if (!TOKEN || !INSTANCE_ID) {
58
+ process.stderr.write("[drive-shim] missing JISHUSHELL_INTERNAL_TOKEN or JISHUSHELL_INSTANCE_ID env\\n");
59
+ }
60
+
61
+ function authedHeaders(extra) {
62
+ return Object.assign({
63
+ "X-Jishushell-Internal-Token": TOKEN,
64
+ "X-Jishushell-Instance": INSTANCE_ID,
65
+ }, extra || {});
66
+ }
67
+
68
+ async function panelGet(path, query) {
69
+ const url = new URL(PANEL_URL + path);
70
+ for (const [k, v] of Object.entries(query || {})) {
71
+ if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
72
+ }
73
+ const res = await fetch(url, { headers: authedHeaders() });
74
+ const body = await res.text();
75
+ let parsed;
76
+ try { parsed = body ? JSON.parse(body) : null; } catch { parsed = body; }
77
+ return { status: res.status, body: parsed };
78
+ }
79
+
80
+ async function panelSend(method, path, query, jsonBody) {
81
+ const url = new URL(PANEL_URL + path);
82
+ for (const [k, v] of Object.entries(query || {})) {
83
+ if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
84
+ }
85
+ const res = await fetch(url, {
86
+ method,
87
+ headers: authedHeaders({ "Content-Type": "application/json" }),
88
+ body: jsonBody !== undefined ? JSON.stringify(jsonBody) : undefined,
89
+ });
90
+ const body = await res.text();
91
+ let parsed;
92
+ try { parsed = body ? JSON.parse(body) : null; } catch { parsed = body; }
93
+ return { status: res.status, body: parsed };
94
+ }
95
+
96
+ async function panelPutBinary(path, query, buf) {
97
+ const url = new URL(PANEL_URL + path);
98
+ for (const [k, v] of Object.entries(query || {})) {
99
+ if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
100
+ }
101
+ const res = await fetch(url, {
102
+ method: "PUT",
103
+ headers: authedHeaders({
104
+ "Content-Type": "application/octet-stream",
105
+ "Content-Length": String(buf.length),
106
+ }),
107
+ body: buf,
108
+ });
109
+ const body = await res.text();
110
+ let parsed;
111
+ try { parsed = body ? JSON.parse(body) : null; } catch { parsed = body; }
112
+ return { status: res.status, body: parsed };
113
+ }
114
+
115
+ const TOOLS = [
116
+ {
117
+ name: "drive_list",
118
+ description:
119
+ "List a directory in the user's NAS (~/.jishushell/files/). Returns entries with name, is_dir, size, mtime, mime, etag. These paths are also valid host filesystem paths inside your container — call drive_resolve_local_path to get an absolute path you can hand to native IM channel send_file tools (Feishu / WeChat / ...).",
120
+ inputSchema: {
121
+ type: "object",
122
+ properties: {
123
+ path: { type: "string", description: "directory path relative to files/ root; \\"\\" for root" },
124
+ show_hidden: { type: "boolean" },
125
+ },
126
+ required: [],
127
+ },
128
+ },
129
+ {
130
+ name: "drive_read_preview",
131
+ description:
132
+ "Read a small text/JSON file preview (up to max_kb KB, default 256). Returns content + truncated flag.",
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: {
136
+ path: { type: "string" },
137
+ max_kb: { type: "integer", description: "max kilobytes to read; default 256" },
138
+ },
139
+ required: ["path"],
140
+ },
141
+ },
142
+ {
143
+ name: "drive_quota",
144
+ description: "Get NAS storage quota: quota_mb, used_mb, available_mb, trash_mb.",
145
+ inputSchema: { type: "object", properties: {}, required: [] },
146
+ },
147
+ {
148
+ name: "drive_mkdir",
149
+ description: "Create a new directory in the user's NAS.",
150
+ inputSchema: {
151
+ type: "object",
152
+ properties: { path: { type: "string" } },
153
+ required: ["path"],
154
+ },
155
+ },
156
+ {
157
+ name: "drive_organize_scan",
158
+ description:
159
+ "Scan a directory and propose organize moves based on user-defined rules. Returns batch.id and suggestions[].",
160
+ inputSchema: {
161
+ type: "object",
162
+ properties: {
163
+ path: { type: "string", description: "subdirectory to scan; default scans the entire root" },
164
+ },
165
+ required: [],
166
+ },
167
+ },
168
+ {
169
+ name: "drive_organize_apply",
170
+ description:
171
+ "Apply previously-scanned organize suggestions. selected_ids defaults to all suggestions in the batch. Returns report.applied/skipped/failed and an undo window of 30 seconds.",
172
+ inputSchema: {
173
+ type: "object",
174
+ properties: {
175
+ batch_id: { type: "string" },
176
+ selected_ids: { type: "array", items: { type: "string" } },
177
+ },
178
+ required: ["batch_id"],
179
+ },
180
+ },
181
+ {
182
+ name: "drive_organize_revert",
183
+ description:
184
+ "Revert an applied organize batch. Only valid within the 30-second undo window; returns 410 expired afterwards.",
185
+ inputSchema: {
186
+ type: "object",
187
+ properties: { batch_id: { type: "string" } },
188
+ required: ["batch_id"],
189
+ },
190
+ },
191
+ {
192
+ name: "drive_read_full",
193
+ description:
194
+ "Read a TEXT file in full (up to 1MB). Use for summarizing PDFs/notes/code; for binary files this returns a hint to use the file URL instead. Always check the truncated flag.",
195
+ inputSchema: {
196
+ type: "object",
197
+ properties: {
198
+ path: { type: "string" },
199
+ max_kb: {
200
+ type: "integer",
201
+ description: "max kilobytes (default 1024 = 1MB; hard ceiling 4096)",
202
+ },
203
+ },
204
+ required: ["path"],
205
+ },
206
+ },
207
+ {
208
+ name: "drive_write_text",
209
+ description:
210
+ "Write a text file (overwrites if it exists). Use this when the agent generates a report / summary / draft and wants to persist it. Path is files/-relative; agents typically write under agent-data/{instance}/. To then SEND the file via an IM channel, call drive_resolve_local_path on the same path and pass the abs_path to the channel plugin's send_file tool.",
211
+ inputSchema: {
212
+ type: "object",
213
+ properties: {
214
+ path: { type: "string" },
215
+ content: { type: "string" },
216
+ },
217
+ required: ["path", "content"],
218
+ },
219
+ },
220
+ {
221
+ name: "drive_move",
222
+ description:
223
+ "Move or rename a single file/directory. Both paths are files/-relative. Default does NOT overwrite — pass overwrite=true to replace.",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ from: { type: "string" },
228
+ to: { type: "string" },
229
+ overwrite: { type: "boolean" },
230
+ },
231
+ required: ["from", "to"],
232
+ },
233
+ },
234
+ {
235
+ name: "drive_delete",
236
+ description:
237
+ "Soft-delete a file/directory (moves it to .trash/{date}/, recoverable for 30 days). Files-relative path; do not include leading slash.",
238
+ inputSchema: {
239
+ type: "object",
240
+ properties: { path: { type: "string" } },
241
+ required: ["path"],
242
+ },
243
+ },
244
+ {
245
+ name: "drive_search",
246
+ description: "Search indexed file content via FTS5; returns relative paths + snippets + sha256.",
247
+ inputSchema: {
248
+ type: "object",
249
+ properties: {
250
+ query: { type: "string", description: "FTS5 query string (supports operators: \\"AND\\", \\"OR\\", quoted phrases)." },
251
+ limit: { type: "number", description: "Max hits (default 20, max 100).", minimum: 1, maximum: 100 },
252
+ path: { type: "string", description: "Optional relative-path prefix to scope the search." },
253
+ },
254
+ required: ["query"],
255
+ },
256
+ },
257
+ {
258
+ name: "drive_get_meta",
259
+ description: "Read the sidecar metadata (tags/summary/links/notes) for a file by sha256 or by path. Returns null if no sidecar exists yet.",
260
+ inputSchema: {
261
+ type: "object",
262
+ properties: {
263
+ sha256: { type: "string", pattern: "^[a-fA-F0-9]{64}$", description: "64-hex content hash." },
264
+ path: { type: "string", description: "Relative path under the user file root." },
265
+ },
266
+ oneOf: [{ required: ["sha256"] }, { required: ["path"] }],
267
+ },
268
+ },
269
+ {
270
+ name: "drive_set_meta",
271
+ description: "Write or merge sidecar metadata for a file. payload is free-form JSON; merge=true (default) shallow-merges with the existing sidecar.",
272
+ inputSchema: {
273
+ type: "object",
274
+ properties: {
275
+ sha256: { type: "string", pattern: "^[a-fA-F0-9]{64}$" },
276
+ payload: { type: "object", description: "Sidecar fields to write. Schema: { tags?: string[], summary?: string, links?: [{path, rel}], agent_notes?: string, ... }." },
277
+ merge: { type: "boolean", description: "When true (default), shallow-merge with existing sidecar; when false, replace." },
278
+ },
279
+ required: ["sha256", "payload"],
280
+ },
281
+ },
282
+ {
283
+ name: "drive_reindex",
284
+ description: "Rescan the file tree (or a sub-path) and rebuild FTS5 entries for text/markdown/json/yaml/code files. Explicit trigger only.",
285
+ inputSchema: {
286
+ type: "object",
287
+ properties: {
288
+ path: { type: "string", description: "Optional relative prefix to limit the rescan." },
289
+ },
290
+ },
291
+ },
292
+ {
293
+ name: "drive_resolve_local_path",
294
+ description:
295
+ "Resolve a files/-relative path to its ABSOLUTE host filesystem path. Use this whenever you need to hand a path to a native IM channel plugin (Feishu / WeChat / Telegram / ... send_file tool) — those plugins read bytes from the local filesystem, not via HTTP. The returned abs_path is valid inside your container (panel binds host==container for files mounts) and on the host (raw_exec / process service managers run the agent natively on the host). Returns { abs_path, exists, is_dir?, size?, mtime?, external_mount?, external_mount_mode? }. Check exists before sending; check external_mount_mode='ro' before trying to overwrite.",
296
+ inputSchema: {
297
+ type: "object",
298
+ properties: {
299
+ path: { type: "string", description: "files/-relative path, e.g. \\"reports/q4.pdf\\" or \\"agent-data/<instance>/outbox/summary.pdf\\"." },
300
+ },
301
+ required: ["path"],
302
+ },
303
+ },
304
+ {
305
+ name: "drive_write_binary",
306
+ description:
307
+ "Write a binary file (overwrites if it exists). Use this when the agent generates a PDF / image / audio / archive that needs to be persisted or handed to an IM channel for sending. content_base64 is standard base64 of the file bytes (no data: URL prefix). Path is files/-relative; agents typically write under agent-data/{instance}/outbox/ so the file is clearly outbound. After writing, call drive_resolve_local_path on the same path to get the absolute path to pass to the IM channel's send_file tool. Soft cap: 10 MB raw (~13.5 MB base64) per call.",
308
+ inputSchema: {
309
+ type: "object",
310
+ properties: {
311
+ path: { type: "string" },
312
+ content_base64: { type: "string", description: "Standard base64 (RFC 4648). No data: URL prefix." },
313
+ },
314
+ required: ["path", "content_base64"],
315
+ },
316
+ },
317
+ ];
318
+
319
+ async function callTool(name, args) {
320
+ switch (name) {
321
+ case "drive_list": {
322
+ const r = await panelGet("/api/files", { path: args.path || "", show_hidden: args.show_hidden });
323
+ return r;
324
+ }
325
+ case "drive_read_preview": {
326
+ const r = await panelGet("/api/files/preview", { path: args.path, max_kb: args.max_kb });
327
+ return r;
328
+ }
329
+ case "drive_quota": {
330
+ return panelGet("/api/files/quota");
331
+ }
332
+ case "drive_mkdir": {
333
+ return panelSend("POST", "/api/files/mkdir", null, { path: args.path });
334
+ }
335
+ case "drive_organize_scan": {
336
+ return panelSend("POST", "/api/files/organize/scan", null, { path: args.path || "" });
337
+ }
338
+ case "drive_organize_apply": {
339
+ // If selected_ids is missing or empty, fetch the batch and apply ALL suggestions.
340
+ let selected = Array.isArray(args.selected_ids) ? args.selected_ids : null;
341
+ if (!selected || selected.length === 0) {
342
+ const got = await panelGet("/api/files/organize/batches/" + encodeURIComponent(args.batch_id));
343
+ if (got.status === 200 && got.body && Array.isArray(got.body.suggestions)) {
344
+ selected = got.body.suggestions.map((s) => s.id);
345
+ } else {
346
+ return got;
347
+ }
348
+ }
349
+ return panelSend("PUT", "/api/files/organize/batches/" + encodeURIComponent(args.batch_id) + "/apply", null, { selected_ids: selected });
350
+ }
351
+ case "drive_organize_revert": {
352
+ return panelSend("POST", "/api/files/organize/batches/" + encodeURIComponent(args.batch_id) + "/revert", null);
353
+ }
354
+ case "drive_read_full": {
355
+ const ceiling = 4096;
356
+ const wanted = Math.min(Number.isFinite(+args.max_kb) ? +args.max_kb : 1024, ceiling);
357
+ return panelGet("/api/files/preview", { path: args.path, max_kb: wanted });
358
+ }
359
+ case "drive_write_text": {
360
+ const text = String(args.content == null ? "" : args.content);
361
+ const buf = Buffer.from(text, "utf8");
362
+ return panelPutBinary("/api/files", { path: args.path, overwrite: "true", client_mime: "text/plain" }, buf);
363
+ }
364
+ case "drive_move": {
365
+ const overwrite = args.overwrite === true;
366
+ return panelSend("POST", "/api/files/move", null, {
367
+ from: args.from,
368
+ to: args.to,
369
+ overwrite,
370
+ });
371
+ }
372
+ case "drive_delete": {
373
+ return panelSend("DELETE", "/api/files", { path: args.path }, undefined);
374
+ }
375
+ case "drive_search": {
376
+ const qs = new URLSearchParams({ q: args.query });
377
+ if (args.limit != null) qs.set("limit", String(args.limit));
378
+ if (args.path) qs.set("path", args.path);
379
+ return panelGet("/api/files/search?" + qs.toString(), null);
380
+ }
381
+ case "drive_get_meta": {
382
+ const qs = new URLSearchParams();
383
+ if (args.sha256) qs.set("sha", args.sha256);
384
+ else if (args.path) qs.set("path", args.path);
385
+ return panelGet("/api/files/meta?" + qs.toString(), null);
386
+ }
387
+ case "drive_set_meta": {
388
+ const qs = new URLSearchParams({ sha: args.sha256 });
389
+ if (args.merge === false) qs.set("merge", "false");
390
+ return panelSend("PUT", "/api/files/meta?" + qs.toString(), null, args.payload);
391
+ }
392
+ case "drive_reindex": {
393
+ const qs = args.path ? "?path=" + encodeURIComponent(args.path) : "";
394
+ return panelSend("POST", "/api/files/reindex" + qs, null, undefined);
395
+ }
396
+ case "drive_resolve_local_path": {
397
+ return panelGet("/api/files/resolve", { path: args.path });
398
+ }
399
+ case "drive_write_binary": {
400
+ const b64 = String(args.content_base64 == null ? "" : args.content_base64);
401
+ if (!b64) {
402
+ return { status: 400, body: { detail: "content_base64 required" } };
403
+ }
404
+ let buf;
405
+ try { buf = Buffer.from(b64, "base64"); } catch (e) {
406
+ return { status: 400, body: { detail: "content_base64 not valid base64" } };
407
+ }
408
+ // Soft cap so MCP stdio framing (NDJSON) stays healthy. Bigger files
409
+ // should be staged via Filebrowser / WebDAV directly into files/.
410
+ if (buf.length > 10 * 1024 * 1024) {
411
+ return { status: 413, body: { detail: "content_base64 exceeds 10MB soft cap; split into smaller writes or stage the file via Filebrowser/WebDAV" } };
412
+ }
413
+ return panelPutBinary("/api/files", { path: args.path, overwrite: "true" }, buf);
414
+ }
415
+ default:
416
+ throw new Error("unknown tool: " + name);
417
+ }
418
+ }
419
+
420
+ function send(msg) {
421
+ process.stdout.write(JSON.stringify(msg) + "\\n");
422
+ }
423
+
424
+ function ok(id, result) { send({ jsonrpc: "2.0", id, result }); }
425
+ function err(id, code, message, data) {
426
+ send({ jsonrpc: "2.0", id, error: { code, message, data } });
427
+ }
428
+
429
+ async function dispatch(msg) {
430
+ if (msg.method === "initialize") {
431
+ return ok(msg.id, {
432
+ protocolVersion: "2024-11-05",
433
+ capabilities: { tools: {} },
434
+ serverInfo: { name: "jishushell-drive-shim", version: ${JSON.stringify(DRIVE_MCP_SHIM_VERSION)} },
435
+ });
436
+ }
437
+ if (msg.method === "tools/list") {
438
+ return ok(msg.id, { tools: TOOLS });
439
+ }
440
+ if (msg.method === "tools/call") {
441
+ const name = msg.params && msg.params.name;
442
+ const args = (msg.params && msg.params.arguments) || {};
443
+ try {
444
+ const r = await callTool(name, args);
445
+ const text = r.status >= 200 && r.status < 300
446
+ ? "ok " + r.status + ": " + JSON.stringify(r.body)
447
+ : "error " + r.status + ": " + JSON.stringify(r.body);
448
+ return ok(msg.id, {
449
+ content: [{ type: "text", text: text }],
450
+ isError: !(r.status >= 200 && r.status < 300),
451
+ });
452
+ } catch (e) {
453
+ return err(msg.id, -32000, "tool execution failed", { error: String(e && e.message || e) });
454
+ }
455
+ }
456
+ if (msg.method === "ping") return ok(msg.id, {});
457
+ if (msg.method && msg.method.startsWith("notifications/")) return; // no reply
458
+ if (msg.id !== undefined) err(msg.id, -32601, "method not found: " + msg.method);
459
+ }
460
+
461
+ const rl = createInterface({ input: process.stdin, terminal: false });
462
+ rl.on("line", (line) => {
463
+ const trimmed = line.trim();
464
+ if (!trimmed) return;
465
+ let msg;
466
+ try { msg = JSON.parse(trimmed); } catch { return; }
467
+ Promise.resolve(dispatch(msg)).catch((e) => {
468
+ process.stderr.write("[drive-shim] dispatch crashed: " + (e && e.stack || e) + "\\n");
469
+ });
470
+ });
471
+ process.stdin.on("end", () => process.exit(0));
472
+ `;
473
+ /**
474
+ * Bake per-instance values into the shim template. Returns the source to
475
+ * write to disk. Must be called before writing because OpenClaw scrubs
476
+ * env when spawning MCP subprocesses, so the env-based fallback in the
477
+ * template is unreliable in production.
478
+ *
479
+ * Placeholders use non-JS-identifier marker `__JS_*__` so they grep cleanly
480
+ * and never collide with real code paths.
481
+ */
482
+ export function substituteDriveShimPlaceholders(params) {
483
+ const escapeStr = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
484
+ return DRIVE_MCP_SHIM_SOURCE
485
+ .replace(/__JS_PANEL_URL__/g, escapeStr(params.panelUrl))
486
+ .replace(/__JS_TOKEN__/g, escapeStr(params.token))
487
+ .replace(/__JS_INSTANCE_ID__/g, escapeStr(params.instanceId));
488
+ }
489
+ //# sourceMappingURL=drive-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drive-shim.js","sourceRoot":"","sources":["../../../../src/services/runtime/mcp-shims/drive-shim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAE9C,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8DA0YyB,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCnG,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,+BAA+B,CAC7C,MAA+D;IAE/D,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/E,OAAO,qBAAqB;SACzB,OAAO,CAAC,mBAAmB,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;SACxD,OAAO,CAAC,eAAe,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SACjD,OAAO,CAAC,qBAAqB,EAAE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;AAClE,CAAC"}
@@ -55,6 +55,37 @@ export interface RuntimeSpec {
55
55
  ports?: RuntimePortSpec[];
56
56
  volumes?: RuntimeVolumeSpec[];
57
57
  health?: RuntimeHealthSpec | null;
58
+ /**
59
+ * Per-instance mounts of subtrees from `~/.jishushell/files/`.
60
+ *
61
+ * Each entry exposes one user file-tree subdirectory inside the
62
+ * container under a stable alias, optionally read-only.
63
+ *
64
+ * - Docker mode: enforced by Linux mount options (ro is real)
65
+ * - raw_exec / process mode: configuration only — the agent runs as
66
+ * the panel user and can ignore the ro flag (see vision G8)
67
+ *
68
+ * The structural defaults and built-in `agent-data/{id}` mount are
69
+ * applied by `services/instance-manager.ts` at create time.
70
+ */
71
+ fileMounts?: FileMount[];
72
+ }
73
+ /**
74
+ * One subtree of `~/.jishushell/files/` exposed to a single instance.
75
+ *
76
+ * - `path` is files/-relative; validated by path-safety
77
+ * - `mode` is the mount mode (ro/rw)
78
+ * - `alias` controls how the mount appears inside the container's
79
+ * workspace:
80
+ * - non-empty string → workspace/{alias} symlink to this subtree
81
+ * - "" or null → workspace IS this subtree (root mount;
82
+ * exclusive — at most one per instance)
83
+ * - undefined → defaults to basename(path)
84
+ */
85
+ export interface FileMount {
86
+ path: string;
87
+ mode: "ro" | "rw";
88
+ alias?: string | null;
58
89
  }
59
90
  export interface CapabilityProfile {
60
91
  gateway: {