sparkecoder 0.1.86 → 0.1.87

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 (112) hide show
  1. package/dist/agent/index.d.ts +1 -1
  2. package/dist/agent/index.js +661 -39
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +1996 -225
  5. package/dist/cli.js.map +1 -1
  6. package/dist/{index-OhuTM4a0.d.ts → index-BvIissiB.d.ts} +9 -0
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.js +1683 -199
  9. package/dist/index.js.map +1 -1
  10. package/dist/server/index.js +1683 -199
  11. package/dist/server/index.js.map +1 -1
  12. package/dist/skills/default/computer-use.md +150 -0
  13. package/dist/tools/index.d.ts +167 -1
  14. package/dist/tools/index.js +604 -10
  15. package/dist/tools/index.js.map +1 -1
  16. package/package.json +2 -1
  17. package/src/skills/default/computer-use.md +150 -0
  18. package/web/.next/BUILD_ID +1 -1
  19. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  20. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  21. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  22. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  23. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  38. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  39. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  40. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  49. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  58. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  75. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  76. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  77. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ecd2bdca._.js → 2374f_317b1fef._.js} +1 -1
  85. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9adc1edb._.js → 2374f_37dd9702._.js} +1 -1
  86. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_8dc0f9aa._.js → 2374f_4d44e4ed._.js} +1 -1
  87. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_cc6c6363._.js → 2374f_54ac917f._.js} +1 -1
  88. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_00f7fe07._.js → 2374f_86585101._.js} +1 -1
  89. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_369747ce._.js → 2374f_a383a4d9._.js} +1 -1
  90. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d58d0276._.js → 2374f_c59a35bb._.js} +1 -1
  91. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__25b25c9d._.js → [root-of-the-server]__9a826344._.js} +2 -2
  92. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  93. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  94. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  95. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  96. package/web/.next/standalone/web/.next/static/chunks/275e8268daf318b2.js +7 -0
  97. package/web/.next/standalone/web/.next/static/static/chunks/275e8268daf318b2.js +7 -0
  98. package/web/.next/standalone/web/src/app/embed/[id]/page.tsx +12 -0
  99. package/web/.next/standalone/web/src/lib/embed-bootstrap.ts +108 -0
  100. package/web/.next/static/chunks/275e8268daf318b2.js +7 -0
  101. package/web/.next/standalone/web/.next/static/chunks/5383c5717758f575.js +0 -7
  102. package/web/.next/standalone/web/.next/static/static/chunks/5383c5717758f575.js +0 -7
  103. package/web/.next/static/chunks/5383c5717758f575.js +0 -7
  104. /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → static/uUaN7Xe5kF_pP6zhfaeYi}/_buildManifest.js +0 -0
  105. /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → static/uUaN7Xe5kF_pP6zhfaeYi}/_clientMiddlewareManifest.json +0 -0
  106. /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → static/uUaN7Xe5kF_pP6zhfaeYi}/_ssgManifest.js +0 -0
  107. /package/web/.next/standalone/web/.next/static/{static/Pt6kwIO6lniM7h7I5E6kk → uUaN7Xe5kF_pP6zhfaeYi}/_buildManifest.js +0 -0
  108. /package/web/.next/standalone/web/.next/static/{static/Pt6kwIO6lniM7h7I5E6kk → uUaN7Xe5kF_pP6zhfaeYi}/_clientMiddlewareManifest.json +0 -0
  109. /package/web/.next/standalone/web/.next/static/{static/Pt6kwIO6lniM7h7I5E6kk → uUaN7Xe5kF_pP6zhfaeYi}/_ssgManifest.js +0 -0
  110. /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → uUaN7Xe5kF_pP6zhfaeYi}/_buildManifest.js +0 -0
  111. /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → uUaN7Xe5kF_pP6zhfaeYi}/_clientMiddlewareManifest.json +0 -0
  112. /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → uUaN7Xe5kF_pP6zhfaeYi}/_ssgManifest.js +0 -0
@@ -516,7 +516,12 @@ var init_types = __esm({
516
516
  // Whether to always inject this skill into context (vs on-demand loading)
517
517
  alwaysApply: z.boolean().optional().default(false),
518
518
  // Glob patterns - auto-inject when working with matching files
519
- globs: z.array(z.string()).optional().default([])
519
+ globs: z.array(z.string()).optional().default([]),
520
+ // Platform requirements — skill is hidden from the model on platforms
521
+ // not listed here. Values match `process.platform`
522
+ // (darwin, linux, win32, freebsd, ...). If omitted or empty, the skill is
523
+ // available on all platforms.
524
+ platforms: z.array(z.string()).optional().default([])
520
525
  });
521
526
  TaskConfigSchema = z.object({
522
527
  enabled: z.boolean(),
@@ -534,7 +539,13 @@ var init_types = __esm({
534
539
  approvalWebhook: z.string().url().optional(),
535
540
  skillsDirectory: z.string().optional(),
536
541
  maxContextChars: z.number().optional().default(2e5),
537
- task: TaskConfigSchema.optional()
542
+ task: TaskConfigSchema.optional(),
543
+ // Anthropic computer use tool — opt-in. When true, the `computer` tool is
544
+ // included in the toolset for Anthropic models. Default false.
545
+ computerUseEnabled: z.boolean().optional(),
546
+ // Display dimensions for the computer use tool (defaults: 1280x800).
547
+ computerUseDisplayWidth: z.number().int().positive().optional(),
548
+ computerUseDisplayHeight: z.number().int().positive().optional()
538
549
  });
539
550
  VectorGatewayConfigSchema = z.object({
540
551
  // Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
@@ -1535,7 +1546,8 @@ async function loadSkillsFromDirectory(directory, options = {}) {
1535
1546
  globs: parsed.metadata.globs,
1536
1547
  loadType,
1537
1548
  priority,
1538
- sourceDir: directory
1549
+ sourceDir: directory,
1550
+ platforms: parsed.metadata.platforms
1539
1551
  });
1540
1552
  } else {
1541
1553
  const name = getSkillNameFromPath(filePath);
@@ -1548,11 +1560,14 @@ async function loadSkillsFromDirectory(directory, options = {}) {
1548
1560
  globs: [],
1549
1561
  loadType: forceAlwaysApply ? "always" : defaultLoadType,
1550
1562
  priority,
1551
- sourceDir: directory
1563
+ sourceDir: directory,
1564
+ platforms: []
1552
1565
  });
1553
1566
  }
1554
1567
  }
1555
- return skills;
1568
+ return skills.filter(
1569
+ (s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
1570
+ );
1556
1571
  }
1557
1572
  async function loadAllSkills(directories) {
1558
1573
  const allSkills = [];
@@ -2149,6 +2164,7 @@ __export(webhook_exports, {
2149
2164
  sendWebhook: () => sendWebhook
2150
2165
  });
2151
2166
  async function sendWebhook(url, event) {
2167
+ const t0 = Date.now();
2152
2168
  try {
2153
2169
  const controller = new AbortController();
2154
2170
  const timeout = setTimeout(() => controller.abort(), 5e3);
@@ -2162,17 +2178,36 @@ async function sendWebhook(url, event) {
2162
2178
  signal: controller.signal
2163
2179
  });
2164
2180
  clearTimeout(timeout);
2181
+ const ms = Date.now() - t0;
2165
2182
  if (!response.ok) {
2166
- console.warn(`[WEBHOOK] ${event.type} to ${url} returned HTTP ${response.status}`);
2183
+ const body = await response.text().catch(() => "");
2184
+ console.warn(
2185
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> HTTP ${response.status} in ${ms}ms${body ? ` (${body.slice(0, 200)})` : ""}`
2186
+ );
2187
+ return;
2188
+ }
2189
+ if (!QUIET || TERMINAL_EVENTS.has(event.type)) {
2190
+ console.log(
2191
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> 200 in ${ms}ms`
2192
+ );
2167
2193
  }
2168
2194
  } catch (err) {
2169
2195
  const reason = err.name === "AbortError" ? "timeout (5s)" : err.message;
2170
- console.warn(`[WEBHOOK] ${event.type} to ${url} failed: ${reason}`);
2196
+ console.warn(
2197
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> failed: ${reason}`
2198
+ );
2171
2199
  }
2172
2200
  }
2201
+ var TERMINAL_EVENTS, QUIET;
2173
2202
  var init_webhook = __esm({
2174
2203
  "src/utils/webhook.ts"() {
2175
2204
  "use strict";
2205
+ TERMINAL_EVENTS = /* @__PURE__ */ new Set([
2206
+ "task.started",
2207
+ "task.completed",
2208
+ "task.failed"
2209
+ ]);
2210
+ QUIET = process.env.SPARKECODER_QUIET_WEBHOOKS === "1";
2176
2211
  }
2177
2212
  });
2178
2213
 
@@ -2382,15 +2417,15 @@ var recorder_exports = {};
2382
2417
  __export(recorder_exports, {
2383
2418
  FrameRecorder: () => FrameRecorder
2384
2419
  });
2385
- import { exec as exec5 } from "child_process";
2386
- import { promisify as promisify5 } from "util";
2420
+ import { exec as exec6 } from "child_process";
2421
+ import { promisify as promisify6 } from "util";
2387
2422
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
2388
- import { join as join8 } from "path";
2389
- import { tmpdir } from "os";
2390
- import { nanoid as nanoid3 } from "nanoid";
2423
+ import { join as join9 } from "path";
2424
+ import { tmpdir as tmpdir2 } from "os";
2425
+ import { nanoid as nanoid4 } from "nanoid";
2391
2426
  async function checkFfmpeg() {
2392
2427
  try {
2393
- await execAsync5("ffmpeg -version", { timeout: 5e3 });
2428
+ await execAsync6("ffmpeg -version", { timeout: 5e3 });
2394
2429
  return true;
2395
2430
  } catch {
2396
2431
  return false;
@@ -2402,11 +2437,11 @@ async function cleanup(dir) {
2402
2437
  } catch {
2403
2438
  }
2404
2439
  }
2405
- var execAsync5, FrameRecorder;
2440
+ var execAsync6, FrameRecorder;
2406
2441
  var init_recorder = __esm({
2407
2442
  "src/browser/recorder.ts"() {
2408
2443
  "use strict";
2409
- execAsync5 = promisify5(exec5);
2444
+ execAsync6 = promisify6(exec6);
2410
2445
  FrameRecorder = class {
2411
2446
  frames = [];
2412
2447
  startTime = null;
@@ -2442,21 +2477,21 @@ var init_recorder = __esm({
2442
2477
  */
2443
2478
  async encode() {
2444
2479
  if (this.frames.length === 0) return null;
2445
- const workDir = join8(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
2480
+ const workDir = join9(tmpdir2(), `sparkecoder-recording-${nanoid4(8)}`);
2446
2481
  await mkdir4(workDir, { recursive: true });
2447
2482
  try {
2448
2483
  for (let i = 0; i < this.frames.length; i++) {
2449
- const framePath = join8(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
2484
+ const framePath = join9(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
2450
2485
  await writeFile5(framePath, this.frames[i].data);
2451
2486
  }
2452
2487
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
2453
2488
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
2454
2489
  const clampedFps = Math.max(1, Math.min(fps, 30));
2455
- const outputPath = join8(workDir, `recording_${this.sessionId}.mp4`);
2490
+ const outputPath = join9(workDir, `recording_${this.sessionId}.mp4`);
2456
2491
  const hasFfmpeg = await checkFfmpeg();
2457
2492
  if (hasFfmpeg) {
2458
- await execAsync5(
2459
- `ffmpeg -y -framerate ${clampedFps} -i "${join8(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
2493
+ await execAsync6(
2494
+ `ffmpeg -y -framerate ${clampedFps} -i "${join9(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
2460
2495
  { timeout: 12e4 }
2461
2496
  );
2462
2497
  } else {
@@ -2468,7 +2503,7 @@ var init_recorder = __esm({
2468
2503
  const files = await readdir5(workDir);
2469
2504
  for (const f of files) {
2470
2505
  if (f.startsWith("frame_")) {
2471
- await unlink2(join8(workDir, f)).catch(() => {
2506
+ await unlink2(join9(workDir, f)).catch(() => {
2472
2507
  });
2473
2508
  }
2474
2509
  }
@@ -2491,12 +2526,12 @@ var init_recorder = __esm({
2491
2526
 
2492
2527
  // src/server/index.ts
2493
2528
  import "dotenv/config";
2494
- import { Hono as Hono6 } from "hono";
2529
+ import { Hono as Hono7 } from "hono";
2495
2530
  import { serve } from "@hono/node-server";
2496
2531
  import { cors } from "hono/cors";
2497
2532
  import { logger } from "hono/logger";
2498
- import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
2499
- import { resolve as resolve10, dirname as dirname7, join as join12 } from "path";
2533
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
2534
+ import { resolve as resolve10, dirname as dirname7, join as join13 } from "path";
2500
2535
  import { spawn as spawn2 } from "child_process";
2501
2536
  import { createServer as createNetServer } from "net";
2502
2537
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -2505,17 +2540,17 @@ import { fileURLToPath as fileURLToPath4 } from "url";
2505
2540
  init_db();
2506
2541
  import { Hono } from "hono";
2507
2542
  import { zValidator } from "@hono/zod-validator";
2508
- import { z as z15 } from "zod";
2509
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
2543
+ import { z as z16 } from "zod";
2544
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
2510
2545
  import { readdir as readdir6 } from "fs/promises";
2511
- import { join as join9, basename as basename5, extname as extname8, relative as relative9 } from "path";
2512
- import { nanoid as nanoid5 } from "nanoid";
2546
+ import { join as join10, basename as basename5, extname as extname8, relative as relative9 } from "path";
2547
+ import { nanoid as nanoid6 } from "nanoid";
2513
2548
 
2514
2549
  // src/agent/index.ts
2515
2550
  import {
2516
2551
  streamText as streamText2,
2517
2552
  generateText as generateText3,
2518
- tool as tool13,
2553
+ tool as tool14,
2519
2554
  stepCountIs as stepCountIs2
2520
2555
  } from "ai";
2521
2556
 
@@ -2706,8 +2741,8 @@ var SUBAGENT_MODELS = {
2706
2741
  // src/agent/index.ts
2707
2742
  init_db();
2708
2743
  init_config();
2709
- import { z as z14 } from "zod";
2710
- import { nanoid as nanoid4 } from "nanoid";
2744
+ import { z as z15 } from "zod";
2745
+ import { nanoid as nanoid5 } from "nanoid";
2711
2746
 
2712
2747
  // src/tools/bash.ts
2713
2748
  import { tool } from "ai";
@@ -3774,12 +3809,12 @@ function findNearestRoot(startDir, markers) {
3774
3809
  }
3775
3810
  async function commandExists(cmd) {
3776
3811
  try {
3777
- const { exec: exec7 } = await import("child_process");
3778
- const { promisify: promisify7 } = await import("util");
3779
- const execAsync7 = promisify7(exec7);
3812
+ const { exec: exec8 } = await import("child_process");
3813
+ const { promisify: promisify8 } = await import("util");
3814
+ const execAsync8 = promisify8(exec8);
3780
3815
  const isWindows = process.platform === "win32";
3781
3816
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
3782
- await execAsync7(checkCmd);
3817
+ await execAsync8(checkCmd);
3783
3818
  return true;
3784
3819
  } catch {
3785
3820
  return false;
@@ -6170,6 +6205,568 @@ function createUploadFileTool(options) {
6170
6205
  });
6171
6206
  }
6172
6207
 
6208
+ // src/tools/computer-use.ts
6209
+ import { anthropic } from "@ai-sdk/anthropic";
6210
+ import { exec as exec5 } from "child_process";
6211
+ import { promisify as promisify5 } from "util";
6212
+ import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
6213
+ import { join as join8 } from "path";
6214
+ import { tmpdir } from "os";
6215
+ import { nanoid as nanoid3 } from "nanoid";
6216
+ var execAsync5 = promisify5(exec5);
6217
+ var DEFAULT_WIDTH = 1280;
6218
+ var DEFAULT_HEIGHT = 800;
6219
+ function isMacOs() {
6220
+ return process.platform === "darwin";
6221
+ }
6222
+ async function isCliclickInstalled() {
6223
+ try {
6224
+ await execAsync5("command -v cliclick", { timeout: 2e3 });
6225
+ return true;
6226
+ } catch {
6227
+ return false;
6228
+ }
6229
+ }
6230
+ async function runJxa(script) {
6231
+ try {
6232
+ const escaped = script.replace(/'/g, `'\\''`);
6233
+ const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
6234
+ timeout: 5e3
6235
+ });
6236
+ return JSON.parse(stdout.trim());
6237
+ } catch {
6238
+ return null;
6239
+ }
6240
+ }
6241
+ async function hasAccessibilityPermissions() {
6242
+ try {
6243
+ const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
6244
+ if (/accessibility privileges not enabled/i.test(stderr)) {
6245
+ return { ok: false, error: stderr.trim().split("\n")[0] };
6246
+ }
6247
+ return { ok: true };
6248
+ } catch (err) {
6249
+ return { ok: false, error: err?.message || String(err) };
6250
+ }
6251
+ }
6252
+ async function hasScreenRecordingPermissions() {
6253
+ const result = await runJxa(
6254
+ `ObjC.import("Cocoa");
6255
+ ObjC.import("CoreGraphics");
6256
+ ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
6257
+ JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
6258
+ );
6259
+ return result?.hasAccess ?? false;
6260
+ }
6261
+ async function requestAccessibilityPrompt() {
6262
+ const result = await runJxa(
6263
+ `ObjC.import("ApplicationServices");
6264
+ var key = $.kAXTrustedCheckOptionPrompt;
6265
+ var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
6266
+ var trusted = $.AXIsProcessTrustedWithOptions(dict);
6267
+ JSON.stringify({ trusted: !!trusted });`
6268
+ );
6269
+ return result?.trusted ?? false;
6270
+ }
6271
+ async function requestScreenRecordingPrompt() {
6272
+ const result = await runJxa(
6273
+ `ObjC.import("Cocoa");
6274
+ ObjC.import("CoreGraphics");
6275
+ ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
6276
+ JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
6277
+ );
6278
+ return result?.granted ?? false;
6279
+ }
6280
+ async function openSystemSettings(pane) {
6281
+ const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
6282
+ try {
6283
+ await execAsync5(`open '${url}'`, { timeout: 3e3 });
6284
+ } catch {
6285
+ }
6286
+ }
6287
+ async function detectScreenSize() {
6288
+ try {
6289
+ const { stdout } = await execAsync5(
6290
+ `osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
6291
+ { timeout: 3e3 }
6292
+ );
6293
+ const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
6294
+ if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
6295
+ const [x1, y1, x2, y2] = parts;
6296
+ return { width: x2 - x1, height: y2 - y1 };
6297
+ }
6298
+ } catch {
6299
+ }
6300
+ return null;
6301
+ }
6302
+ async function runCliclick(args) {
6303
+ const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
6304
+ const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
6305
+ timeout: 15e3,
6306
+ maxBuffer: 1024 * 1024
6307
+ });
6308
+ if (/accessibility privileges not enabled/i.test(stderr)) {
6309
+ throw new Error(
6310
+ "Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
6311
+ );
6312
+ }
6313
+ if (stderr && !stdout) throw new Error(stderr.trim());
6314
+ return (stdout || "").trim();
6315
+ }
6316
+ async function runScreencapture(path) {
6317
+ await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
6318
+ timeout: 5e3
6319
+ });
6320
+ }
6321
+ async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
6322
+ const sharpModule = await import("sharp");
6323
+ const sharp2 = sharpModule.default || sharpModule;
6324
+ const meta = await sharp2(path).metadata();
6325
+ if (meta.width === targetWidth && meta.height === targetHeight) {
6326
+ return readFileSync7(path);
6327
+ }
6328
+ return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
6329
+ }
6330
+ async function runScroll(dx, dy) {
6331
+ const wheelY = -Math.round(dy);
6332
+ const wheelX = -Math.round(dx);
6333
+ const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
6334
+ await execAsync5(
6335
+ `osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
6336
+ { timeout: 5e3 }
6337
+ );
6338
+ }
6339
+ function translateKeyForCliclick(key) {
6340
+ if (!key) return [];
6341
+ const parts = key.split("+").map((p) => p.trim()).filter(Boolean);
6342
+ if (parts.length === 0) return [];
6343
+ const modMap = {
6344
+ ctrl: "ctrl",
6345
+ control: "ctrl",
6346
+ alt: "alt",
6347
+ option: "alt",
6348
+ shift: "shift",
6349
+ cmd: "cmd",
6350
+ super: "cmd",
6351
+ meta: "cmd",
6352
+ win: "cmd",
6353
+ fn: "fn"
6354
+ };
6355
+ const keyMap = {
6356
+ return: "enter",
6357
+ enter: "enter",
6358
+ esc: "esc",
6359
+ escape: "esc",
6360
+ backspace: "delete",
6361
+ back_space: "delete",
6362
+ delete: "fwd-delete",
6363
+ fwd_delete: "fwd-delete",
6364
+ forward_delete: "fwd-delete",
6365
+ tab: "tab",
6366
+ space: "space",
6367
+ up: "arrow-up",
6368
+ arrow_up: "arrow-up",
6369
+ down: "arrow-down",
6370
+ arrow_down: "arrow-down",
6371
+ left: "arrow-left",
6372
+ arrow_left: "arrow-left",
6373
+ right: "arrow-right",
6374
+ arrow_right: "arrow-right",
6375
+ page_up: "page-up",
6376
+ pageup: "page-up",
6377
+ page_down: "page-down",
6378
+ pagedown: "page-down",
6379
+ home: "home",
6380
+ end: "end",
6381
+ f1: "f1",
6382
+ f2: "f2",
6383
+ f3: "f3",
6384
+ f4: "f4",
6385
+ f5: "f5",
6386
+ f6: "f6",
6387
+ f7: "f7",
6388
+ f8: "f8",
6389
+ f9: "f9",
6390
+ f10: "f10",
6391
+ f11: "f11",
6392
+ f12: "f12"
6393
+ };
6394
+ const modifiers = [];
6395
+ let mainKey = null;
6396
+ for (let i = 0; i < parts.length; i++) {
6397
+ const lower = parts[i].toLowerCase().replace(/-/g, "_");
6398
+ if (i < parts.length - 1 && modMap[lower]) {
6399
+ modifiers.push(modMap[lower]);
6400
+ } else {
6401
+ mainKey = keyMap[lower] || lower;
6402
+ }
6403
+ }
6404
+ const args = [];
6405
+ if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
6406
+ if (mainKey) {
6407
+ const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
6408
+ if (isNamedKey) {
6409
+ args.push(`kp:${mainKey}`);
6410
+ } else {
6411
+ args.push(`t:${mainKey}`);
6412
+ }
6413
+ }
6414
+ if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
6415
+ return args;
6416
+ }
6417
+ function modifierStringToCliclick(text) {
6418
+ return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
6419
+ if (p === "ctrl" || p === "control") return "ctrl";
6420
+ if (p === "alt" || p === "option") return "alt";
6421
+ if (p === "shift") return "shift";
6422
+ if (p === "super" || p === "meta" || p === "cmd") return "cmd";
6423
+ return "";
6424
+ }).filter(Boolean);
6425
+ }
6426
+ function createComputerUseTool(options) {
6427
+ const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
6428
+ const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
6429
+ return anthropic.tools.computer_20251124({
6430
+ displayWidthPx: displayWidth,
6431
+ displayHeightPx: displayHeight,
6432
+ enableZoom: true,
6433
+ execute: async (input) => {
6434
+ try {
6435
+ switch (input.action) {
6436
+ case "screenshot": {
6437
+ const path = join8(tmpdir(), `cu-${nanoid3(8)}.png`);
6438
+ await runScreencapture(path);
6439
+ const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
6440
+ try {
6441
+ unlinkSync2(path);
6442
+ } catch {
6443
+ }
6444
+ return { type: "image", data: resized.toString("base64") };
6445
+ }
6446
+ case "left_click": {
6447
+ const [x, y] = input.coordinate ?? [0, 0];
6448
+ if (input.text) {
6449
+ const mods = modifierStringToCliclick(input.text);
6450
+ if (mods.length > 0) {
6451
+ await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
6452
+ } else {
6453
+ await runCliclick([`c:${x},${y}`]);
6454
+ }
6455
+ } else {
6456
+ await runCliclick([`c:${x},${y}`]);
6457
+ }
6458
+ return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
6459
+ }
6460
+ case "right_click": {
6461
+ const [x, y] = input.coordinate ?? [0, 0];
6462
+ await runCliclick([`rc:${x},${y}`]);
6463
+ return `right-clicked at (${x}, ${y})`;
6464
+ }
6465
+ case "middle_click": {
6466
+ const [x, y] = input.coordinate ?? [0, 0];
6467
+ const script = `ObjC.import('CoreGraphics');var loc = $.CGPointMake(${x}, ${y});var down = $.CGEventCreateMouseEvent(null, 25, loc, 2);var up = $.CGEventCreateMouseEvent(null, 26, loc, 2);$.CGEventPost(0, down); $.CGEventPost(0, up);`;
6468
+ await execAsync5(
6469
+ `osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
6470
+ { timeout: 3e3 }
6471
+ );
6472
+ return `middle-clicked at (${x}, ${y})`;
6473
+ }
6474
+ case "double_click": {
6475
+ const [x, y] = input.coordinate ?? [0, 0];
6476
+ await runCliclick([`dc:${x},${y}`]);
6477
+ return `double-clicked at (${x}, ${y})`;
6478
+ }
6479
+ case "triple_click": {
6480
+ const [x, y] = input.coordinate ?? [0, 0];
6481
+ await runCliclick([`tc:${x},${y}`]);
6482
+ return `triple-clicked at (${x}, ${y})`;
6483
+ }
6484
+ case "mouse_move": {
6485
+ const [x, y] = input.coordinate ?? [0, 0];
6486
+ await runCliclick([`m:${x},${y}`]);
6487
+ return `moved cursor to (${x}, ${y})`;
6488
+ }
6489
+ case "left_mouse_down": {
6490
+ const [x, y] = input.coordinate ?? [0, 0];
6491
+ await runCliclick([`dd:${x},${y}`]);
6492
+ return `left mouse button pressed at (${x}, ${y})`;
6493
+ }
6494
+ case "left_mouse_up": {
6495
+ const [x, y] = input.coordinate ?? [0, 0];
6496
+ await runCliclick([`du:${x},${y}`]);
6497
+ return `left mouse button released at (${x}, ${y})`;
6498
+ }
6499
+ case "left_click_drag": {
6500
+ const [sx, sy] = input.start_coordinate ?? [0, 0];
6501
+ const [ex, ey] = input.coordinate ?? [0, 0];
6502
+ await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
6503
+ return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
6504
+ }
6505
+ case "type": {
6506
+ const text = input.text ?? "";
6507
+ await runCliclick([`t:${text}`]);
6508
+ return `typed ${text.length} character(s)`;
6509
+ }
6510
+ case "key": {
6511
+ const args = translateKeyForCliclick(input.text ?? "");
6512
+ if (args.length === 0) return "no key specified";
6513
+ await runCliclick(args);
6514
+ return `pressed ${input.text}`;
6515
+ }
6516
+ case "hold_key": {
6517
+ const text = (input.text ?? "").toLowerCase();
6518
+ const duration = input.duration ?? 1;
6519
+ const modMap = {
6520
+ ctrl: "ctrl",
6521
+ control: "ctrl",
6522
+ alt: "alt",
6523
+ option: "alt",
6524
+ shift: "shift",
6525
+ cmd: "cmd",
6526
+ super: "cmd",
6527
+ meta: "cmd",
6528
+ fn: "fn"
6529
+ };
6530
+ const cliName = modMap[text] || text;
6531
+ await runCliclick([`kd:${cliName}`]);
6532
+ await new Promise((r) => setTimeout(r, duration * 1e3));
6533
+ await runCliclick([`ku:${cliName}`]);
6534
+ return `held ${text} for ${duration}s`;
6535
+ }
6536
+ case "scroll": {
6537
+ const direction = input.scroll_direction ?? "down";
6538
+ const amount = input.scroll_amount ?? 3;
6539
+ const px = amount * 100;
6540
+ const dx = direction === "left" ? -px : direction === "right" ? px : 0;
6541
+ const dy = direction === "up" ? -px : direction === "down" ? px : 0;
6542
+ if (input.coordinate) {
6543
+ const [x, y] = input.coordinate;
6544
+ await runCliclick([`m:${x},${y}`]);
6545
+ }
6546
+ const mods = input.text ? modifierStringToCliclick(input.text) : [];
6547
+ if (mods.length > 0) {
6548
+ await runCliclick([`kd:${mods.join(",")}`]);
6549
+ }
6550
+ await runScroll(dx, dy);
6551
+ if (mods.length > 0) {
6552
+ await runCliclick([`ku:${mods.join(",")}`]);
6553
+ }
6554
+ return `scrolled ${direction} by ${amount}`;
6555
+ }
6556
+ case "wait": {
6557
+ const duration = input.duration ?? 1;
6558
+ await new Promise((r) => setTimeout(r, duration * 1e3));
6559
+ return `waited ${duration}s`;
6560
+ }
6561
+ case "cursor_position": {
6562
+ const out = await runCliclick(["p:."]);
6563
+ return `cursor at ${out}`;
6564
+ }
6565
+ case "zoom": {
6566
+ const region = input.region ?? [0, 0, displayWidth, displayHeight];
6567
+ const [x1, y1, x2, y2] = region;
6568
+ const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid3(8)}.png`);
6569
+ await runScreencapture(tmpPath);
6570
+ const sharpModule = await import("sharp");
6571
+ const sharp2 = sharpModule.default || sharpModule;
6572
+ const meta = await sharp2(tmpPath).metadata();
6573
+ const scaleX = (meta.width || displayWidth) / displayWidth;
6574
+ const scaleY = (meta.height || displayHeight) / displayHeight;
6575
+ const px = {
6576
+ left: Math.max(0, Math.round(x1 * scaleX)),
6577
+ top: Math.max(0, Math.round(y1 * scaleY)),
6578
+ width: Math.max(1, Math.round((x2 - x1) * scaleX)),
6579
+ height: Math.max(1, Math.round((y2 - y1) * scaleY))
6580
+ };
6581
+ const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
6582
+ try {
6583
+ unlinkSync2(tmpPath);
6584
+ } catch {
6585
+ }
6586
+ return { type: "image", data: buf.toString("base64") };
6587
+ }
6588
+ default: {
6589
+ const exhaustive = input.action;
6590
+ return `unsupported action: ${String(exhaustive)}`;
6591
+ }
6592
+ }
6593
+ } catch (err) {
6594
+ const msg = err?.message || String(err);
6595
+ let hint = "";
6596
+ if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
6597
+ hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
6598
+ } else if (/command not found/i.test(msg)) {
6599
+ hint = " (Hint: install cliclick with `brew install cliclick`)";
6600
+ }
6601
+ return `Error: ${msg}${hint}`;
6602
+ }
6603
+ },
6604
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6605
+ toModelOutput({ output }) {
6606
+ if (typeof output === "string") {
6607
+ return { type: "content", value: [{ type: "text", text: output }] };
6608
+ }
6609
+ return {
6610
+ type: "content",
6611
+ value: [{ type: "media", data: output.data, mediaType: "image/png" }]
6612
+ };
6613
+ }
6614
+ });
6615
+ }
6616
+
6617
+ // src/tools/enable-computer-use.ts
6618
+ init_db();
6619
+ import { tool as tool13 } from "ai";
6620
+ import { z as z14 } from "zod";
6621
+ var inputSchema = z14.object({
6622
+ display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
6623
+ display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
6624
+ request_permissions: z14.boolean().optional().default(true).describe(
6625
+ "When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
6626
+ )
6627
+ });
6628
+ var ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
6629
+ var SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
6630
+ function createEnableComputerUseTool(options) {
6631
+ return tool13({
6632
+ description: "Enable Anthropic's computer use beta tool for this session. macOS only. Drives the actual desktop (mouse, keyboard, screenshots) at pixel coordinates. Requires `cliclick` (brew install cliclick), Accessibility permissions, and Screen Recording permissions. When called, this tool will automatically request any missing permissions and open System Settings to the right pane. Only works on Anthropic Claude models. After this tool succeeds, you MUST stop the current turn and ask the user to send another message \u2014 the `computer` tool only becomes available on the NEXT message because the toolset is fixed for the current turn.",
6633
+ inputSchema,
6634
+ execute: async ({ display_width, display_height, request_permissions }) => {
6635
+ try {
6636
+ if (!isMacOs()) {
6637
+ return {
6638
+ success: false,
6639
+ error: "Computer use is currently only supported on macOS.",
6640
+ platform: process.platform
6641
+ };
6642
+ }
6643
+ if (!await isCliclickInstalled()) {
6644
+ return {
6645
+ success: false,
6646
+ error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
6647
+ installCommand: "brew install cliclick",
6648
+ fixSteps: [
6649
+ "In a terminal on this Mac, run: brew install cliclick",
6650
+ "(If Homebrew is not installed, install it first from https://brew.sh)",
6651
+ "Then call enable_computer_use again"
6652
+ ]
6653
+ };
6654
+ }
6655
+ const acc = await hasAccessibilityPermissions();
6656
+ const screen = await hasScreenRecordingPermissions();
6657
+ const missing = [];
6658
+ if (!acc.ok) {
6659
+ let prompted = false;
6660
+ let panelOpened = false;
6661
+ if (request_permissions) {
6662
+ prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
6663
+ await openSystemSettings("accessibility").then(() => {
6664
+ panelOpened = true;
6665
+ }).catch(() => void 0);
6666
+ }
6667
+ missing.push({
6668
+ name: "Accessibility",
6669
+ reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
6670
+ pane: "accessibility",
6671
+ settingsUrl: ACCESSIBILITY_URL,
6672
+ fixSteps: [
6673
+ "In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
6674
+ "Click the + button",
6675
+ "Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
6676
+ "Toggle the switch ON",
6677
+ "Restart the agent process so the new permission takes effect",
6678
+ "Then call enable_computer_use again"
6679
+ ],
6680
+ prompted,
6681
+ panelOpened
6682
+ });
6683
+ }
6684
+ if (!screen) {
6685
+ let prompted = false;
6686
+ let panelOpened = false;
6687
+ if (request_permissions) {
6688
+ prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
6689
+ await openSystemSettings("screen-recording").then(() => {
6690
+ panelOpened = true;
6691
+ }).catch(() => void 0);
6692
+ }
6693
+ missing.push({
6694
+ name: "Screen Recording",
6695
+ reason: "CGPreflightScreenCaptureAccess returned false",
6696
+ pane: "screen-recording",
6697
+ settingsUrl: SCREEN_RECORDING_URL,
6698
+ fixSteps: [
6699
+ "In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
6700
+ "Click the + button",
6701
+ "Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
6702
+ "Toggle the switch ON",
6703
+ "Restart the agent process so the new permission takes effect",
6704
+ "Then call enable_computer_use again"
6705
+ ],
6706
+ prompted,
6707
+ panelOpened
6708
+ });
6709
+ }
6710
+ if (missing.length > 0) {
6711
+ return {
6712
+ success: false,
6713
+ error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
6714
+ missingPermissions: missing,
6715
+ note: request_permissions ? "System permission prompts have been triggered (best-effort) and System Settings has been opened to the relevant pane(s). After granting and restarting the agent, call enable_computer_use again." : "Re-run with request_permissions: true to auto-open System Settings."
6716
+ };
6717
+ }
6718
+ let width = display_width;
6719
+ let height = display_height;
6720
+ let detected = null;
6721
+ if (width === void 0 || height === void 0) {
6722
+ detected = await detectScreenSize();
6723
+ width = width ?? detected?.width ?? 1280;
6724
+ height = height ?? detected?.height ?? 800;
6725
+ }
6726
+ const session = await sessionQueries.getById(options.sessionId);
6727
+ if (!session) {
6728
+ return { success: false, error: "Session not found" };
6729
+ }
6730
+ const config = session.config || {};
6731
+ if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
6732
+ return {
6733
+ success: true,
6734
+ alreadyEnabled: true,
6735
+ message: "Computer use was already enabled for this session.",
6736
+ displayWidth: width,
6737
+ displayHeight: height
6738
+ };
6739
+ }
6740
+ const updated = {
6741
+ ...config,
6742
+ computerUseEnabled: true,
6743
+ computerUseDisplayWidth: width,
6744
+ computerUseDisplayHeight: height
6745
+ };
6746
+ await sessionQueries.update(options.sessionId, { config: updated });
6747
+ return {
6748
+ success: true,
6749
+ enabled: true,
6750
+ platform: "darwin",
6751
+ displayWidth: width,
6752
+ displayHeight: height,
6753
+ detectedScreenSize: detected || void 0,
6754
+ permissions: {
6755
+ accessibility: "granted",
6756
+ screenRecording: "granted"
6757
+ },
6758
+ message: `Computer use is now enabled for this session. IMPORTANT: The \`computer\` tool is NOT yet available in this turn. Stop here, send a brief message to the user telling them computer use is enabled (display: ${width}x${height}), and ask them to send their next message to begin using it.`
6759
+ };
6760
+ } catch (err) {
6761
+ return {
6762
+ success: false,
6763
+ error: err?.message || String(err)
6764
+ };
6765
+ }
6766
+ }
6767
+ });
6768
+ }
6769
+
6173
6770
  // src/tools/index.ts
6174
6771
  init_semantic();
6175
6772
  init_remote();
@@ -6218,6 +6815,20 @@ async function createTools(options) {
6218
6815
  sessionId: options.sessionId
6219
6816
  });
6220
6817
  }
6818
+ if (process.platform === "darwin") {
6819
+ if (options.enableComputerUse) {
6820
+ tools.computer = createComputerUseTool({
6821
+ workingDirectory: options.workingDirectory,
6822
+ sessionId: options.sessionId,
6823
+ displayWidth: options.computerUseDisplayWidth,
6824
+ displayHeight: options.computerUseDisplayHeight
6825
+ });
6826
+ } else {
6827
+ tools.enable_computer_use = createEnableComputerUseTool({
6828
+ sessionId: options.sessionId
6829
+ });
6830
+ }
6831
+ }
6221
6832
  if (options.enableSemanticSearch !== false) {
6222
6833
  try {
6223
6834
  if (isVectorGatewayConfigured()) {
@@ -6248,11 +6859,11 @@ init_db();
6248
6859
  init_todo();
6249
6860
  import os from "os";
6250
6861
  function getSearchInstructions() {
6251
- const platform3 = process.platform;
6862
+ const platform5 = process.platform;
6252
6863
  const common = `- **Prefer \`read_file\` over shell commands** for reading files - don't use \`cat\`, \`head\`, or \`tail\` when \`read_file\` is available
6253
6864
  - **Avoid unbounded searches** - always scope searches with glob patterns and directory paths to prevent overwhelming output
6254
6865
  - **Search strategically**: Start with specific patterns and directories, then broaden only if needed`;
6255
- if (platform3 === "win32") {
6866
+ if (platform5 === "win32") {
6256
6867
  return `${common}
6257
6868
  - **Find files**: \`dir /s /b *.ts\` or PowerShell: \`Get-ChildItem -Recurse -Filter *.ts\`
6258
6869
  - **Search content**: \`findstr /s /n "pattern" *.ts\` or PowerShell: \`Select-String -Pattern "pattern" -Path *.ts -Recurse\`
@@ -6299,13 +6910,13 @@ async function buildSystemPrompt(options) {
6299
6910
  );
6300
6911
  const hasNoTodos = todos.length === 0;
6301
6912
  const plansContext = formatPlansForContext(plans, allTodosDone || hasNoTodos);
6302
- const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
6913
+ const platform5 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
6303
6914
  const currentDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
6304
6915
  const searchInstructions = getSearchInstructions();
6305
6916
  const systemPrompt = `You are SparkECoder, an expert AI coding assistant. You help developers write, debug, and improve code.
6306
6917
 
6307
6918
  ## Environment
6308
- - **Platform**: ${platform3} (${os.release()})
6919
+ - **Platform**: ${platform5} (${os.release()})
6309
6920
  - **Date**: ${currentDate}
6310
6921
  - **Working Directory**: ${workingDirectory}
6311
6922
 
@@ -7243,10 +7854,14 @@ var Agent = class _Agent {
7243
7854
  */
7244
7855
  async createToolsWithCallbacks(options) {
7245
7856
  const config = getConfig();
7857
+ const sessionConfig = this.session.config || {};
7246
7858
  return createTools({
7247
7859
  sessionId: this.session.id,
7248
7860
  workingDirectory: this.session.workingDirectory,
7249
7861
  skillsDirectories: config.resolvedSkillsDirectories,
7862
+ enableComputerUse: sessionConfig.computerUseEnabled === true,
7863
+ computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
7864
+ computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
7250
7865
  onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
7251
7866
  onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
7252
7867
  onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
@@ -7279,10 +7894,14 @@ var Agent = class _Agent {
7279
7894
  keepRecentMessages: config.context?.keepRecentMessages || 10,
7280
7895
  autoSummarize: config.context?.autoSummarize ?? true
7281
7896
  });
7897
+ const sessionConfig = session.config || {};
7282
7898
  const tools = await createTools({
7283
7899
  sessionId: session.id,
7284
7900
  workingDirectory: session.workingDirectory,
7285
- skillsDirectories: config.resolvedSkillsDirectories
7901
+ skillsDirectories: config.resolvedSkillsDirectories,
7902
+ enableComputerUse: sessionConfig.computerUseEnabled === true,
7903
+ computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
7904
+ computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
7286
7905
  });
7287
7906
  return new _Agent(session, context, tools);
7288
7907
  }
@@ -7455,10 +8074,10 @@ ${prompt}` });
7455
8074
  const maxIterations = options.taskConfig.maxIterations ?? 50;
7456
8075
  const webhookUrl = options.taskConfig.webhookUrl;
7457
8076
  const parentTaskId = options.taskConfig.parentTaskId;
7458
- const fireWebhook = (type, data) => {
8077
+ const fireWebhook = (type2, data) => {
7459
8078
  if (!webhookUrl) return;
7460
8079
  sendWebhook(webhookUrl, {
7461
- type,
8080
+ type: type2,
7462
8081
  taskId: this.session.id,
7463
8082
  sessionId: this.session.id,
7464
8083
  ...parentTaskId ? { parentTaskId } : {},
@@ -7501,10 +8120,14 @@ ${prompt}` });
7501
8120
  });
7502
8121
  }
7503
8122
  };
8123
+ const taskSessionConfig = this.session.config || {};
7504
8124
  const taskTools = await createTools({
7505
8125
  sessionId: this.session.id,
7506
8126
  workingDirectory: this.session.workingDirectory,
7507
8127
  skillsDirectories: config.resolvedSkillsDirectories,
8128
+ enableComputerUse: taskSessionConfig.computerUseEnabled === true,
8129
+ computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
8130
+ computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
7508
8131
  onBashProgress: bashProgressHandler,
7509
8132
  onWriteFileProgress: (progress) => {
7510
8133
  options.onToolProgress?.({ toolName: "write_file", data: progress });
@@ -7787,11 +8410,11 @@ ${taskAddendum}`;
7787
8410
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7788
8411
  if (!isRemoteConfigured2()) return [];
7789
8412
  const { readFile: readFile12 } = await import("fs/promises");
7790
- const { join: join13, basename: basename6 } = await import("path");
8413
+ const { join: join14, basename: basename6 } = await import("path");
7791
8414
  const urls = [];
7792
8415
  for (const filePath of filePaths) {
7793
8416
  try {
7794
- const fullPath = filePath.startsWith("/") ? filePath : join13(this.session.workingDirectory, filePath);
8417
+ const fullPath = filePath.startsWith("/") ? filePath : join14(this.session.workingDirectory, filePath);
7795
8418
  const fileName = basename6(fullPath);
7796
8419
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
7797
8420
  const mimeMap = {
@@ -7849,11 +8472,11 @@ ${taskAddendum}`;
7849
8472
  wrappedTools[name] = originalTool;
7850
8473
  continue;
7851
8474
  }
7852
- wrappedTools[name] = tool13({
8475
+ wrappedTools[name] = tool14({
7853
8476
  description: originalTool.description || "",
7854
- inputSchema: originalTool.inputSchema || z14.object({}),
8477
+ inputSchema: originalTool.inputSchema || z15.object({}),
7855
8478
  execute: async (input, toolOptions) => {
7856
- const toolCallId = toolOptions.toolCallId || nanoid4();
8479
+ const toolCallId = toolOptions.toolCallId || nanoid5();
7857
8480
  const execution = toolExecutionQueries.create({
7858
8481
  sessionId: this.session.id,
7859
8482
  toolName: name,
@@ -7871,10 +8494,10 @@ ${taskAddendum}`;
7871
8494
  const resolverData = approvalResolvers.get(toolCallId);
7872
8495
  approvalResolvers.delete(toolCallId);
7873
8496
  this.pendingApprovals.delete(toolCallId);
7874
- const exec7 = await execution;
8497
+ const exec8 = await execution;
7875
8498
  if (!approved) {
7876
8499
  const reason = resolverData?.reason || "User rejected the tool execution";
7877
- await toolExecutionQueries.reject(exec7.id);
8500
+ await toolExecutionQueries.reject(exec8.id);
7878
8501
  await sessionQueries.updateStatus(this.session.id, "active");
7879
8502
  return {
7880
8503
  status: "rejected",
@@ -7884,14 +8507,14 @@ ${taskAddendum}`;
7884
8507
  message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
7885
8508
  };
7886
8509
  }
7887
- await toolExecutionQueries.approve(exec7.id);
8510
+ await toolExecutionQueries.approve(exec8.id);
7888
8511
  await sessionQueries.updateStatus(this.session.id, "active");
7889
8512
  try {
7890
8513
  const result = await originalTool.execute(input, toolOptions);
7891
- await toolExecutionQueries.complete(exec7.id, result);
8514
+ await toolExecutionQueries.complete(exec8.id, result);
7892
8515
  return result;
7893
8516
  } catch (error) {
7894
- await toolExecutionQueries.complete(exec7.id, null, error.message);
8517
+ await toolExecutionQueries.complete(exec8.id, null, error.message);
7895
8518
  throw error;
7896
8519
  }
7897
8520
  }
@@ -7992,18 +8615,20 @@ function cleanupPendingInputs() {
7992
8615
  }
7993
8616
  }
7994
8617
  }
7995
- var createSessionSchema = z15.object({
7996
- name: z15.string().optional(),
7997
- workingDirectory: z15.string().optional(),
7998
- model: z15.string().optional(),
7999
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
8618
+ var createSessionSchema = z16.object({
8619
+ name: z16.string().optional(),
8620
+ workingDirectory: z16.string().optional(),
8621
+ model: z16.string().optional(),
8622
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
8623
+ // Optional full session-config passthrough (computerUseEnabled, etc.)
8624
+ config: z16.record(z16.string(), z16.unknown()).optional()
8000
8625
  });
8001
- var paginationQuerySchema = z15.object({
8002
- limit: z15.string().optional(),
8003
- offset: z15.string().optional()
8626
+ var paginationQuerySchema = z16.object({
8627
+ limit: z16.string().optional(),
8628
+ offset: z16.string().optional()
8004
8629
  });
8005
- var messagesQuerySchema = z15.object({
8006
- limit: z15.string().optional()
8630
+ var messagesQuerySchema = z16.object({
8631
+ limit: z16.string().optional()
8007
8632
  });
8008
8633
  sessions.get(
8009
8634
  "/",
@@ -8041,11 +8666,20 @@ sessions.post(
8041
8666
  async (c) => {
8042
8667
  const body = c.req.valid("json");
8043
8668
  const config = getConfig();
8669
+ const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
8670
+ const baseConfig = body.config || {};
8671
+ const mergedConfig = {
8672
+ ...baseConfig,
8673
+ ...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
8674
+ // Turn on computer use by default if the server was launched with --enable-computer-use,
8675
+ // unless the client explicitly provided a value.
8676
+ ...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
8677
+ };
8044
8678
  const agent = await Agent.create({
8045
8679
  name: body.name,
8046
8680
  workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
8047
8681
  model: body.model || config.defaultModel,
8048
- sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
8682
+ sessionConfig: Object.keys(mergedConfig).length > 0 ? mergedConfig : void 0
8049
8683
  });
8050
8684
  const session = agent.getSession();
8051
8685
  return c.json({
@@ -8142,10 +8776,10 @@ sessions.get("/:id/tools", async (c) => {
8142
8776
  count: executions.length
8143
8777
  });
8144
8778
  });
8145
- var updateSessionSchema = z15.object({
8146
- model: z15.string().optional(),
8147
- name: z15.string().optional(),
8148
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
8779
+ var updateSessionSchema = z16.object({
8780
+ model: z16.string().optional(),
8781
+ name: z16.string().optional(),
8782
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
8149
8783
  });
8150
8784
  sessions.patch(
8151
8785
  "/:id",
@@ -8215,8 +8849,8 @@ sessions.post("/:id/clear", async (c) => {
8215
8849
  await agent.clearContext();
8216
8850
  return c.json({ success: true, sessionId: id });
8217
8851
  });
8218
- var pendingInputSchema = z15.object({
8219
- text: z15.string()
8852
+ var pendingInputSchema = z16.object({
8853
+ text: z16.string()
8220
8854
  });
8221
8855
  sessions.post(
8222
8856
  "/:id/pending-input",
@@ -8247,13 +8881,13 @@ sessions.get("/:id/pending-input", async (c) => {
8247
8881
  createdAt: pending.createdAt.toISOString()
8248
8882
  });
8249
8883
  });
8250
- var devtoolsContextSchema = z15.object({
8251
- url: z15.string(),
8252
- path: z15.string(),
8253
- pageName: z15.string().optional(),
8254
- screenWidth: z15.number().optional(),
8255
- screenHeight: z15.number().optional(),
8256
- devicePixelRatio: z15.number().optional()
8884
+ var devtoolsContextSchema = z16.object({
8885
+ url: z16.string(),
8886
+ path: z16.string(),
8887
+ pageName: z16.string().optional(),
8888
+ screenWidth: z16.number().optional(),
8889
+ screenHeight: z16.number().optional(),
8890
+ devicePixelRatio: z16.number().optional()
8257
8891
  });
8258
8892
  sessions.post(
8259
8893
  "/:id/devtools-context",
@@ -8439,12 +9073,12 @@ sessions.get("/:id/diff/:filePath", async (c) => {
8439
9073
  });
8440
9074
  function getAttachmentsDir(sessionId) {
8441
9075
  const appDataDir = getAppDataDirectory();
8442
- return join9(appDataDir, "attachments", sessionId);
9076
+ return join10(appDataDir, "attachments", sessionId);
8443
9077
  }
8444
9078
  function ensureAttachmentsDir(sessionId) {
8445
9079
  const dir = getAttachmentsDir(sessionId);
8446
- if (!existsSync15(dir)) {
8447
- mkdirSync5(dir, { recursive: true });
9080
+ if (!existsSync16(dir)) {
9081
+ mkdirSync6(dir, { recursive: true });
8448
9082
  }
8449
9083
  return dir;
8450
9084
  }
@@ -8455,12 +9089,12 @@ sessions.get("/:id/attachments", async (c) => {
8455
9089
  return c.json({ error: "Session not found" }, 404);
8456
9090
  }
8457
9091
  const dir = getAttachmentsDir(sessionId);
8458
- if (!existsSync15(dir)) {
9092
+ if (!existsSync16(dir)) {
8459
9093
  return c.json({ sessionId, attachments: [], count: 0 });
8460
9094
  }
8461
9095
  const files = readdirSync2(dir);
8462
9096
  const attachments = files.map((filename) => {
8463
- const filePath = join9(dir, filename);
9097
+ const filePath = join10(dir, filename);
8464
9098
  const stats = statSync2(filePath);
8465
9099
  return {
8466
9100
  id: filename.split("_")[0],
@@ -8492,10 +9126,10 @@ sessions.post("/:id/attachments", async (c) => {
8492
9126
  return c.json({ error: "No file provided" }, 400);
8493
9127
  }
8494
9128
  const dir = ensureAttachmentsDir(sessionId);
8495
- const id = nanoid5(10);
9129
+ const id = nanoid6(10);
8496
9130
  const ext = extname8(file.name) || "";
8497
9131
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
8498
- const filePath = join9(dir, safeFilename);
9132
+ const filePath = join10(dir, safeFilename);
8499
9133
  const arrayBuffer = await file.arrayBuffer();
8500
9134
  writeFileSync3(filePath, Buffer.from(arrayBuffer));
8501
9135
  return c.json({
@@ -8518,10 +9152,10 @@ sessions.post("/:id/attachments", async (c) => {
8518
9152
  return c.json({ error: "Missing filename or data" }, 400);
8519
9153
  }
8520
9154
  const dir = ensureAttachmentsDir(sessionId);
8521
- const id = nanoid5(10);
9155
+ const id = nanoid6(10);
8522
9156
  const ext = extname8(body.filename) || "";
8523
9157
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
8524
- const filePath = join9(dir, safeFilename);
9158
+ const filePath = join10(dir, safeFilename);
8525
9159
  let base64Data = body.data;
8526
9160
  if (base64Data.includes(",")) {
8527
9161
  base64Data = base64Data.split(",")[1];
@@ -8550,7 +9184,7 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
8550
9184
  return c.json({ error: "Session not found" }, 404);
8551
9185
  }
8552
9186
  const dir = getAttachmentsDir(sessionId);
8553
- if (!existsSync15(dir)) {
9187
+ if (!existsSync16(dir)) {
8554
9188
  return c.json({ error: "Attachment not found" }, 404);
8555
9189
  }
8556
9190
  const files = readdirSync2(dir);
@@ -8558,14 +9192,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
8558
9192
  if (!file) {
8559
9193
  return c.json({ error: "Attachment not found" }, 404);
8560
9194
  }
8561
- const filePath = join9(dir, file);
8562
- unlinkSync2(filePath);
9195
+ const filePath = join10(dir, file);
9196
+ unlinkSync3(filePath);
8563
9197
  return c.json({ success: true, id: attachmentId });
8564
9198
  });
8565
- var filesQuerySchema = z15.object({
8566
- query: z15.string().optional(),
9199
+ var filesQuerySchema = z16.object({
9200
+ query: z16.string().optional(),
8567
9201
  // Filter query (e.g., "src/com" to match "src/components")
8568
- limit: z15.string().optional()
9202
+ limit: z16.string().optional()
8569
9203
  // Max results (default 50)
8570
9204
  });
8571
9205
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -8641,7 +9275,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
8641
9275
  const entries = await readdir6(currentDir, { withFileTypes: true });
8642
9276
  for (const entry of entries) {
8643
9277
  if (results.length >= limit * 2) break;
8644
- const fullPath = join9(currentDir, entry.name);
9278
+ const fullPath = join10(currentDir, entry.name);
8645
9279
  const relativePath = relative9(baseDir, fullPath);
8646
9280
  if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
8647
9281
  continue;
@@ -8689,7 +9323,7 @@ sessions.get(
8689
9323
  return c.json({ error: "Session not found" }, 404);
8690
9324
  }
8691
9325
  const workingDirectory = session.workingDirectory;
8692
- if (!existsSync15(workingDirectory)) {
9326
+ if (!existsSync16(workingDirectory)) {
8693
9327
  return c.json({
8694
9328
  sessionId,
8695
9329
  workingDirectory,
@@ -8799,9 +9433,9 @@ sessions.get("/:id/browser-recording", async (c) => {
8799
9433
  init_db();
8800
9434
  import { Hono as Hono2 } from "hono";
8801
9435
  import { zValidator as zValidator2 } from "@hono/zod-validator";
8802
- import { z as z16 } from "zod";
8803
- import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
8804
- import { join as join10 } from "path";
9436
+ import { z as z17 } from "zod";
9437
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
9438
+ import { join as join11 } from "path";
8805
9439
  init_config();
8806
9440
 
8807
9441
  // src/server/resumable-stream.ts
@@ -8888,7 +9522,7 @@ var streamContext = createResumableStreamContext({
8888
9522
  });
8889
9523
 
8890
9524
  // src/server/routes/agents.ts
8891
- import { nanoid as nanoid6 } from "nanoid";
9525
+ import { nanoid as nanoid7 } from "nanoid";
8892
9526
  init_stream_proxy();
8893
9527
  init_recorder();
8894
9528
  init_remote();
@@ -8979,40 +9613,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
8979
9613
  ${prompt}`;
8980
9614
  }
8981
9615
  var agents = new Hono2();
8982
- var attachmentSchema = z16.object({
8983
- type: z16.enum(["image", "file"]),
8984
- data: z16.string(),
9616
+ var attachmentSchema = z17.object({
9617
+ type: z17.enum(["image", "file"]),
9618
+ data: z17.string(),
8985
9619
  // base64 data URL or raw base64
8986
- mediaType: z16.string().optional(),
8987
- filename: z16.string().optional()
9620
+ mediaType: z17.string().optional(),
9621
+ filename: z17.string().optional()
8988
9622
  });
8989
- var runPromptSchema = z16.object({
8990
- prompt: z16.string(),
9623
+ var runPromptSchema = z17.object({
9624
+ prompt: z17.string(),
8991
9625
  // Can be empty if attachments are provided
8992
- attachments: z16.array(attachmentSchema).optional()
9626
+ attachments: z17.array(attachmentSchema).optional()
8993
9627
  }).refine(
8994
9628
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
8995
9629
  { message: "Either prompt or attachments must be provided" }
8996
9630
  );
8997
- var quickStartSchema = z16.object({
8998
- prompt: z16.string().min(1),
8999
- name: z16.string().optional(),
9000
- workingDirectory: z16.string().optional(),
9001
- model: z16.string().optional(),
9002
- toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
9631
+ var quickStartSchema = z17.object({
9632
+ prompt: z17.string().min(1),
9633
+ name: z17.string().optional(),
9634
+ workingDirectory: z17.string().optional(),
9635
+ model: z17.string().optional(),
9636
+ toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
9003
9637
  });
9004
- var rejectSchema = z16.object({
9005
- reason: z16.string().optional()
9638
+ var rejectSchema = z17.object({
9639
+ reason: z17.string().optional()
9006
9640
  }).optional();
9007
9641
  var streamAbortControllers = /* @__PURE__ */ new Map();
9008
9642
  function getAttachmentsDirectory(sessionId) {
9009
9643
  const appDataDir = getAppDataDirectory();
9010
- return join10(appDataDir, "attachments", sessionId);
9644
+ return join11(appDataDir, "attachments", sessionId);
9011
9645
  }
9012
9646
  async function saveAttachmentToDisk(sessionId, attachment, index) {
9013
9647
  const attachmentsDir = getAttachmentsDirectory(sessionId);
9014
- if (!existsSync16(attachmentsDir)) {
9015
- mkdirSync6(attachmentsDir, { recursive: true });
9648
+ if (!existsSync17(attachmentsDir)) {
9649
+ mkdirSync7(attachmentsDir, { recursive: true });
9016
9650
  }
9017
9651
  let filename = attachment.filename;
9018
9652
  if (!filename) {
@@ -9030,7 +9664,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
9030
9664
  attachment.mediaType = resized.mediaType;
9031
9665
  attachment.data = buffer.toString("base64");
9032
9666
  }
9033
- const filePath = join10(attachmentsDir, filename);
9667
+ const filePath = join11(attachmentsDir, filename);
9034
9668
  writeFileSync4(filePath, buffer);
9035
9669
  return filePath;
9036
9670
  }
@@ -9041,9 +9675,9 @@ function stripDataUrlPrefix2(data) {
9041
9675
  }
9042
9676
  return data;
9043
9677
  }
9044
- function getExtensionFromMediaType(mediaType, type) {
9678
+ function getExtensionFromMediaType(mediaType, type2) {
9045
9679
  if (!mediaType) {
9046
- return type === "image" ? ".png" : ".bin";
9680
+ return type2 === "image" ? ".png" : ".bin";
9047
9681
  }
9048
9682
  const mimeToExt = {
9049
9683
  "image/png": ".png",
@@ -9447,7 +10081,7 @@ ${prompt}` });
9447
10081
  userMessageContent = prompt;
9448
10082
  }
9449
10083
  await messageQueries.create(id, { role: "user", content: userMessageContent });
9450
- const streamId = `stream_${id}_${nanoid6(10)}`;
10084
+ const streamId = `stream_${id}_${nanoid7(10)}`;
9451
10085
  console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
9452
10086
  await activeStreamQueries.create(id, streamId);
9453
10087
  const stream = await streamContext.resumableStream(
@@ -9652,7 +10286,7 @@ agents.post(
9652
10286
  });
9653
10287
  const session = agent.getSession();
9654
10288
  const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
9655
- const streamId = `stream_${session.id}_${nanoid6(10)}`;
10289
+ const streamId = `stream_${session.id}_${nanoid7(10)}`;
9656
10290
  await createCheckpoint(session.id, session.workingDirectory, 0);
9657
10291
  await activeStreamQueries.create(session.id, streamId);
9658
10292
  const createQuickStreamProducer = () => {
@@ -9919,23 +10553,23 @@ agents.post(
9919
10553
  });
9920
10554
  }
9921
10555
  );
9922
- var browserInputSchema = z16.object({
9923
- type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
9924
- eventType: z16.string(),
9925
- x: z16.number().optional(),
9926
- y: z16.number().optional(),
9927
- button: z16.string().optional(),
9928
- clickCount: z16.number().optional(),
9929
- deltaX: z16.number().optional(),
9930
- deltaY: z16.number().optional(),
9931
- key: z16.string().optional(),
9932
- code: z16.string().optional(),
9933
- text: z16.string().optional(),
9934
- modifiers: z16.number().optional(),
9935
- touchPoints: z16.array(z16.object({
9936
- x: z16.number(),
9937
- y: z16.number(),
9938
- id: z16.number().optional()
10556
+ var browserInputSchema = z17.object({
10557
+ type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
10558
+ eventType: z17.string(),
10559
+ x: z17.number().optional(),
10560
+ y: z17.number().optional(),
10561
+ button: z17.string().optional(),
10562
+ clickCount: z17.number().optional(),
10563
+ deltaX: z17.number().optional(),
10564
+ deltaY: z17.number().optional(),
10565
+ key: z17.string().optional(),
10566
+ code: z17.string().optional(),
10567
+ text: z17.string().optional(),
10568
+ modifiers: z17.number().optional(),
10569
+ touchPoints: z17.array(z17.object({
10570
+ x: z17.number(),
10571
+ y: z17.number(),
10572
+ id: z17.number().optional()
9939
10573
  })).optional()
9940
10574
  });
9941
10575
  agents.post(
@@ -9970,27 +10604,279 @@ agents.get("/:id/browser-stream", async (c) => {
9970
10604
  init_config();
9971
10605
  import { Hono as Hono3 } from "hono";
9972
10606
  import { zValidator as zValidator3 } from "@hono/zod-validator";
9973
- import { z as z17 } from "zod";
9974
- import { readFileSync as readFileSync7 } from "fs";
10607
+ import { z as z18 } from "zod";
10608
+ import { readFileSync as readFileSync10 } from "fs";
9975
10609
  import { fileURLToPath as fileURLToPath3 } from "url";
9976
- import { dirname as dirname6, join as join11 } from "path";
10610
+ import { dirname as dirname6, join as join12 } from "path";
10611
+
10612
+ // src/personal-agent/heartbeat.ts
10613
+ import { execSync as execSync3 } from "child_process";
10614
+ import { readFileSync as readFileSync9 } from "fs";
10615
+ import { hostname as hostname2, platform as platform3 } from "os";
10616
+
10617
+ // src/personal-agent/system-metrics.ts
10618
+ import { execSync as execSync2 } from "child_process";
10619
+ import { readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
10620
+ import {
10621
+ arch,
10622
+ cpus,
10623
+ freemem,
10624
+ hostname,
10625
+ loadavg,
10626
+ networkInterfaces,
10627
+ platform as platform2,
10628
+ release,
10629
+ totalmem,
10630
+ type,
10631
+ uptime,
10632
+ userInfo
10633
+ } from "os";
10634
+ var _lastSample = null;
10635
+ function snapshotCpuTimes() {
10636
+ const sum = { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 };
10637
+ for (const c of cpus()) {
10638
+ sum.user += c.times.user;
10639
+ sum.nice += c.times.nice;
10640
+ sum.sys += c.times.sys;
10641
+ sum.idle += c.times.idle;
10642
+ sum.irq += c.times.irq;
10643
+ }
10644
+ return sum;
10645
+ }
10646
+ function readCpuUsage() {
10647
+ const now = snapshotCpuTimes();
10648
+ if (!_lastSample) {
10649
+ _lastSample = now;
10650
+ return 0;
10651
+ }
10652
+ const dUser = now.user - _lastSample.user;
10653
+ const dNice = now.nice - _lastSample.nice;
10654
+ const dSys = now.sys - _lastSample.sys;
10655
+ const dIdle = now.idle - _lastSample.idle;
10656
+ const dIrq = now.irq - _lastSample.irq;
10657
+ const total = dUser + dNice + dSys + dIdle + dIrq;
10658
+ _lastSample = now;
10659
+ if (total <= 0) return 0;
10660
+ return Math.max(0, Math.min(1, (total - dIdle) / total));
10661
+ }
10662
+ snapshotCpuTimes();
10663
+ function readCpuTempC() {
10664
+ try {
10665
+ if (platform2() === "linux") {
10666
+ let hottest = -Infinity;
10667
+ try {
10668
+ for (const entry of readdirSync3("/sys/class/thermal")) {
10669
+ if (!entry.startsWith("thermal_zone")) continue;
10670
+ try {
10671
+ const v = Number(
10672
+ readFileSync8(`/sys/class/thermal/${entry}/temp`, "utf8").trim()
10673
+ );
10674
+ if (Number.isFinite(v) && v > hottest) hottest = v;
10675
+ } catch {
10676
+ }
10677
+ }
10678
+ } catch {
10679
+ }
10680
+ if (hottest > -Infinity) return hottest / 1e3;
10681
+ }
10682
+ const overrideCmd = process.env.PERSONAL_AGENT_TEMP_CMD;
10683
+ if (overrideCmd) {
10684
+ const out = execSync2(overrideCmd, { encoding: "utf8", timeout: 1500 }).trim();
10685
+ const v = Number(out);
10686
+ if (Number.isFinite(v)) return v;
10687
+ }
10688
+ } catch {
10689
+ }
10690
+ return void 0;
10691
+ }
10692
+ function readDisks() {
10693
+ try {
10694
+ const p = platform2();
10695
+ if (p === "darwin" || p === "linux") {
10696
+ const raw = execSync2("df -kP", { encoding: "utf8", timeout: 2e3 });
10697
+ const lines = raw.trim().split("\n").slice(1);
10698
+ const out = [];
10699
+ for (const line of lines) {
10700
+ const parts = line.trim().split(/\s+/);
10701
+ if (parts.length < 6) continue;
10702
+ const filesystem = parts[0];
10703
+ const total1k = Number(parts[1]);
10704
+ const used1k = Number(parts[2]);
10705
+ const free1k = Number(parts[3]);
10706
+ const mount = parts.slice(5).join(" ");
10707
+ if (!Number.isFinite(total1k) || total1k <= 0) continue;
10708
+ if (filesystem === "tmpfs" || filesystem === "devfs" || filesystem === "map" || filesystem.startsWith("/dev/loop") || mount.startsWith("/System/Volumes/") || mount.startsWith("/private/var/vm") || mount.startsWith("/proc") || mount.startsWith("/sys") || mount.startsWith("/run") || mount.startsWith("/snap")) {
10709
+ if (mount !== "/") continue;
10710
+ }
10711
+ out.push({
10712
+ mount,
10713
+ filesystem,
10714
+ totalBytes: total1k * 1024,
10715
+ usedBytes: used1k * 1024,
10716
+ freeBytes: free1k * 1024,
10717
+ usage: total1k > 0 ? used1k / total1k : 0
10718
+ });
10719
+ }
10720
+ out.sort((a, b) => {
10721
+ const score = (m) => m === "/" ? 0 : m.startsWith("/Users") || m.startsWith("/home") ? 1 : 2;
10722
+ return score(a.mount) - score(b.mount);
10723
+ });
10724
+ return out.slice(0, 6);
10725
+ }
10726
+ if (p === "win32") {
10727
+ const raw = execSync2(
10728
+ "wmic logicaldisk get DeviceID,Size,FreeSpace /format:csv",
10729
+ { encoding: "utf8", timeout: 3e3 }
10730
+ );
10731
+ const out = [];
10732
+ for (const line of raw.trim().split(/\r?\n/).slice(1)) {
10733
+ const cols = line.split(",");
10734
+ if (cols.length < 4) continue;
10735
+ const [, deviceId, freeStr, sizeStr] = cols;
10736
+ const total = Number(sizeStr);
10737
+ const free = Number(freeStr);
10738
+ if (!Number.isFinite(total) || total <= 0) continue;
10739
+ const used = Math.max(0, total - free);
10740
+ out.push({
10741
+ mount: deviceId,
10742
+ totalBytes: total,
10743
+ usedBytes: used,
10744
+ freeBytes: free,
10745
+ usage: used / total
10746
+ });
10747
+ }
10748
+ return out;
10749
+ }
10750
+ } catch {
10751
+ }
10752
+ return void 0;
10753
+ }
10754
+ function readNetwork() {
10755
+ try {
10756
+ const out = [];
10757
+ const ifaces = networkInterfaces();
10758
+ for (const [name, addrs] of Object.entries(ifaces)) {
10759
+ if (!addrs) continue;
10760
+ for (const a of addrs) {
10761
+ if (a.internal) continue;
10762
+ out.push({
10763
+ iface: name,
10764
+ family: a.family,
10765
+ address: a.address,
10766
+ mac: a.mac,
10767
+ internal: a.internal
10768
+ });
10769
+ }
10770
+ }
10771
+ return out;
10772
+ } catch {
10773
+ return void 0;
10774
+ }
10775
+ }
10776
+ function readSystemMetrics() {
10777
+ const cpuList = cpus();
10778
+ const usage = readCpuUsage();
10779
+ const tot = totalmem();
10780
+ const free = freemem();
10781
+ const metrics = {
10782
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
10783
+ hostname: hostname(),
10784
+ platform: platform2(),
10785
+ arch: arch(),
10786
+ kernelRelease: release(),
10787
+ osType: type(),
10788
+ processUptimeSec: Math.round(process.uptime()),
10789
+ systemUptimeSec: Math.round(uptime()),
10790
+ user: safeUser(),
10791
+ cpu: cpuList[0] ? {
10792
+ model: cpuList[0].model,
10793
+ count: cpuList.length,
10794
+ speedMhz: cpuList[0].speed,
10795
+ loadAvg1: loadavg()[0],
10796
+ loadAvg5: loadavg()[1],
10797
+ loadAvg15: loadavg()[2],
10798
+ usage,
10799
+ tempC: readCpuTempC()
10800
+ } : void 0,
10801
+ memory: {
10802
+ totalBytes: tot,
10803
+ freeBytes: free,
10804
+ usedBytes: Math.max(0, tot - free),
10805
+ usage: tot > 0 ? (tot - free) / tot : 0
10806
+ },
10807
+ disks: readDisks(),
10808
+ network: readNetwork()
10809
+ };
10810
+ return metrics;
10811
+ }
10812
+ function safeUser() {
10813
+ try {
10814
+ return userInfo().username;
10815
+ } catch {
10816
+ return process.env.USER ?? process.env.USERNAME ?? "unknown";
10817
+ }
10818
+ }
10819
+
10820
+ // src/personal-agent/heartbeat.ts
10821
+ var _cachedHwid = null;
10822
+ function getHardwareIdCached() {
10823
+ if (_cachedHwid !== null) return _cachedHwid;
10824
+ _cachedHwid = getHardwareId();
10825
+ return _cachedHwid;
10826
+ }
10827
+ function getHardwareId() {
10828
+ const p = platform3();
10829
+ try {
10830
+ if (p === "darwin") {
10831
+ const out = execSync3(
10832
+ `ioreg -rd1 -c IOPlatformExpertDevice | awk -F\\" '/IOPlatformUUID/ {print $4}'`,
10833
+ { encoding: "utf8", timeout: 2e3 }
10834
+ ).trim();
10835
+ if (out) return normalize2(out);
10836
+ } else if (p === "linux") {
10837
+ try {
10838
+ return normalize2(readFileSync9("/etc/machine-id", "utf8").trim());
10839
+ } catch {
10840
+ return normalize2(readFileSync9("/var/lib/dbus/machine-id", "utf8").trim());
10841
+ }
10842
+ } else if (p === "win32") {
10843
+ const out = execSync3("wmic csproduct get uuid /value", {
10844
+ encoding: "utf8",
10845
+ timeout: 3e3
10846
+ });
10847
+ const m = out.match(/UUID=([\w-]+)/i);
10848
+ if (m && m[1]) return normalize2(m[1]);
10849
+ }
10850
+ } catch (e) {
10851
+ console.warn(`[personal-agent] could not read hardware UUID: ${e.message}`);
10852
+ }
10853
+ console.warn(
10854
+ "[personal-agent] falling back to hostname for HWID; this is NOT stable across rename/reinstall"
10855
+ );
10856
+ return `host-${hostname2()}`;
10857
+ }
10858
+ function normalize2(raw) {
10859
+ return raw.trim().toLowerCase().replace(/[^a-z0-9-]/g, "");
10860
+ }
10861
+
10862
+ // src/server/routes/health.ts
9977
10863
  var __filename = fileURLToPath3(import.meta.url);
9978
10864
  var __dirname = dirname6(__filename);
9979
10865
  var possiblePaths = [
9980
- join11(__dirname, "../package.json"),
10866
+ join12(__dirname, "../package.json"),
9981
10867
  // From dist/server -> dist/../package.json
9982
- join11(__dirname, "../../package.json"),
10868
+ join12(__dirname, "../../package.json"),
9983
10869
  // From dist/server (if nested differently)
9984
- join11(__dirname, "../../../package.json"),
10870
+ join12(__dirname, "../../../package.json"),
9985
10871
  // From src/server/routes (development)
9986
- join11(process.cwd(), "package.json")
10872
+ join12(process.cwd(), "package.json")
9987
10873
  // From current working directory
9988
10874
  ];
9989
10875
  var currentVersion = "0.0.0";
9990
10876
  var packageName = "sparkecoder";
9991
10877
  for (const packageJsonPath of possiblePaths) {
9992
10878
  try {
9993
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
10879
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
9994
10880
  if (packageJson.name === "sparkecoder") {
9995
10881
  currentVersion = packageJson.version || "0.0.0";
9996
10882
  packageName = packageJson.name || "sparkecoder";
@@ -10005,11 +10891,17 @@ health.get("/", async (c) => {
10005
10891
  const apiKeyStatus = getApiKeyStatus();
10006
10892
  const gatewayKey = apiKeyStatus.find((s) => s.provider === "ai-gateway");
10007
10893
  const hasApiKey = gatewayKey?.configured ?? false;
10894
+ let hwid;
10895
+ try {
10896
+ hwid = getHardwareIdCached();
10897
+ } catch {
10898
+ }
10008
10899
  return c.json({
10009
10900
  status: "ok",
10010
10901
  version: currentVersion,
10011
10902
  uptime: process.uptime(),
10012
10903
  apiKeyConfigured: hasApiKey,
10904
+ hwid,
10013
10905
  config: {
10014
10906
  workingDirectory: config.resolvedWorkingDirectory,
10015
10907
  defaultModel: config.defaultModel,
@@ -10080,9 +10972,9 @@ health.get("/api-keys", async (c) => {
10080
10972
  supportedProviders: SUPPORTED_PROVIDERS
10081
10973
  });
10082
10974
  });
10083
- var setApiKeySchema = z17.object({
10084
- provider: z17.string(),
10085
- apiKey: z17.string().min(1)
10975
+ var setApiKeySchema = z18.object({
10976
+ provider: z18.string(),
10977
+ apiKey: z18.string().min(1)
10086
10978
  });
10087
10979
  health.post(
10088
10980
  "/api-keys",
@@ -10121,13 +11013,13 @@ health.delete("/api-keys/:provider", async (c) => {
10121
11013
  // src/server/routes/terminals.ts
10122
11014
  import { Hono as Hono4 } from "hono";
10123
11015
  import { zValidator as zValidator4 } from "@hono/zod-validator";
10124
- import { z as z18 } from "zod";
11016
+ import { z as z19 } from "zod";
10125
11017
  init_db();
10126
11018
  var terminals = new Hono4();
10127
- var spawnSchema = z18.object({
10128
- command: z18.string(),
10129
- cwd: z18.string().optional(),
10130
- name: z18.string().optional()
11019
+ var spawnSchema = z19.object({
11020
+ command: z19.string(),
11021
+ cwd: z19.string().optional(),
11022
+ name: z19.string().optional()
10131
11023
  });
10132
11024
  terminals.post(
10133
11025
  "/:sessionId/terminals",
@@ -10208,8 +11100,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
10208
11100
  // We don't track exit codes in tmux mode
10209
11101
  });
10210
11102
  });
10211
- var logsQuerySchema = z18.object({
10212
- tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
11103
+ var logsQuerySchema = z19.object({
11104
+ tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
10213
11105
  });
10214
11106
  terminals.get(
10215
11107
  "/:sessionId/terminals/:terminalId/logs",
@@ -10233,8 +11125,8 @@ terminals.get(
10233
11125
  });
10234
11126
  }
10235
11127
  );
10236
- var killSchema = z18.object({
10237
- signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
11128
+ var killSchema = z19.object({
11129
+ signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
10238
11130
  });
10239
11131
  terminals.post(
10240
11132
  "/:sessionId/terminals/:terminalId/kill",
@@ -10248,8 +11140,8 @@ terminals.post(
10248
11140
  return c.json({ success: true, message: "Terminal killed" });
10249
11141
  }
10250
11142
  );
10251
- var writeSchema = z18.object({
10252
- input: z18.string()
11143
+ var writeSchema = z19.object({
11144
+ input: z19.string()
10253
11145
  });
10254
11146
  terminals.post(
10255
11147
  "/:sessionId/terminals/:terminalId/write",
@@ -10434,20 +11326,20 @@ data: ${JSON.stringify({ status: "stopped" })}
10434
11326
  init_db();
10435
11327
  import { Hono as Hono5 } from "hono";
10436
11328
  import { zValidator as zValidator5 } from "@hono/zod-validator";
10437
- import { z as z19 } from "zod";
10438
- import { nanoid as nanoid7 } from "nanoid";
11329
+ import { z as z20 } from "zod";
11330
+ import { nanoid as nanoid8 } from "nanoid";
10439
11331
  init_config();
10440
11332
  var tasks = new Hono5();
10441
11333
  var taskAbortControllers = /* @__PURE__ */ new Map();
10442
- var createTaskSchema = z19.object({
10443
- prompt: z19.string().min(1),
10444
- outputSchema: z19.record(z19.string(), z19.unknown()),
10445
- webhookUrl: z19.string().url().optional(),
10446
- model: z19.string().optional(),
10447
- workingDirectory: z19.string().optional(),
10448
- name: z19.string().optional(),
10449
- maxIterations: z19.number().int().min(1).max(500).optional(),
10450
- parentTaskId: z19.string().optional()
11334
+ var createTaskSchema = z20.object({
11335
+ prompt: z20.string().min(1),
11336
+ outputSchema: z20.record(z20.string(), z20.unknown()),
11337
+ webhookUrl: z20.string().url().optional(),
11338
+ model: z20.string().optional(),
11339
+ workingDirectory: z20.string().optional(),
11340
+ name: z20.string().optional(),
11341
+ maxIterations: z20.number().int().min(1).max(500).optional(),
11342
+ parentTaskId: z20.string().optional()
10451
11343
  });
10452
11344
  tasks.post(
10453
11345
  "/",
@@ -10509,7 +11401,7 @@ tasks.post(
10509
11401
  const taskId = agent.sessionId;
10510
11402
  const abortController = new AbortController();
10511
11403
  taskAbortControllers.set(taskId, abortController);
10512
- const streamId = `stream_${taskId}_${nanoid7(10)}`;
11404
+ const streamId = `stream_${taskId}_${nanoid8(10)}`;
10513
11405
  await activeStreamQueries.create(taskId, streamId);
10514
11406
  const taskStreamProducer = () => {
10515
11407
  const { readable, writable } = new TransformStream();
@@ -10659,17 +11551,581 @@ tasks.post("/:id/cancel", async (c) => {
10659
11551
  });
10660
11552
  var tasks_default = tasks;
10661
11553
 
11554
+ // src/server/routes/system.ts
11555
+ import { Hono as Hono6 } from "hono";
11556
+ import { streamSSE } from "hono/streaming";
11557
+ var system = new Hono6();
11558
+ system.get("/metrics", (c) => {
11559
+ return c.json(readSystemMetrics());
11560
+ });
11561
+ system.get("/metrics/stream", (c) => {
11562
+ return streamSSE(c, async (stream) => {
11563
+ const intervalMs = Number(c.req.query("intervalMs") ?? 2e3);
11564
+ const safeMs = Number.isFinite(intervalMs) ? Math.max(500, Math.min(6e4, intervalMs)) : 2e3;
11565
+ let id = 0;
11566
+ let aborted = false;
11567
+ c.req.raw.signal.addEventListener("abort", () => {
11568
+ aborted = true;
11569
+ });
11570
+ while (!aborted) {
11571
+ try {
11572
+ const snap = readSystemMetrics();
11573
+ await stream.writeSSE({
11574
+ id: String(id++),
11575
+ event: "metrics",
11576
+ data: JSON.stringify(snap)
11577
+ });
11578
+ } catch (e) {
11579
+ await stream.writeSSE({
11580
+ id: String(id++),
11581
+ event: "error",
11582
+ data: JSON.stringify({ error: e.message })
11583
+ });
11584
+ }
11585
+ await stream.sleep(safeMs);
11586
+ }
11587
+ });
11588
+ });
11589
+
11590
+ // src/personal-agent/hwid-middleware.ts
11591
+ var SKIP_PATHS = /* @__PURE__ */ new Set(["/health", "/health/", "/health/ready", "/health/version"]);
11592
+ function personalAgentConfigured() {
11593
+ return process.env.PERSONAL_AGENT_MODE === "1" || process.env.PERSONAL_AGENT_MODE === "true" || Boolean(process.env.PERSONAL_AGENT_PUBLIC_KEY) || Boolean(process.env.PERSONAL_AGENT_DASHBOARD);
11594
+ }
11595
+ function hwidMiddleware() {
11596
+ const enabled = personalAgentConfigured();
11597
+ let warnedMissing = false;
11598
+ return async (c, next) => {
11599
+ if (!enabled) return next();
11600
+ const path = c.req.path;
11601
+ if (SKIP_PATHS.has(path) || path.startsWith("/health/api-keys")) {
11602
+ return next();
11603
+ }
11604
+ const got = c.req.header("x-device-hwid");
11605
+ if (!got) {
11606
+ if (!warnedMissing) {
11607
+ warnedMissing = true;
11608
+ console.warn(
11609
+ `[personal-agent] request to ${path} arrived without X-Device-Hwid; allowing (backwards compat)`
11610
+ );
11611
+ }
11612
+ return next();
11613
+ }
11614
+ const expected = getHardwareIdCached();
11615
+ if (got !== expected) {
11616
+ console.warn(
11617
+ `[personal-agent] HWID mismatch on ${path}: got=${got.slice(0, 12)}\u2026, expected=${expected.slice(0, 12)}\u2026`
11618
+ );
11619
+ return c.json(
11620
+ {
11621
+ error: "hwid mismatch",
11622
+ message: "This sparkecoder's hardware UUID does not match what the dashboard expected. Likely cause: a Cloudflare tunnel hostname is pointing at the wrong machine.",
11623
+ expected: expected.slice(0, 12) + "\u2026",
11624
+ got: got.slice(0, 12) + "\u2026"
11625
+ },
11626
+ 409
11627
+ );
11628
+ }
11629
+ return next();
11630
+ };
11631
+ }
11632
+
11633
+ // src/personal-agent/signature-verify.ts
11634
+ import { createHash as createHash3, createPublicKey, verify as cryptoVerify } from "crypto";
11635
+ import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
11636
+ var REPLAY_WINDOW_SECONDS = 5 * 60;
11637
+ var _cachedKey = null;
11638
+ var _cachedFromInput = null;
11639
+ function loadPublicKey(input) {
11640
+ if (_cachedFromInput === input && _cachedKey) return _cachedKey;
11641
+ let pem = input;
11642
+ if (!input.includes("BEGIN") && existsSync18(input)) {
11643
+ pem = readFileSync11(input, "utf8");
11644
+ }
11645
+ const key = createPublicKey({ key: pem, format: "pem" });
11646
+ if (key.asymmetricKeyType !== "ed25519") {
11647
+ throw new Error(
11648
+ `expected an ed25519 public key, got ${key.asymmetricKeyType}. Generate with personal-agents/scripts/generate-signing-keys.mjs.`
11649
+ );
11650
+ }
11651
+ _cachedKey = key;
11652
+ _cachedFromInput = input;
11653
+ return key;
11654
+ }
11655
+ function bodyHashB64(body) {
11656
+ const hash = createHash3("sha256");
11657
+ if (body == null || body === "") {
11658
+ } else if (typeof body === "string") {
11659
+ hash.update(body, "utf8");
11660
+ } else if (Buffer.isBuffer(body)) {
11661
+ hash.update(body);
11662
+ } else {
11663
+ hash.update(Buffer.from(body));
11664
+ }
11665
+ return hash.digest("base64");
11666
+ }
11667
+ function canonicalSigningString(args) {
11668
+ return [
11669
+ args.method.toUpperCase(),
11670
+ args.path,
11671
+ String(args.timestamp),
11672
+ args.bodyHashB64
11673
+ ].join("\n");
11674
+ }
11675
+ function fromBase64Url(s) {
11676
+ const padded = s.padEnd(s.length + (4 - s.length % 4) % 4, "=");
11677
+ const std = padded.replace(/-/g, "+").replace(/_/g, "/");
11678
+ return Buffer.from(std, "base64");
11679
+ }
11680
+ function verifyEmbedToken(args) {
11681
+ const dot = args.token.indexOf(".");
11682
+ if (dot < 1 || dot >= args.token.length - 1) {
11683
+ return { ok: false, reason: "malformed" };
11684
+ }
11685
+ const payloadB64 = args.token.slice(0, dot);
11686
+ const sigB64 = args.token.slice(dot + 1);
11687
+ let sigBuf;
11688
+ try {
11689
+ sigBuf = fromBase64Url(sigB64);
11690
+ } catch {
11691
+ return { ok: false, reason: "bad-encoding" };
11692
+ }
11693
+ const sigOk = cryptoVerify(
11694
+ null,
11695
+ Buffer.from(payloadB64, "utf8"),
11696
+ args.publicKey,
11697
+ sigBuf
11698
+ );
11699
+ if (!sigOk) return { ok: false, reason: "signature-mismatch" };
11700
+ let payload;
11701
+ try {
11702
+ const json = JSON.parse(fromBase64Url(payloadB64).toString("utf8"));
11703
+ if (!json || typeof json !== "object" || typeof json.sid !== "string" || typeof json.exp !== "number") {
11704
+ return { ok: false, reason: "bad-payload" };
11705
+ }
11706
+ payload = { sid: json.sid, exp: json.exp };
11707
+ } catch (e) {
11708
+ return { ok: false, reason: "bad-payload", detail: e.message };
11709
+ }
11710
+ const now = args.now ?? Math.floor(Date.now() / 1e3);
11711
+ if (payload.exp < now) {
11712
+ return {
11713
+ ok: false,
11714
+ reason: "expired",
11715
+ detail: `${now - payload.exp}s past expiry`
11716
+ };
11717
+ }
11718
+ return { ok: true, payload };
11719
+ }
11720
+ function verifyRequest(args) {
11721
+ if (!args.signatureB64 || !args.timestampSeconds || !args.algorithm) {
11722
+ return { ok: false, reason: "missing-headers" };
11723
+ }
11724
+ if (args.algorithm.toLowerCase() !== "ed25519") {
11725
+ return { ok: false, reason: "bad-algorithm", detail: args.algorithm };
11726
+ }
11727
+ const ts = Number(args.timestampSeconds);
11728
+ if (!Number.isFinite(ts)) {
11729
+ return { ok: false, reason: "bad-timestamp", detail: args.timestampSeconds };
11730
+ }
11731
+ const now = args.now ?? Math.floor(Date.now() / 1e3);
11732
+ if (Math.abs(now - ts) > REPLAY_WINDOW_SECONDS) {
11733
+ return {
11734
+ ok: false,
11735
+ reason: "stale-timestamp",
11736
+ detail: `${Math.abs(now - ts)}s outside the ${REPLAY_WINDOW_SECONDS}s window`
11737
+ };
11738
+ }
11739
+ let sigBuf;
11740
+ try {
11741
+ sigBuf = Buffer.from(args.signatureB64, "base64");
11742
+ } catch {
11743
+ return { ok: false, reason: "bad-signature-encoding" };
11744
+ }
11745
+ const canonical = canonicalSigningString({
11746
+ method: args.method,
11747
+ path: args.path,
11748
+ timestamp: ts,
11749
+ bodyHashB64: bodyHashB64(args.body)
11750
+ });
11751
+ const ok = cryptoVerify(null, Buffer.from(canonical, "utf8"), args.publicKey, sigBuf);
11752
+ return ok ? { ok: true } : { ok: false, reason: "signature-mismatch" };
11753
+ }
11754
+
11755
+ // src/personal-agent/signature-middleware.ts
11756
+ var SKIP_PATHS2 = /* @__PURE__ */ new Set(["/health", "/health/", "/health/ready", "/health/version"]);
11757
+ function isSkipped(path) {
11758
+ return SKIP_PATHS2.has(path) || path.startsWith("/health/api-keys");
11759
+ }
11760
+ function pathBindsSessionId(path, sid) {
11761
+ const cleanPath = path.split("?")[0];
11762
+ const segments = cleanPath.split("/").filter(Boolean);
11763
+ return segments.includes(sid);
11764
+ }
11765
+ function signatureMiddleware(opts = {}) {
11766
+ const acceptSignedOnly = opts.acceptSignedOnly ?? process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY === "1";
11767
+ const publicKeyInput = opts.publicKeyInput ?? process.env.PERSONAL_AGENT_PUBLIC_KEY ?? "";
11768
+ if (acceptSignedOnly && !publicKeyInput) {
11769
+ return async (c) => c.json(
11770
+ {
11771
+ error: "signature middleware misconfigured",
11772
+ message: "started with --accept-signed-only but no --personal-agent-public-key / PERSONAL_AGENT_PUBLIC_KEY supplied. Refusing all non-/health traffic."
11773
+ },
11774
+ 500
11775
+ );
11776
+ }
11777
+ if (!publicKeyInput) {
11778
+ return async (_c, next) => next();
11779
+ }
11780
+ const publicKey = loadPublicKey(publicKeyInput);
11781
+ let warnedUnsigned = false;
11782
+ return async (c, next) => {
11783
+ const path = c.req.path;
11784
+ if (isSkipped(path)) return next();
11785
+ const sig = c.req.header("x-signature");
11786
+ const ts = c.req.header("x-signature-timestamp");
11787
+ const alg = c.req.header("x-signature-algorithm");
11788
+ if (!sig && (c.req.method === "GET" || c.req.method === "HEAD")) {
11789
+ const embedTok = c.req.header("x-embed-token") ?? c.req.query("embed_token");
11790
+ if (embedTok) {
11791
+ const result2 = verifyEmbedToken({ publicKey, token: embedTok });
11792
+ if (!result2.ok) {
11793
+ return c.json(
11794
+ {
11795
+ error: "embed token verification failed",
11796
+ reason: result2.reason,
11797
+ detail: result2.detail
11798
+ },
11799
+ 401
11800
+ );
11801
+ }
11802
+ if (!pathBindsSessionId(path, result2.payload.sid)) {
11803
+ return c.json(
11804
+ {
11805
+ error: "embed token scoped to a different session",
11806
+ detail: `token sid=${result2.payload.sid} but request path=${path}`
11807
+ },
11808
+ 403
11809
+ );
11810
+ }
11811
+ return next();
11812
+ }
11813
+ }
11814
+ if (!sig) {
11815
+ if (acceptSignedOnly) {
11816
+ return c.json(
11817
+ {
11818
+ error: "signature required",
11819
+ message: "this sparkecoder is started with --accept-signed-only; every request must carry X-Signature, X-Signature-Timestamp, X-Signature-Algorithm."
11820
+ },
11821
+ 401
11822
+ );
11823
+ }
11824
+ if (!warnedUnsigned) {
11825
+ warnedUnsigned = true;
11826
+ console.warn(
11827
+ `[personal-agent] inbound ${c.req.method} ${path} arrived without X-Signature; allowed because --accept-signed-only is off`
11828
+ );
11829
+ }
11830
+ return next();
11831
+ }
11832
+ let body;
11833
+ if (c.req.method !== "GET" && c.req.method !== "HEAD") {
11834
+ body = Buffer.from(await c.req.raw.clone().arrayBuffer());
11835
+ }
11836
+ const result = verifyRequest({
11837
+ publicKey,
11838
+ method: c.req.method,
11839
+ path,
11840
+ body,
11841
+ signatureB64: sig,
11842
+ timestampSeconds: ts,
11843
+ algorithm: alg
11844
+ });
11845
+ if (!result.ok) {
11846
+ console.warn(
11847
+ `[personal-agent] signature verification failed on ${c.req.method} ${path}: ${result.reason}${result.detail ? ` (${result.detail})` : ""}`
11848
+ );
11849
+ return c.json(
11850
+ {
11851
+ error: "signature verification failed",
11852
+ reason: result.reason,
11853
+ detail: result.detail
11854
+ },
11855
+ 401
11856
+ );
11857
+ }
11858
+ return next();
11859
+ };
11860
+ }
11861
+
11862
+ // src/personal-agent/pty-server.ts
11863
+ import { hostname as hostname3 } from "os";
11864
+ import { WebSocketServer } from "ws";
11865
+ var RESIZE_RE = /\x1b\[RESIZE:(\d+);(\d+)\]/;
11866
+ var _ptyMod = null;
11867
+ async function loadPty() {
11868
+ if (_ptyMod) return _ptyMod;
11869
+ try {
11870
+ const mod = await import("node-pty");
11871
+ _ptyMod = mod;
11872
+ return mod;
11873
+ } catch (e) {
11874
+ console.warn(
11875
+ `[personal-agent] node-pty failed to load; /pty WebSocket disabled. (${e.message})`
11876
+ );
11877
+ return null;
11878
+ }
11879
+ }
11880
+ function defaultShell() {
11881
+ if (process.platform === "win32") {
11882
+ return { file: process.env.COMSPEC || "cmd.exe", args: [] };
11883
+ }
11884
+ return { file: process.env.SHELL || "/bin/zsh", args: ["-l"] };
11885
+ }
11886
+ function cleanEnv() {
11887
+ const env = {};
11888
+ for (const [k, v] of Object.entries(process.env)) {
11889
+ if (typeof v === "string") env[k] = v;
11890
+ }
11891
+ if (!env.TERM) env.TERM = "xterm-256color";
11892
+ if (!env.LANG) env.LANG = "en_US.UTF-8";
11893
+ return env;
11894
+ }
11895
+ function parseUpgrade(req) {
11896
+ const url = new URL(req.url || "/", "http://placeholder");
11897
+ const cols = clampInt(url.searchParams.get("cols"), 80, 10, 500);
11898
+ const rows = clampInt(url.searchParams.get("rows"), 24, 5, 500);
11899
+ const cwd = url.searchParams.get("cwd") || void 0;
11900
+ const shell = url.searchParams.get("shell") || void 0;
11901
+ return {
11902
+ cols,
11903
+ rows,
11904
+ cwd,
11905
+ shell,
11906
+ hwidHeader: headerStr(req, "x-device-hwid") ?? url.searchParams.get("hwid") ?? void 0,
11907
+ sigHeader: headerStr(req, "x-signature") ?? url.searchParams.get("sig") ?? void 0,
11908
+ tsHeader: headerStr(req, "x-signature-timestamp") ?? url.searchParams.get("ts") ?? void 0,
11909
+ algHeader: headerStr(req, "x-signature-algorithm") ?? url.searchParams.get("alg") ?? void 0
11910
+ };
11911
+ }
11912
+ function clampInt(v, dflt, lo, hi) {
11913
+ if (!v) return dflt;
11914
+ const n = parseInt(v, 10);
11915
+ if (!Number.isFinite(n)) return dflt;
11916
+ return Math.max(lo, Math.min(hi, n));
11917
+ }
11918
+ function headerStr(req, name) {
11919
+ const v = req.headers[name];
11920
+ if (Array.isArray(v)) return v[0];
11921
+ return v;
11922
+ }
11923
+ function authenticate(parsed, path, pubKey, signedOnly) {
11924
+ if (parsed.hwidHeader) {
11925
+ const expected = getHardwareIdCached();
11926
+ if (parsed.hwidHeader !== expected) {
11927
+ return {
11928
+ ok: false,
11929
+ status: 409,
11930
+ reason: `hwid mismatch: got ${parsed.hwidHeader.slice(0, 12)}\u2026, expected ${expected.slice(0, 12)}\u2026`
11931
+ };
11932
+ }
11933
+ }
11934
+ if (!pubKey) {
11935
+ if (signedOnly) {
11936
+ return { ok: false, status: 500, reason: "signature required but no public key configured" };
11937
+ }
11938
+ return { ok: true };
11939
+ }
11940
+ if (!parsed.sigHeader) {
11941
+ if (signedOnly) {
11942
+ return { ok: false, status: 401, reason: "missing X-Signature on upgrade request" };
11943
+ }
11944
+ return { ok: true };
11945
+ }
11946
+ const result = verifyRequest({
11947
+ publicKey: pubKey,
11948
+ method: "GET",
11949
+ path,
11950
+ body: void 0,
11951
+ signatureB64: parsed.sigHeader,
11952
+ timestampSeconds: parsed.tsHeader,
11953
+ algorithm: parsed.algHeader
11954
+ });
11955
+ if (!result.ok) {
11956
+ return { ok: false, status: 401, reason: `signature verification failed: ${result.reason}` };
11957
+ }
11958
+ return { ok: true };
11959
+ }
11960
+ function rejectUpgrade(socket, status, reason) {
11961
+ const body = JSON.stringify({ error: reason });
11962
+ socket.write(
11963
+ `HTTP/1.1 ${status} ${reasonText(status)}\r
11964
+ Content-Type: application/json\r
11965
+ Content-Length: ${Buffer.byteLength(body)}\r
11966
+ Connection: close\r
11967
+ \r
11968
+ ` + body
11969
+ );
11970
+ socket.destroy();
11971
+ }
11972
+ function reasonText(status) {
11973
+ switch (status) {
11974
+ case 401:
11975
+ return "Unauthorized";
11976
+ case 409:
11977
+ return "Conflict";
11978
+ case 500:
11979
+ return "Internal Server Error";
11980
+ case 503:
11981
+ return "Service Unavailable";
11982
+ default:
11983
+ return "Error";
11984
+ }
11985
+ }
11986
+ function attachPtyServer(httpServer, opts = {}) {
11987
+ const path = opts.path ?? "/pty";
11988
+ const wss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
11989
+ const acceptSignedOnly = process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY === "1";
11990
+ const publicKeyInput = process.env.PERSONAL_AGENT_PUBLIC_KEY ?? "";
11991
+ let pubKey = null;
11992
+ if (publicKeyInput) {
11993
+ try {
11994
+ pubKey = loadPublicKey(publicKeyInput);
11995
+ } catch (e) {
11996
+ console.warn(
11997
+ `[personal-agent] /pty signature verification disabled \u2014 failed to load public key: ${e.message}`
11998
+ );
11999
+ }
12000
+ }
12001
+ const handler = (req, socket, head) => {
12002
+ let pathname = "/";
12003
+ try {
12004
+ pathname = new URL(req.url || "/", "http://placeholder").pathname;
12005
+ } catch {
12006
+ }
12007
+ if (pathname !== path) return;
12008
+ const parsed = parseUpgrade(req);
12009
+ const auth = authenticate(parsed, pathname, pubKey, acceptSignedOnly);
12010
+ if (!auth.ok) {
12011
+ console.warn(`[personal-agent] rejecting /pty upgrade: ${auth.reason}`);
12012
+ rejectUpgrade(socket, auth.status ?? 401, auth.reason ?? "unauthorized");
12013
+ return;
12014
+ }
12015
+ void loadPty().then((pty) => {
12016
+ if (!pty) {
12017
+ rejectUpgrade(
12018
+ socket,
12019
+ 503,
12020
+ "node-pty is not available on this device (failed to load native module)"
12021
+ );
12022
+ return;
12023
+ }
12024
+ wss.handleUpgrade(req, socket, head, (ws) => {
12025
+ spawnPty(ws, pty, parsed, opts);
12026
+ });
12027
+ });
12028
+ };
12029
+ httpServer.on("upgrade", handler);
12030
+ if (!opts.quiet) {
12031
+ console.log(
12032
+ `[personal-agent] WebSocket PTY server attached at ${path} (host: ${hostname3()}, sigCheck: ${pubKey ? "on" : "off"})`
12033
+ );
12034
+ }
12035
+ return {
12036
+ close: () => {
12037
+ httpServer.off("upgrade", handler);
12038
+ wss.close();
12039
+ }
12040
+ };
12041
+ }
12042
+ function spawnPty(ws, pty, parsed, opts) {
12043
+ const { file, args } = (() => {
12044
+ if (parsed.shell || opts.shell) {
12045
+ const s = parsed.shell ?? opts.shell;
12046
+ return { file: s, args: process.platform === "win32" ? [] : ["-l"] };
12047
+ }
12048
+ return defaultShell();
12049
+ })();
12050
+ const cwd = parsed.cwd ?? opts.cwd ?? process.env.HOME ?? process.cwd();
12051
+ let proc;
12052
+ try {
12053
+ proc = pty.spawn(file, args, {
12054
+ name: "xterm-256color",
12055
+ cols: parsed.cols,
12056
+ rows: parsed.rows,
12057
+ cwd,
12058
+ env: cleanEnv()
12059
+ });
12060
+ } catch (e) {
12061
+ const msg = e.message;
12062
+ safeSend(ws, `\r
12063
+ \x1B[31mFailed to spawn shell: ${msg}\x1B[0m\r
12064
+ `);
12065
+ try {
12066
+ ws.close();
12067
+ } catch {
12068
+ }
12069
+ return;
12070
+ }
12071
+ const banner = `\x1B[90m[sparkecoder pty] ${file} on ${hostname3()} pid=${proc.pid} ${parsed.cols}x${parsed.rows}\x1B[0m\r
12072
+ `;
12073
+ safeSend(ws, banner);
12074
+ proc.onData((data) => safeSend(ws, data));
12075
+ proc.onExit(({ exitCode }) => {
12076
+ safeSend(ws, `\r
12077
+ \x1B[90m[exit ${exitCode}]\x1B[0m\r
12078
+ `);
12079
+ try {
12080
+ ws.close();
12081
+ } catch {
12082
+ }
12083
+ });
12084
+ ws.on("message", (msg, isBinary) => {
12085
+ const input = typeof msg === "string" ? msg : Buffer.isBuffer(msg) ? msg.toString(isBinary ? "utf8" : "utf8") : Array.isArray(msg) ? Buffer.concat(msg).toString("utf8") : "";
12086
+ if (!input) return;
12087
+ const m = input.match(RESIZE_RE);
12088
+ if (m) {
12089
+ const cols = clampInt(m[1], 80, 10, 500);
12090
+ const rows = clampInt(m[2], 24, 5, 500);
12091
+ try {
12092
+ proc.resize(cols, rows);
12093
+ } catch {
12094
+ }
12095
+ if (input.replace(RESIZE_RE, "").length === 0) return;
12096
+ proc.write(input.replace(RESIZE_RE, ""));
12097
+ return;
12098
+ }
12099
+ proc.write(input);
12100
+ });
12101
+ const onClose = () => {
12102
+ try {
12103
+ proc.kill();
12104
+ } catch {
12105
+ }
12106
+ };
12107
+ ws.on("close", onClose);
12108
+ ws.on("error", onClose);
12109
+ }
12110
+ function safeSend(ws, data) {
12111
+ if (ws.readyState !== 1) return;
12112
+ try {
12113
+ ws.send(data);
12114
+ } catch {
12115
+ }
12116
+ }
12117
+
10662
12118
  // src/server/index.ts
10663
12119
  init_config();
10664
12120
  init_db();
10665
12121
 
10666
12122
  // src/utils/dependencies.ts
10667
- import { exec as exec6 } from "child_process";
10668
- import { promisify as promisify6 } from "util";
10669
- import { platform as platform2 } from "os";
10670
- var execAsync6 = promisify6(exec6);
12123
+ import { exec as exec7 } from "child_process";
12124
+ import { promisify as promisify7 } from "util";
12125
+ import { platform as platform4 } from "os";
12126
+ var execAsync7 = promisify7(exec7);
10671
12127
  function getInstallInstructions() {
10672
- const os2 = platform2();
12128
+ const os2 = platform4();
10673
12129
  if (os2 === "darwin") {
10674
12130
  return `
10675
12131
  Install tmux on macOS:
@@ -10700,7 +12156,7 @@ Install tmux:
10700
12156
  }
10701
12157
  async function checkTmux() {
10702
12158
  try {
10703
- const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
12159
+ const { stdout } = await execAsync7("tmux -V", { timeout: 5e3 });
10704
12160
  const version = stdout.trim();
10705
12161
  return {
10706
12162
  available: true,
@@ -10749,11 +12205,11 @@ function getWebDirectory() {
10749
12205
  try {
10750
12206
  const currentDir = dirname7(fileURLToPath4(import.meta.url));
10751
12207
  const webDir = resolve10(currentDir, "..", "web");
10752
- if (existsSync17(webDir) && existsSync17(join12(webDir, "package.json"))) {
12208
+ if (existsSync19(webDir) && existsSync19(join13(webDir, "package.json"))) {
10753
12209
  return webDir;
10754
12210
  }
10755
12211
  const altWebDir = resolve10(currentDir, "..", "..", "web");
10756
- if (existsSync17(altWebDir) && existsSync17(join12(altWebDir, "package.json"))) {
12212
+ if (existsSync19(altWebDir) && existsSync19(join13(altWebDir, "package.json"))) {
10757
12213
  return altWebDir;
10758
12214
  }
10759
12215
  return null;
@@ -10811,23 +12267,23 @@ async function findWebPort(preferredPort) {
10811
12267
  return { port: preferredPort, alreadyRunning: false };
10812
12268
  }
10813
12269
  function hasProductionBuild(webDir) {
10814
- const buildIdPath = join12(webDir, ".next", "BUILD_ID");
10815
- return existsSync17(buildIdPath);
12270
+ const buildIdPath = join13(webDir, ".next", "BUILD_ID");
12271
+ return existsSync19(buildIdPath);
10816
12272
  }
10817
12273
  function hasSourceFiles(webDir) {
10818
- const appDir = join12(webDir, "src", "app");
10819
- const pagesDir = join12(webDir, "src", "pages");
10820
- const rootAppDir = join12(webDir, "app");
10821
- const rootPagesDir = join12(webDir, "pages");
10822
- return existsSync17(appDir) || existsSync17(pagesDir) || existsSync17(rootAppDir) || existsSync17(rootPagesDir);
12274
+ const appDir = join13(webDir, "src", "app");
12275
+ const pagesDir = join13(webDir, "src", "pages");
12276
+ const rootAppDir = join13(webDir, "app");
12277
+ const rootPagesDir = join13(webDir, "pages");
12278
+ return existsSync19(appDir) || existsSync19(pagesDir) || existsSync19(rootAppDir) || existsSync19(rootPagesDir);
10823
12279
  }
10824
12280
  function getStandaloneServerPath(webDir) {
10825
12281
  const possiblePaths2 = [
10826
- join12(webDir, ".next", "standalone", "server.js"),
10827
- join12(webDir, ".next", "standalone", "web", "server.js")
12282
+ join13(webDir, ".next", "standalone", "server.js"),
12283
+ join13(webDir, ".next", "standalone", "web", "server.js")
10828
12284
  ];
10829
12285
  for (const serverPath of possiblePaths2) {
10830
- if (existsSync17(serverPath)) {
12286
+ if (existsSync19(serverPath)) {
10831
12287
  return serverPath;
10832
12288
  }
10833
12289
  }
@@ -10867,13 +12323,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
10867
12323
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
10868
12324
  return { process: null, port: actualPort };
10869
12325
  }
10870
- const usePnpm = existsSync17(join12(webDir, "pnpm-lock.yaml"));
10871
- const useNpm = !usePnpm && existsSync17(join12(webDir, "package-lock.json"));
12326
+ const usePnpm = existsSync19(join13(webDir, "pnpm-lock.yaml"));
12327
+ const useNpm = !usePnpm && existsSync19(join13(webDir, "package-lock.json"));
10872
12328
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
10873
- const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
12329
+ const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv2 } = process.env;
10874
12330
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
10875
12331
  const runtimeConfig = { apiBaseUrl: apiUrl };
10876
- const runtimeConfigPath = join12(webDir, "runtime-config.json");
12332
+ const runtimeConfigPath = join13(webDir, "runtime-config.json");
10877
12333
  try {
10878
12334
  writeFileSync5(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
10879
12335
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -10881,7 +12337,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
10881
12337
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
10882
12338
  }
10883
12339
  const webEnv = {
10884
- ...cleanEnv,
12340
+ ...cleanEnv2,
10885
12341
  PORT: String(actualPort)
10886
12342
  // Next.js respects PORT env var
10887
12343
  };
@@ -10995,12 +12451,28 @@ function stopWebUI() {
10995
12451
  }
10996
12452
  }
10997
12453
  async function createApp(options = {}) {
10998
- const app = new Hono6();
12454
+ const app = new Hono7();
10999
12455
  app.use("*", cors({
11000
12456
  origin: "*",
11001
12457
  // Allow all origins
11002
12458
  allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
11003
- allowHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
12459
+ allowHeaders: [
12460
+ "Content-Type",
12461
+ "Authorization",
12462
+ "X-Requested-With",
12463
+ // Personal-agent dashboard signs every request to the device with
12464
+ // these. Without them whitelisted the browser preflight strips the
12465
+ // headers and the signature middleware 401s.
12466
+ "X-Signature",
12467
+ "X-Signature-Timestamp",
12468
+ "X-Signature-Algorithm",
12469
+ "X-Device-Hwid",
12470
+ // Short-lived embed token used by the iframed SparkECoder web UI
12471
+ // when it's hosted inside the personal-agents dashboard. The
12472
+ // bootstrap in `web/src/lib/embed-bootstrap.ts` adds this header
12473
+ // to every API call.
12474
+ "X-Embed-Token"
12475
+ ],
11004
12476
  exposeHeaders: ["X-Stream-Id", "x-stream-id"],
11005
12477
  maxAge: 86400
11006
12478
  // 24 hours
@@ -11008,12 +12480,15 @@ async function createApp(options = {}) {
11008
12480
  if (!options.quiet) {
11009
12481
  app.use("*", logger());
11010
12482
  }
12483
+ app.use("*", hwidMiddleware());
12484
+ app.use("*", signatureMiddleware());
11011
12485
  app.route("/health", health);
11012
12486
  app.route("/sessions", sessions);
11013
12487
  app.route("/agents", agents);
11014
12488
  app.route("/sessions", terminals);
11015
12489
  app.route("/terminals", terminals);
11016
12490
  app.route("/tasks", tasks_default);
12491
+ app.route("/system", system);
11017
12492
  app.get("/openapi.json", async (c) => {
11018
12493
  return c.json(generateOpenAPISpec());
11019
12494
  });
@@ -11066,8 +12541,8 @@ async function startServer(options = {}) {
11066
12541
  if (options.workingDirectory) {
11067
12542
  config.resolvedWorkingDirectory = options.workingDirectory;
11068
12543
  }
11069
- if (!existsSync17(config.resolvedWorkingDirectory)) {
11070
- mkdirSync7(config.resolvedWorkingDirectory, { recursive: true });
12544
+ if (!existsSync19(config.resolvedWorkingDirectory)) {
12545
+ mkdirSync8(config.resolvedWorkingDirectory, { recursive: true });
11071
12546
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
11072
12547
  }
11073
12548
  if (!config.resolvedRemoteServer.url) {
@@ -11102,6 +12577,15 @@ async function startServer(options = {}) {
11102
12577
  port,
11103
12578
  hostname: host
11104
12579
  });
12580
+ try {
12581
+ attachPtyServer(serverInstance, {
12582
+ quiet: options.quiet
12583
+ });
12584
+ } catch (e) {
12585
+ if (!options.quiet) {
12586
+ console.warn(` \u26A0 Failed to attach /pty WebSocket server: ${e.message}`);
12587
+ }
12588
+ }
11105
12589
  let webPort;
11106
12590
  let webStarted;
11107
12591
  if (options.webUI !== false) {