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