@bahulam/code 0.1.13 → 0.1.14

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,6 +6,7 @@ import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
7
 
8
8
  const DEFAULT_MAX_ENTRIES = 300;
9
+ const DEFAULT_MAX_SEARCH_RESULTS = 200;
9
10
  const DEFAULT_MAX_TEXT_BYTES = 256 * 1024;
10
11
  export const DEFAULT_MAX_RAW_BYTES = 100 * 1024 * 1024;
11
12
 
@@ -42,8 +43,8 @@ const EXTENSION_FILE_TYPES = new Map([
42
43
  ['env', { kind: 'config', language: 'plaintext', label: 'Environment', textLike: true }],
43
44
  ['properties', { kind: 'config', language: 'plaintext', label: 'Properties', textLike: true }],
44
45
  ['xml', { kind: 'code', language: 'xml', label: 'XML', textLike: true }],
45
- ['html', { kind: 'code', language: 'html', label: 'HTML', textLike: true }],
46
- ['htm', { kind: 'code', language: 'html', label: 'HTML', textLike: true }],
46
+ ['html', { kind: 'web', language: 'html', label: 'HTML', textLike: true }],
47
+ ['htm', { kind: 'web', language: 'html', label: 'HTML', textLike: true }],
47
48
  ['css', { kind: 'code', language: 'css', label: 'CSS', textLike: true }],
48
49
  ['scss', { kind: 'code', language: 'scss', label: 'SCSS', textLike: true }],
49
50
  ['sass', { kind: 'code', language: 'scss', label: 'Sass', textLike: true }],
@@ -110,6 +111,18 @@ const EXTENSION_FILE_TYPES = new Map([
110
111
  ['svg', { kind: 'image', language: 'xml', label: 'SVG Image', textLike: false }],
111
112
  ['bmp', { kind: 'image', language: null, label: 'Bitmap Image', textLike: false }],
112
113
  ['ico', { kind: 'image', language: null, label: 'Icon', textLike: false }],
114
+ ['avif', { kind: 'image', language: null, label: 'AVIF Image', textLike: false }],
115
+ ['mp4', { kind: 'video', language: null, label: 'MP4 Video', textLike: false }],
116
+ ['webm', { kind: 'video', language: null, label: 'WebM Video', textLike: false }],
117
+ ['mov', { kind: 'video', language: null, label: 'QuickTime Video', textLike: false }],
118
+ ['m4v', { kind: 'video', language: null, label: 'M4V Video', textLike: false }],
119
+ ['ogv', { kind: 'video', language: null, label: 'Ogg Video', textLike: false }],
120
+ ['mp3', { kind: 'audio', language: null, label: 'MP3 Audio', textLike: false }],
121
+ ['m4a', { kind: 'audio', language: null, label: 'M4A Audio', textLike: false }],
122
+ ['wav', { kind: 'audio', language: null, label: 'WAV Audio', textLike: false }],
123
+ ['ogg', { kind: 'audio', language: null, label: 'Ogg Audio', textLike: false }],
124
+ ['flac', { kind: 'audio', language: null, label: 'FLAC Audio', textLike: false }],
125
+ ['aac', { kind: 'audio', language: null, label: 'AAC Audio', textLike: false }],
113
126
  ['pdf', { kind: 'pdf', language: null, label: 'PDF', textLike: false }],
114
127
  ['xlsx', { kind: 'spreadsheet', language: null, label: 'Excel Workbook', textLike: false }],
115
128
  ['xls', { kind: 'spreadsheet', language: null, label: 'Excel Workbook', textLike: false }],
@@ -165,12 +178,16 @@ export function listWorkspacePath(session, requestedPath = '.', { maxEntries = D
165
178
  try {
166
179
  childStat = fs.statSync(fullPath);
167
180
  } catch {}
181
+ const file = childStat?.isFile() ? describeFile(root, fullPath, childStat) : null;
168
182
  return {
169
183
  name: entry.name,
170
184
  path: normalizeRelative(root, fullPath),
171
185
  type: entry.isDirectory() ? 'directory' : entry.isFile() ? 'file' : 'other',
172
186
  size: childStat?.size ?? null,
173
187
  updated_at: childStat?.mtime ? childStat.mtime.toISOString() : null,
188
+ kind: file?.kind ?? (entry.isDirectory() ? 'directory' : null),
189
+ viewer: file?.viewer ?? (entry.isDirectory() ? 'directory' : null),
190
+ label: file?.label ?? null,
174
191
  };
175
192
  });
176
193
 
@@ -201,6 +218,88 @@ export function listWorkspacePath(session, requestedPath = '.', { maxEntries = D
201
218
  };
202
219
  }
203
220
 
221
+ export function searchWorkspaceFiles(session, {
222
+ query = '',
223
+ kinds = [],
224
+ maxResults = DEFAULT_MAX_SEARCH_RESULTS,
225
+ } = {}) {
226
+ const root = fs.realpathSync(session.root_path);
227
+ const wanted = new Set((Array.isArray(kinds) ? kinds : String(kinds || '').split(','))
228
+ .map((kind) => String(kind || '').trim().toLowerCase())
229
+ .filter(Boolean)
230
+ .filter((kind) => kind !== 'all'));
231
+ const term = String(query || '').trim().toLowerCase();
232
+ const limit = Math.max(1, Math.min(1000, Number(maxResults) || DEFAULT_MAX_SEARCH_RESULTS));
233
+ const results = [];
234
+
235
+ function visit(dir) {
236
+ if (results.length >= limit) return;
237
+ let entries = [];
238
+ try {
239
+ entries = fs.readdirSync(dir, { withFileTypes: true });
240
+ } catch {
241
+ return;
242
+ }
243
+ entries.sort((a, b) => {
244
+ if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
245
+ return a.name.localeCompare(b.name);
246
+ });
247
+
248
+ for (const entry of entries) {
249
+ if (results.length >= limit) break;
250
+ if (shouldHideEntry(entry.name)) continue;
251
+ const fullPath = path.join(dir, entry.name);
252
+ let stat = null;
253
+ try {
254
+ stat = fs.statSync(fullPath);
255
+ } catch {
256
+ continue;
257
+ }
258
+ const rel = normalizeRelative(root, fullPath);
259
+ if (entry.isDirectory()) {
260
+ if ((!term || entry.name.toLowerCase().includes(term) || rel.toLowerCase().includes(term))
261
+ && (!wanted.size || wanted.has('directory'))) {
262
+ results.push({
263
+ name: entry.name,
264
+ path: rel,
265
+ type: 'directory',
266
+ size: null,
267
+ updated_at: stat.mtime.toISOString(),
268
+ kind: 'directory',
269
+ viewer: 'directory',
270
+ });
271
+ }
272
+ visit(fullPath);
273
+ continue;
274
+ }
275
+ if (!entry.isFile()) continue;
276
+ const file = describeFile(root, fullPath, stat);
277
+ if (wanted.size && !wanted.has(file.kind) && !wanted.has(file.viewer)) continue;
278
+ if (term && !entry.name.toLowerCase().includes(term) && !rel.toLowerCase().includes(term)) continue;
279
+ results.push({
280
+ name: entry.name,
281
+ path: rel,
282
+ type: 'file',
283
+ size: stat.size,
284
+ updated_at: stat.mtime.toISOString(),
285
+ kind: file.kind,
286
+ viewer: file.viewer,
287
+ label: file.label,
288
+ });
289
+ }
290
+ }
291
+
292
+ visit(root);
293
+ return {
294
+ ok: true,
295
+ type: 'search',
296
+ query: term,
297
+ kinds: [...wanted],
298
+ results,
299
+ truncated: results.length >= limit,
300
+ };
301
+ }
302
+
204
303
  export function readWorkspaceFile(session, requestedPath, { maxBytes = DEFAULT_MAX_TEXT_BYTES } = {}) {
205
304
  const target = resolveWorkspacePath(session, requestedPath);
206
305
  const stat = fs.statSync(target);
@@ -252,6 +351,18 @@ export function contentTypeForPath(filePath) {
252
351
  case 'svg': return 'image/svg+xml';
253
352
  case 'bmp': return 'image/bmp';
254
353
  case 'ico': return 'image/x-icon';
354
+ case 'avif': return 'image/avif';
355
+ case 'mp4': return 'video/mp4';
356
+ case 'webm': return 'video/webm';
357
+ case 'mov': return 'video/quicktime';
358
+ case 'm4v': return 'video/x-m4v';
359
+ case 'ogv': return 'video/ogg';
360
+ case 'mp3': return 'audio/mpeg';
361
+ case 'm4a': return 'audio/mp4';
362
+ case 'wav': return 'audio/wav';
363
+ case 'ogg': return 'audio/ogg';
364
+ case 'flac': return 'audio/flac';
365
+ case 'aac': return 'audio/aac';
255
366
  case 'pdf': return 'application/pdf';
256
367
  case 'txt':
257
368
  case 'log':
@@ -351,7 +462,10 @@ function viewerForKind(kind) {
351
462
  case 'mermaid': return 'mermaid';
352
463
  case 'drawio': return 'drawio';
353
464
  case 'image': return 'image';
465
+ case 'video': return 'video';
466
+ case 'audio': return 'audio';
354
467
  case 'pdf': return 'pdf';
468
+ case 'web': return 'web';
355
469
  case 'spreadsheet': return 'spreadsheet';
356
470
  case 'document': return 'document';
357
471
  case 'presentation': return 'presentation';
@@ -19,8 +19,8 @@ import {
19
19
  DEFAULT_MAX_RAW_BYTES,
20
20
  contentTypeForPath,
21
21
  listWorkspacePath,
22
- readWorkspaceFile,
23
22
  resolveWorkspacePath,
23
+ searchWorkspaceFiles,
24
24
  } from './file-access.mjs';
25
25
  import {
26
26
  createOfficePdfPreview,
@@ -45,6 +45,7 @@ const DEFAULT_MAX_UPLOAD_FILES = 20;
45
45
  const DEFAULT_MAX_UPLOAD_FILE_BYTES = DEFAULT_MAX_RAW_BYTES;
46
46
  const DEFAULT_MAX_UPLOAD_BODY_BYTES = 140 * 1024 * 1024;
47
47
  const DEFAULT_MAX_SAVE_BYTES = 2 * 1024 * 1024;
48
+ const DEFAULT_MAX_MEDIA_BYTES = 1024 * 1024 * 1024;
48
49
 
49
50
  export async function startLocalWorkspaceService({
50
51
  session,
@@ -364,6 +365,19 @@ async function routeRequest({ req, res, sessionId, token, events, sseClients, em
364
365
  return;
365
366
  }
366
367
 
368
+ if (req.method === 'GET' && url.pathname === '/api/workspace/search') {
369
+ const query = url.searchParams.get('q') || '';
370
+ const kinds = (url.searchParams.get('kind') || url.searchParams.get('kinds') || '')
371
+ .split(',')
372
+ .map((item) => item.trim())
373
+ .filter(Boolean);
374
+ const maxResults = envInt('BAHULAM_LOCAL_SEARCH_MAX_RESULTS', 200);
375
+ const result = searchWorkspaceFiles(session, { query, kinds, maxResults });
376
+ emit('workspace_searched', { query: result.query, kinds: result.kinds, count: result.results.length });
377
+ sendJson(res, 200, result);
378
+ return;
379
+ }
380
+
367
381
  if (req.method === 'POST' && url.pathname === '/api/files/upload') {
368
382
  const body = await readJsonBody(req, envInt('BAHULAM_LOCAL_UPLOAD_MAX_BODY_BYTES', DEFAULT_MAX_UPLOAD_BODY_BYTES));
369
383
  const result = saveUploadedFiles(session, body);
@@ -402,15 +416,8 @@ async function routeRequest({ req, res, sessionId, token, events, sseClients, em
402
416
  err.code = 'BAD_REQUEST';
403
417
  throw err;
404
418
  }
405
- const bytes = readWorkspaceFile(session, requestedPath, { maxBytes: DEFAULT_MAX_RAW_BYTES });
406
- emit('file_read', { path: requestedPath, bytes: bytes.length });
407
- res.writeHead(200, {
408
- 'Content-Type': contentTypeForPath(requestedPath),
409
- 'Cache-Control': 'no-store',
410
- 'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; img-src data: blob:",
411
- 'X-Content-Type-Options': 'nosniff',
412
- });
413
- res.end(bytes);
419
+ sendWorkspaceRawFile({ req, res, session, requestedPath });
420
+ emit('file_read', { path: requestedPath, streamed: true });
414
421
  return;
415
422
  }
416
423
 
@@ -691,7 +698,9 @@ const PLUGIN_VIEW_MIME = {
691
698
  js: 'text/javascript; charset=utf-8', mjs: 'text/javascript; charset=utf-8',
692
699
  json: 'application/json', map: 'application/json',
693
700
  png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',
694
- webp: 'image/webp', svg: 'image/svg+xml', ico: 'image/x-icon',
701
+ webp: 'image/webp', svg: 'image/svg+xml', ico: 'image/x-icon', avif: 'image/avif',
702
+ mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime', m4v: 'video/x-m4v', ogv: 'video/ogg',
703
+ mp3: 'audio/mpeg', m4a: 'audio/mp4', wav: 'audio/wav', ogg: 'audio/ogg', flac: 'audio/flac', aac: 'audio/aac',
695
704
  woff: 'font/woff', woff2: 'font/woff2', ttf: 'font/ttf',
696
705
  wasm: 'application/wasm',
697
706
  };
@@ -801,8 +810,7 @@ function saveUploadedFiles(session, body = {}) {
801
810
  }
802
811
 
803
812
  const root = fs.realpathSync(session.root_path);
804
- const group = `${new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}-${crypto.randomBytes(3).toString('hex')}`;
805
- const directory = path.posix.join(LOCAL_UPLOAD_DIR, safePathSegment(session.id), group);
813
+ const directory = LOCAL_UPLOAD_DIR;
806
814
  const uploadDir = path.join(root, ...directory.split('/'));
807
815
  fs.mkdirSync(uploadDir, { recursive: true });
808
816
 
@@ -880,6 +888,72 @@ function uniqueUploadTarget(uploadDir, fileName) {
880
888
  return path.join(uploadDir, `${parsed.name}-${crypto.randomBytes(4).toString('hex')}${parsed.ext}`);
881
889
  }
882
890
 
891
+ function sendWorkspaceRawFile({ req, res, session, requestedPath }) {
892
+ const target = resolveWorkspacePath(session, requestedPath);
893
+ const stat = fs.statSync(target);
894
+ if (!stat.isFile()) {
895
+ const err = new Error('Requested path is not a file');
896
+ err.code = 'NOT_FILE';
897
+ throw err;
898
+ }
899
+
900
+ const contentType = contentTypeForPath(target);
901
+ const mediaLike = /^(audio|video)\//.test(contentType);
902
+ const maxBytes = mediaLike
903
+ ? envInt('BAHULAM_LOCAL_MEDIA_MAX_BYTES', DEFAULT_MAX_MEDIA_BYTES)
904
+ : DEFAULT_MAX_RAW_BYTES;
905
+ if (stat.size > maxBytes) {
906
+ const err = new Error(`File is larger than ${maxBytes} bytes`);
907
+ err.code = 'FILE_TOO_LARGE';
908
+ throw err;
909
+ }
910
+
911
+ const commonHeaders = {
912
+ 'Content-Type': contentType,
913
+ 'Cache-Control': 'no-store',
914
+ 'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; img-src data: blob:; media-src 'self' blob:",
915
+ 'X-Content-Type-Options': 'nosniff',
916
+ };
917
+ const range = parseRangeHeader(req.headers.range, stat.size);
918
+ if (range) {
919
+ res.writeHead(206, {
920
+ ...commonHeaders,
921
+ 'Accept-Ranges': 'bytes',
922
+ 'Content-Range': `bytes ${range.start}-${range.end}/${stat.size}`,
923
+ 'Content-Length': range.end - range.start + 1,
924
+ });
925
+ fs.createReadStream(target, { start: range.start, end: range.end }).pipe(res);
926
+ return;
927
+ }
928
+
929
+ res.writeHead(200, {
930
+ ...commonHeaders,
931
+ 'Accept-Ranges': mediaLike ? 'bytes' : 'none',
932
+ 'Content-Length': stat.size,
933
+ });
934
+ fs.createReadStream(target).pipe(res);
935
+ }
936
+
937
+ function parseRangeHeader(header, size) {
938
+ const value = String(header || '').trim();
939
+ if (!value || !value.startsWith('bytes=')) return null;
940
+ const first = value.slice(6).split(',')[0]?.trim() || '';
941
+ const match = first.match(/^(\d*)-(\d*)$/);
942
+ if (!match) return null;
943
+ let start = match[1] ? Number(match[1]) : null;
944
+ let end = match[2] ? Number(match[2]) : null;
945
+ if (start == null && end == null) return null;
946
+ if (start == null) {
947
+ const suffix = Math.max(0, Math.min(size, end || 0));
948
+ start = Math.max(0, size - suffix);
949
+ end = size - 1;
950
+ } else {
951
+ end = end == null ? size - 1 : Math.min(end, size - 1);
952
+ }
953
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end < start || start >= size) return null;
954
+ return { start, end };
955
+ }
956
+
883
957
  function sendJson(res, status, body) {
884
958
  res.writeHead(status, {
885
959
  'Content-Type': 'application/json; charset=utf-8',
@@ -981,6 +1055,18 @@ function staticContentType(filePath) {
981
1055
  case '.jpeg': return 'image/jpeg';
982
1056
  case '.gif': return 'image/gif';
983
1057
  case '.webp': return 'image/webp';
1058
+ case '.avif': return 'image/avif';
1059
+ case '.mp4': return 'video/mp4';
1060
+ case '.webm': return 'video/webm';
1061
+ case '.mov': return 'video/quicktime';
1062
+ case '.m4v': return 'video/x-m4v';
1063
+ case '.ogv': return 'video/ogg';
1064
+ case '.mp3': return 'audio/mpeg';
1065
+ case '.m4a': return 'audio/mp4';
1066
+ case '.wav': return 'audio/wav';
1067
+ case '.ogg': return 'audio/ogg';
1068
+ case '.flac': return 'audio/flac';
1069
+ case '.aac': return 'audio/aac';
984
1070
  case '.ttf': return 'font/ttf';
985
1071
  case '.woff': return 'font/woff';
986
1072
  case '.woff2': return 'font/woff2';
@@ -1070,11 +1156,11 @@ button,textarea,input{font:inherit}button{color:inherit}.shell{display:flex;flex
1070
1156
  .menubar{height:28px;display:flex;align-items:center;gap:2px;border-bottom:1px solid rgba(27,27,27,.06);background:#F7F5EF;padding:0 12px;flex:0 0 auto;user-select:none;position:relative;z-index:20}.menu-item{height:22px;border:0;background:transparent;border-radius:var(--ws-radius-sm);padding:0 8px;color:rgba(27,27,27,.58);font-size:12px;font-weight:500;cursor:pointer}.menu-item:hover{background:var(--ws-hover);color:rgba(27,27,27,.86)}.menubar-spacer{flex:1}.session-menu-wrap{display:flex;align-items:center}.session-menu-button{max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-modal-backdrop,.approval-modal-backdrop{position:fixed;inset:0;z-index:80;display:flex;align-items:flex-start;justify-content:center;background:rgba(27,27,27,.28);padding:92px 16px 24px}.approval-modal-backdrop{z-index:90;background:rgba(27,27,27,.36)}.session-modal-backdrop[hidden],.approval-modal-backdrop[hidden]{display:none}.session-dialog,.approval-dialog{width:min(620px,100%);max-height:min(620px,calc(100vh - 128px));display:flex;flex-direction:column;overflow:hidden;border:1px solid rgba(27,27,27,.12);border-radius:var(--ws-radius-md);background:#FFFDF7;box-shadow:0 24px 70px rgba(27,27,27,.22)}.approval-dialog{width:min(680px,100%)}.session-dialog-head,.approval-dialog-head{height:48px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-bottom:1px solid var(--ws-border-subtle);padding:0 12px}.session-dialog-title,.approval-dialog-title{font-size:13px;font-weight:600;color:rgba(27,27,27,.84)}.session-dialog-subtitle,.approval-dialog-subtitle{margin-top:1px;font:12px/1.45 var(--sans);color:rgba(27,27,27,.46);max-width:560px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-menu{display:block;overflow:auto;padding:6px}.session-menu-empty{padding:10px;color:rgba(27,27,27,.42);font-size:12px}.approval-body{padding:12px;overflow:auto}.approval-subject{border:1px solid var(--ws-border);border-radius:var(--ws-radius-md);background:#fff;padding:10px;font:12px/1.45 var(--mono);color:rgba(27,27,27,.72);white-space:pre-wrap;overflow-wrap:anywhere}.approval-reason{margin-top:10px;color:rgba(27,27,27,.58);font-size:12px;line-height:1.5}.approval-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;border-top:1px solid var(--ws-border-subtle);padding:10px 12px;background:#F7F5EF}.approval-actions .danger{border-color:rgba(185,28,28,.24);color:#B91C1C}.approval-actions .primary-action{background:#1B1B1B;color:#FFFDF7;border-color:#1B1B1B}.approval-inline{width:100%;max-width:960px;border:1px solid rgba(217,119,6,.22);border-radius:var(--ws-radius-md);background:#FFFBEB;overflow:hidden;box-shadow:0 8px 24px rgba(146,64,14,.08)}.approval-inline.done{border-color:rgba(27,27,27,.08);background:#F7F5EF;box-shadow:none}.approval-inline-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 11px;border-bottom:1px solid rgba(217,119,6,.14)}.approval-inline-title{font-size:13px;font-weight:600;color:#92400E}.approval-inline-meta{font:11px var(--sans);color:rgba(146,64,14,.62);white-space:nowrap}.approval-inline-subject{margin:10px 11px 0;border:1px solid rgba(217,119,6,.18);border-radius:var(--ws-radius-md);background:#fff;padding:9px;font:11px/1.45 var(--mono);color:rgba(27,27,27,.74);white-space:pre-wrap;overflow-wrap:anywhere;max-height:180px;overflow:auto}.approval-inline-reason{padding:8px 11px 10px;color:rgba(27,27,27,.58);font-size:12px;line-height:1.45}.approval-inline-actions{display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap;padding:9px 11px;border-top:1px solid rgba(217,119,6,.12);background:rgba(255,255,255,.58)}
1071
1157
  main{--left-w:256px;--right-w:480px;display:grid;grid-template-columns:var(--left-w) 6px minmax(360px,1fr) 6px var(--right-w);flex:1;min-height:0}.panel{min-width:0;background:var(--ws-panel);overflow:hidden}.files{grid-column:1;background:rgba(255,255,255,.52)}.left-resizer{grid-column:2}.work{grid-column:3;display:flex;flex-direction:column;background:#fff}.right-resizer{grid-column:4}.agent{grid-column:5;background:rgba(255,255,255,.58);display:flex;flex-direction:column}.resizer{background:transparent;border-left:1px solid var(--ws-border-subtle);border-right:1px solid transparent;cursor:col-resize;position:relative}.resizer:hover,.resizer.dragging{background:rgba(8,145,178,.08);border-left-color:rgba(8,145,178,.25)}
1072
1158
  main.hide-files{grid-template-columns:0 0 minmax(360px,1fr) 6px var(--right-w)}main.hide-files .files,main.hide-files .left-resizer{display:none}main.hide-agent{grid-template-columns:var(--left-w) 6px minmax(360px,1fr) 0 0}main.hide-agent .agent,main.hide-agent .right-resizer{display:none}
1073
- .section-head{height:36px;border-bottom:1px solid var(--ws-border-subtle);display:flex;align-items:center;justify-content:space-between;padding:0 12px;color:var(--ws-faint);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;background:rgba(255,255,255,.45)}.section-actions{display:flex;align-items:center;gap:4px}.section-button{height:20px;border:0;background:transparent;border-radius:var(--ws-radius-sm);color:rgba(27,27,27,.35);cursor:pointer;padding:0 4px}.section-button:hover{background:var(--ws-hover);color:rgba(27,27,27,.70)}
1074
- .file-list{padding:6px;overflow:auto;height:calc(100% - 36px)}.row{display:flex;align-items:center;gap:5px;width:100%;height:27px;border:0;background:transparent;text-align:left;border-radius:var(--ws-radius-sm);padding:0 8px;color:rgba(27,27,27,.62);cursor:pointer;font-size:13px;line-height:1}.row:hover{background:var(--ws-hover);color:rgba(27,27,27,.84)}.row.selected{background:var(--ws-active);color:var(--ws-foreground)}.row:focus-visible{outline:2px solid rgba(8,145,178,.35);outline-offset:1px}.chev{width:12px;text-align:center;color:rgba(27,27,27,.32);font:11px var(--mono)}.icon{width:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--ws-faint);font-size:11px;font-family:var(--mono)}.name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sub{color:rgba(27,27,27,.34);font:11px var(--sans);margin-left:auto}.loading-row{height:24px;padding-left:32px;color:rgba(27,27,27,.38);font:12px var(--sans)}
1159
+ .section-head{height:36px;border-bottom:1px solid var(--ws-border-subtle);display:flex;align-items:center;justify-content:space-between;padding:0 12px;color:var(--ws-faint);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;background:rgba(255,255,255,.45)}.section-actions{display:flex;align-items:center;gap:4px}.section-button{height:20px;border:0;background:transparent;border-radius:var(--ws-radius-sm);color:rgba(27,27,27,.35);cursor:pointer;padding:0 4px}.section-button:hover{background:var(--ws-hover);color:rgba(27,27,27,.70)}.workspace-tools{height:68px;border-bottom:1px solid var(--ws-border-subtle);padding:7px 8px;background:#FFFDF7;display:flex;flex-direction:column;gap:7px}.file-search{width:100%;height:26px;border:1px solid rgba(27,27,27,.10);border-radius:6px;background:#fff;color:var(--ws-foreground);outline:none;padding:0 8px;font-size:12px}.file-search:focus{border-color:rgba(8,145,178,.35);box-shadow:0 0 0 2px rgba(8,145,178,.10)}.file-filters{display:flex;gap:5px;overflow-x:auto;scrollbar-width:none}.file-filters::-webkit-scrollbar{display:none}.filter-chip{height:22px;border:1px solid rgba(27,27,27,.08);border-radius:999px;background:#fff;color:rgba(27,27,27,.52);padding:0 8px;font-size:11px;cursor:pointer;white-space:nowrap}.filter-chip:hover{background:#F7F5EF;color:rgba(27,27,27,.78)}.filter-chip.active{background:#ECFEFF;border-color:rgba(8,145,178,.22);color:#0E7490}.search-results{padding:4px 8px;color:rgba(27,27,27,.38);font:11px var(--sans)}
1160
+ .file-list{padding:6px;overflow:auto;height:calc(100% - 104px)}.row{display:flex;align-items:center;gap:5px;width:100%;height:27px;border:0;background:transparent;text-align:left;border-radius:var(--ws-radius-sm);padding:0 8px;color:rgba(27,27,27,.62);cursor:pointer;font-size:13px;line-height:1}.row:hover{background:var(--ws-hover);color:rgba(27,27,27,.84)}.row.selected{background:var(--ws-active);color:var(--ws-foreground)}.row:focus-visible{outline:2px solid rgba(8,145,178,.35);outline-offset:1px}.chev{width:12px;text-align:center;color:rgba(27,27,27,.32);font:11px var(--mono)}.icon{width:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--ws-faint);font-size:11px;font-family:var(--mono)}.name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sub{color:rgba(27,27,27,.34);font:11px var(--sans);margin-left:auto}.loading-row{height:24px;padding-left:32px;color:rgba(27,27,27,.38);font:12px var(--sans)}
1075
1161
  .tabbar{height:36px;display:flex;align-items:center;border-bottom:1px solid var(--ws-border-subtle);background:var(--ws-tab);flex:0 0 auto;overflow-x:auto}.tab{height:100%;display:flex;align-items:center;gap:7px;border:0;border-right:1px solid var(--ws-border-subtle);padding:0 9px;background:transparent;color:rgba(27,27,27,.48);font-size:13px;font-weight:500;cursor:pointer;max-width:220px;min-width:90px}.tab.active{background:#fff;color:var(--ws-foreground);font-weight:600;box-shadow:inset 0 -1.5px 0 var(--ws-primary)}.tab:hover{background:rgba(27,27,27,.03);color:rgba(27,27,27,.75)}.tab-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close{border:0;background:transparent;border-radius:var(--ws-radius-sm);color:rgba(27,27,27,.28);cursor:pointer;padding:0 2px}.tab-close:hover{background:rgba(27,27,27,.07);color:rgba(27,27,27,.70)}.tab-muted{flex:1;height:100%;border-left:1px solid var(--ws-border-subtle);min-width:24px}
1076
1162
  .viewer{flex:1;min-height:0;overflow:hidden;background:#fff;display:flex;flex-direction:column}.file-header{min-height:34px;flex:0 0 auto;border-bottom:1px solid var(--ws-border-subtle);display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:5px 12px;background:rgba(27,27,27,.015);font:12px var(--mono);color:rgba(27,27,27,.50)}.path{min-width:120px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-chip{display:inline-flex;align-items:center;gap:5px;border:1px solid rgba(27,27,27,.08);border-radius:999px;background:#fff;padding:1px 7px;font:10px var(--sans);color:rgba(27,27,27,.48);white-space:nowrap}.file-chip.warn{border-color:#FDE68A;background:#FFFBEB;color:#92400E}.file-actions{margin-left:auto;display:flex;align-items:center;gap:6px;min-width:0;flex-wrap:wrap}.file-action{font:11px var(--sans);color:rgba(27,27,27,.50);text-decoration:none;border:1px solid var(--ws-border);border-radius:6px;padding:2px 7px;background:#fff;cursor:pointer}.file-action:hover{color:var(--ws-foreground);background:var(--ws-surface)}.file-icon-action{width:24px;height:24px;min-width:24px;padding:0;display:inline-flex;align-items:center;justify-content:center}.file-icon-action svg{width:13px;height:13px;display:block}select.file-action{height:23px;max-width:190px;padding:1px 24px 1px 7px}.empty{color:var(--ws-faint);padding:24px;font-size:12px}.error{color:var(--ws-error)}
1077
- .monaco-host{flex:1;min-height:0}.code-fallback{flex:1;min-height:0;overflow:auto}.code-wrap{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:start;min-height:100%;font:12px/1.55 var(--mono)}.line-nums{user-select:none;text-align:right;padding:14px 10px 14px 14px;color:rgba(27,27,27,.25);background:#FAF9F5;border-right:1px solid var(--ws-border-subtle);white-space:pre}.code-pre{margin:0;padding:14px;white-space:pre;overflow:auto;color:#1F2937;background:#fff;min-height:100%}.markdown-preview{flex:1;min-height:0;overflow:auto;max-width:920px;padding:24px 28px;color:rgba(27,27,27,.86);font-size:14px;line-height:1.65}.markdown-preview h1,.markdown-preview h2,.markdown-preview h3{line-height:1.2;margin:18px 0 8px}.markdown-preview p{margin:0 0 12px}.markdown-preview code,.message-content code{font-family:var(--mono);font-size:.92em;background:rgba(27,27,27,.06);border-radius:4px;padding:1px 4px}.markdown-preview pre,.message-content pre{overflow:auto;background:#0D1117;color:#E5E7EB;border-radius:7px;padding:12px}.image-stage{flex:1;min-height:0;display:flex;flex-direction:column;background:#F8F7F2}.image-toolbar{height:34px;flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:6px;padding:0 10px;border-bottom:1px solid var(--ws-border-subtle);background:#fff}.image-preview{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;padding:18px;overflow:auto}.image-preview img{max-width:100%;max-height:100%;object-fit:contain;border:1px solid var(--ws-border);background:#fff;box-shadow:0 10px 30px rgba(27,27,27,.10);transform-origin:center center}.frame-preview{flex:1;min-height:0;background:#F8F7F2}.frame-preview iframe{width:100%;height:100%;border:0;background:#fff}.table-preview{flex:1;min-height:0;padding:18px;overflow:auto}.table-preview .table-meta{margin:0 0 10px;color:rgba(27,27,27,.42);font:11px var(--mono)}.table-preview table{border-collapse:collapse;font-size:12px;background:#fff}.table-preview th,.table-preview td{border:1px solid var(--ws-border);padding:5px 7px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.table-preview th{background:#F7F5EF;text-align:left;color:rgba(27,27,27,.65);position:sticky;top:0}.table-preview td.formula-cell{background:#F0F9FF;color:#075985}.diagram-preview{flex:1;min-height:0;padding:18px;display:grid;gap:14px;max-width:none;overflow:auto}.mermaid-card{border:1px solid var(--ws-border);border-radius:8px;background:#fff;overflow:hidden;display:flex;flex-direction:column}.mermaid-output{padding:18px;overflow:auto;min-height:220px;display:flex;align-items:center;justify-content:center}.mermaid-output svg{width:100%;max-width:100%;height:auto;display:block}.mermaid-source{margin:0;border-top:1px solid var(--ws-border-subtle);border-radius:0;background:#FAF9F5;color:rgba(27,27,27,.68);font:11px/1.45 var(--mono);max-height:220px;overflow:auto;padding:10px 12px;white-space:pre}.viewer-note{flex:1;min-height:0;overflow:auto;display:flex;align-items:center;justify-content:center;padding:24px;background:#FAF9F5}.viewer-note-card{width:min(520px,100%);max-height:100%;overflow:auto;border:1px solid var(--ws-border);border-radius:8px;background:#fff;padding:18px;box-shadow:0 10px 34px rgba(27,27,27,.05)}.viewer-note-title{font-size:14px;font-weight:750;color:rgba(27,27,27,.84)}.viewer-note-body{margin-top:8px;color:rgba(27,27,27,.56);font-size:12px;line-height:1.55}.viewer-note-actions{margin-top:14px;display:flex;gap:8px;flex-wrap:wrap}.binary-preview{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;padding:24px;color:rgba(27,27,27,.50);text-align:center}
1163
+ .monaco-host{flex:1;min-height:0}.code-fallback{flex:1;min-height:0;overflow:auto}.code-wrap{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:start;min-height:100%;font:12px/1.55 var(--mono)}.line-nums{user-select:none;text-align:right;padding:14px 10px 14px 14px;color:rgba(27,27,27,.25);background:#FAF9F5;border-right:1px solid var(--ws-border-subtle);white-space:pre}.code-pre{margin:0;padding:14px;white-space:pre;overflow:auto;color:#1F2937;background:#fff;min-height:100%}.markdown-preview{flex:1;min-height:0;overflow:auto;max-width:920px;padding:24px 28px;color:rgba(27,27,27,.86);font-size:14px;line-height:1.65}.markdown-preview h1,.markdown-preview h2,.markdown-preview h3{line-height:1.2;margin:18px 0 8px}.markdown-preview p{margin:0 0 12px}.markdown-preview code,.message-content code{font-family:var(--mono);font-size:.92em;background:rgba(27,27,27,.06);border-radius:4px;padding:1px 4px}.markdown-preview pre,.message-content pre{overflow:auto;background:#0D1117;color:#E5E7EB;border-radius:7px;padding:12px}.image-stage,.media-stage{flex:1;min-height:0;display:flex;flex-direction:column;background:#F8F7F2}.image-toolbar,.media-toolbar{height:34px;flex:0 0 auto;display:flex;align-items:center;justify-content:flex-end;gap:6px;padding:0 10px;border-bottom:1px solid var(--ws-border-subtle);background:#fff}.image-preview,.media-preview{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;padding:18px;overflow:auto}.image-preview img{max-width:100%;max-height:100%;object-fit:contain;border:1px solid var(--ws-border);background:#fff;box-shadow:0 10px 30px rgba(27,27,27,.10);transform-origin:center center}.media-preview video{width:min(100%,1120px);max-height:100%;background:#0B0B0B;border-radius:8px;box-shadow:0 14px 42px rgba(27,27,27,.16)}.media-preview audio{width:min(100%,720px)}.frame-preview{flex:1;min-height:0;background:#F8F7F2}.frame-preview iframe{width:100%;height:100%;border:0;background:#fff}.table-preview{flex:1;min-height:0;padding:18px;overflow:auto}.table-preview .table-meta{margin:0 0 10px;color:rgba(27,27,27,.42);font:11px var(--mono)}.table-preview table{border-collapse:collapse;font-size:12px;background:#fff}.table-preview th,.table-preview td{border:1px solid var(--ws-border);padding:5px 7px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.table-preview th{background:#F7F5EF;text-align:left;color:rgba(27,27,27,.65);position:sticky;top:0}.table-preview td.formula-cell{background:#F0F9FF;color:#075985}.diagram-preview{flex:1;min-height:0;padding:18px;display:grid;gap:14px;max-width:none;overflow:auto}.mermaid-card{border:1px solid var(--ws-border);border-radius:8px;background:#fff;overflow:hidden;display:flex;flex-direction:column}.mermaid-output{padding:18px;overflow:auto;min-height:220px;display:flex;align-items:center;justify-content:center}.mermaid-output svg{width:100%;max-width:100%;height:auto;display:block}.mermaid-source{margin:0;border-top:1px solid var(--ws-border-subtle);border-radius:0;background:#FAF9F5;color:rgba(27,27,27,.68);font:11px/1.45 var(--mono);max-height:220px;overflow:auto;padding:10px 12px;white-space:pre}.viewer-note{flex:1;min-height:0;overflow:auto;display:flex;align-items:center;justify-content:center;padding:24px;background:#FAF9F5}.viewer-note-card{width:min(520px,100%);max-height:100%;overflow:auto;border:1px solid var(--ws-border);border-radius:8px;background:#fff;padding:18px;box-shadow:0 10px 34px rgba(27,27,27,.05)}.viewer-note-title{font-size:14px;font-weight:750;color:rgba(27,27,27,.84)}.viewer-note-body{margin-top:8px;color:rgba(27,27,27,.56);font-size:12px;line-height:1.55}.viewer-note-actions{margin-top:14px;display:flex;gap:8px;flex-wrap:wrap}.binary-preview{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;padding:24px;color:rgba(27,27,27,.50);text-align:center}
1078
1164
  .chat-head{height:36px;border-bottom:1px solid var(--ws-border-subtle);display:flex;align-items:center;gap:8px;padding:0 12px;background:rgba(255,255,255,.55)}.chat-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:rgba(27,27,27,.42)}.chat-subtitle{min-width:0;flex:1;font-size:12px;color:rgba(27,27,27,.42);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-tabs{height:34px;display:flex;align-items:center;border-bottom:1px solid var(--ws-border-subtle);background:#F7F5EF;padding:0 8px;gap:3px}.agent-tab{height:26px;border:0;border-radius:var(--ws-radius-md);background:transparent;color:rgba(27,27,27,.48);font-size:13px;font-weight:500;padding:0 9px;cursor:pointer}.agent-tab:hover{background:var(--ws-hover);color:rgba(27,27,27,.76)}.agent-tab.active{background:#fff;color:var(--ws-foreground);font-weight:600;box-shadow:0 0 0 1px rgba(27,27,27,.06)}.chat-body{display:flex;flex-direction:column;flex:1;min-height:0}.chat-pane{display:none;flex-direction:column;flex:1;min-height:0}.chat-pane.active{display:flex}.plugins-menu-wrap{position:relative}.plugins-menu-wrap[hidden]{display:none}.plugins-dropdown{position:absolute;top:26px;left:0;z-index:60;min-width:210px;background:#fff;border:1px solid var(--ws-border);border-radius:var(--ws-radius-md);box-shadow:0 12px 34px rgba(27,27,27,.14);padding:4px;display:flex;flex-direction:column}.plugins-dropdown[hidden]{display:none}.plugins-dropdown button{border:0;background:transparent;text-align:left;font:12px var(--sans);color:rgba(27,27,27,.78);padding:6px 9px;border-radius:var(--ws-radius-sm);cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:10px}.plugins-dropdown button:hover{background:var(--ws-hover)}.plugins-dropdown .plugin-origin{color:var(--ws-faint);font:11px var(--sans)}.thread{flex:1;min-height:0;overflow:auto;padding:16px 14px 10px;background:rgba(255,255,255,.40)}.thread-inner{display:flex;flex-direction:column;gap:12px}.empty-chat{display:flex;height:100%;align-items:center;justify-content:center;text-align:center;color:rgba(27,27,27,.42);font-size:12px}.session-list{display:flex;flex-direction:column;gap:2px}.session-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:center;width:100%;border:0;background:transparent;text-align:left;border-radius:var(--ws-radius-md);padding:8px;color:inherit;cursor:pointer}.session-row:hover{background:rgba(27,27,27,.035)}.session-row.selected{background:var(--ws-active);box-shadow:inset 2px 0 0 var(--ws-primary)}.session-row:focus-visible{outline:2px solid rgba(8,145,178,.35);outline-offset:1px}.session-main{min-width:0}.session-prompt{display:block;font-size:13px;font-weight:500;color:rgba(27,27,27,.84);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-meta{display:block;margin-top:3px;font:12px/1.4 var(--sans);color:rgba(27,27,27,.46);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-tools{margin-top:5px;display:flex;gap:4px;flex-wrap:wrap}.session-chip{border-radius:999px;border:1px solid rgba(27,27,27,.07);background:#F7F5EF;padding:1px 6px;font:11px var(--sans);color:rgba(27,27,27,.52)}.session-action{height:24px;display:inline-flex;align-items:center;justify-content:center;border:1px solid rgba(27,27,27,.10);border-radius:var(--ws-radius-md);background:#fff;color:rgba(27,27,27,.62);padding:0 8px;font-size:12px;font-weight:500}.session-row:hover .session-action{background:#F7F5EF;color:var(--ws-foreground)}.msg{display:flex}.msg.user{justify-content:flex-end}.msg.assistant{justify-content:flex-start}.bubble{max-width:86%;border-radius:16px;padding:10px 12px;font-size:13px;line-height:1.55;word-break:break-word}.user .bubble{background:#1B1B1B;color:#FFFDF7}.message-attachments{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}.message-attachment{max-width:100%;display:inline-flex;align-items:center;gap:5px;border:1px solid rgba(255,255,255,.20);border-radius:999px;background:rgba(255,255,255,.10);color:#FFFDF7;padding:2px 7px;cursor:pointer}.message-attachment span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}.message-attachment small{font:11px var(--sans);opacity:.68}.message-attachment:hover{background:rgba(255,255,255,.17)}.assistant-stack{width:100%;max-width:960px;display:flex;flex-direction:column;gap:8px}.assistant-bubble{display:none;max-width:94%;border-radius:16px;background:#F7F5EF;padding:11px 13px;color:rgba(27,27,27,.86);font-size:13px;line-height:1.6}.assistant-bubble:not(:empty){display:block}.message-content p{margin:0 0 10px}.message-content p:last-child{margin-bottom:0}.message-content ul{margin:0 0 10px 18px;padding:0}.message-content li{margin:2px 0}
1079
1165
  .activity-card{display:none;overflow:hidden;border:1px solid var(--ws-border);border-radius:8px;background:#FFFDF7;font-size:12px}.activity-card.active{display:block}.activity-head{height:28px;width:100%;border:0;background:transparent;display:flex;align-items:center;gap:8px;padding:0 9px;cursor:pointer;color:rgba(27,27,27,.68)}.activity-head:hover{background:#F7F5EF}.activity-label{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left;font-size:11px;font-weight:650}.activity-rollup{font-size:10px;color:rgba(27,27,27,.38)}.activity-rows{border-top:1px solid rgba(27,27,27,.05);padding:7px 9px;max-height:96px;overflow:auto;display:flex;flex-direction:column;gap:5px}.activity-card.expanded .activity-rows{max-height:360px}.activity-row{display:flex;align-items:flex-start;gap:7px;color:rgba(27,27,27,.65);font-size:11px;line-height:1.35}.activity-row .status-dot{width:8px;height:8px;border-radius:999px;background:rgba(27,27,27,.18);margin-top:4px;flex:0 0 auto}.activity-row.running .status-dot{background:#0891B2}.activity-row.done .status-dot{background:#059669}.activity-row.error .status-dot{background:#DC2626}.activity-row.approval .status-dot{background:#D97706}.activity-row.thinking{font-style:italic;color:rgba(27,27,27,.48)}.activity-main{display:block;min-width:0;flex:1}.activity-message{display:block}.activity-row pre{display:none;margin:4px 0 0;max-height:120px;overflow:auto;border-radius:6px;background:rgba(27,27,27,.04);padding:6px;font:10px/1.4 var(--mono);color:rgba(27,27,27,.62);white-space:pre-wrap}.activity-card.expanded .activity-row pre{display:block}.activity-approval{margin-top:7px;border:1px solid rgba(217,119,6,.20);border-radius:7px;background:#FFFBEB;overflow:hidden;transition:opacity .85s ease,transform .85s ease,max-height .85s ease,margin .85s ease}.activity-approval.fading{opacity:0;transform:translateY(-4px);max-height:0;margin:0;pointer-events:none}.activity-approval-head{display:flex;align-items:center;justify-content:space-between;gap:8px;border-bottom:1px solid rgba(217,119,6,.12);padding:6px 8px}.activity-approval-title{font-size:11px;font-weight:800;color:#92400E}.activity-approval-meta{font:10px var(--mono);color:rgba(146,64,14,.58);white-space:nowrap}.activity-approval-subject{margin:7px 8px 0;border:1px solid rgba(217,119,6,.14);border-radius:5px;background:#fff;padding:7px;font:10px/1.45 var(--mono);color:rgba(27,27,27,.72);white-space:pre-wrap;overflow-wrap:anywhere;max-height:130px;overflow:auto}.activity-approval-reason{padding:6px 8px 8px;color:rgba(27,27,27,.56);font-size:11px;line-height:1.45}.activity-approval-actions{display:flex;justify-content:flex-end;gap:6px;flex-wrap:wrap;border-top:1px solid rgba(217,119,6,.12);background:rgba(255,255,255,.58);padding:7px 8px}.activity-result{margin-top:6px}.trace{display:block;flex:1;min-height:0;overflow:auto;background:#fff;padding:8px}.trace-row{font:11px/1.45 var(--mono);border-bottom:1px solid rgba(27,27,27,.05);padding:7px 4px;color:rgba(27,27,27,.52);white-space:pre-wrap;overflow-wrap:anywhere}.trace-row b{color:var(--ws-primary)}
1080
1166
  .notebook-preview{flex:1;min-height:0;overflow:auto;background:#F7F5EF;padding:14px 14px 40px;display:flex;flex-direction:column;gap:10px}.notebook-meta{font:11px var(--mono);color:rgba(27,27,27,.42);padding:0 6px}.notebook-cell{border:1px solid var(--ws-border-subtle);border-radius:8px;background:#fff;overflow:hidden;box-shadow:0 1px 0 rgba(27,27,27,.02)}.notebook-cell.code{display:grid;grid-template-columns:64px minmax(0,1fr);padding:0;align-items:stretch}.notebook-cell.markdown{padding:16px 20px}.notebook-cell.raw{background:#F7F5EF;padding:12px 16px}.notebook-cell.raw .notebook-cell-body{padding:0}.notebook-cell.raw pre{margin:0;white-space:pre-wrap;font:12px/1.5 var(--mono);color:rgba(27,27,27,.72)}.notebook-cell-body{padding:0}.notebook-markdown{font-size:14px;line-height:1.65;color:rgba(27,27,27,.86)}.notebook-markdown h1,.notebook-markdown h2,.notebook-markdown h3{line-height:1.2;margin:14px 0 8px;color:rgba(27,27,27,.92)}.notebook-markdown h1{font-size:22px;border-bottom:1px solid var(--ws-border-subtle);padding-bottom:4px}.notebook-markdown p{margin:0 0 10px}.notebook-markdown code{font-family:var(--mono);font-size:.92em;background:rgba(27,27,27,.06);border-radius:4px;padding:1px 4px}.notebook-markdown pre{overflow:auto;background:#0D1117;color:#E5E7EB;border-radius:6px;padding:10px}.notebook-prompt{padding:12px 10px 12px 10px;font:11px/1.4 var(--mono);text-align:right;user-select:none;color:#4B6BFB;background:transparent;border-right:1px solid var(--ws-border-subtle);white-space:nowrap;display:flex;align-items:flex-start;justify-content:flex-end}.notebook-prompt.out{color:#A21818}.notebook-prompt.empty{color:transparent;pointer-events:none}.notebook-prompt.in::before{content:"In "}.notebook-prompt.out::before{content:"Out "}.notebook-cell-monaco{background:#FAFBFC;min-height:52px;position:relative;overflow:hidden;padding:6px 0}.notebook-cell-monaco .monaco-editor,.notebook-cell-monaco .monaco-editor .overflow-guard,.notebook-cell-monaco .monaco-editor-background,.notebook-cell-monaco .margin{background:#FAFBFC!important}.notebook-code-fallback{margin:0;padding:8px 12px;background:transparent;color:#1F2937;font:12px/1.5 var(--mono);white-space:pre;overflow:auto}.notebook-cell-outputs{grid-column:1 / -1;display:grid;grid-template-columns:64px minmax(0,1fr);background:#fff;border-top:1px solid var(--ws-border-subtle)}.notebook-output-item{display:contents}.notebook-output-item+.notebook-output-item>.notebook-prompt,.notebook-output-item+.notebook-output-item>.notebook-output-body{border-top:1px solid var(--ws-border-subtle)}.notebook-output-body{padding:8px 12px;overflow:auto;min-width:0;background:#fff}.notebook-stream{margin:0;font:12px/1.5 var(--mono);color:#1F2937;white-space:pre-wrap;word-break:break-word;background:transparent;padding:0}.notebook-stream.stderr{color:#7F1D1D;background:#FEF2F2;padding:6px 8px;border-radius:5px}.notebook-error{margin:0;font:12px/1.5 var(--mono);color:#7F1D1D;background:#FEF2F2;padding:8px 10px;border-radius:5px;white-space:pre-wrap;word-break:break-word;overflow:auto}.notebook-plain{margin:0;font:12px/1.5 var(--mono);color:#1F2937;white-space:pre-wrap;word-break:break-word;background:transparent;padding:0}.notebook-json{margin:0;font:12px/1.5 var(--mono);color:#0F172A;background:#F8FAFC;padding:8px 10px;border-radius:5px;white-space:pre;overflow:auto}.notebook-output-body img{max-width:100%;height:auto;display:block;background:#fff;border-radius:4px}.notebook-output-body iframe{width:100%;min-height:220px;border:1px solid var(--ws-border-subtle);border-radius:5px;background:#fff}.notebook-latex{font-size:14px;color:rgba(27,27,27,.90);padding:6px 0;overflow:auto}.katex-display{margin:6px 0!important}.viewer.maximized{position:fixed;inset:12px;z-index:70;border:1px solid rgba(27,27,27,.12);border-radius:8px;box-shadow:0 24px 80px rgba(27,27,27,.28);background:#fff}.viewer.maximized .diagram-preview,.viewer.maximized .markdown-preview{max-width:none}.viewer.maximized .mermaid-output{min-height:calc(100vh - 220px)}.composer{border-top:1px solid var(--ws-border-subtle);background:#FFFDF7;padding:10px 12px}.composer-box{border:1px solid var(--ws-border);border-radius:10px;background:#fff;overflow:hidden}.upload-tray{display:flex;gap:6px;flex-wrap:wrap;padding:8px 9px 0}.upload-tray[hidden]{display:none}.upload-chip{height:24px;display:inline-flex;align-items:center;gap:6px;max-width:100%;border:1px solid rgba(8,145,178,.18);border-radius:999px;background:#F0F9FF;color:#075985;padding:0 4px 0 8px;font-size:11px}.upload-chip-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:210px}.upload-chip-meta{font:10px var(--mono);color:rgba(7,89,133,.58)}.upload-chip button{width:18px;height:18px;border:0;border-radius:999px;background:transparent;color:rgba(7,89,133,.52);cursor:pointer;padding:0}.upload-chip button:hover{background:rgba(8,145,178,.12);color:#075985}textarea{display:block;width:100%;min-height:84px;max-height:240px;resize:vertical;border:0;padding:10px 11px;background:#fff;color:var(--ws-foreground);outline:none}.composer-actions{height:34px;border-top:1px solid var(--ws-border-subtle);display:flex;align-items:center;justify-content:space-between;padding:0 8px}.composer-left,.composer-right{display:flex;align-items:center;gap:6px;min-width:0}.composer-right{margin-left:auto}.status{font-size:11px;color:var(--ws-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:8px}.stop-button{height:24px;border:1px solid rgba(185,28,28,.20);border-radius:6px;background:#FEF2F2;color:#B91C1C;font-size:11px;font-weight:750;padding:0 8px;display:inline-flex;align-items:center;gap:5px;cursor:pointer}.stop-button svg{width:12px;height:12px}.stop-button:hover{background:#FEE2E2;border-color:rgba(185,28,28,.30)}.stop-button:disabled{opacity:.55;cursor:not-allowed}.stop-button[hidden]{display:none}button.primary{height:24px;border:0;border-radius:6px;background:var(--ws-foreground);color:#fff;font-size:11px;font-weight:750;padding:0 10px;cursor:pointer}button.primary:hover{background:#2B2B2B}button.primary:disabled{opacity:.45;cursor:not-allowed}
@@ -1137,6 +1223,17 @@ main.hide-files{grid-template-columns:0 0 minmax(360px,1fr) 6px var(--right-w)}m
1137
1223
  <main id="layout">
1138
1224
  <section class="panel files" id="filesPanel">
1139
1225
  <div class="section-head"><span>Files</span><span class="section-actions"><button class="section-button" id="refreshFiles" type="button" title="Refresh">↻</button><span id="fileCount"></span></span></div>
1226
+ <div class="workspace-tools">
1227
+ <input class="file-search" id="fileSearch" type="search" placeholder="Search workspace" autocomplete="off">
1228
+ <div class="file-filters" id="fileFilters" aria-label="File type filters">
1229
+ <button class="filter-chip active" type="button" data-file-filter="all">All</button>
1230
+ <button class="filter-chip" type="button" data-file-filter="code">Code</button>
1231
+ <button class="filter-chip" type="button" data-file-filter="docs">Docs</button>
1232
+ <button class="filter-chip" type="button" data-file-filter="data">Data</button>
1233
+ <button class="filter-chip" type="button" data-file-filter="media">Media</button>
1234
+ <button class="filter-chip" type="button" data-file-filter="web">Web</button>
1235
+ </div>
1236
+ </div>
1140
1237
  <div class="file-list" id="fileList"></div>
1141
1238
  </section>
1142
1239
  <div class="resizer left-resizer" data-resize="left"></div>
@@ -1217,6 +1314,10 @@ const headers = { 'X-Bahulam-Local-Token': token };
1217
1314
  const dirCache = new Map();
1218
1315
  const expandedDirs = new Set(['.']);
1219
1316
  const loadingDirs = new Set();
1317
+ let workspaceQuery = '';
1318
+ let workspaceFilter = 'all';
1319
+ let workspaceSearchTimer = null;
1320
+ let workspaceSearchSeq = 0;
1220
1321
  const openTabs = [];
1221
1322
  const fileCache = new Map();
1222
1323
  let activePath = null;
@@ -1282,7 +1383,10 @@ function attachmentHint(file){
1282
1383
  const mime=String(file?.mime_type||'').toLowerCase();
1283
1384
  const ext=extname(file?.path||file?.name||'');
1284
1385
  if(kind==='image'||mime.startsWith('image/'))return 'vision';
1386
+ if(kind==='video'||mime.startsWith('video/'))return 'video';
1387
+ if(kind==='audio'||mime.startsWith('audio/'))return 'audio';
1285
1388
  if(kind==='spreadsheet'||kind==='table'||['csv','tsv','xlsx','xls','ods'].includes(ext))return 'table';
1389
+ if(kind==='web')return 'web';
1286
1390
  if(['pdf','markdown','text','code','config','notebook'].includes(kind)||['txt','md','mdx','pdf','json','yaml','yml','toml','html','xml','ipynb','log','rst','sql','sh'].includes(ext))return 'read';
1287
1391
  return 'file';
1288
1392
  }
@@ -1368,8 +1472,11 @@ function fileKind(data){
1368
1472
  const file=data.file||{};
1369
1473
  if(file.viewer)return file.viewer;
1370
1474
  const ext=extname(data.path);
1371
- if(['png','jpg','jpeg','gif','webp','svg','bmp','ico'].includes(ext))return 'image';
1475
+ if(['png','jpg','jpeg','gif','webp','svg','bmp','ico','avif'].includes(ext))return 'image';
1476
+ if(['mp4','webm','mov','m4v','ogv'].includes(ext))return 'video';
1477
+ if(['mp3','m4a','wav','ogg','flac','aac'].includes(ext))return 'audio';
1372
1478
  if(ext==='pdf')return 'pdf';
1479
+ if(['html','htm'].includes(ext))return 'web';
1373
1480
  if(['md','mdx'].includes(ext))return 'markdown';
1374
1481
  if(['mmd','mermaid'].includes(ext))return 'mermaid';
1375
1482
  if(['drawio','dio'].includes(ext))return 'drawio';
@@ -1502,7 +1609,7 @@ function bindHeaderActions(path){
1502
1609
  }
1503
1610
  function askAgentForFile(path,kind){
1504
1611
  const prompt=document.getElementById('prompt');
1505
- const label=kind==='image'?'Analyze this image':kind==='pdf'?'Summarize this PDF':kind==='spreadsheet'?'Inspect this spreadsheet':kind==='document'?'Inspect this document':kind==='presentation'?'Inspect this presentation':'Inspect this file';
1612
+ const label=kind==='image'?'Analyze this image':kind==='video'?'Inspect this video':kind==='audio'?'Inspect this audio':kind==='web'?'Review this web page':kind==='pdf'?'Summarize this PDF':kind==='spreadsheet'?'Inspect this spreadsheet':kind==='document'?'Inspect this document':kind==='presentation'?'Inspect this presentation':'Inspect this file';
1506
1613
  prompt.value=label+' at '+path+'. Explain what it contains and call the right local tools if needed.';
1507
1614
  prompt.focus();
1508
1615
  }
@@ -1695,11 +1802,75 @@ function renderTabs(){
1695
1802
  function renderExplorer(){
1696
1803
  const root=dirCache.get('.');
1697
1804
  const list=document.getElementById('fileList');
1805
+ if(workspaceSearchActive()){scheduleWorkspaceSearch(0);return;}
1698
1806
  if(!root){list.innerHTML='<div class="loading-row">Loading...</div>';return;}
1699
1807
  list.innerHTML=renderEntries(root.entries||[],0);
1700
1808
  list.querySelectorAll('[data-dir]').forEach(btn=>btn.addEventListener('click',async()=>{const p=btn.dataset.dir;if(expandedDirs.has(p)&&p!=='.')expandedDirs.delete(p);else{expandedDirs.add(p);if(!dirCache.has(p))await loadDir(p);}renderExplorer();}));
1701
1809
  list.querySelectorAll('[data-file]').forEach(btn=>btn.addEventListener('click',()=>openFile(btn.dataset.file)));
1702
1810
  }
1811
+ function workspaceSearchActive(){
1812
+ return Boolean(workspaceQuery.trim() || workspaceFilter !== 'all');
1813
+ }
1814
+ function filterKinds(filter){
1815
+ switch(filter){
1816
+ case 'code': return ['code','config'];
1817
+ case 'docs': return ['markdown','pdf','document','presentation','notebook'];
1818
+ case 'data': return ['table','spreadsheet'];
1819
+ case 'media': return ['image','video','audio'];
1820
+ case 'web': return ['web','mermaid','drawio'];
1821
+ default: return [];
1822
+ }
1823
+ }
1824
+ function setWorkspaceFilter(filter){
1825
+ workspaceFilter=filter||'all';
1826
+ document.querySelectorAll('[data-file-filter]').forEach(btn=>btn.classList.toggle('active',btn.dataset.fileFilter===workspaceFilter));
1827
+ renderExplorer();
1828
+ }
1829
+ function scheduleWorkspaceSearch(delay=160){
1830
+ if(workspaceSearchTimer)clearTimeout(workspaceSearchTimer);
1831
+ workspaceSearchTimer=setTimeout(loadWorkspaceSearch,delay);
1832
+ }
1833
+ async function loadWorkspaceSearch(){
1834
+ if(!workspaceSearchActive())return;
1835
+ const list=document.getElementById('fileList');
1836
+ const seq=++workspaceSearchSeq;
1837
+ const kinds=filterKinds(workspaceFilter);
1838
+ list.innerHTML='<div class="loading-row">Searching...</div>';
1839
+ try{
1840
+ const url='/api/workspace/search?q='+encodeURIComponent(workspaceQuery.trim())+(kinds.length?'&kind='+encodeURIComponent(kinds.join(',')):'');
1841
+ const result=await api(url);
1842
+ if(seq!==workspaceSearchSeq)return;
1843
+ document.getElementById('fileCount').textContent=String((result.results||[]).length);
1844
+ renderSearchResults(result);
1845
+ }catch(err){
1846
+ if(seq!==workspaceSearchSeq)return;
1847
+ list.innerHTML='<div class="search-results error">'+esc(err.message||'Search failed')+'</div>';
1848
+ }
1849
+ }
1850
+ function renderSearchResults(result){
1851
+ const list=document.getElementById('fileList');
1852
+ const rows=Array.isArray(result.results)?result.results:[];
1853
+ const note='<div class="search-results">'+esc(rows.length)+' result'+(rows.length===1?'':'s')+(result.truncated?' · truncated':'')+'</div>';
1854
+ if(!rows.length){list.innerHTML=note+'<div class="empty">No matching files.</div>';return;}
1855
+ list.innerHTML=note+rows.map(entry=>{
1856
+ const attrs=entry.type==='directory'?'data-dir="'+esc(entry.path)+'"':'data-file="'+esc(entry.path)+'"';
1857
+ return '<button class="row '+(entry.path===activePath?'selected':'')+'" '+attrs+' title="'+esc(entry.path)+'"><span class="chev"></span><span class="icon">'+entryIcon(entry)+'</span><span class="name">'+esc(entry.path)+'</span><span class="sub">'+esc(entry.label||entry.kind||'')+'</span></button>';
1858
+ }).join('');
1859
+ list.querySelectorAll('[data-dir]').forEach(btn=>btn.addEventListener('click',async()=>{const dir=btn.dataset.dir;expandedDirs.add(dir);workspaceQuery='';document.getElementById('fileSearch').value='';setWorkspaceFilter('all');if(!dirCache.has(dir))await loadDir(dir);else renderExplorer();}));
1860
+ list.querySelectorAll('[data-file]').forEach(btn=>btn.addEventListener('click',()=>openFile(btn.dataset.file)));
1861
+ }
1862
+ function entryIcon(entry){
1863
+ const kind=String(entry.kind||entry.viewer||'').toLowerCase();
1864
+ if(entry.type==='directory'||kind==='directory')return '▦';
1865
+ if(kind==='image')return '◉';
1866
+ if(kind==='video')return '▶';
1867
+ if(kind==='audio')return '♪';
1868
+ if(kind==='web')return '⌘';
1869
+ if(['spreadsheet','table'].includes(kind))return '▤';
1870
+ if(['markdown','pdf','document','presentation','notebook'].includes(kind))return '▣';
1871
+ if(['code','config','mermaid','drawio'].includes(kind))return '◇';
1872
+ return '·';
1873
+ }
1703
1874
  function renderEntries(entries,depth){
1704
1875
  return entries.map(entry=>{
1705
1876
  const pad=8+depth*13;
@@ -1709,7 +1880,7 @@ function renderEntries(entries,depth){
1709
1880
  const rows=open?(child?renderEntries(child.entries||[],depth+1):'<div class="loading-row" style="padding-left:'+(pad+24)+'px">Loading...</div>'):'';
1710
1881
  return '<button class="row" style="padding-left:'+pad+'px" data-dir="'+esc(entry.path)+'"><span class="chev">'+(open?'⌄':'›')+'</span><span class="icon">▦</span><span class="name">'+esc(entry.name)+'</span></button>'+rows;
1711
1882
  }
1712
- return '<button class="row '+(entry.path===activePath?'selected':'')+'" style="padding-left:'+pad+'px" data-file="'+esc(entry.path)+'"><span class="chev"></span><span class="icon">◇</span><span class="name">'+esc(entry.name)+'</span><span class="sub">'+(entry.size!=null?formatBytes(entry.size):'')+'</span></button>';
1883
+ return '<button class="row '+(entry.path===activePath?'selected':'')+'" style="padding-left:'+pad+'px" data-file="'+esc(entry.path)+'"><span class="chev"></span><span class="icon">'+entryIcon(entry)+'</span><span class="name">'+esc(entry.name)+'</span><span class="sub">'+(entry.size!=null?formatBytes(entry.size):'')+'</span></button>';
1713
1884
  }).join('');
1714
1885
  }
1715
1886
  function renderEmptyViewer(){
@@ -1720,6 +1891,8 @@ function renderFile(data){
1720
1891
  const text=data.preview&&data.preview.content!=null?sourceTextForRender(data):null;
1721
1892
  const kind=fileKind(data);
1722
1893
  if(kind==='image'){renderImage(data);return;}
1894
+ if(kind==='video'||kind==='audio'){renderMedia(data,kind);return;}
1895
+ if(kind==='web'&&text!=null){renderWebFile(data,text);return;}
1723
1896
  if(kind==='pdf'){renderPdf(data);return;}
1724
1897
  if(kind==='markdown'&&text!=null){renderMarkdownFile(data,text);return;}
1725
1898
  if(kind==='mermaid'&&text!=null){renderMermaidFile(data,text);return;}
@@ -1929,11 +2102,25 @@ function renderImage(data){
1929
2102
  if(scale)scale.textContent=Math.round(activeImageScale*100)+'%';
1930
2103
  }));
1931
2104
  }
2105
+ function renderMedia(data,kind){
2106
+ const header=fileHeader(data,{extraActions:'<span class="file-chip">'+esc(kind)+'</span>'});
2107
+ const source=rawUrl(data.path);
2108
+ const body=kind==='video'
2109
+ ? '<video controls playsinline preload="metadata" src="'+source+'"></video>'
2110
+ : '<audio controls preload="metadata" src="'+source+'"></audio>';
2111
+ renderViewer(header+'<div class="media-stage" data-preview-content><div class="media-toolbar"><span class="file-chip">'+esc(formatBytes(data.file?.size))+'</span></div><div class="media-preview">'+body+'</div></div>');
2112
+ bindHeaderActions(data.path);
2113
+ }
1932
2114
  function renderPdf(data){
1933
2115
  const header=fileHeader(data);
1934
2116
  renderViewer(header+'<div class="frame-preview" data-preview-content><iframe src="'+rawUrl(data.path)+'" title="'+esc(data.path)+'"></iframe></div>');
1935
2117
  bindHeaderActions(data.path);
1936
2118
  }
2119
+ function renderWebFile(data,text){
2120
+ const header=fileHeader(data,{language:'html',truncated:Boolean(data.preview&&data.preview.truncated),save:canEditFile(data),editSource:canEditFile(data),extraActions:'<span class="file-chip">web preview</span>'});
2121
+ renderViewer(header+'<div class="frame-preview" data-preview-content><iframe sandbox="allow-scripts allow-forms allow-modals" srcdoc="'+esc(text)+'" title="'+esc(data.path)+'"></iframe></div>');
2122
+ bindHeaderActions(data.path);
2123
+ }
1937
2124
  function renderMarkdownFile(data,text){
1938
2125
  const header=fileHeader(data,{language:'markdown',truncated:Boolean(data.preview&&data.preview.truncated),save:canEditFile(data),editSource:canEditFile(data)});
1939
2126
  renderViewer(header+'<div class="markdown-preview" data-preview-content>'+markdown(text)+'</div>');
@@ -2942,7 +3129,9 @@ document.getElementById('sessionModal').addEventListener('click',(e)=>{if(e.targ
2942
3129
  document.getElementById('approvalDenyTop').addEventListener('click',()=>submitApproval('reject'));
2943
3130
  document.getElementById('approvalAuto').addEventListener('change',e=>setApprovalAutoMode(e.target.checked));
2944
3131
  document.addEventListener('keydown',(e)=>{if(e.key==='Escape'&&pendingApproval){submitApproval('reject');return;}if(e.key==='Escape'&&sessionMenuOpen){setSessionMenuOpen(false);return;}if(e.key==='Escape'&&previewMaximized)setPreviewMaximized(false);});
2945
- document.getElementById('refreshFiles').addEventListener('click',()=>{dirCache.clear();loadDir('.');});
3132
+ document.getElementById('fileSearch').addEventListener('input',e=>{workspaceQuery=e.target.value||'';renderExplorer();});
3133
+ document.querySelectorAll('[data-file-filter]').forEach(btn=>btn.addEventListener('click',()=>setWorkspaceFilter(btn.dataset.fileFilter)));
3134
+ document.getElementById('refreshFiles').addEventListener('click',()=>{dirCache.clear();if(workspaceSearchActive())scheduleWorkspaceSearch(0);else loadDir('.');});
2946
3135
  document.getElementById('toggleExplorer').addEventListener('click',()=>{let prefs={explorerVisible:true};try{prefs={...prefs,...JSON.parse(localStorage.getItem(layoutKey)||'{}')};}catch{}saveLayoutPatch({explorerVisible:!prefs.explorerVisible});});
2947
3136
  document.getElementById('toggleAgent').addEventListener('click',()=>{let prefs={agentVisible:true};try{prefs={...prefs,...JSON.parse(localStorage.getItem(layoutKey)||'{}')};}catch{}saveLayoutPatch({agentVisible:!prefs.agentVisible});});
2948
3137
  applyLayout();