sparkecoder 0.1.20 → 0.1.22

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 (100) hide show
  1. package/dist/agent/index.d.ts +2 -2
  2. package/dist/agent/index.js +53 -3
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +397 -46
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -1
  7. package/dist/db/index.js.map +1 -1
  8. package/dist/{index-BzedNBK-.d.ts → index-CNwLFGiZ.d.ts} +24 -3
  9. package/dist/index.d.ts +4 -4
  10. package/dist/index.js +392 -41
  11. package/dist/index.js.map +1 -1
  12. package/dist/{schema-CkrIadxa.d.ts → schema-Df7MU3nM.d.ts} +26 -3
  13. package/dist/server/index.js +392 -41
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/tools/index.js.map +1 -1
  16. package/package.json +1 -1
  17. package/web/.next/BUILD_ID +1 -1
  18. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  19. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  20. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  21. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  22. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  23. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  24. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  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/page_client-reference-manifest.js +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  41. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  42. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  43. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  44. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  45. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  46. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  49. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1d78db71._.js → 2374f_387a1437._.js} +1 -1
  50. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_378282b1._.js → 2374f_5f58fd73._.js} +1 -1
  51. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_30f9df13._.js → 2374f_65fcfd95._.js} +1 -1
  52. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_8825dcc9._.js → 2374f_741f6b67._.js} +1 -1
  53. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9bf3c7f3._.js → 2374f_814be2c9._.js} +2 -2
  54. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5de336d2._.js → 2374f_84859a94._.js} +1 -1
  55. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_bbc99511._.js → 2374f_cfd0137a._.js} +1 -1
  56. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d94c2b70._.js → 2374f_f1038f7c._.js} +1 -1
  57. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__a984d933._.js → [root-of-the-server]__3ec22171._.js} +2 -2
  58. package/web/.next/standalone/web/.next/server/chunks/ssr/web_96bca05b._.js +1 -1
  59. package/web/.next/standalone/web/.next/server/chunks/ssr/web_c7618534._.js +8 -0
  60. package/web/.next/standalone/web/.next/server/chunks/ssr/web_d7d3e40d._.js +1 -1
  61. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  62. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  63. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  64. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  65. package/web/.next/standalone/web/.next/static/chunks/{55705f91c6cfe307.js → 3bb454ca848ec78e.js} +3 -3
  66. package/web/.next/standalone/web/.next/static/chunks/{5ec82ce8f3aabaf0.js → 5e5b485d77ac0d8f.js} +1 -1
  67. package/web/.next/standalone/web/.next/static/chunks/{c9d8a4b9a763e232.js → beb9625c4a470042.js} +1 -1
  68. package/web/.next/standalone/web/.next/static/chunks/c81c1aec4369c77f.js +5 -0
  69. package/web/.next/standalone/web/.next/static/chunks/cb355fac10c6ad11.css +1 -0
  70. package/web/.next/standalone/web/.next/static/static/chunks/{55705f91c6cfe307.js → 3bb454ca848ec78e.js} +3 -3
  71. package/web/.next/standalone/web/.next/static/static/chunks/{5ec82ce8f3aabaf0.js → 5e5b485d77ac0d8f.js} +1 -1
  72. package/web/.next/standalone/web/.next/static/static/chunks/{c9d8a4b9a763e232.js → beb9625c4a470042.js} +1 -1
  73. package/web/.next/standalone/web/.next/static/static/chunks/c81c1aec4369c77f.js +5 -0
  74. package/web/.next/standalone/web/.next/static/static/chunks/cb355fac10c6ad11.css +1 -0
  75. package/web/.next/standalone/web/src/app/(main)/layout.tsx +2 -2
  76. package/web/.next/standalone/web/src/components/ai-elements/speech-input.tsx +89 -36
  77. package/web/.next/standalone/web/src/components/chat-interface.tsx +354 -38
  78. package/web/.next/standalone/web/src/components/ui/sidebar.tsx +2 -2
  79. package/web/.next/standalone/web/src/lib/api.ts +133 -2
  80. package/web/.next/static/chunks/{55705f91c6cfe307.js → 3bb454ca848ec78e.js} +3 -3
  81. package/web/.next/static/chunks/{5ec82ce8f3aabaf0.js → 5e5b485d77ac0d8f.js} +1 -1
  82. package/web/.next/static/chunks/{c9d8a4b9a763e232.js → beb9625c4a470042.js} +1 -1
  83. package/web/.next/static/chunks/c81c1aec4369c77f.js +5 -0
  84. package/web/.next/static/chunks/cb355fac10c6ad11.css +1 -0
  85. package/web/.next/standalone/web/.next/server/chunks/ssr/web_19b6934c._.js +0 -8
  86. package/web/.next/standalone/web/.next/static/chunks/61d61c75ce7cd4ba.js +0 -5
  87. package/web/.next/standalone/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
  88. package/web/.next/standalone/web/.next/static/static/chunks/61d61c75ce7cd4ba.js +0 -5
  89. package/web/.next/standalone/web/.next/static/static/chunks/d0a69c59b1c0d99c.css +0 -1
  90. package/web/.next/static/chunks/61d61c75ce7cd4ba.js +0 -5
  91. package/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
  92. /package/web/.next/standalone/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
  93. /package/web/.next/standalone/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
  94. /package/web/.next/standalone/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
  95. /package/web/.next/standalone/web/.next/static/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
  96. /package/web/.next/standalone/web/.next/static/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
  97. /package/web/.next/standalone/web/.next/static/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
  98. /package/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
  99. /package/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
  100. /package/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
@@ -1263,9 +1263,28 @@ interface SystemModelMessage {
1263
1263
  role: 'system';
1264
1264
  content: string;
1265
1265
  }
1266
+ interface UserTextPart {
1267
+ type: 'text';
1268
+ text: string;
1269
+ }
1270
+ interface UserImagePart {
1271
+ type: 'image';
1272
+ image: string;
1273
+ mediaType?: string;
1274
+ filename?: string;
1275
+ savedPath?: string;
1276
+ }
1277
+ interface UserFilePart {
1278
+ type: 'file';
1279
+ data: string;
1280
+ mediaType?: string;
1281
+ filename?: string;
1282
+ savedPath?: string;
1283
+ }
1284
+ type UserContentPart = UserTextPart | UserImagePart | UserFilePart;
1266
1285
  interface UserModelMessage {
1267
1286
  role: 'user';
1268
- content: string;
1287
+ content: string | UserContentPart[];
1269
1288
  }
1270
1289
  interface AssistantModelMessage {
1271
1290
  role: 'assistant';
@@ -1330,7 +1349,11 @@ type schema_TodoItem = TodoItem;
1330
1349
  type schema_ToolExecution = ToolExecution;
1331
1350
  type schema_ToolModelMessage = ToolModelMessage;
1332
1351
  type schema_ToolResultPart = ToolResultPart;
1352
+ type schema_UserContentPart = UserContentPart;
1353
+ type schema_UserFilePart = UserFilePart;
1354
+ type schema_UserImagePart = UserImagePart;
1333
1355
  type schema_UserModelMessage = UserModelMessage;
1356
+ type schema_UserTextPart = UserTextPart;
1334
1357
  declare const schema_activeStreams: typeof activeStreams;
1335
1358
  declare const schema_checkpoints: typeof checkpoints;
1336
1359
  declare const schema_fileBackups: typeof fileBackups;
@@ -1341,7 +1364,7 @@ declare const schema_terminals: typeof terminals;
1341
1364
  declare const schema_todoItems: typeof todoItems;
1342
1365
  declare const schema_toolExecutions: typeof toolExecutions;
1343
1366
  declare namespace schema {
1344
- export { type schema_ActiveStream as ActiveStream, type schema_AssistantContentPart as AssistantContentPart, type schema_AssistantModelMessage as AssistantModelMessage, type schema_Checkpoint as Checkpoint, type schema_FileBackup as FileBackup, type schema_LoadedSkill as LoadedSkill, type schema_Message as Message, type schema_ModelMessage as ModelMessage, type schema_NewActiveStream as NewActiveStream, type schema_NewCheckpoint as NewCheckpoint, type schema_NewFileBackup as NewFileBackup, type schema_NewMessage as NewMessage, type schema_NewSession as NewSession, type schema_NewTerminal as NewTerminal, type schema_NewTodoItem as NewTodoItem, type schema_NewToolExecution as NewToolExecution, type schema_Session as Session, type schema_SessionConfig as SessionConfig, type schema_SystemModelMessage as SystemModelMessage, type schema_Terminal as Terminal, type schema_TodoItem as TodoItem, type schema_ToolExecution as ToolExecution, type schema_ToolModelMessage as ToolModelMessage, type schema_ToolResultPart as ToolResultPart, type schema_UserModelMessage as UserModelMessage, schema_activeStreams as activeStreams, schema_checkpoints as checkpoints, schema_fileBackups as fileBackups, schema_loadedSkills as loadedSkills, schema_messages as messages, schema_sessions as sessions, schema_terminals as terminals, schema_todoItems as todoItems, schema_toolExecutions as toolExecutions };
1367
+ export { type schema_ActiveStream as ActiveStream, type schema_AssistantContentPart as AssistantContentPart, type schema_AssistantModelMessage as AssistantModelMessage, type schema_Checkpoint as Checkpoint, type schema_FileBackup as FileBackup, type schema_LoadedSkill as LoadedSkill, type schema_Message as Message, type schema_ModelMessage as ModelMessage, type schema_NewActiveStream as NewActiveStream, type schema_NewCheckpoint as NewCheckpoint, type schema_NewFileBackup as NewFileBackup, type schema_NewMessage as NewMessage, type schema_NewSession as NewSession, type schema_NewTerminal as NewTerminal, type schema_NewTodoItem as NewTodoItem, type schema_NewToolExecution as NewToolExecution, type schema_Session as Session, type schema_SessionConfig as SessionConfig, type schema_SystemModelMessage as SystemModelMessage, type schema_Terminal as Terminal, type schema_TodoItem as TodoItem, type schema_ToolExecution as ToolExecution, type schema_ToolModelMessage as ToolModelMessage, type schema_ToolResultPart as ToolResultPart, type schema_UserContentPart as UserContentPart, type schema_UserFilePart as UserFilePart, type schema_UserImagePart as UserImagePart, type schema_UserModelMessage as UserModelMessage, type schema_UserTextPart as UserTextPart, schema_activeStreams as activeStreams, schema_checkpoints as checkpoints, schema_fileBackups as fileBackups, schema_loadedSkills as loadedSkills, schema_messages as messages, schema_sessions as sessions, schema_terminals as terminals, schema_todoItems as todoItems, schema_toolExecutions as toolExecutions };
1345
1368
  }
1346
1369
 
1347
- export { type ActiveStream as A, type Checkpoint as C, type FileBackup as F, type LoadedSkill as L, type ModelMessage as M, type NewSession as N, type Session as S, type TodoItem as T, type Message as a, type SessionConfig as b, type NewTodoItem as c, type NewToolExecution as d, type ToolExecution as e, type NewTerminal as f, type Terminal as g, schema as s };
1370
+ export { type ActiveStream as A, type Checkpoint as C, type FileBackup as F, type LoadedSkill as L, type ModelMessage as M, type NewSession as N, type Session as S, type TodoItem as T, type UserContentPart as U, type Message as a, type SessionConfig as b, type NewTodoItem as c, type NewToolExecution as d, type ToolExecution as e, type NewTerminal as f, type Terminal as g, type UserFilePart as h, type UserImagePart as i, type UserModelMessage as j, type UserTextPart as k, schema as s };
@@ -10,8 +10,8 @@ import { Hono as Hono5 } from "hono";
10
10
  import { serve } from "@hono/node-server";
11
11
  import { cors } from "hono/cors";
12
12
  import { logger } from "hono/logger";
13
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
14
- import { resolve as resolve8, dirname as dirname6, join as join3 } from "path";
13
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
14
+ import { resolve as resolve8, dirname as dirname6, join as join5 } from "path";
15
15
  import { spawn as spawn2 } from "child_process";
16
16
  import { createServer as createNetServer } from "net";
17
17
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -20,6 +20,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
20
20
  import { Hono } from "hono";
21
21
  import { zValidator } from "@hono/zod-validator";
22
22
  import { z as z9 } from "zod";
23
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync, unlinkSync } from "fs";
24
+ import { join as join3, basename as basename2, extname as extname5 } from "path";
25
+ import { nanoid as nanoid4 } from "nanoid";
23
26
 
24
27
  // src/db/index.ts
25
28
  import Database from "better-sqlite3";
@@ -3260,11 +3263,12 @@ ${this.summary}`
3260
3263
  }
3261
3264
  /**
3262
3265
  * Add a user message to the context
3266
+ * Content can be a string or an array of content parts (for messages with images/files)
3263
3267
  */
3264
- addUserMessage(text2) {
3268
+ addUserMessage(content) {
3265
3269
  const userMessage = {
3266
3270
  role: "user",
3267
- content: text2
3271
+ content
3268
3272
  };
3269
3273
  messageQueries.create(this.sessionId, userMessage);
3270
3274
  }
@@ -3364,13 +3368,62 @@ var Agent = class _Agent {
3364
3368
  getSession() {
3365
3369
  return this.session;
3366
3370
  }
3371
+ /**
3372
+ * Build user message content from prompt and attachments
3373
+ */
3374
+ buildUserMessageContent(prompt, attachments) {
3375
+ if (!attachments || attachments.length === 0) {
3376
+ return prompt;
3377
+ }
3378
+ const contentParts = [];
3379
+ const attachmentDescriptions = attachments.map((a, i) => {
3380
+ const name = a.filename || `attachment_${i + 1}`;
3381
+ const typeLabel = a.type === "image" ? "Image" : "File";
3382
+ const location = a.savedPath || "(path unknown)";
3383
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
3384
+ }).join("\n");
3385
+ contentParts.push({
3386
+ type: "text",
3387
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
3388
+ ${attachmentDescriptions}
3389
+
3390
+ You can reference these files by their paths above. The file contents are also shown inline below.`
3391
+ });
3392
+ if (prompt) {
3393
+ contentParts.push({ type: "text", text: `
3394
+ [USER MESSAGE]
3395
+ ${prompt}` });
3396
+ }
3397
+ for (const attachment of attachments) {
3398
+ if (attachment.type === "image") {
3399
+ contentParts.push({
3400
+ type: "image",
3401
+ image: attachment.data,
3402
+ // base64 data URL or raw base64
3403
+ mediaType: attachment.mediaType,
3404
+ filename: attachment.filename,
3405
+ savedPath: attachment.savedPath
3406
+ });
3407
+ } else {
3408
+ contentParts.push({
3409
+ type: "file",
3410
+ data: attachment.data,
3411
+ mediaType: attachment.mediaType || "application/octet-stream",
3412
+ filename: attachment.filename,
3413
+ savedPath: attachment.savedPath
3414
+ });
3415
+ }
3416
+ }
3417
+ return contentParts;
3418
+ }
3367
3419
  /**
3368
3420
  * Run the agent with a prompt (streaming)
3369
3421
  */
3370
3422
  async stream(options) {
3371
3423
  const config = getConfig();
3424
+ const userContent = this.buildUserMessageContent(options.prompt, options.attachments);
3372
3425
  if (!options.skipSaveUserMessage) {
3373
- this.context.addUserMessage(options.prompt);
3426
+ this.context.addUserMessage(userContent);
3374
3427
  }
3375
3428
  sessionQueries.updateStatus(this.session.id, "active");
3376
3429
  const systemPrompt = await buildSystemPrompt({
@@ -3930,11 +3983,138 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
3930
3983
  currentContent: fileDiff.currentContent
3931
3984
  });
3932
3985
  });
3986
+ function getAttachmentsDir(sessionId) {
3987
+ const appDataDir = getAppDataDirectory();
3988
+ return join3(appDataDir, "attachments", sessionId);
3989
+ }
3990
+ function ensureAttachmentsDir(sessionId) {
3991
+ const dir = getAttachmentsDir(sessionId);
3992
+ if (!existsSync10(dir)) {
3993
+ mkdirSync3(dir, { recursive: true });
3994
+ }
3995
+ return dir;
3996
+ }
3997
+ sessions2.get("/:id/attachments", async (c) => {
3998
+ const sessionId = c.req.param("id");
3999
+ const session = sessionQueries.getById(sessionId);
4000
+ if (!session) {
4001
+ return c.json({ error: "Session not found" }, 404);
4002
+ }
4003
+ const dir = getAttachmentsDir(sessionId);
4004
+ if (!existsSync10(dir)) {
4005
+ return c.json({ sessionId, attachments: [], count: 0 });
4006
+ }
4007
+ const files = readdirSync(dir);
4008
+ const attachments = files.map((filename) => {
4009
+ const filePath = join3(dir, filename);
4010
+ const stats = statSync(filePath);
4011
+ return {
4012
+ id: filename.split("_")[0],
4013
+ // Extract the nanoid prefix
4014
+ filename,
4015
+ path: filePath,
4016
+ size: stats.size,
4017
+ createdAt: stats.birthtime.toISOString()
4018
+ };
4019
+ });
4020
+ return c.json({
4021
+ sessionId,
4022
+ attachments,
4023
+ count: attachments.length
4024
+ });
4025
+ });
4026
+ sessions2.post("/:id/attachments", async (c) => {
4027
+ const sessionId = c.req.param("id");
4028
+ const session = sessionQueries.getById(sessionId);
4029
+ if (!session) {
4030
+ return c.json({ error: "Session not found" }, 404);
4031
+ }
4032
+ const contentType = c.req.header("content-type") || "";
4033
+ if (contentType.includes("multipart/form-data")) {
4034
+ try {
4035
+ const formData = await c.req.formData();
4036
+ const file = formData.get("file");
4037
+ if (!file || !(file instanceof File)) {
4038
+ return c.json({ error: "No file provided" }, 400);
4039
+ }
4040
+ const dir = ensureAttachmentsDir(sessionId);
4041
+ const id = nanoid4(10);
4042
+ const ext = extname5(file.name) || "";
4043
+ const safeFilename = `${id}_${basename2(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
4044
+ const filePath = join3(dir, safeFilename);
4045
+ const arrayBuffer = await file.arrayBuffer();
4046
+ writeFileSync2(filePath, Buffer.from(arrayBuffer));
4047
+ return c.json({
4048
+ id,
4049
+ filename: file.name,
4050
+ storedAs: safeFilename,
4051
+ path: filePath,
4052
+ size: file.size,
4053
+ mediaType: file.type,
4054
+ sessionId
4055
+ }, 201);
4056
+ } catch (err) {
4057
+ console.error("Failed to upload attachment:", err);
4058
+ return c.json({ error: "Failed to upload file" }, 500);
4059
+ }
4060
+ }
4061
+ try {
4062
+ const body = await c.req.json();
4063
+ if (!body.filename || !body.data) {
4064
+ return c.json({ error: "Missing filename or data" }, 400);
4065
+ }
4066
+ const dir = ensureAttachmentsDir(sessionId);
4067
+ const id = nanoid4(10);
4068
+ const ext = extname5(body.filename) || "";
4069
+ const safeFilename = `${id}_${basename2(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
4070
+ const filePath = join3(dir, safeFilename);
4071
+ let base64Data = body.data;
4072
+ if (base64Data.includes(",")) {
4073
+ base64Data = base64Data.split(",")[1];
4074
+ }
4075
+ const buffer = Buffer.from(base64Data, "base64");
4076
+ writeFileSync2(filePath, buffer);
4077
+ return c.json({
4078
+ id,
4079
+ filename: body.filename,
4080
+ storedAs: safeFilename,
4081
+ path: filePath,
4082
+ size: buffer.length,
4083
+ mediaType: body.mediaType,
4084
+ sessionId
4085
+ }, 201);
4086
+ } catch (err) {
4087
+ console.error("Failed to upload attachment:", err);
4088
+ return c.json({ error: "Failed to upload file" }, 500);
4089
+ }
4090
+ });
4091
+ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
4092
+ const sessionId = c.req.param("id");
4093
+ const attachmentId = c.req.param("attachmentId");
4094
+ const session = sessionQueries.getById(sessionId);
4095
+ if (!session) {
4096
+ return c.json({ error: "Session not found" }, 404);
4097
+ }
4098
+ const dir = getAttachmentsDir(sessionId);
4099
+ if (!existsSync10(dir)) {
4100
+ return c.json({ error: "Attachment not found" }, 404);
4101
+ }
4102
+ const files = readdirSync(dir);
4103
+ const file = files.find((f) => f.startsWith(attachmentId + "_"));
4104
+ if (!file) {
4105
+ return c.json({ error: "Attachment not found" }, 404);
4106
+ }
4107
+ const filePath = join3(dir, file);
4108
+ unlinkSync(filePath);
4109
+ return c.json({ success: true, id: attachmentId });
4110
+ });
3933
4111
 
3934
4112
  // src/server/routes/agents.ts
3935
4113
  import { Hono as Hono2 } from "hono";
3936
4114
  import { zValidator as zValidator2 } from "@hono/zod-validator";
3937
4115
  import { z as z10 } from "zod";
4116
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
4117
+ import { join as join4 } from "path";
3938
4118
 
3939
4119
  // src/server/resumable-stream.ts
3940
4120
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -4009,11 +4189,23 @@ var streamContext = createResumableStreamContext({
4009
4189
  });
4010
4190
 
4011
4191
  // src/server/routes/agents.ts
4012
- import { nanoid as nanoid4 } from "nanoid";
4192
+ import { nanoid as nanoid5 } from "nanoid";
4013
4193
  var agents = new Hono2();
4014
- var runPromptSchema = z10.object({
4015
- prompt: z10.string().min(1)
4194
+ var attachmentSchema = z10.object({
4195
+ type: z10.enum(["image", "file"]),
4196
+ data: z10.string(),
4197
+ // base64 data URL or raw base64
4198
+ mediaType: z10.string().optional(),
4199
+ filename: z10.string().optional()
4016
4200
  });
4201
+ var runPromptSchema = z10.object({
4202
+ prompt: z10.string(),
4203
+ // Can be empty if attachments are provided
4204
+ attachments: z10.array(attachmentSchema).optional()
4205
+ }).refine(
4206
+ (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
4207
+ { message: "Either prompt or attachments must be provided" }
4208
+ );
4017
4209
  var quickStartSchema = z10.object({
4018
4210
  prompt: z10.string().min(1),
4019
4211
  name: z10.string().optional(),
@@ -4025,7 +4217,53 @@ var rejectSchema = z10.object({
4025
4217
  reason: z10.string().optional()
4026
4218
  }).optional();
4027
4219
  var streamAbortControllers = /* @__PURE__ */ new Map();
4028
- function createAgentStreamProducer(sessionId, prompt, streamId) {
4220
+ function getAttachmentsDirectory(sessionId) {
4221
+ const appDataDir = getAppDataDirectory();
4222
+ return join4(appDataDir, "attachments", sessionId);
4223
+ }
4224
+ function saveAttachmentToDisk(sessionId, attachment, index) {
4225
+ const attachmentsDir = getAttachmentsDirectory(sessionId);
4226
+ if (!existsSync11(attachmentsDir)) {
4227
+ mkdirSync4(attachmentsDir, { recursive: true });
4228
+ }
4229
+ let filename = attachment.filename;
4230
+ if (!filename) {
4231
+ const ext = getExtensionFromMediaType(attachment.mediaType, attachment.type);
4232
+ filename = `attachment_${index + 1}${ext}`;
4233
+ }
4234
+ let base64Data = attachment.data;
4235
+ if (base64Data.includes(",")) {
4236
+ base64Data = base64Data.split(",")[1];
4237
+ }
4238
+ const filePath = join4(attachmentsDir, filename);
4239
+ const buffer = Buffer.from(base64Data, "base64");
4240
+ writeFileSync3(filePath, buffer);
4241
+ return filePath;
4242
+ }
4243
+ function getExtensionFromMediaType(mediaType, type) {
4244
+ if (!mediaType) {
4245
+ return type === "image" ? ".png" : ".bin";
4246
+ }
4247
+ const mimeToExt = {
4248
+ "image/png": ".png",
4249
+ "image/jpeg": ".jpg",
4250
+ "image/jpg": ".jpg",
4251
+ "image/gif": ".gif",
4252
+ "image/webp": ".webp",
4253
+ "image/svg+xml": ".svg",
4254
+ "application/pdf": ".pdf",
4255
+ "text/plain": ".txt",
4256
+ "text/markdown": ".md",
4257
+ "application/json": ".json",
4258
+ "application/javascript": ".js",
4259
+ "text/javascript": ".js",
4260
+ "text/typescript": ".ts",
4261
+ "text/html": ".html",
4262
+ "text/css": ".css"
4263
+ };
4264
+ return mimeToExt[mediaType] || ".bin";
4265
+ }
4266
+ function createAgentStreamProducer(sessionId, prompt, streamId, attachments) {
4029
4267
  return () => {
4030
4268
  const { readable, writable } = new TransformStream();
4031
4269
  const writer = writable.getWriter();
@@ -4058,9 +4296,53 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4058
4296
  try {
4059
4297
  const agent = await Agent.create({ sessionId });
4060
4298
  await writeSSE(JSON.stringify({ type: "data-stream-id", streamId }));
4299
+ let broadcastContent;
4300
+ if (attachments && attachments.length > 0) {
4301
+ const contentParts = [];
4302
+ const attachmentDescriptions = attachments.map((a, i) => {
4303
+ const name = a.filename || `attachment_${i + 1}`;
4304
+ const typeLabel = a.type === "image" ? "Image" : "File";
4305
+ const location = a.savedPath || "(path unknown)";
4306
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
4307
+ }).join("\n");
4308
+ contentParts.push({
4309
+ type: "text",
4310
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
4311
+ ${attachmentDescriptions}
4312
+
4313
+ You can reference these files by their paths above. The file contents are also shown inline below.`
4314
+ });
4315
+ if (prompt) {
4316
+ contentParts.push({ type: "text", text: `
4317
+ [USER MESSAGE]
4318
+ ${prompt}` });
4319
+ }
4320
+ for (const attachment of attachments) {
4321
+ if (attachment.type === "image") {
4322
+ contentParts.push({
4323
+ type: "image",
4324
+ image: attachment.data,
4325
+ mediaType: attachment.mediaType,
4326
+ filename: attachment.filename,
4327
+ savedPath: attachment.savedPath
4328
+ });
4329
+ } else {
4330
+ contentParts.push({
4331
+ type: "file",
4332
+ data: attachment.data,
4333
+ mediaType: attachment.mediaType || "application/octet-stream",
4334
+ filename: attachment.filename,
4335
+ savedPath: attachment.savedPath
4336
+ });
4337
+ }
4338
+ }
4339
+ broadcastContent = contentParts;
4340
+ } else {
4341
+ broadcastContent = prompt;
4342
+ }
4061
4343
  await writeSSE(JSON.stringify({
4062
4344
  type: "data-user-message",
4063
- data: { id: `user_${Date.now()}`, content: prompt }
4345
+ data: { id: `user_${Date.now()}`, content: broadcastContent }
4064
4346
  }));
4065
4347
  const messageId = `msg_${Date.now()}`;
4066
4348
  await writeSSE(JSON.stringify({ type: "start", messageId }));
@@ -4068,6 +4350,8 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4068
4350
  let textStarted = false;
4069
4351
  const result = await agent.stream({
4070
4352
  prompt,
4353
+ attachments,
4354
+ // Pass attachments to agent
4071
4355
  abortSignal: abortController.signal,
4072
4356
  // Use our managed abort controller, NOT client signal
4073
4357
  skipSaveUserMessage: true,
@@ -4177,14 +4461,20 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4177
4461
  } else {
4178
4462
  await writeSSE(JSON.stringify({ type: "finish" }));
4179
4463
  }
4180
- activeStreamQueries.finish(streamId);
4464
+ try {
4465
+ activeStreamQueries.finish(streamId);
4466
+ } catch {
4467
+ }
4181
4468
  } catch (error) {
4182
4469
  if (error.name === "AbortError" || error.message?.includes("aborted")) {
4183
4470
  await writeSSE(JSON.stringify({ type: "abort" }));
4184
4471
  } else {
4185
4472
  console.error("Agent error:", error);
4186
4473
  await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
4187
- activeStreamQueries.markError(streamId);
4474
+ try {
4475
+ activeStreamQueries.markError(streamId);
4476
+ } catch {
4477
+ }
4188
4478
  }
4189
4479
  } finally {
4190
4480
  cleanupAbortController();
@@ -4200,19 +4490,74 @@ agents.post(
4200
4490
  zValidator2("json", runPromptSchema),
4201
4491
  async (c) => {
4202
4492
  const id = c.req.param("id");
4203
- const { prompt } = c.req.valid("json");
4493
+ const { prompt, attachments } = c.req.valid("json");
4204
4494
  const session = sessionQueries.getById(id);
4205
4495
  if (!session) {
4206
4496
  return c.json({ error: "Session not found" }, 404);
4207
4497
  }
4208
4498
  const nextSequence = messageQueries.getNextSequence(id);
4209
4499
  await createCheckpoint(id, session.workingDirectory, nextSequence);
4210
- messageQueries.create(id, { role: "user", content: prompt });
4211
- const streamId = `stream_${id}_${nanoid4(10)}`;
4500
+ let userMessageContent;
4501
+ const streamAttachments = attachments;
4502
+ if (streamAttachments && streamAttachments.length > 0) {
4503
+ for (let i = 0; i < streamAttachments.length; i++) {
4504
+ const attachment = streamAttachments[i];
4505
+ try {
4506
+ const savedPath = saveAttachmentToDisk(id, attachment, i);
4507
+ attachment.savedPath = savedPath;
4508
+ } catch (err) {
4509
+ console.error(`Failed to save attachment ${i}:`, err);
4510
+ }
4511
+ }
4512
+ const contentParts = [];
4513
+ const attachmentDescriptions = streamAttachments.map((a, i) => {
4514
+ const name = a.filename || `attachment_${i + 1}`;
4515
+ const typeLabel = a.type === "image" ? "Image" : "File";
4516
+ const location = a.savedPath || "(path unknown)";
4517
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
4518
+ }).join("\n");
4519
+ contentParts.push({
4520
+ type: "text",
4521
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
4522
+ ${attachmentDescriptions}
4523
+
4524
+ You can reference these files by their paths above. The file contents are also shown inline below.`
4525
+ });
4526
+ if (prompt) {
4527
+ contentParts.push({ type: "text", text: `
4528
+ [USER MESSAGE]
4529
+ ${prompt}` });
4530
+ }
4531
+ for (const attachment of streamAttachments) {
4532
+ if (attachment.type === "image") {
4533
+ contentParts.push({
4534
+ type: "image",
4535
+ image: attachment.data,
4536
+ // base64 data URL or raw base64
4537
+ mediaType: attachment.mediaType,
4538
+ filename: attachment.filename,
4539
+ savedPath: attachment.savedPath
4540
+ });
4541
+ } else {
4542
+ contentParts.push({
4543
+ type: "file",
4544
+ data: attachment.data,
4545
+ mediaType: attachment.mediaType || "application/octet-stream",
4546
+ filename: attachment.filename,
4547
+ savedPath: attachment.savedPath
4548
+ });
4549
+ }
4550
+ }
4551
+ userMessageContent = contentParts;
4552
+ } else {
4553
+ userMessageContent = prompt;
4554
+ }
4555
+ messageQueries.create(id, { role: "user", content: userMessageContent });
4556
+ const streamId = `stream_${id}_${nanoid5(10)}`;
4212
4557
  activeStreamQueries.create(id, streamId);
4213
4558
  const stream = await streamContext.resumableStream(
4214
4559
  streamId,
4215
- createAgentStreamProducer(id, prompt, streamId)
4560
+ createAgentStreamProducer(id, prompt, streamId, streamAttachments)
4216
4561
  );
4217
4562
  if (!stream) {
4218
4563
  return c.json({ error: "Failed to create stream" }, 500);
@@ -4405,7 +4750,7 @@ agents.post(
4405
4750
  sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
4406
4751
  });
4407
4752
  const session = agent.getSession();
4408
- const streamId = `stream_${session.id}_${nanoid4(10)}`;
4753
+ const streamId = `stream_${session.id}_${nanoid5(10)}`;
4409
4754
  await createCheckpoint(session.id, session.workingDirectory, 0);
4410
4755
  activeStreamQueries.create(session.id, streamId);
4411
4756
  const createQuickStreamProducer = () => {
@@ -5064,11 +5409,11 @@ function getWebDirectory() {
5064
5409
  try {
5065
5410
  const currentDir = dirname6(fileURLToPath2(import.meta.url));
5066
5411
  const webDir = resolve8(currentDir, "..", "web");
5067
- if (existsSync10(webDir) && existsSync10(join3(webDir, "package.json"))) {
5412
+ if (existsSync12(webDir) && existsSync12(join5(webDir, "package.json"))) {
5068
5413
  return webDir;
5069
5414
  }
5070
5415
  const altWebDir = resolve8(currentDir, "..", "..", "web");
5071
- if (existsSync10(altWebDir) && existsSync10(join3(altWebDir, "package.json"))) {
5416
+ if (existsSync12(altWebDir) && existsSync12(join5(altWebDir, "package.json"))) {
5072
5417
  return altWebDir;
5073
5418
  }
5074
5419
  return null;
@@ -5126,23 +5471,23 @@ async function findWebPort(preferredPort) {
5126
5471
  return { port: preferredPort, alreadyRunning: false };
5127
5472
  }
5128
5473
  function hasProductionBuild(webDir) {
5129
- const buildIdPath = join3(webDir, ".next", "BUILD_ID");
5130
- return existsSync10(buildIdPath);
5474
+ const buildIdPath = join5(webDir, ".next", "BUILD_ID");
5475
+ return existsSync12(buildIdPath);
5131
5476
  }
5132
5477
  function hasSourceFiles(webDir) {
5133
- const appDir = join3(webDir, "src", "app");
5134
- const pagesDir = join3(webDir, "src", "pages");
5135
- const rootAppDir = join3(webDir, "app");
5136
- const rootPagesDir = join3(webDir, "pages");
5137
- return existsSync10(appDir) || existsSync10(pagesDir) || existsSync10(rootAppDir) || existsSync10(rootPagesDir);
5478
+ const appDir = join5(webDir, "src", "app");
5479
+ const pagesDir = join5(webDir, "src", "pages");
5480
+ const rootAppDir = join5(webDir, "app");
5481
+ const rootPagesDir = join5(webDir, "pages");
5482
+ return existsSync12(appDir) || existsSync12(pagesDir) || existsSync12(rootAppDir) || existsSync12(rootPagesDir);
5138
5483
  }
5139
5484
  function getStandaloneServerPath(webDir) {
5140
5485
  const possiblePaths = [
5141
- join3(webDir, ".next", "standalone", "server.js"),
5142
- join3(webDir, ".next", "standalone", "web", "server.js")
5486
+ join5(webDir, ".next", "standalone", "server.js"),
5487
+ join5(webDir, ".next", "standalone", "web", "server.js")
5143
5488
  ];
5144
5489
  for (const serverPath of possiblePaths) {
5145
- if (existsSync10(serverPath)) {
5490
+ if (existsSync12(serverPath)) {
5146
5491
  return serverPath;
5147
5492
  }
5148
5493
  }
@@ -5182,15 +5527,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5182
5527
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
5183
5528
  return { process: null, port: actualPort };
5184
5529
  }
5185
- const usePnpm = existsSync10(join3(webDir, "pnpm-lock.yaml"));
5186
- const useNpm = !usePnpm && existsSync10(join3(webDir, "package-lock.json"));
5530
+ const usePnpm = existsSync12(join5(webDir, "pnpm-lock.yaml"));
5531
+ const useNpm = !usePnpm && existsSync12(join5(webDir, "package-lock.json"));
5187
5532
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
5188
5533
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
5189
5534
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
5190
5535
  const runtimeConfig = { apiBaseUrl: apiUrl };
5191
- const runtimeConfigPath = join3(webDir, "runtime-config.json");
5536
+ const runtimeConfigPath = join5(webDir, "runtime-config.json");
5192
5537
  try {
5193
- writeFileSync2(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
5538
+ writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
5194
5539
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
5195
5540
  } catch (err) {
5196
5541
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -5216,7 +5561,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5216
5561
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
5217
5562
  } else if (hasBuild && (isProduction || !hasSource)) {
5218
5563
  command = pkgManager;
5219
- args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start", "--", "-p", String(actualPort)];
5564
+ args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"];
5220
5565
  } else if (hasSource) {
5221
5566
  if (isProduction && !hasBuild) {
5222
5567
  if (!quiet) console.log(" \u{1F4E6} Building Web UI for production...");
@@ -5228,10 +5573,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5228
5573
  }
5229
5574
  if (!quiet) console.log(" \u2713 Web UI build complete");
5230
5575
  command = pkgManager;
5231
- args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start", "--", "-p", String(actualPort)];
5576
+ args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"];
5232
5577
  } else {
5233
5578
  command = pkgManager;
5234
- args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev", "--", "-p", String(actualPort)];
5579
+ args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev"];
5235
5580
  }
5236
5581
  } else {
5237
5582
  if (!quiet) {
@@ -5259,6 +5604,12 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5259
5604
  }, startupTimeout);
5260
5605
  child.stdout?.on("data", (data) => {
5261
5606
  const output = data.toString();
5607
+ if (!quiet) {
5608
+ const lines = output.trim().split("\n").filter((l) => l.trim());
5609
+ for (const line of lines) {
5610
+ console.log(` Web UI: ${line}`);
5611
+ }
5612
+ }
5262
5613
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
5263
5614
  started = true;
5264
5615
  clearTimeout(timeout);
@@ -5266,9 +5617,9 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5266
5617
  }
5267
5618
  });
5268
5619
  child.stderr?.on("data", (data) => {
5269
- const output = data.toString();
5270
- if (output.toLowerCase().includes("error")) {
5271
- if (!quiet) console.error(` Web UI error: ${output.trim().slice(0, 200)}`);
5620
+ const output = data.toString().trim();
5621
+ if (!quiet && output) {
5622
+ console.error(` Web UI: ${output.slice(0, 500)}`);
5272
5623
  }
5273
5624
  });
5274
5625
  child.on("error", (err) => {
@@ -5374,8 +5725,8 @@ async function startServer(options = {}) {
5374
5725
  if (options.workingDirectory) {
5375
5726
  config.resolvedWorkingDirectory = options.workingDirectory;
5376
5727
  }
5377
- if (!existsSync10(config.resolvedWorkingDirectory)) {
5378
- mkdirSync3(config.resolvedWorkingDirectory, { recursive: true });
5728
+ if (!existsSync12(config.resolvedWorkingDirectory)) {
5729
+ mkdirSync5(config.resolvedWorkingDirectory, { recursive: true });
5379
5730
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
5380
5731
  }
5381
5732
  initDatabase(config.resolvedDatabasePath);