sparkecoder 0.1.59 → 0.1.60

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.
Files changed (95) hide show
  1. package/dist/agent/index.js +74 -27
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +88 -34
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +87 -33
  6. package/dist/index.js.map +1 -1
  7. package/dist/server/index.js +87 -33
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/skills/default/browser.md +143 -0
  10. package/dist/skills/default/code-review.md +122 -0
  11. package/dist/skills/default/debugging.md +105 -0
  12. package/dist/skills/default/refactoring.md +197 -0
  13. package/dist/tools/index.d.ts +16 -1
  14. package/dist/tools/index.js +74 -27
  15. package/dist/tools/index.js.map +1 -1
  16. package/package.json +4 -1
  17. package/src/skills/default/browser.md +143 -0
  18. package/src/skills/default/code-review.md +122 -0
  19. package/src/skills/default/debugging.md +105 -0
  20. package/src/skills/default/refactoring.md +197 -0
  21. package/web/.next/BUILD_ID +1 -1
  22. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  23. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  24. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  25. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  26. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  41. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  50. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  76. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  84. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  85. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  86. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  87. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → R5xiWSOp_Nqqe_js-LROo}/_buildManifest.js +0 -0
  88. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → R5xiWSOp_Nqqe_js-LROo}/_clientMiddlewareManifest.json +0 -0
  89. /package/web/.next/standalone/web/.next/static/{static/u5qqIWWrYpWW_mZUgKKjg → R5xiWSOp_Nqqe_js-LROo}/_ssgManifest.js +0 -0
  90. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/R5xiWSOp_Nqqe_js-LROo}/_buildManifest.js +0 -0
  91. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/R5xiWSOp_Nqqe_js-LROo}/_clientMiddlewareManifest.json +0 -0
  92. /package/web/.next/standalone/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → static/R5xiWSOp_Nqqe_js-LROo}/_ssgManifest.js +0 -0
  93. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → R5xiWSOp_Nqqe_js-LROo}/_buildManifest.js +0 -0
  94. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → R5xiWSOp_Nqqe_js-LROo}/_clientMiddlewareManifest.json +0 -0
  95. /package/web/.next/static/{u5qqIWWrYpWW_mZUgKKjg → R5xiWSOp_Nqqe_js-LROo}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -558,8 +558,15 @@ function discoverSkillDirectories(workingDir) {
558
558
  if (existsSync(agentsMd)) {
559
559
  agentsMdPath = agentsMd;
560
560
  }
561
- const builtInSkillsDir = resolve(dirname(import.meta.url.replace("file://", "")), "../skills/default");
562
- if (existsSync(builtInSkillsDir)) {
561
+ const baseDir = dirname(import.meta.url.replace("file://", ""));
562
+ const builtInCandidates = [
563
+ resolve(baseDir, "../skills/default"),
564
+ // dev: src/config → src/skills/default
565
+ resolve(baseDir, "./skills/default")
566
+ // prod: dist/ → dist/skills/default
567
+ ];
568
+ const builtInSkillsDir = builtInCandidates.find((p) => existsSync(p));
569
+ if (builtInSkillsDir) {
563
570
  onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
564
571
  allDirectories.push(builtInSkillsDir);
565
572
  }
@@ -938,7 +945,7 @@ __export(skills_exports, {
938
945
  loadSkillsFromDirectory: () => loadSkillsFromDirectory
939
946
  });
940
947
  import { readFile as readFile6, readdir } from "fs/promises";
941
- import { resolve as resolve6, basename, extname as extname3, relative as relative4 } from "path";
948
+ import { resolve as resolve6, basename, extname as extname4, relative as relative4 } from "path";
942
949
  import { existsSync as existsSync8 } from "fs";
943
950
  import { minimatch } from "minimatch";
944
951
  function parseSkillFrontmatter(content) {
@@ -1009,7 +1016,7 @@ function parseSkillFrontmatter(content) {
1009
1016
  }
1010
1017
  }
1011
1018
  function getSkillNameFromPath(filePath) {
1012
- return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1019
+ return basename(filePath, extname4(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1013
1020
  }
1014
1021
  async function loadSkillsFromDirectory(directory, options = {}) {
1015
1022
  const {
@@ -1334,9 +1341,9 @@ var init_hasher = __esm({
1334
1341
  });
1335
1342
 
1336
1343
  // src/semantic/chunker.ts
1337
- import { extname as extname5, basename as basename2 } from "path";
1344
+ import { extname as extname6, basename as basename2 } from "path";
1338
1345
  function detectLanguage(filePath) {
1339
- const ext = extname5(filePath).toLowerCase();
1346
+ const ext = extname6(filePath).toLowerCase();
1340
1347
  return LANGUAGE_MAP[ext] || "unknown";
1341
1348
  }
1342
1349
  function supportsSemanticChunking(language) {
@@ -2428,7 +2435,7 @@ import { zValidator } from "@hono/zod-validator";
2428
2435
  import { z as z13 } from "zod";
2429
2436
  import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync as statSync2, unlinkSync } from "fs";
2430
2437
  import { readdir as readdir5 } from "fs/promises";
2431
- import { join as join5, basename as basename4, extname as extname6, relative as relative9 } from "path";
2438
+ import { join as join5, basename as basename4, extname as extname7, relative as relative9 } from "path";
2432
2439
  import { nanoid as nanoid4 } from "nanoid";
2433
2440
 
2434
2441
  // src/agent/index.ts
@@ -3039,26 +3046,41 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3039
3046
  import { tool as tool2 } from "ai";
3040
3047
  import { z as z3 } from "zod";
3041
3048
  import { readFile as readFile2, stat } from "fs/promises";
3042
- import { resolve as resolve2, relative, isAbsolute } from "path";
3049
+ import { resolve as resolve2, relative, isAbsolute, extname } from "path";
3043
3050
  import { existsSync as existsSync3 } from "fs";
3044
3051
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
3052
+ var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
3045
3053
  var MAX_OUTPUT_CHARS3 = 5e4;
3054
+ var IMAGE_EXTENSIONS = {
3055
+ ".png": "image/png",
3056
+ ".jpg": "image/jpeg",
3057
+ ".jpeg": "image/jpeg",
3058
+ ".gif": "image/gif",
3059
+ ".webp": "image/webp"
3060
+ };
3061
+ function isImageFile(filePath) {
3062
+ return extname(filePath).toLowerCase() in IMAGE_EXTENSIONS;
3063
+ }
3064
+ function getImageMediaType(filePath) {
3065
+ return IMAGE_EXTENSIONS[extname(filePath).toLowerCase()] || "image/png";
3066
+ }
3046
3067
  var readFileInputSchema = z3.object({
3047
- path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute."),
3048
- startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed)"),
3049
- endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive)")
3068
+ path: z3.string().describe("The path to the file to read. Can be relative to working directory or absolute. Supports text files and images (png, jpg, jpeg, gif, webp)."),
3069
+ startLine: z3.number().optional().describe("Optional: Start reading from this line number (1-indexed). Only for text files."),
3070
+ endLine: z3.number().optional().describe("Optional: Stop reading at this line number (1-indexed, inclusive). Only for text files.")
3050
3071
  });
3051
3072
  function createReadFileTool(options) {
3052
3073
  return tool2({
3053
3074
  description: `Read the contents of a file. Provide a path relative to the working directory (${options.workingDirectory}) or an absolute path.
3054
- Large files will be automatically truncated. Binary files are not supported.
3055
- Use this to understand existing code, check file contents, or gather context.`,
3075
+ Supports text files (automatically truncated if large) and image files (png, jpg, jpeg, gif, webp).
3076
+ For images, the file contents are returned as visual data you can see and analyze.
3077
+ Use this to understand existing code, check file contents, view screenshots, or gather context.`,
3056
3078
  inputSchema: readFileInputSchema,
3057
- execute: async ({ path, startLine, endLine }) => {
3079
+ execute: async ({ path: filePath, startLine, endLine }) => {
3058
3080
  try {
3059
- const absolutePath = isAbsolute(path) ? path : resolve2(options.workingDirectory, path);
3081
+ const absolutePath = isAbsolute(filePath) ? filePath : resolve2(options.workingDirectory, filePath);
3060
3082
  const relativePath = relative(options.workingDirectory, absolutePath);
3061
- if (relativePath.startsWith("..") && !isAbsolute(path)) {
3083
+ if (relativePath.startsWith("..") && !isAbsolute(filePath)) {
3062
3084
  return {
3063
3085
  success: false,
3064
3086
  error: "Path escapes the working directory. Use an absolute path if intentional.",
@@ -3068,22 +3090,43 @@ Use this to understand existing code, check file contents, or gather context.`,
3068
3090
  if (!existsSync3(absolutePath)) {
3069
3091
  return {
3070
3092
  success: false,
3071
- error: `File not found: ${path}`,
3093
+ error: `File not found: ${filePath}`,
3072
3094
  content: null
3073
3095
  };
3074
3096
  }
3075
3097
  const stats = await stat(absolutePath);
3076
- if (stats.size > MAX_FILE_SIZE) {
3098
+ if (stats.isDirectory()) {
3077
3099
  return {
3078
3100
  success: false,
3079
- error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
3101
+ error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
3080
3102
  content: null
3081
3103
  };
3082
3104
  }
3083
- if (stats.isDirectory()) {
3105
+ if (isImageFile(absolutePath)) {
3106
+ if (stats.size > MAX_IMAGE_SIZE) {
3107
+ return {
3108
+ success: false,
3109
+ error: `Image is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_IMAGE_SIZE / 1024 / 1024}MB.`,
3110
+ content: null
3111
+ };
3112
+ }
3113
+ const buffer = await readFile2(absolutePath);
3114
+ const base64 = buffer.toString("base64");
3115
+ const mediaType = getImageMediaType(absolutePath);
3116
+ return {
3117
+ success: true,
3118
+ path: absolutePath,
3119
+ relativePath: relative(options.workingDirectory, absolutePath),
3120
+ content: `[Image: ${relativePath} (${mediaType}, ${(stats.size / 1024).toFixed(1)}KB)]`,
3121
+ mediaType,
3122
+ imageData: base64,
3123
+ sizeBytes: stats.size
3124
+ };
3125
+ }
3126
+ if (stats.size > MAX_FILE_SIZE) {
3084
3127
  return {
3085
3128
  success: false,
3086
- error: 'Path is a directory, not a file. Use bash with "ls" to list directory contents.',
3129
+ error: `File is too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum size is ${MAX_FILE_SIZE / 1024 / 1024}MB.`,
3087
3130
  content: null
3088
3131
  };
3089
3132
  }
@@ -3099,9 +3142,7 @@ Use this to understand existing code, check file contents, or gather context.`,
3099
3142
  content: null
3100
3143
  };
3101
3144
  }
3102
- content = lines.slice(start, end).join("\n");
3103
- const lineNumbers = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
3104
- content = lineNumbers;
3145
+ content = lines.slice(start, end).map((line, idx) => `${(start + idx + 1).toString().padStart(4)}: ${line}`).join("\n");
3105
3146
  }
3106
3147
  const truncatedContent = truncateOutput(content, MAX_OUTPUT_CHARS3);
3107
3148
  const wasTruncated = truncatedContent.length < content.length;
@@ -3128,6 +3169,19 @@ Use this to understand existing code, check file contents, or gather context.`,
3128
3169
  content: null
3129
3170
  };
3130
3171
  }
3172
+ },
3173
+ toModelOutput: ({ output }) => {
3174
+ if (output && typeof output === "object" && "imageData" in output && output.imageData) {
3175
+ const result = output;
3176
+ return {
3177
+ type: "content",
3178
+ value: [
3179
+ { type: "text", text: result.content },
3180
+ { type: "image-data", data: result.imageData, mediaType: result.mediaType }
3181
+ ]
3182
+ };
3183
+ }
3184
+ return typeof output === "string" ? { type: "text", value: output } : { type: "json", value: output };
3131
3185
  }
3132
3186
  });
3133
3187
  }
@@ -3328,7 +3382,7 @@ function clearCheckpointManager(sessionId) {
3328
3382
  }
3329
3383
 
3330
3384
  // src/lsp/index.ts
3331
- import { extname as extname2, dirname as dirname4 } from "path";
3385
+ import { extname as extname3, dirname as dirname4 } from "path";
3332
3386
 
3333
3387
  // src/lsp/servers.ts
3334
3388
  import { spawn } from "child_process";
@@ -3451,9 +3505,9 @@ import {
3451
3505
  import { pathToFileURL, fileURLToPath } from "url";
3452
3506
  import { readFile as readFile4 } from "fs/promises";
3453
3507
  import { existsSync as existsSync6 } from "fs";
3454
- import { extname, normalize } from "path";
3508
+ import { extname as extname2, normalize } from "path";
3455
3509
  function getLanguageId(filePath) {
3456
- const ext = extname(filePath).toLowerCase();
3510
+ const ext = extname2(filePath).toLowerCase();
3457
3511
  const map = {
3458
3512
  ".ts": "typescript",
3459
3513
  ".tsx": "typescriptreact",
@@ -3841,7 +3895,7 @@ var state = {
3841
3895
  };
3842
3896
  async function getClientForFile(filePath) {
3843
3897
  const normalized = normalizePath(filePath);
3844
- const ext = extname2(normalized);
3898
+ const ext = extname3(normalized);
3845
3899
  const serverDef = getServerForExtension(ext);
3846
3900
  if (!serverDef) {
3847
3901
  return null;
@@ -3934,7 +3988,7 @@ async function formatDiagnosticsOutput(filePath, options = {}) {
3934
3988
  return formatDiagnosticsForAgent(filePath, diagnostics, options);
3935
3989
  }
3936
3990
  function isSupported(filePath) {
3937
- const ext = extname2(filePath);
3991
+ const ext = extname3(filePath);
3938
3992
  return getServerForExtension(ext) !== null;
3939
3993
  }
3940
3994
 
@@ -4361,7 +4415,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
4361
4415
  // src/tools/linter.ts
4362
4416
  import { tool as tool6 } from "ai";
4363
4417
  import { z as z7 } from "zod";
4364
- import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname4 } from "path";
4418
+ import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname5 } from "path";
4365
4419
  import { existsSync as existsSync9 } from "fs";
4366
4420
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
4367
4421
  var linterInputSchema = z7.object({
@@ -4384,7 +4438,7 @@ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
4384
4438
  }
4385
4439
  await walk(fullPath);
4386
4440
  } else if (entry.isFile()) {
4387
- const ext = extname4(entry.name);
4441
+ const ext = extname5(entry.name);
4388
4442
  if (supportedExtensions.includes(ext)) {
4389
4443
  files.push(fullPath);
4390
4444
  }
@@ -7208,7 +7262,7 @@ sessions.post("/:id/attachments", async (c) => {
7208
7262
  }
7209
7263
  const dir = ensureAttachmentsDir(sessionId);
7210
7264
  const id = nanoid4(10);
7211
- const ext = extname6(file.name) || "";
7265
+ const ext = extname7(file.name) || "";
7212
7266
  const safeFilename = `${id}_${basename4(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
7213
7267
  const filePath = join5(dir, safeFilename);
7214
7268
  const arrayBuffer = await file.arrayBuffer();
@@ -7234,7 +7288,7 @@ sessions.post("/:id/attachments", async (c) => {
7234
7288
  }
7235
7289
  const dir = ensureAttachmentsDir(sessionId);
7236
7290
  const id = nanoid4(10);
7237
- const ext = extname6(body.filename) || "";
7291
+ const ext = extname7(body.filename) || "";
7238
7292
  const safeFilename = `${id}_${basename4(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
7239
7293
  const filePath = join5(dir, safeFilename);
7240
7294
  let base64Data = body.data;
@@ -7364,7 +7418,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
7364
7418
  if (entry.name.startsWith(".")) {
7365
7419
  continue;
7366
7420
  }
7367
- const ext = extname6(entry.name).toLowerCase();
7421
+ const ext = extname7(entry.name).toLowerCase();
7368
7422
  if (IGNORED_EXTENSIONS.has(ext)) {
7369
7423
  continue;
7370
7424
  }