aws-runtime-bridge 1.4.0 → 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.
@@ -11,7 +11,7 @@
11
11
  * 参考:spectrai-community/src/main/adapter/OpenCodeSdkAdapter.ts
12
12
  */
13
13
  import { EventEmitter } from 'node:events';
14
- import type { BaseProviderAdapter, AdapterSessionConfig, ConversationMessage, SessionStatus } from './types.js';
14
+ import type { AdapterSessionConfig, BaseProviderAdapter, ConversationMessage, SessionStatus } from './types.js';
15
15
  export declare class OpencodeSdkAdapter extends EventEmitter implements BaseProviderAdapter {
16
16
  readonly providerId = "opencode";
17
17
  readonly displayName = "OpenCode";
@@ -39,6 +39,18 @@ export declare class OpencodeSdkAdapter extends EventEmitter implements BaseProv
39
39
  */
40
40
  private loadSdk;
41
41
  private waitForServer;
42
+ /**
43
+ * 记录 OpenCode serve 启动阶段输出,用于进程提前退出时返回可诊断错误。
44
+ */
45
+ private recordStartupOutput;
46
+ /**
47
+ * 将 OpenCode serve 提前退出信息转成前端可直接展示的错误文本。
48
+ */
49
+ private formatStartupExitMessage;
50
+ /**
51
+ * 异步投递启动提示,避免首轮 OpenCode 处理耗时导致 /runtime/start 被前端误判为启动失败。
52
+ */
53
+ private dispatchInitialPrompt;
42
54
  private startSseLoop;
43
55
  private runSseLoop;
44
56
  private handleOpenCodeEvent;
@@ -1 +1 @@
1
- {"version":3,"file":"OpencodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/OpencodeSdkAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EAGpB,mBAAmB,EACnB,aAAa,EACd,MAAM,YAAY,CAAC;AAsGpB,qBAAa,kBAAmB,SAAQ,YAAa,YAAW,mBAAmB;IACjF,QAAQ,CAAC,UAAU,cAAc;IACjC,QAAQ,CAAC,WAAW,cAAc;IAElC,OAAO,CAAC,QAAQ,CAA2C;IAC3D,OAAO,CAAC,SAAS,CAAC,CAAoB;IAEhC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgI5E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwC9D,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBnE,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7F,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAOnG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAclD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BxD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,EAAE;IAIzD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3D,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAOpD,OAAO,IAAI,IAAI;IAaf;;;OAGG;YACW,OAAO;YA6BP,aAAa;IAmB3B,OAAO,CAAC,YAAY;YAYN,UAAU;IAuBxB,OAAO,CAAC,mBAAmB;IA6H3B,OAAO,CAAC,gBAAgB;IA6GxB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,2BAA2B;CAOpC"}
1
+ {"version":3,"file":"OpencodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/OpencodeSdkAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAc3C,OAAO,KAAK,EACV,oBAAoB,EAEpB,mBAAmB,EACnB,mBAAmB,EAEnB,aAAa,EACd,MAAM,YAAY,CAAC;AA4GpB,qBAAa,kBAAmB,SAAQ,YAAa,YAAW,mBAAmB;IACjF,QAAQ,CAAC,UAAU,cAAc;IACjC,QAAQ,CAAC,WAAW,cAAc;IAElC,OAAO,CAAC,QAAQ,CAA2C;IAC3D,OAAO,CAAC,SAAS,CAAC,CAAoB;IAEhC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+I5E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwC9D,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBnE,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7F,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAUnG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAclD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BxD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,EAAE;IAIzD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3D,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAOpD,OAAO,IAAI,IAAI;IAaf;;;OAGG;YACW,OAAO;YA6BP,aAAa;IAmB3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAahC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,YAAY;YAYN,UAAU;IAuBxB,OAAO,CAAC,mBAAmB;IA6H3B,OAAO,CAAC,gBAAgB;IA6GxB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,2BAA2B;CAOpC"}
@@ -132,11 +132,16 @@ export class OpencodeSdkAdapter extends EventEmitter {
132
132
  shell: useShell,
133
133
  });
134
134
  session.serverProcess = proc;
135
+ session.startupOutput = [];
135
136
  proc.stdout?.on('data', (d) => {
136
- console.debug(`[OpencodeSdkAdapter] serve stdout: ${d.toString().slice(0, 200)}`);
137
+ const text = d.toString();
138
+ this.recordStartupOutput(session, text);
139
+ console.debug(`[OpencodeSdkAdapter] serve stdout: ${text.slice(0, 200)}`);
137
140
  });
138
141
  proc.stderr?.on('data', (d) => {
139
- console.debug(`[OpencodeSdkAdapter] serve stderr: ${d.toString().slice(0, 200)}`);
142
+ const text = d.toString();
143
+ this.recordStartupOutput(session, text);
144
+ console.debug(`[OpencodeSdkAdapter] serve stderr: ${text.slice(0, 200)}`);
140
145
  });
141
146
  proc.on('error', (err) => {
142
147
  console.error(`[OpencodeSdkAdapter] Server process error:`, err);
@@ -155,6 +160,9 @@ export class OpencodeSdkAdapter extends EventEmitter {
155
160
  console.log(`[OpencodeSdkAdapter] Server exited with code ${code}`);
156
161
  session.sseActive = false;
157
162
  });
163
+ const exitDuringStartup = new Promise((resolve) => {
164
+ proc.once('exit', (code, signal) => resolve({ code, signal }));
165
+ });
158
166
  // 步骤 3:创建 SDK 客户端
159
167
  const sdk = await this.loadSdk();
160
168
  const client = sdk.createOpencodeClient({
@@ -162,7 +170,12 @@ export class OpencodeSdkAdapter extends EventEmitter {
162
170
  });
163
171
  session.client = client;
164
172
  // 步骤 4:等待服务器就绪
165
- await this.waitForServer(client, config.workingDirectory, 10_000, port);
173
+ await Promise.race([
174
+ this.waitForServer(client, config.workingDirectory, 10_000, port),
175
+ exitDuringStartup.then((exitInfo) => {
176
+ throw new Error(this.formatStartupExitMessage(exitInfo, session.startupOutput));
177
+ }),
178
+ ]);
166
179
  console.log(`[OpencodeSdkAdapter] ★★★ OpenCode server ready ★★★ port=${port}`);
167
180
  // 步骤 5:创建 OpenCode 会话
168
181
  const createResult = await client.session.create({
@@ -182,7 +195,7 @@ export class OpencodeSdkAdapter extends EventEmitter {
182
195
  this.emit('status-change', sessionId, 'waiting_input');
183
196
  // 步骤 7:发送初始 Prompt
184
197
  if (config.initialPrompt) {
185
- await this.sendMessage(sessionId, config.initialPrompt);
198
+ this.dispatchInitialPrompt(sessionId, config.initialPrompt);
186
199
  }
187
200
  }
188
201
  catch (err) {
@@ -264,6 +277,9 @@ export class OpencodeSdkAdapter extends EventEmitter {
264
277
  const session = this.sessions.get(sessionId);
265
278
  if (session) {
266
279
  session.idleCommands = commands;
280
+ if (session.adapterSession.status === 'waiting_input' && (commands.idleInputCommand || commands.nonInputCommand)) {
281
+ this.startIdleDetection(sessionId);
282
+ }
267
283
  }
268
284
  }
269
285
  async abortCurrentTurn(sessionId) {
@@ -382,6 +398,42 @@ export class OpencodeSdkAdapter extends EventEmitter {
382
398
  }
383
399
  throw new Error(`OpenCode server (port ${port}) did not start within ${timeoutMs}ms`);
384
400
  }
401
+ /**
402
+ * 记录 OpenCode serve 启动阶段输出,用于进程提前退出时返回可诊断错误。
403
+ */
404
+ recordStartupOutput(session, text) {
405
+ const normalized = text.trim();
406
+ if (!normalized)
407
+ return;
408
+ session.startupOutput = [...(session.startupOutput ?? []), normalized].slice(-8);
409
+ }
410
+ /**
411
+ * 将 OpenCode serve 提前退出信息转成前端可直接展示的错误文本。
412
+ */
413
+ formatStartupExitMessage(exitInfo, startupOutput) {
414
+ const exitReason = exitInfo.signal
415
+ ? `signal ${exitInfo.signal}`
416
+ : `code ${exitInfo.code ?? 'unknown'}`;
417
+ const output = (startupOutput ?? []).join('\n').trim();
418
+ if (!output) {
419
+ return `OpenCode server exited during startup (${exitReason}). Please check whether opencode serve can run in this workspace.`;
420
+ }
421
+ return `OpenCode server exited during startup (${exitReason}): ${output}`;
422
+ }
423
+ /**
424
+ * 异步投递启动提示,避免首轮 OpenCode 处理耗时导致 /runtime/start 被前端误判为启动失败。
425
+ */
426
+ dispatchInitialPrompt(sessionId, initialPrompt) {
427
+ void this.sendMessage(sessionId, initialPrompt).catch((err) => {
428
+ const msg = err instanceof Error ? err.message : String(err);
429
+ this.emitEvent({
430
+ type: 'error',
431
+ sessionId,
432
+ timestamp: new Date().toISOString(),
433
+ data: { text: `OpenCode initial prompt failed: ${msg}` },
434
+ });
435
+ });
436
+ }
385
437
  startSseLoop(sessionId, session) {
386
438
  session.sseActive = true;
387
439
  const ac = new AbortController();
@@ -1,5 +1,9 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
2
  import { OpencodeSdkAdapter } from './OpencodeSdkAdapter.js';
3
+ afterEach(() => {
4
+ vi.useRealTimers();
5
+ vi.restoreAllMocks();
6
+ });
3
7
  describe('OpencodeSdkAdapter', () => {
4
8
  it('should expose opencode provider identity', () => {
5
9
  const adapter = new OpencodeSdkAdapter();
@@ -16,4 +20,56 @@ describe('OpencodeSdkAdapter', () => {
16
20
  const prompt = Reflect.get(adapter, 'toIdlePrompt').call(adapter, 'system: 醒来了吗?使用get_profile获取自己的信息,继续未完成的任务或使用poll_message阻塞获取消息');
17
21
  expect(prompt).toBe('请调用 poll_message 工具阻塞等待新消息;收到消息后再处理消息内容。');
18
22
  });
23
+ it('starts idle detection when commands are set after OpenCode is already waiting for input', async () => {
24
+ vi.useFakeTimers();
25
+ const adapter = new OpencodeSdkAdapter();
26
+ const sentMessages = [];
27
+ Reflect.set(adapter, 'sessions', new Map([
28
+ [
29
+ 'session-1',
30
+ {
31
+ adapterSession: {
32
+ sessionId: 'session-1',
33
+ status: 'waiting_input',
34
+ messages: [],
35
+ createdAt: new Date().toISOString(),
36
+ totalUsage: { inputTokens: 0, outputTokens: 0 },
37
+ },
38
+ config: {
39
+ command: 'opencode',
40
+ workingDirectory: process.cwd(),
41
+ autoAccept: true,
42
+ },
43
+ sseActive: false,
44
+ pendingPermissions: new Map(),
45
+ emittedToolStarts: new Set(),
46
+ workingDirectory: process.cwd(),
47
+ userMessageIds: new Set(),
48
+ currentAssistantText: '',
49
+ seenTextPartIds: new Set(),
50
+ },
51
+ ],
52
+ ]));
53
+ vi.spyOn(adapter, 'sendMessage').mockImplementation(async (_sessionId, message) => {
54
+ sentMessages.push(message);
55
+ });
56
+ adapter.setIdleCommands('session-1', {
57
+ idleInputCommand: 'system: 使用poll_message阻塞获取消息',
58
+ nonInputCommand: '',
59
+ });
60
+ await vi.advanceTimersByTimeAsync(500);
61
+ expect(sentMessages).toEqual(['请调用 poll_message 工具阻塞等待新消息;收到消息后再处理消息内容。']);
62
+ });
63
+ it('includes serve startup output when the OpenCode process exits early', () => {
64
+ const adapter = new OpencodeSdkAdapter();
65
+ const message = Reflect.get(adapter, 'formatStartupExitMessage').call(adapter, { code: 1, signal: null }, ['missing auth configuration']);
66
+ expect(message).toContain('OpenCode server exited during startup (code 1)');
67
+ expect(message).toContain('missing auth configuration');
68
+ });
69
+ it('dispatches the initial prompt asynchronously so launch can return after session creation', () => {
70
+ const adapter = new OpencodeSdkAdapter();
71
+ const sendMessage = vi.spyOn(adapter, 'sendMessage').mockResolvedValue(undefined);
72
+ Reflect.get(adapter, 'dispatchInitialPrompt').call(adapter, 'session-1', 'hello');
73
+ expect(sendMessage).toHaveBeenCalledWith('session-1', 'hello');
74
+ });
19
75
  });
@@ -3,5 +3,15 @@
3
3
  *
4
4
  * 提供文件系统浏览功能
5
5
  */
6
+ import multer from 'multer';
6
7
  export declare const fileBrowserRouter: import("express-serve-static-core").Router;
8
+ export declare const WORKSPACE_UPLOAD_FILE_LIMIT = 2000;
9
+ export declare function createWorkspaceUploadLimitResponse(error: multer.MulterError): {
10
+ status: number;
11
+ body: {
12
+ error: string;
13
+ code: string;
14
+ };
15
+ };
16
+ export declare function parseWorkspaceUploadRelativePaths(body: Record<string, unknown>): string[];
7
17
  //# sourceMappingURL=file-browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-browser.d.ts","sourceRoot":"","sources":["../../src/routes/file-browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmBH,eAAO,MAAM,iBAAiB,4CAAW,CAAC"}
1
+ {"version":3,"file":"file-browser.d.ts","sourceRoot":"","sources":["../../src/routes/file-browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAsB5B,eAAO,MAAM,iBAAiB,4CAAW,CAAC;AAE1C,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAqChD,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAUvI;AAED,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAgBzF"}
@@ -3,15 +3,79 @@
3
3
  *
4
4
  * 提供文件系统浏览功能
5
5
  */
6
- import { Router } from 'express';
6
+ import os from 'node:os';
7
+ import { createReadStream, promises as fs } from 'node:fs';
7
8
  import path from 'node:path';
8
- import { promises as fs } from 'node:fs';
9
- import { validateToken } from '../middleware/auth.js';
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
  });
@@ -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;AASH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,4BAA4B;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,mBAAmB;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,mBAAmB;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,wBAAyB,SAAQ,mBAAmB;IAC5D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,0BAA2B,SAAQ,mBAAmB;IAC9D,UAAU,EAAE,MAAM,CAAC;CACpB;AA+FD;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAoB/G;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAerG;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAWzI;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAiCxF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAkCtF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAelE"}
1
+ {"version":3,"file":"workspace-files.d.ts","sourceRoot":"","sources":["../../src/services/workspace-files.ts"],"names":[],"mappings":"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"}