@bahulam/code 0.1.13 → 0.1.15

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.
@@ -99,10 +99,13 @@ export const DAEMON_OWNED_FIELDS = Object.freeze([
99
99
  * • It's input state tied to a keyboard (`inputHistory`).
100
100
  * • It's a rendering flag whose value depends on the current visible
101
101
  * transcript, not the session's actual state (`inSubAgent`).
102
+ * • It's an active renderer collection that does not JSON round-trip
103
+ * safely (`activeSubAgentRuns` is a Map).
102
104
  */
103
105
  export const CLIENT_OWNED_SESSION_FIELDS = Object.freeze([
104
106
  'inputHistory',
105
107
  'inSubAgent',
108
+ 'activeSubAgentRuns',
106
109
  'creditsLowWarned',
107
110
  'msgsLowWarned',
108
111
  '_lastEmittedThinking',
@@ -748,6 +748,8 @@ export class LocalAgentRelay {
748
748
  lines.push(
749
749
  'Use analyze_image(path=..., question=...) for images.',
750
750
  'Use read_table(path=...) for CSV/TSV/Excel-style tables.',
751
+ 'Use browser/web inspection tools for HTML and browser-rendered web assets when available.',
752
+ 'For video and audio, inspect file metadata and ask for a transcript or frame extraction when content analysis is needed.',
751
753
  'Use read_attachment(path=...) for text, PDFs, Markdown, JSON/YAML, and Jupyter notebooks.',
752
754
  'For unsupported binary files, inspect metadata or ask before attempting lossy conversion.',
753
755
  );
@@ -902,7 +904,10 @@ function attachmentToolHint(file) {
902
904
  const ext = String(file?.path || file?.name || '').split('.').pop().toLowerCase();
903
905
  if (kind === 'image' || mime.startsWith('image/')) return 'analyze_image';
904
906
  if (kind === 'spreadsheet' || kind === 'table' || ['csv', 'tsv', 'xlsx', 'xls', 'ods'].includes(ext)) return 'read_table';
905
- 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_attachment';
907
+ if (kind === 'web' || ['html', 'htm'].includes(ext)) return 'web_preview';
908
+ if (kind === 'video' || mime.startsWith('video/')) return 'media_metadata';
909
+ if (kind === 'audio' || mime.startsWith('audio/')) return 'media_metadata';
910
+ if (['pdf', 'markdown', 'text', 'code', 'config', 'notebook'].includes(kind) || ['txt', 'md', 'mdx', 'pdf', 'json', 'yaml', 'yml', 'toml', 'xml', 'ipynb', 'log', 'rst', 'sql', 'sh'].includes(ext)) return 'read_attachment';
906
911
  return '';
907
912
  }
908
913
 
@@ -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';