@ai-support-agent/cli 0.1.30-beta.0 → 0.1.30-beta.2

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.
@@ -6,7 +6,7 @@ import { BrowserSessionManager } from '../mcp/tools/browser/browser-session-mana
6
6
  * API Server → Agent メッセージ
7
7
  */
8
8
  export interface VsCodeServerMessage {
9
- type: 'vscode_open' | 'vscode_close' | 'port_forward_open' | 'port_forward_close' | 'http_request' | 'ws_frame' | 'auth_success' | 'error' | 'browser_open' | 'browser_close' | 'browser_navigate' | 'browser_go_back' | 'browser_go_forward' | 'browser_reload' | 'browser_mouse_click' | 'browser_mouse_move' | 'browser_mouse_down' | 'browser_mouse_up' | 'browser_mouse_wheel' | 'browser_keyboard_type' | 'browser_keyboard_press' | 'browser_screenshot' | 'browser_viewport' | 'browser_execute_script' | 'browser_set_file' | 'browser_get_selection';
9
+ type: 'vscode_open' | 'vscode_close' | 'port_forward_open' | 'port_forward_close' | 'http_request' | 'ws_frame' | 'auth_success' | 'error' | 'browser_open' | 'browser_close' | 'browser_navigate' | 'browser_go_back' | 'browser_go_forward' | 'browser_reload' | 'browser_mouse_click' | 'browser_mouse_move' | 'browser_mouse_down' | 'browser_mouse_up' | 'browser_mouse_wheel' | 'browser_keyboard_type' | 'browser_keyboard_press' | 'browser_screenshot' | 'browser_viewport' | 'browser_execute_script' | 'browser_set_file' | 'browser_get_selection' | 'browser_set_input_value';
10
10
  filePaths?: string[];
11
11
  files?: Array<{
12
12
  name: string;
@@ -41,12 +41,15 @@ export interface VsCodeServerMessage {
41
41
  height?: number;
42
42
  deviceId?: string;
43
43
  script?: string;
44
+ value?: string;
45
+ selectionStart?: number;
46
+ selectionEnd?: number;
44
47
  }
45
48
  /**
46
49
  * Agent → API Server メッセージ
47
50
  */
48
51
  export interface VsCodeAgentMessage {
49
- type: 'vscode_ready' | 'vscode_stopped' | 'port_forward_ready' | 'port_forward_stopped' | 'http_response' | 'ws_frame' | 'error' | 'browser_ready' | 'browser_frame' | 'browser_screenshot_result' | 'browser_selection_result' | 'browser_action_log' | 'browser_stopped' | 'browser_script_result' | 'browser_script_progress' | 'browser_file_chooser_opened';
52
+ type: 'vscode_ready' | 'vscode_stopped' | 'port_forward_ready' | 'port_forward_stopped' | 'http_response' | 'ws_frame' | 'error' | 'browser_ready' | 'browser_frame' | 'browser_screenshot_result' | 'browser_selection_result' | 'browser_action_log' | 'browser_stopped' | 'browser_script_result' | 'browser_script_progress' | 'browser_file_chooser_opened' | 'browser_cursor_update' | 'browser_focus_changed';
50
53
  sessionId?: string;
51
54
  targetPort?: number;
52
55
  requestId?: string;
@@ -87,6 +90,26 @@ export interface VsCodeAgentMessage {
87
90
  step?: number;
88
91
  line?: string;
89
92
  script?: string;
93
+ /** CSS cursor value at the last mouse-move point (browser_cursor_update) */
94
+ cursor?: string;
95
+ focused?: boolean;
96
+ rect?: {
97
+ x: number;
98
+ y: number;
99
+ width: number;
100
+ height: number;
101
+ };
102
+ value?: string;
103
+ selectionStart?: number;
104
+ selectionEnd?: number;
105
+ multiline?: boolean;
106
+ inputType?: string;
107
+ maxLength?: number;
108
+ fontSize?: number;
109
+ lineHeight?: number;
110
+ textAlign?: string;
111
+ paddingTop?: number;
112
+ paddingLeft?: number;
90
113
  }
91
114
  /**
92
115
  * VS Code トンネル WebSocket
@@ -118,6 +141,11 @@ export declare class VsCodeTunnelWebSocket extends BaseWebSocketConnection<VsCod
118
141
  private browserLocalServer;
119
142
  private browserLocalPort;
120
143
  private readonly pendingFileChoosers;
144
+ /**
145
+ * Last CSS cursor value sent per browser session. Used to suppress redundant
146
+ * browser_cursor_update messages: only send when the cursor shape changes.
147
+ */
148
+ private readonly lastSentCursor;
121
149
  constructor(apiUrl: string, token: string, agentId: string, projectDir?: string | undefined,
122
150
  /**
123
151
  * code-server セッション起動時に最新の envVars を取り出す関数。
@@ -169,9 +197,55 @@ export declare class VsCodeTunnelWebSocket extends BaseWebSocketConnection<VsCod
169
197
  private handleBrowserKeyboardPress;
170
198
  private handleBrowserScreenshot;
171
199
  private handleBrowserGetSelection;
200
+ private handleBrowserSetInputValue;
172
201
  private handleBrowserExecuteScript;
173
202
  private handleBrowserViewport;
174
203
  private handleBrowserSetFile;
204
+ /**
205
+ * Branch 1: Agent FS paths chosen directly by the user via the workspace file
206
+ * explorer. These arrive as workspace-relative paths (e.g. `repos/app.ts`) —
207
+ * Playwright's setFiles requires absolute paths, so each is resolved against
208
+ * the workspace root before being forwarded. Already absolute paths are
209
+ * accepted as-is for backward compatibility, but every resolved path must
210
+ * stay inside the workspace root (traversal + symlink-escape guard).
211
+ */
212
+ private handleBrowserSetFileByPaths;
213
+ /**
214
+ * Branch 2: base64 file contents uploaded from the web client. Decode each
215
+ * into a temporary file on the agent host, hand the temp paths to setFiles,
216
+ * then always remove the temp files afterwards.
217
+ */
218
+ private handleBrowserSetFileByContent;
219
+ /**
220
+ * Resolve user-chosen file-explorer paths into absolute paths suitable for
221
+ * Playwright's `setFiles`, enforcing that every resolved path stays inside
222
+ * the agent workspace root.
223
+ *
224
+ * Resolution rules per path:
225
+ * - Absolute paths are kept as-is (backward compatibility for callers that
226
+ * already send absolute agent-FS paths).
227
+ * - Relative paths (e.g. `repos/app.ts`) are resolved against the workspace
228
+ * directory (`getWorkspaceDir(projectDir)`).
229
+ *
230
+ * Traversal guard (lexical): after resolution, the absolute path must be the
231
+ * workspace root itself or a descendant of it. `path.relative(workspaceDir,
232
+ * resolved)` starting with `..` (or being absolute) means the path escaped the
233
+ * workspace and is rejected. This rejects both relative escapes
234
+ * (`../../etc/passwd`) and absolute paths pointing outside the workspace.
235
+ *
236
+ * Symlink-escape guard (physical): a path that is lexically inside the
237
+ * workspace may still resolve, through a symlink, to a target OUTSIDE it
238
+ * (e.g. a `link` inside the workspace pointing at `../../.ssh/id_rsa`). After
239
+ * the lexical check, each existing path's real (canonical) location is
240
+ * resolved via `fs.realpath` and re-checked against the workspace root. Paths
241
+ * whose real location escapes the workspace are rejected. Non-existent paths
242
+ * cannot be canonicalized; they are left as-is (Playwright's setFiles will
243
+ * fail on them) rather than rejected here, preserving the lexical guarantee.
244
+ *
245
+ * Returns the resolved absolute paths on success, or an error message string
246
+ * describing why the upload was rejected (never silently dropped).
247
+ */
248
+ private resolveWorkspaceFilePaths;
175
249
  /**
176
250
  * Cancel a pending file chooser by applying an empty file list so the remote
177
251
  * `<input type=file>` is cleared rather than left pending. Failures are
@@ -1 +1 @@
1
- {"version":3,"file":"vscode-tunnel-websocket.d.ts","sourceRoot":"","sources":["../../src/vscode/vscode-tunnel-websocket.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vscode-tunnel-websocket.d.ts","sourceRoot":"","sources":["../../src/vscode/vscode-tunnel-websocket.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,51 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.VsCodeTunnelWebSocket = void 0;
40
+ const node_fs_1 = require("node:fs");
41
+ const os = __importStar(require("node:os"));
42
+ const path = __importStar(require("node:path"));
7
43
  const ws_1 = __importDefault(require("ws"));
8
44
  const base_websocket_1 = require("../base-websocket");
9
45
  const browser_local_server_1 = require("../browser/browser-local-server");
10
46
  const constants_1 = require("../constants");
11
47
  const logger_1 = require("../logger");
48
+ const project_dir_1 = require("../project-dir");
12
49
  const utils_1 = require("../utils");
13
50
  const browser_session_manager_1 = require("../mcp/tools/browser/browser-session-manager");
14
51
  const browser_security_1 = require("../mcp/tools/browser/browser-security");
@@ -24,6 +61,27 @@ const LIVE_VIEW_INTERVAL_MS = 200;
24
61
  * Guards against unbounded base64 payloads consuming agent memory.
25
62
  */
26
63
  const MAX_UPLOAD_BYTES = 10 * 1024 * 1024; // 10MB
64
+ /**
65
+ * Sanitize a client-supplied upload file name into a safe basename so it can be
66
+ * joined to a temp directory without escaping it (path-traversal defense).
67
+ *
68
+ * Strips any directory components (`path.basename`), then rejects path-relative
69
+ * tokens (`.`/`..`) and any remaining separator characters by replacing unsafe
70
+ * characters with `_`. Always returns a non-empty basename, falling back to
71
+ * `upload` when nothing safe remains.
72
+ */
73
+ function sanitizeUploadFileName(name) {
74
+ const raw = typeof name === 'string' ? name : '';
75
+ // Drop any directory portion the client may have included.
76
+ let base = path.basename(raw);
77
+ // Replace path separators and traversal-relevant characters; collapse control
78
+ // chars and leading dots that could hide the file or break paths.
79
+ base = base.replace(/[/\\]/g, '_').replace(/\0/g, '');
80
+ if (base === '' || base === '.' || base === '..') {
81
+ return 'upload';
82
+ }
83
+ return base;
84
+ }
27
85
  /**
28
86
  * VS Code トンネル WebSocket
29
87
  *
@@ -50,6 +108,11 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
50
108
  browserLocalServer = null;
51
109
  browserLocalPort = 0;
52
110
  pendingFileChoosers = new Map();
111
+ /**
112
+ * Last CSS cursor value sent per browser session. Used to suppress redundant
113
+ * browser_cursor_update messages: only send when the cursor shape changes.
114
+ */
115
+ lastSentCursor = new Map();
53
116
  constructor(apiUrl, token, agentId, projectDir,
54
117
  /**
55
118
  * code-server セッション起動時に最新の envVars を取り出す関数。
@@ -208,6 +271,9 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
208
271
  case 'browser_get_selection':
209
272
  void this.handleBrowserGetSelection(msg);
210
273
  break;
274
+ case 'browser_set_input_value':
275
+ void this.handleBrowserSetInputValue(msg);
276
+ break;
211
277
  default:
212
278
  logger_1.logger.debug(`[vscode-ws] Unknown message type: ${msg.type}`);
213
279
  }
@@ -452,6 +518,14 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
452
518
  this.sendMissingSessionIdError();
453
519
  return;
454
520
  }
521
+ // Reset any stale cursor state for this sessionId on (re)open. The idle-timeout
522
+ // auto-close path closes a session via BrowserSessionManager WITHOUT routing
523
+ // through handleBrowserClose/cleanup, so lastSentCursor can survive a closed
524
+ // session. If the same sessionId is later re-opened, a stale entry would make
525
+ // the first mouse-move's cursor look "unchanged" and suppress the initial
526
+ // browser_cursor_update, freezing the client cursor at the previous session's
527
+ // last shape. Deleting here guarantees the first move after (re)open is sent.
528
+ this.lastSentCursor.delete(sessionId);
455
529
  try {
456
530
  const session = await this.browserSessionManager.getOrCreate(sessionId);
457
531
  // Wire up action log notifications so in-process operations are relayed to Web UI
@@ -467,6 +541,11 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
467
541
  this.pendingFileChoosers.set(sessionId, accept);
468
542
  this.send({ type: 'browser_file_chooser_opened', sessionId });
469
543
  };
544
+ // Wire up focus-change notifications so the Web client can overlay a real
545
+ // input/textarea on the focused element (native caret + IME).
546
+ session.onFocusChange = (payload) => {
547
+ this.send({ type: 'browser_focus_changed', sessionId, ...payload });
548
+ };
470
549
  if (msg.conversationId) {
471
550
  this.browserSessionManager.linkConversation(msg.conversationId, sessionId);
472
551
  }
@@ -523,6 +602,7 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
523
602
  await this.browserSessionManager.close(sessionId);
524
603
  }
525
604
  this.pendingFileChoosers.delete(sessionId);
605
+ this.lastSentCursor.delete(sessionId);
526
606
  this.send({ type: 'browser_stopped', sessionId, reason: 'closed' });
527
607
  logger_1.logger.info(`[vscode-ws] Browser session closed: ${sessionId}`);
528
608
  }
@@ -598,8 +678,9 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
598
678
  }
599
679
  }
600
680
  async handleBrowserMouseMove(msg) {
601
- const session = msg.sessionId ? this.browserSessionManager.get(msg.sessionId) : undefined;
602
- if (!session || msg.x === undefined || msg.y === undefined)
681
+ const sessionId = msg.sessionId;
682
+ const session = sessionId ? this.browserSessionManager.get(sessionId) : undefined;
683
+ if (!session || !sessionId || msg.x === undefined || msg.y === undefined)
603
684
  return;
604
685
  try {
605
686
  await session.executeMouseMove(msg.x, msg.y);
@@ -609,7 +690,24 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
609
690
  // navigation) is intentionally logged but NOT surfaced as an `error` message
610
691
  // to the web client, to avoid error spam during drag-selection. Unlike
611
692
  // mouse_down / mouse_up, a dropped move does not leave the page in a broken state.
612
- logger_1.logger.warn(`[vscode-ws] mouseMove failed (session=${msg.sessionId}): ${(0, utils_1.getErrorMessage)(error)}`);
693
+ logger_1.logger.warn(`[vscode-ws] mouseMove failed (session=${sessionId}): ${(0, utils_1.getErrorMessage)(error)}`);
694
+ return;
695
+ }
696
+ // Best-effort cursor-shape mirroring: read the CSS cursor at the point and,
697
+ // only when it differs from the last value sent for this session, forward a
698
+ // browser_cursor_update. A failed read is logged (not silently swallowed)
699
+ // and skips the update for this frame rather than spamming an error message.
700
+ let cursor;
701
+ try {
702
+ cursor = await session.getCursorAt(msg.x, msg.y);
703
+ }
704
+ catch (error) {
705
+ logger_1.logger.warn(`[vscode-ws] getCursorAt failed (session=${sessionId}): ${(0, utils_1.getErrorMessage)(error)}`);
706
+ return;
707
+ }
708
+ if (this.lastSentCursor.get(sessionId) !== cursor) {
709
+ this.lastSentCursor.set(sessionId, cursor);
710
+ this.send({ type: 'browser_cursor_update', sessionId, cursor });
613
711
  }
614
712
  }
615
713
  async handleBrowserMouseDown(msg) {
@@ -715,6 +813,21 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
715
813
  this.send({ type: 'error', sessionId, message: `getSelection failed: ${(0, utils_1.getErrorMessage)(error)}` });
716
814
  }
717
815
  }
816
+ async handleBrowserSetInputValue(msg) {
817
+ const session = msg.sessionId ? this.browserSessionManager.get(msg.sessionId) : undefined;
818
+ if (!session)
819
+ return;
820
+ // The contract requires `value` to be a string; ignore malformed payloads.
821
+ if (typeof msg.value !== 'string')
822
+ return;
823
+ try {
824
+ await session.setFocusedInputValue(msg.value, msg.selectionStart, msg.selectionEnd);
825
+ }
826
+ catch (error) {
827
+ logger_1.logger.warn(`[vscode-ws] setInputValue failed (session=${msg.sessionId}): ${(0, utils_1.getErrorMessage)(error)}`);
828
+ this.send({ type: 'error', sessionId: msg.sessionId, message: `setInputValue failed: ${(0, utils_1.getErrorMessage)(error)}` });
829
+ }
830
+ }
718
831
  async handleBrowserExecuteScript(msg) {
719
832
  const sessionId = msg.sessionId;
720
833
  if (!sessionId || !msg.script) {
@@ -773,34 +886,197 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
773
886
  }
774
887
  // Consume the pending chooser up-front so it is not left dangling on any path.
775
888
  this.pendingFileChoosers.delete(sessionId);
776
- const files = msg.files ?? [];
889
+ // Outermost guard: any unexpected error (malformed payload, FS failure, a
890
+ // throwing `accept`) MUST surface as an `error` notification AND cancel the
891
+ // chooser, never escape as an unhandled rejection that leaves the remote
892
+ // `<input type=file>` stuck waiting. Each branch below already cancels on
893
+ // its own validation failures; this catch is the final backstop so a
894
+ // non-string filePath element, a non-array `files`, etc. cannot wedge the
895
+ // chooser.
896
+ try {
897
+ if (msg.files === undefined && msg.filePaths !== undefined) {
898
+ await this.handleBrowserSetFileByPaths(sessionId, accept, msg.filePaths);
899
+ return;
900
+ }
901
+ await this.handleBrowserSetFileByContent(sessionId, accept, msg.files);
902
+ }
903
+ catch (error) {
904
+ this.send({ type: 'error', sessionId, message: `File upload failed: ${(0, utils_1.getErrorMessage)(error)}` });
905
+ await this.cancelFileChooser(accept, sessionId);
906
+ }
907
+ }
908
+ /**
909
+ * Branch 1: Agent FS paths chosen directly by the user via the workspace file
910
+ * explorer. These arrive as workspace-relative paths (e.g. `repos/app.ts`) —
911
+ * Playwright's setFiles requires absolute paths, so each is resolved against
912
+ * the workspace root before being forwarded. Already absolute paths are
913
+ * accepted as-is for backward compatibility, but every resolved path must
914
+ * stay inside the workspace root (traversal + symlink-escape guard).
915
+ */
916
+ async handleBrowserSetFileByPaths(sessionId, accept, filePaths) {
917
+ // Explicit input validation (do not rely on a thrown TypeError): the payload
918
+ // must be an array of strings. Anything else is rejected + chooser cancelled.
919
+ if (!Array.isArray(filePaths) || filePaths.some((p) => typeof p !== 'string')) {
920
+ this.send({
921
+ type: 'error',
922
+ sessionId,
923
+ message: 'Invalid file payload: filePaths must be an array of strings',
924
+ });
925
+ await this.cancelFileChooser(accept, sessionId);
926
+ return;
927
+ }
928
+ const resolved = await this.resolveWorkspaceFilePaths(filePaths);
929
+ if (typeof resolved === 'string') {
930
+ // Resolution rejected the upload (escape attempt / missing workspace /
931
+ // symlink pointing outside). Surface the reason and cancel the chooser so
932
+ // the remote input is not left pending.
933
+ this.send({ type: 'error', sessionId, message: resolved });
934
+ await this.cancelFileChooser(accept, sessionId);
935
+ return;
936
+ }
937
+ try {
938
+ await accept(resolved);
939
+ }
940
+ catch (error) {
941
+ // Symmetry with the base64 branch: when setFiles rejects, surface the
942
+ // error AND cancel the chooser so the remote input is not left pending.
943
+ this.send({ type: 'error', sessionId, message: `File upload failed: ${(0, utils_1.getErrorMessage)(error)}` });
944
+ await this.cancelFileChooser(accept, sessionId);
945
+ }
946
+ }
947
+ /**
948
+ * Branch 2: base64 file contents uploaded from the web client. Decode each
949
+ * into a temporary file on the agent host, hand the temp paths to setFiles,
950
+ * then always remove the temp files afterwards.
951
+ */
952
+ async handleBrowserSetFileByContent(sessionId, accept, rawFiles) {
953
+ // Explicit input validation (do not rely on a thrown TypeError): `files`
954
+ // must be an array, and every entry must carry a string `dataBase64`.
955
+ if (rawFiles !== undefined && !Array.isArray(rawFiles)) {
956
+ this.send({ type: 'error', sessionId, message: 'Invalid file payload: files must be an array' });
957
+ await this.cancelFileChooser(accept, sessionId);
958
+ return;
959
+ }
960
+ const files = (rawFiles ?? []);
777
961
  // Validate every entry has a string dataBase64 before decoding so we never
778
962
  // pass undefined / non-string into Buffer.from.
779
- if (files.some((f) => typeof f.dataBase64 !== 'string')) {
963
+ if (files.some((f) => typeof f?.dataBase64 !== 'string')) {
780
964
  this.send({ type: 'error', sessionId, message: 'Invalid file payload: dataBase64 must be a string' });
781
965
  // Cancel the remote input so it is not left pending/half-filled.
782
966
  await this.cancelFileChooser(accept, sessionId);
783
967
  return;
784
968
  }
969
+ const safeFiles = files;
785
970
  // Estimate decoded size from base64 length and enforce the authoritative limit
786
971
  // before allocating buffers.
787
- const totalBytes = files.reduce((sum, f) => sum + Math.floor((f.dataBase64.length * 3) / 4), 0);
972
+ const totalBytes = safeFiles.reduce((sum, f) => sum + Math.floor((f.dataBase64.length * 3) / 4), 0);
788
973
  if (totalBytes > MAX_UPLOAD_BYTES) {
789
974
  this.send({ type: 'error', sessionId, message: 'File too large (max 10MB)' });
790
975
  await this.cancelFileChooser(accept, sessionId);
791
976
  return;
792
977
  }
793
- const payload = files.map((f) => ({
794
- name: f.name,
795
- mimeType: f.mimeType,
796
- buffer: Buffer.from(f.dataBase64, 'base64'),
797
- }));
978
+ // Create a unique temp directory for this upload so concurrent uploads and
979
+ // duplicate file names never collide.
980
+ let tmpDir;
981
+ try {
982
+ tmpDir = await node_fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'browser-upload-'));
983
+ }
984
+ catch (error) {
985
+ this.send({ type: 'error', sessionId, message: `File upload failed: ${(0, utils_1.getErrorMessage)(error)}` });
986
+ await this.cancelFileChooser(accept, sessionId);
987
+ return;
988
+ }
798
989
  try {
990
+ const tmpPaths = [];
991
+ for (let i = 0; i < safeFiles.length; i++) {
992
+ const file = safeFiles[i];
993
+ // Prefix with the index so distinct uploads sharing a sanitized name
994
+ // never overwrite one another within the temp dir.
995
+ const safeName = `${i}-${sanitizeUploadFileName(file.name)}`;
996
+ const tmpPath = path.join(tmpDir, safeName);
997
+ await node_fs_1.promises.writeFile(tmpPath, Buffer.from(file.dataBase64, 'base64'));
998
+ tmpPaths.push(tmpPath);
999
+ }
1000
+ const payload = tmpPaths;
799
1001
  await accept(payload);
800
1002
  }
801
1003
  catch (error) {
802
1004
  this.send({ type: 'error', sessionId, message: `File upload failed: ${(0, utils_1.getErrorMessage)(error)}` });
1005
+ await this.cancelFileChooser(accept, sessionId);
1006
+ }
1007
+ finally {
1008
+ // Always remove the temp files/dir once setFiles has resolved or rejected.
1009
+ await node_fs_1.promises.rm(tmpDir, { recursive: true, force: true }).catch((error) => {
1010
+ logger_1.logger.warn(`[vscode-ws] failed to remove temp upload dir ${tmpDir}: ${(0, utils_1.getErrorMessage)(error)}`);
1011
+ });
1012
+ }
1013
+ }
1014
+ /**
1015
+ * Resolve user-chosen file-explorer paths into absolute paths suitable for
1016
+ * Playwright's `setFiles`, enforcing that every resolved path stays inside
1017
+ * the agent workspace root.
1018
+ *
1019
+ * Resolution rules per path:
1020
+ * - Absolute paths are kept as-is (backward compatibility for callers that
1021
+ * already send absolute agent-FS paths).
1022
+ * - Relative paths (e.g. `repos/app.ts`) are resolved against the workspace
1023
+ * directory (`getWorkspaceDir(projectDir)`).
1024
+ *
1025
+ * Traversal guard (lexical): after resolution, the absolute path must be the
1026
+ * workspace root itself or a descendant of it. `path.relative(workspaceDir,
1027
+ * resolved)` starting with `..` (or being absolute) means the path escaped the
1028
+ * workspace and is rejected. This rejects both relative escapes
1029
+ * (`../../etc/passwd`) and absolute paths pointing outside the workspace.
1030
+ *
1031
+ * Symlink-escape guard (physical): a path that is lexically inside the
1032
+ * workspace may still resolve, through a symlink, to a target OUTSIDE it
1033
+ * (e.g. a `link` inside the workspace pointing at `../../.ssh/id_rsa`). After
1034
+ * the lexical check, each existing path's real (canonical) location is
1035
+ * resolved via `fs.realpath` and re-checked against the workspace root. Paths
1036
+ * whose real location escapes the workspace are rejected. Non-existent paths
1037
+ * cannot be canonicalized; they are left as-is (Playwright's setFiles will
1038
+ * fail on them) rather than rejected here, preserving the lexical guarantee.
1039
+ *
1040
+ * Returns the resolved absolute paths on success, or an error message string
1041
+ * describing why the upload was rejected (never silently dropped).
1042
+ */
1043
+ async resolveWorkspaceFilePaths(filePaths) {
1044
+ // A workspace root is required to both resolve relative paths and to bound
1045
+ // absolute ones. Without a project directory we cannot safely accept any
1046
+ // file-explorer path.
1047
+ if (!this.projectDir) {
1048
+ return 'Cannot resolve file paths: no project directory configured for this agent';
1049
+ }
1050
+ const workspaceDir = path.resolve((0, project_dir_1.getWorkspaceDir)(this.projectDir));
1051
+ // Canonicalize the workspace root too, so a symlinked workspace dir does not
1052
+ // produce false positives when comparing against realpath'd children.
1053
+ const realWorkspaceDir = await node_fs_1.promises.realpath(workspaceDir).catch(() => workspaceDir);
1054
+ const isInside = (root, target) => {
1055
+ const rel = path.relative(root, target);
1056
+ // Inside when the relative path stays under root: not climbing out (`..`)
1057
+ // and not forced absolute (different root/drive). An empty `rel` means the
1058
+ // path IS the root, which is allowed.
1059
+ return !rel.startsWith('..') && !path.isAbsolute(rel);
1060
+ };
1061
+ const resolved = [];
1062
+ for (const filePath of filePaths) {
1063
+ const absolute = path.isAbsolute(filePath)
1064
+ ? path.resolve(filePath)
1065
+ : path.resolve(workspaceDir, filePath);
1066
+ // Lexical guard first (cheap, also covers non-existent paths).
1067
+ if (!isInside(workspaceDir, absolute)) {
1068
+ return `Access denied: file path is outside the workspace: ${filePath}`;
1069
+ }
1070
+ // Physical guard: resolve symlinks and re-check. Missing paths cannot be
1071
+ // canonicalized — keep the lexically-validated absolute path (setFiles
1072
+ // will surface the non-existence downstream).
1073
+ const real = await node_fs_1.promises.realpath(absolute).catch(() => null);
1074
+ if (real !== null && !isInside(realWorkspaceDir, real)) {
1075
+ return `Access denied: file path is outside the workspace: ${filePath}`;
1076
+ }
1077
+ resolved.push(absolute);
803
1078
  }
1079
+ return resolved;
804
1080
  }
805
1081
  /**
806
1082
  * Cancel a pending file chooser by applying an empty file list so the remote
@@ -820,6 +1096,7 @@ class VsCodeTunnelWebSocket extends base_websocket_1.BaseWebSocketConnection {
820
1096
  // ブラウザセッションのクリーンアップ
821
1097
  void this.browserSessionManager.closeAll();
822
1098
  this.pendingFileChoosers.clear();
1099
+ this.lastSentCursor.clear();
823
1100
  // ブラウザローカルサーバーのクリーンアップ
824
1101
  if (this.browserLocalServer) {
825
1102
  void this.browserLocalServer.stop();