jishushell 0.4.30 → 0.5.22

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 (226) hide show
  1. package/Dockerfile.hermes-slim +2 -5
  2. package/apps/anythingllm-container.yaml +287 -0
  3. package/apps/browserless-chromium-container.yaml +18 -6
  4. package/apps/filebrowser-container.yaml +164 -0
  5. package/apps/ollama-binary.yaml +44 -0
  6. package/apps/ollama-with-hollama-binary.yaml +45 -1
  7. package/apps/openclaw-binary.yaml +8 -0
  8. package/apps/openclaw-container.yaml +9 -1
  9. package/apps/openclaw-with-searxng-container.yaml +4 -0
  10. package/apps/searxng-container.yaml +5 -4
  11. package/apps/weknora-container.yaml +471 -0
  12. package/dist/cli/doctor.js +144 -16
  13. package/dist/cli/doctor.js.map +1 -1
  14. package/dist/cli/panel.js.map +1 -1
  15. package/dist/config.d.ts +19 -0
  16. package/dist/config.js +99 -1
  17. package/dist/config.js.map +1 -1
  18. package/dist/install.js +4 -4
  19. package/dist/install.js.map +1 -1
  20. package/dist/routes/auth.js +2 -2
  21. package/dist/routes/auth.js.map +1 -1
  22. package/dist/routes/backup.js +64 -11
  23. package/dist/routes/backup.js.map +1 -1
  24. package/dist/routes/external-mounts.d.ts +17 -0
  25. package/dist/routes/external-mounts.js +73 -0
  26. package/dist/routes/external-mounts.js.map +1 -0
  27. package/dist/routes/file-mounts.d.ts +13 -0
  28. package/dist/routes/file-mounts.js +90 -0
  29. package/dist/routes/file-mounts.js.map +1 -0
  30. package/dist/routes/files-organize.d.ts +28 -0
  31. package/dist/routes/files-organize.js +167 -0
  32. package/dist/routes/files-organize.js.map +1 -0
  33. package/dist/routes/files.d.ts +31 -0
  34. package/dist/routes/files.js +321 -0
  35. package/dist/routes/files.js.map +1 -0
  36. package/dist/routes/instances.js +87 -12
  37. package/dist/routes/instances.js.map +1 -1
  38. package/dist/routes/internal.d.ts +2 -0
  39. package/dist/routes/internal.js +59 -0
  40. package/dist/routes/internal.js.map +1 -0
  41. package/dist/routes/llm.js +29 -0
  42. package/dist/routes/llm.js.map +1 -1
  43. package/dist/routes/setup.js +9 -9
  44. package/dist/routes/setup.js.map +1 -1
  45. package/dist/routes/system.js +1 -1
  46. package/dist/routes/system.js.map +1 -1
  47. package/dist/routes/webdav.d.ts +17 -0
  48. package/dist/routes/webdav.js +114 -0
  49. package/dist/routes/webdav.js.map +1 -0
  50. package/dist/server.js +358 -6
  51. package/dist/server.js.map +1 -1
  52. package/dist/services/agent-apps/catalog.d.ts +3 -0
  53. package/dist/services/agent-apps/catalog.js +40 -13
  54. package/dist/services/agent-apps/catalog.js.map +1 -1
  55. package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
  56. package/dist/services/agent-apps/installers/shell-script.js +19 -2
  57. package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
  58. package/dist/services/agent-apps/types.d.ts +3 -0
  59. package/dist/services/app/app-compiler.d.ts +1 -1
  60. package/dist/services/app/app-compiler.js +5 -5
  61. package/dist/services/app/app-compiler.js.map +1 -1
  62. package/dist/services/app/app-manager.d.ts +9 -0
  63. package/dist/services/app/app-manager.js +248 -43
  64. package/dist/services/app/app-manager.js.map +1 -1
  65. package/dist/services/app/custom-manager.js.map +1 -1
  66. package/dist/services/app/hermes-agent-manager.js +1 -0
  67. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  68. package/dist/services/app/ollama-manager.js +1 -1
  69. package/dist/services/app/ollama-manager.js.map +1 -1
  70. package/dist/services/app/openclaw-manager.js +37 -5
  71. package/dist/services/app/openclaw-manager.js.map +1 -1
  72. package/dist/services/app/platform-transform.d.ts +32 -0
  73. package/dist/services/app/platform-transform.js +65 -0
  74. package/dist/services/app/platform-transform.js.map +1 -0
  75. package/dist/services/app-passwords.d.ts +61 -0
  76. package/dist/services/app-passwords.js +173 -0
  77. package/dist/services/app-passwords.js.map +1 -0
  78. package/dist/services/backup-manager.d.ts +11 -0
  79. package/dist/services/backup-manager.js +220 -8
  80. package/dist/services/backup-manager.js.map +1 -1
  81. package/dist/services/capability-endpoint-validator.js +26 -7
  82. package/dist/services/capability-endpoint-validator.js.map +1 -1
  83. package/dist/services/connection-apply.d.ts +2 -0
  84. package/dist/services/connection-apply.js +55 -1
  85. package/dist/services/connection-apply.js.map +1 -1
  86. package/dist/services/connection-resolver.js +1 -1
  87. package/dist/services/connection-resolver.js.map +1 -1
  88. package/dist/services/connection-transactor.d.ts +2 -0
  89. package/dist/services/connection-transactor.js +12 -2
  90. package/dist/services/connection-transactor.js.map +1 -1
  91. package/dist/services/external-mounts.d.ts +40 -0
  92. package/dist/services/external-mounts.js +187 -0
  93. package/dist/services/external-mounts.js.map +1 -0
  94. package/dist/services/files-manager.d.ts +252 -0
  95. package/dist/services/files-manager.js +1075 -0
  96. package/dist/services/files-manager.js.map +1 -0
  97. package/dist/services/files-mounts.d.ts +42 -0
  98. package/dist/services/files-mounts.js +207 -0
  99. package/dist/services/files-mounts.js.map +1 -0
  100. package/dist/services/instance-manager.js +90 -32
  101. package/dist/services/instance-manager.js.map +1 -1
  102. package/dist/services/llm-proxy/index.d.ts +28 -0
  103. package/dist/services/llm-proxy/index.js +76 -3
  104. package/dist/services/llm-proxy/index.js.map +1 -1
  105. package/dist/services/llm-proxy/ssrf.js +6 -2
  106. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  107. package/dist/services/llm-proxy/validate-key.d.ts +41 -0
  108. package/dist/services/llm-proxy/validate-key.js +672 -0
  109. package/dist/services/llm-proxy/validate-key.js.map +1 -0
  110. package/dist/services/macos-launchd.d.ts +89 -0
  111. package/dist/services/macos-launchd.js +273 -0
  112. package/dist/services/macos-launchd.js.map +1 -0
  113. package/dist/services/nomad-manager.d.ts +11 -0
  114. package/dist/services/nomad-manager.js +343 -98
  115. package/dist/services/nomad-manager.js.map +1 -1
  116. package/dist/services/organize/applier.d.ts +46 -0
  117. package/dist/services/organize/applier.js +218 -0
  118. package/dist/services/organize/applier.js.map +1 -0
  119. package/dist/services/organize/rules.d.ts +57 -0
  120. package/dist/services/organize/rules.js +286 -0
  121. package/dist/services/organize/rules.js.map +1 -0
  122. package/dist/services/organize/scanner.d.ts +50 -0
  123. package/dist/services/organize/scanner.js +366 -0
  124. package/dist/services/organize/scanner.js.map +1 -0
  125. package/dist/services/organize/store.d.ts +14 -0
  126. package/dist/services/organize/store.js +82 -0
  127. package/dist/services/organize/store.js.map +1 -0
  128. package/dist/services/panel-manager.js +40 -11
  129. package/dist/services/panel-manager.js.map +1 -1
  130. package/dist/services/process-manager.js +3 -2
  131. package/dist/services/process-manager.js.map +1 -1
  132. package/dist/services/runtime/adapters/custom.js +56 -0
  133. package/dist/services/runtime/adapters/custom.js.map +1 -1
  134. package/dist/services/runtime/adapters/hermes.d.ts +4 -3
  135. package/dist/services/runtime/adapters/hermes.js +166 -64
  136. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  137. package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
  138. package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
  139. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
  140. package/dist/services/runtime/adapters/openclaw.d.ts +118 -0
  141. package/dist/services/runtime/adapters/openclaw.js +1459 -49
  142. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  143. package/dist/services/runtime/instance.d.ts +1 -1
  144. package/dist/services/runtime/instance.js +1 -1
  145. package/dist/services/runtime/instance.js.map +1 -1
  146. package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
  147. package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
  148. package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
  149. package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
  150. package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
  151. package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
  152. package/dist/services/runtime/types.d.ts +31 -0
  153. package/dist/services/setup-manager.js +190 -68
  154. package/dist/services/setup-manager.js.map +1 -1
  155. package/dist/services/suggestions.js.map +1 -1
  156. package/dist/services/update-manager.js +32 -14
  157. package/dist/services/update-manager.js.map +1 -1
  158. package/dist/services/webdav/server.d.ts +24 -0
  159. package/dist/services/webdav/server.js +420 -0
  160. package/dist/services/webdav/server.js.map +1 -0
  161. package/dist/services/webdav/xml-builder.d.ts +73 -0
  162. package/dist/services/webdav/xml-builder.js +156 -0
  163. package/dist/services/webdav/xml-builder.js.map +1 -0
  164. package/dist/services/workspace-builder.d.ts +29 -0
  165. package/dist/services/workspace-builder.js +188 -0
  166. package/dist/services/workspace-builder.js.map +1 -0
  167. package/dist/types.d.ts +61 -0
  168. package/dist/utils/path-locks.d.ts +30 -0
  169. package/dist/utils/path-locks.js +63 -0
  170. package/dist/utils/path-locks.js.map +1 -0
  171. package/dist/utils/path-safety.d.ts +41 -0
  172. package/dist/utils/path-safety.js +119 -0
  173. package/dist/utils/path-safety.js.map +1 -0
  174. package/dist/utils/safe-write.d.ts +24 -0
  175. package/dist/utils/safe-write.js +82 -0
  176. package/dist/utils/safe-write.js.map +1 -0
  177. package/install/jishu-install.sh +247 -35
  178. package/install/jishu-uninstall.sh +45 -5
  179. package/package.json +20 -2
  180. package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
  181. package/public/assets/Dashboard-AuJESBlJ.js +1 -0
  182. package/public/assets/{HermesChatPanel-_GHoklgo.js → HermesChatPanel-CByPREwb.js} +1 -1
  183. package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
  184. package/public/assets/InitPassword-ka4wNpM5.js +1 -0
  185. package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
  186. package/public/assets/Login-aPajuQzf.js +1 -0
  187. package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
  188. package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
  189. package/public/assets/Settings-BYQnbLYL.js +1 -0
  190. package/public/assets/Setup-D05lwDOV.js +1 -0
  191. package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
  192. package/public/assets/index-HSXCsceK.css +1 -0
  193. package/public/assets/index-bnBu0nlQ.js +19 -0
  194. package/public/assets/registry-C_qeFTkZ.js +2 -0
  195. package/public/assets/usePolling-Bn93fe7M.js +1 -0
  196. package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-flxcMVeP.js} +2 -2
  197. package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-ZC5T_huj.js} +7 -7
  198. package/public/index.html +4 -4
  199. package/scripts/check-app-spec.mjs +18 -4
  200. package/scripts/check-colima-launchd.mjs +230 -0
  201. package/scripts/check-new-file-tests.mjs +230 -0
  202. package/scripts/check-quarantine-expiry.mjs +105 -0
  203. package/scripts/perf/README.md +49 -0
  204. package/scripts/perf/auth.js +99 -0
  205. package/scripts/perf/config.js +63 -0
  206. package/scripts/perf/instances.js +143 -0
  207. package/scripts/perf/proxy.js +96 -0
  208. package/scripts/smoke/files-w1.sh +142 -0
  209. package/scripts/smoke-backend.mjs +122 -0
  210. package/scripts/smoke-post-publish.mjs +346 -0
  211. package/public/assets/Dashboard-rkWp-CXd.js +0 -1
  212. package/public/assets/HermesConfigForm-anDnwUp_.js +0 -4
  213. package/public/assets/InitPassword-ZU9_-hDr.js +0 -1
  214. package/public/assets/InstanceDetail-CN0FH1aw.js +0 -92
  215. package/public/assets/Login-BItXqYAJ.js +0 -1
  216. package/public/assets/NewInstance-BousE6kY.js +0 -1
  217. package/public/assets/ProviderRecommendations-DFYj7Fb6.js +0 -1
  218. package/public/assets/Settings-Bttc6QmM.js +0 -1
  219. package/public/assets/Setup-Bsxx1zgj.js +0 -1
  220. package/public/assets/WeixinLoginPanel-DPZpAKgO.js +0 -9
  221. package/public/assets/index-8xZy1z5k.css +0 -1
  222. package/public/assets/index-Dw3HhUYE.js +0 -19
  223. package/public/assets/input-paste-CrNVAyOy.js +0 -1
  224. package/public/assets/providers-DtNXh9JD.js +0 -1
  225. package/public/assets/registry-5s2UB6is.js +0 -2
  226. package/public/assets/usePolling-Do5Erqm_.js +0 -1
@@ -0,0 +1,31 @@
1
+ /**
2
+ * /api/files routes (M1 W1 PR-4).
3
+ *
4
+ * Mounted as a Fastify plugin so that the route-scoped octet-stream parser
5
+ * does not leak globally. The parser transparently passes the raw payload
6
+ * stream to handlers (no buffering), letting `FilesManager.writeStream`
7
+ * pipe straight to disk.
8
+ *
9
+ * Endpoints:
10
+ * GET /api/files list directory
11
+ * GET /api/files/raw stream file content
12
+ * GET /api/files/preview small text/image read (truncated)
13
+ * GET /api/files/quota quota info
14
+ * PUT /api/files upload (octet-stream only)
15
+ * POST /api/files/mkdir create directory
16
+ * POST /api/files/move rename / move
17
+ * DELETE /api/files soft-delete to .trash/
18
+ * GET /api/files/search full-text search index
19
+ * GET /api/files/meta read per-file sidecar metadata
20
+ * PUT /api/files/meta write/merge per-file sidecar metadata
21
+ * POST /api/files/reindex rebuild search index
22
+ * GET /api/files/resolve relative path → absolute host path
23
+ *
24
+ * Auth: covered by the global onRequest hook in server.ts.
25
+ */
26
+ import type { FastifyInstance } from "fastify";
27
+ import { FilesManager } from "../services/files-manager.js";
28
+ export interface FilesRoutesDeps {
29
+ filesManager: FilesManager;
30
+ }
31
+ export declare function filesRoutes(app: FastifyInstance, deps: FilesRoutesDeps): Promise<void>;
@@ -0,0 +1,321 @@
1
+ import { FilesError, } from "../services/files-manager.js";
2
+ const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // matches FilesManager default
3
+ export async function filesRoutes(app, deps) {
4
+ const { filesManager } = deps;
5
+ // ── Route-scoped parser ───────────────────────────
6
+ // Pass-through: hand the raw payload stream straight to the handler.
7
+ // Do NOT use parseAs:'buffer' — that would read the whole body into memory.
8
+ app.addContentTypeParser("application/octet-stream", (_req, payload, done) => done(null, payload));
9
+ // W2 PR-3: /api/files/* is the panel-internal substrate for the drive_*
10
+ // MCP shim only. Browser users now manage files via the Filebrowser app
11
+ // mounted at /apps/filebrowser/*. Reject any request that didn't pass
12
+ // the X-Jishushell-Internal-Token check in server.ts auth middleware.
13
+ // Return 404 rather than 401 so unauthenticated probes can't fingerprint
14
+ // the surface.
15
+ app.addHook("onRequest", async (request, reply) => {
16
+ if (!request.internalCallerInstance) {
17
+ return reply.status(404).send();
18
+ }
19
+ });
20
+ // ── GET /api/files (list) ─────────────────────────
21
+ app.get("/api/files", async (req, reply) => {
22
+ const path = decodeOrEmpty(req.query.path);
23
+ const showHidden = req.query.show_hidden === "true";
24
+ try {
25
+ const result = await filesManager.list(path, { showHidden });
26
+ reply.send(result);
27
+ }
28
+ catch (e) {
29
+ sendFilesError(reply, e);
30
+ }
31
+ });
32
+ // ── GET /api/files/raw ────────────────────────────
33
+ app.get("/api/files/raw", async (req, reply) => {
34
+ const path = decodeOrEmpty(req.query.path);
35
+ try {
36
+ // readStream returns a lazy stream factory — no fd opened yet
37
+ const r = await filesManager.readStream(path);
38
+ // Conditional GET (weak ETag) — answer without opening the file
39
+ const ifNoneMatch = req.headers["if-none-match"];
40
+ if (typeof ifNoneMatch === "string" && weakETagMatch(ifNoneMatch, r.etag)) {
41
+ reply.code(304);
42
+ reply.header("ETag", r.etag);
43
+ return reply.send();
44
+ }
45
+ reply.header("Content-Type", r.mime);
46
+ reply.header("Content-Length", String(r.size));
47
+ reply.header("ETag", r.etag);
48
+ reply.header("Last-Modified", new Date(r.mtime * 1000).toUTCString());
49
+ reply.header("Cache-Control", "private, max-age=0");
50
+ reply.header("Content-Disposition", `inline; filename*=UTF-8''${encodeRfc5987(basename(path))}`);
51
+ // Open the fd only now, just before piping
52
+ return reply.send(r.openStream());
53
+ }
54
+ catch (e) {
55
+ sendFilesError(reply, e);
56
+ }
57
+ });
58
+ // ── GET /api/files/preview ────────────────────────
59
+ app.get("/api/files/preview", async (req, reply) => {
60
+ const path = decodeOrEmpty(req.query.path);
61
+ const maxKb = parsePositiveInt(req.query.max_kb, 256) ?? 256;
62
+ const maxBytes = maxKb * 1024;
63
+ try {
64
+ const r = await filesManager.readSmall(path, maxBytes);
65
+ // Response is a JSON envelope; the underlying file's mime is in the body.
66
+ reply.header("ETag", r.etag);
67
+ reply.send({
68
+ path,
69
+ mime: r.mime,
70
+ size: r.size,
71
+ truncated: r.truncated,
72
+ content: r.buf.toString("utf8"),
73
+ });
74
+ }
75
+ catch (e) {
76
+ sendFilesError(reply, e);
77
+ }
78
+ });
79
+ // ── GET /api/files/quota ──────────────────────────
80
+ app.get("/api/files/quota", async (_req, reply) => {
81
+ try {
82
+ const q = await filesManager.quota();
83
+ reply.send(q);
84
+ }
85
+ catch (e) {
86
+ sendFilesError(reply, e);
87
+ }
88
+ });
89
+ // ── PUT /api/files (upload) ───────────────────────
90
+ app.put("/api/files", { bodyLimit: MAX_UPLOAD_BYTES }, async (req, reply) => {
91
+ // Reject non-octet-stream up front for a clean 415
92
+ const ct = (req.headers["content-type"] || "")
93
+ .split(";")[0]
94
+ .trim()
95
+ .toLowerCase();
96
+ if (ct !== "application/octet-stream") {
97
+ return reply
98
+ .code(415)
99
+ .send({
100
+ error: "Content-Type must be application/octet-stream",
101
+ code: "unsupported-media-type",
102
+ });
103
+ }
104
+ const path = decodeOrEmpty(req.query.path);
105
+ const overwrite = req.query.overwrite === "true";
106
+ const ifMatch = req.headers["if-match"];
107
+ // Content-Length is mandatory: chunked uploads (no length) would let
108
+ // a client stream gigabytes before we hit the in-flight size guard,
109
+ // and the resulting "write past limit then soft-delete" path is
110
+ // dangerous when disk is near-full. Reject up front per spec.
111
+ const cl = req.headers["content-length"];
112
+ const clNum = typeof cl === "string" ? Number.parseInt(cl, 10) : Number.NaN;
113
+ if (!Number.isFinite(clNum) || clNum < 0) {
114
+ return reply.code(411).send({
115
+ error: "Content-Length header is required",
116
+ code: "length-required",
117
+ });
118
+ }
119
+ if (clNum > MAX_UPLOAD_BYTES) {
120
+ return reply.code(413).send({
121
+ error: `Content-Length ${clNum} exceeds max ${MAX_UPLOAD_BYTES}`,
122
+ code: "too-large",
123
+ });
124
+ }
125
+ // The route-scoped parser passes payload as the request body (Readable)
126
+ const body = req.body;
127
+ if (!body) {
128
+ return reply
129
+ .code(400)
130
+ .send({ error: "missing request body", code: "no-body" });
131
+ }
132
+ try {
133
+ const result = await filesManager.writeStream(path, body, {
134
+ overwrite,
135
+ ifMatch: typeof ifMatch === "string" ? ifMatch : undefined,
136
+ expectedSize: clNum,
137
+ });
138
+ reply.code(overwrite ? 200 : 201).send({
139
+ path,
140
+ etag: result.etag,
141
+ size: result.size,
142
+ mime: result.mime,
143
+ });
144
+ }
145
+ catch (e) {
146
+ sendFilesError(reply, e);
147
+ }
148
+ });
149
+ // ── POST /api/files/mkdir ─────────────────────────
150
+ app.post("/api/files/mkdir", async (req, reply) => {
151
+ const p = req.body?.path ?? "";
152
+ try {
153
+ await filesManager.mkdir(p);
154
+ reply.code(201).send({ path: p });
155
+ }
156
+ catch (e) {
157
+ sendFilesError(reply, e);
158
+ }
159
+ });
160
+ // ── POST /api/files/move ──────────────────────────
161
+ app.post("/api/files/move", async (req, reply) => {
162
+ const from = req.body?.from ?? "";
163
+ const to = req.body?.to ?? "";
164
+ const overwrite = req.body?.overwrite === true;
165
+ try {
166
+ await filesManager.move(from, to, overwrite);
167
+ reply.send({ from, to });
168
+ }
169
+ catch (e) {
170
+ sendFilesError(reply, e);
171
+ }
172
+ });
173
+ // ── DELETE /api/files ─────────────────────────────
174
+ app.delete("/api/files", async (req, reply) => {
175
+ const path = decodeOrEmpty(req.query.path);
176
+ try {
177
+ await filesManager.remove(path);
178
+ reply.code(204).send();
179
+ }
180
+ catch (e) {
181
+ sendFilesError(reply, e);
182
+ }
183
+ });
184
+ // ── GET /api/files/search ─────────────────────────
185
+ app.get("/api/files/search", async (req, reply) => {
186
+ const q = (req.query.q ?? "").trim();
187
+ if (!q) {
188
+ return reply.code(400).send({ detail: "q required" });
189
+ }
190
+ const rawLimit = parsePositiveInt(req.query.limit, 20) ?? 20;
191
+ const limit = Math.min(Math.max(rawLimit, 1), 100);
192
+ const pathPrefix = decodeOrEmpty(req.query.path) || undefined;
193
+ try {
194
+ const hits = await filesManager.searchIndex(q, { limit, pathPrefix });
195
+ reply.send({ hits });
196
+ }
197
+ catch (e) {
198
+ sendFilesError(reply, e);
199
+ }
200
+ });
201
+ // ── GET /api/files/meta ───────────────────────────
202
+ app.get("/api/files/meta", async (req, reply) => {
203
+ const { sha, path } = req.query;
204
+ if (sha && path) {
205
+ return reply.code(400).send({ detail: "provide sha or path, not both" });
206
+ }
207
+ if (!sha && !path) {
208
+ return reply.code(400).send({ detail: "sha or path required" });
209
+ }
210
+ if (sha && !/^[a-f0-9]{64}$/i.test(sha)) {
211
+ return reply.code(400).send({ detail: "sha must be a 64-char hex string" });
212
+ }
213
+ try {
214
+ const meta = await filesManager.getMeta({
215
+ sha256: sha,
216
+ path: path ? decodeOrEmpty(path) : undefined,
217
+ });
218
+ reply.send(meta);
219
+ }
220
+ catch (e) {
221
+ sendFilesError(reply, e);
222
+ }
223
+ });
224
+ // ── PUT /api/files/meta ───────────────────────────
225
+ // 96KB body cap: Agent A enforces 64KB on the serialized result;
226
+ // the slack covers merged result growth.
227
+ app.put("/api/files/meta", { bodyLimit: 96 * 1024 }, async (req, reply) => {
228
+ const { sha } = req.query;
229
+ if (!sha) {
230
+ return reply.code(400).send({ detail: "sha required" });
231
+ }
232
+ if (!/^[a-f0-9]{64}$/i.test(sha)) {
233
+ return reply.code(400).send({ detail: "sha must be a 64-char hex string" });
234
+ }
235
+ const merge = (req.query.merge ?? "").toLowerCase() !== "false";
236
+ const body = req.body;
237
+ if (body === null ||
238
+ body === undefined ||
239
+ typeof body !== "object" ||
240
+ Array.isArray(body)) {
241
+ return reply.code(400).send({ detail: "body must be a JSON object" });
242
+ }
243
+ try {
244
+ const result = await filesManager.setMeta(sha, body, { merge });
245
+ reply.send(result);
246
+ }
247
+ catch (e) {
248
+ if (e instanceof FilesError && e.httpStatus === 413) {
249
+ return reply.code(413).send({ error: e.message, code: e.code });
250
+ }
251
+ sendFilesError(reply, e);
252
+ }
253
+ });
254
+ // ── POST /api/files/reindex ───────────────────────
255
+ // Potentially long-running — no timeout; let it finish.
256
+ app.post("/api/files/reindex", async (req, reply) => {
257
+ const pathPrefix = decodeOrEmpty(req.query.path) || undefined;
258
+ try {
259
+ const result = await filesManager.reindex({ pathPrefix });
260
+ reply.send(result);
261
+ }
262
+ catch (e) {
263
+ sendFilesError(reply, e);
264
+ }
265
+ });
266
+ // ── GET /api/files/resolve ────────────────────────
267
+ // Returns the absolute host filesystem path for a files/-relative path,
268
+ // plus existence + stat. Used by the drive MCP shim so agents can hand
269
+ // a real filesystem path to IM channel plugins (Feishu / WeChat / ...)
270
+ // that send files. Host==container by buildVolumes(), so the same
271
+ // string is valid inside the OpenClaw container.
272
+ app.get("/api/files/resolve", async (req, reply) => {
273
+ const rel = decodeOrEmpty(req.query.path);
274
+ try {
275
+ const info = await filesManager.resolveLocalPath(rel);
276
+ reply.send(info);
277
+ }
278
+ catch (e) {
279
+ sendFilesError(reply, e);
280
+ }
281
+ });
282
+ }
283
+ // ── Helpers ──────────────────────────────────────────
284
+ function decodeOrEmpty(input) {
285
+ if (!input)
286
+ return "";
287
+ // Fastify already URL-decodes query strings; this is just a defensive type guard.
288
+ return input;
289
+ }
290
+ function parsePositiveInt(s, def) {
291
+ if (s === undefined || s === null)
292
+ return def;
293
+ const n = Number.parseInt(s, 10);
294
+ if (Number.isFinite(n) && n >= 0)
295
+ return n;
296
+ return def;
297
+ }
298
+ function basename(p) {
299
+ if (!p)
300
+ return "";
301
+ const idx = p.lastIndexOf("/");
302
+ return idx === -1 ? p : p.slice(idx + 1);
303
+ }
304
+ function encodeRfc5987(s) {
305
+ // RFC 5987 encoding for filename* parameter
306
+ return encodeURIComponent(s).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
307
+ }
308
+ function weakETagMatch(provided, current) {
309
+ const strip = (x) => x.replace(/^W\//, "");
310
+ return strip(provided) === strip(current);
311
+ }
312
+ function sendFilesError(reply, e) {
313
+ if (e instanceof FilesError) {
314
+ reply.code(e.httpStatus).send({ error: e.message, code: e.code });
315
+ return;
316
+ }
317
+ const err = e;
318
+ console.error("[files-routes] unexpected:", err.message);
319
+ reply.code(500).send({ error: "internal error", code: err.code ?? "internal" });
320
+ }
321
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/routes/files.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,20 @@ export const HOP_BY_HOP = new Set([
16
16
  "connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
17
17
  "te", "trailer", "transfer-encoding", "upgrade",
18
18
  ]);
19
+ /**
20
+ * Strip the panel session cookie (`jishushell_session`) from a cookie header
21
+ * value, preserving any other cookies for upstream forwarding.
22
+ */
23
+ function stripPanelSessionCookie(value) {
24
+ if (value === undefined)
25
+ return undefined;
26
+ const cookie = Array.isArray(value) ? value.join("; ") : value;
27
+ const preserved = cookie
28
+ .split(";")
29
+ .map((part) => part.trim())
30
+ .filter((part) => part && !/^jishushell_session=/i.test(part));
31
+ return preserved.length ? preserved.join("; ") : undefined;
32
+ }
19
33
  function readHeaderValue(value) {
20
34
  const header = Array.isArray(value) ? value[0] : value;
21
35
  return typeof header === "string" && header.trim() ? header.trim() : null;
@@ -269,6 +283,20 @@ function capabilityProxyBootstrap(proxyBasePath) {
269
283
  "var _lr=location.replace.bind(location);",
270
284
  "location.assign=function(u){return _la(rw(u));};",
271
285
  "location.replace=function(u){return _lr(rw(u));};",
286
+ // --- frame-busting defense ---
287
+ // Embedded SPAs (e.g. WeKnora) frequently do
288
+ // window.top.location.href = '/login'
289
+ // when they see a 401, intending to log the user out. Inside our
290
+ // capability proxy iframe `top` is the panel's main window — that
291
+ // tears the user away from the instance detail page entirely.
292
+ // Redirect `top`/`parent` to the iframe's own window so the
293
+ // navigation stays inside the embed. Safe because the iframe IS
294
+ // same-origin as the panel (our reverse proxy serves it from the
295
+ // panel's host); cross-origin access would throw and fail closed.
296
+ "try{",
297
+ "Object.defineProperty(window,'top',{configurable:true,get:function(){return window;}});",
298
+ "Object.defineProperty(window,'parent',{configurable:true,get:function(){return window;}});",
299
+ "}catch(_e){}",
272
300
  // --- dynamic property assignment: img.src = '/static/...' ---
273
301
  "function patchProp(tag,prop){",
274
302
  "var d=Object.getOwnPropertyDescriptor(tag.prototype,prop);",
@@ -507,11 +535,15 @@ async function proxyProvidedCapability(req, reply) {
507
535
  if (capability.protocol !== "http" && capability.protocol !== "https") {
508
536
  return reply.status(400).send({ detail: `Capability '${req.params.capability}' does not use HTTP(S)` });
509
537
  }
510
- if (typeof capability.port !== "number" || capability.port < 1) {
538
+ // Resolve the runtime port (handles port reallocation) instead of using
539
+ // the declared AppSpec port which may be stale.
540
+ const runtimePort = instanceManager.resolveRuntimeCapabilityPort(req.params.id, req.params.capability)
541
+ ?? capability.port;
542
+ if (typeof runtimePort !== "number" || runtimePort < 1) {
511
543
  return reply.status(500).send({ detail: `Capability '${req.params.capability}' has no resolved port` });
512
544
  }
513
- const upstreamHost = await instanceManager.getHostForAppPort(req.params.id, capability.port);
514
- const upstreamOrigin = `${capability.protocol}://${instanceManager.urlHost(upstreamHost)}:${capability.port}`;
545
+ const upstreamHost = await instanceManager.getHostForAppPort(req.params.id, runtimePort);
546
+ const upstreamOrigin = `${capability.protocol}://${instanceManager.urlHost(upstreamHost)}:${runtimePort}`;
515
547
  const wildcardSuffix = typeof req.params["*"] === "string" ? req.params["*"] : "";
516
548
  const proxyBasePath = capabilityProxyPath(req.params.id, req.params.capability);
517
549
  const querySuffix = req.raw.url?.includes("?") ? req.raw.url.slice(req.raw.url.indexOf("?")) : "";
@@ -531,6 +563,16 @@ async function proxyProvidedCapability(req, reply) {
531
563
  if (HOP_BY_HOP.has(normalizedKey) || normalizedKey === "host" || normalizedKey === "content-length" || normalizedKey === "accept-encoding") {
532
564
  continue;
533
565
  }
566
+ // Strip panel session credentials to avoid leaking them upstream
567
+ // (consistent with the WebSocket capability proxy in server.ts)
568
+ if (normalizedKey === "authorization")
569
+ continue;
570
+ if (normalizedKey === "cookie") {
571
+ const stripped = stripPanelSessionCookie(value);
572
+ if (stripped)
573
+ headers.set(key, stripped);
574
+ continue;
575
+ }
534
576
  if (Array.isArray(value)) {
535
577
  for (const item of value)
536
578
  headers.append(key, item);
@@ -598,6 +640,21 @@ async function proxyProvidedCapability(req, reply) {
598
640
  if (HOP_BY_HOP.has(normalizedKey) || normalizedKey === "content-length" || normalizedKey === "content-encoding") {
599
641
  return;
600
642
  }
643
+ // When we rewrite the response body (HTML/CSS/JS), the upstream ETag /
644
+ // Cache-Control values describe the *original* upstream bytes — but the
645
+ // body the browser receives is post-rewrite (proxy-prefixed paths, JS
646
+ // hard-coded redirect targets, etc.). Honoring the upstream cache hints
647
+ // lets the browser pin a stale rewrite indefinitely: e.g. an early
648
+ // visit that pre-dated the JS rewrite gets cached and survives across
649
+ // panel restarts, breaking the auth redirect logic until a hard refresh.
650
+ // Strip cache validators and force revalidation on every load.
651
+ if (willRewriteBody && (normalizedKey === "cache-control" ||
652
+ normalizedKey === "etag" ||
653
+ normalizedKey === "last-modified" ||
654
+ normalizedKey === "expires" ||
655
+ normalizedKey === "pragma")) {
656
+ return;
657
+ }
601
658
  if (willInjectHtml && (normalizedKey === "content-security-policy" ||
602
659
  normalizedKey === "content-security-policy-report-only" ||
603
660
  normalizedKey === "x-frame-options")) {
@@ -627,6 +684,10 @@ async function proxyProvidedCapability(req, reply) {
627
684
  return reply.send();
628
685
  }
629
686
  if (willRewriteBody) {
687
+ // Pair with the cache-validator strip above.
688
+ reply.header("cache-control", "no-cache, no-store, must-revalidate");
689
+ reply.header("pragma", "no-cache");
690
+ reply.header("expires", "0");
630
691
  let extraHeadHtml = "";
631
692
  if (req.params.capability === "browserless-debugger") {
632
693
  extraHeadHtml = browserlessDebuggerBootstrap(req.params.id);
@@ -645,11 +706,16 @@ async function proxyProvidedCapability(req, reply) {
645
706
  // emit Clear-Site-Data so the browser drops the SW + its cache and
646
707
  // reloads through the proxy. We mark the success with a long-lived
647
708
  // cookie scoped to the proxy path to avoid a reload loop.
648
- const cookieHeader = (req.headers.cookie || "").toString();
649
- const swCleaned = /(?:^|;\s*)cap_proxy_sw_clean=1(?:;|$)/.test(cookieHeader);
650
- if (!swCleaned) {
651
- reply.header("Clear-Site-Data", '"cache", "storage"');
652
- reply.header("Set-Cookie", `cap_proxy_sw_clean=1; Path=${proxyBasePath}; Max-Age=2592000; SameSite=Lax`);
709
+ // Gate to HTML only — JS/CSS sub-resources also flow through this branch
710
+ // now that we rewrite JS bundles, and emitting Clear-Site-Data on a JS
711
+ // response would clear storage mid-page-load.
712
+ if (willInjectHtml) {
713
+ const cookieHeader = (req.headers.cookie || "").toString();
714
+ const swCleaned = /(?:^|;\s*)cap_proxy_sw_clean=1(?:;|$)/.test(cookieHeader);
715
+ if (!swCleaned) {
716
+ reply.header("Clear-Site-Data", '"cache", "storage"');
717
+ reply.header("Set-Cookie", `cap_proxy_sw_clean=1; Path=${proxyBasePath}; Max-Age=2592000; SameSite=Lax`);
718
+ }
653
719
  }
654
720
  const rawBody = await upstream.text();
655
721
  req.raw.off("close", onClientClose);
@@ -1074,10 +1140,19 @@ export async function instanceRoutes(app) {
1074
1140
  const sudoPassword = typeof req.body?.sudoPassword === "string" && req.body.sudoPassword.trim()
1075
1141
  ? req.body.sudoPassword
1076
1142
  : undefined;
1077
- await instanceManager.uninstallApp(req.params.id, sudoPassword ? { exec: { sudoPassword } } : {});
1143
+ const result = instanceManager.uninstallAppTask(req.params.id, sudoPassword ? { sudoPassword } : undefined);
1144
+ if (!result.ok) {
1145
+ const code = result.code;
1146
+ const status = code === "TASK_BUSY" ? 409 : code === "APP_NOT_FOUND" ? 404 : 400;
1147
+ return reply.status(status).send({ detail: result.error, code });
1148
+ }
1078
1149
  statusCache.delete(req.params.id);
1079
1150
  llmProxy.cleanupInstance(req.params.id);
1080
- return { ok: true };
1151
+ return reply.status(202).send({
1152
+ ok: true,
1153
+ taskId: result.taskId,
1154
+ ...result.reused ? { reused: true } : {},
1155
+ });
1081
1156
  }
1082
1157
  const svc = await getSvc();
1083
1158
  let stopFailed = false;
@@ -1625,7 +1700,7 @@ export async function instanceRoutes(app) {
1625
1700
  return { lines: logLines };
1626
1701
  });
1627
1702
  // Admin: re-encrypt all instance secrets with current AES key
1628
- app.post("/api/admin/migrate-secrets", async (_req, reply) => {
1703
+ app.post("/api/admin/migrate-secrets", async (_req, _reply) => {
1629
1704
  const { getAesKey, getJwtSecret } = await import("../config.js");
1630
1705
  const { scryptSync, createDecipheriv, createCipheriv, randomBytes } = await import("crypto");
1631
1706
  const { readFileSync, existsSync: fsExistsSync } = await import("fs");
@@ -1775,7 +1850,7 @@ export async function instanceRoutes(app) {
1775
1850
  const consumerAgentType = String(meta?.agentType ?? "");
1776
1851
  const consumerIsAgent = consumerAgentType === "hermes" || consumerAgentType === "openclaw";
1777
1852
  const requires = (specInfo.spec.requires ?? []).map((r) => {
1778
- const isCategoryToken = ["llm", "search", "browser", "mcp"].includes(r.capability);
1853
+ const isCategoryToken = ["llm", "search", "browser", "mcp", "files", "knowledge"].includes(r.capability);
1779
1854
  const candidates = isCategoryToken
1780
1855
  ? Object.entries(capabilityRegistry.snapshot().providersByCapability ?? {})
1781
1856
  .filter(([cap]) => cap.startsWith(r.capability + "-") || cap === r.capability)