sparkecoder 0.1.86 → 0.1.93

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 (146) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/index.d.ts +3 -3
  3. package/dist/agent/index.js +809 -97
  4. package/dist/agent/index.js.map +1 -1
  5. package/dist/cli.js +2395 -316
  6. package/dist/cli.js.map +1 -1
  7. package/dist/db/index.d.ts +2 -2
  8. package/dist/{index-OhuTM4a0.d.ts → index-Bn0Zox8f.d.ts} +32 -23
  9. package/dist/index.d.ts +5 -5
  10. package/dist/index.js +1932 -273
  11. package/dist/index.js.map +1 -1
  12. package/dist/{schema-CohdIL13.d.ts → schema-EmpbnQeQ.d.ts} +3 -3
  13. package/dist/{search-CCffrVJE.d.ts → search-BRnGaIl-.d.ts} +7 -7
  14. package/dist/server/index.js +1932 -273
  15. package/dist/server/index.js.map +1 -1
  16. package/dist/skills/default/computer-use.md +150 -0
  17. package/dist/tools/index.d.ts +196 -3
  18. package/dist/tools/index.js +639 -11
  19. package/dist/tools/index.js.map +1 -1
  20. package/package.json +6 -5
  21. package/src/skills/default/computer-use.md +150 -0
  22. package/web/.next/BUILD_ID +1 -1
  23. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  24. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  25. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  26. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  27. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  29. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  41. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  45. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +3 -3
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +3 -3
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +2 -2
  54. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +2 -2
  56. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +3 -3
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +3 -3
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  63. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +2 -2
  67. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +3 -3
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +3 -3
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +2 -2
  75. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.rsc +3 -3
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +3 -3
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  82. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +2 -2
  84. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +2 -2
  85. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +1 -1
  86. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  87. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  88. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  89. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  90. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  91. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  92. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  94. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  95. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +1 -1
  96. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ecd2bdca._.js → 2374f_317b1fef._.js} +1 -1
  97. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9adc1edb._.js → 2374f_37dd9702._.js} +1 -1
  98. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_8dc0f9aa._.js → 2374f_4d44e4ed._.js} +1 -1
  99. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_cc6c6363._.js → 2374f_54ac917f._.js} +1 -1
  100. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_00f7fe07._.js → 2374f_86585101._.js} +1 -1
  101. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_369747ce._.js → 2374f_a383a4d9._.js} +1 -1
  102. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d58d0276._.js → 2374f_c59a35bb._.js} +1 -1
  103. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__25b25c9d._.js → [root-of-the-server]__9a826344._.js} +2 -2
  104. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +1 -1
  105. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
  106. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
  107. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  108. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  109. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  110. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  111. package/web/.next/standalone/web/.next/static/chunks/275e8268daf318b2.js +7 -0
  112. package/web/.next/standalone/web/.next/static/{static/chunks/26eb5fab5216f3cc.js → chunks/58fd0aaa2746b444.js} +1 -1
  113. package/web/.next/standalone/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
  114. package/web/.next/{static/chunks/8d3efc76109d2efc.js → standalone/web/.next/static/chunks/a888d448ceab1abe.js} +1 -1
  115. package/web/.next/standalone/web/.next/static/static/chunks/275e8268daf318b2.js +7 -0
  116. package/web/.next/{static/chunks/26eb5fab5216f3cc.js → standalone/web/.next/static/static/chunks/58fd0aaa2746b444.js} +1 -1
  117. package/web/.next/standalone/web/.next/static/static/chunks/9fce2ce79c4c834e.js +1 -0
  118. package/web/.next/standalone/web/.next/static/{chunks/8d3efc76109d2efc.js → static/chunks/a888d448ceab1abe.js} +1 -1
  119. package/web/.next/standalone/web/package-lock.json +27 -27
  120. package/web/.next/standalone/web/package.json +1 -1
  121. package/web/.next/standalone/web/src/app/(main)/page.tsx +2 -2
  122. package/web/.next/standalone/web/src/app/api/config/route.ts +3 -2
  123. package/web/.next/standalone/web/src/app/embed/[id]/page.tsx +12 -0
  124. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +1 -1
  125. package/web/.next/standalone/web/src/lib/config.ts +2 -1
  126. package/web/.next/standalone/web/src/lib/embed-bootstrap.ts +108 -0
  127. package/web/.next/static/chunks/275e8268daf318b2.js +7 -0
  128. package/web/.next/{standalone/web/.next/static/chunks/26eb5fab5216f3cc.js → static/chunks/58fd0aaa2746b444.js} +1 -1
  129. package/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
  130. package/web/.next/{standalone/web/.next/static/static/chunks/8d3efc76109d2efc.js → static/chunks/a888d448ceab1abe.js} +1 -1
  131. package/web/package.json +1 -1
  132. package/web/.next/standalone/web/.next/static/chunks/5383c5717758f575.js +0 -7
  133. package/web/.next/standalone/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
  134. package/web/.next/standalone/web/.next/static/static/chunks/5383c5717758f575.js +0 -7
  135. package/web/.next/standalone/web/.next/static/static/chunks/b31b0765abe0c427.js +0 -1
  136. package/web/.next/static/chunks/5383c5717758f575.js +0 -7
  137. package/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
  138. /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
  139. /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
  140. /package/web/.next/standalone/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
  141. /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
  142. /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
  143. /package/web/.next/standalone/web/.next/static/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_ssgManifest.js +0 -0
  144. /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_buildManifest.js +0 -0
  145. /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_clientMiddlewareManifest.json +0 -0
  146. /package/web/.next/static/{Pt6kwIO6lniM7h7I5E6kk → -ax3tJNqBGukss29jpu41}/_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)
@@ -96,7 +107,7 @@ var init_types = __esm({
96
107
  }).optional();
97
108
  SparkcoderConfigSchema = z.object({
98
109
  // Default model to use (Vercel AI Gateway format)
99
- defaultModel: z.string().default("anthropic/claude-opus-4-6"),
110
+ defaultModel: z.string().default("anthropic/claude-opus-4.7"),
100
111
  // Working directory for file operations
101
112
  workingDirectory: z.string().optional(),
102
113
  // Tool approval settings
@@ -455,6 +466,14 @@ function loadApiKeysIntoEnv() {
455
466
  }
456
467
  }
457
468
  }
469
+ function isRemoteInferenceConfigured() {
470
+ try {
471
+ const config = getConfig();
472
+ return config.resolvedRemoteServer.isConfigured;
473
+ } catch {
474
+ return false;
475
+ }
476
+ }
458
477
  function setApiKey(provider, apiKey) {
459
478
  const normalizedProvider = provider.toLowerCase();
460
479
  const envVar = PROVIDER_ENV_MAP[normalizedProvider];
@@ -504,11 +523,11 @@ function getApiKeyStatus() {
504
523
  };
505
524
  });
506
525
  }
507
- function maskApiKey(key) {
508
- if (key.length <= 12) {
509
- return "****" + key.slice(-4);
526
+ function maskApiKey(key2) {
527
+ if (key2.length <= 12) {
528
+ return "****" + key2.slice(-4);
510
529
  }
511
- return key.slice(0, 4) + "..." + key.slice(-4);
530
+ return key2.slice(0, 4) + "..." + key2.slice(-4);
512
531
  }
513
532
  var CONFIG_FILE_NAMES, cachedConfig, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
514
533
  var init_config = __esm({
@@ -555,9 +574,9 @@ __export(remote_exports, {
555
574
  remoteToolExecutionQueries: () => remoteToolExecutionQueries,
556
575
  storageQueries: () => storageQueries
557
576
  });
558
- function initRemoteDatabase(serverUrl, key) {
577
+ function initRemoteDatabase(serverUrl, key2) {
559
578
  remoteServerUrl = serverUrl.replace(/\/$/, "");
560
- authKey = key;
579
+ authKey = key2;
561
580
  }
562
581
  function closeRemoteDatabase() {
563
582
  remoteServerUrl = null;
@@ -571,14 +590,14 @@ function parseDates(obj) {
571
590
  if (Array.isArray(obj)) return obj.map(parseDates);
572
591
  if (typeof obj !== "object" || obj instanceof Date) return obj;
573
592
  const result = { ...obj };
574
- for (const key of Object.keys(result)) {
575
- if (MODEL_MESSAGE_FIELDS.includes(key)) {
593
+ for (const key2 of Object.keys(result)) {
594
+ if (MODEL_MESSAGE_FIELDS.includes(key2)) {
576
595
  continue;
577
596
  }
578
- if (DATE_FIELDS.includes(key) && typeof result[key] === "string") {
579
- result[key] = new Date(result[key]);
580
- } else if (typeof result[key] === "object") {
581
- result[key] = parseDates(result[key]);
597
+ if (DATE_FIELDS.includes(key2) && typeof result[key2] === "string") {
598
+ result[key2] = new Date(result[key2]);
599
+ } else if (typeof result[key2] === "object") {
600
+ result[key2] = parseDates(result[key2]);
582
601
  }
583
602
  }
584
603
  return result;
@@ -1451,10 +1470,10 @@ function parseSkillFrontmatter(content) {
1451
1470
  }
1452
1471
  const colonIndex = line.indexOf(":");
1453
1472
  if (colonIndex > 0) {
1454
- const key = line.slice(0, colonIndex).trim();
1473
+ const key2 = line.slice(0, colonIndex).trim();
1455
1474
  let value = line.slice(colonIndex + 1).trim();
1456
1475
  if (value === "" || value === "[]") {
1457
- currentArrayKey = key;
1476
+ currentArrayKey = key2;
1458
1477
  currentArray = [];
1459
1478
  continue;
1460
1479
  }
@@ -1467,18 +1486,18 @@ function parseSkillFrontmatter(content) {
1467
1486
  }
1468
1487
  return trimmed;
1469
1488
  }).filter((item) => item.length > 0);
1470
- data[key] = items;
1489
+ data[key2] = items;
1471
1490
  continue;
1472
1491
  }
1473
1492
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1474
1493
  value = value.slice(1, -1);
1475
1494
  }
1476
1495
  if (value === "true") {
1477
- data[key] = true;
1496
+ data[key2] = true;
1478
1497
  } else if (value === "false") {
1479
- data[key] = false;
1498
+ data[key2] = false;
1480
1499
  } else {
1481
- data[key] = value;
1500
+ data[key2] = value;
1482
1501
  }
1483
1502
  }
1484
1503
  }
@@ -1535,7 +1554,8 @@ async function loadSkillsFromDirectory(directory, options = {}) {
1535
1554
  globs: parsed.metadata.globs,
1536
1555
  loadType,
1537
1556
  priority,
1538
- sourceDir: directory
1557
+ sourceDir: directory,
1558
+ platforms: parsed.metadata.platforms
1539
1559
  });
1540
1560
  } else {
1541
1561
  const name = getSkillNameFromPath(filePath);
@@ -1548,11 +1568,14 @@ async function loadSkillsFromDirectory(directory, options = {}) {
1548
1568
  globs: [],
1549
1569
  loadType: forceAlwaysApply ? "always" : defaultLoadType,
1550
1570
  priority,
1551
- sourceDir: directory
1571
+ sourceDir: directory,
1572
+ platforms: []
1552
1573
  });
1553
1574
  }
1554
1575
  }
1555
- return skills;
1576
+ return skills.filter(
1577
+ (s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
1578
+ );
1556
1579
  }
1557
1580
  async function loadAllSkills(directories) {
1558
1581
  const allSkills = [];
@@ -1806,9 +1829,9 @@ var init_chunker = __esm({
1806
1829
  });
1807
1830
 
1808
1831
  // src/semantic/client.ts
1809
- function initVectorClient(serverUrl, key) {
1832
+ function initVectorClient(serverUrl, key2) {
1810
1833
  remoteServerUrl2 = serverUrl.replace(/\/$/, "");
1811
- authKey2 = key;
1834
+ authKey2 = key2;
1812
1835
  }
1813
1836
  function isVectorClientConfigured() {
1814
1837
  return !!remoteServerUrl2 && !!authKey2;
@@ -2149,6 +2172,7 @@ __export(webhook_exports, {
2149
2172
  sendWebhook: () => sendWebhook
2150
2173
  });
2151
2174
  async function sendWebhook(url, event) {
2175
+ const t0 = Date.now();
2152
2176
  try {
2153
2177
  const controller = new AbortController();
2154
2178
  const timeout = setTimeout(() => controller.abort(), 5e3);
@@ -2162,17 +2186,36 @@ async function sendWebhook(url, event) {
2162
2186
  signal: controller.signal
2163
2187
  });
2164
2188
  clearTimeout(timeout);
2189
+ const ms = Date.now() - t0;
2165
2190
  if (!response.ok) {
2166
- console.warn(`[WEBHOOK] ${event.type} to ${url} returned HTTP ${response.status}`);
2191
+ const body = await response.text().catch(() => "");
2192
+ console.warn(
2193
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> HTTP ${response.status} in ${ms}ms${body ? ` (${body.slice(0, 200)})` : ""}`
2194
+ );
2195
+ return;
2196
+ }
2197
+ if (!QUIET || TERMINAL_EVENTS.has(event.type)) {
2198
+ console.log(
2199
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> 200 in ${ms}ms`
2200
+ );
2167
2201
  }
2168
2202
  } catch (err) {
2169
2203
  const reason = err.name === "AbortError" ? "timeout (5s)" : err.message;
2170
- console.warn(`[WEBHOOK] ${event.type} to ${url} failed: ${reason}`);
2204
+ console.warn(
2205
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> failed: ${reason}`
2206
+ );
2171
2207
  }
2172
2208
  }
2209
+ var TERMINAL_EVENTS, QUIET;
2173
2210
  var init_webhook = __esm({
2174
2211
  "src/utils/webhook.ts"() {
2175
2212
  "use strict";
2213
+ TERMINAL_EVENTS = /* @__PURE__ */ new Set([
2214
+ "task.started",
2215
+ "task.completed",
2216
+ "task.failed"
2217
+ ]);
2218
+ QUIET = process.env.SPARKECODER_QUIET_WEBHOOKS === "1";
2176
2219
  }
2177
2220
  });
2178
2221
 
@@ -2382,15 +2425,15 @@ var recorder_exports = {};
2382
2425
  __export(recorder_exports, {
2383
2426
  FrameRecorder: () => FrameRecorder
2384
2427
  });
2385
- import { exec as exec5 } from "child_process";
2386
- import { promisify as promisify5 } from "util";
2428
+ import { exec as exec6 } from "child_process";
2429
+ import { promisify as promisify6 } from "util";
2387
2430
  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";
2431
+ import { join as join9 } from "path";
2432
+ import { tmpdir as tmpdir2 } from "os";
2433
+ import { nanoid as nanoid5 } from "nanoid";
2391
2434
  async function checkFfmpeg() {
2392
2435
  try {
2393
- await execAsync5("ffmpeg -version", { timeout: 5e3 });
2436
+ await execAsync6("ffmpeg -version", { timeout: 5e3 });
2394
2437
  return true;
2395
2438
  } catch {
2396
2439
  return false;
@@ -2402,11 +2445,11 @@ async function cleanup(dir) {
2402
2445
  } catch {
2403
2446
  }
2404
2447
  }
2405
- var execAsync5, FrameRecorder;
2448
+ var execAsync6, FrameRecorder;
2406
2449
  var init_recorder = __esm({
2407
2450
  "src/browser/recorder.ts"() {
2408
2451
  "use strict";
2409
- execAsync5 = promisify5(exec5);
2452
+ execAsync6 = promisify6(exec6);
2410
2453
  FrameRecorder = class {
2411
2454
  frames = [];
2412
2455
  startTime = null;
@@ -2442,21 +2485,21 @@ var init_recorder = __esm({
2442
2485
  */
2443
2486
  async encode() {
2444
2487
  if (this.frames.length === 0) return null;
2445
- const workDir = join8(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
2488
+ const workDir = join9(tmpdir2(), `sparkecoder-recording-${nanoid5(8)}`);
2446
2489
  await mkdir4(workDir, { recursive: true });
2447
2490
  try {
2448
2491
  for (let i = 0; i < this.frames.length; i++) {
2449
- const framePath = join8(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
2492
+ const framePath = join9(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
2450
2493
  await writeFile5(framePath, this.frames[i].data);
2451
2494
  }
2452
2495
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
2453
2496
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
2454
2497
  const clampedFps = Math.max(1, Math.min(fps, 30));
2455
- const outputPath = join8(workDir, `recording_${this.sessionId}.mp4`);
2498
+ const outputPath = join9(workDir, `recording_${this.sessionId}.mp4`);
2456
2499
  const hasFfmpeg = await checkFfmpeg();
2457
2500
  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}"`,
2501
+ await execAsync6(
2502
+ `ffmpeg -y -framerate ${clampedFps} -i "${join9(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
2460
2503
  { timeout: 12e4 }
2461
2504
  );
2462
2505
  } else {
@@ -2468,7 +2511,7 @@ var init_recorder = __esm({
2468
2511
  const files = await readdir5(workDir);
2469
2512
  for (const f of files) {
2470
2513
  if (f.startsWith("frame_")) {
2471
- await unlink2(join8(workDir, f)).catch(() => {
2514
+ await unlink2(join9(workDir, f)).catch(() => {
2472
2515
  });
2473
2516
  }
2474
2517
  }
@@ -2493,7 +2536,7 @@ var init_recorder = __esm({
2493
2536
  import {
2494
2537
  streamText as streamText2,
2495
2538
  generateText as generateText3,
2496
- tool as tool13,
2539
+ tool as tool14,
2497
2540
  stepCountIs as stepCountIs2
2498
2541
  } from "ai";
2499
2542
 
@@ -2662,6 +2705,23 @@ function isAnthropicModel(modelId) {
2662
2705
  const normalized = modelId.trim().toLowerCase();
2663
2706
  return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
2664
2707
  }
2708
+ function requiresAdaptiveThinking(modelId) {
2709
+ const m = modelId.toLowerCase().match(/claude-(?:opus|sonnet|haiku)-(\d+)[.-](\d{1,2})(?!\d)/);
2710
+ if (!m) return false;
2711
+ const major = Number(m[1]);
2712
+ const minor = Number(m[2]);
2713
+ if (Number.isNaN(major) || Number.isNaN(minor)) return false;
2714
+ if (major > 4) return true;
2715
+ if (major === 4 && minor >= 6) return true;
2716
+ return false;
2717
+ }
2718
+ function getAnthropicProviderOptions(modelId, opts = {}) {
2719
+ const { toolStreaming, budgetTokens = 1e4 } = opts;
2720
+ const thinking = requiresAdaptiveThinking(modelId) ? { type: "adaptive" } : { type: "enabled", budgetTokens };
2721
+ const out = { thinking };
2722
+ if (toolStreaming) out.toolStreaming = true;
2723
+ return out;
2724
+ }
2665
2725
  function resolveModel(modelId) {
2666
2726
  try {
2667
2727
  const config = getConfig();
@@ -2684,8 +2744,8 @@ var SUBAGENT_MODELS = {
2684
2744
  // src/agent/index.ts
2685
2745
  init_db();
2686
2746
  init_config();
2687
- import { z as z14 } from "zod";
2688
- import { nanoid as nanoid4 } from "nanoid";
2747
+ import { z as z15 } from "zod";
2748
+ import { nanoid as nanoid6 } from "nanoid";
2689
2749
 
2690
2750
  // src/tools/bash.ts
2691
2751
  import { tool } from "ai";
@@ -3035,11 +3095,11 @@ async function sendInput(terminalId, input, options = {}) {
3035
3095
  return false;
3036
3096
  }
3037
3097
  }
3038
- async function sendKey(terminalId, key) {
3098
+ async function sendKey(terminalId, key2) {
3039
3099
  const session = getSessionName(terminalId);
3040
3100
  try {
3041
3101
  await execAsync(`tmux has-session -t ${session}`, { timeout: 1e3 });
3042
- await execAsync(`tmux send-keys -t ${session} ${key}`, { timeout: 1e3 });
3102
+ await execAsync(`tmux send-keys -t ${session} ${key2}`, { timeout: 1e3 });
3043
3103
  return true;
3044
3104
  } catch {
3045
3105
  return false;
@@ -3178,7 +3238,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
3178
3238
  Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
3179
3239
  inputSchema: bashInputSchema,
3180
3240
  execute: async (inputArgs) => {
3181
- const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
3241
+ const { command, background, id, kill, tail, input: textInput, key: key2 } = inputArgs;
3182
3242
  if (id) {
3183
3243
  if (kill) {
3184
3244
  const success = await killTerminal(id);
@@ -3209,8 +3269,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3209
3269
  message: `Sent input "${textInput}" to terminal`
3210
3270
  };
3211
3271
  }
3212
- if (key) {
3213
- const success = await sendKey(id, key);
3272
+ if (key2) {
3273
+ const success = await sendKey(id, key2);
3214
3274
  if (!success) {
3215
3275
  return {
3216
3276
  success: false,
@@ -3226,7 +3286,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3226
3286
  id,
3227
3287
  output: truncatedOutput2,
3228
3288
  status: status2,
3229
- message: `Sent key "${key}" to terminal`
3289
+ message: `Sent key "${key2}" to terminal`
3230
3290
  };
3231
3291
  }
3232
3292
  const { output, status } = await getLogs(id, options.workingDirectory, { tail, sessionId: options.sessionId });
@@ -3368,13 +3428,13 @@ async function resizeImageIfNeeded(buffer, mediaType) {
3368
3428
  const needsResize = longEdge > MAX_LONG_EDGE;
3369
3429
  const needsShrink = buffer.length > MAX_FILE_BYTES;
3370
3430
  if (!needsResize && !needsShrink) return { buffer, mediaType: inputMediaType };
3371
- const key = cacheKey(buffer);
3431
+ const key2 = cacheKey(buffer);
3372
3432
  const cacheDir = getCacheDir();
3373
3433
  const isPng = inputMediaType.includes("png");
3374
3434
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
3375
3435
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
3376
3436
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
3377
- const cachePath = join3(cacheDir, key + ext);
3437
+ const cachePath = join3(cacheDir, key2 + ext);
3378
3438
  if (existsSync3(cachePath)) {
3379
3439
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
3380
3440
  return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
@@ -3769,12 +3829,12 @@ function findNearestRoot(startDir, markers) {
3769
3829
  }
3770
3830
  async function commandExists(cmd) {
3771
3831
  try {
3772
- const { exec: exec7 } = await import("child_process");
3773
- const { promisify: promisify7 } = await import("util");
3774
- const execAsync7 = promisify7(exec7);
3832
+ const { exec: exec8 } = await import("child_process");
3833
+ const { promisify: promisify8 } = await import("util");
3834
+ const execAsync8 = promisify8(exec8);
3775
3835
  const isWindows = process.platform === "win32";
3776
3836
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
3777
- await execAsync7(checkCmd);
3837
+ await execAsync8(checkCmd);
3778
3838
  return true;
3779
3839
  } catch {
3780
3840
  return false;
@@ -4265,31 +4325,31 @@ async function getClientForFile(filePath) {
4265
4325
  return null;
4266
4326
  }
4267
4327
  const root = dirname4(normalized);
4268
- const key = `${serverDef.id}:${root}`;
4269
- const existing = state.clients.get(key);
4328
+ const key2 = `${serverDef.id}:${root}`;
4329
+ const existing = state.clients.get(key2);
4270
4330
  if (existing) {
4271
4331
  return existing;
4272
4332
  }
4273
- if (state.broken.has(key)) {
4333
+ if (state.broken.has(key2)) {
4274
4334
  return null;
4275
4335
  }
4276
4336
  try {
4277
4337
  const handle = await serverDef.spawn(root);
4278
4338
  if (!handle) {
4279
- state.broken.add(key);
4339
+ state.broken.add(key2);
4280
4340
  return null;
4281
4341
  }
4282
4342
  console.log(`[lsp] Started ${serverDef.name} for ${root}`);
4283
4343
  const client = await createClient(serverDef.id, handle, root);
4284
- state.clients.set(key, client);
4344
+ state.clients.set(key2, client);
4285
4345
  handle.process.on("exit", (code) => {
4286
4346
  console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
4287
- state.clients.delete(key);
4347
+ state.clients.delete(key2);
4288
4348
  });
4289
4349
  return client;
4290
4350
  } catch (error) {
4291
4351
  console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
4292
- state.broken.add(key);
4352
+ state.broken.add(key2);
4293
4353
  return null;
4294
4354
  }
4295
4355
  }
@@ -6021,6 +6081,7 @@ init_semantic_search();
6021
6081
  import { tool as tool11 } from "ai";
6022
6082
  import { z as z12 } from "zod";
6023
6083
  import Ajv from "ajv";
6084
+ import { nanoid as nanoid3 } from "nanoid";
6024
6085
  var ajv = new Ajv({ allErrors: true });
6025
6086
  function createCompleteTaskTool(options) {
6026
6087
  const validate = ajv.compile(options.outputSchema);
@@ -6067,6 +6128,37 @@ function createTaskFailedTool(options) {
6067
6128
  }
6068
6129
  });
6069
6130
  }
6131
+ function createAskQuestionToUserTool(options) {
6132
+ return tool11({
6133
+ description: "Ask the user a blocking clarification question when you cannot safely continue without more information. Use this only after trying to infer the answer from the available context. The task will pause until the orchestrator or user answers.",
6134
+ inputSchema: z12.object({
6135
+ question: z12.string().min(1).describe("The concise question you need answered."),
6136
+ context: z12.string().optional().describe("Brief context explaining why this answer is needed and what you already tried."),
6137
+ choices: z12.array(z12.string().min(1)).max(10).optional().describe("Optional suggested answer choices when the question is multiple choice.")
6138
+ }),
6139
+ execute: async (input) => {
6140
+ if (!options.onQuestion) {
6141
+ return {
6142
+ status: "unavailable",
6143
+ message: "Question routing is not configured for this task. Continue with the best safe assumption or call task_failed if blocked."
6144
+ };
6145
+ }
6146
+ const questionId = `q_${nanoid3(12)}`;
6147
+ const answer = await options.onQuestion({
6148
+ questionId,
6149
+ question: input.question,
6150
+ context: input.context,
6151
+ choices: input.choices
6152
+ });
6153
+ return {
6154
+ status: "answered",
6155
+ questionId,
6156
+ answer: answer.answer,
6157
+ answeredBy: answer.answeredBy ?? "unknown"
6158
+ };
6159
+ }
6160
+ });
6161
+ }
6070
6162
 
6071
6163
  // src/tools/upload-file.ts
6072
6164
  import { tool as tool12 } from "ai";
@@ -6165,6 +6257,568 @@ function createUploadFileTool(options) {
6165
6257
  });
6166
6258
  }
6167
6259
 
6260
+ // src/tools/computer-use.ts
6261
+ import { anthropic } from "@ai-sdk/anthropic";
6262
+ import { exec as exec5 } from "child_process";
6263
+ import { promisify as promisify5 } from "util";
6264
+ import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
6265
+ import { join as join8 } from "path";
6266
+ import { tmpdir } from "os";
6267
+ import { nanoid as nanoid4 } from "nanoid";
6268
+ var execAsync5 = promisify5(exec5);
6269
+ var DEFAULT_WIDTH = 1280;
6270
+ var DEFAULT_HEIGHT = 800;
6271
+ function isMacOs() {
6272
+ return process.platform === "darwin";
6273
+ }
6274
+ async function isCliclickInstalled() {
6275
+ try {
6276
+ await execAsync5("command -v cliclick", { timeout: 2e3 });
6277
+ return true;
6278
+ } catch {
6279
+ return false;
6280
+ }
6281
+ }
6282
+ async function runJxa(script) {
6283
+ try {
6284
+ const escaped = script.replace(/'/g, `'\\''`);
6285
+ const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
6286
+ timeout: 5e3
6287
+ });
6288
+ return JSON.parse(stdout.trim());
6289
+ } catch {
6290
+ return null;
6291
+ }
6292
+ }
6293
+ async function hasAccessibilityPermissions() {
6294
+ try {
6295
+ const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
6296
+ if (/accessibility privileges not enabled/i.test(stderr)) {
6297
+ return { ok: false, error: stderr.trim().split("\n")[0] };
6298
+ }
6299
+ return { ok: true };
6300
+ } catch (err) {
6301
+ return { ok: false, error: err?.message || String(err) };
6302
+ }
6303
+ }
6304
+ async function hasScreenRecordingPermissions() {
6305
+ const result = await runJxa(
6306
+ `ObjC.import("Cocoa");
6307
+ ObjC.import("CoreGraphics");
6308
+ ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
6309
+ JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
6310
+ );
6311
+ return result?.hasAccess ?? false;
6312
+ }
6313
+ async function requestAccessibilityPrompt() {
6314
+ const result = await runJxa(
6315
+ `ObjC.import("ApplicationServices");
6316
+ var key = $.kAXTrustedCheckOptionPrompt;
6317
+ var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
6318
+ var trusted = $.AXIsProcessTrustedWithOptions(dict);
6319
+ JSON.stringify({ trusted: !!trusted });`
6320
+ );
6321
+ return result?.trusted ?? false;
6322
+ }
6323
+ async function requestScreenRecordingPrompt() {
6324
+ const result = await runJxa(
6325
+ `ObjC.import("Cocoa");
6326
+ ObjC.import("CoreGraphics");
6327
+ ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
6328
+ JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
6329
+ );
6330
+ return result?.granted ?? false;
6331
+ }
6332
+ async function openSystemSettings(pane) {
6333
+ const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
6334
+ try {
6335
+ await execAsync5(`open '${url}'`, { timeout: 3e3 });
6336
+ } catch {
6337
+ }
6338
+ }
6339
+ async function detectScreenSize() {
6340
+ try {
6341
+ const { stdout } = await execAsync5(
6342
+ `osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
6343
+ { timeout: 3e3 }
6344
+ );
6345
+ const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
6346
+ if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
6347
+ const [x1, y1, x2, y2] = parts;
6348
+ return { width: x2 - x1, height: y2 - y1 };
6349
+ }
6350
+ } catch {
6351
+ }
6352
+ return null;
6353
+ }
6354
+ async function runCliclick(args) {
6355
+ const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
6356
+ const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
6357
+ timeout: 15e3,
6358
+ maxBuffer: 1024 * 1024
6359
+ });
6360
+ if (/accessibility privileges not enabled/i.test(stderr)) {
6361
+ throw new Error(
6362
+ "Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
6363
+ );
6364
+ }
6365
+ if (stderr && !stdout) throw new Error(stderr.trim());
6366
+ return (stdout || "").trim();
6367
+ }
6368
+ async function runScreencapture(path) {
6369
+ await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
6370
+ timeout: 5e3
6371
+ });
6372
+ }
6373
+ async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
6374
+ const sharpModule = await import("sharp");
6375
+ const sharp2 = sharpModule.default || sharpModule;
6376
+ const meta = await sharp2(path).metadata();
6377
+ if (meta.width === targetWidth && meta.height === targetHeight) {
6378
+ return readFileSync7(path);
6379
+ }
6380
+ return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
6381
+ }
6382
+ async function runScroll(dx, dy) {
6383
+ const wheelY = -Math.round(dy);
6384
+ const wheelX = -Math.round(dx);
6385
+ const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
6386
+ await execAsync5(
6387
+ `osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
6388
+ { timeout: 5e3 }
6389
+ );
6390
+ }
6391
+ function translateKeyForCliclick(key2) {
6392
+ if (!key2) return [];
6393
+ const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
6394
+ if (parts.length === 0) return [];
6395
+ const modMap = {
6396
+ ctrl: "ctrl",
6397
+ control: "ctrl",
6398
+ alt: "alt",
6399
+ option: "alt",
6400
+ shift: "shift",
6401
+ cmd: "cmd",
6402
+ super: "cmd",
6403
+ meta: "cmd",
6404
+ win: "cmd",
6405
+ fn: "fn"
6406
+ };
6407
+ const keyMap = {
6408
+ return: "enter",
6409
+ enter: "enter",
6410
+ esc: "esc",
6411
+ escape: "esc",
6412
+ backspace: "delete",
6413
+ back_space: "delete",
6414
+ delete: "fwd-delete",
6415
+ fwd_delete: "fwd-delete",
6416
+ forward_delete: "fwd-delete",
6417
+ tab: "tab",
6418
+ space: "space",
6419
+ up: "arrow-up",
6420
+ arrow_up: "arrow-up",
6421
+ down: "arrow-down",
6422
+ arrow_down: "arrow-down",
6423
+ left: "arrow-left",
6424
+ arrow_left: "arrow-left",
6425
+ right: "arrow-right",
6426
+ arrow_right: "arrow-right",
6427
+ page_up: "page-up",
6428
+ pageup: "page-up",
6429
+ page_down: "page-down",
6430
+ pagedown: "page-down",
6431
+ home: "home",
6432
+ end: "end",
6433
+ f1: "f1",
6434
+ f2: "f2",
6435
+ f3: "f3",
6436
+ f4: "f4",
6437
+ f5: "f5",
6438
+ f6: "f6",
6439
+ f7: "f7",
6440
+ f8: "f8",
6441
+ f9: "f9",
6442
+ f10: "f10",
6443
+ f11: "f11",
6444
+ f12: "f12"
6445
+ };
6446
+ const modifiers = [];
6447
+ let mainKey = null;
6448
+ for (let i = 0; i < parts.length; i++) {
6449
+ const lower = parts[i].toLowerCase().replace(/-/g, "_");
6450
+ if (i < parts.length - 1 && modMap[lower]) {
6451
+ modifiers.push(modMap[lower]);
6452
+ } else {
6453
+ mainKey = keyMap[lower] || lower;
6454
+ }
6455
+ }
6456
+ const args = [];
6457
+ if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
6458
+ if (mainKey) {
6459
+ const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
6460
+ if (isNamedKey) {
6461
+ args.push(`kp:${mainKey}`);
6462
+ } else {
6463
+ args.push(`t:${mainKey}`);
6464
+ }
6465
+ }
6466
+ if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
6467
+ return args;
6468
+ }
6469
+ function modifierStringToCliclick(text) {
6470
+ return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
6471
+ if (p === "ctrl" || p === "control") return "ctrl";
6472
+ if (p === "alt" || p === "option") return "alt";
6473
+ if (p === "shift") return "shift";
6474
+ if (p === "super" || p === "meta" || p === "cmd") return "cmd";
6475
+ return "";
6476
+ }).filter(Boolean);
6477
+ }
6478
+ function createComputerUseTool(options) {
6479
+ const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
6480
+ const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
6481
+ return anthropic.tools.computer_20251124({
6482
+ displayWidthPx: displayWidth,
6483
+ displayHeightPx: displayHeight,
6484
+ enableZoom: true,
6485
+ execute: async (input) => {
6486
+ try {
6487
+ switch (input.action) {
6488
+ case "screenshot": {
6489
+ const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
6490
+ await runScreencapture(path);
6491
+ const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
6492
+ try {
6493
+ unlinkSync2(path);
6494
+ } catch {
6495
+ }
6496
+ return { type: "image", data: resized.toString("base64") };
6497
+ }
6498
+ case "left_click": {
6499
+ const [x, y] = input.coordinate ?? [0, 0];
6500
+ if (input.text) {
6501
+ const mods = modifierStringToCliclick(input.text);
6502
+ if (mods.length > 0) {
6503
+ await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
6504
+ } else {
6505
+ await runCliclick([`c:${x},${y}`]);
6506
+ }
6507
+ } else {
6508
+ await runCliclick([`c:${x},${y}`]);
6509
+ }
6510
+ return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
6511
+ }
6512
+ case "right_click": {
6513
+ const [x, y] = input.coordinate ?? [0, 0];
6514
+ await runCliclick([`rc:${x},${y}`]);
6515
+ return `right-clicked at (${x}, ${y})`;
6516
+ }
6517
+ case "middle_click": {
6518
+ const [x, y] = input.coordinate ?? [0, 0];
6519
+ 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);`;
6520
+ await execAsync5(
6521
+ `osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
6522
+ { timeout: 3e3 }
6523
+ );
6524
+ return `middle-clicked at (${x}, ${y})`;
6525
+ }
6526
+ case "double_click": {
6527
+ const [x, y] = input.coordinate ?? [0, 0];
6528
+ await runCliclick([`dc:${x},${y}`]);
6529
+ return `double-clicked at (${x}, ${y})`;
6530
+ }
6531
+ case "triple_click": {
6532
+ const [x, y] = input.coordinate ?? [0, 0];
6533
+ await runCliclick([`tc:${x},${y}`]);
6534
+ return `triple-clicked at (${x}, ${y})`;
6535
+ }
6536
+ case "mouse_move": {
6537
+ const [x, y] = input.coordinate ?? [0, 0];
6538
+ await runCliclick([`m:${x},${y}`]);
6539
+ return `moved cursor to (${x}, ${y})`;
6540
+ }
6541
+ case "left_mouse_down": {
6542
+ const [x, y] = input.coordinate ?? [0, 0];
6543
+ await runCliclick([`dd:${x},${y}`]);
6544
+ return `left mouse button pressed at (${x}, ${y})`;
6545
+ }
6546
+ case "left_mouse_up": {
6547
+ const [x, y] = input.coordinate ?? [0, 0];
6548
+ await runCliclick([`du:${x},${y}`]);
6549
+ return `left mouse button released at (${x}, ${y})`;
6550
+ }
6551
+ case "left_click_drag": {
6552
+ const [sx, sy] = input.start_coordinate ?? [0, 0];
6553
+ const [ex, ey] = input.coordinate ?? [0, 0];
6554
+ await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
6555
+ return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
6556
+ }
6557
+ case "type": {
6558
+ const text = input.text ?? "";
6559
+ await runCliclick([`t:${text}`]);
6560
+ return `typed ${text.length} character(s)`;
6561
+ }
6562
+ case "key": {
6563
+ const args = translateKeyForCliclick(input.text ?? "");
6564
+ if (args.length === 0) return "no key specified";
6565
+ await runCliclick(args);
6566
+ return `pressed ${input.text}`;
6567
+ }
6568
+ case "hold_key": {
6569
+ const text = (input.text ?? "").toLowerCase();
6570
+ const duration = input.duration ?? 1;
6571
+ const modMap = {
6572
+ ctrl: "ctrl",
6573
+ control: "ctrl",
6574
+ alt: "alt",
6575
+ option: "alt",
6576
+ shift: "shift",
6577
+ cmd: "cmd",
6578
+ super: "cmd",
6579
+ meta: "cmd",
6580
+ fn: "fn"
6581
+ };
6582
+ const cliName = modMap[text] || text;
6583
+ await runCliclick([`kd:${cliName}`]);
6584
+ await new Promise((r) => setTimeout(r, duration * 1e3));
6585
+ await runCliclick([`ku:${cliName}`]);
6586
+ return `held ${text} for ${duration}s`;
6587
+ }
6588
+ case "scroll": {
6589
+ const direction = input.scroll_direction ?? "down";
6590
+ const amount = input.scroll_amount ?? 3;
6591
+ const px = amount * 100;
6592
+ const dx = direction === "left" ? -px : direction === "right" ? px : 0;
6593
+ const dy = direction === "up" ? -px : direction === "down" ? px : 0;
6594
+ if (input.coordinate) {
6595
+ const [x, y] = input.coordinate;
6596
+ await runCliclick([`m:${x},${y}`]);
6597
+ }
6598
+ const mods = input.text ? modifierStringToCliclick(input.text) : [];
6599
+ if (mods.length > 0) {
6600
+ await runCliclick([`kd:${mods.join(",")}`]);
6601
+ }
6602
+ await runScroll(dx, dy);
6603
+ if (mods.length > 0) {
6604
+ await runCliclick([`ku:${mods.join(",")}`]);
6605
+ }
6606
+ return `scrolled ${direction} by ${amount}`;
6607
+ }
6608
+ case "wait": {
6609
+ const duration = input.duration ?? 1;
6610
+ await new Promise((r) => setTimeout(r, duration * 1e3));
6611
+ return `waited ${duration}s`;
6612
+ }
6613
+ case "cursor_position": {
6614
+ const out = await runCliclick(["p:."]);
6615
+ return `cursor at ${out}`;
6616
+ }
6617
+ case "zoom": {
6618
+ const region = input.region ?? [0, 0, displayWidth, displayHeight];
6619
+ const [x1, y1, x2, y2] = region;
6620
+ const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
6621
+ await runScreencapture(tmpPath);
6622
+ const sharpModule = await import("sharp");
6623
+ const sharp2 = sharpModule.default || sharpModule;
6624
+ const meta = await sharp2(tmpPath).metadata();
6625
+ const scaleX = (meta.width || displayWidth) / displayWidth;
6626
+ const scaleY = (meta.height || displayHeight) / displayHeight;
6627
+ const px = {
6628
+ left: Math.max(0, Math.round(x1 * scaleX)),
6629
+ top: Math.max(0, Math.round(y1 * scaleY)),
6630
+ width: Math.max(1, Math.round((x2 - x1) * scaleX)),
6631
+ height: Math.max(1, Math.round((y2 - y1) * scaleY))
6632
+ };
6633
+ const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
6634
+ try {
6635
+ unlinkSync2(tmpPath);
6636
+ } catch {
6637
+ }
6638
+ return { type: "image", data: buf.toString("base64") };
6639
+ }
6640
+ default: {
6641
+ const exhaustive = input.action;
6642
+ return `unsupported action: ${String(exhaustive)}`;
6643
+ }
6644
+ }
6645
+ } catch (err) {
6646
+ const msg = err?.message || String(err);
6647
+ let hint = "";
6648
+ if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
6649
+ hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
6650
+ } else if (/command not found/i.test(msg)) {
6651
+ hint = " (Hint: install cliclick with `brew install cliclick`)";
6652
+ }
6653
+ return `Error: ${msg}${hint}`;
6654
+ }
6655
+ },
6656
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6657
+ toModelOutput({ output }) {
6658
+ if (typeof output === "string") {
6659
+ return { type: "content", value: [{ type: "text", text: output }] };
6660
+ }
6661
+ return {
6662
+ type: "content",
6663
+ value: [{ type: "media", data: output.data, mediaType: "image/png" }]
6664
+ };
6665
+ }
6666
+ });
6667
+ }
6668
+
6669
+ // src/tools/enable-computer-use.ts
6670
+ init_db();
6671
+ import { tool as tool13 } from "ai";
6672
+ import { z as z14 } from "zod";
6673
+ var inputSchema = z14.object({
6674
+ display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
6675
+ display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
6676
+ request_permissions: z14.boolean().optional().default(true).describe(
6677
+ "When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
6678
+ )
6679
+ });
6680
+ var ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
6681
+ var SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
6682
+ function createEnableComputerUseTool(options) {
6683
+ return tool13({
6684
+ 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.",
6685
+ inputSchema,
6686
+ execute: async ({ display_width, display_height, request_permissions }) => {
6687
+ try {
6688
+ if (!isMacOs()) {
6689
+ return {
6690
+ success: false,
6691
+ error: "Computer use is currently only supported on macOS.",
6692
+ platform: process.platform
6693
+ };
6694
+ }
6695
+ if (!await isCliclickInstalled()) {
6696
+ return {
6697
+ success: false,
6698
+ error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
6699
+ installCommand: "brew install cliclick",
6700
+ fixSteps: [
6701
+ "In a terminal on this Mac, run: brew install cliclick",
6702
+ "(If Homebrew is not installed, install it first from https://brew.sh)",
6703
+ "Then call enable_computer_use again"
6704
+ ]
6705
+ };
6706
+ }
6707
+ const acc = await hasAccessibilityPermissions();
6708
+ const screen = await hasScreenRecordingPermissions();
6709
+ const missing = [];
6710
+ if (!acc.ok) {
6711
+ let prompted = false;
6712
+ let panelOpened = false;
6713
+ if (request_permissions) {
6714
+ prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
6715
+ await openSystemSettings("accessibility").then(() => {
6716
+ panelOpened = true;
6717
+ }).catch(() => void 0);
6718
+ }
6719
+ missing.push({
6720
+ name: "Accessibility",
6721
+ reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
6722
+ pane: "accessibility",
6723
+ settingsUrl: ACCESSIBILITY_URL,
6724
+ fixSteps: [
6725
+ "In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
6726
+ "Click the + button",
6727
+ "Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
6728
+ "Toggle the switch ON",
6729
+ "Restart the agent process so the new permission takes effect",
6730
+ "Then call enable_computer_use again"
6731
+ ],
6732
+ prompted,
6733
+ panelOpened
6734
+ });
6735
+ }
6736
+ if (!screen) {
6737
+ let prompted = false;
6738
+ let panelOpened = false;
6739
+ if (request_permissions) {
6740
+ prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
6741
+ await openSystemSettings("screen-recording").then(() => {
6742
+ panelOpened = true;
6743
+ }).catch(() => void 0);
6744
+ }
6745
+ missing.push({
6746
+ name: "Screen Recording",
6747
+ reason: "CGPreflightScreenCaptureAccess returned false",
6748
+ pane: "screen-recording",
6749
+ settingsUrl: SCREEN_RECORDING_URL,
6750
+ fixSteps: [
6751
+ "In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
6752
+ "Click the + button",
6753
+ "Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
6754
+ "Toggle the switch ON",
6755
+ "Restart the agent process so the new permission takes effect",
6756
+ "Then call enable_computer_use again"
6757
+ ],
6758
+ prompted,
6759
+ panelOpened
6760
+ });
6761
+ }
6762
+ if (missing.length > 0) {
6763
+ return {
6764
+ success: false,
6765
+ error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
6766
+ missingPermissions: missing,
6767
+ 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."
6768
+ };
6769
+ }
6770
+ let width = display_width;
6771
+ let height = display_height;
6772
+ let detected = null;
6773
+ if (width === void 0 || height === void 0) {
6774
+ detected = await detectScreenSize();
6775
+ width = width ?? detected?.width ?? 1280;
6776
+ height = height ?? detected?.height ?? 800;
6777
+ }
6778
+ const session = await sessionQueries.getById(options.sessionId);
6779
+ if (!session) {
6780
+ return { success: false, error: "Session not found" };
6781
+ }
6782
+ const config = session.config || {};
6783
+ if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
6784
+ return {
6785
+ success: true,
6786
+ alreadyEnabled: true,
6787
+ message: "Computer use was already enabled for this session.",
6788
+ displayWidth: width,
6789
+ displayHeight: height
6790
+ };
6791
+ }
6792
+ const updated = {
6793
+ ...config,
6794
+ computerUseEnabled: true,
6795
+ computerUseDisplayWidth: width,
6796
+ computerUseDisplayHeight: height
6797
+ };
6798
+ await sessionQueries.update(options.sessionId, { config: updated });
6799
+ return {
6800
+ success: true,
6801
+ enabled: true,
6802
+ platform: "darwin",
6803
+ displayWidth: width,
6804
+ displayHeight: height,
6805
+ detectedScreenSize: detected || void 0,
6806
+ permissions: {
6807
+ accessibility: "granted",
6808
+ screenRecording: "granted"
6809
+ },
6810
+ 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.`
6811
+ };
6812
+ } catch (err) {
6813
+ return {
6814
+ success: false,
6815
+ error: err?.message || String(err)
6816
+ };
6817
+ }
6818
+ }
6819
+ });
6820
+ }
6821
+
6168
6822
  // src/tools/index.ts
6169
6823
  init_semantic();
6170
6824
  init_remote();
@@ -6213,6 +6867,20 @@ async function createTools(options) {
6213
6867
  sessionId: options.sessionId
6214
6868
  });
6215
6869
  }
6870
+ if (process.platform === "darwin") {
6871
+ if (options.enableComputerUse) {
6872
+ tools.computer = createComputerUseTool({
6873
+ workingDirectory: options.workingDirectory,
6874
+ sessionId: options.sessionId,
6875
+ displayWidth: options.computerUseDisplayWidth,
6876
+ displayHeight: options.computerUseDisplayHeight
6877
+ });
6878
+ } else {
6879
+ tools.enable_computer_use = createEnableComputerUseTool({
6880
+ sessionId: options.sessionId
6881
+ });
6882
+ }
6883
+ }
6216
6884
  if (options.enableSemanticSearch !== false) {
6217
6885
  try {
6218
6886
  if (isVectorGatewayConfigured()) {
@@ -6229,6 +6897,7 @@ async function createTools(options) {
6229
6897
  if (options.taskTools) {
6230
6898
  tools.complete_task = createCompleteTaskTool(options.taskTools);
6231
6899
  tools.task_failed = createTaskFailedTool(options.taskTools);
6900
+ tools.ask_question_to_user = createAskQuestionToUserTool(options.taskTools);
6232
6901
  }
6233
6902
  return tools;
6234
6903
  }
@@ -6243,11 +6912,11 @@ init_db();
6243
6912
  init_todo();
6244
6913
  import os from "os";
6245
6914
  function getSearchInstructions() {
6246
- const platform3 = process.platform;
6915
+ const platform5 = process.platform;
6247
6916
  const common = `- **Prefer \`read_file\` over shell commands** for reading files - don't use \`cat\`, \`head\`, or \`tail\` when \`read_file\` is available
6248
6917
  - **Avoid unbounded searches** - always scope searches with glob patterns and directory paths to prevent overwhelming output
6249
6918
  - **Search strategically**: Start with specific patterns and directories, then broaden only if needed`;
6250
- if (platform3 === "win32") {
6919
+ if (platform5 === "win32") {
6251
6920
  return `${common}
6252
6921
  - **Find files**: \`dir /s /b *.ts\` or PowerShell: \`Get-ChildItem -Recurse -Filter *.ts\`
6253
6922
  - **Search content**: \`findstr /s /n "pattern" *.ts\` or PowerShell: \`Select-String -Pattern "pattern" -Path *.ts -Recurse\`
@@ -6294,13 +6963,13 @@ async function buildSystemPrompt(options) {
6294
6963
  );
6295
6964
  const hasNoTodos = todos.length === 0;
6296
6965
  const plansContext = formatPlansForContext(plans, allTodosDone || hasNoTodos);
6297
- const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
6966
+ const platform5 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
6298
6967
  const currentDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
6299
6968
  const searchInstructions = getSearchInstructions();
6300
6969
  const systemPrompt = `You are SparkECoder, an expert AI coding assistant. You help developers write, debug, and improve code.
6301
6970
 
6302
6971
  ## Environment
6303
- - **Platform**: ${platform3} (${os.release()})
6972
+ - **Platform**: ${platform5} (${os.release()})
6304
6973
  - **Date**: ${currentDate}
6305
6974
  - **Working Directory**: ${workingDirectory}
6306
6975
 
@@ -6636,9 +7305,10 @@ If you need to give the user a downloadable file (report, image, export, etc.),
6636
7305
  ### Rules
6637
7306
  1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
6638
7307
  2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
6639
- 3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
6640
- 4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
6641
- 5. Do NOT stop without calling one of these two tools.
7308
+ 3. If you are blocked by missing information, call \`ask_question_to_user\` with a concise question. The run will pause until the orchestrator or user answers, then you should continue from that answer.
7309
+ 4. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
7310
+ 5. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
7311
+ 6. Do NOT stop without calling \`complete_task\`, \`task_failed\`, or \`ask_question_to_user\` when blocked.
6642
7312
 
6643
7313
  ### Verification \u2014 BE EXTREMELY THOROUGH
6644
7314
  Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
@@ -6709,6 +7379,7 @@ ${JSON.stringify(outputSchema, null, 2)}
6709
7379
  ### Completion Tools
6710
7380
  - **\`complete_task({ result: ... })\`** \u2014 Call ONLY after thorough verification. The result is validated against the schema above. If validation fails you will get errors back \u2014 fix and retry.
6711
7381
  - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
7382
+ - **\`ask_question_to_user({ question, context?, choices? })\`** \u2014 Call only when you need information that is not available in the repo, task prompt, files, logs, or tools. Ask one clear question; after the answer is returned, continue working.
6712
7383
  `;
6713
7384
  }
6714
7385
  function createSummaryPrompt(conversationHistory) {
@@ -6864,6 +7535,7 @@ function sanitizeModelMessages(messages) {
6864
7535
 
6865
7536
  // src/agent/model-limits.ts
6866
7537
  var MODEL_LIMITS = {
7538
+ "anthropic/claude-opus-4.7": { contextWindow: 2e5, rollingTarget: 15e4 },
6867
7539
  "anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
6868
7540
  "anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
6869
7541
  "anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
@@ -7200,17 +7872,65 @@ function repairToolPairing(messages) {
7200
7872
 
7201
7873
  // src/agent/index.ts
7202
7874
  init_webhook();
7875
+
7876
+ // src/tasks/questions.ts
7877
+ var pendingQuestions = /* @__PURE__ */ new Map();
7878
+ var answeredQuestions = /* @__PURE__ */ new Map();
7879
+ function key(taskId, questionId) {
7880
+ return `${taskId}:${questionId}`;
7881
+ }
7882
+ function waitForTaskQuestionAnswer(question) {
7883
+ const k = key(question.taskId, question.questionId);
7884
+ if (pendingQuestions.has(k)) {
7885
+ return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
7886
+ }
7887
+ return new Promise((resolve11, reject) => {
7888
+ pendingQuestions.set(k, {
7889
+ ...question,
7890
+ createdAt: /* @__PURE__ */ new Date(),
7891
+ resolve: resolve11,
7892
+ reject
7893
+ });
7894
+ });
7895
+ }
7896
+ function answerTaskQuestion(taskId, questionId, answer) {
7897
+ const k = key(taskId, questionId);
7898
+ const pending = pendingQuestions.get(k);
7899
+ if (!pending) return answeredQuestions.has(k) ? "already_answered" : "not_found";
7900
+ pendingQuestions.delete(k);
7901
+ answeredQuestions.set(k, answer);
7902
+ pending.resolve(answer);
7903
+ return "answered";
7904
+ }
7905
+ function rejectTaskQuestions(taskId, reason) {
7906
+ for (const [k, pending] of pendingQuestions) {
7907
+ if (pending.taskId !== taskId) continue;
7908
+ pendingQuestions.delete(k);
7909
+ pending.reject(new Error(reason));
7910
+ }
7911
+ }
7912
+ function getPendingTaskQuestion(taskId, questionId) {
7913
+ const pending = pendingQuestions.get(key(taskId, questionId));
7914
+ if (!pending) return null;
7915
+ const { resolve: _resolve, reject: _reject, ...question } = pending;
7916
+ return question;
7917
+ }
7918
+ function getAnsweredTaskQuestion(taskId, questionId) {
7919
+ return answeredQuestions.get(key(taskId, questionId)) ?? null;
7920
+ }
7921
+
7922
+ // src/agent/index.ts
7203
7923
  var MAX_SSE_FIELD_LENGTH = 8 * 1024;
7204
7924
  var SSE_PREVIEW_LENGTH = 2 * 1024;
7205
7925
  function truncateWriteFileInput(input) {
7206
7926
  const out = { ...input };
7207
- for (const key of ["content", "old_string", "new_string"]) {
7208
- const val = out[key];
7927
+ for (const key2 of ["content", "old_string", "new_string"]) {
7928
+ const val = out[key2];
7209
7929
  if (typeof val === "string" && val.length > MAX_SSE_FIELD_LENGTH) {
7210
- out[key] = `${val.slice(0, SSE_PREVIEW_LENGTH)}
7930
+ out[key2] = `${val.slice(0, SSE_PREVIEW_LENGTH)}
7211
7931
  ... (truncated)`;
7212
- out[`${key}Truncated`] = true;
7213
- out[`${key}Length`] = val.length;
7932
+ out[`${key2}Truncated`] = true;
7933
+ out[`${key2}Length`] = val.length;
7214
7934
  }
7215
7935
  }
7216
7936
  return out;
@@ -7238,10 +7958,14 @@ var Agent = class _Agent {
7238
7958
  */
7239
7959
  async createToolsWithCallbacks(options) {
7240
7960
  const config = getConfig();
7961
+ const sessionConfig = this.session.config || {};
7241
7962
  return createTools({
7242
7963
  sessionId: this.session.id,
7243
7964
  workingDirectory: this.session.workingDirectory,
7244
7965
  skillsDirectories: config.resolvedSkillsDirectories,
7966
+ enableComputerUse: sessionConfig.computerUseEnabled === true,
7967
+ computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
7968
+ computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
7245
7969
  onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
7246
7970
  onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
7247
7971
  onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
@@ -7274,10 +7998,14 @@ var Agent = class _Agent {
7274
7998
  keepRecentMessages: config.context?.keepRecentMessages || 10,
7275
7999
  autoSummarize: config.context?.autoSummarize ?? true
7276
8000
  });
8001
+ const sessionConfig = session.config || {};
7277
8002
  const tools = await createTools({
7278
8003
  sessionId: session.id,
7279
8004
  workingDirectory: session.workingDirectory,
7280
- skillsDirectories: config.resolvedSkillsDirectories
8005
+ skillsDirectories: config.resolvedSkillsDirectories,
8006
+ enableComputerUse: sessionConfig.computerUseEnabled === true,
8007
+ computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
8008
+ computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
7281
8009
  });
7282
8010
  return new _Agent(session, context, tools);
7283
8011
  }
@@ -7372,13 +8100,7 @@ ${prompt}` });
7372
8100
  abortSignal: options.abortSignal,
7373
8101
  // Enable extended thinking/reasoning for models that support it
7374
8102
  providerOptions: useAnthropic ? {
7375
- anthropic: {
7376
- toolStreaming: true,
7377
- thinking: {
7378
- type: "enabled",
7379
- budgetTokens: 1e4
7380
- }
7381
- }
8103
+ anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
7382
8104
  } : void 0,
7383
8105
  onStepFinish: async (step) => {
7384
8106
  options.onStepFinish?.(step);
@@ -7425,12 +8147,7 @@ ${prompt}` });
7425
8147
  stopWhen: stepCountIs2(500),
7426
8148
  // Enable extended thinking/reasoning for models that support it
7427
8149
  providerOptions: useAnthropic ? {
7428
- anthropic: {
7429
- thinking: {
7430
- type: "enabled",
7431
- budgetTokens: 1e4
7432
- }
7433
- }
8150
+ anthropic: getAnthropicProviderOptions(this.session.model)
7434
8151
  } : void 0
7435
8152
  });
7436
8153
  const responseMessages = result.response.messages;
@@ -7450,10 +8167,10 @@ ${prompt}` });
7450
8167
  const maxIterations = options.taskConfig.maxIterations ?? 50;
7451
8168
  const webhookUrl = options.taskConfig.webhookUrl;
7452
8169
  const parentTaskId = options.taskConfig.parentTaskId;
7453
- const fireWebhook = (type, data) => {
8170
+ const fireWebhook = (type2, data) => {
7454
8171
  if (!webhookUrl) return;
7455
8172
  sendWebhook(webhookUrl, {
7456
- type,
8173
+ type: type2,
7457
8174
  taskId: this.session.id,
7458
8175
  sessionId: this.session.id,
7459
8176
  ...parentTaskId ? { parentTaskId } : {},
@@ -7496,10 +8213,14 @@ ${prompt}` });
7496
8213
  });
7497
8214
  }
7498
8215
  };
8216
+ const taskSessionConfig = this.session.config || {};
7499
8217
  const taskTools = await createTools({
7500
8218
  sessionId: this.session.id,
7501
8219
  workingDirectory: this.session.workingDirectory,
7502
8220
  skillsDirectories: config.resolvedSkillsDirectories,
8221
+ enableComputerUse: taskSessionConfig.computerUseEnabled === true,
8222
+ computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
8223
+ computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
7503
8224
  onBashProgress: bashProgressHandler,
7504
8225
  onWriteFileProgress: (progress) => {
7505
8226
  options.onToolProgress?.({ toolName: "write_file", data: progress });
@@ -7513,7 +8234,38 @@ ${prompt}` });
7513
8234
  },
7514
8235
  taskTools: {
7515
8236
  outputSchema: options.taskConfig.outputSchema,
7516
- onComplete
8237
+ onComplete,
8238
+ onQuestion: async (question) => {
8239
+ const payload = {
8240
+ questionId: question.questionId,
8241
+ question: question.question,
8242
+ context: question.context,
8243
+ choices: question.choices,
8244
+ status: "pending"
8245
+ };
8246
+ const answerPromise = waitForTaskQuestionAnswer({
8247
+ taskId: this.session.id,
8248
+ questionId: question.questionId,
8249
+ question: question.question,
8250
+ context: question.context,
8251
+ choices: question.choices
8252
+ });
8253
+ fireWebhook("task.question", payload);
8254
+ if (emit) {
8255
+ await emit(JSON.stringify({ type: "task-question", data: payload }));
8256
+ }
8257
+ const answer = await answerPromise;
8258
+ const answeredPayload = {
8259
+ questionId: question.questionId,
8260
+ answer: answer.answer,
8261
+ answeredBy: answer.answeredBy
8262
+ };
8263
+ fireWebhook("task.question_answered", answeredPayload);
8264
+ if (emit) {
8265
+ await emit(JSON.stringify({ type: "task-question-answered", data: answeredPayload }));
8266
+ }
8267
+ return answer;
8268
+ }
7517
8269
  }
7518
8270
  });
7519
8271
  const baseSystemPrompt = await buildSystemPrompt({
@@ -7558,10 +8310,7 @@ ${taskAddendum}`;
7558
8310
  stopWhen: stepCountIs2(500),
7559
8311
  abortSignal: options.abortSignal,
7560
8312
  providerOptions: useAnthropic ? {
7561
- anthropic: {
7562
- toolStreaming: true,
7563
- thinking: { type: "enabled", budgetTokens: 1e4 }
7564
- }
8313
+ anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
7565
8314
  } : void 0,
7566
8315
  onStepFinish: async (step) => {
7567
8316
  options.onStepFinish?.(step);
@@ -7782,11 +8531,11 @@ ${taskAddendum}`;
7782
8531
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
7783
8532
  if (!isRemoteConfigured2()) return [];
7784
8533
  const { readFile: readFile12 } = await import("fs/promises");
7785
- const { join: join13, basename: basename6 } = await import("path");
8534
+ const { join: join14, basename: basename6 } = await import("path");
7786
8535
  const urls = [];
7787
8536
  for (const filePath of filePaths) {
7788
8537
  try {
7789
- const fullPath = filePath.startsWith("/") ? filePath : join13(this.session.workingDirectory, filePath);
8538
+ const fullPath = filePath.startsWith("/") ? filePath : join14(this.session.workingDirectory, filePath);
7790
8539
  const fileName = basename6(fullPath);
7791
8540
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
7792
8541
  const mimeMap = {
@@ -7844,11 +8593,11 @@ ${taskAddendum}`;
7844
8593
  wrappedTools[name] = originalTool;
7845
8594
  continue;
7846
8595
  }
7847
- wrappedTools[name] = tool13({
8596
+ wrappedTools[name] = tool14({
7848
8597
  description: originalTool.description || "",
7849
- inputSchema: originalTool.inputSchema || z14.object({}),
8598
+ inputSchema: originalTool.inputSchema || z15.object({}),
7850
8599
  execute: async (input, toolOptions) => {
7851
- const toolCallId = toolOptions.toolCallId || nanoid4();
8600
+ const toolCallId = toolOptions.toolCallId || nanoid6();
7852
8601
  const execution = toolExecutionQueries.create({
7853
8602
  sessionId: this.session.id,
7854
8603
  toolName: name,
@@ -7866,10 +8615,10 @@ ${taskAddendum}`;
7866
8615
  const resolverData = approvalResolvers.get(toolCallId);
7867
8616
  approvalResolvers.delete(toolCallId);
7868
8617
  this.pendingApprovals.delete(toolCallId);
7869
- const exec7 = await execution;
8618
+ const exec8 = await execution;
7870
8619
  if (!approved) {
7871
8620
  const reason = resolverData?.reason || "User rejected the tool execution";
7872
- await toolExecutionQueries.reject(exec7.id);
8621
+ await toolExecutionQueries.reject(exec8.id);
7873
8622
  await sessionQueries.updateStatus(this.session.id, "active");
7874
8623
  return {
7875
8624
  status: "rejected",
@@ -7879,14 +8628,14 @@ ${taskAddendum}`;
7879
8628
  message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
7880
8629
  };
7881
8630
  }
7882
- await toolExecutionQueries.approve(exec7.id);
8631
+ await toolExecutionQueries.approve(exec8.id);
7883
8632
  await sessionQueries.updateStatus(this.session.id, "active");
7884
8633
  try {
7885
8634
  const result = await originalTool.execute(input, toolOptions);
7886
- await toolExecutionQueries.complete(exec7.id, result);
8635
+ await toolExecutionQueries.complete(exec8.id, result);
7887
8636
  return result;
7888
8637
  } catch (error) {
7889
- await toolExecutionQueries.complete(exec7.id, null, error.message);
8638
+ await toolExecutionQueries.complete(exec8.id, null, error.message);
7890
8639
  throw error;
7891
8640
  }
7892
8641
  }
@@ -7957,12 +8706,12 @@ ${taskAddendum}`;
7957
8706
 
7958
8707
  // src/server/index.ts
7959
8708
  import "dotenv/config";
7960
- import { Hono as Hono6 } from "hono";
8709
+ import { Hono as Hono7 } from "hono";
7961
8710
  import { serve } from "@hono/node-server";
7962
8711
  import { cors } from "hono/cors";
7963
8712
  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";
8713
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
8714
+ import { resolve as resolve10, dirname as dirname7, join as join13 } from "path";
7966
8715
  import { spawn as spawn2 } from "child_process";
7967
8716
  import { createServer as createNetServer } from "net";
7968
8717
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -7971,11 +8720,11 @@ import { fileURLToPath as fileURLToPath4 } from "url";
7971
8720
  init_db();
7972
8721
  import { Hono } from "hono";
7973
8722
  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";
8723
+ import { z as z16 } from "zod";
8724
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
7976
8725
  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";
8726
+ import { join as join10, basename as basename5, extname as extname8, relative as relative9 } from "path";
8727
+ import { nanoid as nanoid7 } from "nanoid";
7979
8728
  init_config();
7980
8729
 
7981
8730
  // src/server/devtools-store.ts
@@ -8007,18 +8756,20 @@ function cleanupPendingInputs() {
8007
8756
  }
8008
8757
  }
8009
8758
  }
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()
8759
+ var createSessionSchema = z16.object({
8760
+ name: z16.string().optional(),
8761
+ workingDirectory: z16.string().optional(),
8762
+ model: z16.string().optional(),
8763
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
8764
+ // Optional full session-config passthrough (computerUseEnabled, etc.)
8765
+ config: z16.record(z16.string(), z16.unknown()).optional()
8015
8766
  });
8016
- var paginationQuerySchema = z15.object({
8017
- limit: z15.string().optional(),
8018
- offset: z15.string().optional()
8767
+ var paginationQuerySchema = z16.object({
8768
+ limit: z16.string().optional(),
8769
+ offset: z16.string().optional()
8019
8770
  });
8020
- var messagesQuerySchema = z15.object({
8021
- limit: z15.string().optional()
8771
+ var messagesQuerySchema = z16.object({
8772
+ limit: z16.string().optional()
8022
8773
  });
8023
8774
  sessions.get(
8024
8775
  "/",
@@ -8056,11 +8807,20 @@ sessions.post(
8056
8807
  async (c) => {
8057
8808
  const body = c.req.valid("json");
8058
8809
  const config = getConfig();
8810
+ const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
8811
+ const baseConfig = body.config || {};
8812
+ const mergedConfig = {
8813
+ ...baseConfig,
8814
+ ...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
8815
+ // Turn on computer use by default if the server was launched with --enable-computer-use,
8816
+ // unless the client explicitly provided a value.
8817
+ ...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
8818
+ };
8059
8819
  const agent = await Agent.create({
8060
8820
  name: body.name,
8061
8821
  workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
8062
8822
  model: body.model || config.defaultModel,
8063
- sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
8823
+ sessionConfig: Object.keys(mergedConfig).length > 0 ? mergedConfig : void 0
8064
8824
  });
8065
8825
  const session = agent.getSession();
8066
8826
  return c.json({
@@ -8157,10 +8917,10 @@ sessions.get("/:id/tools", async (c) => {
8157
8917
  count: executions.length
8158
8918
  });
8159
8919
  });
8160
- var updateSessionSchema = z15.object({
8161
- model: z15.string().optional(),
8162
- name: z15.string().optional(),
8163
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
8920
+ var updateSessionSchema = z16.object({
8921
+ model: z16.string().optional(),
8922
+ name: z16.string().optional(),
8923
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
8164
8924
  });
8165
8925
  sessions.patch(
8166
8926
  "/:id",
@@ -8230,8 +8990,8 @@ sessions.post("/:id/clear", async (c) => {
8230
8990
  await agent.clearContext();
8231
8991
  return c.json({ success: true, sessionId: id });
8232
8992
  });
8233
- var pendingInputSchema = z15.object({
8234
- text: z15.string()
8993
+ var pendingInputSchema = z16.object({
8994
+ text: z16.string()
8235
8995
  });
8236
8996
  sessions.post(
8237
8997
  "/:id/pending-input",
@@ -8262,13 +9022,13 @@ sessions.get("/:id/pending-input", async (c) => {
8262
9022
  createdAt: pending.createdAt.toISOString()
8263
9023
  });
8264
9024
  });
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()
9025
+ var devtoolsContextSchema = z16.object({
9026
+ url: z16.string(),
9027
+ path: z16.string(),
9028
+ pageName: z16.string().optional(),
9029
+ screenWidth: z16.number().optional(),
9030
+ screenHeight: z16.number().optional(),
9031
+ devicePixelRatio: z16.number().optional()
8272
9032
  });
8273
9033
  sessions.post(
8274
9034
  "/:id/devtools-context",
@@ -8454,12 +9214,12 @@ sessions.get("/:id/diff/:filePath", async (c) => {
8454
9214
  });
8455
9215
  function getAttachmentsDir(sessionId) {
8456
9216
  const appDataDir = getAppDataDirectory();
8457
- return join9(appDataDir, "attachments", sessionId);
9217
+ return join10(appDataDir, "attachments", sessionId);
8458
9218
  }
8459
9219
  function ensureAttachmentsDir(sessionId) {
8460
9220
  const dir = getAttachmentsDir(sessionId);
8461
- if (!existsSync15(dir)) {
8462
- mkdirSync5(dir, { recursive: true });
9221
+ if (!existsSync16(dir)) {
9222
+ mkdirSync6(dir, { recursive: true });
8463
9223
  }
8464
9224
  return dir;
8465
9225
  }
@@ -8470,12 +9230,12 @@ sessions.get("/:id/attachments", async (c) => {
8470
9230
  return c.json({ error: "Session not found" }, 404);
8471
9231
  }
8472
9232
  const dir = getAttachmentsDir(sessionId);
8473
- if (!existsSync15(dir)) {
9233
+ if (!existsSync16(dir)) {
8474
9234
  return c.json({ sessionId, attachments: [], count: 0 });
8475
9235
  }
8476
9236
  const files = readdirSync2(dir);
8477
9237
  const attachments = files.map((filename) => {
8478
- const filePath = join9(dir, filename);
9238
+ const filePath = join10(dir, filename);
8479
9239
  const stats = statSync2(filePath);
8480
9240
  return {
8481
9241
  id: filename.split("_")[0],
@@ -8507,10 +9267,10 @@ sessions.post("/:id/attachments", async (c) => {
8507
9267
  return c.json({ error: "No file provided" }, 400);
8508
9268
  }
8509
9269
  const dir = ensureAttachmentsDir(sessionId);
8510
- const id = nanoid5(10);
9270
+ const id = nanoid7(10);
8511
9271
  const ext = extname8(file.name) || "";
8512
9272
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
8513
- const filePath = join9(dir, safeFilename);
9273
+ const filePath = join10(dir, safeFilename);
8514
9274
  const arrayBuffer = await file.arrayBuffer();
8515
9275
  writeFileSync3(filePath, Buffer.from(arrayBuffer));
8516
9276
  return c.json({
@@ -8533,10 +9293,10 @@ sessions.post("/:id/attachments", async (c) => {
8533
9293
  return c.json({ error: "Missing filename or data" }, 400);
8534
9294
  }
8535
9295
  const dir = ensureAttachmentsDir(sessionId);
8536
- const id = nanoid5(10);
9296
+ const id = nanoid7(10);
8537
9297
  const ext = extname8(body.filename) || "";
8538
9298
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
8539
- const filePath = join9(dir, safeFilename);
9299
+ const filePath = join10(dir, safeFilename);
8540
9300
  let base64Data = body.data;
8541
9301
  if (base64Data.includes(",")) {
8542
9302
  base64Data = base64Data.split(",")[1];
@@ -8565,7 +9325,7 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
8565
9325
  return c.json({ error: "Session not found" }, 404);
8566
9326
  }
8567
9327
  const dir = getAttachmentsDir(sessionId);
8568
- if (!existsSync15(dir)) {
9328
+ if (!existsSync16(dir)) {
8569
9329
  return c.json({ error: "Attachment not found" }, 404);
8570
9330
  }
8571
9331
  const files = readdirSync2(dir);
@@ -8573,14 +9333,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
8573
9333
  if (!file) {
8574
9334
  return c.json({ error: "Attachment not found" }, 404);
8575
9335
  }
8576
- const filePath = join9(dir, file);
8577
- unlinkSync2(filePath);
9336
+ const filePath = join10(dir, file);
9337
+ unlinkSync3(filePath);
8578
9338
  return c.json({ success: true, id: attachmentId });
8579
9339
  });
8580
- var filesQuerySchema = z15.object({
8581
- query: z15.string().optional(),
9340
+ var filesQuerySchema = z16.object({
9341
+ query: z16.string().optional(),
8582
9342
  // Filter query (e.g., "src/com" to match "src/components")
8583
- limit: z15.string().optional()
9343
+ limit: z16.string().optional()
8584
9344
  // Max results (default 50)
8585
9345
  });
8586
9346
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -8656,7 +9416,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
8656
9416
  const entries = await readdir6(currentDir, { withFileTypes: true });
8657
9417
  for (const entry of entries) {
8658
9418
  if (results.length >= limit * 2) break;
8659
- const fullPath = join9(currentDir, entry.name);
9419
+ const fullPath = join10(currentDir, entry.name);
8660
9420
  const relativePath = relative9(baseDir, fullPath);
8661
9421
  if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
8662
9422
  continue;
@@ -8704,7 +9464,7 @@ sessions.get(
8704
9464
  return c.json({ error: "Session not found" }, 404);
8705
9465
  }
8706
9466
  const workingDirectory = session.workingDirectory;
8707
- if (!existsSync15(workingDirectory)) {
9467
+ if (!existsSync16(workingDirectory)) {
8708
9468
  return c.json({
8709
9469
  sessionId,
8710
9470
  workingDirectory,
@@ -8814,9 +9574,9 @@ sessions.get("/:id/browser-recording", async (c) => {
8814
9574
  init_db();
8815
9575
  import { Hono as Hono2 } from "hono";
8816
9576
  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";
9577
+ import { z as z17 } from "zod";
9578
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
9579
+ import { join as join11 } from "path";
8820
9580
  init_config();
8821
9581
 
8822
9582
  // src/server/resumable-stream.ts
@@ -8825,9 +9585,9 @@ var store = /* @__PURE__ */ new Map();
8825
9585
  var channels = /* @__PURE__ */ new Map();
8826
9586
  var cleanupInterval = setInterval(() => {
8827
9587
  const now = Date.now();
8828
- for (const [key, data] of store.entries()) {
9588
+ for (const [key2, data] of store.entries()) {
8829
9589
  if (data.expiresAt && data.expiresAt < now) {
8830
- store.delete(key);
9590
+ store.delete(key2);
8831
9591
  }
8832
9592
  }
8833
9593
  }, 6e4);
@@ -8851,27 +9611,27 @@ var publisher = {
8851
9611
  }
8852
9612
  }
8853
9613
  },
8854
- set: async (key, value, options) => {
9614
+ set: async (key2, value, options) => {
8855
9615
  const expiresAt = options?.EX ? Date.now() + options.EX * 1e3 : void 0;
8856
- store.set(key, { value, expiresAt });
9616
+ store.set(key2, { value, expiresAt });
8857
9617
  if (options?.EX) {
8858
- setTimeout(() => store.delete(key), options.EX * 1e3);
9618
+ setTimeout(() => store.delete(key2), options.EX * 1e3);
8859
9619
  }
8860
9620
  },
8861
- get: async (key) => {
8862
- const data = store.get(key);
9621
+ get: async (key2) => {
9622
+ const data = store.get(key2);
8863
9623
  if (!data) return null;
8864
9624
  if (data.expiresAt && data.expiresAt < Date.now()) {
8865
- store.delete(key);
9625
+ store.delete(key2);
8866
9626
  return null;
8867
9627
  }
8868
9628
  return data.value;
8869
9629
  },
8870
- incr: async (key) => {
8871
- const data = store.get(key);
9630
+ incr: async (key2) => {
9631
+ const data = store.get(key2);
8872
9632
  const current = data ? parseInt(data.value, 10) : 0;
8873
9633
  const next = (isNaN(current) ? 0 : current) + 1;
8874
- store.set(key, { value: String(next), expiresAt: data?.expiresAt });
9634
+ store.set(key2, { value: String(next), expiresAt: data?.expiresAt });
8875
9635
  return next;
8876
9636
  }
8877
9637
  };
@@ -8903,7 +9663,7 @@ var streamContext = createResumableStreamContext({
8903
9663
  });
8904
9664
 
8905
9665
  // src/server/routes/agents.ts
8906
- import { nanoid as nanoid6 } from "nanoid";
9666
+ import { nanoid as nanoid8 } from "nanoid";
8907
9667
  init_stream_proxy();
8908
9668
  init_recorder();
8909
9669
  init_remote();
@@ -8994,40 +9754,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
8994
9754
  ${prompt}`;
8995
9755
  }
8996
9756
  var agents = new Hono2();
8997
- var attachmentSchema = z16.object({
8998
- type: z16.enum(["image", "file"]),
8999
- data: z16.string(),
9757
+ var attachmentSchema = z17.object({
9758
+ type: z17.enum(["image", "file"]),
9759
+ data: z17.string(),
9000
9760
  // base64 data URL or raw base64
9001
- mediaType: z16.string().optional(),
9002
- filename: z16.string().optional()
9761
+ mediaType: z17.string().optional(),
9762
+ filename: z17.string().optional()
9003
9763
  });
9004
- var runPromptSchema = z16.object({
9005
- prompt: z16.string(),
9764
+ var runPromptSchema = z17.object({
9765
+ prompt: z17.string(),
9006
9766
  // Can be empty if attachments are provided
9007
- attachments: z16.array(attachmentSchema).optional()
9767
+ attachments: z17.array(attachmentSchema).optional()
9008
9768
  }).refine(
9009
9769
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
9010
9770
  { message: "Either prompt or attachments must be provided" }
9011
9771
  );
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()
9772
+ var quickStartSchema = z17.object({
9773
+ prompt: z17.string().min(1),
9774
+ name: z17.string().optional(),
9775
+ workingDirectory: z17.string().optional(),
9776
+ model: z17.string().optional(),
9777
+ toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
9018
9778
  });
9019
- var rejectSchema = z16.object({
9020
- reason: z16.string().optional()
9779
+ var rejectSchema = z17.object({
9780
+ reason: z17.string().optional()
9021
9781
  }).optional();
9022
9782
  var streamAbortControllers = /* @__PURE__ */ new Map();
9023
9783
  function getAttachmentsDirectory(sessionId) {
9024
9784
  const appDataDir = getAppDataDirectory();
9025
- return join10(appDataDir, "attachments", sessionId);
9785
+ return join11(appDataDir, "attachments", sessionId);
9026
9786
  }
9027
9787
  async function saveAttachmentToDisk(sessionId, attachment, index) {
9028
9788
  const attachmentsDir = getAttachmentsDirectory(sessionId);
9029
- if (!existsSync16(attachmentsDir)) {
9030
- mkdirSync6(attachmentsDir, { recursive: true });
9789
+ if (!existsSync17(attachmentsDir)) {
9790
+ mkdirSync7(attachmentsDir, { recursive: true });
9031
9791
  }
9032
9792
  let filename = attachment.filename;
9033
9793
  if (!filename) {
@@ -9045,7 +9805,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
9045
9805
  attachment.mediaType = resized.mediaType;
9046
9806
  attachment.data = buffer.toString("base64");
9047
9807
  }
9048
- const filePath = join10(attachmentsDir, filename);
9808
+ const filePath = join11(attachmentsDir, filename);
9049
9809
  writeFileSync4(filePath, buffer);
9050
9810
  return filePath;
9051
9811
  }
@@ -9056,9 +9816,9 @@ function stripDataUrlPrefix2(data) {
9056
9816
  }
9057
9817
  return data;
9058
9818
  }
9059
- function getExtensionFromMediaType(mediaType, type) {
9819
+ function getExtensionFromMediaType(mediaType, type2) {
9060
9820
  if (!mediaType) {
9061
- return type === "image" ? ".png" : ".bin";
9821
+ return type2 === "image" ? ".png" : ".bin";
9062
9822
  }
9063
9823
  const mimeToExt = {
9064
9824
  "image/png": ".png",
@@ -9462,7 +10222,7 @@ ${prompt}` });
9462
10222
  userMessageContent = prompt;
9463
10223
  }
9464
10224
  await messageQueries.create(id, { role: "user", content: userMessageContent });
9465
- const streamId = `stream_${id}_${nanoid6(10)}`;
10225
+ const streamId = `stream_${id}_${nanoid8(10)}`;
9466
10226
  console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
9467
10227
  await activeStreamQueries.create(id, streamId);
9468
10228
  const stream = await streamContext.resumableStream(
@@ -9667,7 +10427,7 @@ agents.post(
9667
10427
  });
9668
10428
  const session = agent.getSession();
9669
10429
  const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
9670
- const streamId = `stream_${session.id}_${nanoid6(10)}`;
10430
+ const streamId = `stream_${session.id}_${nanoid8(10)}`;
9671
10431
  await createCheckpoint(session.id, session.workingDirectory, 0);
9672
10432
  await activeStreamQueries.create(session.id, streamId);
9673
10433
  const createQuickStreamProducer = () => {
@@ -9934,23 +10694,23 @@ agents.post(
9934
10694
  });
9935
10695
  }
9936
10696
  );
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()
10697
+ var browserInputSchema = z17.object({
10698
+ type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
10699
+ eventType: z17.string(),
10700
+ x: z17.number().optional(),
10701
+ y: z17.number().optional(),
10702
+ button: z17.string().optional(),
10703
+ clickCount: z17.number().optional(),
10704
+ deltaX: z17.number().optional(),
10705
+ deltaY: z17.number().optional(),
10706
+ key: z17.string().optional(),
10707
+ code: z17.string().optional(),
10708
+ text: z17.string().optional(),
10709
+ modifiers: z17.number().optional(),
10710
+ touchPoints: z17.array(z17.object({
10711
+ x: z17.number(),
10712
+ y: z17.number(),
10713
+ id: z17.number().optional()
9954
10714
  })).optional()
9955
10715
  });
9956
10716
  agents.post(
@@ -9985,27 +10745,279 @@ agents.get("/:id/browser-stream", async (c) => {
9985
10745
  init_config();
9986
10746
  import { Hono as Hono3 } from "hono";
9987
10747
  import { zValidator as zValidator3 } from "@hono/zod-validator";
9988
- import { z as z17 } from "zod";
9989
- import { readFileSync as readFileSync7 } from "fs";
10748
+ import { z as z18 } from "zod";
10749
+ import { readFileSync as readFileSync10 } from "fs";
9990
10750
  import { fileURLToPath as fileURLToPath3 } from "url";
9991
- import { dirname as dirname6, join as join11 } from "path";
10751
+ import { dirname as dirname6, join as join12 } from "path";
10752
+
10753
+ // src/personal-agent/heartbeat.ts
10754
+ import { execSync as execSync3 } from "child_process";
10755
+ import { readFileSync as readFileSync9 } from "fs";
10756
+ import { hostname as hostname2, platform as platform3 } from "os";
10757
+
10758
+ // src/personal-agent/system-metrics.ts
10759
+ import { execSync as execSync2 } from "child_process";
10760
+ import { readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
10761
+ import {
10762
+ arch,
10763
+ cpus,
10764
+ freemem,
10765
+ hostname,
10766
+ loadavg,
10767
+ networkInterfaces,
10768
+ platform as platform2,
10769
+ release,
10770
+ totalmem,
10771
+ type,
10772
+ uptime,
10773
+ userInfo
10774
+ } from "os";
10775
+ var _lastSample = null;
10776
+ function snapshotCpuTimes() {
10777
+ const sum = { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 };
10778
+ for (const c of cpus()) {
10779
+ sum.user += c.times.user;
10780
+ sum.nice += c.times.nice;
10781
+ sum.sys += c.times.sys;
10782
+ sum.idle += c.times.idle;
10783
+ sum.irq += c.times.irq;
10784
+ }
10785
+ return sum;
10786
+ }
10787
+ function readCpuUsage() {
10788
+ const now = snapshotCpuTimes();
10789
+ if (!_lastSample) {
10790
+ _lastSample = now;
10791
+ return 0;
10792
+ }
10793
+ const dUser = now.user - _lastSample.user;
10794
+ const dNice = now.nice - _lastSample.nice;
10795
+ const dSys = now.sys - _lastSample.sys;
10796
+ const dIdle = now.idle - _lastSample.idle;
10797
+ const dIrq = now.irq - _lastSample.irq;
10798
+ const total = dUser + dNice + dSys + dIdle + dIrq;
10799
+ _lastSample = now;
10800
+ if (total <= 0) return 0;
10801
+ return Math.max(0, Math.min(1, (total - dIdle) / total));
10802
+ }
10803
+ snapshotCpuTimes();
10804
+ function readCpuTempC() {
10805
+ try {
10806
+ if (platform2() === "linux") {
10807
+ let hottest = -Infinity;
10808
+ try {
10809
+ for (const entry of readdirSync3("/sys/class/thermal")) {
10810
+ if (!entry.startsWith("thermal_zone")) continue;
10811
+ try {
10812
+ const v = Number(
10813
+ readFileSync8(`/sys/class/thermal/${entry}/temp`, "utf8").trim()
10814
+ );
10815
+ if (Number.isFinite(v) && v > hottest) hottest = v;
10816
+ } catch {
10817
+ }
10818
+ }
10819
+ } catch {
10820
+ }
10821
+ if (hottest > -Infinity) return hottest / 1e3;
10822
+ }
10823
+ const overrideCmd = process.env.PERSONAL_AGENT_TEMP_CMD;
10824
+ if (overrideCmd) {
10825
+ const out = execSync2(overrideCmd, { encoding: "utf8", timeout: 1500 }).trim();
10826
+ const v = Number(out);
10827
+ if (Number.isFinite(v)) return v;
10828
+ }
10829
+ } catch {
10830
+ }
10831
+ return void 0;
10832
+ }
10833
+ function readDisks() {
10834
+ try {
10835
+ const p = platform2();
10836
+ if (p === "darwin" || p === "linux") {
10837
+ const raw = execSync2("df -kP", { encoding: "utf8", timeout: 2e3 });
10838
+ const lines = raw.trim().split("\n").slice(1);
10839
+ const out = [];
10840
+ for (const line of lines) {
10841
+ const parts = line.trim().split(/\s+/);
10842
+ if (parts.length < 6) continue;
10843
+ const filesystem = parts[0];
10844
+ const total1k = Number(parts[1]);
10845
+ const used1k = Number(parts[2]);
10846
+ const free1k = Number(parts[3]);
10847
+ const mount = parts.slice(5).join(" ");
10848
+ if (!Number.isFinite(total1k) || total1k <= 0) continue;
10849
+ 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")) {
10850
+ if (mount !== "/") continue;
10851
+ }
10852
+ out.push({
10853
+ mount,
10854
+ filesystem,
10855
+ totalBytes: total1k * 1024,
10856
+ usedBytes: used1k * 1024,
10857
+ freeBytes: free1k * 1024,
10858
+ usage: total1k > 0 ? used1k / total1k : 0
10859
+ });
10860
+ }
10861
+ out.sort((a, b) => {
10862
+ const score = (m) => m === "/" ? 0 : m.startsWith("/Users") || m.startsWith("/home") ? 1 : 2;
10863
+ return score(a.mount) - score(b.mount);
10864
+ });
10865
+ return out.slice(0, 6);
10866
+ }
10867
+ if (p === "win32") {
10868
+ const raw = execSync2(
10869
+ "wmic logicaldisk get DeviceID,Size,FreeSpace /format:csv",
10870
+ { encoding: "utf8", timeout: 3e3 }
10871
+ );
10872
+ const out = [];
10873
+ for (const line of raw.trim().split(/\r?\n/).slice(1)) {
10874
+ const cols = line.split(",");
10875
+ if (cols.length < 4) continue;
10876
+ const [, deviceId, freeStr, sizeStr] = cols;
10877
+ const total = Number(sizeStr);
10878
+ const free = Number(freeStr);
10879
+ if (!Number.isFinite(total) || total <= 0) continue;
10880
+ const used = Math.max(0, total - free);
10881
+ out.push({
10882
+ mount: deviceId,
10883
+ totalBytes: total,
10884
+ usedBytes: used,
10885
+ freeBytes: free,
10886
+ usage: used / total
10887
+ });
10888
+ }
10889
+ return out;
10890
+ }
10891
+ } catch {
10892
+ }
10893
+ return void 0;
10894
+ }
10895
+ function readNetwork() {
10896
+ try {
10897
+ const out = [];
10898
+ const ifaces = networkInterfaces();
10899
+ for (const [name, addrs] of Object.entries(ifaces)) {
10900
+ if (!addrs) continue;
10901
+ for (const a of addrs) {
10902
+ if (a.internal) continue;
10903
+ out.push({
10904
+ iface: name,
10905
+ family: a.family,
10906
+ address: a.address,
10907
+ mac: a.mac,
10908
+ internal: a.internal
10909
+ });
10910
+ }
10911
+ }
10912
+ return out;
10913
+ } catch {
10914
+ return void 0;
10915
+ }
10916
+ }
10917
+ function readSystemMetrics() {
10918
+ const cpuList = cpus();
10919
+ const usage = readCpuUsage();
10920
+ const tot = totalmem();
10921
+ const free = freemem();
10922
+ const metrics = {
10923
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
10924
+ hostname: hostname(),
10925
+ platform: platform2(),
10926
+ arch: arch(),
10927
+ kernelRelease: release(),
10928
+ osType: type(),
10929
+ processUptimeSec: Math.round(process.uptime()),
10930
+ systemUptimeSec: Math.round(uptime()),
10931
+ user: safeUser(),
10932
+ cpu: cpuList[0] ? {
10933
+ model: cpuList[0].model,
10934
+ count: cpuList.length,
10935
+ speedMhz: cpuList[0].speed,
10936
+ loadAvg1: loadavg()[0],
10937
+ loadAvg5: loadavg()[1],
10938
+ loadAvg15: loadavg()[2],
10939
+ usage,
10940
+ tempC: readCpuTempC()
10941
+ } : void 0,
10942
+ memory: {
10943
+ totalBytes: tot,
10944
+ freeBytes: free,
10945
+ usedBytes: Math.max(0, tot - free),
10946
+ usage: tot > 0 ? (tot - free) / tot : 0
10947
+ },
10948
+ disks: readDisks(),
10949
+ network: readNetwork()
10950
+ };
10951
+ return metrics;
10952
+ }
10953
+ function safeUser() {
10954
+ try {
10955
+ return userInfo().username;
10956
+ } catch {
10957
+ return process.env.USER ?? process.env.USERNAME ?? "unknown";
10958
+ }
10959
+ }
10960
+
10961
+ // src/personal-agent/heartbeat.ts
10962
+ var _cachedHwid = null;
10963
+ function getHardwareIdCached() {
10964
+ if (_cachedHwid !== null) return _cachedHwid;
10965
+ _cachedHwid = getHardwareId();
10966
+ return _cachedHwid;
10967
+ }
10968
+ function getHardwareId() {
10969
+ const p = platform3();
10970
+ try {
10971
+ if (p === "darwin") {
10972
+ const out = execSync3(
10973
+ `ioreg -rd1 -c IOPlatformExpertDevice | awk -F\\" '/IOPlatformUUID/ {print $4}'`,
10974
+ { encoding: "utf8", timeout: 2e3 }
10975
+ ).trim();
10976
+ if (out) return normalize2(out);
10977
+ } else if (p === "linux") {
10978
+ try {
10979
+ return normalize2(readFileSync9("/etc/machine-id", "utf8").trim());
10980
+ } catch {
10981
+ return normalize2(readFileSync9("/var/lib/dbus/machine-id", "utf8").trim());
10982
+ }
10983
+ } else if (p === "win32") {
10984
+ const out = execSync3("wmic csproduct get uuid /value", {
10985
+ encoding: "utf8",
10986
+ timeout: 3e3
10987
+ });
10988
+ const m = out.match(/UUID=([\w-]+)/i);
10989
+ if (m && m[1]) return normalize2(m[1]);
10990
+ }
10991
+ } catch (e) {
10992
+ console.warn(`[personal-agent] could not read hardware UUID: ${e.message}`);
10993
+ }
10994
+ console.warn(
10995
+ "[personal-agent] falling back to hostname for HWID; this is NOT stable across rename/reinstall"
10996
+ );
10997
+ return `host-${hostname2()}`;
10998
+ }
10999
+ function normalize2(raw) {
11000
+ return raw.trim().toLowerCase().replace(/[^a-z0-9-]/g, "");
11001
+ }
11002
+
11003
+ // src/server/routes/health.ts
9992
11004
  var __filename = fileURLToPath3(import.meta.url);
9993
11005
  var __dirname = dirname6(__filename);
9994
11006
  var possiblePaths = [
9995
- join11(__dirname, "../package.json"),
11007
+ join12(__dirname, "../package.json"),
9996
11008
  // From dist/server -> dist/../package.json
9997
- join11(__dirname, "../../package.json"),
11009
+ join12(__dirname, "../../package.json"),
9998
11010
  // From dist/server (if nested differently)
9999
- join11(__dirname, "../../../package.json"),
11011
+ join12(__dirname, "../../../package.json"),
10000
11012
  // From src/server/routes (development)
10001
- join11(process.cwd(), "package.json")
11013
+ join12(process.cwd(), "package.json")
10002
11014
  // From current working directory
10003
11015
  ];
10004
11016
  var currentVersion = "0.0.0";
10005
11017
  var packageName = "sparkecoder";
10006
11018
  for (const packageJsonPath of possiblePaths) {
10007
11019
  try {
10008
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
11020
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
10009
11021
  if (packageJson.name === "sparkecoder") {
10010
11022
  currentVersion = packageJson.version || "0.0.0";
10011
11023
  packageName = packageJson.name || "sparkecoder";
@@ -10019,12 +11031,20 @@ health.get("/", async (c) => {
10019
11031
  const config = getConfig();
10020
11032
  const apiKeyStatus = getApiKeyStatus();
10021
11033
  const gatewayKey = apiKeyStatus.find((s) => s.provider === "ai-gateway");
10022
- const hasApiKey = gatewayKey?.configured ?? false;
11034
+ const remoteInference = isRemoteInferenceConfigured();
11035
+ const hasApiKey = remoteInference || (gatewayKey?.configured ?? false);
11036
+ let hwid;
11037
+ try {
11038
+ hwid = getHardwareIdCached();
11039
+ } catch {
11040
+ }
10023
11041
  return c.json({
10024
11042
  status: "ok",
10025
11043
  version: currentVersion,
10026
11044
  uptime: process.uptime(),
10027
11045
  apiKeyConfigured: hasApiKey,
11046
+ inferenceMode: remoteInference ? "remote" : "local",
11047
+ hwid,
10028
11048
  config: {
10029
11049
  workingDirectory: config.resolvedWorkingDirectory,
10030
11050
  defaultModel: config.defaultModel,
@@ -10095,9 +11115,9 @@ health.get("/api-keys", async (c) => {
10095
11115
  supportedProviders: SUPPORTED_PROVIDERS
10096
11116
  });
10097
11117
  });
10098
- var setApiKeySchema = z17.object({
10099
- provider: z17.string(),
10100
- apiKey: z17.string().min(1)
11118
+ var setApiKeySchema = z18.object({
11119
+ provider: z18.string(),
11120
+ apiKey: z18.string().min(1)
10101
11121
  });
10102
11122
  health.post(
10103
11123
  "/api-keys",
@@ -10136,13 +11156,13 @@ health.delete("/api-keys/:provider", async (c) => {
10136
11156
  // src/server/routes/terminals.ts
10137
11157
  import { Hono as Hono4 } from "hono";
10138
11158
  import { zValidator as zValidator4 } from "@hono/zod-validator";
10139
- import { z as z18 } from "zod";
11159
+ import { z as z19 } from "zod";
10140
11160
  init_db();
10141
11161
  var terminals = new Hono4();
10142
- var spawnSchema = z18.object({
10143
- command: z18.string(),
10144
- cwd: z18.string().optional(),
10145
- name: z18.string().optional()
11162
+ var spawnSchema = z19.object({
11163
+ command: z19.string(),
11164
+ cwd: z19.string().optional(),
11165
+ name: z19.string().optional()
10146
11166
  });
10147
11167
  terminals.post(
10148
11168
  "/:sessionId/terminals",
@@ -10223,8 +11243,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
10223
11243
  // We don't track exit codes in tmux mode
10224
11244
  });
10225
11245
  });
10226
- var logsQuerySchema = z18.object({
10227
- tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
11246
+ var logsQuerySchema = z19.object({
11247
+ tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
10228
11248
  });
10229
11249
  terminals.get(
10230
11250
  "/:sessionId/terminals/:terminalId/logs",
@@ -10248,8 +11268,8 @@ terminals.get(
10248
11268
  });
10249
11269
  }
10250
11270
  );
10251
- var killSchema = z18.object({
10252
- signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
11271
+ var killSchema = z19.object({
11272
+ signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
10253
11273
  });
10254
11274
  terminals.post(
10255
11275
  "/:sessionId/terminals/:terminalId/kill",
@@ -10263,8 +11283,8 @@ terminals.post(
10263
11283
  return c.json({ success: true, message: "Terminal killed" });
10264
11284
  }
10265
11285
  );
10266
- var writeSchema = z18.object({
10267
- input: z18.string()
11286
+ var writeSchema = z19.object({
11287
+ input: z19.string()
10268
11288
  });
10269
11289
  terminals.post(
10270
11290
  "/:sessionId/terminals/:terminalId/write",
@@ -10449,20 +11469,20 @@ data: ${JSON.stringify({ status: "stopped" })}
10449
11469
  init_db();
10450
11470
  import { Hono as Hono5 } from "hono";
10451
11471
  import { zValidator as zValidator5 } from "@hono/zod-validator";
10452
- import { z as z19 } from "zod";
10453
- import { nanoid as nanoid7 } from "nanoid";
11472
+ import { z as z20 } from "zod";
11473
+ import { nanoid as nanoid9 } from "nanoid";
10454
11474
  init_config();
10455
11475
  var tasks = new Hono5();
10456
11476
  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()
11477
+ var createTaskSchema = z20.object({
11478
+ prompt: z20.string().min(1),
11479
+ outputSchema: z20.record(z20.string(), z20.unknown()),
11480
+ webhookUrl: z20.string().url().optional(),
11481
+ model: z20.string().optional(),
11482
+ workingDirectory: z20.string().optional(),
11483
+ name: z20.string().optional(),
11484
+ maxIterations: z20.number().int().min(1).max(500).optional(),
11485
+ parentTaskId: z20.string().optional()
10466
11486
  });
10467
11487
  tasks.post(
10468
11488
  "/",
@@ -10524,7 +11544,7 @@ tasks.post(
10524
11544
  const taskId = agent.sessionId;
10525
11545
  const abortController = new AbortController();
10526
11546
  taskAbortControllers.set(taskId, abortController);
10527
- const streamId = `stream_${taskId}_${nanoid7(10)}`;
11547
+ const streamId = `stream_${taskId}_${nanoid9(10)}`;
10528
11548
  await activeStreamQueries.create(taskId, streamId);
10529
11549
  const taskStreamProducer = () => {
10530
11550
  const { readable, writable } = new TransformStream();
@@ -10651,6 +11671,7 @@ tasks.post("/:id/cancel", async (c) => {
10651
11671
  abortController.abort();
10652
11672
  taskAbortControllers.delete(id);
10653
11673
  }
11674
+ rejectTaskQuestions(id, "Task cancelled by user");
10654
11675
  const cancelledTask = {
10655
11676
  ...task,
10656
11677
  status: "failed",
@@ -10672,19 +11693,629 @@ tasks.post("/:id/cancel", async (c) => {
10672
11693
  }
10673
11694
  return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
10674
11695
  });
11696
+ var answerQuestionSchema = z20.object({
11697
+ answer: z20.string().min(1),
11698
+ answeredBy: z20.enum(["orchestrator", "user", "system"]).optional()
11699
+ });
11700
+ tasks.post(
11701
+ "/:id/questions/:questionId/answer",
11702
+ zValidator5("json", answerQuestionSchema),
11703
+ async (c) => {
11704
+ const id = c.req.param("id");
11705
+ const questionId = c.req.param("questionId");
11706
+ const body = c.req.valid("json");
11707
+ const session = await sessionQueries.getById(id);
11708
+ if (!session) {
11709
+ return c.json({ error: "Task not found" }, 404);
11710
+ }
11711
+ const task = session.config?.task;
11712
+ if (!task?.enabled) {
11713
+ return c.json({ error: "Session is not a task" }, 400);
11714
+ }
11715
+ const pending = getPendingTaskQuestion(id, questionId);
11716
+ if (!pending) {
11717
+ if (getAnsweredTaskQuestion(id, questionId)) {
11718
+ return c.json({
11719
+ taskId: id,
11720
+ questionId,
11721
+ status: "answered",
11722
+ alreadyAnswered: true
11723
+ });
11724
+ }
11725
+ return c.json({ error: "Question is not pending" }, 404);
11726
+ }
11727
+ const answerStatus = answerTaskQuestion(id, questionId, {
11728
+ answer: body.answer,
11729
+ answeredBy: body.answeredBy
11730
+ });
11731
+ if (answerStatus === "not_found") {
11732
+ return c.json({ error: "Question is not pending" }, 404);
11733
+ }
11734
+ return c.json({
11735
+ taskId: id,
11736
+ questionId,
11737
+ status: "answered",
11738
+ alreadyAnswered: answerStatus === "already_answered"
11739
+ });
11740
+ }
11741
+ );
10675
11742
  var tasks_default = tasks;
10676
11743
 
11744
+ // src/server/routes/system.ts
11745
+ import { Hono as Hono6 } from "hono";
11746
+ import { streamSSE } from "hono/streaming";
11747
+ var system = new Hono6();
11748
+ system.get("/metrics", (c) => {
11749
+ return c.json(readSystemMetrics());
11750
+ });
11751
+ system.get("/metrics/stream", (c) => {
11752
+ return streamSSE(c, async (stream) => {
11753
+ const intervalMs = Number(c.req.query("intervalMs") ?? 2e3);
11754
+ const safeMs = Number.isFinite(intervalMs) ? Math.max(500, Math.min(6e4, intervalMs)) : 2e3;
11755
+ let id = 0;
11756
+ let aborted = false;
11757
+ c.req.raw.signal.addEventListener("abort", () => {
11758
+ aborted = true;
11759
+ });
11760
+ while (!aborted) {
11761
+ try {
11762
+ const snap = readSystemMetrics();
11763
+ await stream.writeSSE({
11764
+ id: String(id++),
11765
+ event: "metrics",
11766
+ data: JSON.stringify(snap)
11767
+ });
11768
+ } catch (e) {
11769
+ await stream.writeSSE({
11770
+ id: String(id++),
11771
+ event: "error",
11772
+ data: JSON.stringify({ error: e.message })
11773
+ });
11774
+ }
11775
+ await stream.sleep(safeMs);
11776
+ }
11777
+ });
11778
+ });
11779
+
11780
+ // src/personal-agent/hwid-middleware.ts
11781
+ var SKIP_PATHS = /* @__PURE__ */ new Set(["/health", "/health/", "/health/ready", "/health/version"]);
11782
+ function personalAgentConfigured() {
11783
+ 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);
11784
+ }
11785
+ function hwidMiddleware() {
11786
+ const enabled = personalAgentConfigured();
11787
+ let warnedMissing = false;
11788
+ return async (c, next) => {
11789
+ if (!enabled) return next();
11790
+ const path = c.req.path;
11791
+ if (SKIP_PATHS.has(path) || path.startsWith("/health/api-keys")) {
11792
+ return next();
11793
+ }
11794
+ const got = c.req.header("x-device-hwid");
11795
+ if (!got) {
11796
+ if (!warnedMissing) {
11797
+ warnedMissing = true;
11798
+ console.warn(
11799
+ `[personal-agent] request to ${path} arrived without X-Device-Hwid; allowing (backwards compat)`
11800
+ );
11801
+ }
11802
+ return next();
11803
+ }
11804
+ const expected = getHardwareIdCached();
11805
+ if (got !== expected) {
11806
+ console.warn(
11807
+ `[personal-agent] HWID mismatch on ${path}: got=${got.slice(0, 12)}\u2026, expected=${expected.slice(0, 12)}\u2026`
11808
+ );
11809
+ return c.json(
11810
+ {
11811
+ error: "hwid mismatch",
11812
+ 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.",
11813
+ expected: expected.slice(0, 12) + "\u2026",
11814
+ got: got.slice(0, 12) + "\u2026"
11815
+ },
11816
+ 409
11817
+ );
11818
+ }
11819
+ return next();
11820
+ };
11821
+ }
11822
+
11823
+ // src/personal-agent/signature-verify.ts
11824
+ import { createHash as createHash3, createPublicKey, verify as cryptoVerify } from "crypto";
11825
+ import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
11826
+ var REPLAY_WINDOW_SECONDS = 5 * 60;
11827
+ var _cachedKey = null;
11828
+ var _cachedFromInput = null;
11829
+ function loadPublicKey(input) {
11830
+ if (_cachedFromInput === input && _cachedKey) return _cachedKey;
11831
+ let pem = input;
11832
+ if (!input.includes("BEGIN") && existsSync18(input)) {
11833
+ pem = readFileSync11(input, "utf8");
11834
+ }
11835
+ const key2 = createPublicKey({ key: pem, format: "pem" });
11836
+ if (key2.asymmetricKeyType !== "ed25519") {
11837
+ throw new Error(
11838
+ `expected an ed25519 public key, got ${key2.asymmetricKeyType}. Generate with personal-agents/scripts/generate-signing-keys.mjs.`
11839
+ );
11840
+ }
11841
+ _cachedKey = key2;
11842
+ _cachedFromInput = input;
11843
+ return key2;
11844
+ }
11845
+ function bodyHashB64(body) {
11846
+ const hash = createHash3("sha256");
11847
+ if (body == null || body === "") {
11848
+ } else if (typeof body === "string") {
11849
+ hash.update(body, "utf8");
11850
+ } else if (Buffer.isBuffer(body)) {
11851
+ hash.update(body);
11852
+ } else {
11853
+ hash.update(Buffer.from(body));
11854
+ }
11855
+ return hash.digest("base64");
11856
+ }
11857
+ function canonicalSigningString(args) {
11858
+ return [
11859
+ args.method.toUpperCase(),
11860
+ args.path,
11861
+ String(args.timestamp),
11862
+ args.bodyHashB64
11863
+ ].join("\n");
11864
+ }
11865
+ function fromBase64Url(s) {
11866
+ const padded = s.padEnd(s.length + (4 - s.length % 4) % 4, "=");
11867
+ const std = padded.replace(/-/g, "+").replace(/_/g, "/");
11868
+ return Buffer.from(std, "base64");
11869
+ }
11870
+ function verifyEmbedToken(args) {
11871
+ const dot = args.token.indexOf(".");
11872
+ if (dot < 1 || dot >= args.token.length - 1) {
11873
+ return { ok: false, reason: "malformed" };
11874
+ }
11875
+ const payloadB64 = args.token.slice(0, dot);
11876
+ const sigB64 = args.token.slice(dot + 1);
11877
+ let sigBuf;
11878
+ try {
11879
+ sigBuf = fromBase64Url(sigB64);
11880
+ } catch {
11881
+ return { ok: false, reason: "bad-encoding" };
11882
+ }
11883
+ const sigOk = cryptoVerify(
11884
+ null,
11885
+ Buffer.from(payloadB64, "utf8"),
11886
+ args.publicKey,
11887
+ sigBuf
11888
+ );
11889
+ if (!sigOk) return { ok: false, reason: "signature-mismatch" };
11890
+ let payload;
11891
+ try {
11892
+ const json = JSON.parse(fromBase64Url(payloadB64).toString("utf8"));
11893
+ if (!json || typeof json !== "object" || typeof json.sid !== "string" || typeof json.exp !== "number") {
11894
+ return { ok: false, reason: "bad-payload" };
11895
+ }
11896
+ payload = { sid: json.sid, exp: json.exp };
11897
+ } catch (e) {
11898
+ return { ok: false, reason: "bad-payload", detail: e.message };
11899
+ }
11900
+ const now = args.now ?? Math.floor(Date.now() / 1e3);
11901
+ if (payload.exp < now) {
11902
+ return {
11903
+ ok: false,
11904
+ reason: "expired",
11905
+ detail: `${now - payload.exp}s past expiry`
11906
+ };
11907
+ }
11908
+ return { ok: true, payload };
11909
+ }
11910
+ function verifyRequest(args) {
11911
+ if (!args.signatureB64 || !args.timestampSeconds || !args.algorithm) {
11912
+ return { ok: false, reason: "missing-headers" };
11913
+ }
11914
+ if (args.algorithm.toLowerCase() !== "ed25519") {
11915
+ return { ok: false, reason: "bad-algorithm", detail: args.algorithm };
11916
+ }
11917
+ const ts = Number(args.timestampSeconds);
11918
+ if (!Number.isFinite(ts)) {
11919
+ return { ok: false, reason: "bad-timestamp", detail: args.timestampSeconds };
11920
+ }
11921
+ const now = args.now ?? Math.floor(Date.now() / 1e3);
11922
+ if (Math.abs(now - ts) > REPLAY_WINDOW_SECONDS) {
11923
+ return {
11924
+ ok: false,
11925
+ reason: "stale-timestamp",
11926
+ detail: `${Math.abs(now - ts)}s outside the ${REPLAY_WINDOW_SECONDS}s window`
11927
+ };
11928
+ }
11929
+ let sigBuf;
11930
+ try {
11931
+ sigBuf = Buffer.from(args.signatureB64, "base64");
11932
+ } catch {
11933
+ return { ok: false, reason: "bad-signature-encoding" };
11934
+ }
11935
+ const canonical = canonicalSigningString({
11936
+ method: args.method,
11937
+ path: args.path,
11938
+ timestamp: ts,
11939
+ bodyHashB64: bodyHashB64(args.body)
11940
+ });
11941
+ const ok = cryptoVerify(null, Buffer.from(canonical, "utf8"), args.publicKey, sigBuf);
11942
+ return ok ? { ok: true } : { ok: false, reason: "signature-mismatch" };
11943
+ }
11944
+
11945
+ // src/personal-agent/signature-middleware.ts
11946
+ var SKIP_PATHS2 = /* @__PURE__ */ new Set(["/health", "/health/", "/health/ready", "/health/version"]);
11947
+ function isSkipped(path) {
11948
+ return SKIP_PATHS2.has(path) || path.startsWith("/health/api-keys");
11949
+ }
11950
+ function pathBindsSessionId(path, sid) {
11951
+ const cleanPath = path.split("?")[0];
11952
+ const segments = cleanPath.split("/").filter(Boolean);
11953
+ return segments.includes(sid);
11954
+ }
11955
+ function signatureMiddleware(opts = {}) {
11956
+ const acceptSignedOnly = opts.acceptSignedOnly ?? process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY === "1";
11957
+ const publicKeyInput = opts.publicKeyInput ?? process.env.PERSONAL_AGENT_PUBLIC_KEY ?? "";
11958
+ if (acceptSignedOnly && !publicKeyInput) {
11959
+ return async (c) => c.json(
11960
+ {
11961
+ error: "signature middleware misconfigured",
11962
+ message: "started with --accept-signed-only but no --personal-agent-public-key / PERSONAL_AGENT_PUBLIC_KEY supplied. Refusing all non-/health traffic."
11963
+ },
11964
+ 500
11965
+ );
11966
+ }
11967
+ if (!publicKeyInput) {
11968
+ return async (_c, next) => next();
11969
+ }
11970
+ const publicKey = loadPublicKey(publicKeyInput);
11971
+ let warnedUnsigned = false;
11972
+ return async (c, next) => {
11973
+ const path = c.req.path;
11974
+ if (isSkipped(path)) return next();
11975
+ const sig = c.req.header("x-signature");
11976
+ const ts = c.req.header("x-signature-timestamp");
11977
+ const alg = c.req.header("x-signature-algorithm");
11978
+ if (!sig && (c.req.method === "GET" || c.req.method === "HEAD")) {
11979
+ const embedTok = c.req.header("x-embed-token") ?? c.req.query("embed_token");
11980
+ if (embedTok) {
11981
+ const result2 = verifyEmbedToken({ publicKey, token: embedTok });
11982
+ if (!result2.ok) {
11983
+ return c.json(
11984
+ {
11985
+ error: "embed token verification failed",
11986
+ reason: result2.reason,
11987
+ detail: result2.detail
11988
+ },
11989
+ 401
11990
+ );
11991
+ }
11992
+ if (!pathBindsSessionId(path, result2.payload.sid)) {
11993
+ return c.json(
11994
+ {
11995
+ error: "embed token scoped to a different session",
11996
+ detail: `token sid=${result2.payload.sid} but request path=${path}`
11997
+ },
11998
+ 403
11999
+ );
12000
+ }
12001
+ return next();
12002
+ }
12003
+ }
12004
+ if (!sig) {
12005
+ if (acceptSignedOnly) {
12006
+ return c.json(
12007
+ {
12008
+ error: "signature required",
12009
+ message: "this sparkecoder is started with --accept-signed-only; every request must carry X-Signature, X-Signature-Timestamp, X-Signature-Algorithm."
12010
+ },
12011
+ 401
12012
+ );
12013
+ }
12014
+ if (!warnedUnsigned) {
12015
+ warnedUnsigned = true;
12016
+ console.warn(
12017
+ `[personal-agent] inbound ${c.req.method} ${path} arrived without X-Signature; allowed because --accept-signed-only is off`
12018
+ );
12019
+ }
12020
+ return next();
12021
+ }
12022
+ let body;
12023
+ if (c.req.method !== "GET" && c.req.method !== "HEAD") {
12024
+ body = Buffer.from(await c.req.raw.clone().arrayBuffer());
12025
+ }
12026
+ const result = verifyRequest({
12027
+ publicKey,
12028
+ method: c.req.method,
12029
+ path,
12030
+ body,
12031
+ signatureB64: sig,
12032
+ timestampSeconds: ts,
12033
+ algorithm: alg
12034
+ });
12035
+ if (!result.ok) {
12036
+ console.warn(
12037
+ `[personal-agent] signature verification failed on ${c.req.method} ${path}: ${result.reason}${result.detail ? ` (${result.detail})` : ""}`
12038
+ );
12039
+ return c.json(
12040
+ {
12041
+ error: "signature verification failed",
12042
+ reason: result.reason,
12043
+ detail: result.detail
12044
+ },
12045
+ 401
12046
+ );
12047
+ }
12048
+ return next();
12049
+ };
12050
+ }
12051
+
12052
+ // src/personal-agent/pty-server.ts
12053
+ import { hostname as hostname3 } from "os";
12054
+ import { WebSocketServer } from "ws";
12055
+ var RESIZE_RE = /\x1b\[RESIZE:(\d+);(\d+)\]/;
12056
+ var _ptyMod = null;
12057
+ async function loadPty() {
12058
+ if (_ptyMod) return _ptyMod;
12059
+ try {
12060
+ const mod = await import("node-pty");
12061
+ _ptyMod = mod;
12062
+ return mod;
12063
+ } catch (e) {
12064
+ console.warn(
12065
+ `[personal-agent] node-pty failed to load; /pty WebSocket disabled. (${e.message})`
12066
+ );
12067
+ return null;
12068
+ }
12069
+ }
12070
+ function defaultShell() {
12071
+ if (process.platform === "win32") {
12072
+ return { file: process.env.COMSPEC || "cmd.exe", args: [] };
12073
+ }
12074
+ return { file: process.env.SHELL || "/bin/zsh", args: ["-l"] };
12075
+ }
12076
+ function cleanEnv() {
12077
+ const env = {};
12078
+ for (const [k, v] of Object.entries(process.env)) {
12079
+ if (typeof v === "string") env[k] = v;
12080
+ }
12081
+ if (!env.TERM) env.TERM = "xterm-256color";
12082
+ if (!env.LANG) env.LANG = "en_US.UTF-8";
12083
+ return env;
12084
+ }
12085
+ function parseUpgrade(req) {
12086
+ const url = new URL(req.url || "/", "http://placeholder");
12087
+ const cols = clampInt(url.searchParams.get("cols"), 80, 10, 500);
12088
+ const rows = clampInt(url.searchParams.get("rows"), 24, 5, 500);
12089
+ const cwd = url.searchParams.get("cwd") || void 0;
12090
+ const shell = url.searchParams.get("shell") || void 0;
12091
+ return {
12092
+ cols,
12093
+ rows,
12094
+ cwd,
12095
+ shell,
12096
+ hwidHeader: headerStr(req, "x-device-hwid") ?? url.searchParams.get("hwid") ?? void 0,
12097
+ sigHeader: headerStr(req, "x-signature") ?? url.searchParams.get("sig") ?? void 0,
12098
+ tsHeader: headerStr(req, "x-signature-timestamp") ?? url.searchParams.get("ts") ?? void 0,
12099
+ algHeader: headerStr(req, "x-signature-algorithm") ?? url.searchParams.get("alg") ?? void 0
12100
+ };
12101
+ }
12102
+ function clampInt(v, dflt, lo, hi) {
12103
+ if (!v) return dflt;
12104
+ const n = parseInt(v, 10);
12105
+ if (!Number.isFinite(n)) return dflt;
12106
+ return Math.max(lo, Math.min(hi, n));
12107
+ }
12108
+ function headerStr(req, name) {
12109
+ const v = req.headers[name];
12110
+ if (Array.isArray(v)) return v[0];
12111
+ return v;
12112
+ }
12113
+ function authenticate(parsed, path, pubKey, signedOnly) {
12114
+ if (parsed.hwidHeader) {
12115
+ const expected = getHardwareIdCached();
12116
+ if (parsed.hwidHeader !== expected) {
12117
+ return {
12118
+ ok: false,
12119
+ status: 409,
12120
+ reason: `hwid mismatch: got ${parsed.hwidHeader.slice(0, 12)}\u2026, expected ${expected.slice(0, 12)}\u2026`
12121
+ };
12122
+ }
12123
+ }
12124
+ if (!pubKey) {
12125
+ if (signedOnly) {
12126
+ return { ok: false, status: 500, reason: "signature required but no public key configured" };
12127
+ }
12128
+ return { ok: true };
12129
+ }
12130
+ if (!parsed.sigHeader) {
12131
+ if (signedOnly) {
12132
+ return { ok: false, status: 401, reason: "missing X-Signature on upgrade request" };
12133
+ }
12134
+ return { ok: true };
12135
+ }
12136
+ const result = verifyRequest({
12137
+ publicKey: pubKey,
12138
+ method: "GET",
12139
+ path,
12140
+ body: void 0,
12141
+ signatureB64: parsed.sigHeader,
12142
+ timestampSeconds: parsed.tsHeader,
12143
+ algorithm: parsed.algHeader
12144
+ });
12145
+ if (!result.ok) {
12146
+ return { ok: false, status: 401, reason: `signature verification failed: ${result.reason}` };
12147
+ }
12148
+ return { ok: true };
12149
+ }
12150
+ function rejectUpgrade(socket, status, reason) {
12151
+ const body = JSON.stringify({ error: reason });
12152
+ socket.write(
12153
+ `HTTP/1.1 ${status} ${reasonText(status)}\r
12154
+ Content-Type: application/json\r
12155
+ Content-Length: ${Buffer.byteLength(body)}\r
12156
+ Connection: close\r
12157
+ \r
12158
+ ` + body
12159
+ );
12160
+ socket.destroy();
12161
+ }
12162
+ function reasonText(status) {
12163
+ switch (status) {
12164
+ case 401:
12165
+ return "Unauthorized";
12166
+ case 409:
12167
+ return "Conflict";
12168
+ case 500:
12169
+ return "Internal Server Error";
12170
+ case 503:
12171
+ return "Service Unavailable";
12172
+ default:
12173
+ return "Error";
12174
+ }
12175
+ }
12176
+ function attachPtyServer(httpServer, opts = {}) {
12177
+ const path = opts.path ?? "/pty";
12178
+ const wss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
12179
+ const acceptSignedOnly = process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY === "1";
12180
+ const publicKeyInput = process.env.PERSONAL_AGENT_PUBLIC_KEY ?? "";
12181
+ let pubKey = null;
12182
+ if (publicKeyInput) {
12183
+ try {
12184
+ pubKey = loadPublicKey(publicKeyInput);
12185
+ } catch (e) {
12186
+ console.warn(
12187
+ `[personal-agent] /pty signature verification disabled \u2014 failed to load public key: ${e.message}`
12188
+ );
12189
+ }
12190
+ }
12191
+ const handler = (req, socket, head) => {
12192
+ let pathname = "/";
12193
+ try {
12194
+ pathname = new URL(req.url || "/", "http://placeholder").pathname;
12195
+ } catch {
12196
+ }
12197
+ if (pathname !== path) return;
12198
+ const parsed = parseUpgrade(req);
12199
+ const auth = authenticate(parsed, pathname, pubKey, acceptSignedOnly);
12200
+ if (!auth.ok) {
12201
+ console.warn(`[personal-agent] rejecting /pty upgrade: ${auth.reason}`);
12202
+ rejectUpgrade(socket, auth.status ?? 401, auth.reason ?? "unauthorized");
12203
+ return;
12204
+ }
12205
+ void loadPty().then((pty) => {
12206
+ if (!pty) {
12207
+ rejectUpgrade(
12208
+ socket,
12209
+ 503,
12210
+ "node-pty is not available on this device (failed to load native module)"
12211
+ );
12212
+ return;
12213
+ }
12214
+ wss.handleUpgrade(req, socket, head, (ws) => {
12215
+ spawnPty(ws, pty, parsed, opts);
12216
+ });
12217
+ });
12218
+ };
12219
+ httpServer.on("upgrade", handler);
12220
+ if (!opts.quiet) {
12221
+ console.log(
12222
+ `[personal-agent] WebSocket PTY server attached at ${path} (host: ${hostname3()}, sigCheck: ${pubKey ? "on" : "off"})`
12223
+ );
12224
+ }
12225
+ return {
12226
+ close: () => {
12227
+ httpServer.off("upgrade", handler);
12228
+ wss.close();
12229
+ }
12230
+ };
12231
+ }
12232
+ function spawnPty(ws, pty, parsed, opts) {
12233
+ const { file, args } = (() => {
12234
+ if (parsed.shell || opts.shell) {
12235
+ const s = parsed.shell ?? opts.shell;
12236
+ return { file: s, args: process.platform === "win32" ? [] : ["-l"] };
12237
+ }
12238
+ return defaultShell();
12239
+ })();
12240
+ const cwd = parsed.cwd ?? opts.cwd ?? process.env.HOME ?? process.cwd();
12241
+ let proc;
12242
+ try {
12243
+ proc = pty.spawn(file, args, {
12244
+ name: "xterm-256color",
12245
+ cols: parsed.cols,
12246
+ rows: parsed.rows,
12247
+ cwd,
12248
+ env: cleanEnv()
12249
+ });
12250
+ } catch (e) {
12251
+ const msg = e.message;
12252
+ safeSend(ws, `\r
12253
+ \x1B[31mFailed to spawn shell: ${msg}\x1B[0m\r
12254
+ `);
12255
+ try {
12256
+ ws.close();
12257
+ } catch {
12258
+ }
12259
+ return;
12260
+ }
12261
+ const banner = `\x1B[90m[sparkecoder pty] ${file} on ${hostname3()} pid=${proc.pid} ${parsed.cols}x${parsed.rows}\x1B[0m\r
12262
+ `;
12263
+ safeSend(ws, banner);
12264
+ proc.onData((data) => safeSend(ws, data));
12265
+ proc.onExit(({ exitCode }) => {
12266
+ safeSend(ws, `\r
12267
+ \x1B[90m[exit ${exitCode}]\x1B[0m\r
12268
+ `);
12269
+ try {
12270
+ ws.close();
12271
+ } catch {
12272
+ }
12273
+ });
12274
+ ws.on("message", (msg, isBinary) => {
12275
+ const input = typeof msg === "string" ? msg : Buffer.isBuffer(msg) ? msg.toString(isBinary ? "utf8" : "utf8") : Array.isArray(msg) ? Buffer.concat(msg).toString("utf8") : "";
12276
+ if (!input) return;
12277
+ const m = input.match(RESIZE_RE);
12278
+ if (m) {
12279
+ const cols = clampInt(m[1], 80, 10, 500);
12280
+ const rows = clampInt(m[2], 24, 5, 500);
12281
+ try {
12282
+ proc.resize(cols, rows);
12283
+ } catch {
12284
+ }
12285
+ if (input.replace(RESIZE_RE, "").length === 0) return;
12286
+ proc.write(input.replace(RESIZE_RE, ""));
12287
+ return;
12288
+ }
12289
+ proc.write(input);
12290
+ });
12291
+ const onClose = () => {
12292
+ try {
12293
+ proc.kill();
12294
+ } catch {
12295
+ }
12296
+ };
12297
+ ws.on("close", onClose);
12298
+ ws.on("error", onClose);
12299
+ }
12300
+ function safeSend(ws, data) {
12301
+ if (ws.readyState !== 1) return;
12302
+ try {
12303
+ ws.send(data);
12304
+ } catch {
12305
+ }
12306
+ }
12307
+
10677
12308
  // src/server/index.ts
10678
12309
  init_config();
10679
12310
  init_db();
10680
12311
 
10681
12312
  // 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);
12313
+ import { exec as exec7 } from "child_process";
12314
+ import { promisify as promisify7 } from "util";
12315
+ import { platform as platform4 } from "os";
12316
+ var execAsync7 = promisify7(exec7);
10686
12317
  function getInstallInstructions() {
10687
- const os2 = platform2();
12318
+ const os2 = platform4();
10688
12319
  if (os2 === "darwin") {
10689
12320
  return `
10690
12321
  Install tmux on macOS:
@@ -10715,7 +12346,7 @@ Install tmux:
10715
12346
  }
10716
12347
  async function checkTmux() {
10717
12348
  try {
10718
- const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
12349
+ const { stdout } = await execAsync7("tmux -V", { timeout: 5e3 });
10719
12350
  const version = stdout.trim();
10720
12351
  return {
10721
12352
  available: true,
@@ -10764,11 +12395,11 @@ function getWebDirectory() {
10764
12395
  try {
10765
12396
  const currentDir = dirname7(fileURLToPath4(import.meta.url));
10766
12397
  const webDir = resolve10(currentDir, "..", "web");
10767
- if (existsSync17(webDir) && existsSync17(join12(webDir, "package.json"))) {
12398
+ if (existsSync19(webDir) && existsSync19(join13(webDir, "package.json"))) {
10768
12399
  return webDir;
10769
12400
  }
10770
12401
  const altWebDir = resolve10(currentDir, "..", "..", "web");
10771
- if (existsSync17(altWebDir) && existsSync17(join12(altWebDir, "package.json"))) {
12402
+ if (existsSync19(altWebDir) && existsSync19(join13(altWebDir, "package.json"))) {
10772
12403
  return altWebDir;
10773
12404
  }
10774
12405
  return null;
@@ -10826,23 +12457,23 @@ async function findWebPort(preferredPort) {
10826
12457
  return { port: preferredPort, alreadyRunning: false };
10827
12458
  }
10828
12459
  function hasProductionBuild(webDir) {
10829
- const buildIdPath = join12(webDir, ".next", "BUILD_ID");
10830
- return existsSync17(buildIdPath);
12460
+ const buildIdPath = join13(webDir, ".next", "BUILD_ID");
12461
+ return existsSync19(buildIdPath);
10831
12462
  }
10832
12463
  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);
12464
+ const appDir = join13(webDir, "src", "app");
12465
+ const pagesDir = join13(webDir, "src", "pages");
12466
+ const rootAppDir = join13(webDir, "app");
12467
+ const rootPagesDir = join13(webDir, "pages");
12468
+ return existsSync19(appDir) || existsSync19(pagesDir) || existsSync19(rootAppDir) || existsSync19(rootPagesDir);
10838
12469
  }
10839
12470
  function getStandaloneServerPath(webDir) {
10840
12471
  const possiblePaths2 = [
10841
- join12(webDir, ".next", "standalone", "server.js"),
10842
- join12(webDir, ".next", "standalone", "web", "server.js")
12472
+ join13(webDir, ".next", "standalone", "server.js"),
12473
+ join13(webDir, ".next", "standalone", "web", "server.js")
10843
12474
  ];
10844
12475
  for (const serverPath of possiblePaths2) {
10845
- if (existsSync17(serverPath)) {
12476
+ if (existsSync19(serverPath)) {
10846
12477
  return serverPath;
10847
12478
  }
10848
12479
  }
@@ -10882,13 +12513,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
10882
12513
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
10883
12514
  return { process: null, port: actualPort };
10884
12515
  }
10885
- const usePnpm = existsSync17(join12(webDir, "pnpm-lock.yaml"));
10886
- const useNpm = !usePnpm && existsSync17(join12(webDir, "package-lock.json"));
12516
+ const usePnpm = existsSync19(join13(webDir, "pnpm-lock.yaml"));
12517
+ const useNpm = !usePnpm && existsSync19(join13(webDir, "package-lock.json"));
10887
12518
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
10888
- const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
12519
+ const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv2 } = process.env;
10889
12520
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
10890
12521
  const runtimeConfig = { apiBaseUrl: apiUrl };
10891
- const runtimeConfigPath = join12(webDir, "runtime-config.json");
12522
+ const runtimeConfigPath = join13(webDir, "runtime-config.json");
10892
12523
  try {
10893
12524
  writeFileSync5(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
10894
12525
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -10896,7 +12527,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
10896
12527
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
10897
12528
  }
10898
12529
  const webEnv = {
10899
- ...cleanEnv,
12530
+ ...cleanEnv2,
10900
12531
  PORT: String(actualPort)
10901
12532
  // Next.js respects PORT env var
10902
12533
  };
@@ -11010,12 +12641,28 @@ function stopWebUI() {
11010
12641
  }
11011
12642
  }
11012
12643
  async function createApp(options = {}) {
11013
- const app = new Hono6();
12644
+ const app = new Hono7();
11014
12645
  app.use("*", cors({
11015
12646
  origin: "*",
11016
12647
  // Allow all origins
11017
12648
  allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
11018
- allowHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
12649
+ allowHeaders: [
12650
+ "Content-Type",
12651
+ "Authorization",
12652
+ "X-Requested-With",
12653
+ // Personal-agent dashboard signs every request to the device with
12654
+ // these. Without them whitelisted the browser preflight strips the
12655
+ // headers and the signature middleware 401s.
12656
+ "X-Signature",
12657
+ "X-Signature-Timestamp",
12658
+ "X-Signature-Algorithm",
12659
+ "X-Device-Hwid",
12660
+ // Short-lived embed token used by the iframed SparkECoder web UI
12661
+ // when it's hosted inside the personal-agents dashboard. The
12662
+ // bootstrap in `web/src/lib/embed-bootstrap.ts` adds this header
12663
+ // to every API call.
12664
+ "X-Embed-Token"
12665
+ ],
11019
12666
  exposeHeaders: ["X-Stream-Id", "x-stream-id"],
11020
12667
  maxAge: 86400
11021
12668
  // 24 hours
@@ -11023,12 +12670,15 @@ async function createApp(options = {}) {
11023
12670
  if (!options.quiet) {
11024
12671
  app.use("*", logger());
11025
12672
  }
12673
+ app.use("*", hwidMiddleware());
12674
+ app.use("*", signatureMiddleware());
11026
12675
  app.route("/health", health);
11027
12676
  app.route("/sessions", sessions);
11028
12677
  app.route("/agents", agents);
11029
12678
  app.route("/sessions", terminals);
11030
12679
  app.route("/terminals", terminals);
11031
12680
  app.route("/tasks", tasks_default);
12681
+ app.route("/system", system);
11032
12682
  app.get("/openapi.json", async (c) => {
11033
12683
  return c.json(generateOpenAPISpec());
11034
12684
  });
@@ -11081,8 +12731,8 @@ async function startServer(options = {}) {
11081
12731
  if (options.workingDirectory) {
11082
12732
  config.resolvedWorkingDirectory = options.workingDirectory;
11083
12733
  }
11084
- if (!existsSync17(config.resolvedWorkingDirectory)) {
11085
- mkdirSync7(config.resolvedWorkingDirectory, { recursive: true });
12734
+ if (!existsSync19(config.resolvedWorkingDirectory)) {
12735
+ mkdirSync8(config.resolvedWorkingDirectory, { recursive: true });
11086
12736
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
11087
12737
  }
11088
12738
  if (!config.resolvedRemoteServer.url) {
@@ -11117,6 +12767,15 @@ async function startServer(options = {}) {
11117
12767
  port,
11118
12768
  hostname: host
11119
12769
  });
12770
+ try {
12771
+ attachPtyServer(serverInstance, {
12772
+ quiet: options.quiet
12773
+ });
12774
+ } catch (e) {
12775
+ if (!options.quiet) {
12776
+ console.warn(` \u26A0 Failed to attach /pty WebSocket server: ${e.message}`);
12777
+ }
12778
+ }
11120
12779
  let webPort;
11121
12780
  let webStarted;
11122
12781
  if (options.webUI !== false) {