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
package/dist/index.js CHANGED
@@ -27,7 +27,12 @@ var init_types = __esm({
27
27
  // Whether to always inject this skill into context (vs on-demand loading)
28
28
  alwaysApply: z.boolean().optional().default(false),
29
29
  // Glob patterns - auto-inject when working with matching files
30
- globs: z.array(z.string()).optional().default([])
30
+ globs: z.array(z.string()).optional().default([]),
31
+ // Platform requirements — skill is hidden from the model on platforms
32
+ // not listed here. Values match `process.platform`
33
+ // (darwin, linux, win32, freebsd, ...). If omitted or empty, the skill is
34
+ // available on all platforms.
35
+ platforms: z.array(z.string()).optional().default([])
31
36
  });
32
37
  TaskConfigSchema = z.object({
33
38
  enabled: z.boolean(),
@@ -45,7 +50,13 @@ var init_types = __esm({
45
50
  approvalWebhook: z.string().url().optional(),
46
51
  skillsDirectory: z.string().optional(),
47
52
  maxContextChars: z.number().optional().default(2e5),
48
- task: TaskConfigSchema.optional()
53
+ task: TaskConfigSchema.optional(),
54
+ // Anthropic computer use tool — opt-in. When true, the `computer` tool is
55
+ // included in the toolset for Anthropic models. Default false.
56
+ computerUseEnabled: z.boolean().optional(),
57
+ // Display dimensions for the computer use tool (defaults: 1280x800).
58
+ computerUseDisplayWidth: z.number().int().positive().optional(),
59
+ computerUseDisplayHeight: z.number().int().positive().optional()
49
60
  });
50
61
  VectorGatewayConfigSchema = z.object({
51
62
  // 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
  }
@@ -2493,7 +2528,7 @@ var init_recorder = __esm({
2493
2528
  import {
2494
2529
  streamText as streamText2,
2495
2530
  generateText as generateText3,
2496
- tool as tool13,
2531
+ tool as tool14,
2497
2532
  stepCountIs as stepCountIs2
2498
2533
  } from "ai";
2499
2534
 
@@ -2684,8 +2719,8 @@ var SUBAGENT_MODELS = {
2684
2719
  // src/agent/index.ts
2685
2720
  init_db();
2686
2721
  init_config();
2687
- import { z as z14 } from "zod";
2688
- import { nanoid as nanoid4 } from "nanoid";
2722
+ import { z as z15 } from "zod";
2723
+ import { nanoid as nanoid5 } from "nanoid";
2689
2724
 
2690
2725
  // src/tools/bash.ts
2691
2726
  import { tool } from "ai";
@@ -3769,12 +3804,12 @@ function findNearestRoot(startDir, markers) {
3769
3804
  }
3770
3805
  async function commandExists(cmd) {
3771
3806
  try {
3772
- const { exec: exec7 } = await import("child_process");
3773
- const { promisify: promisify7 } = await import("util");
3774
- const execAsync7 = promisify7(exec7);
3807
+ const { exec: exec8 } = await import("child_process");
3808
+ const { promisify: promisify8 } = await import("util");
3809
+ const execAsync8 = promisify8(exec8);
3775
3810
  const isWindows = process.platform === "win32";
3776
3811
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
3777
- await execAsync7(checkCmd);
3812
+ await execAsync8(checkCmd);
3778
3813
  return true;
3779
3814
  } catch {
3780
3815
  return false;
@@ -6165,6 +6200,568 @@ function createUploadFileTool(options) {
6165
6200
  });
6166
6201
  }
6167
6202
 
6203
+ // src/tools/computer-use.ts
6204
+ import { anthropic } from "@ai-sdk/anthropic";
6205
+ import { exec as exec5 } from "child_process";
6206
+ import { promisify as promisify5 } from "util";
6207
+ import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
6208
+ import { join as join8 } from "path";
6209
+ import { tmpdir } from "os";
6210
+ import { nanoid as nanoid3 } from "nanoid";
6211
+ var execAsync5 = promisify5(exec5);
6212
+ var DEFAULT_WIDTH = 1280;
6213
+ var DEFAULT_HEIGHT = 800;
6214
+ function isMacOs() {
6215
+ return process.platform === "darwin";
6216
+ }
6217
+ async function isCliclickInstalled() {
6218
+ try {
6219
+ await execAsync5("command -v cliclick", { timeout: 2e3 });
6220
+ return true;
6221
+ } catch {
6222
+ return false;
6223
+ }
6224
+ }
6225
+ async function runJxa(script) {
6226
+ try {
6227
+ const escaped = script.replace(/'/g, `'\\''`);
6228
+ const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
6229
+ timeout: 5e3
6230
+ });
6231
+ return JSON.parse(stdout.trim());
6232
+ } catch {
6233
+ return null;
6234
+ }
6235
+ }
6236
+ async function hasAccessibilityPermissions() {
6237
+ try {
6238
+ const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
6239
+ if (/accessibility privileges not enabled/i.test(stderr)) {
6240
+ return { ok: false, error: stderr.trim().split("\n")[0] };
6241
+ }
6242
+ return { ok: true };
6243
+ } catch (err) {
6244
+ return { ok: false, error: err?.message || String(err) };
6245
+ }
6246
+ }
6247
+ async function hasScreenRecordingPermissions() {
6248
+ const result = await runJxa(
6249
+ `ObjC.import("Cocoa");
6250
+ ObjC.import("CoreGraphics");
6251
+ ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
6252
+ JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
6253
+ );
6254
+ return result?.hasAccess ?? false;
6255
+ }
6256
+ async function requestAccessibilityPrompt() {
6257
+ const result = await runJxa(
6258
+ `ObjC.import("ApplicationServices");
6259
+ var key = $.kAXTrustedCheckOptionPrompt;
6260
+ var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
6261
+ var trusted = $.AXIsProcessTrustedWithOptions(dict);
6262
+ JSON.stringify({ trusted: !!trusted });`
6263
+ );
6264
+ return result?.trusted ?? false;
6265
+ }
6266
+ async function requestScreenRecordingPrompt() {
6267
+ const result = await runJxa(
6268
+ `ObjC.import("Cocoa");
6269
+ ObjC.import("CoreGraphics");
6270
+ ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
6271
+ JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
6272
+ );
6273
+ return result?.granted ?? false;
6274
+ }
6275
+ async function openSystemSettings(pane) {
6276
+ const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
6277
+ try {
6278
+ await execAsync5(`open '${url}'`, { timeout: 3e3 });
6279
+ } catch {
6280
+ }
6281
+ }
6282
+ async function detectScreenSize() {
6283
+ try {
6284
+ const { stdout } = await execAsync5(
6285
+ `osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
6286
+ { timeout: 3e3 }
6287
+ );
6288
+ const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
6289
+ if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
6290
+ const [x1, y1, x2, y2] = parts;
6291
+ return { width: x2 - x1, height: y2 - y1 };
6292
+ }
6293
+ } catch {
6294
+ }
6295
+ return null;
6296
+ }
6297
+ async function runCliclick(args) {
6298
+ const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
6299
+ const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
6300
+ timeout: 15e3,
6301
+ maxBuffer: 1024 * 1024
6302
+ });
6303
+ if (/accessibility privileges not enabled/i.test(stderr)) {
6304
+ throw new Error(
6305
+ "Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
6306
+ );
6307
+ }
6308
+ if (stderr && !stdout) throw new Error(stderr.trim());
6309
+ return (stdout || "").trim();
6310
+ }
6311
+ async function runScreencapture(path) {
6312
+ await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
6313
+ timeout: 5e3
6314
+ });
6315
+ }
6316
+ async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
6317
+ const sharpModule = await import("sharp");
6318
+ const sharp2 = sharpModule.default || sharpModule;
6319
+ const meta = await sharp2(path).metadata();
6320
+ if (meta.width === targetWidth && meta.height === targetHeight) {
6321
+ return readFileSync7(path);
6322
+ }
6323
+ return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
6324
+ }
6325
+ async function runScroll(dx, dy) {
6326
+ const wheelY = -Math.round(dy);
6327
+ const wheelX = -Math.round(dx);
6328
+ const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
6329
+ await execAsync5(
6330
+ `osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
6331
+ { timeout: 5e3 }
6332
+ );
6333
+ }
6334
+ function translateKeyForCliclick(key) {
6335
+ if (!key) return [];
6336
+ const parts = key.split("+").map((p) => p.trim()).filter(Boolean);
6337
+ if (parts.length === 0) return [];
6338
+ const modMap = {
6339
+ ctrl: "ctrl",
6340
+ control: "ctrl",
6341
+ alt: "alt",
6342
+ option: "alt",
6343
+ shift: "shift",
6344
+ cmd: "cmd",
6345
+ super: "cmd",
6346
+ meta: "cmd",
6347
+ win: "cmd",
6348
+ fn: "fn"
6349
+ };
6350
+ const keyMap = {
6351
+ return: "enter",
6352
+ enter: "enter",
6353
+ esc: "esc",
6354
+ escape: "esc",
6355
+ backspace: "delete",
6356
+ back_space: "delete",
6357
+ delete: "fwd-delete",
6358
+ fwd_delete: "fwd-delete",
6359
+ forward_delete: "fwd-delete",
6360
+ tab: "tab",
6361
+ space: "space",
6362
+ up: "arrow-up",
6363
+ arrow_up: "arrow-up",
6364
+ down: "arrow-down",
6365
+ arrow_down: "arrow-down",
6366
+ left: "arrow-left",
6367
+ arrow_left: "arrow-left",
6368
+ right: "arrow-right",
6369
+ arrow_right: "arrow-right",
6370
+ page_up: "page-up",
6371
+ pageup: "page-up",
6372
+ page_down: "page-down",
6373
+ pagedown: "page-down",
6374
+ home: "home",
6375
+ end: "end",
6376
+ f1: "f1",
6377
+ f2: "f2",
6378
+ f3: "f3",
6379
+ f4: "f4",
6380
+ f5: "f5",
6381
+ f6: "f6",
6382
+ f7: "f7",
6383
+ f8: "f8",
6384
+ f9: "f9",
6385
+ f10: "f10",
6386
+ f11: "f11",
6387
+ f12: "f12"
6388
+ };
6389
+ const modifiers = [];
6390
+ let mainKey = null;
6391
+ for (let i = 0; i < parts.length; i++) {
6392
+ const lower = parts[i].toLowerCase().replace(/-/g, "_");
6393
+ if (i < parts.length - 1 && modMap[lower]) {
6394
+ modifiers.push(modMap[lower]);
6395
+ } else {
6396
+ mainKey = keyMap[lower] || lower;
6397
+ }
6398
+ }
6399
+ const args = [];
6400
+ if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
6401
+ if (mainKey) {
6402
+ const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
6403
+ if (isNamedKey) {
6404
+ args.push(`kp:${mainKey}`);
6405
+ } else {
6406
+ args.push(`t:${mainKey}`);
6407
+ }
6408
+ }
6409
+ if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
6410
+ return args;
6411
+ }
6412
+ function modifierStringToCliclick(text) {
6413
+ return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
6414
+ if (p === "ctrl" || p === "control") return "ctrl";
6415
+ if (p === "alt" || p === "option") return "alt";
6416
+ if (p === "shift") return "shift";
6417
+ if (p === "super" || p === "meta" || p === "cmd") return "cmd";
6418
+ return "";
6419
+ }).filter(Boolean);
6420
+ }
6421
+ function createComputerUseTool(options) {
6422
+ const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
6423
+ const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
6424
+ return anthropic.tools.computer_20251124({
6425
+ displayWidthPx: displayWidth,
6426
+ displayHeightPx: displayHeight,
6427
+ enableZoom: true,
6428
+ execute: async (input) => {
6429
+ try {
6430
+ switch (input.action) {
6431
+ case "screenshot": {
6432
+ const path = join8(tmpdir(), `cu-${nanoid3(8)}.png`);
6433
+ await runScreencapture(path);
6434
+ const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
6435
+ try {
6436
+ unlinkSync2(path);
6437
+ } catch {
6438
+ }
6439
+ return { type: "image", data: resized.toString("base64") };
6440
+ }
6441
+ case "left_click": {
6442
+ const [x, y] = input.coordinate ?? [0, 0];
6443
+ if (input.text) {
6444
+ const mods = modifierStringToCliclick(input.text);
6445
+ if (mods.length > 0) {
6446
+ await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
6447
+ } else {
6448
+ await runCliclick([`c:${x},${y}`]);
6449
+ }
6450
+ } else {
6451
+ await runCliclick([`c:${x},${y}`]);
6452
+ }
6453
+ return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
6454
+ }
6455
+ case "right_click": {
6456
+ const [x, y] = input.coordinate ?? [0, 0];
6457
+ await runCliclick([`rc:${x},${y}`]);
6458
+ return `right-clicked at (${x}, ${y})`;
6459
+ }
6460
+ case "middle_click": {
6461
+ const [x, y] = input.coordinate ?? [0, 0];
6462
+ 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);`;
6463
+ await execAsync5(
6464
+ `osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
6465
+ { timeout: 3e3 }
6466
+ );
6467
+ return `middle-clicked at (${x}, ${y})`;
6468
+ }
6469
+ case "double_click": {
6470
+ const [x, y] = input.coordinate ?? [0, 0];
6471
+ await runCliclick([`dc:${x},${y}`]);
6472
+ return `double-clicked at (${x}, ${y})`;
6473
+ }
6474
+ case "triple_click": {
6475
+ const [x, y] = input.coordinate ?? [0, 0];
6476
+ await runCliclick([`tc:${x},${y}`]);
6477
+ return `triple-clicked at (${x}, ${y})`;
6478
+ }
6479
+ case "mouse_move": {
6480
+ const [x, y] = input.coordinate ?? [0, 0];
6481
+ await runCliclick([`m:${x},${y}`]);
6482
+ return `moved cursor to (${x}, ${y})`;
6483
+ }
6484
+ case "left_mouse_down": {
6485
+ const [x, y] = input.coordinate ?? [0, 0];
6486
+ await runCliclick([`dd:${x},${y}`]);
6487
+ return `left mouse button pressed at (${x}, ${y})`;
6488
+ }
6489
+ case "left_mouse_up": {
6490
+ const [x, y] = input.coordinate ?? [0, 0];
6491
+ await runCliclick([`du:${x},${y}`]);
6492
+ return `left mouse button released at (${x}, ${y})`;
6493
+ }
6494
+ case "left_click_drag": {
6495
+ const [sx, sy] = input.start_coordinate ?? [0, 0];
6496
+ const [ex, ey] = input.coordinate ?? [0, 0];
6497
+ await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
6498
+ return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
6499
+ }
6500
+ case "type": {
6501
+ const text = input.text ?? "";
6502
+ await runCliclick([`t:${text}`]);
6503
+ return `typed ${text.length} character(s)`;
6504
+ }
6505
+ case "key": {
6506
+ const args = translateKeyForCliclick(input.text ?? "");
6507
+ if (args.length === 0) return "no key specified";
6508
+ await runCliclick(args);
6509
+ return `pressed ${input.text}`;
6510
+ }
6511
+ case "hold_key": {
6512
+ const text = (input.text ?? "").toLowerCase();
6513
+ const duration = input.duration ?? 1;
6514
+ const modMap = {
6515
+ ctrl: "ctrl",
6516
+ control: "ctrl",
6517
+ alt: "alt",
6518
+ option: "alt",
6519
+ shift: "shift",
6520
+ cmd: "cmd",
6521
+ super: "cmd",
6522
+ meta: "cmd",
6523
+ fn: "fn"
6524
+ };
6525
+ const cliName = modMap[text] || text;
6526
+ await runCliclick([`kd:${cliName}`]);
6527
+ await new Promise((r) => setTimeout(r, duration * 1e3));
6528
+ await runCliclick([`ku:${cliName}`]);
6529
+ return `held ${text} for ${duration}s`;
6530
+ }
6531
+ case "scroll": {
6532
+ const direction = input.scroll_direction ?? "down";
6533
+ const amount = input.scroll_amount ?? 3;
6534
+ const px = amount * 100;
6535
+ const dx = direction === "left" ? -px : direction === "right" ? px : 0;
6536
+ const dy = direction === "up" ? -px : direction === "down" ? px : 0;
6537
+ if (input.coordinate) {
6538
+ const [x, y] = input.coordinate;
6539
+ await runCliclick([`m:${x},${y}`]);
6540
+ }
6541
+ const mods = input.text ? modifierStringToCliclick(input.text) : [];
6542
+ if (mods.length > 0) {
6543
+ await runCliclick([`kd:${mods.join(",")}`]);
6544
+ }
6545
+ await runScroll(dx, dy);
6546
+ if (mods.length > 0) {
6547
+ await runCliclick([`ku:${mods.join(",")}`]);
6548
+ }
6549
+ return `scrolled ${direction} by ${amount}`;
6550
+ }
6551
+ case "wait": {
6552
+ const duration = input.duration ?? 1;
6553
+ await new Promise((r) => setTimeout(r, duration * 1e3));
6554
+ return `waited ${duration}s`;
6555
+ }
6556
+ case "cursor_position": {
6557
+ const out = await runCliclick(["p:."]);
6558
+ return `cursor at ${out}`;
6559
+ }
6560
+ case "zoom": {
6561
+ const region = input.region ?? [0, 0, displayWidth, displayHeight];
6562
+ const [x1, y1, x2, y2] = region;
6563
+ const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid3(8)}.png`);
6564
+ await runScreencapture(tmpPath);
6565
+ const sharpModule = await import("sharp");
6566
+ const sharp2 = sharpModule.default || sharpModule;
6567
+ const meta = await sharp2(tmpPath).metadata();
6568
+ const scaleX = (meta.width || displayWidth) / displayWidth;
6569
+ const scaleY = (meta.height || displayHeight) / displayHeight;
6570
+ const px = {
6571
+ left: Math.max(0, Math.round(x1 * scaleX)),
6572
+ top: Math.max(0, Math.round(y1 * scaleY)),
6573
+ width: Math.max(1, Math.round((x2 - x1) * scaleX)),
6574
+ height: Math.max(1, Math.round((y2 - y1) * scaleY))
6575
+ };
6576
+ const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
6577
+ try {
6578
+ unlinkSync2(tmpPath);
6579
+ } catch {
6580
+ }
6581
+ return { type: "image", data: buf.toString("base64") };
6582
+ }
6583
+ default: {
6584
+ const exhaustive = input.action;
6585
+ return `unsupported action: ${String(exhaustive)}`;
6586
+ }
6587
+ }
6588
+ } catch (err) {
6589
+ const msg = err?.message || String(err);
6590
+ let hint = "";
6591
+ if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
6592
+ hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
6593
+ } else if (/command not found/i.test(msg)) {
6594
+ hint = " (Hint: install cliclick with `brew install cliclick`)";
6595
+ }
6596
+ return `Error: ${msg}${hint}`;
6597
+ }
6598
+ },
6599
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6600
+ toModelOutput({ output }) {
6601
+ if (typeof output === "string") {
6602
+ return { type: "content", value: [{ type: "text", text: output }] };
6603
+ }
6604
+ return {
6605
+ type: "content",
6606
+ value: [{ type: "media", data: output.data, mediaType: "image/png" }]
6607
+ };
6608
+ }
6609
+ });
6610
+ }
6611
+
6612
+ // src/tools/enable-computer-use.ts
6613
+ init_db();
6614
+ import { tool as tool13 } from "ai";
6615
+ import { z as z14 } from "zod";
6616
+ var inputSchema = z14.object({
6617
+ display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
6618
+ display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
6619
+ request_permissions: z14.boolean().optional().default(true).describe(
6620
+ "When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
6621
+ )
6622
+ });
6623
+ var ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
6624
+ var SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
6625
+ function createEnableComputerUseTool(options) {
6626
+ return tool13({
6627
+ 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.",
6628
+ inputSchema,
6629
+ execute: async ({ display_width, display_height, request_permissions }) => {
6630
+ try {
6631
+ if (!isMacOs()) {
6632
+ return {
6633
+ success: false,
6634
+ error: "Computer use is currently only supported on macOS.",
6635
+ platform: process.platform
6636
+ };
6637
+ }
6638
+ if (!await isCliclickInstalled()) {
6639
+ return {
6640
+ success: false,
6641
+ error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
6642
+ installCommand: "brew install cliclick",
6643
+ fixSteps: [
6644
+ "In a terminal on this Mac, run: brew install cliclick",
6645
+ "(If Homebrew is not installed, install it first from https://brew.sh)",
6646
+ "Then call enable_computer_use again"
6647
+ ]
6648
+ };
6649
+ }
6650
+ const acc = await hasAccessibilityPermissions();
6651
+ const screen = await hasScreenRecordingPermissions();
6652
+ const missing = [];
6653
+ if (!acc.ok) {
6654
+ let prompted = false;
6655
+ let panelOpened = false;
6656
+ if (request_permissions) {
6657
+ prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
6658
+ await openSystemSettings("accessibility").then(() => {
6659
+ panelOpened = true;
6660
+ }).catch(() => void 0);
6661
+ }
6662
+ missing.push({
6663
+ name: "Accessibility",
6664
+ reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
6665
+ pane: "accessibility",
6666
+ settingsUrl: ACCESSIBILITY_URL,
6667
+ fixSteps: [
6668
+ "In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
6669
+ "Click the + button",
6670
+ "Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
6671
+ "Toggle the switch ON",
6672
+ "Restart the agent process so the new permission takes effect",
6673
+ "Then call enable_computer_use again"
6674
+ ],
6675
+ prompted,
6676
+ panelOpened
6677
+ });
6678
+ }
6679
+ if (!screen) {
6680
+ let prompted = false;
6681
+ let panelOpened = false;
6682
+ if (request_permissions) {
6683
+ prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
6684
+ await openSystemSettings("screen-recording").then(() => {
6685
+ panelOpened = true;
6686
+ }).catch(() => void 0);
6687
+ }
6688
+ missing.push({
6689
+ name: "Screen Recording",
6690
+ reason: "CGPreflightScreenCaptureAccess returned false",
6691
+ pane: "screen-recording",
6692
+ settingsUrl: SCREEN_RECORDING_URL,
6693
+ fixSteps: [
6694
+ "In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
6695
+ "Click the + button",
6696
+ "Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
6697
+ "Toggle the switch ON",
6698
+ "Restart the agent process so the new permission takes effect",
6699
+ "Then call enable_computer_use again"
6700
+ ],
6701
+ prompted,
6702
+ panelOpened
6703
+ });
6704
+ }
6705
+ if (missing.length > 0) {
6706
+ return {
6707
+ success: false,
6708
+ error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
6709
+ missingPermissions: missing,
6710
+ 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."
6711
+ };
6712
+ }
6713
+ let width = display_width;
6714
+ let height = display_height;
6715
+ let detected = null;
6716
+ if (width === void 0 || height === void 0) {
6717
+ detected = await detectScreenSize();
6718
+ width = width ?? detected?.width ?? 1280;
6719
+ height = height ?? detected?.height ?? 800;
6720
+ }
6721
+ const session = await sessionQueries.getById(options.sessionId);
6722
+ if (!session) {
6723
+ return { success: false, error: "Session not found" };
6724
+ }
6725
+ const config = session.config || {};
6726
+ if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
6727
+ return {
6728
+ success: true,
6729
+ alreadyEnabled: true,
6730
+ message: "Computer use was already enabled for this session.",
6731
+ displayWidth: width,
6732
+ displayHeight: height
6733
+ };
6734
+ }
6735
+ const updated = {
6736
+ ...config,
6737
+ computerUseEnabled: true,
6738
+ computerUseDisplayWidth: width,
6739
+ computerUseDisplayHeight: height
6740
+ };
6741
+ await sessionQueries.update(options.sessionId, { config: updated });
6742
+ return {
6743
+ success: true,
6744
+ enabled: true,
6745
+ platform: "darwin",
6746
+ displayWidth: width,
6747
+ displayHeight: height,
6748
+ detectedScreenSize: detected || void 0,
6749
+ permissions: {
6750
+ accessibility: "granted",
6751
+ screenRecording: "granted"
6752
+ },
6753
+ 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.`
6754
+ };
6755
+ } catch (err) {
6756
+ return {
6757
+ success: false,
6758
+ error: err?.message || String(err)
6759
+ };
6760
+ }
6761
+ }
6762
+ });
6763
+ }
6764
+
6168
6765
  // src/tools/index.ts
6169
6766
  init_semantic();
6170
6767
  init_remote();
@@ -6213,6 +6810,20 @@ async function createTools(options) {
6213
6810
  sessionId: options.sessionId
6214
6811
  });
6215
6812
  }
6813
+ if (process.platform === "darwin") {
6814
+ if (options.enableComputerUse) {
6815
+ tools.computer = createComputerUseTool({
6816
+ workingDirectory: options.workingDirectory,
6817
+ sessionId: options.sessionId,
6818
+ displayWidth: options.computerUseDisplayWidth,
6819
+ displayHeight: options.computerUseDisplayHeight
6820
+ });
6821
+ } else {
6822
+ tools.enable_computer_use = createEnableComputerUseTool({
6823
+ sessionId: options.sessionId
6824
+ });
6825
+ }
6826
+ }
6216
6827
  if (options.enableSemanticSearch !== false) {
6217
6828
  try {
6218
6829
  if (isVectorGatewayConfigured()) {
@@ -6243,11 +6854,11 @@ init_db();
6243
6854
  init_todo();
6244
6855
  import os from "os";
6245
6856
  function getSearchInstructions() {
6246
- const platform3 = process.platform;
6857
+ const platform5 = process.platform;
6247
6858
  const common = `- **Prefer \`read_file\` over shell commands** for reading files - don't use \`cat\`, \`head\`, or \`tail\` when \`read_file\` is available
6248
6859
  - **Avoid unbounded searches** - always scope searches with glob patterns and directory paths to prevent overwhelming output
6249
6860
  - **Search strategically**: Start with specific patterns and directories, then broaden only if needed`;
6250
- if (platform3 === "win32") {
6861
+ if (platform5 === "win32") {
6251
6862
  return `${common}
6252
6863
  - **Find files**: \`dir /s /b *.ts\` or PowerShell: \`Get-ChildItem -Recurse -Filter *.ts\`
6253
6864
  - **Search content**: \`findstr /s /n "pattern" *.ts\` or PowerShell: \`Select-String -Pattern "pattern" -Path *.ts -Recurse\`
@@ -6294,13 +6905,13 @@ async function buildSystemPrompt(options) {
6294
6905
  );
6295
6906
  const hasNoTodos = todos.length === 0;
6296
6907
  const plansContext = formatPlansForContext(plans, allTodosDone || hasNoTodos);
6297
- const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
6908
+ const platform5 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
6298
6909
  const currentDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
6299
6910
  const searchInstructions = getSearchInstructions();
6300
6911
  const systemPrompt = `You are SparkECoder, an expert AI coding assistant. You help developers write, debug, and improve code.
6301
6912
 
6302
6913
  ## Environment
6303
- - **Platform**: ${platform3} (${os.release()})
6914
+ - **Platform**: ${platform5} (${os.release()})
6304
6915
  - **Date**: ${currentDate}
6305
6916
  - **Working Directory**: ${workingDirectory}
6306
6917
 
@@ -7238,10 +7849,14 @@ var Agent = class _Agent {
7238
7849
  */
7239
7850
  async createToolsWithCallbacks(options) {
7240
7851
  const config = getConfig();
7852
+ const sessionConfig = this.session.config || {};
7241
7853
  return createTools({
7242
7854
  sessionId: this.session.id,
7243
7855
  workingDirectory: this.session.workingDirectory,
7244
7856
  skillsDirectories: config.resolvedSkillsDirectories,
7857
+ enableComputerUse: sessionConfig.computerUseEnabled === true,
7858
+ computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
7859
+ computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
7245
7860
  onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
7246
7861
  onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
7247
7862
  onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
@@ -7274,10 +7889,14 @@ var Agent = class _Agent {
7274
7889
  keepRecentMessages: config.context?.keepRecentMessages || 10,
7275
7890
  autoSummarize: config.context?.autoSummarize ?? true
7276
7891
  });
7892
+ const sessionConfig = session.config || {};
7277
7893
  const tools = await createTools({
7278
7894
  sessionId: session.id,
7279
7895
  workingDirectory: session.workingDirectory,
7280
- skillsDirectories: config.resolvedSkillsDirectories
7896
+ skillsDirectories: config.resolvedSkillsDirectories,
7897
+ enableComputerUse: sessionConfig.computerUseEnabled === true,
7898
+ computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
7899
+ computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
7281
7900
  });
7282
7901
  return new _Agent(session, context, tools);
7283
7902
  }
@@ -7450,10 +8069,10 @@ ${prompt}` });
7450
8069
  const maxIterations = options.taskConfig.maxIterations ?? 50;
7451
8070
  const webhookUrl = options.taskConfig.webhookUrl;
7452
8071
  const parentTaskId = options.taskConfig.parentTaskId;
7453
- const fireWebhook = (type, data) => {
8072
+ const fireWebhook = (type2, data) => {
7454
8073
  if (!webhookUrl) return;
7455
8074
  sendWebhook(webhookUrl, {
7456
- type,
8075
+ type: type2,
7457
8076
  taskId: this.session.id,
7458
8077
  sessionId: this.session.id,
7459
8078
  ...parentTaskId ? { parentTaskId } : {},
@@ -7496,10 +8115,14 @@ ${prompt}` });
7496
8115
  });
7497
8116
  }
7498
8117
  };
8118
+ const taskSessionConfig = this.session.config || {};
7499
8119
  const taskTools = await createTools({
7500
8120
  sessionId: this.session.id,
7501
8121
  workingDirectory: this.session.workingDirectory,
7502
8122
  skillsDirectories: config.resolvedSkillsDirectories,
8123
+ enableComputerUse: taskSessionConfig.computerUseEnabled === true,
8124
+ computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
8125
+ computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
7503
8126
  onBashProgress: bashProgressHandler,
7504
8127
  onWriteFileProgress: (progress) => {
7505
8128
  options.onToolProgress?.({ toolName: "write_file", data: progress });
@@ -7782,11 +8405,11 @@ ${taskAddendum}`;
7782
8405
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7783
8406
  if (!isRemoteConfigured2()) return [];
7784
8407
  const { readFile: readFile12 } = await import("fs/promises");
7785
- const { join: join13, basename: basename6 } = await import("path");
8408
+ const { join: join14, basename: basename6 } = await import("path");
7786
8409
  const urls = [];
7787
8410
  for (const filePath of filePaths) {
7788
8411
  try {
7789
- const fullPath = filePath.startsWith("/") ? filePath : join13(this.session.workingDirectory, filePath);
8412
+ const fullPath = filePath.startsWith("/") ? filePath : join14(this.session.workingDirectory, filePath);
7790
8413
  const fileName = basename6(fullPath);
7791
8414
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
7792
8415
  const mimeMap = {
@@ -7844,11 +8467,11 @@ ${taskAddendum}`;
7844
8467
  wrappedTools[name] = originalTool;
7845
8468
  continue;
7846
8469
  }
7847
- wrappedTools[name] = tool13({
8470
+ wrappedTools[name] = tool14({
7848
8471
  description: originalTool.description || "",
7849
- inputSchema: originalTool.inputSchema || z14.object({}),
8472
+ inputSchema: originalTool.inputSchema || z15.object({}),
7850
8473
  execute: async (input, toolOptions) => {
7851
- const toolCallId = toolOptions.toolCallId || nanoid4();
8474
+ const toolCallId = toolOptions.toolCallId || nanoid5();
7852
8475
  const execution = toolExecutionQueries.create({
7853
8476
  sessionId: this.session.id,
7854
8477
  toolName: name,
@@ -7866,10 +8489,10 @@ ${taskAddendum}`;
7866
8489
  const resolverData = approvalResolvers.get(toolCallId);
7867
8490
  approvalResolvers.delete(toolCallId);
7868
8491
  this.pendingApprovals.delete(toolCallId);
7869
- const exec7 = await execution;
8492
+ const exec8 = await execution;
7870
8493
  if (!approved) {
7871
8494
  const reason = resolverData?.reason || "User rejected the tool execution";
7872
- await toolExecutionQueries.reject(exec7.id);
8495
+ await toolExecutionQueries.reject(exec8.id);
7873
8496
  await sessionQueries.updateStatus(this.session.id, "active");
7874
8497
  return {
7875
8498
  status: "rejected",
@@ -7879,14 +8502,14 @@ ${taskAddendum}`;
7879
8502
  message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
7880
8503
  };
7881
8504
  }
7882
- await toolExecutionQueries.approve(exec7.id);
8505
+ await toolExecutionQueries.approve(exec8.id);
7883
8506
  await sessionQueries.updateStatus(this.session.id, "active");
7884
8507
  try {
7885
8508
  const result = await originalTool.execute(input, toolOptions);
7886
- await toolExecutionQueries.complete(exec7.id, result);
8509
+ await toolExecutionQueries.complete(exec8.id, result);
7887
8510
  return result;
7888
8511
  } catch (error) {
7889
- await toolExecutionQueries.complete(exec7.id, null, error.message);
8512
+ await toolExecutionQueries.complete(exec8.id, null, error.message);
7890
8513
  throw error;
7891
8514
  }
7892
8515
  }
@@ -7957,12 +8580,12 @@ ${taskAddendum}`;
7957
8580
 
7958
8581
  // src/server/index.ts
7959
8582
  import "dotenv/config";
7960
- import { Hono as Hono6 } from "hono";
8583
+ import { Hono as Hono7 } from "hono";
7961
8584
  import { serve } from "@hono/node-server";
7962
8585
  import { cors } from "hono/cors";
7963
8586
  import { logger } from "hono/logger";
7964
- import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
7965
- import { resolve as resolve10, dirname as dirname7, join as join12 } from "path";
8587
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
8588
+ import { resolve as resolve10, dirname as dirname7, join as join13 } from "path";
7966
8589
  import { spawn as spawn2 } from "child_process";
7967
8590
  import { createServer as createNetServer } from "net";
7968
8591
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -7971,11 +8594,11 @@ import { fileURLToPath as fileURLToPath4 } from "url";
7971
8594
  init_db();
7972
8595
  import { Hono } from "hono";
7973
8596
  import { zValidator } from "@hono/zod-validator";
7974
- import { z as z15 } from "zod";
7975
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
8597
+ import { z as z16 } from "zod";
8598
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
7976
8599
  import { readdir as readdir6 } from "fs/promises";
7977
- import { join as join9, basename as basename5, extname as extname8, relative as relative9 } from "path";
7978
- import { nanoid as nanoid5 } from "nanoid";
8600
+ import { join as join10, basename as basename5, extname as extname8, relative as relative9 } from "path";
8601
+ import { nanoid as nanoid6 } from "nanoid";
7979
8602
  init_config();
7980
8603
 
7981
8604
  // src/server/devtools-store.ts
@@ -8007,18 +8630,20 @@ function cleanupPendingInputs() {
8007
8630
  }
8008
8631
  }
8009
8632
  }
8010
- var createSessionSchema = z15.object({
8011
- name: z15.string().optional(),
8012
- workingDirectory: z15.string().optional(),
8013
- model: z15.string().optional(),
8014
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
8633
+ var createSessionSchema = z16.object({
8634
+ name: z16.string().optional(),
8635
+ workingDirectory: z16.string().optional(),
8636
+ model: z16.string().optional(),
8637
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
8638
+ // Optional full session-config passthrough (computerUseEnabled, etc.)
8639
+ config: z16.record(z16.string(), z16.unknown()).optional()
8015
8640
  });
8016
- var paginationQuerySchema = z15.object({
8017
- limit: z15.string().optional(),
8018
- offset: z15.string().optional()
8641
+ var paginationQuerySchema = z16.object({
8642
+ limit: z16.string().optional(),
8643
+ offset: z16.string().optional()
8019
8644
  });
8020
- var messagesQuerySchema = z15.object({
8021
- limit: z15.string().optional()
8645
+ var messagesQuerySchema = z16.object({
8646
+ limit: z16.string().optional()
8022
8647
  });
8023
8648
  sessions.get(
8024
8649
  "/",
@@ -8056,11 +8681,20 @@ sessions.post(
8056
8681
  async (c) => {
8057
8682
  const body = c.req.valid("json");
8058
8683
  const config = getConfig();
8684
+ const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
8685
+ const baseConfig = body.config || {};
8686
+ const mergedConfig = {
8687
+ ...baseConfig,
8688
+ ...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
8689
+ // Turn on computer use by default if the server was launched with --enable-computer-use,
8690
+ // unless the client explicitly provided a value.
8691
+ ...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
8692
+ };
8059
8693
  const agent = await Agent.create({
8060
8694
  name: body.name,
8061
8695
  workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
8062
8696
  model: body.model || config.defaultModel,
8063
- sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
8697
+ sessionConfig: Object.keys(mergedConfig).length > 0 ? mergedConfig : void 0
8064
8698
  });
8065
8699
  const session = agent.getSession();
8066
8700
  return c.json({
@@ -8157,10 +8791,10 @@ sessions.get("/:id/tools", async (c) => {
8157
8791
  count: executions.length
8158
8792
  });
8159
8793
  });
8160
- var updateSessionSchema = z15.object({
8161
- model: z15.string().optional(),
8162
- name: z15.string().optional(),
8163
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
8794
+ var updateSessionSchema = z16.object({
8795
+ model: z16.string().optional(),
8796
+ name: z16.string().optional(),
8797
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
8164
8798
  });
8165
8799
  sessions.patch(
8166
8800
  "/:id",
@@ -8230,8 +8864,8 @@ sessions.post("/:id/clear", async (c) => {
8230
8864
  await agent.clearContext();
8231
8865
  return c.json({ success: true, sessionId: id });
8232
8866
  });
8233
- var pendingInputSchema = z15.object({
8234
- text: z15.string()
8867
+ var pendingInputSchema = z16.object({
8868
+ text: z16.string()
8235
8869
  });
8236
8870
  sessions.post(
8237
8871
  "/:id/pending-input",
@@ -8262,13 +8896,13 @@ sessions.get("/:id/pending-input", async (c) => {
8262
8896
  createdAt: pending.createdAt.toISOString()
8263
8897
  });
8264
8898
  });
8265
- var devtoolsContextSchema = z15.object({
8266
- url: z15.string(),
8267
- path: z15.string(),
8268
- pageName: z15.string().optional(),
8269
- screenWidth: z15.number().optional(),
8270
- screenHeight: z15.number().optional(),
8271
- devicePixelRatio: z15.number().optional()
8899
+ var devtoolsContextSchema = z16.object({
8900
+ url: z16.string(),
8901
+ path: z16.string(),
8902
+ pageName: z16.string().optional(),
8903
+ screenWidth: z16.number().optional(),
8904
+ screenHeight: z16.number().optional(),
8905
+ devicePixelRatio: z16.number().optional()
8272
8906
  });
8273
8907
  sessions.post(
8274
8908
  "/:id/devtools-context",
@@ -8454,12 +9088,12 @@ sessions.get("/:id/diff/:filePath", async (c) => {
8454
9088
  });
8455
9089
  function getAttachmentsDir(sessionId) {
8456
9090
  const appDataDir = getAppDataDirectory();
8457
- return join9(appDataDir, "attachments", sessionId);
9091
+ return join10(appDataDir, "attachments", sessionId);
8458
9092
  }
8459
9093
  function ensureAttachmentsDir(sessionId) {
8460
9094
  const dir = getAttachmentsDir(sessionId);
8461
- if (!existsSync15(dir)) {
8462
- mkdirSync5(dir, { recursive: true });
9095
+ if (!existsSync16(dir)) {
9096
+ mkdirSync6(dir, { recursive: true });
8463
9097
  }
8464
9098
  return dir;
8465
9099
  }
@@ -8470,12 +9104,12 @@ sessions.get("/:id/attachments", async (c) => {
8470
9104
  return c.json({ error: "Session not found" }, 404);
8471
9105
  }
8472
9106
  const dir = getAttachmentsDir(sessionId);
8473
- if (!existsSync15(dir)) {
9107
+ if (!existsSync16(dir)) {
8474
9108
  return c.json({ sessionId, attachments: [], count: 0 });
8475
9109
  }
8476
9110
  const files = readdirSync2(dir);
8477
9111
  const attachments = files.map((filename) => {
8478
- const filePath = join9(dir, filename);
9112
+ const filePath = join10(dir, filename);
8479
9113
  const stats = statSync2(filePath);
8480
9114
  return {
8481
9115
  id: filename.split("_")[0],
@@ -8507,10 +9141,10 @@ sessions.post("/:id/attachments", async (c) => {
8507
9141
  return c.json({ error: "No file provided" }, 400);
8508
9142
  }
8509
9143
  const dir = ensureAttachmentsDir(sessionId);
8510
- const id = nanoid5(10);
9144
+ const id = nanoid6(10);
8511
9145
  const ext = extname8(file.name) || "";
8512
9146
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
8513
- const filePath = join9(dir, safeFilename);
9147
+ const filePath = join10(dir, safeFilename);
8514
9148
  const arrayBuffer = await file.arrayBuffer();
8515
9149
  writeFileSync3(filePath, Buffer.from(arrayBuffer));
8516
9150
  return c.json({
@@ -8533,10 +9167,10 @@ sessions.post("/:id/attachments", async (c) => {
8533
9167
  return c.json({ error: "Missing filename or data" }, 400);
8534
9168
  }
8535
9169
  const dir = ensureAttachmentsDir(sessionId);
8536
- const id = nanoid5(10);
9170
+ const id = nanoid6(10);
8537
9171
  const ext = extname8(body.filename) || "";
8538
9172
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
8539
- const filePath = join9(dir, safeFilename);
9173
+ const filePath = join10(dir, safeFilename);
8540
9174
  let base64Data = body.data;
8541
9175
  if (base64Data.includes(",")) {
8542
9176
  base64Data = base64Data.split(",")[1];
@@ -8565,7 +9199,7 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
8565
9199
  return c.json({ error: "Session not found" }, 404);
8566
9200
  }
8567
9201
  const dir = getAttachmentsDir(sessionId);
8568
- if (!existsSync15(dir)) {
9202
+ if (!existsSync16(dir)) {
8569
9203
  return c.json({ error: "Attachment not found" }, 404);
8570
9204
  }
8571
9205
  const files = readdirSync2(dir);
@@ -8573,14 +9207,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
8573
9207
  if (!file) {
8574
9208
  return c.json({ error: "Attachment not found" }, 404);
8575
9209
  }
8576
- const filePath = join9(dir, file);
8577
- unlinkSync2(filePath);
9210
+ const filePath = join10(dir, file);
9211
+ unlinkSync3(filePath);
8578
9212
  return c.json({ success: true, id: attachmentId });
8579
9213
  });
8580
- var filesQuerySchema = z15.object({
8581
- query: z15.string().optional(),
9214
+ var filesQuerySchema = z16.object({
9215
+ query: z16.string().optional(),
8582
9216
  // Filter query (e.g., "src/com" to match "src/components")
8583
- limit: z15.string().optional()
9217
+ limit: z16.string().optional()
8584
9218
  // Max results (default 50)
8585
9219
  });
8586
9220
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -8656,7 +9290,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
8656
9290
  const entries = await readdir6(currentDir, { withFileTypes: true });
8657
9291
  for (const entry of entries) {
8658
9292
  if (results.length >= limit * 2) break;
8659
- const fullPath = join9(currentDir, entry.name);
9293
+ const fullPath = join10(currentDir, entry.name);
8660
9294
  const relativePath = relative9(baseDir, fullPath);
8661
9295
  if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
8662
9296
  continue;
@@ -8704,7 +9338,7 @@ sessions.get(
8704
9338
  return c.json({ error: "Session not found" }, 404);
8705
9339
  }
8706
9340
  const workingDirectory = session.workingDirectory;
8707
- if (!existsSync15(workingDirectory)) {
9341
+ if (!existsSync16(workingDirectory)) {
8708
9342
  return c.json({
8709
9343
  sessionId,
8710
9344
  workingDirectory,
@@ -8814,9 +9448,9 @@ sessions.get("/:id/browser-recording", async (c) => {
8814
9448
  init_db();
8815
9449
  import { Hono as Hono2 } from "hono";
8816
9450
  import { zValidator as zValidator2 } from "@hono/zod-validator";
8817
- import { z as z16 } from "zod";
8818
- import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
8819
- import { join as join10 } from "path";
9451
+ import { z as z17 } from "zod";
9452
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
9453
+ import { join as join11 } from "path";
8820
9454
  init_config();
8821
9455
 
8822
9456
  // src/server/resumable-stream.ts
@@ -8903,7 +9537,7 @@ var streamContext = createResumableStreamContext({
8903
9537
  });
8904
9538
 
8905
9539
  // src/server/routes/agents.ts
8906
- import { nanoid as nanoid6 } from "nanoid";
9540
+ import { nanoid as nanoid7 } from "nanoid";
8907
9541
  init_stream_proxy();
8908
9542
  init_recorder();
8909
9543
  init_remote();
@@ -8994,40 +9628,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
8994
9628
  ${prompt}`;
8995
9629
  }
8996
9630
  var agents = new Hono2();
8997
- var attachmentSchema = z16.object({
8998
- type: z16.enum(["image", "file"]),
8999
- data: z16.string(),
9631
+ var attachmentSchema = z17.object({
9632
+ type: z17.enum(["image", "file"]),
9633
+ data: z17.string(),
9000
9634
  // base64 data URL or raw base64
9001
- mediaType: z16.string().optional(),
9002
- filename: z16.string().optional()
9635
+ mediaType: z17.string().optional(),
9636
+ filename: z17.string().optional()
9003
9637
  });
9004
- var runPromptSchema = z16.object({
9005
- prompt: z16.string(),
9638
+ var runPromptSchema = z17.object({
9639
+ prompt: z17.string(),
9006
9640
  // Can be empty if attachments are provided
9007
- attachments: z16.array(attachmentSchema).optional()
9641
+ attachments: z17.array(attachmentSchema).optional()
9008
9642
  }).refine(
9009
9643
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
9010
9644
  { message: "Either prompt or attachments must be provided" }
9011
9645
  );
9012
- var quickStartSchema = z16.object({
9013
- prompt: z16.string().min(1),
9014
- name: z16.string().optional(),
9015
- workingDirectory: z16.string().optional(),
9016
- model: z16.string().optional(),
9017
- toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
9646
+ var quickStartSchema = z17.object({
9647
+ prompt: z17.string().min(1),
9648
+ name: z17.string().optional(),
9649
+ workingDirectory: z17.string().optional(),
9650
+ model: z17.string().optional(),
9651
+ toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
9018
9652
  });
9019
- var rejectSchema = z16.object({
9020
- reason: z16.string().optional()
9653
+ var rejectSchema = z17.object({
9654
+ reason: z17.string().optional()
9021
9655
  }).optional();
9022
9656
  var streamAbortControllers = /* @__PURE__ */ new Map();
9023
9657
  function getAttachmentsDirectory(sessionId) {
9024
9658
  const appDataDir = getAppDataDirectory();
9025
- return join10(appDataDir, "attachments", sessionId);
9659
+ return join11(appDataDir, "attachments", sessionId);
9026
9660
  }
9027
9661
  async function saveAttachmentToDisk(sessionId, attachment, index) {
9028
9662
  const attachmentsDir = getAttachmentsDirectory(sessionId);
9029
- if (!existsSync16(attachmentsDir)) {
9030
- mkdirSync6(attachmentsDir, { recursive: true });
9663
+ if (!existsSync17(attachmentsDir)) {
9664
+ mkdirSync7(attachmentsDir, { recursive: true });
9031
9665
  }
9032
9666
  let filename = attachment.filename;
9033
9667
  if (!filename) {
@@ -9045,7 +9679,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
9045
9679
  attachment.mediaType = resized.mediaType;
9046
9680
  attachment.data = buffer.toString("base64");
9047
9681
  }
9048
- const filePath = join10(attachmentsDir, filename);
9682
+ const filePath = join11(attachmentsDir, filename);
9049
9683
  writeFileSync4(filePath, buffer);
9050
9684
  return filePath;
9051
9685
  }
@@ -9056,9 +9690,9 @@ function stripDataUrlPrefix2(data) {
9056
9690
  }
9057
9691
  return data;
9058
9692
  }
9059
- function getExtensionFromMediaType(mediaType, type) {
9693
+ function getExtensionFromMediaType(mediaType, type2) {
9060
9694
  if (!mediaType) {
9061
- return type === "image" ? ".png" : ".bin";
9695
+ return type2 === "image" ? ".png" : ".bin";
9062
9696
  }
9063
9697
  const mimeToExt = {
9064
9698
  "image/png": ".png",
@@ -9462,7 +10096,7 @@ ${prompt}` });
9462
10096
  userMessageContent = prompt;
9463
10097
  }
9464
10098
  await messageQueries.create(id, { role: "user", content: userMessageContent });
9465
- const streamId = `stream_${id}_${nanoid6(10)}`;
10099
+ const streamId = `stream_${id}_${nanoid7(10)}`;
9466
10100
  console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
9467
10101
  await activeStreamQueries.create(id, streamId);
9468
10102
  const stream = await streamContext.resumableStream(
@@ -9667,7 +10301,7 @@ agents.post(
9667
10301
  });
9668
10302
  const session = agent.getSession();
9669
10303
  const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
9670
- const streamId = `stream_${session.id}_${nanoid6(10)}`;
10304
+ const streamId = `stream_${session.id}_${nanoid7(10)}`;
9671
10305
  await createCheckpoint(session.id, session.workingDirectory, 0);
9672
10306
  await activeStreamQueries.create(session.id, streamId);
9673
10307
  const createQuickStreamProducer = () => {
@@ -9934,23 +10568,23 @@ agents.post(
9934
10568
  });
9935
10569
  }
9936
10570
  );
9937
- var browserInputSchema = z16.object({
9938
- type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
9939
- eventType: z16.string(),
9940
- x: z16.number().optional(),
9941
- y: z16.number().optional(),
9942
- button: z16.string().optional(),
9943
- clickCount: z16.number().optional(),
9944
- deltaX: z16.number().optional(),
9945
- deltaY: z16.number().optional(),
9946
- key: z16.string().optional(),
9947
- code: z16.string().optional(),
9948
- text: z16.string().optional(),
9949
- modifiers: z16.number().optional(),
9950
- touchPoints: z16.array(z16.object({
9951
- x: z16.number(),
9952
- y: z16.number(),
9953
- id: z16.number().optional()
10571
+ var browserInputSchema = z17.object({
10572
+ type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
10573
+ eventType: z17.string(),
10574
+ x: z17.number().optional(),
10575
+ y: z17.number().optional(),
10576
+ button: z17.string().optional(),
10577
+ clickCount: z17.number().optional(),
10578
+ deltaX: z17.number().optional(),
10579
+ deltaY: z17.number().optional(),
10580
+ key: z17.string().optional(),
10581
+ code: z17.string().optional(),
10582
+ text: z17.string().optional(),
10583
+ modifiers: z17.number().optional(),
10584
+ touchPoints: z17.array(z17.object({
10585
+ x: z17.number(),
10586
+ y: z17.number(),
10587
+ id: z17.number().optional()
9954
10588
  })).optional()
9955
10589
  });
9956
10590
  agents.post(
@@ -9985,27 +10619,279 @@ agents.get("/:id/browser-stream", async (c) => {
9985
10619
  init_config();
9986
10620
  import { Hono as Hono3 } from "hono";
9987
10621
  import { zValidator as zValidator3 } from "@hono/zod-validator";
9988
- import { z as z17 } from "zod";
9989
- import { readFileSync as readFileSync7 } from "fs";
10622
+ import { z as z18 } from "zod";
10623
+ import { readFileSync as readFileSync10 } from "fs";
9990
10624
  import { fileURLToPath as fileURLToPath3 } from "url";
9991
- import { dirname as dirname6, join as join11 } from "path";
10625
+ import { dirname as dirname6, join as join12 } from "path";
10626
+
10627
+ // src/personal-agent/heartbeat.ts
10628
+ import { execSync as execSync3 } from "child_process";
10629
+ import { readFileSync as readFileSync9 } from "fs";
10630
+ import { hostname as hostname2, platform as platform3 } from "os";
10631
+
10632
+ // src/personal-agent/system-metrics.ts
10633
+ import { execSync as execSync2 } from "child_process";
10634
+ import { readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
10635
+ import {
10636
+ arch,
10637
+ cpus,
10638
+ freemem,
10639
+ hostname,
10640
+ loadavg,
10641
+ networkInterfaces,
10642
+ platform as platform2,
10643
+ release,
10644
+ totalmem,
10645
+ type,
10646
+ uptime,
10647
+ userInfo
10648
+ } from "os";
10649
+ var _lastSample = null;
10650
+ function snapshotCpuTimes() {
10651
+ const sum = { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 };
10652
+ for (const c of cpus()) {
10653
+ sum.user += c.times.user;
10654
+ sum.nice += c.times.nice;
10655
+ sum.sys += c.times.sys;
10656
+ sum.idle += c.times.idle;
10657
+ sum.irq += c.times.irq;
10658
+ }
10659
+ return sum;
10660
+ }
10661
+ function readCpuUsage() {
10662
+ const now = snapshotCpuTimes();
10663
+ if (!_lastSample) {
10664
+ _lastSample = now;
10665
+ return 0;
10666
+ }
10667
+ const dUser = now.user - _lastSample.user;
10668
+ const dNice = now.nice - _lastSample.nice;
10669
+ const dSys = now.sys - _lastSample.sys;
10670
+ const dIdle = now.idle - _lastSample.idle;
10671
+ const dIrq = now.irq - _lastSample.irq;
10672
+ const total = dUser + dNice + dSys + dIdle + dIrq;
10673
+ _lastSample = now;
10674
+ if (total <= 0) return 0;
10675
+ return Math.max(0, Math.min(1, (total - dIdle) / total));
10676
+ }
10677
+ snapshotCpuTimes();
10678
+ function readCpuTempC() {
10679
+ try {
10680
+ if (platform2() === "linux") {
10681
+ let hottest = -Infinity;
10682
+ try {
10683
+ for (const entry of readdirSync3("/sys/class/thermal")) {
10684
+ if (!entry.startsWith("thermal_zone")) continue;
10685
+ try {
10686
+ const v = Number(
10687
+ readFileSync8(`/sys/class/thermal/${entry}/temp`, "utf8").trim()
10688
+ );
10689
+ if (Number.isFinite(v) && v > hottest) hottest = v;
10690
+ } catch {
10691
+ }
10692
+ }
10693
+ } catch {
10694
+ }
10695
+ if (hottest > -Infinity) return hottest / 1e3;
10696
+ }
10697
+ const overrideCmd = process.env.PERSONAL_AGENT_TEMP_CMD;
10698
+ if (overrideCmd) {
10699
+ const out = execSync2(overrideCmd, { encoding: "utf8", timeout: 1500 }).trim();
10700
+ const v = Number(out);
10701
+ if (Number.isFinite(v)) return v;
10702
+ }
10703
+ } catch {
10704
+ }
10705
+ return void 0;
10706
+ }
10707
+ function readDisks() {
10708
+ try {
10709
+ const p = platform2();
10710
+ if (p === "darwin" || p === "linux") {
10711
+ const raw = execSync2("df -kP", { encoding: "utf8", timeout: 2e3 });
10712
+ const lines = raw.trim().split("\n").slice(1);
10713
+ const out = [];
10714
+ for (const line of lines) {
10715
+ const parts = line.trim().split(/\s+/);
10716
+ if (parts.length < 6) continue;
10717
+ const filesystem = parts[0];
10718
+ const total1k = Number(parts[1]);
10719
+ const used1k = Number(parts[2]);
10720
+ const free1k = Number(parts[3]);
10721
+ const mount = parts.slice(5).join(" ");
10722
+ if (!Number.isFinite(total1k) || total1k <= 0) continue;
10723
+ 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")) {
10724
+ if (mount !== "/") continue;
10725
+ }
10726
+ out.push({
10727
+ mount,
10728
+ filesystem,
10729
+ totalBytes: total1k * 1024,
10730
+ usedBytes: used1k * 1024,
10731
+ freeBytes: free1k * 1024,
10732
+ usage: total1k > 0 ? used1k / total1k : 0
10733
+ });
10734
+ }
10735
+ out.sort((a, b) => {
10736
+ const score = (m) => m === "/" ? 0 : m.startsWith("/Users") || m.startsWith("/home") ? 1 : 2;
10737
+ return score(a.mount) - score(b.mount);
10738
+ });
10739
+ return out.slice(0, 6);
10740
+ }
10741
+ if (p === "win32") {
10742
+ const raw = execSync2(
10743
+ "wmic logicaldisk get DeviceID,Size,FreeSpace /format:csv",
10744
+ { encoding: "utf8", timeout: 3e3 }
10745
+ );
10746
+ const out = [];
10747
+ for (const line of raw.trim().split(/\r?\n/).slice(1)) {
10748
+ const cols = line.split(",");
10749
+ if (cols.length < 4) continue;
10750
+ const [, deviceId, freeStr, sizeStr] = cols;
10751
+ const total = Number(sizeStr);
10752
+ const free = Number(freeStr);
10753
+ if (!Number.isFinite(total) || total <= 0) continue;
10754
+ const used = Math.max(0, total - free);
10755
+ out.push({
10756
+ mount: deviceId,
10757
+ totalBytes: total,
10758
+ usedBytes: used,
10759
+ freeBytes: free,
10760
+ usage: used / total
10761
+ });
10762
+ }
10763
+ return out;
10764
+ }
10765
+ } catch {
10766
+ }
10767
+ return void 0;
10768
+ }
10769
+ function readNetwork() {
10770
+ try {
10771
+ const out = [];
10772
+ const ifaces = networkInterfaces();
10773
+ for (const [name, addrs] of Object.entries(ifaces)) {
10774
+ if (!addrs) continue;
10775
+ for (const a of addrs) {
10776
+ if (a.internal) continue;
10777
+ out.push({
10778
+ iface: name,
10779
+ family: a.family,
10780
+ address: a.address,
10781
+ mac: a.mac,
10782
+ internal: a.internal
10783
+ });
10784
+ }
10785
+ }
10786
+ return out;
10787
+ } catch {
10788
+ return void 0;
10789
+ }
10790
+ }
10791
+ function readSystemMetrics() {
10792
+ const cpuList = cpus();
10793
+ const usage = readCpuUsage();
10794
+ const tot = totalmem();
10795
+ const free = freemem();
10796
+ const metrics = {
10797
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
10798
+ hostname: hostname(),
10799
+ platform: platform2(),
10800
+ arch: arch(),
10801
+ kernelRelease: release(),
10802
+ osType: type(),
10803
+ processUptimeSec: Math.round(process.uptime()),
10804
+ systemUptimeSec: Math.round(uptime()),
10805
+ user: safeUser(),
10806
+ cpu: cpuList[0] ? {
10807
+ model: cpuList[0].model,
10808
+ count: cpuList.length,
10809
+ speedMhz: cpuList[0].speed,
10810
+ loadAvg1: loadavg()[0],
10811
+ loadAvg5: loadavg()[1],
10812
+ loadAvg15: loadavg()[2],
10813
+ usage,
10814
+ tempC: readCpuTempC()
10815
+ } : void 0,
10816
+ memory: {
10817
+ totalBytes: tot,
10818
+ freeBytes: free,
10819
+ usedBytes: Math.max(0, tot - free),
10820
+ usage: tot > 0 ? (tot - free) / tot : 0
10821
+ },
10822
+ disks: readDisks(),
10823
+ network: readNetwork()
10824
+ };
10825
+ return metrics;
10826
+ }
10827
+ function safeUser() {
10828
+ try {
10829
+ return userInfo().username;
10830
+ } catch {
10831
+ return process.env.USER ?? process.env.USERNAME ?? "unknown";
10832
+ }
10833
+ }
10834
+
10835
+ // src/personal-agent/heartbeat.ts
10836
+ var _cachedHwid = null;
10837
+ function getHardwareIdCached() {
10838
+ if (_cachedHwid !== null) return _cachedHwid;
10839
+ _cachedHwid = getHardwareId();
10840
+ return _cachedHwid;
10841
+ }
10842
+ function getHardwareId() {
10843
+ const p = platform3();
10844
+ try {
10845
+ if (p === "darwin") {
10846
+ const out = execSync3(
10847
+ `ioreg -rd1 -c IOPlatformExpertDevice | awk -F\\" '/IOPlatformUUID/ {print $4}'`,
10848
+ { encoding: "utf8", timeout: 2e3 }
10849
+ ).trim();
10850
+ if (out) return normalize2(out);
10851
+ } else if (p === "linux") {
10852
+ try {
10853
+ return normalize2(readFileSync9("/etc/machine-id", "utf8").trim());
10854
+ } catch {
10855
+ return normalize2(readFileSync9("/var/lib/dbus/machine-id", "utf8").trim());
10856
+ }
10857
+ } else if (p === "win32") {
10858
+ const out = execSync3("wmic csproduct get uuid /value", {
10859
+ encoding: "utf8",
10860
+ timeout: 3e3
10861
+ });
10862
+ const m = out.match(/UUID=([\w-]+)/i);
10863
+ if (m && m[1]) return normalize2(m[1]);
10864
+ }
10865
+ } catch (e) {
10866
+ console.warn(`[personal-agent] could not read hardware UUID: ${e.message}`);
10867
+ }
10868
+ console.warn(
10869
+ "[personal-agent] falling back to hostname for HWID; this is NOT stable across rename/reinstall"
10870
+ );
10871
+ return `host-${hostname2()}`;
10872
+ }
10873
+ function normalize2(raw) {
10874
+ return raw.trim().toLowerCase().replace(/[^a-z0-9-]/g, "");
10875
+ }
10876
+
10877
+ // src/server/routes/health.ts
9992
10878
  var __filename = fileURLToPath3(import.meta.url);
9993
10879
  var __dirname = dirname6(__filename);
9994
10880
  var possiblePaths = [
9995
- join11(__dirname, "../package.json"),
10881
+ join12(__dirname, "../package.json"),
9996
10882
  // From dist/server -> dist/../package.json
9997
- join11(__dirname, "../../package.json"),
10883
+ join12(__dirname, "../../package.json"),
9998
10884
  // From dist/server (if nested differently)
9999
- join11(__dirname, "../../../package.json"),
10885
+ join12(__dirname, "../../../package.json"),
10000
10886
  // From src/server/routes (development)
10001
- join11(process.cwd(), "package.json")
10887
+ join12(process.cwd(), "package.json")
10002
10888
  // From current working directory
10003
10889
  ];
10004
10890
  var currentVersion = "0.0.0";
10005
10891
  var packageName = "sparkecoder";
10006
10892
  for (const packageJsonPath of possiblePaths) {
10007
10893
  try {
10008
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
10894
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
10009
10895
  if (packageJson.name === "sparkecoder") {
10010
10896
  currentVersion = packageJson.version || "0.0.0";
10011
10897
  packageName = packageJson.name || "sparkecoder";
@@ -10020,11 +10906,17 @@ health.get("/", async (c) => {
10020
10906
  const apiKeyStatus = getApiKeyStatus();
10021
10907
  const gatewayKey = apiKeyStatus.find((s) => s.provider === "ai-gateway");
10022
10908
  const hasApiKey = gatewayKey?.configured ?? false;
10909
+ let hwid;
10910
+ try {
10911
+ hwid = getHardwareIdCached();
10912
+ } catch {
10913
+ }
10023
10914
  return c.json({
10024
10915
  status: "ok",
10025
10916
  version: currentVersion,
10026
10917
  uptime: process.uptime(),
10027
10918
  apiKeyConfigured: hasApiKey,
10919
+ hwid,
10028
10920
  config: {
10029
10921
  workingDirectory: config.resolvedWorkingDirectory,
10030
10922
  defaultModel: config.defaultModel,
@@ -10095,9 +10987,9 @@ health.get("/api-keys", async (c) => {
10095
10987
  supportedProviders: SUPPORTED_PROVIDERS
10096
10988
  });
10097
10989
  });
10098
- var setApiKeySchema = z17.object({
10099
- provider: z17.string(),
10100
- apiKey: z17.string().min(1)
10990
+ var setApiKeySchema = z18.object({
10991
+ provider: z18.string(),
10992
+ apiKey: z18.string().min(1)
10101
10993
  });
10102
10994
  health.post(
10103
10995
  "/api-keys",
@@ -10136,13 +11028,13 @@ health.delete("/api-keys/:provider", async (c) => {
10136
11028
  // src/server/routes/terminals.ts
10137
11029
  import { Hono as Hono4 } from "hono";
10138
11030
  import { zValidator as zValidator4 } from "@hono/zod-validator";
10139
- import { z as z18 } from "zod";
11031
+ import { z as z19 } from "zod";
10140
11032
  init_db();
10141
11033
  var terminals = new Hono4();
10142
- var spawnSchema = z18.object({
10143
- command: z18.string(),
10144
- cwd: z18.string().optional(),
10145
- name: z18.string().optional()
11034
+ var spawnSchema = z19.object({
11035
+ command: z19.string(),
11036
+ cwd: z19.string().optional(),
11037
+ name: z19.string().optional()
10146
11038
  });
10147
11039
  terminals.post(
10148
11040
  "/:sessionId/terminals",
@@ -10223,8 +11115,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
10223
11115
  // We don't track exit codes in tmux mode
10224
11116
  });
10225
11117
  });
10226
- var logsQuerySchema = z18.object({
10227
- tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
11118
+ var logsQuerySchema = z19.object({
11119
+ tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
10228
11120
  });
10229
11121
  terminals.get(
10230
11122
  "/:sessionId/terminals/:terminalId/logs",
@@ -10248,8 +11140,8 @@ terminals.get(
10248
11140
  });
10249
11141
  }
10250
11142
  );
10251
- var killSchema = z18.object({
10252
- signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
11143
+ var killSchema = z19.object({
11144
+ signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
10253
11145
  });
10254
11146
  terminals.post(
10255
11147
  "/:sessionId/terminals/:terminalId/kill",
@@ -10263,8 +11155,8 @@ terminals.post(
10263
11155
  return c.json({ success: true, message: "Terminal killed" });
10264
11156
  }
10265
11157
  );
10266
- var writeSchema = z18.object({
10267
- input: z18.string()
11158
+ var writeSchema = z19.object({
11159
+ input: z19.string()
10268
11160
  });
10269
11161
  terminals.post(
10270
11162
  "/:sessionId/terminals/:terminalId/write",
@@ -10449,20 +11341,20 @@ data: ${JSON.stringify({ status: "stopped" })}
10449
11341
  init_db();
10450
11342
  import { Hono as Hono5 } from "hono";
10451
11343
  import { zValidator as zValidator5 } from "@hono/zod-validator";
10452
- import { z as z19 } from "zod";
10453
- import { nanoid as nanoid7 } from "nanoid";
11344
+ import { z as z20 } from "zod";
11345
+ import { nanoid as nanoid8 } from "nanoid";
10454
11346
  init_config();
10455
11347
  var tasks = new Hono5();
10456
11348
  var taskAbortControllers = /* @__PURE__ */ new Map();
10457
- var createTaskSchema = z19.object({
10458
- prompt: z19.string().min(1),
10459
- outputSchema: z19.record(z19.string(), z19.unknown()),
10460
- webhookUrl: z19.string().url().optional(),
10461
- model: z19.string().optional(),
10462
- workingDirectory: z19.string().optional(),
10463
- name: z19.string().optional(),
10464
- maxIterations: z19.number().int().min(1).max(500).optional(),
10465
- parentTaskId: z19.string().optional()
11349
+ var createTaskSchema = z20.object({
11350
+ prompt: z20.string().min(1),
11351
+ outputSchema: z20.record(z20.string(), z20.unknown()),
11352
+ webhookUrl: z20.string().url().optional(),
11353
+ model: z20.string().optional(),
11354
+ workingDirectory: z20.string().optional(),
11355
+ name: z20.string().optional(),
11356
+ maxIterations: z20.number().int().min(1).max(500).optional(),
11357
+ parentTaskId: z20.string().optional()
10466
11358
  });
10467
11359
  tasks.post(
10468
11360
  "/",
@@ -10524,7 +11416,7 @@ tasks.post(
10524
11416
  const taskId = agent.sessionId;
10525
11417
  const abortController = new AbortController();
10526
11418
  taskAbortControllers.set(taskId, abortController);
10527
- const streamId = `stream_${taskId}_${nanoid7(10)}`;
11419
+ const streamId = `stream_${taskId}_${nanoid8(10)}`;
10528
11420
  await activeStreamQueries.create(taskId, streamId);
10529
11421
  const taskStreamProducer = () => {
10530
11422
  const { readable, writable } = new TransformStream();
@@ -10674,17 +11566,581 @@ tasks.post("/:id/cancel", async (c) => {
10674
11566
  });
10675
11567
  var tasks_default = tasks;
10676
11568
 
11569
+ // src/server/routes/system.ts
11570
+ import { Hono as Hono6 } from "hono";
11571
+ import { streamSSE } from "hono/streaming";
11572
+ var system = new Hono6();
11573
+ system.get("/metrics", (c) => {
11574
+ return c.json(readSystemMetrics());
11575
+ });
11576
+ system.get("/metrics/stream", (c) => {
11577
+ return streamSSE(c, async (stream) => {
11578
+ const intervalMs = Number(c.req.query("intervalMs") ?? 2e3);
11579
+ const safeMs = Number.isFinite(intervalMs) ? Math.max(500, Math.min(6e4, intervalMs)) : 2e3;
11580
+ let id = 0;
11581
+ let aborted = false;
11582
+ c.req.raw.signal.addEventListener("abort", () => {
11583
+ aborted = true;
11584
+ });
11585
+ while (!aborted) {
11586
+ try {
11587
+ const snap = readSystemMetrics();
11588
+ await stream.writeSSE({
11589
+ id: String(id++),
11590
+ event: "metrics",
11591
+ data: JSON.stringify(snap)
11592
+ });
11593
+ } catch (e) {
11594
+ await stream.writeSSE({
11595
+ id: String(id++),
11596
+ event: "error",
11597
+ data: JSON.stringify({ error: e.message })
11598
+ });
11599
+ }
11600
+ await stream.sleep(safeMs);
11601
+ }
11602
+ });
11603
+ });
11604
+
11605
+ // src/personal-agent/hwid-middleware.ts
11606
+ var SKIP_PATHS = /* @__PURE__ */ new Set(["/health", "/health/", "/health/ready", "/health/version"]);
11607
+ function personalAgentConfigured() {
11608
+ 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);
11609
+ }
11610
+ function hwidMiddleware() {
11611
+ const enabled = personalAgentConfigured();
11612
+ let warnedMissing = false;
11613
+ return async (c, next) => {
11614
+ if (!enabled) return next();
11615
+ const path = c.req.path;
11616
+ if (SKIP_PATHS.has(path) || path.startsWith("/health/api-keys")) {
11617
+ return next();
11618
+ }
11619
+ const got = c.req.header("x-device-hwid");
11620
+ if (!got) {
11621
+ if (!warnedMissing) {
11622
+ warnedMissing = true;
11623
+ console.warn(
11624
+ `[personal-agent] request to ${path} arrived without X-Device-Hwid; allowing (backwards compat)`
11625
+ );
11626
+ }
11627
+ return next();
11628
+ }
11629
+ const expected = getHardwareIdCached();
11630
+ if (got !== expected) {
11631
+ console.warn(
11632
+ `[personal-agent] HWID mismatch on ${path}: got=${got.slice(0, 12)}\u2026, expected=${expected.slice(0, 12)}\u2026`
11633
+ );
11634
+ return c.json(
11635
+ {
11636
+ error: "hwid mismatch",
11637
+ 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.",
11638
+ expected: expected.slice(0, 12) + "\u2026",
11639
+ got: got.slice(0, 12) + "\u2026"
11640
+ },
11641
+ 409
11642
+ );
11643
+ }
11644
+ return next();
11645
+ };
11646
+ }
11647
+
11648
+ // src/personal-agent/signature-verify.ts
11649
+ import { createHash as createHash3, createPublicKey, verify as cryptoVerify } from "crypto";
11650
+ import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
11651
+ var REPLAY_WINDOW_SECONDS = 5 * 60;
11652
+ var _cachedKey = null;
11653
+ var _cachedFromInput = null;
11654
+ function loadPublicKey(input) {
11655
+ if (_cachedFromInput === input && _cachedKey) return _cachedKey;
11656
+ let pem = input;
11657
+ if (!input.includes("BEGIN") && existsSync18(input)) {
11658
+ pem = readFileSync11(input, "utf8");
11659
+ }
11660
+ const key = createPublicKey({ key: pem, format: "pem" });
11661
+ if (key.asymmetricKeyType !== "ed25519") {
11662
+ throw new Error(
11663
+ `expected an ed25519 public key, got ${key.asymmetricKeyType}. Generate with personal-agents/scripts/generate-signing-keys.mjs.`
11664
+ );
11665
+ }
11666
+ _cachedKey = key;
11667
+ _cachedFromInput = input;
11668
+ return key;
11669
+ }
11670
+ function bodyHashB64(body) {
11671
+ const hash = createHash3("sha256");
11672
+ if (body == null || body === "") {
11673
+ } else if (typeof body === "string") {
11674
+ hash.update(body, "utf8");
11675
+ } else if (Buffer.isBuffer(body)) {
11676
+ hash.update(body);
11677
+ } else {
11678
+ hash.update(Buffer.from(body));
11679
+ }
11680
+ return hash.digest("base64");
11681
+ }
11682
+ function canonicalSigningString(args) {
11683
+ return [
11684
+ args.method.toUpperCase(),
11685
+ args.path,
11686
+ String(args.timestamp),
11687
+ args.bodyHashB64
11688
+ ].join("\n");
11689
+ }
11690
+ function fromBase64Url(s) {
11691
+ const padded = s.padEnd(s.length + (4 - s.length % 4) % 4, "=");
11692
+ const std = padded.replace(/-/g, "+").replace(/_/g, "/");
11693
+ return Buffer.from(std, "base64");
11694
+ }
11695
+ function verifyEmbedToken(args) {
11696
+ const dot = args.token.indexOf(".");
11697
+ if (dot < 1 || dot >= args.token.length - 1) {
11698
+ return { ok: false, reason: "malformed" };
11699
+ }
11700
+ const payloadB64 = args.token.slice(0, dot);
11701
+ const sigB64 = args.token.slice(dot + 1);
11702
+ let sigBuf;
11703
+ try {
11704
+ sigBuf = fromBase64Url(sigB64);
11705
+ } catch {
11706
+ return { ok: false, reason: "bad-encoding" };
11707
+ }
11708
+ const sigOk = cryptoVerify(
11709
+ null,
11710
+ Buffer.from(payloadB64, "utf8"),
11711
+ args.publicKey,
11712
+ sigBuf
11713
+ );
11714
+ if (!sigOk) return { ok: false, reason: "signature-mismatch" };
11715
+ let payload;
11716
+ try {
11717
+ const json = JSON.parse(fromBase64Url(payloadB64).toString("utf8"));
11718
+ if (!json || typeof json !== "object" || typeof json.sid !== "string" || typeof json.exp !== "number") {
11719
+ return { ok: false, reason: "bad-payload" };
11720
+ }
11721
+ payload = { sid: json.sid, exp: json.exp };
11722
+ } catch (e) {
11723
+ return { ok: false, reason: "bad-payload", detail: e.message };
11724
+ }
11725
+ const now = args.now ?? Math.floor(Date.now() / 1e3);
11726
+ if (payload.exp < now) {
11727
+ return {
11728
+ ok: false,
11729
+ reason: "expired",
11730
+ detail: `${now - payload.exp}s past expiry`
11731
+ };
11732
+ }
11733
+ return { ok: true, payload };
11734
+ }
11735
+ function verifyRequest(args) {
11736
+ if (!args.signatureB64 || !args.timestampSeconds || !args.algorithm) {
11737
+ return { ok: false, reason: "missing-headers" };
11738
+ }
11739
+ if (args.algorithm.toLowerCase() !== "ed25519") {
11740
+ return { ok: false, reason: "bad-algorithm", detail: args.algorithm };
11741
+ }
11742
+ const ts = Number(args.timestampSeconds);
11743
+ if (!Number.isFinite(ts)) {
11744
+ return { ok: false, reason: "bad-timestamp", detail: args.timestampSeconds };
11745
+ }
11746
+ const now = args.now ?? Math.floor(Date.now() / 1e3);
11747
+ if (Math.abs(now - ts) > REPLAY_WINDOW_SECONDS) {
11748
+ return {
11749
+ ok: false,
11750
+ reason: "stale-timestamp",
11751
+ detail: `${Math.abs(now - ts)}s outside the ${REPLAY_WINDOW_SECONDS}s window`
11752
+ };
11753
+ }
11754
+ let sigBuf;
11755
+ try {
11756
+ sigBuf = Buffer.from(args.signatureB64, "base64");
11757
+ } catch {
11758
+ return { ok: false, reason: "bad-signature-encoding" };
11759
+ }
11760
+ const canonical = canonicalSigningString({
11761
+ method: args.method,
11762
+ path: args.path,
11763
+ timestamp: ts,
11764
+ bodyHashB64: bodyHashB64(args.body)
11765
+ });
11766
+ const ok = cryptoVerify(null, Buffer.from(canonical, "utf8"), args.publicKey, sigBuf);
11767
+ return ok ? { ok: true } : { ok: false, reason: "signature-mismatch" };
11768
+ }
11769
+
11770
+ // src/personal-agent/signature-middleware.ts
11771
+ var SKIP_PATHS2 = /* @__PURE__ */ new Set(["/health", "/health/", "/health/ready", "/health/version"]);
11772
+ function isSkipped(path) {
11773
+ return SKIP_PATHS2.has(path) || path.startsWith("/health/api-keys");
11774
+ }
11775
+ function pathBindsSessionId(path, sid) {
11776
+ const cleanPath = path.split("?")[0];
11777
+ const segments = cleanPath.split("/").filter(Boolean);
11778
+ return segments.includes(sid);
11779
+ }
11780
+ function signatureMiddleware(opts = {}) {
11781
+ const acceptSignedOnly = opts.acceptSignedOnly ?? process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY === "1";
11782
+ const publicKeyInput = opts.publicKeyInput ?? process.env.PERSONAL_AGENT_PUBLIC_KEY ?? "";
11783
+ if (acceptSignedOnly && !publicKeyInput) {
11784
+ return async (c) => c.json(
11785
+ {
11786
+ error: "signature middleware misconfigured",
11787
+ message: "started with --accept-signed-only but no --personal-agent-public-key / PERSONAL_AGENT_PUBLIC_KEY supplied. Refusing all non-/health traffic."
11788
+ },
11789
+ 500
11790
+ );
11791
+ }
11792
+ if (!publicKeyInput) {
11793
+ return async (_c, next) => next();
11794
+ }
11795
+ const publicKey = loadPublicKey(publicKeyInput);
11796
+ let warnedUnsigned = false;
11797
+ return async (c, next) => {
11798
+ const path = c.req.path;
11799
+ if (isSkipped(path)) return next();
11800
+ const sig = c.req.header("x-signature");
11801
+ const ts = c.req.header("x-signature-timestamp");
11802
+ const alg = c.req.header("x-signature-algorithm");
11803
+ if (!sig && (c.req.method === "GET" || c.req.method === "HEAD")) {
11804
+ const embedTok = c.req.header("x-embed-token") ?? c.req.query("embed_token");
11805
+ if (embedTok) {
11806
+ const result2 = verifyEmbedToken({ publicKey, token: embedTok });
11807
+ if (!result2.ok) {
11808
+ return c.json(
11809
+ {
11810
+ error: "embed token verification failed",
11811
+ reason: result2.reason,
11812
+ detail: result2.detail
11813
+ },
11814
+ 401
11815
+ );
11816
+ }
11817
+ if (!pathBindsSessionId(path, result2.payload.sid)) {
11818
+ return c.json(
11819
+ {
11820
+ error: "embed token scoped to a different session",
11821
+ detail: `token sid=${result2.payload.sid} but request path=${path}`
11822
+ },
11823
+ 403
11824
+ );
11825
+ }
11826
+ return next();
11827
+ }
11828
+ }
11829
+ if (!sig) {
11830
+ if (acceptSignedOnly) {
11831
+ return c.json(
11832
+ {
11833
+ error: "signature required",
11834
+ message: "this sparkecoder is started with --accept-signed-only; every request must carry X-Signature, X-Signature-Timestamp, X-Signature-Algorithm."
11835
+ },
11836
+ 401
11837
+ );
11838
+ }
11839
+ if (!warnedUnsigned) {
11840
+ warnedUnsigned = true;
11841
+ console.warn(
11842
+ `[personal-agent] inbound ${c.req.method} ${path} arrived without X-Signature; allowed because --accept-signed-only is off`
11843
+ );
11844
+ }
11845
+ return next();
11846
+ }
11847
+ let body;
11848
+ if (c.req.method !== "GET" && c.req.method !== "HEAD") {
11849
+ body = Buffer.from(await c.req.raw.clone().arrayBuffer());
11850
+ }
11851
+ const result = verifyRequest({
11852
+ publicKey,
11853
+ method: c.req.method,
11854
+ path,
11855
+ body,
11856
+ signatureB64: sig,
11857
+ timestampSeconds: ts,
11858
+ algorithm: alg
11859
+ });
11860
+ if (!result.ok) {
11861
+ console.warn(
11862
+ `[personal-agent] signature verification failed on ${c.req.method} ${path}: ${result.reason}${result.detail ? ` (${result.detail})` : ""}`
11863
+ );
11864
+ return c.json(
11865
+ {
11866
+ error: "signature verification failed",
11867
+ reason: result.reason,
11868
+ detail: result.detail
11869
+ },
11870
+ 401
11871
+ );
11872
+ }
11873
+ return next();
11874
+ };
11875
+ }
11876
+
11877
+ // src/personal-agent/pty-server.ts
11878
+ import { hostname as hostname3 } from "os";
11879
+ import { WebSocketServer } from "ws";
11880
+ var RESIZE_RE = /\x1b\[RESIZE:(\d+);(\d+)\]/;
11881
+ var _ptyMod = null;
11882
+ async function loadPty() {
11883
+ if (_ptyMod) return _ptyMod;
11884
+ try {
11885
+ const mod = await import("node-pty");
11886
+ _ptyMod = mod;
11887
+ return mod;
11888
+ } catch (e) {
11889
+ console.warn(
11890
+ `[personal-agent] node-pty failed to load; /pty WebSocket disabled. (${e.message})`
11891
+ );
11892
+ return null;
11893
+ }
11894
+ }
11895
+ function defaultShell() {
11896
+ if (process.platform === "win32") {
11897
+ return { file: process.env.COMSPEC || "cmd.exe", args: [] };
11898
+ }
11899
+ return { file: process.env.SHELL || "/bin/zsh", args: ["-l"] };
11900
+ }
11901
+ function cleanEnv() {
11902
+ const env = {};
11903
+ for (const [k, v] of Object.entries(process.env)) {
11904
+ if (typeof v === "string") env[k] = v;
11905
+ }
11906
+ if (!env.TERM) env.TERM = "xterm-256color";
11907
+ if (!env.LANG) env.LANG = "en_US.UTF-8";
11908
+ return env;
11909
+ }
11910
+ function parseUpgrade(req) {
11911
+ const url = new URL(req.url || "/", "http://placeholder");
11912
+ const cols = clampInt(url.searchParams.get("cols"), 80, 10, 500);
11913
+ const rows = clampInt(url.searchParams.get("rows"), 24, 5, 500);
11914
+ const cwd = url.searchParams.get("cwd") || void 0;
11915
+ const shell = url.searchParams.get("shell") || void 0;
11916
+ return {
11917
+ cols,
11918
+ rows,
11919
+ cwd,
11920
+ shell,
11921
+ hwidHeader: headerStr(req, "x-device-hwid") ?? url.searchParams.get("hwid") ?? void 0,
11922
+ sigHeader: headerStr(req, "x-signature") ?? url.searchParams.get("sig") ?? void 0,
11923
+ tsHeader: headerStr(req, "x-signature-timestamp") ?? url.searchParams.get("ts") ?? void 0,
11924
+ algHeader: headerStr(req, "x-signature-algorithm") ?? url.searchParams.get("alg") ?? void 0
11925
+ };
11926
+ }
11927
+ function clampInt(v, dflt, lo, hi) {
11928
+ if (!v) return dflt;
11929
+ const n = parseInt(v, 10);
11930
+ if (!Number.isFinite(n)) return dflt;
11931
+ return Math.max(lo, Math.min(hi, n));
11932
+ }
11933
+ function headerStr(req, name) {
11934
+ const v = req.headers[name];
11935
+ if (Array.isArray(v)) return v[0];
11936
+ return v;
11937
+ }
11938
+ function authenticate(parsed, path, pubKey, signedOnly) {
11939
+ if (parsed.hwidHeader) {
11940
+ const expected = getHardwareIdCached();
11941
+ if (parsed.hwidHeader !== expected) {
11942
+ return {
11943
+ ok: false,
11944
+ status: 409,
11945
+ reason: `hwid mismatch: got ${parsed.hwidHeader.slice(0, 12)}\u2026, expected ${expected.slice(0, 12)}\u2026`
11946
+ };
11947
+ }
11948
+ }
11949
+ if (!pubKey) {
11950
+ if (signedOnly) {
11951
+ return { ok: false, status: 500, reason: "signature required but no public key configured" };
11952
+ }
11953
+ return { ok: true };
11954
+ }
11955
+ if (!parsed.sigHeader) {
11956
+ if (signedOnly) {
11957
+ return { ok: false, status: 401, reason: "missing X-Signature on upgrade request" };
11958
+ }
11959
+ return { ok: true };
11960
+ }
11961
+ const result = verifyRequest({
11962
+ publicKey: pubKey,
11963
+ method: "GET",
11964
+ path,
11965
+ body: void 0,
11966
+ signatureB64: parsed.sigHeader,
11967
+ timestampSeconds: parsed.tsHeader,
11968
+ algorithm: parsed.algHeader
11969
+ });
11970
+ if (!result.ok) {
11971
+ return { ok: false, status: 401, reason: `signature verification failed: ${result.reason}` };
11972
+ }
11973
+ return { ok: true };
11974
+ }
11975
+ function rejectUpgrade(socket, status, reason) {
11976
+ const body = JSON.stringify({ error: reason });
11977
+ socket.write(
11978
+ `HTTP/1.1 ${status} ${reasonText(status)}\r
11979
+ Content-Type: application/json\r
11980
+ Content-Length: ${Buffer.byteLength(body)}\r
11981
+ Connection: close\r
11982
+ \r
11983
+ ` + body
11984
+ );
11985
+ socket.destroy();
11986
+ }
11987
+ function reasonText(status) {
11988
+ switch (status) {
11989
+ case 401:
11990
+ return "Unauthorized";
11991
+ case 409:
11992
+ return "Conflict";
11993
+ case 500:
11994
+ return "Internal Server Error";
11995
+ case 503:
11996
+ return "Service Unavailable";
11997
+ default:
11998
+ return "Error";
11999
+ }
12000
+ }
12001
+ function attachPtyServer(httpServer, opts = {}) {
12002
+ const path = opts.path ?? "/pty";
12003
+ const wss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
12004
+ const acceptSignedOnly = process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY === "1";
12005
+ const publicKeyInput = process.env.PERSONAL_AGENT_PUBLIC_KEY ?? "";
12006
+ let pubKey = null;
12007
+ if (publicKeyInput) {
12008
+ try {
12009
+ pubKey = loadPublicKey(publicKeyInput);
12010
+ } catch (e) {
12011
+ console.warn(
12012
+ `[personal-agent] /pty signature verification disabled \u2014 failed to load public key: ${e.message}`
12013
+ );
12014
+ }
12015
+ }
12016
+ const handler = (req, socket, head) => {
12017
+ let pathname = "/";
12018
+ try {
12019
+ pathname = new URL(req.url || "/", "http://placeholder").pathname;
12020
+ } catch {
12021
+ }
12022
+ if (pathname !== path) return;
12023
+ const parsed = parseUpgrade(req);
12024
+ const auth = authenticate(parsed, pathname, pubKey, acceptSignedOnly);
12025
+ if (!auth.ok) {
12026
+ console.warn(`[personal-agent] rejecting /pty upgrade: ${auth.reason}`);
12027
+ rejectUpgrade(socket, auth.status ?? 401, auth.reason ?? "unauthorized");
12028
+ return;
12029
+ }
12030
+ void loadPty().then((pty) => {
12031
+ if (!pty) {
12032
+ rejectUpgrade(
12033
+ socket,
12034
+ 503,
12035
+ "node-pty is not available on this device (failed to load native module)"
12036
+ );
12037
+ return;
12038
+ }
12039
+ wss.handleUpgrade(req, socket, head, (ws) => {
12040
+ spawnPty(ws, pty, parsed, opts);
12041
+ });
12042
+ });
12043
+ };
12044
+ httpServer.on("upgrade", handler);
12045
+ if (!opts.quiet) {
12046
+ console.log(
12047
+ `[personal-agent] WebSocket PTY server attached at ${path} (host: ${hostname3()}, sigCheck: ${pubKey ? "on" : "off"})`
12048
+ );
12049
+ }
12050
+ return {
12051
+ close: () => {
12052
+ httpServer.off("upgrade", handler);
12053
+ wss.close();
12054
+ }
12055
+ };
12056
+ }
12057
+ function spawnPty(ws, pty, parsed, opts) {
12058
+ const { file, args } = (() => {
12059
+ if (parsed.shell || opts.shell) {
12060
+ const s = parsed.shell ?? opts.shell;
12061
+ return { file: s, args: process.platform === "win32" ? [] : ["-l"] };
12062
+ }
12063
+ return defaultShell();
12064
+ })();
12065
+ const cwd = parsed.cwd ?? opts.cwd ?? process.env.HOME ?? process.cwd();
12066
+ let proc;
12067
+ try {
12068
+ proc = pty.spawn(file, args, {
12069
+ name: "xterm-256color",
12070
+ cols: parsed.cols,
12071
+ rows: parsed.rows,
12072
+ cwd,
12073
+ env: cleanEnv()
12074
+ });
12075
+ } catch (e) {
12076
+ const msg = e.message;
12077
+ safeSend(ws, `\r
12078
+ \x1B[31mFailed to spawn shell: ${msg}\x1B[0m\r
12079
+ `);
12080
+ try {
12081
+ ws.close();
12082
+ } catch {
12083
+ }
12084
+ return;
12085
+ }
12086
+ const banner = `\x1B[90m[sparkecoder pty] ${file} on ${hostname3()} pid=${proc.pid} ${parsed.cols}x${parsed.rows}\x1B[0m\r
12087
+ `;
12088
+ safeSend(ws, banner);
12089
+ proc.onData((data) => safeSend(ws, data));
12090
+ proc.onExit(({ exitCode }) => {
12091
+ safeSend(ws, `\r
12092
+ \x1B[90m[exit ${exitCode}]\x1B[0m\r
12093
+ `);
12094
+ try {
12095
+ ws.close();
12096
+ } catch {
12097
+ }
12098
+ });
12099
+ ws.on("message", (msg, isBinary) => {
12100
+ const input = typeof msg === "string" ? msg : Buffer.isBuffer(msg) ? msg.toString(isBinary ? "utf8" : "utf8") : Array.isArray(msg) ? Buffer.concat(msg).toString("utf8") : "";
12101
+ if (!input) return;
12102
+ const m = input.match(RESIZE_RE);
12103
+ if (m) {
12104
+ const cols = clampInt(m[1], 80, 10, 500);
12105
+ const rows = clampInt(m[2], 24, 5, 500);
12106
+ try {
12107
+ proc.resize(cols, rows);
12108
+ } catch {
12109
+ }
12110
+ if (input.replace(RESIZE_RE, "").length === 0) return;
12111
+ proc.write(input.replace(RESIZE_RE, ""));
12112
+ return;
12113
+ }
12114
+ proc.write(input);
12115
+ });
12116
+ const onClose = () => {
12117
+ try {
12118
+ proc.kill();
12119
+ } catch {
12120
+ }
12121
+ };
12122
+ ws.on("close", onClose);
12123
+ ws.on("error", onClose);
12124
+ }
12125
+ function safeSend(ws, data) {
12126
+ if (ws.readyState !== 1) return;
12127
+ try {
12128
+ ws.send(data);
12129
+ } catch {
12130
+ }
12131
+ }
12132
+
10677
12133
  // src/server/index.ts
10678
12134
  init_config();
10679
12135
  init_db();
10680
12136
 
10681
12137
  // src/utils/dependencies.ts
10682
- import { exec as exec6 } from "child_process";
10683
- import { promisify as promisify6 } from "util";
10684
- import { platform as platform2 } from "os";
10685
- var execAsync6 = promisify6(exec6);
12138
+ import { exec as exec7 } from "child_process";
12139
+ import { promisify as promisify7 } from "util";
12140
+ import { platform as platform4 } from "os";
12141
+ var execAsync7 = promisify7(exec7);
10686
12142
  function getInstallInstructions() {
10687
- const os2 = platform2();
12143
+ const os2 = platform4();
10688
12144
  if (os2 === "darwin") {
10689
12145
  return `
10690
12146
  Install tmux on macOS:
@@ -10715,7 +12171,7 @@ Install tmux:
10715
12171
  }
10716
12172
  async function checkTmux() {
10717
12173
  try {
10718
- const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
12174
+ const { stdout } = await execAsync7("tmux -V", { timeout: 5e3 });
10719
12175
  const version = stdout.trim();
10720
12176
  return {
10721
12177
  available: true,
@@ -10764,11 +12220,11 @@ function getWebDirectory() {
10764
12220
  try {
10765
12221
  const currentDir = dirname7(fileURLToPath4(import.meta.url));
10766
12222
  const webDir = resolve10(currentDir, "..", "web");
10767
- if (existsSync17(webDir) && existsSync17(join12(webDir, "package.json"))) {
12223
+ if (existsSync19(webDir) && existsSync19(join13(webDir, "package.json"))) {
10768
12224
  return webDir;
10769
12225
  }
10770
12226
  const altWebDir = resolve10(currentDir, "..", "..", "web");
10771
- if (existsSync17(altWebDir) && existsSync17(join12(altWebDir, "package.json"))) {
12227
+ if (existsSync19(altWebDir) && existsSync19(join13(altWebDir, "package.json"))) {
10772
12228
  return altWebDir;
10773
12229
  }
10774
12230
  return null;
@@ -10826,23 +12282,23 @@ async function findWebPort(preferredPort) {
10826
12282
  return { port: preferredPort, alreadyRunning: false };
10827
12283
  }
10828
12284
  function hasProductionBuild(webDir) {
10829
- const buildIdPath = join12(webDir, ".next", "BUILD_ID");
10830
- return existsSync17(buildIdPath);
12285
+ const buildIdPath = join13(webDir, ".next", "BUILD_ID");
12286
+ return existsSync19(buildIdPath);
10831
12287
  }
10832
12288
  function hasSourceFiles(webDir) {
10833
- const appDir = join12(webDir, "src", "app");
10834
- const pagesDir = join12(webDir, "src", "pages");
10835
- const rootAppDir = join12(webDir, "app");
10836
- const rootPagesDir = join12(webDir, "pages");
10837
- return existsSync17(appDir) || existsSync17(pagesDir) || existsSync17(rootAppDir) || existsSync17(rootPagesDir);
12289
+ const appDir = join13(webDir, "src", "app");
12290
+ const pagesDir = join13(webDir, "src", "pages");
12291
+ const rootAppDir = join13(webDir, "app");
12292
+ const rootPagesDir = join13(webDir, "pages");
12293
+ return existsSync19(appDir) || existsSync19(pagesDir) || existsSync19(rootAppDir) || existsSync19(rootPagesDir);
10838
12294
  }
10839
12295
  function getStandaloneServerPath(webDir) {
10840
12296
  const possiblePaths2 = [
10841
- join12(webDir, ".next", "standalone", "server.js"),
10842
- join12(webDir, ".next", "standalone", "web", "server.js")
12297
+ join13(webDir, ".next", "standalone", "server.js"),
12298
+ join13(webDir, ".next", "standalone", "web", "server.js")
10843
12299
  ];
10844
12300
  for (const serverPath of possiblePaths2) {
10845
- if (existsSync17(serverPath)) {
12301
+ if (existsSync19(serverPath)) {
10846
12302
  return serverPath;
10847
12303
  }
10848
12304
  }
@@ -10882,13 +12338,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
10882
12338
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
10883
12339
  return { process: null, port: actualPort };
10884
12340
  }
10885
- const usePnpm = existsSync17(join12(webDir, "pnpm-lock.yaml"));
10886
- const useNpm = !usePnpm && existsSync17(join12(webDir, "package-lock.json"));
12341
+ const usePnpm = existsSync19(join13(webDir, "pnpm-lock.yaml"));
12342
+ const useNpm = !usePnpm && existsSync19(join13(webDir, "package-lock.json"));
10887
12343
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
10888
- const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
12344
+ const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv2 } = process.env;
10889
12345
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
10890
12346
  const runtimeConfig = { apiBaseUrl: apiUrl };
10891
- const runtimeConfigPath = join12(webDir, "runtime-config.json");
12347
+ const runtimeConfigPath = join13(webDir, "runtime-config.json");
10892
12348
  try {
10893
12349
  writeFileSync5(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
10894
12350
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -10896,7 +12352,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
10896
12352
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
10897
12353
  }
10898
12354
  const webEnv = {
10899
- ...cleanEnv,
12355
+ ...cleanEnv2,
10900
12356
  PORT: String(actualPort)
10901
12357
  // Next.js respects PORT env var
10902
12358
  };
@@ -11010,12 +12466,28 @@ function stopWebUI() {
11010
12466
  }
11011
12467
  }
11012
12468
  async function createApp(options = {}) {
11013
- const app = new Hono6();
12469
+ const app = new Hono7();
11014
12470
  app.use("*", cors({
11015
12471
  origin: "*",
11016
12472
  // Allow all origins
11017
12473
  allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
11018
- allowHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
12474
+ allowHeaders: [
12475
+ "Content-Type",
12476
+ "Authorization",
12477
+ "X-Requested-With",
12478
+ // Personal-agent dashboard signs every request to the device with
12479
+ // these. Without them whitelisted the browser preflight strips the
12480
+ // headers and the signature middleware 401s.
12481
+ "X-Signature",
12482
+ "X-Signature-Timestamp",
12483
+ "X-Signature-Algorithm",
12484
+ "X-Device-Hwid",
12485
+ // Short-lived embed token used by the iframed SparkECoder web UI
12486
+ // when it's hosted inside the personal-agents dashboard. The
12487
+ // bootstrap in `web/src/lib/embed-bootstrap.ts` adds this header
12488
+ // to every API call.
12489
+ "X-Embed-Token"
12490
+ ],
11019
12491
  exposeHeaders: ["X-Stream-Id", "x-stream-id"],
11020
12492
  maxAge: 86400
11021
12493
  // 24 hours
@@ -11023,12 +12495,15 @@ async function createApp(options = {}) {
11023
12495
  if (!options.quiet) {
11024
12496
  app.use("*", logger());
11025
12497
  }
12498
+ app.use("*", hwidMiddleware());
12499
+ app.use("*", signatureMiddleware());
11026
12500
  app.route("/health", health);
11027
12501
  app.route("/sessions", sessions);
11028
12502
  app.route("/agents", agents);
11029
12503
  app.route("/sessions", terminals);
11030
12504
  app.route("/terminals", terminals);
11031
12505
  app.route("/tasks", tasks_default);
12506
+ app.route("/system", system);
11032
12507
  app.get("/openapi.json", async (c) => {
11033
12508
  return c.json(generateOpenAPISpec());
11034
12509
  });
@@ -11081,8 +12556,8 @@ async function startServer(options = {}) {
11081
12556
  if (options.workingDirectory) {
11082
12557
  config.resolvedWorkingDirectory = options.workingDirectory;
11083
12558
  }
11084
- if (!existsSync17(config.resolvedWorkingDirectory)) {
11085
- mkdirSync7(config.resolvedWorkingDirectory, { recursive: true });
12559
+ if (!existsSync19(config.resolvedWorkingDirectory)) {
12560
+ mkdirSync8(config.resolvedWorkingDirectory, { recursive: true });
11086
12561
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
11087
12562
  }
11088
12563
  if (!config.resolvedRemoteServer.url) {
@@ -11117,6 +12592,15 @@ async function startServer(options = {}) {
11117
12592
  port,
11118
12593
  hostname: host
11119
12594
  });
12595
+ try {
12596
+ attachPtyServer(serverInstance, {
12597
+ quiet: options.quiet
12598
+ });
12599
+ } catch (e) {
12600
+ if (!options.quiet) {
12601
+ console.warn(` \u26A0 Failed to attach /pty WebSocket server: ${e.message}`);
12602
+ }
12603
+ }
11120
12604
  let webPort;
11121
12605
  let webStarted;
11122
12606
  if (options.webUI !== false) {