shennian 0.2.88 → 0.2.90

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 (143) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +22 -0
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.d.ts +6 -0
  8. package/dist/src/agents/adapter.js +1 -19
  9. package/dist/src/agents/claude.js +8 -305
  10. package/dist/src/agents/codex-control.d.ts +35 -0
  11. package/dist/src/agents/codex-control.js +2 -0
  12. package/dist/src/agents/codex-utils.js +7 -200
  13. package/dist/src/agents/codex.d.ts +8 -0
  14. package/dist/src/agents/codex.js +15 -863
  15. package/dist/src/agents/command-spec.js +2 -413
  16. package/dist/src/agents/config-status.js +1 -226
  17. package/dist/src/agents/cursor.js +1 -249
  18. package/dist/src/agents/custom.js +4 -271
  19. package/dist/src/agents/detect.js +1 -56
  20. package/dist/src/agents/external-channel-instructions.js +10 -94
  21. package/dist/src/agents/gemini.js +1 -173
  22. package/dist/src/agents/manager.js +13 -157
  23. package/dist/src/agents/model-registry/cache.js +1 -37
  24. package/dist/src/agents/model-registry/discovery.js +2 -187
  25. package/dist/src/agents/model-registry/parsers.js +4 -447
  26. package/dist/src/agents/model-registry/runner.js +1 -30
  27. package/dist/src/agents/model-registry/service.js +1 -78
  28. package/dist/src/agents/model-registry/types.js +1 -8
  29. package/dist/src/agents/model-registry.js +1 -18
  30. package/dist/src/agents/openclaw.js +2 -275
  31. package/dist/src/agents/opencode.js +1 -231
  32. package/dist/src/agents/pi-context.js +12 -217
  33. package/dist/src/agents/pi.js +14 -723
  34. package/dist/src/agents/platform-instructions.js +9 -54
  35. package/dist/src/channels/base.d.ts +4 -1
  36. package/dist/src/channels/base.js +1 -3
  37. package/dist/src/channels/registry.js +1 -30
  38. package/dist/src/channels/reply-split.js +10 -89
  39. package/dist/src/channels/runtime.d.ts +1 -0
  40. package/dist/src/channels/runtime.js +5 -533
  41. package/dist/src/channels/secret-registry.d.ts +1 -0
  42. package/dist/src/channels/secret-registry.js +1 -46
  43. package/dist/src/channels/websocket.js +8 -378
  44. package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
  45. package/dist/src/channels/wechat-channel/anchor.js +1 -0
  46. package/dist/src/channels/wechat-channel/client.d.ts +74 -0
  47. package/dist/src/channels/wechat-channel/client.js +1 -0
  48. package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
  49. package/dist/src/channels/wechat-channel/cooldown.js +1 -0
  50. package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
  51. package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
  52. package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
  53. package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
  54. package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
  55. package/dist/src/channels/wechat-channel/helper-client.js +3 -0
  56. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
  57. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
  58. package/dist/src/channels/wechat-channel/index.d.ts +17 -0
  59. package/dist/src/channels/wechat-channel/index.js +1 -0
  60. package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
  61. package/dist/src/channels/wechat-channel/ledger.js +1 -0
  62. package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
  63. package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
  64. package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
  65. package/dist/src/channels/wechat-channel/message-key.js +1 -0
  66. package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
  67. package/dist/src/channels/wechat-channel/observer.js +1 -0
  68. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
  69. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
  70. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  71. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  72. package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
  73. package/dist/src/channels/wechat-channel/preflight.js +1 -0
  74. package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
  75. package/dist/src/channels/wechat-channel/runner.js +1 -0
  76. package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
  77. package/dist/src/channels/wechat-channel/runtime.js +1 -0
  78. package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
  79. package/dist/src/channels/wechat-channel/scheduler.js +1 -0
  80. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  81. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  82. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  83. package/dist/src/channels/wechat-rpa.d.ts +21 -0
  84. package/dist/src/channels/wechat-rpa.js +6 -1022
  85. package/dist/src/channels/wecom.js +4 -357
  86. package/dist/src/commands/agent.js +6 -131
  87. package/dist/src/commands/daemon-windows.js +8 -48
  88. package/dist/src/commands/daemon.js +19 -1013
  89. package/dist/src/commands/external-attachments.js +1 -51
  90. package/dist/src/commands/external.js +1 -137
  91. package/dist/src/commands/manager.js +2 -389
  92. package/dist/src/commands/pair-qr.js +1 -6
  93. package/dist/src/commands/pair.js +9 -287
  94. package/dist/src/commands/tools.js +1 -34
  95. package/dist/src/commands/upgrade.js +1 -198
  96. package/dist/src/config/index.js +1 -35
  97. package/dist/src/daemon-log.js +6 -58
  98. package/dist/src/env-path.js +1 -64
  99. package/dist/src/fs/boundary.js +1 -126
  100. package/dist/src/fs/handler.js +1 -130
  101. package/dist/src/fs/security.js +1 -32
  102. package/dist/src/fs/text-decoder.d.ts +10 -0
  103. package/dist/src/fs/text-decoder.js +1 -0
  104. package/dist/src/index.js +2 -404
  105. package/dist/src/log-reporter.js +1 -16
  106. package/dist/src/manager/prompt.js +29 -34
  107. package/dist/src/manager/registry.js +2 -269
  108. package/dist/src/manager/runtime.js +19 -1003
  109. package/dist/src/native-fusion/config.js +1 -5
  110. package/dist/src/native-fusion/opencode-parser.js +3 -123
  111. package/dist/src/native-fusion/parser-common.js +8 -264
  112. package/dist/src/native-fusion/parsers.js +8 -729
  113. package/dist/src/native-fusion/service.d.ts +10 -0
  114. package/dist/src/native-fusion/service.js +2 -198
  115. package/dist/src/native-fusion/state.js +1 -22
  116. package/dist/src/native-fusion/types.js +1 -1
  117. package/dist/src/region.js +1 -88
  118. package/dist/src/relay/client.js +1 -343
  119. package/dist/src/session/archive-zip.js +1 -220
  120. package/dist/src/session/handlers/agent-config.js +1 -150
  121. package/dist/src/session/handlers/agents.js +1 -55
  122. package/dist/src/session/handlers/chat.js +2 -733
  123. package/dist/src/session/handlers/control.js +1 -55
  124. package/dist/src/session/handlers/fs.js +1 -747
  125. package/dist/src/session/handlers/session-refresh.js +1 -35
  126. package/dist/src/session/handlers/skills.js +1 -121
  127. package/dist/src/session/handlers/title.js +1 -60
  128. package/dist/src/session/handlers/tool-detail.d.ts +3 -0
  129. package/dist/src/session/handlers/tool-detail.js +1 -0
  130. package/dist/src/session/manager.d.ts +3 -0
  131. package/dist/src/session/manager.js +1 -261
  132. package/dist/src/session/projection.js +1 -54
  133. package/dist/src/session/queue.js +4 -317
  134. package/dist/src/session/remote-attachments.js +1 -72
  135. package/dist/src/session/store.js +3 -109
  136. package/dist/src/session/types.d.ts +4 -0
  137. package/dist/src/session/types.js +1 -4
  138. package/dist/src/skills/registry.js +15 -148
  139. package/dist/src/skills/setup.js +1 -101
  140. package/dist/src/tools/markdown-to-pdf.js +10 -346
  141. package/dist/src/upgrade/engine.js +3 -347
  142. package/package.json +3 -2
  143. package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
@@ -1,747 +1 @@
1
- // @arch docs/architecture/cli/daemon.md#会话管理
2
- // @test src/__tests__/session-manager.test.ts
3
- import fs from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import { convertMarkdownToPdf, defaultPdfOutputPath, MarkdownPdfBrowserMissingError, } from '../../tools/markdown-to-pdf.js';
7
- import { createZipArchive } from '../archive-zip.js';
8
- const FILE_SYSTEM_ROOTS_PATH = '__roots__';
9
- const MAX_FOLDER_UPLOAD_FILES = 2000;
10
- const MAX_FOLDER_UPLOAD_TOTAL_SIZE = 1024 * 1024 * 1024;
11
- const MAX_ARCHIVE_FILES = 5000;
12
- const MAX_ARCHIVE_TOTAL_SIZE = 1024 * 1024 * 1024;
13
- function isWindowsAbsolutePath(pathValue) {
14
- return /^[A-Za-z]:([\\/]|$)/.test(pathValue) || /^\\\\[^\\]+\\[^\\]+/.test(pathValue);
15
- }
16
- function pathApiForPath(pathValue) {
17
- return isWindowsAbsolutePath(pathValue) ? path.win32 : path.posix;
18
- }
19
- function makeFsEntry(entryPath) {
20
- const stat = fs.statSync(entryPath);
21
- const api = pathApiForPath(entryPath);
22
- return {
23
- name: api.basename(entryPath),
24
- path: entryPath,
25
- isDir: stat.isDirectory(),
26
- size: stat.isFile() ? stat.size : undefined,
27
- modifiedAt: stat.mtimeMs,
28
- };
29
- }
30
- function listFileSystemRoots() {
31
- if (os.platform() === 'win32') {
32
- const entries = [];
33
- for (let code = 65; code <= 90; code += 1) {
34
- const drive = `${String.fromCharCode(code)}:\\`;
35
- if (fs.existsSync(drive))
36
- entries.push({ name: drive, path: drive, isDir: true });
37
- }
38
- return entries;
39
- }
40
- return [{ name: '/', path: '/', isDir: true }];
41
- }
42
- function fsErrorMessage(err, fallbackPath) {
43
- const code = typeof err === 'object' && err && 'code' in err
44
- ? String(err.code)
45
- : '';
46
- if (code === 'ENOENT')
47
- return `Directory not found: ${fallbackPath}`;
48
- if (code === 'EACCES' || code === 'EPERM')
49
- return `Permission denied: ${fallbackPath}`;
50
- return err instanceof Error ? err.message : String(err);
51
- }
52
- function isSafeRelativeUploadPath(relativePath) {
53
- if (!relativePath || relativePath === '.' || relativePath.trim() !== relativePath)
54
- return false;
55
- if (path.posix.isAbsolute(relativePath) || path.win32.isAbsolute(relativePath))
56
- return false;
57
- const normalized = relativePath.replace(/\\/g, '/');
58
- if (normalized !== relativePath)
59
- return false;
60
- return normalized
61
- .split('/')
62
- .every((segment) => segment && segment !== '.' && segment !== '..' && !hasControlChar(segment));
63
- }
64
- function isSafeRenameName(name) {
65
- const trimmed = name.trim();
66
- if (!trimmed || trimmed !== name)
67
- return false;
68
- if (name === '.' || name === '..')
69
- return false;
70
- if (hasControlChar(name) || /[\\/:]/.test(name))
71
- return false;
72
- if (/[<>|"?*]/.test(name))
73
- return false;
74
- if (/[. ]$/.test(name))
75
- return false;
76
- const upper = name.split('.')[0]?.toUpperCase();
77
- if (upper && /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(upper))
78
- return false;
79
- return true;
80
- }
81
- function hasControlChar(value) {
82
- return Array.from(value).some((char) => {
83
- const code = char.charCodeAt(0);
84
- return code >= 0x00 && code <= 0x1f;
85
- });
86
- }
87
- function decodeManifest(value) {
88
- if (!Array.isArray(value))
89
- return [];
90
- return value.map((item) => ({
91
- relativePath: String(item.relativePath || ''),
92
- size: Number(item.size || 0),
93
- mimeType: item.mimeType,
94
- modifiedAt: item.modifiedAt,
95
- }));
96
- }
97
- export async function handleFsLs(runtime, req) {
98
- const requestedPath = req.params.path || os.homedir();
99
- const rootPath = req.params.rootPath || requestedPath;
100
- if (requestedPath === FILE_SYSTEM_ROOTS_PATH) {
101
- runtime.client.sendRes({
102
- type: 'res',
103
- id: req.id,
104
- ok: true,
105
- payload: { path: FILE_SYSTEM_ROOTS_PATH, entries: listFileSystemRoots() },
106
- });
107
- return;
108
- }
109
- const resolved = runtime.resolveAuthorizedPath(requestedPath, rootPath);
110
- if (!resolved.ok) {
111
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
112
- return;
113
- }
114
- const dirPath = resolved.path;
115
- try {
116
- const raw = fs.readdirSync(dirPath, { withFileTypes: true });
117
- const joinPath = isWindowsAbsolutePath(dirPath) ? path.win32.join : path.join;
118
- const entries = raw
119
- .map((entry) => ({
120
- name: entry.name,
121
- path: joinPath(dirPath, entry.name),
122
- isDir: entry.isDirectory(),
123
- }))
124
- .sort((left, right) => {
125
- if (left.isDir !== right.isDir)
126
- return left.isDir ? -1 : 1;
127
- return left.name.localeCompare(right.name);
128
- });
129
- runtime.client.sendRes({
130
- type: 'res',
131
- id: req.id,
132
- ok: true,
133
- payload: { path: dirPath, entries },
134
- });
135
- }
136
- catch (err) {
137
- runtime.client.sendRes({
138
- type: 'res',
139
- id: req.id,
140
- ok: false,
141
- error: fsErrorMessage(err, dirPath),
142
- });
143
- }
144
- }
145
- export async function handleFsRead(runtime, req) {
146
- const requestedPath = req.params.path;
147
- const rootPath = req.params.rootPath || requestedPath;
148
- const resolved = runtime.resolveAuthorizedPath(requestedPath, rootPath);
149
- if (!resolved.ok) {
150
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
151
- return;
152
- }
153
- const filePath = resolved.path;
154
- const encoding = req.params.encoding || 'utf8';
155
- const offset = req.params.offset;
156
- const length = req.params.length;
157
- try {
158
- const stat = fs.statSync(filePath);
159
- if (!stat.isFile()) {
160
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Not a file' });
161
- return;
162
- }
163
- if (offset != null && length != null) {
164
- const readLen = Math.min(length, Math.max(0, stat.size - offset));
165
- if (readLen <= 0) {
166
- runtime.client.sendRes({
167
- type: 'res',
168
- id: req.id,
169
- ok: true,
170
- payload: { data: '', offset, length: 0, totalSize: stat.size, path: filePath },
171
- });
172
- return;
173
- }
174
- const fd = fs.openSync(filePath, 'r');
175
- try {
176
- const buffer = Buffer.alloc(readLen);
177
- const bytesRead = fs.readSync(fd, buffer, 0, readLen, offset);
178
- runtime.client.sendRes({
179
- type: 'res',
180
- id: req.id,
181
- ok: true,
182
- payload: {
183
- data: buffer.subarray(0, bytesRead).toString('base64'),
184
- offset,
185
- length: bytesRead,
186
- totalSize: stat.size,
187
- path: filePath,
188
- },
189
- });
190
- }
191
- finally {
192
- fs.closeSync(fd);
193
- }
194
- return;
195
- }
196
- const maxSize = req.params.maxSize || (encoding === 'base64' ? 5 * 1024 * 1024 : 512 * 1024);
197
- if (stat.size > maxSize) {
198
- runtime.client.sendRes({
199
- type: 'res',
200
- id: req.id,
201
- ok: false,
202
- error: `File too large: ${stat.size} bytes (max ${maxSize})`,
203
- payload: { size: stat.size, path: filePath },
204
- });
205
- return;
206
- }
207
- if (encoding === 'base64') {
208
- const buffer = fs.readFileSync(filePath);
209
- runtime.client.sendRes({
210
- type: 'res',
211
- id: req.id,
212
- ok: true,
213
- payload: { data: buffer.toString('base64'), path: filePath, size: stat.size },
214
- });
215
- return;
216
- }
217
- const content = fs.readFileSync(filePath, 'utf-8');
218
- runtime.client.sendRes({
219
- type: 'res',
220
- id: req.id,
221
- ok: true,
222
- payload: { content, path: filePath, size: stat.size },
223
- });
224
- }
225
- catch (err) {
226
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: String(err) });
227
- }
228
- }
229
- export async function handleFsWrite(runtime, req) {
230
- const requestedPath = req.params.path;
231
- const content = req.params.content;
232
- const rootPath = req.params.rootPath || requestedPath;
233
- if (!requestedPath || typeof content !== 'string') {
234
- runtime.client.sendRes({
235
- type: 'res',
236
- id: req.id,
237
- ok: false,
238
- error: 'path and content are required',
239
- });
240
- return;
241
- }
242
- const resolved = runtime.resolveAuthorizedPath(requestedPath, rootPath);
243
- if (!resolved.ok) {
244
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
245
- return;
246
- }
247
- try {
248
- const existing = fs.existsSync(resolved.path) ? fs.statSync(resolved.path) : null;
249
- if (existing && !existing.isFile()) {
250
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Not a file' });
251
- return;
252
- }
253
- fs.writeFileSync(resolved.path, content, 'utf-8');
254
- const stat = fs.statSync(resolved.path);
255
- runtime.client.sendRes({
256
- type: 'res',
257
- id: req.id,
258
- ok: true,
259
- payload: { path: resolved.path, size: stat.size, modifiedAt: stat.mtimeMs },
260
- });
261
- }
262
- catch (err) {
263
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: String(err) });
264
- }
265
- }
266
- export async function handleFsRename(runtime, req) {
267
- const requestedPath = req.params.path;
268
- const newName = req.params.newName;
269
- if (!requestedPath || !newName) {
270
- runtime.client.sendRes({
271
- type: 'res',
272
- id: req.id,
273
- ok: false,
274
- error: 'path and newName are required',
275
- });
276
- return;
277
- }
278
- if (!isSafeRenameName(newName)) {
279
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Invalid newName' });
280
- return;
281
- }
282
- const api = pathApiForPath(requestedPath);
283
- const rootPath = req.params.rootPath || api.dirname(requestedPath);
284
- const resolved = runtime.resolveAuthorizedPath(requestedPath, rootPath);
285
- if (!resolved.ok) {
286
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
287
- return;
288
- }
289
- try {
290
- const api = pathApiForPath(resolved.path);
291
- const newPath = api.join(api.dirname(resolved.path), newName);
292
- const checkedNewPath = runtime.resolveAuthorizedPath(newPath, rootPath);
293
- if (!checkedNewPath.ok) {
294
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: checkedNewPath.error });
295
- return;
296
- }
297
- if (fs.existsSync(checkedNewPath.path)) {
298
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Target already exists' });
299
- return;
300
- }
301
- fs.renameSync(resolved.path, checkedNewPath.path);
302
- runtime.client.sendRes({
303
- type: 'res',
304
- id: req.id,
305
- ok: true,
306
- payload: {
307
- oldPath: resolved.path,
308
- newPath: checkedNewPath.path,
309
- entry: makeFsEntry(checkedNewPath.path),
310
- },
311
- });
312
- }
313
- catch (err) {
314
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: String(err) });
315
- }
316
- }
317
- export async function handleFsExportMarkdownPdf(runtime, req) {
318
- const requestedPath = req.params.path;
319
- const rootPath = req.params.rootPath || path.dirname(requestedPath || '.');
320
- const title = typeof req.params.title === 'string' ? req.params.title : undefined;
321
- if (!requestedPath) {
322
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'path is required' });
323
- return;
324
- }
325
- const resolved = runtime.resolveAuthorizedPath(requestedPath, rootPath);
326
- if (!resolved.ok) {
327
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
328
- return;
329
- }
330
- if (!/\.mdx?$/i.test(resolved.path)) {
331
- runtime.client.sendRes({
332
- type: 'res',
333
- id: req.id,
334
- ok: false,
335
- error: 'Only Markdown files can be exported to PDF',
336
- });
337
- return;
338
- }
339
- const outputPath = defaultPdfOutputPath(resolved.path);
340
- const checkedOutput = runtime.resolveAuthorizedPath(outputPath, rootPath);
341
- if (!checkedOutput.ok) {
342
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: checkedOutput.error });
343
- return;
344
- }
345
- try {
346
- const result = await convertMarkdownToPdf(resolved.path, {
347
- outputPath: checkedOutput.path,
348
- title,
349
- });
350
- runtime.client.sendRes({
351
- type: 'res',
352
- id: req.id,
353
- ok: true,
354
- payload: {
355
- sourcePath: resolved.path,
356
- outputPath: result.outputPath,
357
- entry: makeFsEntry(result.outputPath),
358
- },
359
- });
360
- }
361
- catch (err) {
362
- if (err instanceof MarkdownPdfBrowserMissingError) {
363
- runtime.client.sendRes({
364
- type: 'res',
365
- id: req.id,
366
- ok: false,
367
- error: 'This machine needs the PDF export component before Markdown files can be exported to PDF.',
368
- payload: err.setup,
369
- });
370
- return;
371
- }
372
- runtime.client.sendRes({
373
- type: 'res',
374
- id: req.id,
375
- ok: false,
376
- error: err instanceof Error ? err.message : String(err),
377
- });
378
- }
379
- }
380
- function safeArchiveBaseName(name) {
381
- const trimmed = name.trim().replace(/[<>:"/\\|?*\u0000-\u001f]+/g, '-').replace(/[. ]+$/g, '');
382
- return trimmed || 'folder';
383
- }
384
- export async function handleFsArchiveZip(runtime, req) {
385
- const requestedPath = req.params.path;
386
- const rootPath = req.params.rootPath || requestedPath;
387
- if (!requestedPath) {
388
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'path is required' });
389
- return;
390
- }
391
- const resolved = runtime.resolveAuthorizedPath(requestedPath, rootPath);
392
- if (!resolved.ok) {
393
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
394
- return;
395
- }
396
- try {
397
- const stat = fs.statSync(resolved.path);
398
- if (!stat.isDirectory()) {
399
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Not a directory' });
400
- return;
401
- }
402
- const api = pathApiForPath(resolved.path);
403
- const baseName = safeArchiveBaseName(api.basename(resolved.path));
404
- const archiveDir = path.join(os.tmpdir(), 'shennian-archives');
405
- const outputPath = path.join(archiveDir, `${baseName}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.zip`);
406
- const result = createZipArchive(resolved.path, outputPath, {
407
- maxFiles: MAX_ARCHIVE_FILES,
408
- maxTotalSize: MAX_ARCHIVE_TOTAL_SIZE,
409
- });
410
- runtime.client.sendRes({
411
- type: 'res',
412
- id: req.id,
413
- ok: true,
414
- payload: {
415
- sourcePath: resolved.path,
416
- outputPath: result.outputPath,
417
- entry: makeFsEntry(result.outputPath),
418
- fileCount: result.fileCount,
419
- totalSize: result.totalSize,
420
- },
421
- });
422
- }
423
- catch (err) {
424
- runtime.client.sendRes({
425
- type: 'res',
426
- id: req.id,
427
- ok: false,
428
- error: err instanceof Error ? err.message : String(err),
429
- });
430
- }
431
- }
432
- export async function handleFsTransfer(runtime, req) {
433
- const { name, targetPath, data, direct } = req.params;
434
- if (!name || !data) {
435
- runtime.client.sendRes({
436
- type: 'res',
437
- id: req.id,
438
- ok: false,
439
- error: 'name and data are required',
440
- });
441
- return;
442
- }
443
- try {
444
- const rootPath = req.params.rootPath || targetPath || os.homedir();
445
- const resolved = runtime.resolveAuthorizedPath(targetPath || rootPath, rootPath);
446
- if (!resolved.ok) {
447
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
448
- return;
449
- }
450
- const baseDir = resolved.path;
451
- const uploadDir = direct ? baseDir : path.join(baseDir, '.uploads');
452
- if (!direct)
453
- fs.mkdirSync(uploadDir, { recursive: true });
454
- const filePath = path.join(uploadDir, path.basename(name));
455
- const buffer = Buffer.from(data, 'base64');
456
- fs.writeFileSync(filePath, buffer);
457
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: { path: filePath } });
458
- }
459
- catch (err) {
460
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: String(err) });
461
- }
462
- }
463
- export async function handleFsTransferStart(runtime, req) {
464
- const { name, targetPath, totalSize, direct, kind, baseName } = req.params;
465
- const manifest = decodeManifest(req.params.manifest);
466
- const isFolder = kind === 'folder';
467
- const transferName = isFolder ? baseName || name : name;
468
- if (!transferName || (!isFolder && !totalSize)) {
469
- runtime.client.sendRes({
470
- type: 'res',
471
- id: req.id,
472
- ok: false,
473
- error: 'name and totalSize are required',
474
- });
475
- return;
476
- }
477
- try {
478
- const rootPath = req.params.rootPath || targetPath || os.homedir();
479
- const resolved = runtime.resolveAuthorizedPath(targetPath || rootPath, rootPath);
480
- if (!resolved.ok) {
481
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: resolved.error });
482
- return;
483
- }
484
- const baseDir = resolved.path;
485
- const destinationDir = direct ? baseDir : path.join(baseDir, '.uploads');
486
- if (!direct)
487
- fs.mkdirSync(destinationDir, { recursive: true });
488
- const transferId = `tf-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
489
- if (isFolder) {
490
- if (!manifest.length) {
491
- runtime.client.sendRes({
492
- type: 'res',
493
- id: req.id,
494
- ok: false,
495
- error: 'manifest is required for folder uploads',
496
- });
497
- return;
498
- }
499
- if (manifest.length > MAX_FOLDER_UPLOAD_FILES) {
500
- runtime.client.sendRes({
501
- type: 'res',
502
- id: req.id,
503
- ok: false,
504
- error: `Too many files: ${manifest.length}`,
505
- });
506
- return;
507
- }
508
- const folderName = path.basename(transferName);
509
- if (!isSafeRenameName(folderName)) {
510
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Invalid folder name' });
511
- return;
512
- }
513
- const targetDir = path.join(destinationDir, folderName);
514
- const checkedTargetDir = runtime.resolveAuthorizedPath(targetDir, rootPath);
515
- if (!checkedTargetDir.ok) {
516
- runtime.client.sendRes({
517
- type: 'res',
518
- id: req.id,
519
- ok: false,
520
- error: checkedTargetDir.error,
521
- });
522
- return;
523
- }
524
- const seen = new Set();
525
- let aggregateSize = 0;
526
- const files = new Map();
527
- for (const item of manifest) {
528
- if (!isSafeRelativeUploadPath(item.relativePath)) {
529
- runtime.client.sendRes({
530
- type: 'res',
531
- id: req.id,
532
- ok: false,
533
- error: `Invalid relativePath: ${item.relativePath}`,
534
- });
535
- return;
536
- }
537
- if (!Number.isFinite(item.size) || item.size < 0) {
538
- runtime.client.sendRes({
539
- type: 'res',
540
- id: req.id,
541
- ok: false,
542
- error: `Invalid size: ${item.relativePath}`,
543
- });
544
- return;
545
- }
546
- if (seen.has(item.relativePath)) {
547
- runtime.client.sendRes({
548
- type: 'res',
549
- id: req.id,
550
- ok: false,
551
- error: `Duplicate relativePath: ${item.relativePath}`,
552
- });
553
- return;
554
- }
555
- seen.add(item.relativePath);
556
- aggregateSize += item.size;
557
- if (aggregateSize > MAX_FOLDER_UPLOAD_TOTAL_SIZE) {
558
- runtime.client.sendRes({
559
- type: 'res',
560
- id: req.id,
561
- ok: false,
562
- error: `Folder too large: ${aggregateSize} bytes`,
563
- });
564
- return;
565
- }
566
- const finalPath = path.join(checkedTargetDir.path, ...item.relativePath.split('/'));
567
- const checkedFinalPath = runtime.resolveAuthorizedPath(finalPath, rootPath);
568
- if (!checkedFinalPath.ok) {
569
- runtime.client.sendRes({
570
- type: 'res',
571
- id: req.id,
572
- ok: false,
573
- error: checkedFinalPath.error,
574
- });
575
- return;
576
- }
577
- const tempPath = path.join(os.tmpdir(), `.shennian-upload-${transferId}-${files.size}`);
578
- fs.writeFileSync(tempPath, Buffer.alloc(0));
579
- files.set(item.relativePath, {
580
- relativePath: item.relativePath,
581
- tempPath,
582
- targetPath: checkedFinalPath.path,
583
- size: item.size,
584
- });
585
- }
586
- runtime.pendingTransfers.set(transferId, {
587
- tempPath: '',
588
- targetPath: checkedTargetDir.path,
589
- totalSize: aggregateSize,
590
- kind: 'folder',
591
- rootPath,
592
- targetDir: checkedTargetDir.path,
593
- files,
594
- });
595
- runtime.client.sendRes({
596
- type: 'res',
597
- id: req.id,
598
- ok: true,
599
- payload: { transferId, path: checkedTargetDir.path },
600
- });
601
- return;
602
- }
603
- const tempPath = path.join(os.tmpdir(), `.shennian-upload-${transferId}`);
604
- const finalPath = path.join(destinationDir, path.basename(transferName));
605
- fs.writeFileSync(tempPath, Buffer.alloc(0));
606
- runtime.pendingTransfers.set(transferId, {
607
- tempPath,
608
- targetPath: finalPath,
609
- totalSize,
610
- kind: 'file',
611
- });
612
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true, payload: { transferId } });
613
- }
614
- catch (err) {
615
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: String(err) });
616
- }
617
- }
618
- export async function handleFsTransferChunk(runtime, req) {
619
- const { transferId, offset, data, relativePath } = req.params;
620
- const transfer = runtime.pendingTransfers.get(transferId);
621
- if (!transfer) {
622
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Invalid transferId' });
623
- return;
624
- }
625
- try {
626
- const target = transfer.kind === 'folder'
627
- ? transfer.files?.get(String(relativePath || ''))
628
- : { tempPath: transfer.tempPath, size: transfer.totalSize };
629
- if (!target) {
630
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Invalid relativePath' });
631
- return;
632
- }
633
- const buffer = Buffer.from(data, 'base64');
634
- if (offset < 0 || offset + buffer.length > target.size) {
635
- runtime.client.sendRes({
636
- type: 'res',
637
- id: req.id,
638
- ok: false,
639
- error: 'Chunk exceeds declared size',
640
- });
641
- return;
642
- }
643
- const fd = fs.openSync(target.tempPath, 'r+');
644
- try {
645
- fs.writeSync(fd, buffer, 0, buffer.length, offset);
646
- }
647
- finally {
648
- fs.closeSync(fd);
649
- }
650
- runtime.client.sendRes({
651
- type: 'res',
652
- id: req.id,
653
- ok: true,
654
- payload: { written: buffer.length },
655
- });
656
- }
657
- catch (err) {
658
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: String(err) });
659
- }
660
- }
661
- export async function handleFsTransferFinish(runtime, req) {
662
- const { transferId } = req.params;
663
- const transfer = runtime.pendingTransfers.get(transferId);
664
- if (!transfer) {
665
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'Invalid transferId' });
666
- return;
667
- }
668
- try {
669
- if (transfer.kind === 'folder') {
670
- let count = 0;
671
- for (const file of transfer.files?.values() ?? []) {
672
- fs.mkdirSync(path.dirname(file.targetPath), { recursive: true });
673
- fs.renameSync(file.tempPath, file.targetPath);
674
- count += 1;
675
- }
676
- runtime.pendingTransfers.delete(transferId);
677
- runtime.client.sendRes({
678
- type: 'res',
679
- id: req.id,
680
- ok: true,
681
- payload: { path: transfer.targetDir ?? transfer.targetPath, count },
682
- });
683
- return;
684
- }
685
- fs.renameSync(transfer.tempPath, transfer.targetPath);
686
- runtime.pendingTransfers.delete(transferId);
687
- runtime.client.sendRes({
688
- type: 'res',
689
- id: req.id,
690
- ok: true,
691
- payload: { path: transfer.targetPath },
692
- });
693
- }
694
- catch {
695
- try {
696
- fs.copyFileSync(transfer.tempPath, transfer.targetPath);
697
- fs.unlinkSync(transfer.tempPath);
698
- runtime.pendingTransfers.delete(transferId);
699
- runtime.client.sendRes({
700
- type: 'res',
701
- id: req.id,
702
- ok: true,
703
- payload: { path: transfer.targetPath },
704
- });
705
- }
706
- catch (err) {
707
- runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: String(err) });
708
- }
709
- }
710
- }
711
- export async function handleFsTransferAbort(runtime, req) {
712
- const { transferId } = req.params;
713
- const transfer = runtime.pendingTransfers.get(transferId);
714
- if (transfer) {
715
- const tempPaths = transfer.kind === 'folder'
716
- ? Array.from(transfer.files?.values() ?? []).map((file) => file.tempPath)
717
- : [transfer.tempPath];
718
- for (const tempPath of tempPaths) {
719
- try {
720
- if (tempPath)
721
- fs.unlinkSync(tempPath);
722
- }
723
- catch {
724
- // ignore cleanup failures
725
- }
726
- }
727
- runtime.pendingTransfers.delete(transferId);
728
- }
729
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true });
730
- }
731
- export function cleanupPendingTransfers(runtime) {
732
- for (const [, transfer] of runtime.pendingTransfers) {
733
- const tempPaths = transfer.kind === 'folder'
734
- ? Array.from(transfer.files?.values() ?? []).map((file) => file.tempPath)
735
- : [transfer.tempPath];
736
- for (const tempPath of tempPaths) {
737
- try {
738
- if (tempPath)
739
- fs.unlinkSync(tempPath);
740
- }
741
- catch {
742
- // ignore cleanup failures
743
- }
744
- }
745
- }
746
- runtime.pendingTransfers.clear();
747
- }
1
+ import d from"node:fs";import g from"node:os";import p from"node:path";import{convertMarkdownToPdf as C,defaultPdfOutputPath as j,MarkdownPdfBrowserMissingError as O}from"../../tools/markdown-to-pdf.js";import{createZipArchive as L}from"../archive-zip.js";import{BinaryTextPreviewError as B,decodeTextBufferAuto as U,isAutoTextEncoding as X}from"../../fs/text-decoder.js";const D="__roots__",Z=2e3,H=1024*1024*1024,W=5e3,V=1024*1024*1024;function E(e){return/^[A-Za-z]:([\\/]|$)/.test(e)||/^\\\\[^\\]+\\[^\\]+/.test(e)}function v(e){return E(e)?p.win32:p.posix}function T(e){const t=d.statSync(e);return{name:v(e).basename(e),path:e,isDir:t.isDirectory(),size:t.isFile()?t.size:void 0,modifiedAt:t.mtimeMs}}function Y(){if(g.platform()==="win32"){const e=[];for(let t=65;t<=90;t+=1){const r=`${String.fromCharCode(t)}:\\`;d.existsSync(r)&&e.push({name:r,path:r,isDir:!0})}return e}return[{name:"/",path:"/",isDir:!0}]}function G(e,t){const r=typeof e=="object"&&e&&"code"in e?String(e.code):"";return r==="ENOENT"?`Directory not found: ${t}`:r==="EACCES"||r==="EPERM"?`Permission denied: ${t}`:e instanceof Error?e.message:String(e)}function J(e){if(!e||e==="."||e.trim()!==e||p.posix.isAbsolute(e)||p.win32.isAbsolute(e))return!1;const t=e.replace(/\\/g,"/");return t!==e?!1:t.split("/").every(r=>r&&r!=="."&&r!==".."&&!M(r))}function $(e){const t=e.trim();if(!t||t!==e||e==="."||e===".."||M(e)||/[\\/:]/.test(e)||/[<>|"?*]/.test(e)||/[. ]$/.test(e))return!1;const r=e.split(".")[0]?.toUpperCase();return!(r&&/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/.test(r))}function M(e){return Array.from(e).some(t=>{const r=t.charCodeAt(0);return r>=0&&r<=31})}function K(e){return Array.isArray(e)?e.map(t=>({relativePath:String(t.relativePath||""),size:Number(t.size||0),mimeType:t.mimeType,modifiedAt:t.modifiedAt})):[]}async function ne(e,t){const r=t.params.path||g.homedir(),a=t.params.rootPath||r;if(r===D){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:D,entries:Y()}});return}const n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}const s=n.path;try{const o=d.readdirSync(s,{withFileTypes:!0}),c=E(s)?p.win32.join:p.join,i=o.map(l=>({name:l.name,path:c(s,l.name),isDir:l.isDirectory()})).sort((l,f)=>l.isDir!==f.isDir?l.isDir?-1:1:l.name.localeCompare(f.name));e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:s,entries:i}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:G(o,s)})}}async function oe(e,t){const r=t.params.path,a=t.params.rootPath||r,n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}const s=n.path,o=t.params.encoding,c=typeof o=="string"?o:"utf8",i=t.params.offset,l=t.params.length;try{const f=d.statSync(s);if(!f.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}if(i!=null&&l!=null){const u=Math.min(l,Math.max(0,f.size-i));if(u<=0){e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:"",offset:i,length:0,totalSize:f.size,path:s}});return}const P=d.openSync(s,"r");try{const k=Buffer.alloc(u),S=d.readSync(P,k,0,u,i);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:k.subarray(0,S).toString("base64"),offset:i,length:S,totalSize:f.size,path:s}})}finally{d.closeSync(P)}return}const y=t.params.maxSize||(c==="base64"?5*1024*1024:512*1024);if(f.size>y){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`File too large: ${f.size} bytes (max ${y})`,payload:{size:f.size,path:s}});return}if(c==="base64"){const u=d.readFileSync(s);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{data:u.toString("base64"),path:s,size:f.size}});return}if(X(c)){const u=d.readFileSync(s),P=U(u);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:P.content,path:s,size:f.size,encoding:P.encoding,encodingFallback:P.fallback}});return}const m=d.readFileSync(s,"utf-8");e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{content:m,path:s,size:f.size}})}catch(f){e.client.sendRes({type:"res",id:t.id,ok:!1,error:f instanceof B?f.message:String(f)})}}async function ie(e,t){const r=t.params.path,a=t.params.content,n=t.params.rootPath||r;if(!r||typeof a!="string"){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and content are required"});return}const s=e.resolveAuthorizedPath(r,n);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}try{const o=d.existsSync(s.path)?d.statSync(s.path):null;if(o&&!o.isFile()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a file"});return}d.writeFileSync(s.path,a,"utf-8");const c=d.statSync(s.path);e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:s.path,size:c.size,modifiedAt:c.mtimeMs}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(o)})}}async function de(e,t){const r=t.params.path,a=t.params.newName;if(!r||!a){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path and newName are required"});return}if(!$(a)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid newName"});return}const n=v(r),s=t.params.rootPath||n.dirname(r),o=e.resolveAuthorizedPath(r,s);if(!o.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:o.error});return}try{const c=v(o.path),i=c.join(c.dirname(o.path),a),l=e.resolveAuthorizedPath(i,s);if(!l.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:l.error});return}if(d.existsSync(l.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Target already exists"});return}d.renameSync(o.path,l.path),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{oldPath:o.path,newPath:l.path,entry:T(l.path)}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function ce(e,t){const r=t.params.path,a=t.params.rootPath||p.dirname(r||"."),n=typeof t.params.title=="string"?t.params.title:void 0;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const s=e.resolveAuthorizedPath(r,a);if(!s.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s.error});return}if(!/\.mdx?$/i.test(s.path)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Only Markdown files can be exported to PDF"});return}const o=j(s.path),c=e.resolveAuthorizedPath(o,a);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}try{const i=await C(s.path,{outputPath:c.path,title:n});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:s.path,outputPath:i.outputPath,entry:T(i.outputPath)}})}catch(i){if(i instanceof O){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"This machine needs the PDF export component before Markdown files can be exported to PDF.",payload:i.setup});return}e.client.sendRes({type:"res",id:t.id,ok:!1,error:i instanceof Error?i.message:String(i)})}}function Q(e){let t="";for(const a of e.trim())a.charCodeAt(0)<=31||'<>:"/\\|?*'.includes(a)?t.endsWith("-")||(t+="-"):t+=a;return t.replace(/[. ]+$/g,"")||"folder"}async function le(e,t){const r=t.params.path,a=t.params.rootPath||r;if(!r){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"path is required"});return}const n=e.resolveAuthorizedPath(r,a);if(!n.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:n.error});return}try{if(!d.statSync(n.path).isDirectory()){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Not a directory"});return}const o=v(n.path),c=Q(o.basename(n.path)),i=p.join(g.tmpdir(),"shennian-archives"),l=p.join(i,`${c}-${Date.now()}-${Math.random().toString(36).slice(2,8)}.zip`),f=L(n.path,l,{maxFiles:W,maxTotalSize:V});e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{sourcePath:n.path,outputPath:f.outputPath,entry:T(f.outputPath),fileCount:f.fileCount,totalSize:f.totalSize}})}catch(s){e.client.sendRes({type:"res",id:t.id,ok:!1,error:s instanceof Error?s.message:String(s)})}}async function fe(e,t){const{name:r,targetPath:a,data:n,direct:s}=t.params;if(!r||!n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"name and data are required"});return}try{const o=t.params.rootPath||a||g.homedir(),c=e.resolveAuthorizedPath(a||o,o);if(!c.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:c.error});return}const i=c.path,l=s?i:p.join(i,".uploads");s||d.mkdirSync(l,{recursive:!0});const f=p.join(l,p.basename(r)),y=Buffer.from(n,"base64");d.writeFileSync(f,y),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:f}})}catch(o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(o)})}}async function pe(e,t){const{name:r,targetPath:a,totalSize:n,direct:s,kind:o,baseName:c}=t.params,i=K(t.params.manifest),l=o==="folder",f=l&&c||r;if(!f||!l&&!n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"name and totalSize are required"});return}try{const y=t.params.rootPath||a||g.homedir(),m=e.resolveAuthorizedPath(a||y,y);if(!m.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:m.error});return}const u=m.path,P=s?u:p.join(u,".uploads");s||d.mkdirSync(P,{recursive:!0});const k=`tf-${Date.now()}-${Math.random().toString(36).slice(2,8)}`;if(l){if(!i.length){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"manifest is required for folder uploads"});return}if(i.length>Z){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Too many files: ${i.length}`});return}const x=p.basename(f);if(!$(x)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid folder name"});return}const I=p.join(P,x),R=e.resolveAuthorizedPath(I,y);if(!R.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:R.error});return}const b=new Set;let z=0;const A=new Map;for(const h of i){if(!J(h.relativePath)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Invalid relativePath: ${h.relativePath}`});return}if(!Number.isFinite(h.size)||h.size<0){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Invalid size: ${h.relativePath}`});return}if(b.has(h.relativePath)){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Duplicate relativePath: ${h.relativePath}`});return}if(b.add(h.relativePath),z+=h.size,z>H){e.client.sendRes({type:"res",id:t.id,ok:!1,error:`Folder too large: ${z} bytes`});return}const _=p.join(R.path,...h.relativePath.split("/")),F=e.resolveAuthorizedPath(_,y);if(!F.ok){e.client.sendRes({type:"res",id:t.id,ok:!1,error:F.error});return}const w=p.join(g.tmpdir(),`.shennian-upload-${k}-${A.size}`);d.writeFileSync(w,Buffer.alloc(0)),A.set(h.relativePath,{relativePath:h.relativePath,tempPath:w,targetPath:F.path,size:h.size})}e.pendingTransfers.set(k,{tempPath:"",targetPath:R.path,totalSize:z,kind:"folder",rootPath:y,targetDir:R.path,files:A}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{transferId:k,path:R.path}});return}const S=p.join(g.tmpdir(),`.shennian-upload-${k}`),N=p.join(P,p.basename(f));d.writeFileSync(S,Buffer.alloc(0)),e.pendingTransfers.set(k,{tempPath:S,targetPath:N,totalSize:n,kind:"file"}),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{transferId:k}})}catch(y){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(y)})}}async function he(e,t){const{transferId:r,offset:a,data:n,relativePath:s}=t.params,o=e.pendingTransfers.get(r);if(!o){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid transferId"});return}try{const c=o.kind==="folder"?o.files?.get(String(s||"")):{tempPath:o.tempPath,size:o.totalSize};if(!c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid relativePath"});return}const i=Buffer.from(n,"base64");if(a<0||a+i.length>c.size){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Chunk exceeds declared size"});return}const l=d.openSync(c.tempPath,"r+");try{d.writeSync(l,i,0,i.length,a)}finally{d.closeSync(l)}e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{written:i.length}})}catch(c){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(c)})}}async function ye(e,t){const{transferId:r}=t.params,a=e.pendingTransfers.get(r);if(!a){e.client.sendRes({type:"res",id:t.id,ok:!1,error:"Invalid transferId"});return}try{if(a.kind==="folder"){let n=0;for(const s of a.files?.values()??[])d.mkdirSync(p.dirname(s.targetPath),{recursive:!0}),d.renameSync(s.tempPath,s.targetPath),n+=1;e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetDir??a.targetPath,count:n}});return}d.renameSync(a.tempPath,a.targetPath),e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetPath}})}catch{try{d.copyFileSync(a.tempPath,a.targetPath),d.unlinkSync(a.tempPath),e.pendingTransfers.delete(r),e.client.sendRes({type:"res",id:t.id,ok:!0,payload:{path:a.targetPath}})}catch(n){e.client.sendRes({type:"res",id:t.id,ok:!1,error:String(n)})}}}async function ue(e,t){const{transferId:r}=t.params,a=e.pendingTransfers.get(r);if(a){const n=a.kind==="folder"?Array.from(a.files?.values()??[]).map(s=>s.tempPath):[a.tempPath];for(const s of n)try{s&&d.unlinkSync(s)}catch{}e.pendingTransfers.delete(r)}e.client.sendRes({type:"res",id:t.id,ok:!0})}function Pe(e){for(const[,t]of e.pendingTransfers){const r=t.kind==="folder"?Array.from(t.files?.values()??[]).map(a=>a.tempPath):[t.tempPath];for(const a of r)try{a&&d.unlinkSync(a)}catch{}}e.pendingTransfers.clear()}export{Pe as cleanupPendingTransfers,le as handleFsArchiveZip,ce as handleFsExportMarkdownPdf,ne as handleFsLs,oe as handleFsRead,de as handleFsRename,fe as handleFsTransfer,ue as handleFsTransferAbort,he as handleFsTransferChunk,ye as handleFsTransferFinish,pe as handleFsTransferStart,ie as handleFsWrite};