aws-runtime-bridge 1.3.9 → 1.5.0
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.
- package/README.md +9 -4
- package/dist/adapter/OpencodeSdkAdapter.d.ts +13 -1
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +56 -4
- package/dist/adapter/OpencodeSdkAdapter.test.js +57 -1
- package/dist/config.d.ts +14 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +103 -4
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +95 -0
- package/dist/routes/file-browser.d.ts +10 -0
- package/dist/routes/file-browser.d.ts.map +1 -1
- package/dist/routes/file-browser.js +206 -4
- package/dist/routes/file-browser.test.js +22 -0
- package/dist/routes/instance.d.ts +15 -0
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +25 -2
- package/dist/routes/instance.test.js +19 -0
- package/dist/services/aws-client-agent-mcp.test.js +1 -0
- package/dist/services/runtime-binding.d.ts.map +1 -1
- package/dist/services/runtime-binding.js +10 -3
- package/dist/services/runtime-binding.test.js +13 -1
- package/dist/services/session-output.d.ts +12 -0
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +15 -0
- package/dist/services/workspace-files.d.ts +72 -0
- package/dist/services/workspace-files.d.ts.map +1 -1
- package/dist/services/workspace-files.js +519 -21
- package/dist/services/workspace-files.test.js +387 -11
- package/dist/services/workspace-watch.d.ts +21 -0
- package/dist/services/workspace-watch.d.ts.map +1 -0
- package/dist/services/workspace-watch.js +123 -0
- package/dist/services/workspace-watch.test.d.ts +2 -0
- package/dist/services/workspace-watch.test.d.ts.map +1 -0
- package/dist/services/workspace-watch.test.js +38 -0
- package/package.json +8 -1
|
@@ -3,15 +3,79 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 提供文件系统浏览功能
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import { createReadStream, promises as fs } from 'node:fs';
|
|
7
8
|
import path from 'node:path';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
9
|
+
import { Router } from 'express';
|
|
10
|
+
import multer from 'multer';
|
|
10
11
|
import { allowHostFileBrowser } from '../config.js';
|
|
12
|
+
import { validateToken } from '../middleware/auth.js';
|
|
11
13
|
import { createLogger } from '../utils/logger.js';
|
|
12
|
-
import { createWorkspaceEntry, deleteWorkspaceEntry, listWorkspaceDirectory, readWorkspaceFile, renameWorkspaceEntry, writeWorkspaceFile, } from '../services/workspace-files.js';
|
|
14
|
+
import { createWorkspaceEntry, deleteWorkspaceEntry, extractWorkspaceArchive, listWorkspaceDirectory, previewWorkspaceDocument, readWorkspaceFile, renameWorkspaceEntry, resolveWorkspaceDownloadTarget, streamWorkspaceDirectoryZip, uploadWorkspaceFiles, writeWorkspaceFile, } from '../services/workspace-files.js';
|
|
15
|
+
import { unwatchWorkspaceFile, watchWorkspaceFile } from '../services/workspace-watch.js';
|
|
13
16
|
const log = createLogger('file-browser');
|
|
14
17
|
export const fileBrowserRouter = Router();
|
|
18
|
+
export const WORKSPACE_UPLOAD_FILE_LIMIT = 2000;
|
|
19
|
+
const upload = multer({
|
|
20
|
+
dest: path.join(os.tmpdir(), 'agentswork-runtime-bridge-uploads'),
|
|
21
|
+
limits: {
|
|
22
|
+
files: WORKSPACE_UPLOAD_FILE_LIMIT,
|
|
23
|
+
fileSize: 512 * 1024 * 1024
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
function toBoolean(value) {
|
|
27
|
+
return value === true || value === 'true' || value === '1';
|
|
28
|
+
}
|
|
29
|
+
function contentDispositionFileName(fileName) {
|
|
30
|
+
const fallbackName = fileName
|
|
31
|
+
.replace(/[\\"\r\n]/g, '_')
|
|
32
|
+
.replace(/[^\x20-\x7E]/g, '_');
|
|
33
|
+
return `attachment; filename="${fallbackName}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
|
|
34
|
+
}
|
|
35
|
+
async function cleanupUploadedFiles(files = []) {
|
|
36
|
+
await Promise.all(files.map((file) => fs.rm(file.path, { force: true }).catch(() => undefined)));
|
|
37
|
+
}
|
|
38
|
+
export function createWorkspaceUploadLimitResponse(error) {
|
|
39
|
+
return {
|
|
40
|
+
status: 400,
|
|
41
|
+
body: {
|
|
42
|
+
error: error.code === 'LIMIT_FILE_COUNT'
|
|
43
|
+
? `一次最多上传 ${WORKSPACE_UPLOAD_FILE_LIMIT} 个文件,请拆分文件夹后重试。`
|
|
44
|
+
: error.message,
|
|
45
|
+
code: error.code
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function parseWorkspaceUploadRelativePaths(body) {
|
|
50
|
+
const relativePathsJson = body.relativePathsJson;
|
|
51
|
+
if (typeof relativePathsJson === 'string' && relativePathsJson.trim()) {
|
|
52
|
+
const parsed = JSON.parse(relativePathsJson);
|
|
53
|
+
if (!Array.isArray(parsed)) {
|
|
54
|
+
throw new Error('relativePathsJson must be an array');
|
|
55
|
+
}
|
|
56
|
+
return parsed.map((relativePath) => String(relativePath || ''));
|
|
57
|
+
}
|
|
58
|
+
const relativePaths = body.relativePaths;
|
|
59
|
+
return Array.isArray(relativePaths)
|
|
60
|
+
? relativePaths.map((relativePath) => String(relativePath || ''))
|
|
61
|
+
: typeof relativePaths === 'string'
|
|
62
|
+
? [relativePaths]
|
|
63
|
+
: [];
|
|
64
|
+
}
|
|
65
|
+
function workspaceUploadMiddleware(req, res, next) {
|
|
66
|
+
upload.array('files')(req, res, (error) => {
|
|
67
|
+
if (!error) {
|
|
68
|
+
next();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (error instanceof multer.MulterError) {
|
|
72
|
+
const response = createWorkspaceUploadLimitResponse(error);
|
|
73
|
+
res.status(response.status).json(response.body);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
next(error);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
15
79
|
async function listHostDirectories(requestPath = '') {
|
|
16
80
|
const platform = process.platform;
|
|
17
81
|
const isWindows = platform === 'win32';
|
|
@@ -155,6 +219,61 @@ fileBrowserRouter.post('/workspace/read', validateToken, async (req, res) => {
|
|
|
155
219
|
res.status(400).json({ error: err.message });
|
|
156
220
|
}
|
|
157
221
|
});
|
|
222
|
+
/**
|
|
223
|
+
* 预览工作区文档内容。
|
|
224
|
+
* POST /api/file-browser/workspace/preview
|
|
225
|
+
*/
|
|
226
|
+
fileBrowserRouter.post('/workspace/preview', validateToken, async (req, res) => {
|
|
227
|
+
try {
|
|
228
|
+
const { workspacePath, filePath } = req.body || {};
|
|
229
|
+
const result = await previewWorkspaceDocument({
|
|
230
|
+
workspacePath: String(workspacePath || ''),
|
|
231
|
+
filePath: String(filePath || '')
|
|
232
|
+
});
|
|
233
|
+
res.json(result);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
const err = error;
|
|
237
|
+
log.error('failed to preview workspace document:', err);
|
|
238
|
+
res.status(400).json({ error: err.message });
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
/**
|
|
242
|
+
* 监听工作区文件变化,用于文档预览自动刷新。
|
|
243
|
+
* POST /api/file-browser/workspace/watch
|
|
244
|
+
*/
|
|
245
|
+
fileBrowserRouter.post('/workspace/watch', validateToken, async (req, res) => {
|
|
246
|
+
try {
|
|
247
|
+
const { workspacePath, filePath } = req.body || {};
|
|
248
|
+
res.json(await watchWorkspaceFile({
|
|
249
|
+
workspacePath: String(workspacePath || ''),
|
|
250
|
+
filePath: String(filePath || '')
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
const err = error;
|
|
255
|
+
log.error('failed to watch workspace file:', err);
|
|
256
|
+
res.status(400).json({ error: err.message });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
/**
|
|
260
|
+
* 取消监听工作区文件变化。
|
|
261
|
+
* POST /api/file-browser/workspace/unwatch
|
|
262
|
+
*/
|
|
263
|
+
fileBrowserRouter.post('/workspace/unwatch', validateToken, async (req, res) => {
|
|
264
|
+
try {
|
|
265
|
+
const { workspacePath, filePath } = req.body || {};
|
|
266
|
+
res.json(await unwatchWorkspaceFile({
|
|
267
|
+
workspacePath: String(workspacePath || ''),
|
|
268
|
+
filePath: String(filePath || '')
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
const err = error;
|
|
273
|
+
log.error('failed to unwatch workspace file:', err);
|
|
274
|
+
res.status(400).json({ error: err.message });
|
|
275
|
+
}
|
|
276
|
+
});
|
|
158
277
|
/**
|
|
159
278
|
* 保存工作区文件内容
|
|
160
279
|
* POST /api/file-browser/workspace/write
|
|
@@ -235,3 +354,86 @@ fileBrowserRouter.post('/workspace/delete', validateToken, async (req, res) => {
|
|
|
235
354
|
res.status(400).json({ error: err.message });
|
|
236
355
|
}
|
|
237
356
|
});
|
|
357
|
+
/**
|
|
358
|
+
* 上传文件到工作区指定目录,可选自动解压常见压缩包。
|
|
359
|
+
* POST /api/file-browser/workspace/upload
|
|
360
|
+
*/
|
|
361
|
+
fileBrowserRouter.post('/workspace/upload', validateToken, workspaceUploadMiddleware, async (req, res) => {
|
|
362
|
+
const files = (req.files || []);
|
|
363
|
+
try {
|
|
364
|
+
const { workspacePath, path: requestPath = '', extractArchives } = req.body || {};
|
|
365
|
+
const relativePaths = parseWorkspaceUploadRelativePaths(req.body || {});
|
|
366
|
+
const result = await uploadWorkspaceFiles({
|
|
367
|
+
workspacePath: String(workspacePath || ''),
|
|
368
|
+
targetPath: String(requestPath || ''),
|
|
369
|
+
extractArchives: toBoolean(extractArchives),
|
|
370
|
+
files: files.map((file, index) => ({
|
|
371
|
+
originalname: file.originalname,
|
|
372
|
+
relativePath: relativePaths[index],
|
|
373
|
+
path: file.path,
|
|
374
|
+
size: file.size
|
|
375
|
+
}))
|
|
376
|
+
});
|
|
377
|
+
res.json(result);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
const err = error;
|
|
381
|
+
log.error('failed to upload workspace files:', err);
|
|
382
|
+
res.status(400).json({ error: err.message });
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
await cleanupUploadedFiles(files);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
/**
|
|
389
|
+
* 下载工作区文件或目录;目录会实时打包为 zip。
|
|
390
|
+
* POST /api/file-browser/workspace/download
|
|
391
|
+
*/
|
|
392
|
+
fileBrowserRouter.post('/workspace/download', validateToken, async (req, res) => {
|
|
393
|
+
try {
|
|
394
|
+
const { workspacePath, targetPath } = req.body || {};
|
|
395
|
+
const target = await resolveWorkspaceDownloadTarget({
|
|
396
|
+
workspacePath: String(workspacePath || ''),
|
|
397
|
+
targetPath: String(targetPath || '')
|
|
398
|
+
});
|
|
399
|
+
res.setHeader('Content-Type', target.contentType);
|
|
400
|
+
res.setHeader('Content-Disposition', contentDispositionFileName(target.fileName));
|
|
401
|
+
res.setHeader('X-Workspace-Path', encodeURIComponent(target.workspacePath));
|
|
402
|
+
res.setHeader('X-Workspace-Target-Path', encodeURIComponent(target.targetPath));
|
|
403
|
+
if (target.isDirectory) {
|
|
404
|
+
await streamWorkspaceDirectoryZip(target.resolvedTargetPath, res);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
createReadStream(target.resolvedTargetPath).pipe(res);
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
const err = error;
|
|
411
|
+
log.error('failed to download workspace entry:', err);
|
|
412
|
+
if (!res.headersSent) {
|
|
413
|
+
res.status(400).json({ error: err.message });
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
res.destroy(err);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
/**
|
|
421
|
+
* 解压工作区内的压缩包到指定目录。
|
|
422
|
+
* POST /api/file-browser/workspace/extract
|
|
423
|
+
*/
|
|
424
|
+
fileBrowserRouter.post('/workspace/extract', validateToken, async (req, res) => {
|
|
425
|
+
try {
|
|
426
|
+
const { workspacePath, archivePath, outputPath = '' } = req.body || {};
|
|
427
|
+
const result = await extractWorkspaceArchive({
|
|
428
|
+
workspacePath: String(workspacePath || ''),
|
|
429
|
+
archivePath: String(archivePath || ''),
|
|
430
|
+
outputPath: String(outputPath || '')
|
|
431
|
+
});
|
|
432
|
+
res.json(result);
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
const err = error;
|
|
436
|
+
log.error('failed to extract workspace archive:', err);
|
|
437
|
+
res.status(400).json({ error: err.message });
|
|
438
|
+
}
|
|
439
|
+
});
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* File Browser 路由单元测试
|
|
3
3
|
*/
|
|
4
4
|
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import multer from 'multer';
|
|
6
|
+
import { createWorkspaceUploadLimitResponse, parseWorkspaceUploadRelativePaths, WORKSPACE_UPLOAD_FILE_LIMIT } from './file-browser.js';
|
|
5
7
|
describe('file-browser route validation', () => {
|
|
6
8
|
it('returns root drives on Windows when path is empty', () => {
|
|
7
9
|
const getRootForWindows = () => {
|
|
@@ -85,4 +87,24 @@ describe('file-browser response building', () => {
|
|
|
85
87
|
expect(directories).toEqual(['src']);
|
|
86
88
|
expect(files).toEqual(['package.json']);
|
|
87
89
|
});
|
|
90
|
+
it('returns JSON guidance when folder upload exceeds file count limit', () => {
|
|
91
|
+
const response = createWorkspaceUploadLimitResponse(new multer.MulterError('LIMIT_FILE_COUNT'));
|
|
92
|
+
expect(response.status).toBe(400);
|
|
93
|
+
expect(response.body).toEqual({
|
|
94
|
+
error: `一次最多上传 ${WORKSPACE_UPLOAD_FILE_LIMIT} 个文件,请拆分文件夹后重试。`,
|
|
95
|
+
code: 'LIMIT_FILE_COUNT'
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it('parses relative paths from a JSON manifest to preserve folder hierarchy', () => {
|
|
99
|
+
const relativePaths = parseWorkspaceUploadRelativePaths({
|
|
100
|
+
relativePathsJson: JSON.stringify(['a/b/c.txt', 'a/d/e.txt'])
|
|
101
|
+
});
|
|
102
|
+
expect(relativePaths).toEqual(['a/b/c.txt', 'a/d/e.txt']);
|
|
103
|
+
});
|
|
104
|
+
it('keeps backward compatibility with repeated relativePaths fields', () => {
|
|
105
|
+
const relativePaths = parseWorkspaceUploadRelativePaths({
|
|
106
|
+
relativePaths: ['a/b/c.txt', 'a/d/e.txt']
|
|
107
|
+
});
|
|
108
|
+
expect(relativePaths).toEqual(['a/b/c.txt', 'a/d/e.txt']);
|
|
109
|
+
});
|
|
88
110
|
});
|
|
@@ -18,9 +18,24 @@ export interface SchedulerPingFailureResponse {
|
|
|
18
18
|
schedulerBaseUrl: string;
|
|
19
19
|
hint: string;
|
|
20
20
|
}
|
|
21
|
+
interface AmbiguousSchedulerBaseUrlResponse {
|
|
22
|
+
ok: false;
|
|
23
|
+
error: "ambiguous_scheduler_base_url";
|
|
24
|
+
failureStage: "scheduler_ping";
|
|
25
|
+
runtimeBridge: "healthy";
|
|
26
|
+
schedulerBaseUrl: string;
|
|
27
|
+
autoRegisterTargetUrls: string[];
|
|
28
|
+
hint: string;
|
|
29
|
+
}
|
|
21
30
|
/**
|
|
22
31
|
* 构建调度中心 ping 失败响应。
|
|
23
32
|
* 主流程:保留底层错误与 scheduler 地址,同时明确 bridge 本身已连通、失败点在 bridge 回连调度中心。
|
|
24
33
|
*/
|
|
25
34
|
export declare function buildSchedulerPingFailureResponse(error: Error, configuredSchedulerBaseUrl: string): SchedulerPingFailureResponse;
|
|
35
|
+
/**
|
|
36
|
+
* Build the response for ambiguous scheduler targets.
|
|
37
|
+
* Main flow: never silently pick the first target; require an explicit scheduler URL or future scheduler identity routing.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildAmbiguousSchedulerBaseUrlResponse(targetUrls: string[]): AmbiguousSchedulerBaseUrlResponse;
|
|
40
|
+
export {};
|
|
26
41
|
//# sourceMappingURL=instance.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AA6BA,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B"}
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AA6BA,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;AAED;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,EAAE,GACnB,iCAAiC,CAUnC"}
|
package/dist/routes/instance.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Router } from "express";
|
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { validateToken } from "../middleware/auth.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveSchedulerBaseUrl } from "../config.js";
|
|
6
6
|
import { getRuntimeAccessToken } from "../services/runtime-binding.js";
|
|
7
7
|
import { loadInstanceState, saveInstanceState, } from "../services/instance-state.js";
|
|
8
8
|
import { initInstance } from "../services/instance-init-service.js";
|
|
@@ -91,6 +91,21 @@ export function buildSchedulerPingFailureResponse(error, configuredSchedulerBase
|
|
|
91
91
|
: "aws-runtime-bridge 已连通,但它回连调度中心失败。请确认 AWS_RUNTIME_SCHEDULER_BASE_URL 指向的调度中心地址可从 bridge 机器访问,并且运行时访问令牌仍有效。",
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the response for ambiguous scheduler targets.
|
|
96
|
+
* Main flow: never silently pick the first target; require an explicit scheduler URL or future scheduler identity routing.
|
|
97
|
+
*/
|
|
98
|
+
export function buildAmbiguousSchedulerBaseUrlResponse(targetUrls) {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
error: "ambiguous_scheduler_base_url",
|
|
102
|
+
failureStage: "scheduler_ping",
|
|
103
|
+
runtimeBridge: "healthy",
|
|
104
|
+
schedulerBaseUrl: "",
|
|
105
|
+
autoRegisterTargetUrls: targetUrls,
|
|
106
|
+
hint: "Multiple autoRegisterTargets.serverUrl values were found, so bridge cannot safely infer which scheduler /runtime/ping should call back. Set AWS_RUNTIME_SCHEDULER_BASE_URL explicitly, or use future schedulerId/token-bound multi-scheduler routing.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
94
109
|
instanceRouter.get("/healthz", (_req, res) => {
|
|
95
110
|
res.json({
|
|
96
111
|
ok: true,
|
|
@@ -103,8 +118,16 @@ instanceRouter.get("/connection-check", (req, res) => {
|
|
|
103
118
|
res.status(result.status).json(result.body);
|
|
104
119
|
});
|
|
105
120
|
instanceRouter.get("/ping", validateToken, async (_req, res) => {
|
|
121
|
+
const schedulerBaseUrlResolution = resolveSchedulerBaseUrl();
|
|
122
|
+
if (schedulerBaseUrlResolution.source === "ambiguous-auto-register-targets") {
|
|
123
|
+
res
|
|
124
|
+
.status(400)
|
|
125
|
+
.json(buildAmbiguousSchedulerBaseUrlResponse(schedulerBaseUrlResolution.ambiguousTargetUrls));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const schedulerBaseUrl = schedulerBaseUrlResolution.url;
|
|
106
129
|
try {
|
|
107
|
-
const runtimeAccessToken = getRuntimeAccessToken(undefined, schedulerBaseUrl)
|
|
130
|
+
const runtimeAccessToken = getRuntimeAccessToken(undefined, schedulerBaseUrl);
|
|
108
131
|
if (!runtimeAccessToken) {
|
|
109
132
|
res.status(401).json({ ok: false, error: "runtime_access_token_required" });
|
|
110
133
|
return;
|
|
@@ -119,4 +119,23 @@ describe('instance route validation', () => {
|
|
|
119
119
|
expect(response.runtimeBridge).toBe('healthy');
|
|
120
120
|
expect(response.hint).toContain('运行时访问令牌仍有效');
|
|
121
121
|
});
|
|
122
|
+
it('explains ambiguous auto-register scheduler targets without picking the first one', async () => {
|
|
123
|
+
const { buildAmbiguousSchedulerBaseUrlResponse } = await import('./instance.js');
|
|
124
|
+
const response = buildAmbiguousSchedulerBaseUrlResponse([
|
|
125
|
+
'http://scheduler-a.local:7380',
|
|
126
|
+
'http://scheduler-b.local:7380',
|
|
127
|
+
]);
|
|
128
|
+
expect(response).toEqual({
|
|
129
|
+
ok: false,
|
|
130
|
+
error: 'ambiguous_scheduler_base_url',
|
|
131
|
+
failureStage: 'scheduler_ping',
|
|
132
|
+
runtimeBridge: 'healthy',
|
|
133
|
+
schedulerBaseUrl: '',
|
|
134
|
+
autoRegisterTargetUrls: [
|
|
135
|
+
'http://scheduler-a.local:7380',
|
|
136
|
+
'http://scheduler-b.local:7380',
|
|
137
|
+
],
|
|
138
|
+
hint: expect.stringContaining('AWS_RUNTIME_SCHEDULER_BASE_URL'),
|
|
139
|
+
});
|
|
140
|
+
});
|
|
122
141
|
});
|
|
@@ -199,6 +199,7 @@ describe('aws-client-agent-mcp service', () => {
|
|
|
199
199
|
process.env.AWS_CLIENT_AGENT_MCP_COMMAND = 'aws-client-agent-mcp';
|
|
200
200
|
process.env.CUSTOM_SECRET = 'should-not-leak';
|
|
201
201
|
process.env.AWS_RUNTIME_HOME_DIR = mkdtempSync(path.join(os.tmpdir(), 'aws-mcp-config-'));
|
|
202
|
+
process.env.AWS_TEST_HOME = process.env.AWS_RUNTIME_HOME_DIR;
|
|
202
203
|
process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = '';
|
|
203
204
|
process.env.AWS_SERVER_URL = '';
|
|
204
205
|
process.env.AWS_MCP_HTTP_URL = '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA2DF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAqB5E;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAiCD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,CAYR;AAsCD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAWT;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,GAAG,SAAS,CAUpB;AAED,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,OAAO,CAeT;AAcD,wBAAgB,kBAAkB,IAAI,mBAAmB,CAgBxD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAClD,mBAAmB,EACnB,WAAW,GAAG,aAAa,CAC5B,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAWtB;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAcnE;AAED,wBAAgB,qBAAqB,CACnC,MAAM,CAAC,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,OAAO,GACtB,MAAM,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA2DF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAqB5E;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAiCD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,CAYR;AAsCD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAWT;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,GAAG,SAAS,CAUpB;AAED,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,OAAO,CAeT;AAcD,wBAAgB,kBAAkB,IAAI,mBAAmB,CAgBxD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAClD,mBAAmB,EACnB,WAAW,GAAG,aAAa,CAC5B,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAWtB;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAcnE;AAED,wBAAgB,qBAAqB,CACnC,MAAM,CAAC,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,OAAO,GACtB,MAAM,GAAG,SAAS,CAyBpB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAEjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,mBAAmB,CAwCtB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAkB1C"}
|
|
@@ -254,13 +254,20 @@ export function getRuntimeAccessToken(userId, serverBaseUrl) {
|
|
|
254
254
|
if (scopedToken) {
|
|
255
255
|
return scopedToken;
|
|
256
256
|
}
|
|
257
|
-
if (serverBaseUrl) {
|
|
258
|
-
return undefined;
|
|
259
|
-
}
|
|
260
257
|
const state = loadRuntimeBinding();
|
|
261
258
|
if (state.status !== "paired") {
|
|
262
259
|
return undefined;
|
|
263
260
|
}
|
|
261
|
+
if (serverBaseUrl) {
|
|
262
|
+
const requestedSchedulerBaseUrl = normalizeSchedulerBaseUrl(serverBaseUrl);
|
|
263
|
+
const boundSchedulerBaseUrl = normalizeSchedulerBaseUrl(state.schedulerBaseUrl);
|
|
264
|
+
if (requestedSchedulerBaseUrl &&
|
|
265
|
+
boundSchedulerBaseUrl &&
|
|
266
|
+
requestedSchedulerBaseUrl === boundSchedulerBaseUrl) {
|
|
267
|
+
return normalizeToken(state.accessToken) || undefined;
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
264
271
|
return normalizeToken(state.accessToken) || undefined;
|
|
265
272
|
}
|
|
266
273
|
export function validateRuntimePairingCode(code) {
|
|
@@ -2,7 +2,7 @@ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { buildRuntimeTokenKey, clearScopedRuntimeAccessToken, getScopedRuntimeAccessToken, normalizeSchedulerBaseUrl, saveScopedRuntimeAccessToken, } from "./runtime-binding.js";
|
|
5
|
+
import { buildRuntimeTokenKey, clearScopedRuntimeAccessToken, getRuntimeAccessToken, getScopedRuntimeAccessToken, normalizeSchedulerBaseUrl, saveScopedRuntimeAccessToken, } from "./runtime-binding.js";
|
|
6
6
|
const originalEnv = { ...process.env };
|
|
7
7
|
const tempRoots = [];
|
|
8
8
|
function useRuntimeHome() {
|
|
@@ -28,6 +28,18 @@ describe('runtime binding scheduler URL normalization', () => {
|
|
|
28
28
|
expect(normalizeSchedulerBaseUrl('ws://127.0.0.1:8080/ws/agent')).toBe('http://127.0.0.1:8080');
|
|
29
29
|
expect(normalizeSchedulerBaseUrl('wss://example.com/ws/agent')).toBe('https://example.com');
|
|
30
30
|
});
|
|
31
|
+
it("returns paired binding token only when requested scheduler URL matches", async () => {
|
|
32
|
+
useRuntimeHome();
|
|
33
|
+
const { saveRuntimeBinding } = await import("./runtime-binding.js");
|
|
34
|
+
saveRuntimeBinding({
|
|
35
|
+
accessToken: "paired-runtime-token-123456",
|
|
36
|
+
instanceId: "bridge-1",
|
|
37
|
+
userId: "user-a",
|
|
38
|
+
schedulerBaseUrl: "http://scheduler.local:7380",
|
|
39
|
+
});
|
|
40
|
+
expect(getRuntimeAccessToken(undefined, "http://scheduler.local:7380/api")).toBe("paired-runtime-token-123456");
|
|
41
|
+
expect(getRuntimeAccessToken(undefined, "http://scheduler.local:8080")).toBeUndefined();
|
|
42
|
+
});
|
|
31
43
|
it("scopes runtime tokens by user and full scheduler origin", () => {
|
|
32
44
|
useRuntimeHome();
|
|
33
45
|
const key7380 = saveScopedRuntimeAccessToken({
|
|
@@ -41,6 +41,18 @@ export declare function sendOutput(agentId: string, output: string, sessionId: s
|
|
|
41
41
|
* @param questions - 结构化问题列表
|
|
42
42
|
*/
|
|
43
43
|
export declare function sendQuestionRequest(agentId: string, sessionId: string, questions: unknown[]): Promise<void>;
|
|
44
|
+
export interface RuntimeFileChangedPayload {
|
|
45
|
+
bridgeBaseUrl?: string;
|
|
46
|
+
workspacePath: string;
|
|
47
|
+
filePath: string;
|
|
48
|
+
eventType: string;
|
|
49
|
+
size?: number;
|
|
50
|
+
mtimeMs?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 发送工作区文件变更事件到调度器,由调度器通过 WebSocket 通知前端刷新预览。
|
|
54
|
+
*/
|
|
55
|
+
export declare function sendFileChanged(payload: RuntimeFileChangedPayload): Promise<void>;
|
|
44
56
|
/**
|
|
45
57
|
* 调度输出刷新
|
|
46
58
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-output.d.ts","sourceRoot":"","sources":["../../src/services/session-output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAO3C,aAAa;AACb,eAAO,MAAM,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,EACjF,KAAK,CAAC,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACpD,OAAO,CAAC,IAAI,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAyBnC;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAAE,GACnB,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAa3D;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCzE"}
|
|
1
|
+
{"version":3,"file":"session-output.d.ts","sourceRoot":"","sources":["../../src/services/session-output.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAO3C,aAAa;AACb,eAAO,MAAM,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAa,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,EACjF,KAAK,CAAC,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACpD,OAAO,CAAC,IAAI,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAyBnC;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,EAAE,GACnB,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAcvF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAa3D;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCzE"}
|
|
@@ -81,6 +81,21 @@ export async function sendQuestionRequest(agentId, sessionId, questions) {
|
|
|
81
81
|
// ignore transient callback failures
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* 发送工作区文件变更事件到调度器,由调度器通过 WebSocket 通知前端刷新预览。
|
|
86
|
+
*/
|
|
87
|
+
export async function sendFileChanged(payload) {
|
|
88
|
+
try {
|
|
89
|
+
const headers = runtimeAuthHeaders();
|
|
90
|
+
if (!headers) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
await axios.post(`${schedulerBaseUrl}/api/runtime/callback/file-changed`, payload, { headers });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// ignore transient callback failures
|
|
97
|
+
}
|
|
98
|
+
}
|
|
84
99
|
/**
|
|
85
100
|
* 调度输出刷新
|
|
86
101
|
*
|
|
@@ -6,6 +6,7 @@ export interface WorkspaceFileItem {
|
|
|
6
6
|
name: string;
|
|
7
7
|
path: string;
|
|
8
8
|
isDirectory: boolean;
|
|
9
|
+
size?: number;
|
|
9
10
|
}
|
|
10
11
|
export interface ListWorkspaceDirectoryResult {
|
|
11
12
|
currentPath: string;
|
|
@@ -17,6 +18,36 @@ export interface ReadWorkspaceFileResult {
|
|
|
17
18
|
filePath: string;
|
|
18
19
|
content: string;
|
|
19
20
|
}
|
|
21
|
+
export interface WorkspaceDocumentPreviewResult {
|
|
22
|
+
workspacePath: string;
|
|
23
|
+
filePath: string;
|
|
24
|
+
kind: 'html' | 'unsupported';
|
|
25
|
+
content: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
size: number;
|
|
28
|
+
mtimeMs: number;
|
|
29
|
+
}
|
|
30
|
+
export interface WorkspaceDownloadTarget {
|
|
31
|
+
workspacePath: string;
|
|
32
|
+
targetPath: string;
|
|
33
|
+
resolvedTargetPath: string;
|
|
34
|
+
fileName: string;
|
|
35
|
+
contentType: string;
|
|
36
|
+
isDirectory: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface WorkspaceUploadFile {
|
|
39
|
+
originalname: string;
|
|
40
|
+
relativePath?: string;
|
|
41
|
+
path: string;
|
|
42
|
+
size: number;
|
|
43
|
+
}
|
|
44
|
+
export interface WorkspaceArchiveExtractResult {
|
|
45
|
+
ok: true;
|
|
46
|
+
workspacePath: string;
|
|
47
|
+
archivePath: string;
|
|
48
|
+
targetPath: string;
|
|
49
|
+
extractedEntries: number;
|
|
50
|
+
}
|
|
20
51
|
interface WorkspacePathParams {
|
|
21
52
|
workspacePath: string;
|
|
22
53
|
targetPath?: string;
|
|
@@ -39,6 +70,17 @@ interface RenameWorkspaceEntryParams extends WorkspacePathParams {
|
|
|
39
70
|
interface DeleteWorkspaceEntryParams extends WorkspacePathParams {
|
|
40
71
|
targetPath: string;
|
|
41
72
|
}
|
|
73
|
+
interface UploadWorkspaceFilesParams extends WorkspacePathParams {
|
|
74
|
+
files: WorkspaceUploadFile[];
|
|
75
|
+
extractArchives?: boolean;
|
|
76
|
+
}
|
|
77
|
+
interface DownloadWorkspaceEntryParams extends WorkspacePathParams {
|
|
78
|
+
targetPath: string;
|
|
79
|
+
}
|
|
80
|
+
interface ExtractWorkspaceArchiveParams extends WorkspacePathParams {
|
|
81
|
+
archivePath: string;
|
|
82
|
+
outputPath?: string;
|
|
83
|
+
}
|
|
42
84
|
/**
|
|
43
85
|
* 列出工作区目录内容。
|
|
44
86
|
*/
|
|
@@ -47,6 +89,10 @@ export declare function listWorkspaceDirectory(params: WorkspacePathParams): Pro
|
|
|
47
89
|
* 读取工作区中的文本文件。
|
|
48
90
|
*/
|
|
49
91
|
export declare function readWorkspaceFile(params: WorkspaceFileParams): Promise<ReadWorkspaceFileResult>;
|
|
92
|
+
/**
|
|
93
|
+
* 返回工作区内 Word 文档的预览元数据;docx 由前端下载原文件后渲染,旧版 doc 明确提示不支持。
|
|
94
|
+
*/
|
|
95
|
+
export declare function previewWorkspaceDocument(params: WorkspaceFileParams): Promise<WorkspaceDocumentPreviewResult>;
|
|
50
96
|
/**
|
|
51
97
|
* 将文本内容保存到工作区文件中。
|
|
52
98
|
*/
|
|
@@ -81,5 +127,31 @@ export declare function deleteWorkspaceEntry(params: DeleteWorkspaceEntryParams)
|
|
|
81
127
|
workspacePath: string;
|
|
82
128
|
targetPath: string;
|
|
83
129
|
}>;
|
|
130
|
+
/**
|
|
131
|
+
* 上传文件到工作区指定目录,并可在上传后自动解压常见归档。
|
|
132
|
+
*/
|
|
133
|
+
export declare function uploadWorkspaceFiles(params: UploadWorkspaceFilesParams): Promise<{
|
|
134
|
+
ok: true;
|
|
135
|
+
workspacePath: string;
|
|
136
|
+
targetPath: string;
|
|
137
|
+
files: Array<{
|
|
138
|
+
fileName: string;
|
|
139
|
+
targetPath: string;
|
|
140
|
+
size: number;
|
|
141
|
+
extracted?: WorkspaceArchiveExtractResult;
|
|
142
|
+
}>;
|
|
143
|
+
}>;
|
|
144
|
+
/**
|
|
145
|
+
* 解析工作区下载目标;目录由路由层打包为 zip,文件直接流式下载。
|
|
146
|
+
*/
|
|
147
|
+
export declare function resolveWorkspaceDownloadTarget(params: DownloadWorkspaceEntryParams): Promise<WorkspaceDownloadTarget>;
|
|
148
|
+
/**
|
|
149
|
+
* 将目录内容以 zip 格式写入输出流,避免在磁盘生成临时包。
|
|
150
|
+
*/
|
|
151
|
+
export declare function streamWorkspaceDirectoryZip(directoryPath: string, output: NodeJS.WritableStream): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* 解压工作区中的归档文件到指定目录,支持 zip、tar、tar.gz/tgz 与单文件 gz。
|
|
154
|
+
*/
|
|
155
|
+
export declare function extractWorkspaceArchive(params: ExtractWorkspaceArchiveParams): Promise<WorkspaceArchiveExtractResult>;
|
|
84
156
|
export {};
|
|
85
157
|
//# sourceMappingURL=workspace-files.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-files.d.ts","sourceRoot":"","sources":["../../src/services/workspace-files.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"workspace-files.d.ts","sourceRoot":"","sources":["../../src/services/workspace-files.ts"],"names":[],"mappings":"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"}
|