sparkecoder 0.1.21 → 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 (87) 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_c7618534._.js +8 -0
  59. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  60. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  61. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  62. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  63. package/web/.next/standalone/web/.next/static/chunks/{a86053f0894587f2.js → 3bb454ca848ec78e.js} +3 -3
  64. package/web/.next/standalone/web/.next/static/chunks/{5ec82ce8f3aabaf0.js → 5e5b485d77ac0d8f.js} +1 -1
  65. package/web/.next/standalone/web/.next/static/chunks/cb355fac10c6ad11.css +1 -0
  66. package/web/.next/standalone/web/.next/static/static/chunks/{a86053f0894587f2.js → 3bb454ca848ec78e.js} +3 -3
  67. package/web/.next/{static/chunks/5ec82ce8f3aabaf0.js → standalone/web/.next/static/static/chunks/5e5b485d77ac0d8f.js} +1 -1
  68. package/web/.next/standalone/web/.next/static/static/chunks/cb355fac10c6ad11.css +1 -0
  69. package/web/.next/standalone/web/src/components/ai-elements/speech-input.tsx +89 -36
  70. package/web/.next/standalone/web/src/components/chat-interface.tsx +353 -37
  71. package/web/.next/standalone/web/src/lib/api.ts +133 -2
  72. package/web/.next/static/chunks/{a86053f0894587f2.js → 3bb454ca848ec78e.js} +3 -3
  73. package/web/.next/{standalone/web/.next/static/static/chunks/5ec82ce8f3aabaf0.js → static/chunks/5e5b485d77ac0d8f.js} +1 -1
  74. package/web/.next/static/chunks/cb355fac10c6ad11.css +1 -0
  75. package/web/.next/standalone/web/.next/server/chunks/ssr/web_19b6934c._.js +0 -8
  76. package/web/.next/standalone/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
  77. package/web/.next/standalone/web/.next/static/static/chunks/d0a69c59b1c0d99c.css +0 -1
  78. package/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
  79. /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
  80. /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
  81. /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
  82. /package/web/.next/standalone/web/.next/static/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
  83. /package/web/.next/standalone/web/.next/static/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
  84. /package/web/.next/standalone/web/.next/static/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
  85. /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
  86. /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
  87. /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -3260,11 +3260,12 @@ ${this.summary}`
3260
3260
  }
3261
3261
  /**
3262
3262
  * Add a user message to the context
3263
+ * Content can be a string or an array of content parts (for messages with images/files)
3263
3264
  */
3264
- addUserMessage(text2) {
3265
+ addUserMessage(content) {
3265
3266
  const userMessage = {
3266
3267
  role: "user",
3267
- content: text2
3268
+ content
3268
3269
  };
3269
3270
  messageQueries.create(this.sessionId, userMessage);
3270
3271
  }
@@ -3364,13 +3365,62 @@ var Agent = class _Agent {
3364
3365
  getSession() {
3365
3366
  return this.session;
3366
3367
  }
3368
+ /**
3369
+ * Build user message content from prompt and attachments
3370
+ */
3371
+ buildUserMessageContent(prompt, attachments) {
3372
+ if (!attachments || attachments.length === 0) {
3373
+ return prompt;
3374
+ }
3375
+ const contentParts = [];
3376
+ const attachmentDescriptions = attachments.map((a, i) => {
3377
+ const name = a.filename || `attachment_${i + 1}`;
3378
+ const typeLabel = a.type === "image" ? "Image" : "File";
3379
+ const location = a.savedPath || "(path unknown)";
3380
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
3381
+ }).join("\n");
3382
+ contentParts.push({
3383
+ type: "text",
3384
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
3385
+ ${attachmentDescriptions}
3386
+
3387
+ You can reference these files by their paths above. The file contents are also shown inline below.`
3388
+ });
3389
+ if (prompt) {
3390
+ contentParts.push({ type: "text", text: `
3391
+ [USER MESSAGE]
3392
+ ${prompt}` });
3393
+ }
3394
+ for (const attachment of attachments) {
3395
+ if (attachment.type === "image") {
3396
+ contentParts.push({
3397
+ type: "image",
3398
+ image: attachment.data,
3399
+ // base64 data URL or raw base64
3400
+ mediaType: attachment.mediaType,
3401
+ filename: attachment.filename,
3402
+ savedPath: attachment.savedPath
3403
+ });
3404
+ } else {
3405
+ contentParts.push({
3406
+ type: "file",
3407
+ data: attachment.data,
3408
+ mediaType: attachment.mediaType || "application/octet-stream",
3409
+ filename: attachment.filename,
3410
+ savedPath: attachment.savedPath
3411
+ });
3412
+ }
3413
+ }
3414
+ return contentParts;
3415
+ }
3367
3416
  /**
3368
3417
  * Run the agent with a prompt (streaming)
3369
3418
  */
3370
3419
  async stream(options) {
3371
3420
  const config = getConfig();
3421
+ const userContent = this.buildUserMessageContent(options.prompt, options.attachments);
3372
3422
  if (!options.skipSaveUserMessage) {
3373
- this.context.addUserMessage(options.prompt);
3423
+ this.context.addUserMessage(userContent);
3374
3424
  }
3375
3425
  sessionQueries.updateStatus(this.session.id, "active");
3376
3426
  const systemPrompt = await buildSystemPrompt({
@@ -3584,8 +3634,8 @@ import { Hono as Hono5 } from "hono";
3584
3634
  import { serve } from "@hono/node-server";
3585
3635
  import { cors } from "hono/cors";
3586
3636
  import { logger } from "hono/logger";
3587
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
3588
- import { resolve as resolve8, dirname as dirname6, join as join3 } from "path";
3637
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
3638
+ import { resolve as resolve8, dirname as dirname6, join as join5 } from "path";
3589
3639
  import { spawn as spawn2 } from "child_process";
3590
3640
  import { createServer as createNetServer } from "net";
3591
3641
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -3594,6 +3644,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
3594
3644
  import { Hono } from "hono";
3595
3645
  import { zValidator } from "@hono/zod-validator";
3596
3646
  import { z as z9 } from "zod";
3647
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync, unlinkSync } from "fs";
3648
+ import { join as join3, basename as basename2, extname as extname5 } from "path";
3649
+ import { nanoid as nanoid4 } from "nanoid";
3597
3650
  var sessions2 = new Hono();
3598
3651
  var createSessionSchema = z9.object({
3599
3652
  name: z9.string().optional(),
@@ -3945,11 +3998,138 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
3945
3998
  currentContent: fileDiff.currentContent
3946
3999
  });
3947
4000
  });
4001
+ function getAttachmentsDir(sessionId) {
4002
+ const appDataDir = getAppDataDirectory();
4003
+ return join3(appDataDir, "attachments", sessionId);
4004
+ }
4005
+ function ensureAttachmentsDir(sessionId) {
4006
+ const dir = getAttachmentsDir(sessionId);
4007
+ if (!existsSync10(dir)) {
4008
+ mkdirSync3(dir, { recursive: true });
4009
+ }
4010
+ return dir;
4011
+ }
4012
+ sessions2.get("/:id/attachments", async (c) => {
4013
+ const sessionId = c.req.param("id");
4014
+ const session = sessionQueries.getById(sessionId);
4015
+ if (!session) {
4016
+ return c.json({ error: "Session not found" }, 404);
4017
+ }
4018
+ const dir = getAttachmentsDir(sessionId);
4019
+ if (!existsSync10(dir)) {
4020
+ return c.json({ sessionId, attachments: [], count: 0 });
4021
+ }
4022
+ const files = readdirSync(dir);
4023
+ const attachments = files.map((filename) => {
4024
+ const filePath = join3(dir, filename);
4025
+ const stats = statSync(filePath);
4026
+ return {
4027
+ id: filename.split("_")[0],
4028
+ // Extract the nanoid prefix
4029
+ filename,
4030
+ path: filePath,
4031
+ size: stats.size,
4032
+ createdAt: stats.birthtime.toISOString()
4033
+ };
4034
+ });
4035
+ return c.json({
4036
+ sessionId,
4037
+ attachments,
4038
+ count: attachments.length
4039
+ });
4040
+ });
4041
+ sessions2.post("/:id/attachments", async (c) => {
4042
+ const sessionId = c.req.param("id");
4043
+ const session = sessionQueries.getById(sessionId);
4044
+ if (!session) {
4045
+ return c.json({ error: "Session not found" }, 404);
4046
+ }
4047
+ const contentType = c.req.header("content-type") || "";
4048
+ if (contentType.includes("multipart/form-data")) {
4049
+ try {
4050
+ const formData = await c.req.formData();
4051
+ const file = formData.get("file");
4052
+ if (!file || !(file instanceof File)) {
4053
+ return c.json({ error: "No file provided" }, 400);
4054
+ }
4055
+ const dir = ensureAttachmentsDir(sessionId);
4056
+ const id = nanoid4(10);
4057
+ const ext = extname5(file.name) || "";
4058
+ const safeFilename = `${id}_${basename2(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
4059
+ const filePath = join3(dir, safeFilename);
4060
+ const arrayBuffer = await file.arrayBuffer();
4061
+ writeFileSync2(filePath, Buffer.from(arrayBuffer));
4062
+ return c.json({
4063
+ id,
4064
+ filename: file.name,
4065
+ storedAs: safeFilename,
4066
+ path: filePath,
4067
+ size: file.size,
4068
+ mediaType: file.type,
4069
+ sessionId
4070
+ }, 201);
4071
+ } catch (err) {
4072
+ console.error("Failed to upload attachment:", err);
4073
+ return c.json({ error: "Failed to upload file" }, 500);
4074
+ }
4075
+ }
4076
+ try {
4077
+ const body = await c.req.json();
4078
+ if (!body.filename || !body.data) {
4079
+ return c.json({ error: "Missing filename or data" }, 400);
4080
+ }
4081
+ const dir = ensureAttachmentsDir(sessionId);
4082
+ const id = nanoid4(10);
4083
+ const ext = extname5(body.filename) || "";
4084
+ const safeFilename = `${id}_${basename2(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
4085
+ const filePath = join3(dir, safeFilename);
4086
+ let base64Data = body.data;
4087
+ if (base64Data.includes(",")) {
4088
+ base64Data = base64Data.split(",")[1];
4089
+ }
4090
+ const buffer = Buffer.from(base64Data, "base64");
4091
+ writeFileSync2(filePath, buffer);
4092
+ return c.json({
4093
+ id,
4094
+ filename: body.filename,
4095
+ storedAs: safeFilename,
4096
+ path: filePath,
4097
+ size: buffer.length,
4098
+ mediaType: body.mediaType,
4099
+ sessionId
4100
+ }, 201);
4101
+ } catch (err) {
4102
+ console.error("Failed to upload attachment:", err);
4103
+ return c.json({ error: "Failed to upload file" }, 500);
4104
+ }
4105
+ });
4106
+ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
4107
+ const sessionId = c.req.param("id");
4108
+ const attachmentId = c.req.param("attachmentId");
4109
+ const session = sessionQueries.getById(sessionId);
4110
+ if (!session) {
4111
+ return c.json({ error: "Session not found" }, 404);
4112
+ }
4113
+ const dir = getAttachmentsDir(sessionId);
4114
+ if (!existsSync10(dir)) {
4115
+ return c.json({ error: "Attachment not found" }, 404);
4116
+ }
4117
+ const files = readdirSync(dir);
4118
+ const file = files.find((f) => f.startsWith(attachmentId + "_"));
4119
+ if (!file) {
4120
+ return c.json({ error: "Attachment not found" }, 404);
4121
+ }
4122
+ const filePath = join3(dir, file);
4123
+ unlinkSync(filePath);
4124
+ return c.json({ success: true, id: attachmentId });
4125
+ });
3948
4126
 
3949
4127
  // src/server/routes/agents.ts
3950
4128
  import { Hono as Hono2 } from "hono";
3951
4129
  import { zValidator as zValidator2 } from "@hono/zod-validator";
3952
4130
  import { z as z10 } from "zod";
4131
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
4132
+ import { join as join4 } from "path";
3953
4133
 
3954
4134
  // src/server/resumable-stream.ts
3955
4135
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -4024,11 +4204,23 @@ var streamContext = createResumableStreamContext({
4024
4204
  });
4025
4205
 
4026
4206
  // src/server/routes/agents.ts
4027
- import { nanoid as nanoid4 } from "nanoid";
4207
+ import { nanoid as nanoid5 } from "nanoid";
4028
4208
  var agents = new Hono2();
4029
- var runPromptSchema = z10.object({
4030
- prompt: z10.string().min(1)
4209
+ var attachmentSchema = z10.object({
4210
+ type: z10.enum(["image", "file"]),
4211
+ data: z10.string(),
4212
+ // base64 data URL or raw base64
4213
+ mediaType: z10.string().optional(),
4214
+ filename: z10.string().optional()
4031
4215
  });
4216
+ var runPromptSchema = z10.object({
4217
+ prompt: z10.string(),
4218
+ // Can be empty if attachments are provided
4219
+ attachments: z10.array(attachmentSchema).optional()
4220
+ }).refine(
4221
+ (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
4222
+ { message: "Either prompt or attachments must be provided" }
4223
+ );
4032
4224
  var quickStartSchema = z10.object({
4033
4225
  prompt: z10.string().min(1),
4034
4226
  name: z10.string().optional(),
@@ -4040,7 +4232,53 @@ var rejectSchema = z10.object({
4040
4232
  reason: z10.string().optional()
4041
4233
  }).optional();
4042
4234
  var streamAbortControllers = /* @__PURE__ */ new Map();
4043
- function createAgentStreamProducer(sessionId, prompt, streamId) {
4235
+ function getAttachmentsDirectory(sessionId) {
4236
+ const appDataDir = getAppDataDirectory();
4237
+ return join4(appDataDir, "attachments", sessionId);
4238
+ }
4239
+ function saveAttachmentToDisk(sessionId, attachment, index) {
4240
+ const attachmentsDir = getAttachmentsDirectory(sessionId);
4241
+ if (!existsSync11(attachmentsDir)) {
4242
+ mkdirSync4(attachmentsDir, { recursive: true });
4243
+ }
4244
+ let filename = attachment.filename;
4245
+ if (!filename) {
4246
+ const ext = getExtensionFromMediaType(attachment.mediaType, attachment.type);
4247
+ filename = `attachment_${index + 1}${ext}`;
4248
+ }
4249
+ let base64Data = attachment.data;
4250
+ if (base64Data.includes(",")) {
4251
+ base64Data = base64Data.split(",")[1];
4252
+ }
4253
+ const filePath = join4(attachmentsDir, filename);
4254
+ const buffer = Buffer.from(base64Data, "base64");
4255
+ writeFileSync3(filePath, buffer);
4256
+ return filePath;
4257
+ }
4258
+ function getExtensionFromMediaType(mediaType, type) {
4259
+ if (!mediaType) {
4260
+ return type === "image" ? ".png" : ".bin";
4261
+ }
4262
+ const mimeToExt = {
4263
+ "image/png": ".png",
4264
+ "image/jpeg": ".jpg",
4265
+ "image/jpg": ".jpg",
4266
+ "image/gif": ".gif",
4267
+ "image/webp": ".webp",
4268
+ "image/svg+xml": ".svg",
4269
+ "application/pdf": ".pdf",
4270
+ "text/plain": ".txt",
4271
+ "text/markdown": ".md",
4272
+ "application/json": ".json",
4273
+ "application/javascript": ".js",
4274
+ "text/javascript": ".js",
4275
+ "text/typescript": ".ts",
4276
+ "text/html": ".html",
4277
+ "text/css": ".css"
4278
+ };
4279
+ return mimeToExt[mediaType] || ".bin";
4280
+ }
4281
+ function createAgentStreamProducer(sessionId, prompt, streamId, attachments) {
4044
4282
  return () => {
4045
4283
  const { readable, writable } = new TransformStream();
4046
4284
  const writer = writable.getWriter();
@@ -4073,9 +4311,53 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4073
4311
  try {
4074
4312
  const agent = await Agent.create({ sessionId });
4075
4313
  await writeSSE(JSON.stringify({ type: "data-stream-id", streamId }));
4314
+ let broadcastContent;
4315
+ if (attachments && attachments.length > 0) {
4316
+ const contentParts = [];
4317
+ const attachmentDescriptions = attachments.map((a, i) => {
4318
+ const name = a.filename || `attachment_${i + 1}`;
4319
+ const typeLabel = a.type === "image" ? "Image" : "File";
4320
+ const location = a.savedPath || "(path unknown)";
4321
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
4322
+ }).join("\n");
4323
+ contentParts.push({
4324
+ type: "text",
4325
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
4326
+ ${attachmentDescriptions}
4327
+
4328
+ You can reference these files by their paths above. The file contents are also shown inline below.`
4329
+ });
4330
+ if (prompt) {
4331
+ contentParts.push({ type: "text", text: `
4332
+ [USER MESSAGE]
4333
+ ${prompt}` });
4334
+ }
4335
+ for (const attachment of attachments) {
4336
+ if (attachment.type === "image") {
4337
+ contentParts.push({
4338
+ type: "image",
4339
+ image: attachment.data,
4340
+ mediaType: attachment.mediaType,
4341
+ filename: attachment.filename,
4342
+ savedPath: attachment.savedPath
4343
+ });
4344
+ } else {
4345
+ contentParts.push({
4346
+ type: "file",
4347
+ data: attachment.data,
4348
+ mediaType: attachment.mediaType || "application/octet-stream",
4349
+ filename: attachment.filename,
4350
+ savedPath: attachment.savedPath
4351
+ });
4352
+ }
4353
+ }
4354
+ broadcastContent = contentParts;
4355
+ } else {
4356
+ broadcastContent = prompt;
4357
+ }
4076
4358
  await writeSSE(JSON.stringify({
4077
4359
  type: "data-user-message",
4078
- data: { id: `user_${Date.now()}`, content: prompt }
4360
+ data: { id: `user_${Date.now()}`, content: broadcastContent }
4079
4361
  }));
4080
4362
  const messageId = `msg_${Date.now()}`;
4081
4363
  await writeSSE(JSON.stringify({ type: "start", messageId }));
@@ -4083,6 +4365,8 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4083
4365
  let textStarted = false;
4084
4366
  const result = await agent.stream({
4085
4367
  prompt,
4368
+ attachments,
4369
+ // Pass attachments to agent
4086
4370
  abortSignal: abortController.signal,
4087
4371
  // Use our managed abort controller, NOT client signal
4088
4372
  skipSaveUserMessage: true,
@@ -4192,14 +4476,20 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4192
4476
  } else {
4193
4477
  await writeSSE(JSON.stringify({ type: "finish" }));
4194
4478
  }
4195
- activeStreamQueries.finish(streamId);
4479
+ try {
4480
+ activeStreamQueries.finish(streamId);
4481
+ } catch {
4482
+ }
4196
4483
  } catch (error) {
4197
4484
  if (error.name === "AbortError" || error.message?.includes("aborted")) {
4198
4485
  await writeSSE(JSON.stringify({ type: "abort" }));
4199
4486
  } else {
4200
4487
  console.error("Agent error:", error);
4201
4488
  await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
4202
- activeStreamQueries.markError(streamId);
4489
+ try {
4490
+ activeStreamQueries.markError(streamId);
4491
+ } catch {
4492
+ }
4203
4493
  }
4204
4494
  } finally {
4205
4495
  cleanupAbortController();
@@ -4215,19 +4505,74 @@ agents.post(
4215
4505
  zValidator2("json", runPromptSchema),
4216
4506
  async (c) => {
4217
4507
  const id = c.req.param("id");
4218
- const { prompt } = c.req.valid("json");
4508
+ const { prompt, attachments } = c.req.valid("json");
4219
4509
  const session = sessionQueries.getById(id);
4220
4510
  if (!session) {
4221
4511
  return c.json({ error: "Session not found" }, 404);
4222
4512
  }
4223
4513
  const nextSequence = messageQueries.getNextSequence(id);
4224
4514
  await createCheckpoint(id, session.workingDirectory, nextSequence);
4225
- messageQueries.create(id, { role: "user", content: prompt });
4226
- const streamId = `stream_${id}_${nanoid4(10)}`;
4515
+ let userMessageContent;
4516
+ const streamAttachments = attachments;
4517
+ if (streamAttachments && streamAttachments.length > 0) {
4518
+ for (let i = 0; i < streamAttachments.length; i++) {
4519
+ const attachment = streamAttachments[i];
4520
+ try {
4521
+ const savedPath = saveAttachmentToDisk(id, attachment, i);
4522
+ attachment.savedPath = savedPath;
4523
+ } catch (err) {
4524
+ console.error(`Failed to save attachment ${i}:`, err);
4525
+ }
4526
+ }
4527
+ const contentParts = [];
4528
+ const attachmentDescriptions = streamAttachments.map((a, i) => {
4529
+ const name = a.filename || `attachment_${i + 1}`;
4530
+ const typeLabel = a.type === "image" ? "Image" : "File";
4531
+ const location = a.savedPath || "(path unknown)";
4532
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
4533
+ }).join("\n");
4534
+ contentParts.push({
4535
+ type: "text",
4536
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
4537
+ ${attachmentDescriptions}
4538
+
4539
+ You can reference these files by their paths above. The file contents are also shown inline below.`
4540
+ });
4541
+ if (prompt) {
4542
+ contentParts.push({ type: "text", text: `
4543
+ [USER MESSAGE]
4544
+ ${prompt}` });
4545
+ }
4546
+ for (const attachment of streamAttachments) {
4547
+ if (attachment.type === "image") {
4548
+ contentParts.push({
4549
+ type: "image",
4550
+ image: attachment.data,
4551
+ // base64 data URL or raw base64
4552
+ mediaType: attachment.mediaType,
4553
+ filename: attachment.filename,
4554
+ savedPath: attachment.savedPath
4555
+ });
4556
+ } else {
4557
+ contentParts.push({
4558
+ type: "file",
4559
+ data: attachment.data,
4560
+ mediaType: attachment.mediaType || "application/octet-stream",
4561
+ filename: attachment.filename,
4562
+ savedPath: attachment.savedPath
4563
+ });
4564
+ }
4565
+ }
4566
+ userMessageContent = contentParts;
4567
+ } else {
4568
+ userMessageContent = prompt;
4569
+ }
4570
+ messageQueries.create(id, { role: "user", content: userMessageContent });
4571
+ const streamId = `stream_${id}_${nanoid5(10)}`;
4227
4572
  activeStreamQueries.create(id, streamId);
4228
4573
  const stream = await streamContext.resumableStream(
4229
4574
  streamId,
4230
- createAgentStreamProducer(id, prompt, streamId)
4575
+ createAgentStreamProducer(id, prompt, streamId, streamAttachments)
4231
4576
  );
4232
4577
  if (!stream) {
4233
4578
  return c.json({ error: "Failed to create stream" }, 500);
@@ -4420,7 +4765,7 @@ agents.post(
4420
4765
  sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
4421
4766
  });
4422
4767
  const session = agent.getSession();
4423
- const streamId = `stream_${session.id}_${nanoid4(10)}`;
4768
+ const streamId = `stream_${session.id}_${nanoid5(10)}`;
4424
4769
  await createCheckpoint(session.id, session.workingDirectory, 0);
4425
4770
  activeStreamQueries.create(session.id, streamId);
4426
4771
  const createQuickStreamProducer = () => {
@@ -5079,11 +5424,11 @@ function getWebDirectory() {
5079
5424
  try {
5080
5425
  const currentDir = dirname6(fileURLToPath2(import.meta.url));
5081
5426
  const webDir = resolve8(currentDir, "..", "web");
5082
- if (existsSync10(webDir) && existsSync10(join3(webDir, "package.json"))) {
5427
+ if (existsSync12(webDir) && existsSync12(join5(webDir, "package.json"))) {
5083
5428
  return webDir;
5084
5429
  }
5085
5430
  const altWebDir = resolve8(currentDir, "..", "..", "web");
5086
- if (existsSync10(altWebDir) && existsSync10(join3(altWebDir, "package.json"))) {
5431
+ if (existsSync12(altWebDir) && existsSync12(join5(altWebDir, "package.json"))) {
5087
5432
  return altWebDir;
5088
5433
  }
5089
5434
  return null;
@@ -5141,23 +5486,23 @@ async function findWebPort(preferredPort) {
5141
5486
  return { port: preferredPort, alreadyRunning: false };
5142
5487
  }
5143
5488
  function hasProductionBuild(webDir) {
5144
- const buildIdPath = join3(webDir, ".next", "BUILD_ID");
5145
- return existsSync10(buildIdPath);
5489
+ const buildIdPath = join5(webDir, ".next", "BUILD_ID");
5490
+ return existsSync12(buildIdPath);
5146
5491
  }
5147
5492
  function hasSourceFiles(webDir) {
5148
- const appDir = join3(webDir, "src", "app");
5149
- const pagesDir = join3(webDir, "src", "pages");
5150
- const rootAppDir = join3(webDir, "app");
5151
- const rootPagesDir = join3(webDir, "pages");
5152
- return existsSync10(appDir) || existsSync10(pagesDir) || existsSync10(rootAppDir) || existsSync10(rootPagesDir);
5493
+ const appDir = join5(webDir, "src", "app");
5494
+ const pagesDir = join5(webDir, "src", "pages");
5495
+ const rootAppDir = join5(webDir, "app");
5496
+ const rootPagesDir = join5(webDir, "pages");
5497
+ return existsSync12(appDir) || existsSync12(pagesDir) || existsSync12(rootAppDir) || existsSync12(rootPagesDir);
5153
5498
  }
5154
5499
  function getStandaloneServerPath(webDir) {
5155
5500
  const possiblePaths = [
5156
- join3(webDir, ".next", "standalone", "server.js"),
5157
- join3(webDir, ".next", "standalone", "web", "server.js")
5501
+ join5(webDir, ".next", "standalone", "server.js"),
5502
+ join5(webDir, ".next", "standalone", "web", "server.js")
5158
5503
  ];
5159
5504
  for (const serverPath of possiblePaths) {
5160
- if (existsSync10(serverPath)) {
5505
+ if (existsSync12(serverPath)) {
5161
5506
  return serverPath;
5162
5507
  }
5163
5508
  }
@@ -5197,15 +5542,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5197
5542
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
5198
5543
  return { process: null, port: actualPort };
5199
5544
  }
5200
- const usePnpm = existsSync10(join3(webDir, "pnpm-lock.yaml"));
5201
- const useNpm = !usePnpm && existsSync10(join3(webDir, "package-lock.json"));
5545
+ const usePnpm = existsSync12(join5(webDir, "pnpm-lock.yaml"));
5546
+ const useNpm = !usePnpm && existsSync12(join5(webDir, "package-lock.json"));
5202
5547
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
5203
5548
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
5204
5549
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
5205
5550
  const runtimeConfig = { apiBaseUrl: apiUrl };
5206
- const runtimeConfigPath = join3(webDir, "runtime-config.json");
5551
+ const runtimeConfigPath = join5(webDir, "runtime-config.json");
5207
5552
  try {
5208
- writeFileSync2(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
5553
+ writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
5209
5554
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
5210
5555
  } catch (err) {
5211
5556
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -5231,7 +5576,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5231
5576
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
5232
5577
  } else if (hasBuild && (isProduction || !hasSource)) {
5233
5578
  command = pkgManager;
5234
- args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start", "--", "-p", String(actualPort)];
5579
+ args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"];
5235
5580
  } else if (hasSource) {
5236
5581
  if (isProduction && !hasBuild) {
5237
5582
  if (!quiet) console.log(" \u{1F4E6} Building Web UI for production...");
@@ -5243,10 +5588,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5243
5588
  }
5244
5589
  if (!quiet) console.log(" \u2713 Web UI build complete");
5245
5590
  command = pkgManager;
5246
- args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start", "--", "-p", String(actualPort)];
5591
+ args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"];
5247
5592
  } else {
5248
5593
  command = pkgManager;
5249
- args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev", "--", "-p", String(actualPort)];
5594
+ args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev"];
5250
5595
  }
5251
5596
  } else {
5252
5597
  if (!quiet) {
@@ -5274,6 +5619,12 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5274
5619
  }, startupTimeout);
5275
5620
  child.stdout?.on("data", (data) => {
5276
5621
  const output = data.toString();
5622
+ if (!quiet) {
5623
+ const lines = output.trim().split("\n").filter((l) => l.trim());
5624
+ for (const line of lines) {
5625
+ console.log(` Web UI: ${line}`);
5626
+ }
5627
+ }
5277
5628
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
5278
5629
  started = true;
5279
5630
  clearTimeout(timeout);
@@ -5281,9 +5632,9 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5281
5632
  }
5282
5633
  });
5283
5634
  child.stderr?.on("data", (data) => {
5284
- const output = data.toString();
5285
- if (output.toLowerCase().includes("error")) {
5286
- if (!quiet) console.error(` Web UI error: ${output.trim().slice(0, 200)}`);
5635
+ const output = data.toString().trim();
5636
+ if (!quiet && output) {
5637
+ console.error(` Web UI: ${output.slice(0, 500)}`);
5287
5638
  }
5288
5639
  });
5289
5640
  child.on("error", (err) => {
@@ -5389,8 +5740,8 @@ async function startServer(options = {}) {
5389
5740
  if (options.workingDirectory) {
5390
5741
  config.resolvedWorkingDirectory = options.workingDirectory;
5391
5742
  }
5392
- if (!existsSync10(config.resolvedWorkingDirectory)) {
5393
- mkdirSync3(config.resolvedWorkingDirectory, { recursive: true });
5743
+ if (!existsSync12(config.resolvedWorkingDirectory)) {
5744
+ mkdirSync5(config.resolvedWorkingDirectory, { recursive: true });
5394
5745
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
5395
5746
  }
5396
5747
  initDatabase(config.resolvedDatabasePath);