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/cli.js CHANGED
@@ -29,9 +29,9 @@ __export(remote_exports, {
29
29
  remoteToolExecutionQueries: () => remoteToolExecutionQueries,
30
30
  storageQueries: () => storageQueries
31
31
  });
32
- function initRemoteDatabase(serverUrl, key) {
32
+ function initRemoteDatabase(serverUrl, key2) {
33
33
  remoteServerUrl = serverUrl.replace(/\/$/, "");
34
- authKey = key;
34
+ authKey = key2;
35
35
  }
36
36
  function closeRemoteDatabase() {
37
37
  remoteServerUrl = null;
@@ -45,14 +45,14 @@ function parseDates(obj) {
45
45
  if (Array.isArray(obj)) return obj.map(parseDates);
46
46
  if (typeof obj !== "object" || obj instanceof Date) return obj;
47
47
  const result = { ...obj };
48
- for (const key of Object.keys(result)) {
49
- if (MODEL_MESSAGE_FIELDS.includes(key)) {
48
+ for (const key2 of Object.keys(result)) {
49
+ if (MODEL_MESSAGE_FIELDS.includes(key2)) {
50
50
  continue;
51
51
  }
52
- if (DATE_FIELDS.includes(key) && typeof result[key] === "string") {
53
- result[key] = new Date(result[key]);
54
- } else if (typeof result[key] === "object") {
55
- result[key] = parseDates(result[key]);
52
+ if (DATE_FIELDS.includes(key2) && typeof result[key2] === "string") {
53
+ result[key2] = new Date(result[key2]);
54
+ } else if (typeof result[key2] === "object") {
55
+ result[key2] = parseDates(result[key2]);
56
56
  }
57
57
  }
58
58
  return result;
@@ -518,7 +518,12 @@ var init_types = __esm({
518
518
  // Whether to always inject this skill into context (vs on-demand loading)
519
519
  alwaysApply: z.boolean().optional().default(false),
520
520
  // Glob patterns - auto-inject when working with matching files
521
- globs: z.array(z.string()).optional().default([])
521
+ globs: z.array(z.string()).optional().default([]),
522
+ // Platform requirements — skill is hidden from the model on platforms
523
+ // not listed here. Values match `process.platform`
524
+ // (darwin, linux, win32, freebsd, ...). If omitted or empty, the skill is
525
+ // available on all platforms.
526
+ platforms: z.array(z.string()).optional().default([])
522
527
  });
523
528
  TaskConfigSchema = z.object({
524
529
  enabled: z.boolean(),
@@ -536,7 +541,13 @@ var init_types = __esm({
536
541
  approvalWebhook: z.string().url().optional(),
537
542
  skillsDirectory: z.string().optional(),
538
543
  maxContextChars: z.number().optional().default(2e5),
539
- task: TaskConfigSchema.optional()
544
+ task: TaskConfigSchema.optional(),
545
+ // Anthropic computer use tool — opt-in. When true, the `computer` tool is
546
+ // included in the toolset for Anthropic models. Default false.
547
+ computerUseEnabled: z.boolean().optional(),
548
+ // Display dimensions for the computer use tool (defaults: 1280x800).
549
+ computerUseDisplayWidth: z.number().int().positive().optional(),
550
+ computerUseDisplayHeight: z.number().int().positive().optional()
540
551
  });
541
552
  VectorGatewayConfigSchema = z.object({
542
553
  // Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
@@ -587,7 +598,7 @@ var init_types = __esm({
587
598
  }).optional();
588
599
  SparkcoderConfigSchema = z.object({
589
600
  // Default model to use (Vercel AI Gateway format)
590
- defaultModel: z.string().default("anthropic/claude-opus-4-6"),
601
+ defaultModel: z.string().default("anthropic/claude-opus-4.7"),
591
602
  // Working directory for file operations
592
603
  workingDirectory: z.string().optional(),
593
604
  // Tool approval settings
@@ -861,7 +872,7 @@ function requiresApproval(toolName, sessionConfig) {
861
872
  }
862
873
  function createDefaultConfig() {
863
874
  return {
864
- defaultModel: "anthropic/claude-opus-4-6",
875
+ defaultModel: "anthropic/claude-opus-4.7",
865
876
  // workingDirectory is intentionally not set - defaults to where CLI is run
866
877
  toolApprovals: {
867
878
  bash: true,
@@ -973,6 +984,14 @@ function loadApiKeysIntoEnv() {
973
984
  }
974
985
  }
975
986
  }
987
+ function isRemoteInferenceConfigured() {
988
+ try {
989
+ const config = getConfig();
990
+ return config.resolvedRemoteServer.isConfigured;
991
+ } catch {
992
+ return false;
993
+ }
994
+ }
976
995
  function setApiKey(provider, apiKey) {
977
996
  const normalizedProvider = provider.toLowerCase();
978
997
  const envVar = PROVIDER_ENV_MAP[normalizedProvider];
@@ -1022,11 +1041,11 @@ function getApiKeyStatus() {
1022
1041
  };
1023
1042
  });
1024
1043
  }
1025
- function maskApiKey(key) {
1026
- if (key.length <= 12) {
1027
- return "****" + key.slice(-4);
1044
+ function maskApiKey(key2) {
1045
+ if (key2.length <= 12) {
1046
+ return "****" + key2.slice(-4);
1028
1047
  }
1029
- return key.slice(0, 4) + "..." + key.slice(-4);
1048
+ return key2.slice(0, 4) + "..." + key2.slice(-4);
1030
1049
  }
1031
1050
  var CONFIG_FILE_NAMES, cachedConfig, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
1032
1051
  var init_config = __esm({
@@ -1480,10 +1499,10 @@ function parseSkillFrontmatter(content) {
1480
1499
  }
1481
1500
  const colonIndex = line.indexOf(":");
1482
1501
  if (colonIndex > 0) {
1483
- const key = line.slice(0, colonIndex).trim();
1502
+ const key2 = line.slice(0, colonIndex).trim();
1484
1503
  let value = line.slice(colonIndex + 1).trim();
1485
1504
  if (value === "" || value === "[]") {
1486
- currentArrayKey = key;
1505
+ currentArrayKey = key2;
1487
1506
  currentArray = [];
1488
1507
  continue;
1489
1508
  }
@@ -1496,18 +1515,18 @@ function parseSkillFrontmatter(content) {
1496
1515
  }
1497
1516
  return trimmed;
1498
1517
  }).filter((item) => item.length > 0);
1499
- data[key] = items;
1518
+ data[key2] = items;
1500
1519
  continue;
1501
1520
  }
1502
1521
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1503
1522
  value = value.slice(1, -1);
1504
1523
  }
1505
1524
  if (value === "true") {
1506
- data[key] = true;
1525
+ data[key2] = true;
1507
1526
  } else if (value === "false") {
1508
- data[key] = false;
1527
+ data[key2] = false;
1509
1528
  } else {
1510
- data[key] = value;
1529
+ data[key2] = value;
1511
1530
  }
1512
1531
  }
1513
1532
  }
@@ -1564,7 +1583,8 @@ async function loadSkillsFromDirectory(directory, options = {}) {
1564
1583
  globs: parsed.metadata.globs,
1565
1584
  loadType,
1566
1585
  priority,
1567
- sourceDir: directory
1586
+ sourceDir: directory,
1587
+ platforms: parsed.metadata.platforms
1568
1588
  });
1569
1589
  } else {
1570
1590
  const name = getSkillNameFromPath(filePath);
@@ -1577,11 +1597,14 @@ async function loadSkillsFromDirectory(directory, options = {}) {
1577
1597
  globs: [],
1578
1598
  loadType: forceAlwaysApply ? "always" : defaultLoadType,
1579
1599
  priority,
1580
- sourceDir: directory
1600
+ sourceDir: directory,
1601
+ platforms: []
1581
1602
  });
1582
1603
  }
1583
1604
  }
1584
- return skills;
1605
+ return skills.filter(
1606
+ (s) => s.platforms.length === 0 || s.platforms.includes(process.platform)
1607
+ );
1585
1608
  }
1586
1609
  async function loadAllSkills(directories) {
1587
1610
  const allSkills = [];
@@ -2042,7 +2065,7 @@ function mergeSmallBlocks(blocks, lines) {
2042
2065
  merged.push(current);
2043
2066
  return merged;
2044
2067
  }
2045
- function splitLargeBlock(filePath, content, startLine, language, type, name) {
2068
+ function splitLargeBlock(filePath, content, startLine, language, type2, name) {
2046
2069
  const chunks = [];
2047
2070
  const lines = content.split("\n");
2048
2071
  let currentStart = 0;
@@ -2062,7 +2085,7 @@ function splitLargeBlock(filePath, content, startLine, language, type, name) {
2062
2085
  startLine: startLine + currentStart + 1,
2063
2086
  endLine: startLine + i,
2064
2087
  language,
2065
- chunkType: type,
2088
+ chunkType: type2,
2066
2089
  symbolName: name
2067
2090
  }
2068
2091
  });
@@ -2084,7 +2107,7 @@ function splitLargeBlock(filePath, content, startLine, language, type, name) {
2084
2107
  startLine: startLine + currentStart + 1,
2085
2108
  endLine: startLine + lines.length,
2086
2109
  language,
2087
- chunkType: type,
2110
+ chunkType: type2,
2088
2111
  symbolName: name
2089
2112
  }
2090
2113
  });
@@ -2241,9 +2264,9 @@ var init_chunker = __esm({
2241
2264
  });
2242
2265
 
2243
2266
  // src/semantic/client.ts
2244
- function initVectorClient(serverUrl, key) {
2267
+ function initVectorClient(serverUrl, key2) {
2245
2268
  remoteServerUrl2 = serverUrl.replace(/\/$/, "");
2246
- authKey2 = key;
2269
+ authKey2 = key2;
2247
2270
  }
2248
2271
  function isVectorClientConfigured() {
2249
2272
  return !!remoteServerUrl2 && !!authKey2;
@@ -2401,18 +2424,18 @@ function isPathExcluded(relativePath, exclude) {
2401
2424
  });
2402
2425
  }
2403
2426
  async function walkDirectory(dir, include, exclude, baseDir) {
2404
- const { readdirSync: readdirSync3 } = await import("fs");
2405
- const { join: join14, relative: relative10 } = await import("path");
2427
+ const { readdirSync: readdirSync4 } = await import("fs");
2428
+ const { join: join15, relative: relative10 } = await import("path");
2406
2429
  const files = [];
2407
2430
  function walk(currentDir) {
2408
2431
  let entries;
2409
2432
  try {
2410
- entries = readdirSync3(currentDir, { withFileTypes: true });
2433
+ entries = readdirSync4(currentDir, { withFileTypes: true });
2411
2434
  } catch {
2412
2435
  return;
2413
2436
  }
2414
2437
  for (const entry of entries) {
2415
- const fullPath = join14(currentDir, entry.name);
2438
+ const fullPath = join15(currentDir, entry.name);
2416
2439
  const relativePath = relative10(baseDir, fullPath);
2417
2440
  if (isPathExcluded(relativePath, exclude)) {
2418
2441
  continue;
@@ -2914,12 +2937,440 @@ var init_semantic_search = __esm({
2914
2937
  }
2915
2938
  });
2916
2939
 
2940
+ // src/tools/computer-use.ts
2941
+ var computer_use_exports = {};
2942
+ __export(computer_use_exports, {
2943
+ createComputerUseTool: () => createComputerUseTool,
2944
+ detectScreenSize: () => detectScreenSize,
2945
+ hasAccessibilityPermissions: () => hasAccessibilityPermissions,
2946
+ hasScreenRecordingPermissions: () => hasScreenRecordingPermissions,
2947
+ isCliclickInstalled: () => isCliclickInstalled,
2948
+ isMacOs: () => isMacOs,
2949
+ openSystemSettings: () => openSystemSettings,
2950
+ requestAccessibilityPrompt: () => requestAccessibilityPrompt,
2951
+ requestScreenRecordingPrompt: () => requestScreenRecordingPrompt
2952
+ });
2953
+ import { anthropic } from "@ai-sdk/anthropic";
2954
+ import { exec as exec5 } from "child_process";
2955
+ import { promisify as promisify5 } from "util";
2956
+ import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
2957
+ import { join as join8 } from "path";
2958
+ import { tmpdir } from "os";
2959
+ import { nanoid as nanoid4 } from "nanoid";
2960
+ function isMacOs() {
2961
+ return process.platform === "darwin";
2962
+ }
2963
+ async function isCliclickInstalled() {
2964
+ try {
2965
+ await execAsync5("command -v cliclick", { timeout: 2e3 });
2966
+ return true;
2967
+ } catch {
2968
+ return false;
2969
+ }
2970
+ }
2971
+ async function runJxa(script) {
2972
+ try {
2973
+ const escaped = script.replace(/'/g, `'\\''`);
2974
+ const { stdout } = await execAsync5(`osascript -l JavaScript -e '${escaped}'`, {
2975
+ timeout: 5e3
2976
+ });
2977
+ return JSON.parse(stdout.trim());
2978
+ } catch {
2979
+ return null;
2980
+ }
2981
+ }
2982
+ async function hasAccessibilityPermissions() {
2983
+ try {
2984
+ const { stderr } = await execAsync5("cliclick p:.", { timeout: 3e3 });
2985
+ if (/accessibility privileges not enabled/i.test(stderr)) {
2986
+ return { ok: false, error: stderr.trim().split("\n")[0] };
2987
+ }
2988
+ return { ok: true };
2989
+ } catch (err) {
2990
+ return { ok: false, error: err?.message || String(err) };
2991
+ }
2992
+ }
2993
+ async function hasScreenRecordingPermissions() {
2994
+ const result = await runJxa(
2995
+ `ObjC.import("Cocoa");
2996
+ ObjC.import("CoreGraphics");
2997
+ ObjC.bindFunction("CGPreflightScreenCaptureAccess", ["bool", []]);
2998
+ JSON.stringify({ hasAccess: !!$.CGPreflightScreenCaptureAccess() });`
2999
+ );
3000
+ return result?.hasAccess ?? false;
3001
+ }
3002
+ async function requestAccessibilityPrompt() {
3003
+ const result = await runJxa(
3004
+ `ObjC.import("ApplicationServices");
3005
+ var key = $.kAXTrustedCheckOptionPrompt;
3006
+ var dict = $.NSDictionary.dictionaryWithObjectForKey($.kCFBooleanTrue, key);
3007
+ var trusted = $.AXIsProcessTrustedWithOptions(dict);
3008
+ JSON.stringify({ trusted: !!trusted });`
3009
+ );
3010
+ return result?.trusted ?? false;
3011
+ }
3012
+ async function requestScreenRecordingPrompt() {
3013
+ const result = await runJxa(
3014
+ `ObjC.import("Cocoa");
3015
+ ObjC.import("CoreGraphics");
3016
+ ObjC.bindFunction("CGRequestScreenCaptureAccess", ["bool", []]);
3017
+ JSON.stringify({ granted: !!$.CGRequestScreenCaptureAccess() });`
3018
+ );
3019
+ return result?.granted ?? false;
3020
+ }
3021
+ async function openSystemSettings(pane) {
3022
+ const url = pane === "accessibility" ? "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
3023
+ try {
3024
+ await execAsync5(`open '${url}'`, { timeout: 3e3 });
3025
+ } catch {
3026
+ }
3027
+ }
3028
+ async function detectScreenSize() {
3029
+ try {
3030
+ const { stdout } = await execAsync5(
3031
+ `osascript -e 'tell application "Finder" to get bounds of window of desktop'`,
3032
+ { timeout: 3e3 }
3033
+ );
3034
+ const parts = stdout.trim().split(",").map((s) => parseInt(s.trim(), 10));
3035
+ if (parts.length >= 4 && parts.every((n) => Number.isFinite(n))) {
3036
+ const [x1, y1, x2, y2] = parts;
3037
+ return { width: x2 - x1, height: y2 - y1 };
3038
+ }
3039
+ } catch {
3040
+ }
3041
+ return null;
3042
+ }
3043
+ async function runCliclick(args) {
3044
+ const quoted = args.map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
3045
+ const { stdout, stderr } = await execAsync5(`cliclick ${quoted}`, {
3046
+ timeout: 15e3,
3047
+ maxBuffer: 1024 * 1024
3048
+ });
3049
+ if (/accessibility privileges not enabled/i.test(stderr)) {
3050
+ throw new Error(
3051
+ "Accessibility permissions not granted to cliclick. Open System Settings \u2192 Privacy & Security \u2192 Accessibility, add cliclick (or the agent runtime), and toggle it on."
3052
+ );
3053
+ }
3054
+ if (stderr && !stdout) throw new Error(stderr.trim());
3055
+ return (stdout || "").trim();
3056
+ }
3057
+ async function runScreencapture(path) {
3058
+ await execAsync5(`screencapture -x -t png '${path.replace(/'/g, `'\\''`)}'`, {
3059
+ timeout: 5e3
3060
+ });
3061
+ }
3062
+ async function resizeScreenshotToPoints(path, targetWidth, targetHeight) {
3063
+ const sharpModule = await import("sharp");
3064
+ const sharp2 = sharpModule.default || sharpModule;
3065
+ const meta = await sharp2(path).metadata();
3066
+ if (meta.width === targetWidth && meta.height === targetHeight) {
3067
+ return readFileSync7(path);
3068
+ }
3069
+ return await sharp2(path).resize(targetWidth, targetHeight, { fit: "fill" }).png().toBuffer();
3070
+ }
3071
+ async function runScroll(dx, dy) {
3072
+ const wheelY = -Math.round(dy);
3073
+ const wheelX = -Math.round(dx);
3074
+ const script = `ObjC.import('CoreGraphics');var ev = $.CGEventCreateScrollWheelEvent(null, 0, 2, ${wheelY}, ${wheelX});$.CGEventPost(0, ev);`;
3075
+ await execAsync5(
3076
+ `osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
3077
+ { timeout: 5e3 }
3078
+ );
3079
+ }
3080
+ function translateKeyForCliclick(key2) {
3081
+ if (!key2) return [];
3082
+ const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
3083
+ if (parts.length === 0) return [];
3084
+ const modMap = {
3085
+ ctrl: "ctrl",
3086
+ control: "ctrl",
3087
+ alt: "alt",
3088
+ option: "alt",
3089
+ shift: "shift",
3090
+ cmd: "cmd",
3091
+ super: "cmd",
3092
+ meta: "cmd",
3093
+ win: "cmd",
3094
+ fn: "fn"
3095
+ };
3096
+ const keyMap = {
3097
+ return: "enter",
3098
+ enter: "enter",
3099
+ esc: "esc",
3100
+ escape: "esc",
3101
+ backspace: "delete",
3102
+ back_space: "delete",
3103
+ delete: "fwd-delete",
3104
+ fwd_delete: "fwd-delete",
3105
+ forward_delete: "fwd-delete",
3106
+ tab: "tab",
3107
+ space: "space",
3108
+ up: "arrow-up",
3109
+ arrow_up: "arrow-up",
3110
+ down: "arrow-down",
3111
+ arrow_down: "arrow-down",
3112
+ left: "arrow-left",
3113
+ arrow_left: "arrow-left",
3114
+ right: "arrow-right",
3115
+ arrow_right: "arrow-right",
3116
+ page_up: "page-up",
3117
+ pageup: "page-up",
3118
+ page_down: "page-down",
3119
+ pagedown: "page-down",
3120
+ home: "home",
3121
+ end: "end",
3122
+ f1: "f1",
3123
+ f2: "f2",
3124
+ f3: "f3",
3125
+ f4: "f4",
3126
+ f5: "f5",
3127
+ f6: "f6",
3128
+ f7: "f7",
3129
+ f8: "f8",
3130
+ f9: "f9",
3131
+ f10: "f10",
3132
+ f11: "f11",
3133
+ f12: "f12"
3134
+ };
3135
+ const modifiers = [];
3136
+ let mainKey = null;
3137
+ for (let i = 0; i < parts.length; i++) {
3138
+ const lower = parts[i].toLowerCase().replace(/-/g, "_");
3139
+ if (i < parts.length - 1 && modMap[lower]) {
3140
+ modifiers.push(modMap[lower]);
3141
+ } else {
3142
+ mainKey = keyMap[lower] || lower;
3143
+ }
3144
+ }
3145
+ const args = [];
3146
+ if (modifiers.length > 0) args.push(`kd:${modifiers.join(",")}`);
3147
+ if (mainKey) {
3148
+ const isNamedKey = Object.values(keyMap).includes(mainKey) || /^f([1-9]|1[0-9]|20)$/.test(mainKey) || /^num-/.test(mainKey);
3149
+ if (isNamedKey) {
3150
+ args.push(`kp:${mainKey}`);
3151
+ } else {
3152
+ args.push(`t:${mainKey}`);
3153
+ }
3154
+ }
3155
+ if (modifiers.length > 0) args.push(`ku:${modifiers.join(",")}`);
3156
+ return args;
3157
+ }
3158
+ function modifierStringToCliclick(text) {
3159
+ return text.split("+").map((p) => p.trim().toLowerCase()).map((p) => {
3160
+ if (p === "ctrl" || p === "control") return "ctrl";
3161
+ if (p === "alt" || p === "option") return "alt";
3162
+ if (p === "shift") return "shift";
3163
+ if (p === "super" || p === "meta" || p === "cmd") return "cmd";
3164
+ return "";
3165
+ }).filter(Boolean);
3166
+ }
3167
+ function createComputerUseTool(options) {
3168
+ const displayWidth = options.displayWidth ?? DEFAULT_WIDTH;
3169
+ const displayHeight = options.displayHeight ?? DEFAULT_HEIGHT;
3170
+ return anthropic.tools.computer_20251124({
3171
+ displayWidthPx: displayWidth,
3172
+ displayHeightPx: displayHeight,
3173
+ enableZoom: true,
3174
+ execute: async (input) => {
3175
+ try {
3176
+ switch (input.action) {
3177
+ case "screenshot": {
3178
+ const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
3179
+ await runScreencapture(path);
3180
+ const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
3181
+ try {
3182
+ unlinkSync2(path);
3183
+ } catch {
3184
+ }
3185
+ return { type: "image", data: resized.toString("base64") };
3186
+ }
3187
+ case "left_click": {
3188
+ const [x, y] = input.coordinate ?? [0, 0];
3189
+ if (input.text) {
3190
+ const mods = modifierStringToCliclick(input.text);
3191
+ if (mods.length > 0) {
3192
+ await runCliclick([`kd:${mods.join(",")}`, `c:${x},${y}`, `ku:${mods.join(",")}`]);
3193
+ } else {
3194
+ await runCliclick([`c:${x},${y}`]);
3195
+ }
3196
+ } else {
3197
+ await runCliclick([`c:${x},${y}`]);
3198
+ }
3199
+ return `clicked at (${x}, ${y})${input.text ? ` with ${input.text}` : ""}`;
3200
+ }
3201
+ case "right_click": {
3202
+ const [x, y] = input.coordinate ?? [0, 0];
3203
+ await runCliclick([`rc:${x},${y}`]);
3204
+ return `right-clicked at (${x}, ${y})`;
3205
+ }
3206
+ case "middle_click": {
3207
+ const [x, y] = input.coordinate ?? [0, 0];
3208
+ 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);`;
3209
+ await execAsync5(
3210
+ `osascript -l JavaScript -e '${script.replace(/'/g, `'\\''`)}'`,
3211
+ { timeout: 3e3 }
3212
+ );
3213
+ return `middle-clicked at (${x}, ${y})`;
3214
+ }
3215
+ case "double_click": {
3216
+ const [x, y] = input.coordinate ?? [0, 0];
3217
+ await runCliclick([`dc:${x},${y}`]);
3218
+ return `double-clicked at (${x}, ${y})`;
3219
+ }
3220
+ case "triple_click": {
3221
+ const [x, y] = input.coordinate ?? [0, 0];
3222
+ await runCliclick([`tc:${x},${y}`]);
3223
+ return `triple-clicked at (${x}, ${y})`;
3224
+ }
3225
+ case "mouse_move": {
3226
+ const [x, y] = input.coordinate ?? [0, 0];
3227
+ await runCliclick([`m:${x},${y}`]);
3228
+ return `moved cursor to (${x}, ${y})`;
3229
+ }
3230
+ case "left_mouse_down": {
3231
+ const [x, y] = input.coordinate ?? [0, 0];
3232
+ await runCliclick([`dd:${x},${y}`]);
3233
+ return `left mouse button pressed at (${x}, ${y})`;
3234
+ }
3235
+ case "left_mouse_up": {
3236
+ const [x, y] = input.coordinate ?? [0, 0];
3237
+ await runCliclick([`du:${x},${y}`]);
3238
+ return `left mouse button released at (${x}, ${y})`;
3239
+ }
3240
+ case "left_click_drag": {
3241
+ const [sx, sy] = input.start_coordinate ?? [0, 0];
3242
+ const [ex, ey] = input.coordinate ?? [0, 0];
3243
+ await runCliclick([`dd:${sx},${sy}`, `m:${ex},${ey}`, `du:${ex},${ey}`]);
3244
+ return `dragged from (${sx}, ${sy}) to (${ex}, ${ey})`;
3245
+ }
3246
+ case "type": {
3247
+ const text = input.text ?? "";
3248
+ await runCliclick([`t:${text}`]);
3249
+ return `typed ${text.length} character(s)`;
3250
+ }
3251
+ case "key": {
3252
+ const args = translateKeyForCliclick(input.text ?? "");
3253
+ if (args.length === 0) return "no key specified";
3254
+ await runCliclick(args);
3255
+ return `pressed ${input.text}`;
3256
+ }
3257
+ case "hold_key": {
3258
+ const text = (input.text ?? "").toLowerCase();
3259
+ const duration = input.duration ?? 1;
3260
+ const modMap = {
3261
+ ctrl: "ctrl",
3262
+ control: "ctrl",
3263
+ alt: "alt",
3264
+ option: "alt",
3265
+ shift: "shift",
3266
+ cmd: "cmd",
3267
+ super: "cmd",
3268
+ meta: "cmd",
3269
+ fn: "fn"
3270
+ };
3271
+ const cliName = modMap[text] || text;
3272
+ await runCliclick([`kd:${cliName}`]);
3273
+ await new Promise((r) => setTimeout(r, duration * 1e3));
3274
+ await runCliclick([`ku:${cliName}`]);
3275
+ return `held ${text} for ${duration}s`;
3276
+ }
3277
+ case "scroll": {
3278
+ const direction = input.scroll_direction ?? "down";
3279
+ const amount = input.scroll_amount ?? 3;
3280
+ const px = amount * 100;
3281
+ const dx = direction === "left" ? -px : direction === "right" ? px : 0;
3282
+ const dy = direction === "up" ? -px : direction === "down" ? px : 0;
3283
+ if (input.coordinate) {
3284
+ const [x, y] = input.coordinate;
3285
+ await runCliclick([`m:${x},${y}`]);
3286
+ }
3287
+ const mods = input.text ? modifierStringToCliclick(input.text) : [];
3288
+ if (mods.length > 0) {
3289
+ await runCliclick([`kd:${mods.join(",")}`]);
3290
+ }
3291
+ await runScroll(dx, dy);
3292
+ if (mods.length > 0) {
3293
+ await runCliclick([`ku:${mods.join(",")}`]);
3294
+ }
3295
+ return `scrolled ${direction} by ${amount}`;
3296
+ }
3297
+ case "wait": {
3298
+ const duration = input.duration ?? 1;
3299
+ await new Promise((r) => setTimeout(r, duration * 1e3));
3300
+ return `waited ${duration}s`;
3301
+ }
3302
+ case "cursor_position": {
3303
+ const out = await runCliclick(["p:."]);
3304
+ return `cursor at ${out}`;
3305
+ }
3306
+ case "zoom": {
3307
+ const region = input.region ?? [0, 0, displayWidth, displayHeight];
3308
+ const [x1, y1, x2, y2] = region;
3309
+ const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
3310
+ await runScreencapture(tmpPath);
3311
+ const sharpModule = await import("sharp");
3312
+ const sharp2 = sharpModule.default || sharpModule;
3313
+ const meta = await sharp2(tmpPath).metadata();
3314
+ const scaleX = (meta.width || displayWidth) / displayWidth;
3315
+ const scaleY = (meta.height || displayHeight) / displayHeight;
3316
+ const px = {
3317
+ left: Math.max(0, Math.round(x1 * scaleX)),
3318
+ top: Math.max(0, Math.round(y1 * scaleY)),
3319
+ width: Math.max(1, Math.round((x2 - x1) * scaleX)),
3320
+ height: Math.max(1, Math.round((y2 - y1) * scaleY))
3321
+ };
3322
+ const buf = await sharp2(tmpPath).extract(px).png().toBuffer();
3323
+ try {
3324
+ unlinkSync2(tmpPath);
3325
+ } catch {
3326
+ }
3327
+ return { type: "image", data: buf.toString("base64") };
3328
+ }
3329
+ default: {
3330
+ const exhaustive = input.action;
3331
+ return `unsupported action: ${String(exhaustive)}`;
3332
+ }
3333
+ }
3334
+ } catch (err) {
3335
+ const msg = err?.message || String(err);
3336
+ let hint = "";
3337
+ if (/accessibility|not authorized|tcc|operation not permitted/i.test(msg)) {
3338
+ hint = " (Hint: call enable_computer_use to (re-)check permissions and open System Settings)";
3339
+ } else if (/command not found/i.test(msg)) {
3340
+ hint = " (Hint: install cliclick with `brew install cliclick`)";
3341
+ }
3342
+ return `Error: ${msg}${hint}`;
3343
+ }
3344
+ },
3345
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3346
+ toModelOutput({ output }) {
3347
+ if (typeof output === "string") {
3348
+ return { type: "content", value: [{ type: "text", text: output }] };
3349
+ }
3350
+ return {
3351
+ type: "content",
3352
+ value: [{ type: "media", data: output.data, mediaType: "image/png" }]
3353
+ };
3354
+ }
3355
+ });
3356
+ }
3357
+ var execAsync5, DEFAULT_WIDTH, DEFAULT_HEIGHT;
3358
+ var init_computer_use = __esm({
3359
+ "src/tools/computer-use.ts"() {
3360
+ "use strict";
3361
+ execAsync5 = promisify5(exec5);
3362
+ DEFAULT_WIDTH = 1280;
3363
+ DEFAULT_HEIGHT = 800;
3364
+ }
3365
+ });
3366
+
2917
3367
  // src/utils/webhook.ts
2918
3368
  var webhook_exports = {};
2919
3369
  __export(webhook_exports, {
2920
3370
  sendWebhook: () => sendWebhook
2921
3371
  });
2922
3372
  async function sendWebhook(url, event) {
3373
+ const t0 = Date.now();
2923
3374
  try {
2924
3375
  const controller = new AbortController();
2925
3376
  const timeout = setTimeout(() => controller.abort(), 5e3);
@@ -2933,17 +3384,36 @@ async function sendWebhook(url, event) {
2933
3384
  signal: controller.signal
2934
3385
  });
2935
3386
  clearTimeout(timeout);
3387
+ const ms = Date.now() - t0;
2936
3388
  if (!response.ok) {
2937
- console.warn(`[WEBHOOK] ${event.type} to ${url} returned HTTP ${response.status}`);
3389
+ const body = await response.text().catch(() => "");
3390
+ console.warn(
3391
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> HTTP ${response.status} in ${ms}ms${body ? ` (${body.slice(0, 200)})` : ""}`
3392
+ );
3393
+ return;
3394
+ }
3395
+ if (!QUIET || TERMINAL_EVENTS.has(event.type)) {
3396
+ console.log(
3397
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> 200 in ${ms}ms`
3398
+ );
2938
3399
  }
2939
3400
  } catch (err) {
2940
3401
  const reason = err.name === "AbortError" ? "timeout (5s)" : err.message;
2941
- console.warn(`[WEBHOOK] ${event.type} to ${url} failed: ${reason}`);
3402
+ console.warn(
3403
+ `[WEBHOOK] ${event.type} task=${event.taskId} -> failed: ${reason}`
3404
+ );
2942
3405
  }
2943
3406
  }
3407
+ var TERMINAL_EVENTS, QUIET;
2944
3408
  var init_webhook = __esm({
2945
3409
  "src/utils/webhook.ts"() {
2946
3410
  "use strict";
3411
+ TERMINAL_EVENTS = /* @__PURE__ */ new Set([
3412
+ "task.started",
3413
+ "task.completed",
3414
+ "task.failed"
3415
+ ]);
3416
+ QUIET = process.env.SPARKECODER_QUIET_WEBHOOKS === "1";
2947
3417
  }
2948
3418
  });
2949
3419
 
@@ -3153,15 +3623,15 @@ var recorder_exports = {};
3153
3623
  __export(recorder_exports, {
3154
3624
  FrameRecorder: () => FrameRecorder
3155
3625
  });
3156
- import { exec as exec5 } from "child_process";
3157
- import { promisify as promisify5 } from "util";
3626
+ import { exec as exec6 } from "child_process";
3627
+ import { promisify as promisify6 } from "util";
3158
3628
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
3159
- import { join as join8 } from "path";
3160
- import { tmpdir } from "os";
3161
- import { nanoid as nanoid3 } from "nanoid";
3629
+ import { join as join9 } from "path";
3630
+ import { tmpdir as tmpdir2 } from "os";
3631
+ import { nanoid as nanoid5 } from "nanoid";
3162
3632
  async function checkFfmpeg() {
3163
3633
  try {
3164
- await execAsync5("ffmpeg -version", { timeout: 5e3 });
3634
+ await execAsync6("ffmpeg -version", { timeout: 5e3 });
3165
3635
  return true;
3166
3636
  } catch {
3167
3637
  return false;
@@ -3173,11 +3643,11 @@ async function cleanup(dir) {
3173
3643
  } catch {
3174
3644
  }
3175
3645
  }
3176
- var execAsync5, FrameRecorder;
3646
+ var execAsync6, FrameRecorder;
3177
3647
  var init_recorder = __esm({
3178
3648
  "src/browser/recorder.ts"() {
3179
3649
  "use strict";
3180
- execAsync5 = promisify5(exec5);
3650
+ execAsync6 = promisify6(exec6);
3181
3651
  FrameRecorder = class {
3182
3652
  frames = [];
3183
3653
  startTime = null;
@@ -3213,21 +3683,21 @@ var init_recorder = __esm({
3213
3683
  */
3214
3684
  async encode() {
3215
3685
  if (this.frames.length === 0) return null;
3216
- const workDir = join8(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
3686
+ const workDir = join9(tmpdir2(), `sparkecoder-recording-${nanoid5(8)}`);
3217
3687
  await mkdir4(workDir, { recursive: true });
3218
3688
  try {
3219
3689
  for (let i = 0; i < this.frames.length; i++) {
3220
- const framePath = join8(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
3690
+ const framePath = join9(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
3221
3691
  await writeFile5(framePath, this.frames[i].data);
3222
3692
  }
3223
3693
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
3224
3694
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
3225
3695
  const clampedFps = Math.max(1, Math.min(fps, 30));
3226
- const outputPath = join8(workDir, `recording_${this.sessionId}.mp4`);
3696
+ const outputPath = join9(workDir, `recording_${this.sessionId}.mp4`);
3227
3697
  const hasFfmpeg = await checkFfmpeg();
3228
3698
  if (hasFfmpeg) {
3229
- await execAsync5(
3230
- `ffmpeg -y -framerate ${clampedFps} -i "${join8(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
3699
+ await execAsync6(
3700
+ `ffmpeg -y -framerate ${clampedFps} -i "${join9(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
3231
3701
  { timeout: 12e4 }
3232
3702
  );
3233
3703
  } else {
@@ -3239,7 +3709,7 @@ var init_recorder = __esm({
3239
3709
  const files = await readdir5(workDir);
3240
3710
  for (const f of files) {
3241
3711
  if (f.startsWith("frame_")) {
3242
- await unlink2(join8(workDir, f)).catch(() => {
3712
+ await unlink2(join9(workDir, f)).catch(() => {
3243
3713
  });
3244
3714
  }
3245
3715
  }
@@ -3260,21 +3730,405 @@ var init_recorder = __esm({
3260
3730
  }
3261
3731
  });
3262
3732
 
3733
+ // src/personal-agent/system-metrics.ts
3734
+ import { execSync as execSync2 } from "child_process";
3735
+ import { readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
3736
+ import {
3737
+ arch,
3738
+ cpus,
3739
+ freemem,
3740
+ hostname,
3741
+ loadavg,
3742
+ networkInterfaces,
3743
+ platform as platform2,
3744
+ release,
3745
+ totalmem,
3746
+ type,
3747
+ uptime,
3748
+ userInfo
3749
+ } from "os";
3750
+ function snapshotCpuTimes() {
3751
+ const sum = { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 };
3752
+ for (const c of cpus()) {
3753
+ sum.user += c.times.user;
3754
+ sum.nice += c.times.nice;
3755
+ sum.sys += c.times.sys;
3756
+ sum.idle += c.times.idle;
3757
+ sum.irq += c.times.irq;
3758
+ }
3759
+ return sum;
3760
+ }
3761
+ function readCpuUsage() {
3762
+ const now = snapshotCpuTimes();
3763
+ if (!_lastSample) {
3764
+ _lastSample = now;
3765
+ return 0;
3766
+ }
3767
+ const dUser = now.user - _lastSample.user;
3768
+ const dNice = now.nice - _lastSample.nice;
3769
+ const dSys = now.sys - _lastSample.sys;
3770
+ const dIdle = now.idle - _lastSample.idle;
3771
+ const dIrq = now.irq - _lastSample.irq;
3772
+ const total = dUser + dNice + dSys + dIdle + dIrq;
3773
+ _lastSample = now;
3774
+ if (total <= 0) return 0;
3775
+ return Math.max(0, Math.min(1, (total - dIdle) / total));
3776
+ }
3777
+ function readCpuTempC() {
3778
+ try {
3779
+ if (platform2() === "linux") {
3780
+ let hottest = -Infinity;
3781
+ try {
3782
+ for (const entry of readdirSync3("/sys/class/thermal")) {
3783
+ if (!entry.startsWith("thermal_zone")) continue;
3784
+ try {
3785
+ const v = Number(
3786
+ readFileSync8(`/sys/class/thermal/${entry}/temp`, "utf8").trim()
3787
+ );
3788
+ if (Number.isFinite(v) && v > hottest) hottest = v;
3789
+ } catch {
3790
+ }
3791
+ }
3792
+ } catch {
3793
+ }
3794
+ if (hottest > -Infinity) return hottest / 1e3;
3795
+ }
3796
+ const overrideCmd = process.env.PERSONAL_AGENT_TEMP_CMD;
3797
+ if (overrideCmd) {
3798
+ const out = execSync2(overrideCmd, { encoding: "utf8", timeout: 1500 }).trim();
3799
+ const v = Number(out);
3800
+ if (Number.isFinite(v)) return v;
3801
+ }
3802
+ } catch {
3803
+ }
3804
+ return void 0;
3805
+ }
3806
+ function readDisks() {
3807
+ try {
3808
+ const p = platform2();
3809
+ if (p === "darwin" || p === "linux") {
3810
+ const raw = execSync2("df -kP", { encoding: "utf8", timeout: 2e3 });
3811
+ const lines = raw.trim().split("\n").slice(1);
3812
+ const out = [];
3813
+ for (const line of lines) {
3814
+ const parts = line.trim().split(/\s+/);
3815
+ if (parts.length < 6) continue;
3816
+ const filesystem = parts[0];
3817
+ const total1k = Number(parts[1]);
3818
+ const used1k = Number(parts[2]);
3819
+ const free1k = Number(parts[3]);
3820
+ const mount = parts.slice(5).join(" ");
3821
+ if (!Number.isFinite(total1k) || total1k <= 0) continue;
3822
+ 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")) {
3823
+ if (mount !== "/") continue;
3824
+ }
3825
+ out.push({
3826
+ mount,
3827
+ filesystem,
3828
+ totalBytes: total1k * 1024,
3829
+ usedBytes: used1k * 1024,
3830
+ freeBytes: free1k * 1024,
3831
+ usage: total1k > 0 ? used1k / total1k : 0
3832
+ });
3833
+ }
3834
+ out.sort((a, b) => {
3835
+ const score = (m) => m === "/" ? 0 : m.startsWith("/Users") || m.startsWith("/home") ? 1 : 2;
3836
+ return score(a.mount) - score(b.mount);
3837
+ });
3838
+ return out.slice(0, 6);
3839
+ }
3840
+ if (p === "win32") {
3841
+ const raw = execSync2(
3842
+ "wmic logicaldisk get DeviceID,Size,FreeSpace /format:csv",
3843
+ { encoding: "utf8", timeout: 3e3 }
3844
+ );
3845
+ const out = [];
3846
+ for (const line of raw.trim().split(/\r?\n/).slice(1)) {
3847
+ const cols = line.split(",");
3848
+ if (cols.length < 4) continue;
3849
+ const [, deviceId, freeStr, sizeStr] = cols;
3850
+ const total = Number(sizeStr);
3851
+ const free = Number(freeStr);
3852
+ if (!Number.isFinite(total) || total <= 0) continue;
3853
+ const used = Math.max(0, total - free);
3854
+ out.push({
3855
+ mount: deviceId,
3856
+ totalBytes: total,
3857
+ usedBytes: used,
3858
+ freeBytes: free,
3859
+ usage: used / total
3860
+ });
3861
+ }
3862
+ return out;
3863
+ }
3864
+ } catch {
3865
+ }
3866
+ return void 0;
3867
+ }
3868
+ function readNetwork() {
3869
+ try {
3870
+ const out = [];
3871
+ const ifaces = networkInterfaces();
3872
+ for (const [name, addrs] of Object.entries(ifaces)) {
3873
+ if (!addrs) continue;
3874
+ for (const a of addrs) {
3875
+ if (a.internal) continue;
3876
+ out.push({
3877
+ iface: name,
3878
+ family: a.family,
3879
+ address: a.address,
3880
+ mac: a.mac,
3881
+ internal: a.internal
3882
+ });
3883
+ }
3884
+ }
3885
+ return out;
3886
+ } catch {
3887
+ return void 0;
3888
+ }
3889
+ }
3890
+ function readSystemMetrics() {
3891
+ const cpuList = cpus();
3892
+ const usage = readCpuUsage();
3893
+ const tot = totalmem();
3894
+ const free = freemem();
3895
+ const metrics = {
3896
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
3897
+ hostname: hostname(),
3898
+ platform: platform2(),
3899
+ arch: arch(),
3900
+ kernelRelease: release(),
3901
+ osType: type(),
3902
+ processUptimeSec: Math.round(process.uptime()),
3903
+ systemUptimeSec: Math.round(uptime()),
3904
+ user: safeUser(),
3905
+ cpu: cpuList[0] ? {
3906
+ model: cpuList[0].model,
3907
+ count: cpuList.length,
3908
+ speedMhz: cpuList[0].speed,
3909
+ loadAvg1: loadavg()[0],
3910
+ loadAvg5: loadavg()[1],
3911
+ loadAvg15: loadavg()[2],
3912
+ usage,
3913
+ tempC: readCpuTempC()
3914
+ } : void 0,
3915
+ memory: {
3916
+ totalBytes: tot,
3917
+ freeBytes: free,
3918
+ usedBytes: Math.max(0, tot - free),
3919
+ usage: tot > 0 ? (tot - free) / tot : 0
3920
+ },
3921
+ disks: readDisks(),
3922
+ network: readNetwork()
3923
+ };
3924
+ return metrics;
3925
+ }
3926
+ function safeUser() {
3927
+ try {
3928
+ return userInfo().username;
3929
+ } catch {
3930
+ return process.env.USER ?? process.env.USERNAME ?? "unknown";
3931
+ }
3932
+ }
3933
+ var _lastSample;
3934
+ var init_system_metrics = __esm({
3935
+ "src/personal-agent/system-metrics.ts"() {
3936
+ "use strict";
3937
+ _lastSample = null;
3938
+ snapshotCpuTimes();
3939
+ }
3940
+ });
3941
+
3942
+ // src/personal-agent/heartbeat.ts
3943
+ var heartbeat_exports = {};
3944
+ __export(heartbeat_exports, {
3945
+ getHardwareId: () => getHardwareId,
3946
+ getHardwareIdCached: () => getHardwareIdCached,
3947
+ readConfig: () => readConfig,
3948
+ startPersonalAgent: () => startPersonalAgent
3949
+ });
3950
+ import { execSync as execSync3 } from "child_process";
3951
+ import { readFileSync as readFileSync9 } from "fs";
3952
+ import { hostname as hostname2, platform as platform3 } from "os";
3953
+ function readConfig(opts) {
3954
+ const enabled = Boolean(opts.personalAgentMode) || process.env.PERSONAL_AGENT_MODE === "1" || process.env.PERSONAL_AGENT_MODE === "true";
3955
+ return {
3956
+ enabled,
3957
+ dashboardUrl: opts.personalAgentDashboard ?? process.env.PERSONAL_AGENT_DASHBOARD,
3958
+ name: opts.personalAgentName ?? process.env.PERSONAL_AGENT_NAME ?? hostname2(),
3959
+ publicUrl: opts.personalAgentPublicUrl ?? process.env.PERSONAL_AGENT_PUBLIC_URL,
3960
+ webUrl: opts.personalAgentWebUrl ?? process.env.PERSONAL_AGENT_WEB_URL,
3961
+ ingestToken: opts.personalAgentIngestToken ?? process.env.PERSONAL_AGENT_INGEST_TOKEN
3962
+ };
3963
+ }
3964
+ function getHardwareIdCached() {
3965
+ if (_cachedHwid !== null) return _cachedHwid;
3966
+ _cachedHwid = getHardwareId();
3967
+ return _cachedHwid;
3968
+ }
3969
+ function getHardwareId() {
3970
+ const p = platform3();
3971
+ try {
3972
+ if (p === "darwin") {
3973
+ const out = execSync3(
3974
+ `ioreg -rd1 -c IOPlatformExpertDevice | awk -F\\" '/IOPlatformUUID/ {print $4}'`,
3975
+ { encoding: "utf8", timeout: 2e3 }
3976
+ ).trim();
3977
+ if (out) return normalize2(out);
3978
+ } else if (p === "linux") {
3979
+ try {
3980
+ return normalize2(readFileSync9("/etc/machine-id", "utf8").trim());
3981
+ } catch {
3982
+ return normalize2(readFileSync9("/var/lib/dbus/machine-id", "utf8").trim());
3983
+ }
3984
+ } else if (p === "win32") {
3985
+ const out = execSync3("wmic csproduct get uuid /value", {
3986
+ encoding: "utf8",
3987
+ timeout: 3e3
3988
+ });
3989
+ const m = out.match(/UUID=([\w-]+)/i);
3990
+ if (m && m[1]) return normalize2(m[1]);
3991
+ }
3992
+ } catch (e) {
3993
+ console.warn(`[personal-agent] could not read hardware UUID: ${e.message}`);
3994
+ }
3995
+ console.warn(
3996
+ "[personal-agent] falling back to hostname for HWID; this is NOT stable across rename/reinstall"
3997
+ );
3998
+ return `host-${hostname2()}`;
3999
+ }
4000
+ function normalize2(raw) {
4001
+ return raw.trim().toLowerCase().replace(/[^a-z0-9-]/g, "");
4002
+ }
4003
+ function startPersonalAgent(cfg) {
4004
+ if (!cfg.enabled) return;
4005
+ if (!cfg.dashboardUrl) {
4006
+ console.warn(
4007
+ "[personal-agent] --personal-agent-mode is on but no --personal-agent-dashboard set; skipping heartbeat"
4008
+ );
4009
+ return;
4010
+ }
4011
+ if (!cfg.publicUrl) {
4012
+ console.warn(
4013
+ "[personal-agent] no --personal-agent-public-url set; the dashboard won't know how to call this sparkecoder"
4014
+ );
4015
+ }
4016
+ if (!cfg.ingestToken) {
4017
+ console.warn(
4018
+ "[personal-agent] no --personal-agent-ingest-token set; the dashboard's heartbeat endpoint will reject us with 401"
4019
+ );
4020
+ }
4021
+ const hwid = getHardwareId();
4022
+ const startedAt = Date.now();
4023
+ console.log("");
4024
+ console.log(`Personal Agent: ${cfg.name}`);
4025
+ console.log(`HWID: ${hwid}`);
4026
+ if (cfg.publicUrl) console.log(`Public URL: ${cfg.publicUrl}`);
4027
+ if (cfg.webUrl) console.log(`Web URL: ${cfg.webUrl}`);
4028
+ console.log(`Dashboard: ${cfg.dashboardUrl}`);
4029
+ console.log("");
4030
+ console.log(`Ask an admin to assign this HWID to your user from`);
4031
+ console.log(`${cfg.dashboardUrl.replace(/\/$/, "")}/admin/devices`);
4032
+ console.log("");
4033
+ const warned = /* @__PURE__ */ new Set();
4034
+ function warnOnce(key2, msg) {
4035
+ if (warned.has(key2)) return;
4036
+ warned.add(key2);
4037
+ console.warn(msg);
4038
+ }
4039
+ async function tick() {
4040
+ if (!cfg.publicUrl) {
4041
+ warnOnce(
4042
+ "no-public-url",
4043
+ "[personal-agent] skipping heartbeat: no --personal-agent-public-url / PERSONAL_AGENT_PUBLIC_URL set"
4044
+ );
4045
+ return;
4046
+ }
4047
+ let metrics;
4048
+ try {
4049
+ metrics = readSystemMetrics();
4050
+ } catch (e) {
4051
+ warnOnce(
4052
+ "metrics-error",
4053
+ `[personal-agent] failed to collect system metrics for heartbeat: ${e.message}`
4054
+ );
4055
+ }
4056
+ const body = {
4057
+ hwid,
4058
+ name: cfg.name,
4059
+ sparkecoderUrl: cfg.publicUrl,
4060
+ ...cfg.webUrl ? { webUrl: cfg.webUrl } : {},
4061
+ status: "online",
4062
+ uptimeMs: Date.now() - startedAt,
4063
+ os: platform3(),
4064
+ ...metrics ? { metrics } : {}
4065
+ };
4066
+ try {
4067
+ const res = await fetch(
4068
+ `${cfg.dashboardUrl.replace(/\/$/, "")}/api/devices/heartbeat`,
4069
+ {
4070
+ method: "POST",
4071
+ headers: {
4072
+ "Content-Type": "application/json",
4073
+ ...cfg.ingestToken ? { Authorization: `Bearer ${cfg.ingestToken}` } : {}
4074
+ },
4075
+ body: JSON.stringify(body)
4076
+ }
4077
+ );
4078
+ if (res.status === 401) {
4079
+ warnOnce(
4080
+ "401",
4081
+ "[personal-agent] heartbeat got 401 from dashboard \u2014 check PERSONAL_AGENT_INGEST_TOKEN matches both sides"
4082
+ );
4083
+ } else if (res.status === 400) {
4084
+ const text = await res.text().catch(() => "");
4085
+ warnOnce(
4086
+ "400",
4087
+ `[personal-agent] heartbeat got 400 from dashboard \u2014 ${text.slice(0, 200)}`
4088
+ );
4089
+ } else if (!res.ok) {
4090
+ warnOnce(
4091
+ `non2xx-${res.status}`,
4092
+ `[personal-agent] heartbeat got ${res.status} from dashboard`
4093
+ );
4094
+ }
4095
+ } catch (e) {
4096
+ warnOnce(
4097
+ "fetch-error",
4098
+ `[personal-agent] heartbeat network error: ${e.message}`
4099
+ );
4100
+ }
4101
+ }
4102
+ void tick();
4103
+ setInterval(tick, 15e3).unref();
4104
+ }
4105
+ var _cachedHwid;
4106
+ var init_heartbeat = __esm({
4107
+ "src/personal-agent/heartbeat.ts"() {
4108
+ "use strict";
4109
+ init_system_metrics();
4110
+ _cachedHwid = null;
4111
+ }
4112
+ });
4113
+
3263
4114
  // src/cli.ts
3264
4115
  import { Command } from "commander";
3265
4116
  import chalk from "chalk";
3266
4117
  import ora from "ora";
3267
4118
  import "dotenv/config";
3268
4119
  import { createInterface } from "readline";
4120
+ import { dirname as dirname8 } from "path";
4121
+ import { fileURLToPath as fileURLToPath5 } from "url";
4122
+ import { hostname as hostname4 } from "os";
3269
4123
 
3270
4124
  // src/server/index.ts
3271
4125
  import "dotenv/config";
3272
- import { Hono as Hono6 } from "hono";
4126
+ import { Hono as Hono7 } from "hono";
3273
4127
  import { serve } from "@hono/node-server";
3274
4128
  import { cors } from "hono/cors";
3275
4129
  import { logger } from "hono/logger";
3276
- import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
3277
- import { resolve as resolve10, dirname as dirname7, join as join12 } from "path";
4130
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5 } from "fs";
4131
+ import { resolve as resolve10, dirname as dirname7, join as join13 } from "path";
3278
4132
  import { spawn as spawn2 } from "child_process";
3279
4133
  import { createServer as createNetServer } from "net";
3280
4134
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -3283,17 +4137,17 @@ import { fileURLToPath as fileURLToPath4 } from "url";
3283
4137
  init_db();
3284
4138
  import { Hono } from "hono";
3285
4139
  import { zValidator } from "@hono/zod-validator";
3286
- import { z as z15 } from "zod";
3287
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
4140
+ import { z as z16 } from "zod";
4141
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
3288
4142
  import { readdir as readdir6 } from "fs/promises";
3289
- import { join as join9, basename as basename5, extname as extname8, relative as relative9 } from "path";
3290
- import { nanoid as nanoid5 } from "nanoid";
4143
+ import { join as join10, basename as basename5, extname as extname8, relative as relative9 } from "path";
4144
+ import { nanoid as nanoid7 } from "nanoid";
3291
4145
 
3292
4146
  // src/agent/index.ts
3293
4147
  import {
3294
4148
  streamText as streamText2,
3295
4149
  generateText as generateText3,
3296
- tool as tool13,
4150
+ tool as tool14,
3297
4151
  stepCountIs as stepCountIs2
3298
4152
  } from "ai";
3299
4153
 
@@ -3462,6 +4316,23 @@ function isAnthropicModel(modelId) {
3462
4316
  const normalized = modelId.trim().toLowerCase();
3463
4317
  return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
3464
4318
  }
4319
+ function requiresAdaptiveThinking(modelId) {
4320
+ const m = modelId.toLowerCase().match(/claude-(?:opus|sonnet|haiku)-(\d+)[.-](\d{1,2})(?!\d)/);
4321
+ if (!m) return false;
4322
+ const major = Number(m[1]);
4323
+ const minor = Number(m[2]);
4324
+ if (Number.isNaN(major) || Number.isNaN(minor)) return false;
4325
+ if (major > 4) return true;
4326
+ if (major === 4 && minor >= 6) return true;
4327
+ return false;
4328
+ }
4329
+ function getAnthropicProviderOptions(modelId, opts = {}) {
4330
+ const { toolStreaming, budgetTokens = 1e4 } = opts;
4331
+ const thinking = requiresAdaptiveThinking(modelId) ? { type: "adaptive" } : { type: "enabled", budgetTokens };
4332
+ const out = { thinking };
4333
+ if (toolStreaming) out.toolStreaming = true;
4334
+ return out;
4335
+ }
3465
4336
  function resolveModel(modelId) {
3466
4337
  try {
3467
4338
  const config = getConfig();
@@ -3484,8 +4355,8 @@ var SUBAGENT_MODELS = {
3484
4355
  // src/agent/index.ts
3485
4356
  init_db();
3486
4357
  init_config();
3487
- import { z as z14 } from "zod";
3488
- import { nanoid as nanoid4 } from "nanoid";
4358
+ import { z as z15 } from "zod";
4359
+ import { nanoid as nanoid6 } from "nanoid";
3489
4360
 
3490
4361
  // src/tools/bash.ts
3491
4362
  import { tool } from "ai";
@@ -3818,11 +4689,11 @@ async function sendInput(terminalId, input, options = {}) {
3818
4689
  return false;
3819
4690
  }
3820
4691
  }
3821
- async function sendKey(terminalId, key) {
4692
+ async function sendKey(terminalId, key2) {
3822
4693
  const session = getSessionName(terminalId);
3823
4694
  try {
3824
4695
  await execAsync(`tmux has-session -t ${session}`, { timeout: 1e3 });
3825
- await execAsync(`tmux send-keys -t ${session} ${key}`, { timeout: 1e3 });
4696
+ await execAsync(`tmux send-keys -t ${session} ${key2}`, { timeout: 1e3 });
3826
4697
  return true;
3827
4698
  } catch {
3828
4699
  return false;
@@ -3961,7 +4832,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
3961
4832
  Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
3962
4833
  inputSchema: bashInputSchema,
3963
4834
  execute: async (inputArgs) => {
3964
- const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
4835
+ const { command, background, id, kill, tail, input: textInput, key: key2 } = inputArgs;
3965
4836
  if (id) {
3966
4837
  if (kill) {
3967
4838
  const success = await killTerminal(id);
@@ -3992,8 +4863,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3992
4863
  message: `Sent input "${textInput}" to terminal`
3993
4864
  };
3994
4865
  }
3995
- if (key) {
3996
- const success = await sendKey(id, key);
4866
+ if (key2) {
4867
+ const success = await sendKey(id, key2);
3997
4868
  if (!success) {
3998
4869
  return {
3999
4870
  success: false,
@@ -4009,7 +4880,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
4009
4880
  id,
4010
4881
  output: truncatedOutput2,
4011
4882
  status: status2,
4012
- message: `Sent key "${key}" to terminal`
4883
+ message: `Sent key "${key2}" to terminal`
4013
4884
  };
4014
4885
  }
4015
4886
  const { output, status } = await getLogs(id, options.workingDirectory, { tail, sessionId: options.sessionId });
@@ -4151,13 +5022,13 @@ async function resizeImageIfNeeded(buffer, mediaType) {
4151
5022
  const needsResize = longEdge > MAX_LONG_EDGE;
4152
5023
  const needsShrink = buffer.length > MAX_FILE_BYTES;
4153
5024
  if (!needsResize && !needsShrink) return { buffer, mediaType: inputMediaType };
4154
- const key = cacheKey(buffer);
5025
+ const key2 = cacheKey(buffer);
4155
5026
  const cacheDir = getCacheDir();
4156
5027
  const isPng = inputMediaType.includes("png");
4157
5028
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
4158
5029
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
4159
5030
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
4160
- const cachePath = join3(cacheDir, key + ext);
5031
+ const cachePath = join3(cacheDir, key2 + ext);
4161
5032
  if (existsSync3(cachePath)) {
4162
5033
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
4163
5034
  return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
@@ -4552,12 +5423,12 @@ function findNearestRoot(startDir, markers) {
4552
5423
  }
4553
5424
  async function commandExists(cmd) {
4554
5425
  try {
4555
- const { exec: exec7 } = await import("child_process");
4556
- const { promisify: promisify7 } = await import("util");
4557
- const execAsync7 = promisify7(exec7);
5426
+ const { exec: exec8 } = await import("child_process");
5427
+ const { promisify: promisify8 } = await import("util");
5428
+ const execAsync8 = promisify8(exec8);
4558
5429
  const isWindows = process.platform === "win32";
4559
5430
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
4560
- await execAsync7(checkCmd);
5431
+ await execAsync8(checkCmd);
4561
5432
  return true;
4562
5433
  } catch {
4563
5434
  return false;
@@ -5048,31 +5919,31 @@ async function getClientForFile(filePath) {
5048
5919
  return null;
5049
5920
  }
5050
5921
  const root = dirname4(normalized);
5051
- const key = `${serverDef.id}:${root}`;
5052
- const existing = state.clients.get(key);
5922
+ const key2 = `${serverDef.id}:${root}`;
5923
+ const existing = state.clients.get(key2);
5053
5924
  if (existing) {
5054
5925
  return existing;
5055
5926
  }
5056
- if (state.broken.has(key)) {
5927
+ if (state.broken.has(key2)) {
5057
5928
  return null;
5058
5929
  }
5059
5930
  try {
5060
5931
  const handle = await serverDef.spawn(root);
5061
5932
  if (!handle) {
5062
- state.broken.add(key);
5933
+ state.broken.add(key2);
5063
5934
  return null;
5064
5935
  }
5065
5936
  console.log(`[lsp] Started ${serverDef.name} for ${root}`);
5066
5937
  const client = await createClient(serverDef.id, handle, root);
5067
- state.clients.set(key, client);
5938
+ state.clients.set(key2, client);
5068
5939
  handle.process.on("exit", (code) => {
5069
5940
  console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
5070
- state.clients.delete(key);
5941
+ state.clients.delete(key2);
5071
5942
  });
5072
5943
  return client;
5073
5944
  } catch (error) {
5074
5945
  console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
5075
- state.broken.add(key);
5946
+ state.broken.add(key2);
5076
5947
  return null;
5077
5948
  }
5078
5949
  }
@@ -6804,6 +7675,7 @@ init_semantic_search();
6804
7675
  import { tool as tool11 } from "ai";
6805
7676
  import { z as z12 } from "zod";
6806
7677
  import Ajv from "ajv";
7678
+ import { nanoid as nanoid3 } from "nanoid";
6807
7679
  var ajv = new Ajv({ allErrors: true });
6808
7680
  function createCompleteTaskTool(options) {
6809
7681
  const validate = ajv.compile(options.outputSchema);
@@ -6850,6 +7722,37 @@ function createTaskFailedTool(options) {
6850
7722
  }
6851
7723
  });
6852
7724
  }
7725
+ function createAskQuestionToUserTool(options) {
7726
+ return tool11({
7727
+ 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.",
7728
+ inputSchema: z12.object({
7729
+ question: z12.string().min(1).describe("The concise question you need answered."),
7730
+ context: z12.string().optional().describe("Brief context explaining why this answer is needed and what you already tried."),
7731
+ choices: z12.array(z12.string().min(1)).max(10).optional().describe("Optional suggested answer choices when the question is multiple choice.")
7732
+ }),
7733
+ execute: async (input) => {
7734
+ if (!options.onQuestion) {
7735
+ return {
7736
+ status: "unavailable",
7737
+ message: "Question routing is not configured for this task. Continue with the best safe assumption or call task_failed if blocked."
7738
+ };
7739
+ }
7740
+ const questionId = `q_${nanoid3(12)}`;
7741
+ const answer = await options.onQuestion({
7742
+ questionId,
7743
+ question: input.question,
7744
+ context: input.context,
7745
+ choices: input.choices
7746
+ });
7747
+ return {
7748
+ status: "answered",
7749
+ questionId,
7750
+ answer: answer.answer,
7751
+ answeredBy: answer.answeredBy ?? "unknown"
7752
+ };
7753
+ }
7754
+ });
7755
+ }
6853
7756
 
6854
7757
  // src/tools/upload-file.ts
6855
7758
  import { tool as tool12 } from "ai";
@@ -6949,17 +7852,175 @@ function createUploadFileTool(options) {
6949
7852
  }
6950
7853
 
6951
7854
  // src/tools/index.ts
6952
- init_semantic();
6953
- init_remote();
6954
- init_todo();
6955
- init_semantic_search();
6956
- async function createTools(options) {
6957
- const tools = {
6958
- bash: createBashTool({
6959
- workingDirectory: options.workingDirectory,
6960
- sessionId: options.sessionId,
6961
- onOutput: options.onBashOutput,
6962
- onProgress: options.onBashProgress
7855
+ init_computer_use();
7856
+
7857
+ // src/tools/enable-computer-use.ts
7858
+ init_db();
7859
+ init_computer_use();
7860
+ import { tool as tool13 } from "ai";
7861
+ import { z as z14 } from "zod";
7862
+ var inputSchema = z14.object({
7863
+ display_width: z14.number().int().positive().optional().describe("Display width in pixels (defaults to detected primary display, fallback 1280)"),
7864
+ display_height: z14.number().int().positive().optional().describe("Display height in pixels (defaults to detected primary display, fallback 800)"),
7865
+ request_permissions: z14.boolean().optional().default(true).describe(
7866
+ "When true (default), proactively trigger macOS permission prompts and open System Settings panes for any missing permissions."
7867
+ )
7868
+ });
7869
+ var ACCESSIBILITY_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
7870
+ var SCREEN_RECORDING_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
7871
+ function createEnableComputerUseTool(options) {
7872
+ return tool13({
7873
+ 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.",
7874
+ inputSchema,
7875
+ execute: async ({ display_width, display_height, request_permissions }) => {
7876
+ try {
7877
+ if (!isMacOs()) {
7878
+ return {
7879
+ success: false,
7880
+ error: "Computer use is currently only supported on macOS.",
7881
+ platform: process.platform
7882
+ };
7883
+ }
7884
+ if (!await isCliclickInstalled()) {
7885
+ return {
7886
+ success: false,
7887
+ error: "`cliclick` is not installed. It is required for mouse/keyboard control on macOS.",
7888
+ installCommand: "brew install cliclick",
7889
+ fixSteps: [
7890
+ "In a terminal on this Mac, run: brew install cliclick",
7891
+ "(If Homebrew is not installed, install it first from https://brew.sh)",
7892
+ "Then call enable_computer_use again"
7893
+ ]
7894
+ };
7895
+ }
7896
+ const acc = await hasAccessibilityPermissions();
7897
+ const screen = await hasScreenRecordingPermissions();
7898
+ const missing = [];
7899
+ if (!acc.ok) {
7900
+ let prompted = false;
7901
+ let panelOpened = false;
7902
+ if (request_permissions) {
7903
+ prompted = await requestAccessibilityPrompt().then(() => true).catch(() => false);
7904
+ await openSystemSettings("accessibility").then(() => {
7905
+ panelOpened = true;
7906
+ }).catch(() => void 0);
7907
+ }
7908
+ missing.push({
7909
+ name: "Accessibility",
7910
+ reason: "cliclick failed: " + (acc.error?.split("\n")[0] || "no permission"),
7911
+ pane: "accessibility",
7912
+ settingsUrl: ACCESSIBILITY_URL,
7913
+ fixSteps: [
7914
+ "In the System Settings \u2192 Privacy & Security \u2192 Accessibility pane that opened",
7915
+ "Click the + button",
7916
+ "Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
7917
+ "Toggle the switch ON",
7918
+ "Restart the agent process so the new permission takes effect",
7919
+ "Then call enable_computer_use again"
7920
+ ],
7921
+ prompted,
7922
+ panelOpened
7923
+ });
7924
+ }
7925
+ if (!screen) {
7926
+ let prompted = false;
7927
+ let panelOpened = false;
7928
+ if (request_permissions) {
7929
+ prompted = await requestScreenRecordingPrompt().then(() => true).catch(() => false);
7930
+ await openSystemSettings("screen-recording").then(() => {
7931
+ panelOpened = true;
7932
+ }).catch(() => void 0);
7933
+ }
7934
+ missing.push({
7935
+ name: "Screen Recording",
7936
+ reason: "CGPreflightScreenCaptureAccess returned false",
7937
+ pane: "screen-recording",
7938
+ settingsUrl: SCREEN_RECORDING_URL,
7939
+ fixSteps: [
7940
+ "In the System Settings \u2192 Privacy & Security \u2192 Screen Recording pane that opened",
7941
+ "Click the + button",
7942
+ "Add the application running the agent (Terminal, iTerm, your IDE, or `node`)",
7943
+ "Toggle the switch ON",
7944
+ "Restart the agent process so the new permission takes effect",
7945
+ "Then call enable_computer_use again"
7946
+ ],
7947
+ prompted,
7948
+ panelOpened
7949
+ });
7950
+ }
7951
+ if (missing.length > 0) {
7952
+ return {
7953
+ success: false,
7954
+ error: `Missing permission${missing.length > 1 ? "s" : ""}: ` + missing.map((m) => m.name).join(" and ") + ".",
7955
+ missingPermissions: missing,
7956
+ 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."
7957
+ };
7958
+ }
7959
+ let width = display_width;
7960
+ let height = display_height;
7961
+ let detected = null;
7962
+ if (width === void 0 || height === void 0) {
7963
+ detected = await detectScreenSize();
7964
+ width = width ?? detected?.width ?? 1280;
7965
+ height = height ?? detected?.height ?? 800;
7966
+ }
7967
+ const session = await sessionQueries.getById(options.sessionId);
7968
+ if (!session) {
7969
+ return { success: false, error: "Session not found" };
7970
+ }
7971
+ const config = session.config || {};
7972
+ if (config.computerUseEnabled === true && config.computerUseDisplayWidth === width && config.computerUseDisplayHeight === height) {
7973
+ return {
7974
+ success: true,
7975
+ alreadyEnabled: true,
7976
+ message: "Computer use was already enabled for this session.",
7977
+ displayWidth: width,
7978
+ displayHeight: height
7979
+ };
7980
+ }
7981
+ const updated = {
7982
+ ...config,
7983
+ computerUseEnabled: true,
7984
+ computerUseDisplayWidth: width,
7985
+ computerUseDisplayHeight: height
7986
+ };
7987
+ await sessionQueries.update(options.sessionId, { config: updated });
7988
+ return {
7989
+ success: true,
7990
+ enabled: true,
7991
+ platform: "darwin",
7992
+ displayWidth: width,
7993
+ displayHeight: height,
7994
+ detectedScreenSize: detected || void 0,
7995
+ permissions: {
7996
+ accessibility: "granted",
7997
+ screenRecording: "granted"
7998
+ },
7999
+ 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.`
8000
+ };
8001
+ } catch (err) {
8002
+ return {
8003
+ success: false,
8004
+ error: err?.message || String(err)
8005
+ };
8006
+ }
8007
+ }
8008
+ });
8009
+ }
8010
+
8011
+ // src/tools/index.ts
8012
+ init_semantic();
8013
+ init_remote();
8014
+ init_todo();
8015
+ init_semantic_search();
8016
+ init_computer_use();
8017
+ async function createTools(options) {
8018
+ const tools = {
8019
+ bash: createBashTool({
8020
+ workingDirectory: options.workingDirectory,
8021
+ sessionId: options.sessionId,
8022
+ onOutput: options.onBashOutput,
8023
+ onProgress: options.onBashProgress
6963
8024
  }),
6964
8025
  read_file: createReadFileTool({
6965
8026
  workingDirectory: options.workingDirectory
@@ -6996,6 +8057,20 @@ async function createTools(options) {
6996
8057
  sessionId: options.sessionId
6997
8058
  });
6998
8059
  }
8060
+ if (process.platform === "darwin") {
8061
+ if (options.enableComputerUse) {
8062
+ tools.computer = createComputerUseTool({
8063
+ workingDirectory: options.workingDirectory,
8064
+ sessionId: options.sessionId,
8065
+ displayWidth: options.computerUseDisplayWidth,
8066
+ displayHeight: options.computerUseDisplayHeight
8067
+ });
8068
+ } else {
8069
+ tools.enable_computer_use = createEnableComputerUseTool({
8070
+ sessionId: options.sessionId
8071
+ });
8072
+ }
8073
+ }
6999
8074
  if (options.enableSemanticSearch !== false) {
7000
8075
  try {
7001
8076
  if (isVectorGatewayConfigured()) {
@@ -7012,6 +8087,7 @@ async function createTools(options) {
7012
8087
  if (options.taskTools) {
7013
8088
  tools.complete_task = createCompleteTaskTool(options.taskTools);
7014
8089
  tools.task_failed = createTaskFailedTool(options.taskTools);
8090
+ tools.ask_question_to_user = createAskQuestionToUserTool(options.taskTools);
7015
8091
  }
7016
8092
  return tools;
7017
8093
  }
@@ -7026,11 +8102,11 @@ init_db();
7026
8102
  init_todo();
7027
8103
  import os from "os";
7028
8104
  function getSearchInstructions() {
7029
- const platform3 = process.platform;
8105
+ const platform5 = process.platform;
7030
8106
  const common = `- **Prefer \`read_file\` over shell commands** for reading files - don't use \`cat\`, \`head\`, or \`tail\` when \`read_file\` is available
7031
8107
  - **Avoid unbounded searches** - always scope searches with glob patterns and directory paths to prevent overwhelming output
7032
8108
  - **Search strategically**: Start with specific patterns and directories, then broaden only if needed`;
7033
- if (platform3 === "win32") {
8109
+ if (platform5 === "win32") {
7034
8110
  return `${common}
7035
8111
  - **Find files**: \`dir /s /b *.ts\` or PowerShell: \`Get-ChildItem -Recurse -Filter *.ts\`
7036
8112
  - **Search content**: \`findstr /s /n "pattern" *.ts\` or PowerShell: \`Select-String -Pattern "pattern" -Path *.ts -Recurse\`
@@ -7077,13 +8153,13 @@ async function buildSystemPrompt(options) {
7077
8153
  );
7078
8154
  const hasNoTodos = todos.length === 0;
7079
8155
  const plansContext = formatPlansForContext(plans, allTodosDone || hasNoTodos);
7080
- const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
8156
+ const platform5 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
7081
8157
  const currentDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
7082
8158
  const searchInstructions = getSearchInstructions();
7083
8159
  const systemPrompt = `You are SparkECoder, an expert AI coding assistant. You help developers write, debug, and improve code.
7084
8160
 
7085
8161
  ## Environment
7086
- - **Platform**: ${platform3} (${os.release()})
8162
+ - **Platform**: ${platform5} (${os.release()})
7087
8163
  - **Date**: ${currentDate}
7088
8164
  - **Working Directory**: ${workingDirectory}
7089
8165
 
@@ -7419,9 +8495,10 @@ If you need to give the user a downloadable file (report, image, export, etc.),
7419
8495
  ### Rules
7420
8496
  1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
7421
8497
  2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
7422
- 3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
7423
- 4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
7424
- 5. Do NOT stop without calling one of these two tools.
8498
+ 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.
8499
+ 4. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
8500
+ 5. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
8501
+ 6. Do NOT stop without calling \`complete_task\`, \`task_failed\`, or \`ask_question_to_user\` when blocked.
7425
8502
 
7426
8503
  ### Verification \u2014 BE EXTREMELY THOROUGH
7427
8504
  Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
@@ -7492,6 +8569,7 @@ ${JSON.stringify(outputSchema, null, 2)}
7492
8569
  ### Completion Tools
7493
8570
  - **\`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.
7494
8571
  - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
8572
+ - **\`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.
7495
8573
  `;
7496
8574
  }
7497
8575
  function createSummaryPrompt(conversationHistory) {
@@ -7647,6 +8725,7 @@ function sanitizeModelMessages(messages) {
7647
8725
 
7648
8726
  // src/agent/model-limits.ts
7649
8727
  var MODEL_LIMITS = {
8728
+ "anthropic/claude-opus-4.7": { contextWindow: 2e5, rollingTarget: 15e4 },
7650
8729
  "anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
7651
8730
  "anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
7652
8731
  "anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
@@ -7983,17 +9062,65 @@ function repairToolPairing(messages) {
7983
9062
 
7984
9063
  // src/agent/index.ts
7985
9064
  init_webhook();
9065
+
9066
+ // src/tasks/questions.ts
9067
+ var pendingQuestions = /* @__PURE__ */ new Map();
9068
+ var answeredQuestions = /* @__PURE__ */ new Map();
9069
+ function key(taskId, questionId) {
9070
+ return `${taskId}:${questionId}`;
9071
+ }
9072
+ function waitForTaskQuestionAnswer(question) {
9073
+ const k = key(question.taskId, question.questionId);
9074
+ if (pendingQuestions.has(k)) {
9075
+ return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
9076
+ }
9077
+ return new Promise((resolve12, reject) => {
9078
+ pendingQuestions.set(k, {
9079
+ ...question,
9080
+ createdAt: /* @__PURE__ */ new Date(),
9081
+ resolve: resolve12,
9082
+ reject
9083
+ });
9084
+ });
9085
+ }
9086
+ function answerTaskQuestion(taskId, questionId, answer) {
9087
+ const k = key(taskId, questionId);
9088
+ const pending = pendingQuestions.get(k);
9089
+ if (!pending) return answeredQuestions.has(k) ? "already_answered" : "not_found";
9090
+ pendingQuestions.delete(k);
9091
+ answeredQuestions.set(k, answer);
9092
+ pending.resolve(answer);
9093
+ return "answered";
9094
+ }
9095
+ function rejectTaskQuestions(taskId, reason) {
9096
+ for (const [k, pending] of pendingQuestions) {
9097
+ if (pending.taskId !== taskId) continue;
9098
+ pendingQuestions.delete(k);
9099
+ pending.reject(new Error(reason));
9100
+ }
9101
+ }
9102
+ function getPendingTaskQuestion(taskId, questionId) {
9103
+ const pending = pendingQuestions.get(key(taskId, questionId));
9104
+ if (!pending) return null;
9105
+ const { resolve: _resolve, reject: _reject, ...question } = pending;
9106
+ return question;
9107
+ }
9108
+ function getAnsweredTaskQuestion(taskId, questionId) {
9109
+ return answeredQuestions.get(key(taskId, questionId)) ?? null;
9110
+ }
9111
+
9112
+ // src/agent/index.ts
7986
9113
  var MAX_SSE_FIELD_LENGTH = 8 * 1024;
7987
9114
  var SSE_PREVIEW_LENGTH = 2 * 1024;
7988
9115
  function truncateWriteFileInput(input) {
7989
9116
  const out = { ...input };
7990
- for (const key of ["content", "old_string", "new_string"]) {
7991
- const val = out[key];
9117
+ for (const key2 of ["content", "old_string", "new_string"]) {
9118
+ const val = out[key2];
7992
9119
  if (typeof val === "string" && val.length > MAX_SSE_FIELD_LENGTH) {
7993
- out[key] = `${val.slice(0, SSE_PREVIEW_LENGTH)}
9120
+ out[key2] = `${val.slice(0, SSE_PREVIEW_LENGTH)}
7994
9121
  ... (truncated)`;
7995
- out[`${key}Truncated`] = true;
7996
- out[`${key}Length`] = val.length;
9122
+ out[`${key2}Truncated`] = true;
9123
+ out[`${key2}Length`] = val.length;
7997
9124
  }
7998
9125
  }
7999
9126
  return out;
@@ -8021,10 +9148,14 @@ var Agent = class _Agent {
8021
9148
  */
8022
9149
  async createToolsWithCallbacks(options) {
8023
9150
  const config = getConfig();
9151
+ const sessionConfig = this.session.config || {};
8024
9152
  return createTools({
8025
9153
  sessionId: this.session.id,
8026
9154
  workingDirectory: this.session.workingDirectory,
8027
9155
  skillsDirectories: config.resolvedSkillsDirectories,
9156
+ enableComputerUse: sessionConfig.computerUseEnabled === true,
9157
+ computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
9158
+ computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight,
8028
9159
  onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
8029
9160
  onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
8030
9161
  onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
@@ -8057,10 +9188,14 @@ var Agent = class _Agent {
8057
9188
  keepRecentMessages: config.context?.keepRecentMessages || 10,
8058
9189
  autoSummarize: config.context?.autoSummarize ?? true
8059
9190
  });
9191
+ const sessionConfig = session.config || {};
8060
9192
  const tools = await createTools({
8061
9193
  sessionId: session.id,
8062
9194
  workingDirectory: session.workingDirectory,
8063
- skillsDirectories: config.resolvedSkillsDirectories
9195
+ skillsDirectories: config.resolvedSkillsDirectories,
9196
+ enableComputerUse: sessionConfig.computerUseEnabled === true,
9197
+ computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
9198
+ computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
8064
9199
  });
8065
9200
  return new _Agent(session, context, tools);
8066
9201
  }
@@ -8155,13 +9290,7 @@ ${prompt}` });
8155
9290
  abortSignal: options.abortSignal,
8156
9291
  // Enable extended thinking/reasoning for models that support it
8157
9292
  providerOptions: useAnthropic ? {
8158
- anthropic: {
8159
- toolStreaming: true,
8160
- thinking: {
8161
- type: "enabled",
8162
- budgetTokens: 1e4
8163
- }
8164
- }
9293
+ anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
8165
9294
  } : void 0,
8166
9295
  onStepFinish: async (step) => {
8167
9296
  options.onStepFinish?.(step);
@@ -8208,12 +9337,7 @@ ${prompt}` });
8208
9337
  stopWhen: stepCountIs2(500),
8209
9338
  // Enable extended thinking/reasoning for models that support it
8210
9339
  providerOptions: useAnthropic ? {
8211
- anthropic: {
8212
- thinking: {
8213
- type: "enabled",
8214
- budgetTokens: 1e4
8215
- }
8216
- }
9340
+ anthropic: getAnthropicProviderOptions(this.session.model)
8217
9341
  } : void 0
8218
9342
  });
8219
9343
  const responseMessages = result.response.messages;
@@ -8233,10 +9357,10 @@ ${prompt}` });
8233
9357
  const maxIterations = options.taskConfig.maxIterations ?? 50;
8234
9358
  const webhookUrl = options.taskConfig.webhookUrl;
8235
9359
  const parentTaskId = options.taskConfig.parentTaskId;
8236
- const fireWebhook = (type, data) => {
9360
+ const fireWebhook = (type2, data) => {
8237
9361
  if (!webhookUrl) return;
8238
9362
  sendWebhook(webhookUrl, {
8239
- type,
9363
+ type: type2,
8240
9364
  taskId: this.session.id,
8241
9365
  sessionId: this.session.id,
8242
9366
  ...parentTaskId ? { parentTaskId } : {},
@@ -8279,10 +9403,14 @@ ${prompt}` });
8279
9403
  });
8280
9404
  }
8281
9405
  };
9406
+ const taskSessionConfig = this.session.config || {};
8282
9407
  const taskTools = await createTools({
8283
9408
  sessionId: this.session.id,
8284
9409
  workingDirectory: this.session.workingDirectory,
8285
9410
  skillsDirectories: config.resolvedSkillsDirectories,
9411
+ enableComputerUse: taskSessionConfig.computerUseEnabled === true,
9412
+ computerUseDisplayWidth: taskSessionConfig.computerUseDisplayWidth,
9413
+ computerUseDisplayHeight: taskSessionConfig.computerUseDisplayHeight,
8286
9414
  onBashProgress: bashProgressHandler,
8287
9415
  onWriteFileProgress: (progress) => {
8288
9416
  options.onToolProgress?.({ toolName: "write_file", data: progress });
@@ -8296,7 +9424,38 @@ ${prompt}` });
8296
9424
  },
8297
9425
  taskTools: {
8298
9426
  outputSchema: options.taskConfig.outputSchema,
8299
- onComplete
9427
+ onComplete,
9428
+ onQuestion: async (question) => {
9429
+ const payload = {
9430
+ questionId: question.questionId,
9431
+ question: question.question,
9432
+ context: question.context,
9433
+ choices: question.choices,
9434
+ status: "pending"
9435
+ };
9436
+ const answerPromise = waitForTaskQuestionAnswer({
9437
+ taskId: this.session.id,
9438
+ questionId: question.questionId,
9439
+ question: question.question,
9440
+ context: question.context,
9441
+ choices: question.choices
9442
+ });
9443
+ fireWebhook("task.question", payload);
9444
+ if (emit) {
9445
+ await emit(JSON.stringify({ type: "task-question", data: payload }));
9446
+ }
9447
+ const answer = await answerPromise;
9448
+ const answeredPayload = {
9449
+ questionId: question.questionId,
9450
+ answer: answer.answer,
9451
+ answeredBy: answer.answeredBy
9452
+ };
9453
+ fireWebhook("task.question_answered", answeredPayload);
9454
+ if (emit) {
9455
+ await emit(JSON.stringify({ type: "task-question-answered", data: answeredPayload }));
9456
+ }
9457
+ return answer;
9458
+ }
8300
9459
  }
8301
9460
  });
8302
9461
  const baseSystemPrompt = await buildSystemPrompt({
@@ -8341,10 +9500,7 @@ ${taskAddendum}`;
8341
9500
  stopWhen: stepCountIs2(500),
8342
9501
  abortSignal: options.abortSignal,
8343
9502
  providerOptions: useAnthropic ? {
8344
- anthropic: {
8345
- toolStreaming: true,
8346
- thinking: { type: "enabled", budgetTokens: 1e4 }
8347
- }
9503
+ anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
8348
9504
  } : void 0,
8349
9505
  onStepFinish: async (step) => {
8350
9506
  options.onStepFinish?.(step);
@@ -8565,11 +9721,11 @@ ${taskAddendum}`;
8565
9721
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
8566
9722
  if (!isRemoteConfigured2()) return [];
8567
9723
  const { readFile: readFile12 } = await import("fs/promises");
8568
- const { join: join14, basename: basename6 } = await import("path");
9724
+ const { join: join15, basename: basename6 } = await import("path");
8569
9725
  const urls = [];
8570
9726
  for (const filePath of filePaths) {
8571
9727
  try {
8572
- const fullPath = filePath.startsWith("/") ? filePath : join14(this.session.workingDirectory, filePath);
9728
+ const fullPath = filePath.startsWith("/") ? filePath : join15(this.session.workingDirectory, filePath);
8573
9729
  const fileName = basename6(fullPath);
8574
9730
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
8575
9731
  const mimeMap = {
@@ -8627,11 +9783,11 @@ ${taskAddendum}`;
8627
9783
  wrappedTools[name] = originalTool;
8628
9784
  continue;
8629
9785
  }
8630
- wrappedTools[name] = tool13({
9786
+ wrappedTools[name] = tool14({
8631
9787
  description: originalTool.description || "",
8632
- inputSchema: originalTool.inputSchema || z14.object({}),
9788
+ inputSchema: originalTool.inputSchema || z15.object({}),
8633
9789
  execute: async (input, toolOptions) => {
8634
- const toolCallId = toolOptions.toolCallId || nanoid4();
9790
+ const toolCallId = toolOptions.toolCallId || nanoid6();
8635
9791
  const execution = toolExecutionQueries.create({
8636
9792
  sessionId: this.session.id,
8637
9793
  toolName: name,
@@ -8649,10 +9805,10 @@ ${taskAddendum}`;
8649
9805
  const resolverData = approvalResolvers.get(toolCallId);
8650
9806
  approvalResolvers.delete(toolCallId);
8651
9807
  this.pendingApprovals.delete(toolCallId);
8652
- const exec7 = await execution;
9808
+ const exec8 = await execution;
8653
9809
  if (!approved) {
8654
9810
  const reason = resolverData?.reason || "User rejected the tool execution";
8655
- await toolExecutionQueries.reject(exec7.id);
9811
+ await toolExecutionQueries.reject(exec8.id);
8656
9812
  await sessionQueries.updateStatus(this.session.id, "active");
8657
9813
  return {
8658
9814
  status: "rejected",
@@ -8662,14 +9818,14 @@ ${taskAddendum}`;
8662
9818
  message: `Tool "${name}" was rejected by the user. Reason: ${reason}`
8663
9819
  };
8664
9820
  }
8665
- await toolExecutionQueries.approve(exec7.id);
9821
+ await toolExecutionQueries.approve(exec8.id);
8666
9822
  await sessionQueries.updateStatus(this.session.id, "active");
8667
9823
  try {
8668
9824
  const result = await originalTool.execute(input, toolOptions);
8669
- await toolExecutionQueries.complete(exec7.id, result);
9825
+ await toolExecutionQueries.complete(exec8.id, result);
8670
9826
  return result;
8671
9827
  } catch (error) {
8672
- await toolExecutionQueries.complete(exec7.id, null, error.message);
9828
+ await toolExecutionQueries.complete(exec8.id, null, error.message);
8673
9829
  throw error;
8674
9830
  }
8675
9831
  }
@@ -8770,18 +9926,20 @@ function cleanupPendingInputs() {
8770
9926
  }
8771
9927
  }
8772
9928
  }
8773
- var createSessionSchema = z15.object({
8774
- name: z15.string().optional(),
8775
- workingDirectory: z15.string().optional(),
8776
- model: z15.string().optional(),
8777
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
9929
+ var createSessionSchema = z16.object({
9930
+ name: z16.string().optional(),
9931
+ workingDirectory: z16.string().optional(),
9932
+ model: z16.string().optional(),
9933
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional(),
9934
+ // Optional full session-config passthrough (computerUseEnabled, etc.)
9935
+ config: z16.record(z16.string(), z16.unknown()).optional()
8778
9936
  });
8779
- var paginationQuerySchema = z15.object({
8780
- limit: z15.string().optional(),
8781
- offset: z15.string().optional()
9937
+ var paginationQuerySchema = z16.object({
9938
+ limit: z16.string().optional(),
9939
+ offset: z16.string().optional()
8782
9940
  });
8783
- var messagesQuerySchema = z15.object({
8784
- limit: z15.string().optional()
9941
+ var messagesQuerySchema = z16.object({
9942
+ limit: z16.string().optional()
8785
9943
  });
8786
9944
  sessions.get(
8787
9945
  "/",
@@ -8819,11 +9977,20 @@ sessions.post(
8819
9977
  async (c) => {
8820
9978
  const body = c.req.valid("json");
8821
9979
  const config = getConfig();
9980
+ const cuDefault = process.env.SPARKECODER_COMPUTER_USE === "1";
9981
+ const baseConfig = body.config || {};
9982
+ const mergedConfig = {
9983
+ ...baseConfig,
9984
+ ...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
9985
+ // Turn on computer use by default if the server was launched with --enable-computer-use,
9986
+ // unless the client explicitly provided a value.
9987
+ ...cuDefault && baseConfig.computerUseEnabled === void 0 ? { computerUseEnabled: true } : {}
9988
+ };
8822
9989
  const agent = await Agent.create({
8823
9990
  name: body.name,
8824
9991
  workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
8825
9992
  model: body.model || config.defaultModel,
8826
- sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
9993
+ sessionConfig: Object.keys(mergedConfig).length > 0 ? mergedConfig : void 0
8827
9994
  });
8828
9995
  const session = agent.getSession();
8829
9996
  return c.json({
@@ -8920,10 +10087,10 @@ sessions.get("/:id/tools", async (c) => {
8920
10087
  count: executions.length
8921
10088
  });
8922
10089
  });
8923
- var updateSessionSchema = z15.object({
8924
- model: z15.string().optional(),
8925
- name: z15.string().optional(),
8926
- toolApprovals: z15.record(z15.string(), z15.boolean()).optional()
10090
+ var updateSessionSchema = z16.object({
10091
+ model: z16.string().optional(),
10092
+ name: z16.string().optional(),
10093
+ toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
8927
10094
  });
8928
10095
  sessions.patch(
8929
10096
  "/:id",
@@ -8993,8 +10160,8 @@ sessions.post("/:id/clear", async (c) => {
8993
10160
  await agent.clearContext();
8994
10161
  return c.json({ success: true, sessionId: id });
8995
10162
  });
8996
- var pendingInputSchema = z15.object({
8997
- text: z15.string()
10163
+ var pendingInputSchema = z16.object({
10164
+ text: z16.string()
8998
10165
  });
8999
10166
  sessions.post(
9000
10167
  "/:id/pending-input",
@@ -9025,13 +10192,13 @@ sessions.get("/:id/pending-input", async (c) => {
9025
10192
  createdAt: pending.createdAt.toISOString()
9026
10193
  });
9027
10194
  });
9028
- var devtoolsContextSchema = z15.object({
9029
- url: z15.string(),
9030
- path: z15.string(),
9031
- pageName: z15.string().optional(),
9032
- screenWidth: z15.number().optional(),
9033
- screenHeight: z15.number().optional(),
9034
- devicePixelRatio: z15.number().optional()
10195
+ var devtoolsContextSchema = z16.object({
10196
+ url: z16.string(),
10197
+ path: z16.string(),
10198
+ pageName: z16.string().optional(),
10199
+ screenWidth: z16.number().optional(),
10200
+ screenHeight: z16.number().optional(),
10201
+ devicePixelRatio: z16.number().optional()
9035
10202
  });
9036
10203
  sessions.post(
9037
10204
  "/:id/devtools-context",
@@ -9217,12 +10384,12 @@ sessions.get("/:id/diff/:filePath", async (c) => {
9217
10384
  });
9218
10385
  function getAttachmentsDir(sessionId) {
9219
10386
  const appDataDir = getAppDataDirectory();
9220
- return join9(appDataDir, "attachments", sessionId);
10387
+ return join10(appDataDir, "attachments", sessionId);
9221
10388
  }
9222
10389
  function ensureAttachmentsDir(sessionId) {
9223
10390
  const dir = getAttachmentsDir(sessionId);
9224
- if (!existsSync15(dir)) {
9225
- mkdirSync5(dir, { recursive: true });
10391
+ if (!existsSync16(dir)) {
10392
+ mkdirSync6(dir, { recursive: true });
9226
10393
  }
9227
10394
  return dir;
9228
10395
  }
@@ -9233,12 +10400,12 @@ sessions.get("/:id/attachments", async (c) => {
9233
10400
  return c.json({ error: "Session not found" }, 404);
9234
10401
  }
9235
10402
  const dir = getAttachmentsDir(sessionId);
9236
- if (!existsSync15(dir)) {
10403
+ if (!existsSync16(dir)) {
9237
10404
  return c.json({ sessionId, attachments: [], count: 0 });
9238
10405
  }
9239
10406
  const files = readdirSync2(dir);
9240
10407
  const attachments = files.map((filename) => {
9241
- const filePath = join9(dir, filename);
10408
+ const filePath = join10(dir, filename);
9242
10409
  const stats = statSync2(filePath);
9243
10410
  return {
9244
10411
  id: filename.split("_")[0],
@@ -9270,10 +10437,10 @@ sessions.post("/:id/attachments", async (c) => {
9270
10437
  return c.json({ error: "No file provided" }, 400);
9271
10438
  }
9272
10439
  const dir = ensureAttachmentsDir(sessionId);
9273
- const id = nanoid5(10);
10440
+ const id = nanoid7(10);
9274
10441
  const ext = extname8(file.name) || "";
9275
10442
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
9276
- const filePath = join9(dir, safeFilename);
10443
+ const filePath = join10(dir, safeFilename);
9277
10444
  const arrayBuffer = await file.arrayBuffer();
9278
10445
  writeFileSync3(filePath, Buffer.from(arrayBuffer));
9279
10446
  return c.json({
@@ -9296,10 +10463,10 @@ sessions.post("/:id/attachments", async (c) => {
9296
10463
  return c.json({ error: "Missing filename or data" }, 400);
9297
10464
  }
9298
10465
  const dir = ensureAttachmentsDir(sessionId);
9299
- const id = nanoid5(10);
10466
+ const id = nanoid7(10);
9300
10467
  const ext = extname8(body.filename) || "";
9301
10468
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
9302
- const filePath = join9(dir, safeFilename);
10469
+ const filePath = join10(dir, safeFilename);
9303
10470
  let base64Data = body.data;
9304
10471
  if (base64Data.includes(",")) {
9305
10472
  base64Data = base64Data.split(",")[1];
@@ -9328,7 +10495,7 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
9328
10495
  return c.json({ error: "Session not found" }, 404);
9329
10496
  }
9330
10497
  const dir = getAttachmentsDir(sessionId);
9331
- if (!existsSync15(dir)) {
10498
+ if (!existsSync16(dir)) {
9332
10499
  return c.json({ error: "Attachment not found" }, 404);
9333
10500
  }
9334
10501
  const files = readdirSync2(dir);
@@ -9336,14 +10503,14 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
9336
10503
  if (!file) {
9337
10504
  return c.json({ error: "Attachment not found" }, 404);
9338
10505
  }
9339
- const filePath = join9(dir, file);
9340
- unlinkSync2(filePath);
10506
+ const filePath = join10(dir, file);
10507
+ unlinkSync3(filePath);
9341
10508
  return c.json({ success: true, id: attachmentId });
9342
10509
  });
9343
- var filesQuerySchema = z15.object({
9344
- query: z15.string().optional(),
10510
+ var filesQuerySchema = z16.object({
10511
+ query: z16.string().optional(),
9345
10512
  // Filter query (e.g., "src/com" to match "src/components")
9346
- limit: z15.string().optional()
10513
+ limit: z16.string().optional()
9347
10514
  // Max results (default 50)
9348
10515
  });
9349
10516
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -9419,7 +10586,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
9419
10586
  const entries = await readdir6(currentDir, { withFileTypes: true });
9420
10587
  for (const entry of entries) {
9421
10588
  if (results.length >= limit * 2) break;
9422
- const fullPath = join9(currentDir, entry.name);
10589
+ const fullPath = join10(currentDir, entry.name);
9423
10590
  const relativePath = relative9(baseDir, fullPath);
9424
10591
  if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
9425
10592
  continue;
@@ -9467,7 +10634,7 @@ sessions.get(
9467
10634
  return c.json({ error: "Session not found" }, 404);
9468
10635
  }
9469
10636
  const workingDirectory = session.workingDirectory;
9470
- if (!existsSync15(workingDirectory)) {
10637
+ if (!existsSync16(workingDirectory)) {
9471
10638
  return c.json({
9472
10639
  sessionId,
9473
10640
  workingDirectory,
@@ -9577,9 +10744,9 @@ sessions.get("/:id/browser-recording", async (c) => {
9577
10744
  init_db();
9578
10745
  import { Hono as Hono2 } from "hono";
9579
10746
  import { zValidator as zValidator2 } from "@hono/zod-validator";
9580
- import { z as z16 } from "zod";
9581
- import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
9582
- import { join as join10 } from "path";
10747
+ import { z as z17 } from "zod";
10748
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
10749
+ import { join as join11 } from "path";
9583
10750
  init_config();
9584
10751
 
9585
10752
  // src/server/resumable-stream.ts
@@ -9588,9 +10755,9 @@ var store = /* @__PURE__ */ new Map();
9588
10755
  var channels = /* @__PURE__ */ new Map();
9589
10756
  var cleanupInterval = setInterval(() => {
9590
10757
  const now = Date.now();
9591
- for (const [key, data] of store.entries()) {
10758
+ for (const [key2, data] of store.entries()) {
9592
10759
  if (data.expiresAt && data.expiresAt < now) {
9593
- store.delete(key);
10760
+ store.delete(key2);
9594
10761
  }
9595
10762
  }
9596
10763
  }, 6e4);
@@ -9614,27 +10781,27 @@ var publisher = {
9614
10781
  }
9615
10782
  }
9616
10783
  },
9617
- set: async (key, value, options) => {
10784
+ set: async (key2, value, options) => {
9618
10785
  const expiresAt = options?.EX ? Date.now() + options.EX * 1e3 : void 0;
9619
- store.set(key, { value, expiresAt });
10786
+ store.set(key2, { value, expiresAt });
9620
10787
  if (options?.EX) {
9621
- setTimeout(() => store.delete(key), options.EX * 1e3);
10788
+ setTimeout(() => store.delete(key2), options.EX * 1e3);
9622
10789
  }
9623
10790
  },
9624
- get: async (key) => {
9625
- const data = store.get(key);
10791
+ get: async (key2) => {
10792
+ const data = store.get(key2);
9626
10793
  if (!data) return null;
9627
10794
  if (data.expiresAt && data.expiresAt < Date.now()) {
9628
- store.delete(key);
10795
+ store.delete(key2);
9629
10796
  return null;
9630
10797
  }
9631
10798
  return data.value;
9632
10799
  },
9633
- incr: async (key) => {
9634
- const data = store.get(key);
10800
+ incr: async (key2) => {
10801
+ const data = store.get(key2);
9635
10802
  const current = data ? parseInt(data.value, 10) : 0;
9636
10803
  const next = (isNaN(current) ? 0 : current) + 1;
9637
- store.set(key, { value: String(next), expiresAt: data?.expiresAt });
10804
+ store.set(key2, { value: String(next), expiresAt: data?.expiresAt });
9638
10805
  return next;
9639
10806
  }
9640
10807
  };
@@ -9666,7 +10833,7 @@ var streamContext = createResumableStreamContext({
9666
10833
  });
9667
10834
 
9668
10835
  // src/server/routes/agents.ts
9669
- import { nanoid as nanoid6 } from "nanoid";
10836
+ import { nanoid as nanoid8 } from "nanoid";
9670
10837
  init_stream_proxy();
9671
10838
  init_recorder();
9672
10839
  init_remote();
@@ -9757,40 +10924,40 @@ function enrichPromptWithDevtoolsContext(sessionId, prompt) {
9757
10924
  ${prompt}`;
9758
10925
  }
9759
10926
  var agents = new Hono2();
9760
- var attachmentSchema = z16.object({
9761
- type: z16.enum(["image", "file"]),
9762
- data: z16.string(),
10927
+ var attachmentSchema = z17.object({
10928
+ type: z17.enum(["image", "file"]),
10929
+ data: z17.string(),
9763
10930
  // base64 data URL or raw base64
9764
- mediaType: z16.string().optional(),
9765
- filename: z16.string().optional()
10931
+ mediaType: z17.string().optional(),
10932
+ filename: z17.string().optional()
9766
10933
  });
9767
- var runPromptSchema = z16.object({
9768
- prompt: z16.string(),
10934
+ var runPromptSchema = z17.object({
10935
+ prompt: z17.string(),
9769
10936
  // Can be empty if attachments are provided
9770
- attachments: z16.array(attachmentSchema).optional()
10937
+ attachments: z17.array(attachmentSchema).optional()
9771
10938
  }).refine(
9772
10939
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
9773
10940
  { message: "Either prompt or attachments must be provided" }
9774
10941
  );
9775
- var quickStartSchema = z16.object({
9776
- prompt: z16.string().min(1),
9777
- name: z16.string().optional(),
9778
- workingDirectory: z16.string().optional(),
9779
- model: z16.string().optional(),
9780
- toolApprovals: z16.record(z16.string(), z16.boolean()).optional()
10942
+ var quickStartSchema = z17.object({
10943
+ prompt: z17.string().min(1),
10944
+ name: z17.string().optional(),
10945
+ workingDirectory: z17.string().optional(),
10946
+ model: z17.string().optional(),
10947
+ toolApprovals: z17.record(z17.string(), z17.boolean()).optional()
9781
10948
  });
9782
- var rejectSchema = z16.object({
9783
- reason: z16.string().optional()
10949
+ var rejectSchema = z17.object({
10950
+ reason: z17.string().optional()
9784
10951
  }).optional();
9785
10952
  var streamAbortControllers = /* @__PURE__ */ new Map();
9786
10953
  function getAttachmentsDirectory(sessionId) {
9787
10954
  const appDataDir = getAppDataDirectory();
9788
- return join10(appDataDir, "attachments", sessionId);
10955
+ return join11(appDataDir, "attachments", sessionId);
9789
10956
  }
9790
10957
  async function saveAttachmentToDisk(sessionId, attachment, index) {
9791
10958
  const attachmentsDir = getAttachmentsDirectory(sessionId);
9792
- if (!existsSync16(attachmentsDir)) {
9793
- mkdirSync6(attachmentsDir, { recursive: true });
10959
+ if (!existsSync17(attachmentsDir)) {
10960
+ mkdirSync7(attachmentsDir, { recursive: true });
9794
10961
  }
9795
10962
  let filename = attachment.filename;
9796
10963
  if (!filename) {
@@ -9808,7 +10975,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
9808
10975
  attachment.mediaType = resized.mediaType;
9809
10976
  attachment.data = buffer.toString("base64");
9810
10977
  }
9811
- const filePath = join10(attachmentsDir, filename);
10978
+ const filePath = join11(attachmentsDir, filename);
9812
10979
  writeFileSync4(filePath, buffer);
9813
10980
  return filePath;
9814
10981
  }
@@ -9819,9 +10986,9 @@ function stripDataUrlPrefix2(data) {
9819
10986
  }
9820
10987
  return data;
9821
10988
  }
9822
- function getExtensionFromMediaType(mediaType, type) {
10989
+ function getExtensionFromMediaType(mediaType, type2) {
9823
10990
  if (!mediaType) {
9824
- return type === "image" ? ".png" : ".bin";
10991
+ return type2 === "image" ? ".png" : ".bin";
9825
10992
  }
9826
10993
  const mimeToExt = {
9827
10994
  "image/png": ".png",
@@ -10225,7 +11392,7 @@ ${prompt}` });
10225
11392
  userMessageContent = prompt;
10226
11393
  }
10227
11394
  await messageQueries.create(id, { role: "user", content: userMessageContent });
10228
- const streamId = `stream_${id}_${nanoid6(10)}`;
11395
+ const streamId = `stream_${id}_${nanoid8(10)}`;
10229
11396
  console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
10230
11397
  await activeStreamQueries.create(id, streamId);
10231
11398
  const stream = await streamContext.resumableStream(
@@ -10430,7 +11597,7 @@ agents.post(
10430
11597
  });
10431
11598
  const session = agent.getSession();
10432
11599
  const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
10433
- const streamId = `stream_${session.id}_${nanoid6(10)}`;
11600
+ const streamId = `stream_${session.id}_${nanoid8(10)}`;
10434
11601
  await createCheckpoint(session.id, session.workingDirectory, 0);
10435
11602
  await activeStreamQueries.create(session.id, streamId);
10436
11603
  const createQuickStreamProducer = () => {
@@ -10697,23 +11864,23 @@ agents.post(
10697
11864
  });
10698
11865
  }
10699
11866
  );
10700
- var browserInputSchema = z16.object({
10701
- type: z16.enum(["input_mouse", "input_keyboard", "input_touch"]),
10702
- eventType: z16.string(),
10703
- x: z16.number().optional(),
10704
- y: z16.number().optional(),
10705
- button: z16.string().optional(),
10706
- clickCount: z16.number().optional(),
10707
- deltaX: z16.number().optional(),
10708
- deltaY: z16.number().optional(),
10709
- key: z16.string().optional(),
10710
- code: z16.string().optional(),
10711
- text: z16.string().optional(),
10712
- modifiers: z16.number().optional(),
10713
- touchPoints: z16.array(z16.object({
10714
- x: z16.number(),
10715
- y: z16.number(),
10716
- id: z16.number().optional()
11867
+ var browserInputSchema = z17.object({
11868
+ type: z17.enum(["input_mouse", "input_keyboard", "input_touch"]),
11869
+ eventType: z17.string(),
11870
+ x: z17.number().optional(),
11871
+ y: z17.number().optional(),
11872
+ button: z17.string().optional(),
11873
+ clickCount: z17.number().optional(),
11874
+ deltaX: z17.number().optional(),
11875
+ deltaY: z17.number().optional(),
11876
+ key: z17.string().optional(),
11877
+ code: z17.string().optional(),
11878
+ text: z17.string().optional(),
11879
+ modifiers: z17.number().optional(),
11880
+ touchPoints: z17.array(z17.object({
11881
+ x: z17.number(),
11882
+ y: z17.number(),
11883
+ id: z17.number().optional()
10717
11884
  })).optional()
10718
11885
  });
10719
11886
  agents.post(
@@ -10746,29 +11913,30 @@ agents.get("/:id/browser-stream", async (c) => {
10746
11913
 
10747
11914
  // src/server/routes/health.ts
10748
11915
  init_config();
11916
+ init_heartbeat();
10749
11917
  import { Hono as Hono3 } from "hono";
10750
11918
  import { zValidator as zValidator3 } from "@hono/zod-validator";
10751
- import { z as z17 } from "zod";
10752
- import { readFileSync as readFileSync7 } from "fs";
11919
+ import { z as z18 } from "zod";
11920
+ import { readFileSync as readFileSync10 } from "fs";
10753
11921
  import { fileURLToPath as fileURLToPath3 } from "url";
10754
- import { dirname as dirname6, join as join11 } from "path";
11922
+ import { dirname as dirname6, join as join12 } from "path";
10755
11923
  var __filename = fileURLToPath3(import.meta.url);
10756
11924
  var __dirname = dirname6(__filename);
10757
11925
  var possiblePaths = [
10758
- join11(__dirname, "../package.json"),
11926
+ join12(__dirname, "../package.json"),
10759
11927
  // From dist/server -> dist/../package.json
10760
- join11(__dirname, "../../package.json"),
11928
+ join12(__dirname, "../../package.json"),
10761
11929
  // From dist/server (if nested differently)
10762
- join11(__dirname, "../../../package.json"),
11930
+ join12(__dirname, "../../../package.json"),
10763
11931
  // From src/server/routes (development)
10764
- join11(process.cwd(), "package.json")
11932
+ join12(process.cwd(), "package.json")
10765
11933
  // From current working directory
10766
11934
  ];
10767
11935
  var currentVersion = "0.0.0";
10768
11936
  var packageName = "sparkecoder";
10769
11937
  for (const packageJsonPath of possiblePaths) {
10770
11938
  try {
10771
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
11939
+ const packageJson = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
10772
11940
  if (packageJson.name === "sparkecoder") {
10773
11941
  currentVersion = packageJson.version || "0.0.0";
10774
11942
  packageName = packageJson.name || "sparkecoder";
@@ -10782,12 +11950,20 @@ health.get("/", async (c) => {
10782
11950
  const config = getConfig();
10783
11951
  const apiKeyStatus = getApiKeyStatus();
10784
11952
  const gatewayKey = apiKeyStatus.find((s) => s.provider === "ai-gateway");
10785
- const hasApiKey = gatewayKey?.configured ?? false;
11953
+ const remoteInference = isRemoteInferenceConfigured();
11954
+ const hasApiKey = remoteInference || (gatewayKey?.configured ?? false);
11955
+ let hwid;
11956
+ try {
11957
+ hwid = getHardwareIdCached();
11958
+ } catch {
11959
+ }
10786
11960
  return c.json({
10787
11961
  status: "ok",
10788
11962
  version: currentVersion,
10789
11963
  uptime: process.uptime(),
10790
11964
  apiKeyConfigured: hasApiKey,
11965
+ inferenceMode: remoteInference ? "remote" : "local",
11966
+ hwid,
10791
11967
  config: {
10792
11968
  workingDirectory: config.resolvedWorkingDirectory,
10793
11969
  defaultModel: config.defaultModel,
@@ -10858,9 +12034,9 @@ health.get("/api-keys", async (c) => {
10858
12034
  supportedProviders: SUPPORTED_PROVIDERS
10859
12035
  });
10860
12036
  });
10861
- var setApiKeySchema = z17.object({
10862
- provider: z17.string(),
10863
- apiKey: z17.string().min(1)
12037
+ var setApiKeySchema = z18.object({
12038
+ provider: z18.string(),
12039
+ apiKey: z18.string().min(1)
10864
12040
  });
10865
12041
  health.post(
10866
12042
  "/api-keys",
@@ -10899,13 +12075,13 @@ health.delete("/api-keys/:provider", async (c) => {
10899
12075
  // src/server/routes/terminals.ts
10900
12076
  import { Hono as Hono4 } from "hono";
10901
12077
  import { zValidator as zValidator4 } from "@hono/zod-validator";
10902
- import { z as z18 } from "zod";
12078
+ import { z as z19 } from "zod";
10903
12079
  init_db();
10904
12080
  var terminals = new Hono4();
10905
- var spawnSchema = z18.object({
10906
- command: z18.string(),
10907
- cwd: z18.string().optional(),
10908
- name: z18.string().optional()
12081
+ var spawnSchema = z19.object({
12082
+ command: z19.string(),
12083
+ cwd: z19.string().optional(),
12084
+ name: z19.string().optional()
10909
12085
  });
10910
12086
  terminals.post(
10911
12087
  "/:sessionId/terminals",
@@ -10986,8 +12162,8 @@ terminals.get("/:sessionId/terminals/:terminalId", async (c) => {
10986
12162
  // We don't track exit codes in tmux mode
10987
12163
  });
10988
12164
  });
10989
- var logsQuerySchema = z18.object({
10990
- tail: z18.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
12165
+ var logsQuerySchema = z19.object({
12166
+ tail: z19.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
10991
12167
  });
10992
12168
  terminals.get(
10993
12169
  "/:sessionId/terminals/:terminalId/logs",
@@ -11011,8 +12187,8 @@ terminals.get(
11011
12187
  });
11012
12188
  }
11013
12189
  );
11014
- var killSchema = z18.object({
11015
- signal: z18.enum(["SIGTERM", "SIGKILL"]).optional()
12190
+ var killSchema = z19.object({
12191
+ signal: z19.enum(["SIGTERM", "SIGKILL"]).optional()
11016
12192
  });
11017
12193
  terminals.post(
11018
12194
  "/:sessionId/terminals/:terminalId/kill",
@@ -11026,8 +12202,8 @@ terminals.post(
11026
12202
  return c.json({ success: true, message: "Terminal killed" });
11027
12203
  }
11028
12204
  );
11029
- var writeSchema = z18.object({
11030
- input: z18.string()
12205
+ var writeSchema = z19.object({
12206
+ input: z19.string()
11031
12207
  });
11032
12208
  terminals.post(
11033
12209
  "/:sessionId/terminals/:terminalId/write",
@@ -11212,20 +12388,20 @@ data: ${JSON.stringify({ status: "stopped" })}
11212
12388
  init_db();
11213
12389
  import { Hono as Hono5 } from "hono";
11214
12390
  import { zValidator as zValidator5 } from "@hono/zod-validator";
11215
- import { z as z19 } from "zod";
11216
- import { nanoid as nanoid7 } from "nanoid";
12391
+ import { z as z20 } from "zod";
12392
+ import { nanoid as nanoid9 } from "nanoid";
11217
12393
  init_config();
11218
12394
  var tasks = new Hono5();
11219
12395
  var taskAbortControllers = /* @__PURE__ */ new Map();
11220
- var createTaskSchema = z19.object({
11221
- prompt: z19.string().min(1),
11222
- outputSchema: z19.record(z19.string(), z19.unknown()),
11223
- webhookUrl: z19.string().url().optional(),
11224
- model: z19.string().optional(),
11225
- workingDirectory: z19.string().optional(),
11226
- name: z19.string().optional(),
11227
- maxIterations: z19.number().int().min(1).max(500).optional(),
11228
- parentTaskId: z19.string().optional()
12396
+ var createTaskSchema = z20.object({
12397
+ prompt: z20.string().min(1),
12398
+ outputSchema: z20.record(z20.string(), z20.unknown()),
12399
+ webhookUrl: z20.string().url().optional(),
12400
+ model: z20.string().optional(),
12401
+ workingDirectory: z20.string().optional(),
12402
+ name: z20.string().optional(),
12403
+ maxIterations: z20.number().int().min(1).max(500).optional(),
12404
+ parentTaskId: z20.string().optional()
11229
12405
  });
11230
12406
  tasks.post(
11231
12407
  "/",
@@ -11287,7 +12463,7 @@ tasks.post(
11287
12463
  const taskId = agent.sessionId;
11288
12464
  const abortController = new AbortController();
11289
12465
  taskAbortControllers.set(taskId, abortController);
11290
- const streamId = `stream_${taskId}_${nanoid7(10)}`;
12466
+ const streamId = `stream_${taskId}_${nanoid9(10)}`;
11291
12467
  await activeStreamQueries.create(taskId, streamId);
11292
12468
  const taskStreamProducer = () => {
11293
12469
  const { readable, writable } = new TransformStream();
@@ -11414,6 +12590,7 @@ tasks.post("/:id/cancel", async (c) => {
11414
12590
  abortController.abort();
11415
12591
  taskAbortControllers.delete(id);
11416
12592
  }
12593
+ rejectTaskQuestions(id, "Task cancelled by user");
11417
12594
  const cancelledTask = {
11418
12595
  ...task,
11419
12596
  status: "failed",
@@ -11435,19 +12612,632 @@ tasks.post("/:id/cancel", async (c) => {
11435
12612
  }
11436
12613
  return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
11437
12614
  });
12615
+ var answerQuestionSchema = z20.object({
12616
+ answer: z20.string().min(1),
12617
+ answeredBy: z20.enum(["orchestrator", "user", "system"]).optional()
12618
+ });
12619
+ tasks.post(
12620
+ "/:id/questions/:questionId/answer",
12621
+ zValidator5("json", answerQuestionSchema),
12622
+ async (c) => {
12623
+ const id = c.req.param("id");
12624
+ const questionId = c.req.param("questionId");
12625
+ const body = c.req.valid("json");
12626
+ const session = await sessionQueries.getById(id);
12627
+ if (!session) {
12628
+ return c.json({ error: "Task not found" }, 404);
12629
+ }
12630
+ const task = session.config?.task;
12631
+ if (!task?.enabled) {
12632
+ return c.json({ error: "Session is not a task" }, 400);
12633
+ }
12634
+ const pending = getPendingTaskQuestion(id, questionId);
12635
+ if (!pending) {
12636
+ if (getAnsweredTaskQuestion(id, questionId)) {
12637
+ return c.json({
12638
+ taskId: id,
12639
+ questionId,
12640
+ status: "answered",
12641
+ alreadyAnswered: true
12642
+ });
12643
+ }
12644
+ return c.json({ error: "Question is not pending" }, 404);
12645
+ }
12646
+ const answerStatus = answerTaskQuestion(id, questionId, {
12647
+ answer: body.answer,
12648
+ answeredBy: body.answeredBy
12649
+ });
12650
+ if (answerStatus === "not_found") {
12651
+ return c.json({ error: "Question is not pending" }, 404);
12652
+ }
12653
+ return c.json({
12654
+ taskId: id,
12655
+ questionId,
12656
+ status: "answered",
12657
+ alreadyAnswered: answerStatus === "already_answered"
12658
+ });
12659
+ }
12660
+ );
11438
12661
  var tasks_default = tasks;
11439
12662
 
12663
+ // src/server/routes/system.ts
12664
+ init_system_metrics();
12665
+ import { Hono as Hono6 } from "hono";
12666
+ import { streamSSE } from "hono/streaming";
12667
+ var system = new Hono6();
12668
+ system.get("/metrics", (c) => {
12669
+ return c.json(readSystemMetrics());
12670
+ });
12671
+ system.get("/metrics/stream", (c) => {
12672
+ return streamSSE(c, async (stream) => {
12673
+ const intervalMs = Number(c.req.query("intervalMs") ?? 2e3);
12674
+ const safeMs = Number.isFinite(intervalMs) ? Math.max(500, Math.min(6e4, intervalMs)) : 2e3;
12675
+ let id = 0;
12676
+ let aborted = false;
12677
+ c.req.raw.signal.addEventListener("abort", () => {
12678
+ aborted = true;
12679
+ });
12680
+ while (!aborted) {
12681
+ try {
12682
+ const snap = readSystemMetrics();
12683
+ await stream.writeSSE({
12684
+ id: String(id++),
12685
+ event: "metrics",
12686
+ data: JSON.stringify(snap)
12687
+ });
12688
+ } catch (e) {
12689
+ await stream.writeSSE({
12690
+ id: String(id++),
12691
+ event: "error",
12692
+ data: JSON.stringify({ error: e.message })
12693
+ });
12694
+ }
12695
+ await stream.sleep(safeMs);
12696
+ }
12697
+ });
12698
+ });
12699
+
12700
+ // src/personal-agent/hwid-middleware.ts
12701
+ init_heartbeat();
12702
+ var SKIP_PATHS = /* @__PURE__ */ new Set(["/health", "/health/", "/health/ready", "/health/version"]);
12703
+ function personalAgentConfigured() {
12704
+ 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);
12705
+ }
12706
+ function hwidMiddleware() {
12707
+ const enabled = personalAgentConfigured();
12708
+ let warnedMissing = false;
12709
+ return async (c, next) => {
12710
+ if (!enabled) return next();
12711
+ const path = c.req.path;
12712
+ if (SKIP_PATHS.has(path) || path.startsWith("/health/api-keys")) {
12713
+ return next();
12714
+ }
12715
+ const got = c.req.header("x-device-hwid");
12716
+ if (!got) {
12717
+ if (!warnedMissing) {
12718
+ warnedMissing = true;
12719
+ console.warn(
12720
+ `[personal-agent] request to ${path} arrived without X-Device-Hwid; allowing (backwards compat)`
12721
+ );
12722
+ }
12723
+ return next();
12724
+ }
12725
+ const expected = getHardwareIdCached();
12726
+ if (got !== expected) {
12727
+ console.warn(
12728
+ `[personal-agent] HWID mismatch on ${path}: got=${got.slice(0, 12)}\u2026, expected=${expected.slice(0, 12)}\u2026`
12729
+ );
12730
+ return c.json(
12731
+ {
12732
+ error: "hwid mismatch",
12733
+ 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.",
12734
+ expected: expected.slice(0, 12) + "\u2026",
12735
+ got: got.slice(0, 12) + "\u2026"
12736
+ },
12737
+ 409
12738
+ );
12739
+ }
12740
+ return next();
12741
+ };
12742
+ }
12743
+
12744
+ // src/personal-agent/signature-verify.ts
12745
+ import { createHash as createHash3, createPublicKey, verify as cryptoVerify } from "crypto";
12746
+ import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
12747
+ var REPLAY_WINDOW_SECONDS = 5 * 60;
12748
+ var _cachedKey = null;
12749
+ var _cachedFromInput = null;
12750
+ function loadPublicKey(input) {
12751
+ if (_cachedFromInput === input && _cachedKey) return _cachedKey;
12752
+ let pem = input;
12753
+ if (!input.includes("BEGIN") && existsSync18(input)) {
12754
+ pem = readFileSync11(input, "utf8");
12755
+ }
12756
+ const key2 = createPublicKey({ key: pem, format: "pem" });
12757
+ if (key2.asymmetricKeyType !== "ed25519") {
12758
+ throw new Error(
12759
+ `expected an ed25519 public key, got ${key2.asymmetricKeyType}. Generate with personal-agents/scripts/generate-signing-keys.mjs.`
12760
+ );
12761
+ }
12762
+ _cachedKey = key2;
12763
+ _cachedFromInput = input;
12764
+ return key2;
12765
+ }
12766
+ function bodyHashB64(body) {
12767
+ const hash = createHash3("sha256");
12768
+ if (body == null || body === "") {
12769
+ } else if (typeof body === "string") {
12770
+ hash.update(body, "utf8");
12771
+ } else if (Buffer.isBuffer(body)) {
12772
+ hash.update(body);
12773
+ } else {
12774
+ hash.update(Buffer.from(body));
12775
+ }
12776
+ return hash.digest("base64");
12777
+ }
12778
+ function canonicalSigningString(args) {
12779
+ return [
12780
+ args.method.toUpperCase(),
12781
+ args.path,
12782
+ String(args.timestamp),
12783
+ args.bodyHashB64
12784
+ ].join("\n");
12785
+ }
12786
+ function fromBase64Url(s) {
12787
+ const padded = s.padEnd(s.length + (4 - s.length % 4) % 4, "=");
12788
+ const std = padded.replace(/-/g, "+").replace(/_/g, "/");
12789
+ return Buffer.from(std, "base64");
12790
+ }
12791
+ function verifyEmbedToken(args) {
12792
+ const dot = args.token.indexOf(".");
12793
+ if (dot < 1 || dot >= args.token.length - 1) {
12794
+ return { ok: false, reason: "malformed" };
12795
+ }
12796
+ const payloadB64 = args.token.slice(0, dot);
12797
+ const sigB64 = args.token.slice(dot + 1);
12798
+ let sigBuf;
12799
+ try {
12800
+ sigBuf = fromBase64Url(sigB64);
12801
+ } catch {
12802
+ return { ok: false, reason: "bad-encoding" };
12803
+ }
12804
+ const sigOk = cryptoVerify(
12805
+ null,
12806
+ Buffer.from(payloadB64, "utf8"),
12807
+ args.publicKey,
12808
+ sigBuf
12809
+ );
12810
+ if (!sigOk) return { ok: false, reason: "signature-mismatch" };
12811
+ let payload;
12812
+ try {
12813
+ const json = JSON.parse(fromBase64Url(payloadB64).toString("utf8"));
12814
+ if (!json || typeof json !== "object" || typeof json.sid !== "string" || typeof json.exp !== "number") {
12815
+ return { ok: false, reason: "bad-payload" };
12816
+ }
12817
+ payload = { sid: json.sid, exp: json.exp };
12818
+ } catch (e) {
12819
+ return { ok: false, reason: "bad-payload", detail: e.message };
12820
+ }
12821
+ const now = args.now ?? Math.floor(Date.now() / 1e3);
12822
+ if (payload.exp < now) {
12823
+ return {
12824
+ ok: false,
12825
+ reason: "expired",
12826
+ detail: `${now - payload.exp}s past expiry`
12827
+ };
12828
+ }
12829
+ return { ok: true, payload };
12830
+ }
12831
+ function verifyRequest(args) {
12832
+ if (!args.signatureB64 || !args.timestampSeconds || !args.algorithm) {
12833
+ return { ok: false, reason: "missing-headers" };
12834
+ }
12835
+ if (args.algorithm.toLowerCase() !== "ed25519") {
12836
+ return { ok: false, reason: "bad-algorithm", detail: args.algorithm };
12837
+ }
12838
+ const ts = Number(args.timestampSeconds);
12839
+ if (!Number.isFinite(ts)) {
12840
+ return { ok: false, reason: "bad-timestamp", detail: args.timestampSeconds };
12841
+ }
12842
+ const now = args.now ?? Math.floor(Date.now() / 1e3);
12843
+ if (Math.abs(now - ts) > REPLAY_WINDOW_SECONDS) {
12844
+ return {
12845
+ ok: false,
12846
+ reason: "stale-timestamp",
12847
+ detail: `${Math.abs(now - ts)}s outside the ${REPLAY_WINDOW_SECONDS}s window`
12848
+ };
12849
+ }
12850
+ let sigBuf;
12851
+ try {
12852
+ sigBuf = Buffer.from(args.signatureB64, "base64");
12853
+ } catch {
12854
+ return { ok: false, reason: "bad-signature-encoding" };
12855
+ }
12856
+ const canonical = canonicalSigningString({
12857
+ method: args.method,
12858
+ path: args.path,
12859
+ timestamp: ts,
12860
+ bodyHashB64: bodyHashB64(args.body)
12861
+ });
12862
+ const ok = cryptoVerify(null, Buffer.from(canonical, "utf8"), args.publicKey, sigBuf);
12863
+ return ok ? { ok: true } : { ok: false, reason: "signature-mismatch" };
12864
+ }
12865
+
12866
+ // src/personal-agent/signature-middleware.ts
12867
+ var SKIP_PATHS2 = /* @__PURE__ */ new Set(["/health", "/health/", "/health/ready", "/health/version"]);
12868
+ function isSkipped(path) {
12869
+ return SKIP_PATHS2.has(path) || path.startsWith("/health/api-keys");
12870
+ }
12871
+ function pathBindsSessionId(path, sid) {
12872
+ const cleanPath = path.split("?")[0];
12873
+ const segments = cleanPath.split("/").filter(Boolean);
12874
+ return segments.includes(sid);
12875
+ }
12876
+ function signatureMiddleware(opts = {}) {
12877
+ const acceptSignedOnly = opts.acceptSignedOnly ?? process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY === "1";
12878
+ const publicKeyInput = opts.publicKeyInput ?? process.env.PERSONAL_AGENT_PUBLIC_KEY ?? "";
12879
+ if (acceptSignedOnly && !publicKeyInput) {
12880
+ return async (c) => c.json(
12881
+ {
12882
+ error: "signature middleware misconfigured",
12883
+ message: "started with --accept-signed-only but no --personal-agent-public-key / PERSONAL_AGENT_PUBLIC_KEY supplied. Refusing all non-/health traffic."
12884
+ },
12885
+ 500
12886
+ );
12887
+ }
12888
+ if (!publicKeyInput) {
12889
+ return async (_c, next) => next();
12890
+ }
12891
+ const publicKey = loadPublicKey(publicKeyInput);
12892
+ let warnedUnsigned = false;
12893
+ return async (c, next) => {
12894
+ const path = c.req.path;
12895
+ if (isSkipped(path)) return next();
12896
+ const sig = c.req.header("x-signature");
12897
+ const ts = c.req.header("x-signature-timestamp");
12898
+ const alg = c.req.header("x-signature-algorithm");
12899
+ if (!sig && (c.req.method === "GET" || c.req.method === "HEAD")) {
12900
+ const embedTok = c.req.header("x-embed-token") ?? c.req.query("embed_token");
12901
+ if (embedTok) {
12902
+ const result2 = verifyEmbedToken({ publicKey, token: embedTok });
12903
+ if (!result2.ok) {
12904
+ return c.json(
12905
+ {
12906
+ error: "embed token verification failed",
12907
+ reason: result2.reason,
12908
+ detail: result2.detail
12909
+ },
12910
+ 401
12911
+ );
12912
+ }
12913
+ if (!pathBindsSessionId(path, result2.payload.sid)) {
12914
+ return c.json(
12915
+ {
12916
+ error: "embed token scoped to a different session",
12917
+ detail: `token sid=${result2.payload.sid} but request path=${path}`
12918
+ },
12919
+ 403
12920
+ );
12921
+ }
12922
+ return next();
12923
+ }
12924
+ }
12925
+ if (!sig) {
12926
+ if (acceptSignedOnly) {
12927
+ return c.json(
12928
+ {
12929
+ error: "signature required",
12930
+ message: "this sparkecoder is started with --accept-signed-only; every request must carry X-Signature, X-Signature-Timestamp, X-Signature-Algorithm."
12931
+ },
12932
+ 401
12933
+ );
12934
+ }
12935
+ if (!warnedUnsigned) {
12936
+ warnedUnsigned = true;
12937
+ console.warn(
12938
+ `[personal-agent] inbound ${c.req.method} ${path} arrived without X-Signature; allowed because --accept-signed-only is off`
12939
+ );
12940
+ }
12941
+ return next();
12942
+ }
12943
+ let body;
12944
+ if (c.req.method !== "GET" && c.req.method !== "HEAD") {
12945
+ body = Buffer.from(await c.req.raw.clone().arrayBuffer());
12946
+ }
12947
+ const result = verifyRequest({
12948
+ publicKey,
12949
+ method: c.req.method,
12950
+ path,
12951
+ body,
12952
+ signatureB64: sig,
12953
+ timestampSeconds: ts,
12954
+ algorithm: alg
12955
+ });
12956
+ if (!result.ok) {
12957
+ console.warn(
12958
+ `[personal-agent] signature verification failed on ${c.req.method} ${path}: ${result.reason}${result.detail ? ` (${result.detail})` : ""}`
12959
+ );
12960
+ return c.json(
12961
+ {
12962
+ error: "signature verification failed",
12963
+ reason: result.reason,
12964
+ detail: result.detail
12965
+ },
12966
+ 401
12967
+ );
12968
+ }
12969
+ return next();
12970
+ };
12971
+ }
12972
+
12973
+ // src/personal-agent/pty-server.ts
12974
+ init_heartbeat();
12975
+ import { hostname as hostname3 } from "os";
12976
+ import { WebSocketServer } from "ws";
12977
+ var RESIZE_RE = /\x1b\[RESIZE:(\d+);(\d+)\]/;
12978
+ var _ptyMod = null;
12979
+ async function loadPty() {
12980
+ if (_ptyMod) return _ptyMod;
12981
+ try {
12982
+ const mod = await import("node-pty");
12983
+ _ptyMod = mod;
12984
+ return mod;
12985
+ } catch (e) {
12986
+ console.warn(
12987
+ `[personal-agent] node-pty failed to load; /pty WebSocket disabled. (${e.message})`
12988
+ );
12989
+ return null;
12990
+ }
12991
+ }
12992
+ function defaultShell() {
12993
+ if (process.platform === "win32") {
12994
+ return { file: process.env.COMSPEC || "cmd.exe", args: [] };
12995
+ }
12996
+ return { file: process.env.SHELL || "/bin/zsh", args: ["-l"] };
12997
+ }
12998
+ function cleanEnv() {
12999
+ const env = {};
13000
+ for (const [k, v] of Object.entries(process.env)) {
13001
+ if (typeof v === "string") env[k] = v;
13002
+ }
13003
+ if (!env.TERM) env.TERM = "xterm-256color";
13004
+ if (!env.LANG) env.LANG = "en_US.UTF-8";
13005
+ return env;
13006
+ }
13007
+ function parseUpgrade(req) {
13008
+ const url = new URL(req.url || "/", "http://placeholder");
13009
+ const cols = clampInt(url.searchParams.get("cols"), 80, 10, 500);
13010
+ const rows = clampInt(url.searchParams.get("rows"), 24, 5, 500);
13011
+ const cwd = url.searchParams.get("cwd") || void 0;
13012
+ const shell = url.searchParams.get("shell") || void 0;
13013
+ return {
13014
+ cols,
13015
+ rows,
13016
+ cwd,
13017
+ shell,
13018
+ hwidHeader: headerStr(req, "x-device-hwid") ?? url.searchParams.get("hwid") ?? void 0,
13019
+ sigHeader: headerStr(req, "x-signature") ?? url.searchParams.get("sig") ?? void 0,
13020
+ tsHeader: headerStr(req, "x-signature-timestamp") ?? url.searchParams.get("ts") ?? void 0,
13021
+ algHeader: headerStr(req, "x-signature-algorithm") ?? url.searchParams.get("alg") ?? void 0
13022
+ };
13023
+ }
13024
+ function clampInt(v, dflt, lo, hi) {
13025
+ if (!v) return dflt;
13026
+ const n = parseInt(v, 10);
13027
+ if (!Number.isFinite(n)) return dflt;
13028
+ return Math.max(lo, Math.min(hi, n));
13029
+ }
13030
+ function headerStr(req, name) {
13031
+ const v = req.headers[name];
13032
+ if (Array.isArray(v)) return v[0];
13033
+ return v;
13034
+ }
13035
+ function authenticate(parsed, path, pubKey, signedOnly) {
13036
+ if (parsed.hwidHeader) {
13037
+ const expected = getHardwareIdCached();
13038
+ if (parsed.hwidHeader !== expected) {
13039
+ return {
13040
+ ok: false,
13041
+ status: 409,
13042
+ reason: `hwid mismatch: got ${parsed.hwidHeader.slice(0, 12)}\u2026, expected ${expected.slice(0, 12)}\u2026`
13043
+ };
13044
+ }
13045
+ }
13046
+ if (!pubKey) {
13047
+ if (signedOnly) {
13048
+ return { ok: false, status: 500, reason: "signature required but no public key configured" };
13049
+ }
13050
+ return { ok: true };
13051
+ }
13052
+ if (!parsed.sigHeader) {
13053
+ if (signedOnly) {
13054
+ return { ok: false, status: 401, reason: "missing X-Signature on upgrade request" };
13055
+ }
13056
+ return { ok: true };
13057
+ }
13058
+ const result = verifyRequest({
13059
+ publicKey: pubKey,
13060
+ method: "GET",
13061
+ path,
13062
+ body: void 0,
13063
+ signatureB64: parsed.sigHeader,
13064
+ timestampSeconds: parsed.tsHeader,
13065
+ algorithm: parsed.algHeader
13066
+ });
13067
+ if (!result.ok) {
13068
+ return { ok: false, status: 401, reason: `signature verification failed: ${result.reason}` };
13069
+ }
13070
+ return { ok: true };
13071
+ }
13072
+ function rejectUpgrade(socket, status, reason) {
13073
+ const body = JSON.stringify({ error: reason });
13074
+ socket.write(
13075
+ `HTTP/1.1 ${status} ${reasonText(status)}\r
13076
+ Content-Type: application/json\r
13077
+ Content-Length: ${Buffer.byteLength(body)}\r
13078
+ Connection: close\r
13079
+ \r
13080
+ ` + body
13081
+ );
13082
+ socket.destroy();
13083
+ }
13084
+ function reasonText(status) {
13085
+ switch (status) {
13086
+ case 401:
13087
+ return "Unauthorized";
13088
+ case 409:
13089
+ return "Conflict";
13090
+ case 500:
13091
+ return "Internal Server Error";
13092
+ case 503:
13093
+ return "Service Unavailable";
13094
+ default:
13095
+ return "Error";
13096
+ }
13097
+ }
13098
+ function attachPtyServer(httpServer, opts = {}) {
13099
+ const path = opts.path ?? "/pty";
13100
+ const wss = new WebSocketServer({ noServer: true, perMessageDeflate: false });
13101
+ const acceptSignedOnly = process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY === "1";
13102
+ const publicKeyInput = process.env.PERSONAL_AGENT_PUBLIC_KEY ?? "";
13103
+ let pubKey = null;
13104
+ if (publicKeyInput) {
13105
+ try {
13106
+ pubKey = loadPublicKey(publicKeyInput);
13107
+ } catch (e) {
13108
+ console.warn(
13109
+ `[personal-agent] /pty signature verification disabled \u2014 failed to load public key: ${e.message}`
13110
+ );
13111
+ }
13112
+ }
13113
+ const handler = (req, socket, head) => {
13114
+ let pathname = "/";
13115
+ try {
13116
+ pathname = new URL(req.url || "/", "http://placeholder").pathname;
13117
+ } catch {
13118
+ }
13119
+ if (pathname !== path) return;
13120
+ const parsed = parseUpgrade(req);
13121
+ const auth = authenticate(parsed, pathname, pubKey, acceptSignedOnly);
13122
+ if (!auth.ok) {
13123
+ console.warn(`[personal-agent] rejecting /pty upgrade: ${auth.reason}`);
13124
+ rejectUpgrade(socket, auth.status ?? 401, auth.reason ?? "unauthorized");
13125
+ return;
13126
+ }
13127
+ void loadPty().then((pty) => {
13128
+ if (!pty) {
13129
+ rejectUpgrade(
13130
+ socket,
13131
+ 503,
13132
+ "node-pty is not available on this device (failed to load native module)"
13133
+ );
13134
+ return;
13135
+ }
13136
+ wss.handleUpgrade(req, socket, head, (ws) => {
13137
+ spawnPty(ws, pty, parsed, opts);
13138
+ });
13139
+ });
13140
+ };
13141
+ httpServer.on("upgrade", handler);
13142
+ if (!opts.quiet) {
13143
+ console.log(
13144
+ `[personal-agent] WebSocket PTY server attached at ${path} (host: ${hostname3()}, sigCheck: ${pubKey ? "on" : "off"})`
13145
+ );
13146
+ }
13147
+ return {
13148
+ close: () => {
13149
+ httpServer.off("upgrade", handler);
13150
+ wss.close();
13151
+ }
13152
+ };
13153
+ }
13154
+ function spawnPty(ws, pty, parsed, opts) {
13155
+ const { file, args } = (() => {
13156
+ if (parsed.shell || opts.shell) {
13157
+ const s = parsed.shell ?? opts.shell;
13158
+ return { file: s, args: process.platform === "win32" ? [] : ["-l"] };
13159
+ }
13160
+ return defaultShell();
13161
+ })();
13162
+ const cwd = parsed.cwd ?? opts.cwd ?? process.env.HOME ?? process.cwd();
13163
+ let proc;
13164
+ try {
13165
+ proc = pty.spawn(file, args, {
13166
+ name: "xterm-256color",
13167
+ cols: parsed.cols,
13168
+ rows: parsed.rows,
13169
+ cwd,
13170
+ env: cleanEnv()
13171
+ });
13172
+ } catch (e) {
13173
+ const msg = e.message;
13174
+ safeSend(ws, `\r
13175
+ \x1B[31mFailed to spawn shell: ${msg}\x1B[0m\r
13176
+ `);
13177
+ try {
13178
+ ws.close();
13179
+ } catch {
13180
+ }
13181
+ return;
13182
+ }
13183
+ const banner = `\x1B[90m[sparkecoder pty] ${file} on ${hostname3()} pid=${proc.pid} ${parsed.cols}x${parsed.rows}\x1B[0m\r
13184
+ `;
13185
+ safeSend(ws, banner);
13186
+ proc.onData((data) => safeSend(ws, data));
13187
+ proc.onExit(({ exitCode }) => {
13188
+ safeSend(ws, `\r
13189
+ \x1B[90m[exit ${exitCode}]\x1B[0m\r
13190
+ `);
13191
+ try {
13192
+ ws.close();
13193
+ } catch {
13194
+ }
13195
+ });
13196
+ ws.on("message", (msg, isBinary) => {
13197
+ const input = typeof msg === "string" ? msg : Buffer.isBuffer(msg) ? msg.toString(isBinary ? "utf8" : "utf8") : Array.isArray(msg) ? Buffer.concat(msg).toString("utf8") : "";
13198
+ if (!input) return;
13199
+ const m = input.match(RESIZE_RE);
13200
+ if (m) {
13201
+ const cols = clampInt(m[1], 80, 10, 500);
13202
+ const rows = clampInt(m[2], 24, 5, 500);
13203
+ try {
13204
+ proc.resize(cols, rows);
13205
+ } catch {
13206
+ }
13207
+ if (input.replace(RESIZE_RE, "").length === 0) return;
13208
+ proc.write(input.replace(RESIZE_RE, ""));
13209
+ return;
13210
+ }
13211
+ proc.write(input);
13212
+ });
13213
+ const onClose = () => {
13214
+ try {
13215
+ proc.kill();
13216
+ } catch {
13217
+ }
13218
+ };
13219
+ ws.on("close", onClose);
13220
+ ws.on("error", onClose);
13221
+ }
13222
+ function safeSend(ws, data) {
13223
+ if (ws.readyState !== 1) return;
13224
+ try {
13225
+ ws.send(data);
13226
+ } catch {
13227
+ }
13228
+ }
13229
+
11440
13230
  // src/server/index.ts
11441
13231
  init_config();
11442
13232
  init_db();
11443
13233
 
11444
13234
  // src/utils/dependencies.ts
11445
- import { exec as exec6 } from "child_process";
11446
- import { promisify as promisify6 } from "util";
11447
- import { platform as platform2 } from "os";
11448
- var execAsync6 = promisify6(exec6);
13235
+ import { exec as exec7 } from "child_process";
13236
+ import { promisify as promisify7 } from "util";
13237
+ import { platform as platform4 } from "os";
13238
+ var execAsync7 = promisify7(exec7);
11449
13239
  function getInstallInstructions() {
11450
- const os2 = platform2();
13240
+ const os2 = platform4();
11451
13241
  if (os2 === "darwin") {
11452
13242
  return `
11453
13243
  Install tmux on macOS:
@@ -11478,7 +13268,7 @@ Install tmux:
11478
13268
  }
11479
13269
  async function checkTmux() {
11480
13270
  try {
11481
- const { stdout } = await execAsync6("tmux -V", { timeout: 5e3 });
13271
+ const { stdout } = await execAsync7("tmux -V", { timeout: 5e3 });
11482
13272
  const version = stdout.trim();
11483
13273
  return {
11484
13274
  available: true,
@@ -11519,7 +13309,7 @@ async function checkDependencies(options = {}) {
11519
13309
  }
11520
13310
  async function checkAgentBrowser() {
11521
13311
  try {
11522
- const { stdout } = await execAsync6("agent-browser --version", { timeout: 1e4 });
13312
+ const { stdout } = await execAsync7("agent-browser --version", { timeout: 1e4 });
11523
13313
  const version = stdout.trim();
11524
13314
  return { available: true, version };
11525
13315
  } catch {
@@ -11535,12 +13325,12 @@ async function tryInstallAgentBrowser(options = {}) {
11535
13325
  if (!options.quiet) {
11536
13326
  console.log("\u{1F4E6} Installing agent-browser globally...");
11537
13327
  }
11538
- await execAsync6("npm install -g agent-browser", { timeout: 12e4 });
13328
+ await execAsync7("npm install -g agent-browser", { timeout: 12e4 });
11539
13329
  try {
11540
13330
  if (!options.quiet) {
11541
13331
  console.log("\u{1F4E6} Installing Chromium for browser automation...");
11542
13332
  }
11543
- await execAsync6("agent-browser install", { timeout: 12e4 });
13333
+ await execAsync7("agent-browser install", { timeout: 12e4 });
11544
13334
  } catch {
11545
13335
  }
11546
13336
  if (!options.quiet) {
@@ -11555,25 +13345,25 @@ async function tryInstallAgentBrowser(options = {}) {
11555
13345
  }
11556
13346
  }
11557
13347
  async function tryAutoInstallTmux() {
11558
- const os2 = platform2();
13348
+ const os2 = platform4();
11559
13349
  try {
11560
13350
  if (os2 === "darwin") {
11561
13351
  try {
11562
- await execAsync6("which brew", { timeout: 5e3 });
13352
+ await execAsync7("which brew", { timeout: 5e3 });
11563
13353
  } catch {
11564
13354
  return false;
11565
13355
  }
11566
13356
  console.log("\u{1F4E6} Installing tmux via Homebrew...");
11567
- await execAsync6("brew install tmux", { timeout: 3e5 });
13357
+ await execAsync7("brew install tmux", { timeout: 3e5 });
11568
13358
  console.log("\u2705 tmux installed successfully");
11569
13359
  return true;
11570
13360
  }
11571
13361
  if (os2 === "linux") {
11572
13362
  try {
11573
- await execAsync6("which apt-get", { timeout: 5e3 });
13363
+ await execAsync7("which apt-get", { timeout: 5e3 });
11574
13364
  console.log("\u{1F4E6} Installing tmux via apt-get...");
11575
13365
  console.log(" (This may require sudo password)");
11576
- await execAsync6("sudo apt-get update && sudo apt-get install -y tmux", {
13366
+ await execAsync7("sudo apt-get update && sudo apt-get install -y tmux", {
11577
13367
  timeout: 3e5
11578
13368
  });
11579
13369
  console.log("\u2705 tmux installed successfully");
@@ -11581,9 +13371,9 @@ async function tryAutoInstallTmux() {
11581
13371
  } catch {
11582
13372
  }
11583
13373
  try {
11584
- await execAsync6("which dnf", { timeout: 5e3 });
13374
+ await execAsync7("which dnf", { timeout: 5e3 });
11585
13375
  console.log("\u{1F4E6} Installing tmux via dnf...");
11586
- await execAsync6("sudo dnf install -y tmux", { timeout: 3e5 });
13376
+ await execAsync7("sudo dnf install -y tmux", { timeout: 3e5 });
11587
13377
  console.log("\u2705 tmux installed successfully");
11588
13378
  return true;
11589
13379
  } catch {
@@ -11623,11 +13413,11 @@ function getWebDirectory() {
11623
13413
  try {
11624
13414
  const currentDir = dirname7(fileURLToPath4(import.meta.url));
11625
13415
  const webDir = resolve10(currentDir, "..", "web");
11626
- if (existsSync17(webDir) && existsSync17(join12(webDir, "package.json"))) {
13416
+ if (existsSync19(webDir) && existsSync19(join13(webDir, "package.json"))) {
11627
13417
  return webDir;
11628
13418
  }
11629
13419
  const altWebDir = resolve10(currentDir, "..", "..", "web");
11630
- if (existsSync17(altWebDir) && existsSync17(join12(altWebDir, "package.json"))) {
13420
+ if (existsSync19(altWebDir) && existsSync19(join13(altWebDir, "package.json"))) {
11631
13421
  return altWebDir;
11632
13422
  }
11633
13423
  return null;
@@ -11685,23 +13475,23 @@ async function findWebPort(preferredPort) {
11685
13475
  return { port: preferredPort, alreadyRunning: false };
11686
13476
  }
11687
13477
  function hasProductionBuild(webDir) {
11688
- const buildIdPath = join12(webDir, ".next", "BUILD_ID");
11689
- return existsSync17(buildIdPath);
13478
+ const buildIdPath = join13(webDir, ".next", "BUILD_ID");
13479
+ return existsSync19(buildIdPath);
11690
13480
  }
11691
13481
  function hasSourceFiles(webDir) {
11692
- const appDir = join12(webDir, "src", "app");
11693
- const pagesDir = join12(webDir, "src", "pages");
11694
- const rootAppDir = join12(webDir, "app");
11695
- const rootPagesDir = join12(webDir, "pages");
11696
- return existsSync17(appDir) || existsSync17(pagesDir) || existsSync17(rootAppDir) || existsSync17(rootPagesDir);
13482
+ const appDir = join13(webDir, "src", "app");
13483
+ const pagesDir = join13(webDir, "src", "pages");
13484
+ const rootAppDir = join13(webDir, "app");
13485
+ const rootPagesDir = join13(webDir, "pages");
13486
+ return existsSync19(appDir) || existsSync19(pagesDir) || existsSync19(rootAppDir) || existsSync19(rootPagesDir);
11697
13487
  }
11698
13488
  function getStandaloneServerPath(webDir) {
11699
13489
  const possiblePaths2 = [
11700
- join12(webDir, ".next", "standalone", "server.js"),
11701
- join12(webDir, ".next", "standalone", "web", "server.js")
13490
+ join13(webDir, ".next", "standalone", "server.js"),
13491
+ join13(webDir, ".next", "standalone", "web", "server.js")
11702
13492
  ];
11703
13493
  for (const serverPath of possiblePaths2) {
11704
- if (existsSync17(serverPath)) {
13494
+ if (existsSync19(serverPath)) {
11705
13495
  return serverPath;
11706
13496
  }
11707
13497
  }
@@ -11741,13 +13531,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
11741
13531
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
11742
13532
  return { process: null, port: actualPort };
11743
13533
  }
11744
- const usePnpm = existsSync17(join12(webDir, "pnpm-lock.yaml"));
11745
- const useNpm = !usePnpm && existsSync17(join12(webDir, "package-lock.json"));
13534
+ const usePnpm = existsSync19(join13(webDir, "pnpm-lock.yaml"));
13535
+ const useNpm = !usePnpm && existsSync19(join13(webDir, "package-lock.json"));
11746
13536
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
11747
- const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
13537
+ const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv2 } = process.env;
11748
13538
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
11749
13539
  const runtimeConfig = { apiBaseUrl: apiUrl };
11750
- const runtimeConfigPath = join12(webDir, "runtime-config.json");
13540
+ const runtimeConfigPath = join13(webDir, "runtime-config.json");
11751
13541
  try {
11752
13542
  writeFileSync5(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
11753
13543
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -11755,7 +13545,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
11755
13545
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
11756
13546
  }
11757
13547
  const webEnv = {
11758
- ...cleanEnv,
13548
+ ...cleanEnv2,
11759
13549
  PORT: String(actualPort)
11760
13550
  // Next.js respects PORT env var
11761
13551
  };
@@ -11869,12 +13659,28 @@ function stopWebUI() {
11869
13659
  }
11870
13660
  }
11871
13661
  async function createApp(options = {}) {
11872
- const app = new Hono6();
13662
+ const app = new Hono7();
11873
13663
  app.use("*", cors({
11874
13664
  origin: "*",
11875
13665
  // Allow all origins
11876
13666
  allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
11877
- allowHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
13667
+ allowHeaders: [
13668
+ "Content-Type",
13669
+ "Authorization",
13670
+ "X-Requested-With",
13671
+ // Personal-agent dashboard signs every request to the device with
13672
+ // these. Without them whitelisted the browser preflight strips the
13673
+ // headers and the signature middleware 401s.
13674
+ "X-Signature",
13675
+ "X-Signature-Timestamp",
13676
+ "X-Signature-Algorithm",
13677
+ "X-Device-Hwid",
13678
+ // Short-lived embed token used by the iframed SparkECoder web UI
13679
+ // when it's hosted inside the personal-agents dashboard. The
13680
+ // bootstrap in `web/src/lib/embed-bootstrap.ts` adds this header
13681
+ // to every API call.
13682
+ "X-Embed-Token"
13683
+ ],
11878
13684
  exposeHeaders: ["X-Stream-Id", "x-stream-id"],
11879
13685
  maxAge: 86400
11880
13686
  // 24 hours
@@ -11882,12 +13688,15 @@ async function createApp(options = {}) {
11882
13688
  if (!options.quiet) {
11883
13689
  app.use("*", logger());
11884
13690
  }
13691
+ app.use("*", hwidMiddleware());
13692
+ app.use("*", signatureMiddleware());
11885
13693
  app.route("/health", health);
11886
13694
  app.route("/sessions", sessions);
11887
13695
  app.route("/agents", agents);
11888
13696
  app.route("/sessions", terminals);
11889
13697
  app.route("/terminals", terminals);
11890
13698
  app.route("/tasks", tasks_default);
13699
+ app.route("/system", system);
11891
13700
  app.get("/openapi.json", async (c) => {
11892
13701
  return c.json(generateOpenAPISpec());
11893
13702
  });
@@ -11940,8 +13749,8 @@ async function startServer(options = {}) {
11940
13749
  if (options.workingDirectory) {
11941
13750
  config.resolvedWorkingDirectory = options.workingDirectory;
11942
13751
  }
11943
- if (!existsSync17(config.resolvedWorkingDirectory)) {
11944
- mkdirSync7(config.resolvedWorkingDirectory, { recursive: true });
13752
+ if (!existsSync19(config.resolvedWorkingDirectory)) {
13753
+ mkdirSync8(config.resolvedWorkingDirectory, { recursive: true });
11945
13754
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
11946
13755
  }
11947
13756
  if (!config.resolvedRemoteServer.url) {
@@ -11976,6 +13785,15 @@ async function startServer(options = {}) {
11976
13785
  port,
11977
13786
  hostname: host
11978
13787
  });
13788
+ try {
13789
+ attachPtyServer(serverInstance, {
13790
+ quiet: options.quiet
13791
+ });
13792
+ } catch (e) {
13793
+ if (!options.quiet) {
13794
+ console.warn(` \u26A0 Failed to attach /pty WebSocket server: ${e.message}`);
13795
+ }
13796
+ }
11979
13797
  let webPort;
11980
13798
  let webStarted;
11981
13799
  if (options.webUI !== false) {
@@ -12457,8 +14275,30 @@ function generateOpenAPISpec() {
12457
14275
  init_config();
12458
14276
  init_semantic();
12459
14277
  init_db();
12460
- import { writeFileSync as writeFileSync6, readFileSync as readFileSync8, existsSync as existsSync18 } from "fs";
12461
- import { resolve as resolve11, join as join13 } from "path";
14278
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6, readFileSync as readFileSync12, existsSync as existsSync20, chmodSync } from "fs";
14279
+ import { resolve as resolve11, join as join14 } from "path";
14280
+ function getCliVersion() {
14281
+ const here = dirname8(fileURLToPath5(import.meta.url));
14282
+ const candidates = [
14283
+ join14(here, "..", "package.json"),
14284
+ join14(here, "..", "..", "package.json"),
14285
+ join14(process.cwd(), "package.json")
14286
+ ];
14287
+ for (const p of candidates) {
14288
+ try {
14289
+ const pkg = JSON.parse(readFileSync12(p, "utf8"));
14290
+ if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
14291
+ } catch {
14292
+ }
14293
+ }
14294
+ return "0.0.0";
14295
+ }
14296
+ function shellExport(name, value) {
14297
+ return `export ${name}='${value.replace(/'/g, `'\\''`)}'`;
14298
+ }
14299
+ function xmlEscape(value) {
14300
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
14301
+ }
12462
14302
  async function apiRequest(baseUrl, path, options = {}) {
12463
14303
  const url = `${baseUrl}${path}`;
12464
14304
  const init = {
@@ -13021,10 +14861,26 @@ Unexpected error: ${outerError.message}`));
13021
14861
  }
13022
14862
  }
13023
14863
  var program = new Command();
13024
- program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version("0.1.0").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
14864
+ program.name("sparkecoder").description("AI coding agent - just type sparkecoder to start chatting").version(getCliVersion()).option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").option("--enable-computer-use", "Enable the Anthropic computer use tool for all new sessions by default (macOS only)").action(async (options) => {
14865
+ if (options.enableComputerUse) {
14866
+ process.env.SPARKECODER_COMPUTER_USE = "1";
14867
+ console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
14868
+ }
13025
14869
  await runChat(options);
13026
14870
  });
13027
- program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").action(async (options) => {
14871
+ program.command("server").description("Start the SparkECoder server (API + Web UI)").option("-p, --port <port>", "API server port", "3141").option("-h, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("-w, --working-dir <path>", "Working directory").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--enable-computer-use", "Enable the Anthropic computer use tool for all new sessions by default (macOS only)").option("--personal-agent-mode", "Heartbeat to a personal-agents dashboard so it can dispatch tasks here. Reads $PERSONAL_AGENT_* env vars for the rest.").option("--personal-agent-dashboard <url>", "Dashboard URL to heartbeat to (env: PERSONAL_AGENT_DASHBOARD)").option("--personal-agent-name <name>", "Friendly device name shown on the dashboard (env: PERSONAL_AGENT_NAME, default: hostname)").option("--personal-agent-public-url <url>", "Public URL the dashboard should call to reach this sparkecoder API (env: PERSONAL_AGENT_PUBLIC_URL)").option("--personal-agent-web-url <url>", "Public URL of this sparkecoder's web UI, typically port 6970. When set, the dashboard can iframe `${webUrl}/embed/${sparkTaskId}` to show the live session inside its TaskDrawer (env: PERSONAL_AGENT_WEB_URL)").option("--personal-agent-ingest-token <token>", "Shared bearer token that auths heartbeats with the dashboard (env: PERSONAL_AGENT_INGEST_TOKEN)").option("--personal-agent-public-key <pem-or-path>", "Ed25519 PUBLIC key the dashboard signs with (PEM string or path to .pem). env: PERSONAL_AGENT_PUBLIC_KEY").option("--accept-signed-only", "Reject any inbound non-/health request that lacks a valid Ed25519 signature from the dashboard. Requires --personal-agent-public-key.").action(async (options) => {
14872
+ const globalOpts = program.opts();
14873
+ const enableCU = options.enableComputerUse || globalOpts.enableComputerUse;
14874
+ if (enableCU) {
14875
+ process.env.SPARKECODER_COMPUTER_USE = "1";
14876
+ console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
14877
+ }
14878
+ if (options.personalAgentPublicKey) {
14879
+ process.env.PERSONAL_AGENT_PUBLIC_KEY = options.personalAgentPublicKey;
14880
+ }
14881
+ if (options.acceptSignedOnly) {
14882
+ process.env.PERSONAL_AGENT_ACCEPT_SIGNED_ONLY = "1";
14883
+ }
13028
14884
  await ensureDependencies({ quiet: false });
13029
14885
  const spinner = ora("Starting SparkECoder server...").start();
13030
14886
  try {
@@ -13047,6 +14903,8 @@ program.command("server").description("Start the SparkECoder server (API + Web U
13047
14903
  console.log("");
13048
14904
  console.log(chalk.dim(` API: http://${host}:${port}`));
13049
14905
  console.log(chalk.dim(` Swagger: http://${host}:${port}/swagger`));
14906
+ const { readConfig: readPaCfg, startPersonalAgent: startPersonalAgent2 } = await Promise.resolve().then(() => (init_heartbeat(), heartbeat_exports));
14907
+ startPersonalAgent2(readPaCfg(options));
13050
14908
  console.log("");
13051
14909
  console.log(chalk.dim("Press Ctrl+C to stop"));
13052
14910
  const cleanup2 = () => {
@@ -13061,7 +14919,13 @@ program.command("server").description("Start the SparkECoder server (API + Web U
13061
14919
  process.exit(1);
13062
14920
  }
13063
14921
  });
13064
- program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
14922
+ program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").option("--enable-computer-use", "Enable the Anthropic computer use tool for all new sessions by default (macOS only)").action(async (options) => {
14923
+ const globalOpts = program.opts();
14924
+ const enableCU = options.enableComputerUse || globalOpts.enableComputerUse;
14925
+ if (enableCU) {
14926
+ process.env.SPARKECODER_COMPUTER_USE = "1";
14927
+ console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
14928
+ }
13065
14929
  await runChat(options);
13066
14930
  });
13067
14931
  program.command("task").description("Run an autonomous task that completes without human interaction").requiredOption("--prompt <prompt>", "Task prompt describing what to do").requiredOption("--schema <schema>", "JSON Schema for the output (file path or inline JSON string)").option("--webhook <url>", "Webhook URL to receive task events").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-n, --name <name>", "Name for the task").option("--max-iterations <n>", "Maximum agent loop iterations", "50").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("--no-auto-start", "Do not auto-start server if not running").option("--parent-task <id>", "Continue from a previous task (inherits its full conversation context)").option("--wait", "Block and poll until task completes").option("-v, --verbose", "Enable verbose logging").action(async (options) => {
@@ -13093,8 +14957,8 @@ program.command("task").description("Run an autonomous task that completes witho
13093
14957
  let outputSchema;
13094
14958
  try {
13095
14959
  const schemaStr = options.schema;
13096
- if (existsSync18(schemaStr)) {
13097
- outputSchema = JSON.parse(readFileSync8(schemaStr, "utf-8"));
14960
+ if (existsSync20(schemaStr)) {
14961
+ outputSchema = JSON.parse(readFileSync12(schemaStr, "utf-8"));
13098
14962
  } else {
13099
14963
  outputSchema = JSON.parse(schemaStr);
13100
14964
  }
@@ -13161,13 +15025,13 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
13161
15025
  let configLocation;
13162
15026
  if (options.global) {
13163
15027
  const appDataDir = ensureAppDataDirectory();
13164
- configPath = join13(appDataDir, "sparkecoder.config.json");
15028
+ configPath = join14(appDataDir, "sparkecoder.config.json");
13165
15029
  configLocation = "global";
13166
15030
  } else {
13167
15031
  configPath = resolve11(process.cwd(), "sparkecoder.config.json");
13168
15032
  configLocation = "local";
13169
15033
  }
13170
- if (existsSync18(configPath) && !options.force) {
15034
+ if (existsSync20(configPath) && !options.force) {
13171
15035
  console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
13172
15036
  console.log(chalk.dim(` ${configPath}`));
13173
15037
  return;
@@ -13178,6 +15042,114 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
13178
15042
  console.log(chalk.dim(` ${configPath}`));
13179
15043
  console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
13180
15044
  });
15045
+ program.command("personal-agent-setup").description("Create a personal-agent setup folder with env, key, start script, and launchd plist template").option("-d, --dir <path>", "Setup directory", "~/sparkecoder-personal-agent").option("--dashboard <url>", "Personal Agents dashboard URL").option("--device-name <name>", "Friendly device name").option("--device-api-url <url>", "Public API URL for this device (port 3141 tunnel)").option("--device-web-url <url>", "Public web UI URL for this device (port 6969 tunnel)").option("--remote-url <url>", "SparkECoder remote server URL (optional; defaults to production config)").option("--auth-key <key>", "SparkECoder remote server auth key (optional; auto-registers if omitted)").option("--ingest-token <token>", "Dashboard PERSONAL_AGENT_INGEST_TOKEN").option("--public-key <pem>", "Dashboard signing public key PEM string").option("--accept-signed-only", "Include --accept-signed-only in generated commands", true).option("--enable-computer-use", "Include --enable-computer-use in generated commands", true).action(async (options) => {
15046
+ const expandHome = (p) => p.startsWith("~/") ? join14(process.env.HOME || process.cwd(), p.slice(2)) : p;
15047
+ const setupDir = resolve11(expandHome(options.dir));
15048
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
15049
+ const ask = (label, fallback = "") => new Promise((resolve12) => {
15050
+ const suffix = fallback ? ` (${fallback})` : "";
15051
+ rl.question(`${label}${suffix}: `, (answer) => resolve12(answer.trim() || fallback));
15052
+ });
15053
+ try {
15054
+ const dashboard = options.dashboard || await ask("Dashboard URL", "https://personal-agents.studyfetchcms.com");
15055
+ const name = options.deviceName || await ask("Device name", hostname4().replace(/\.local$/, ""));
15056
+ const publicUrl = options.deviceApiUrl || await ask("Public API URL (3141 tunnel)");
15057
+ const webUrl = options.deviceWebUrl || await ask("Public web URL (6969 tunnel)", publicUrl.replace(/3141/g, "6969"));
15058
+ const remoteUrl = options.remoteUrl || await ask("SparkECoder remote server URL (blank = default)", "");
15059
+ const authKey3 = options.authKey || "";
15060
+ const ingestToken = options.ingestToken || await ask("PERSONAL_AGENT_INGEST_TOKEN");
15061
+ const publicKey = options.publicKey || await ask("Signing public key PEM (paste single-line with \\n escapes, or leave blank and edit file)");
15062
+ rl.close();
15063
+ mkdirSync9(setupDir, { recursive: true });
15064
+ const normalizedPublicKey = publicKey.replace(/\\n/g, "\n").trim();
15065
+ const keyPath = join14(setupDir, "personal-agent-public-key.pem");
15066
+ const envPath = join14(setupDir, "personal-agent.env");
15067
+ const startPath = join14(setupDir, "start-personal-agent.sh");
15068
+ const plistPath = join14(setupDir, "com.studyfetch.personal-agent.plist");
15069
+ const logsDir = join14(process.env.HOME || setupDir, "Library", "Logs", "PersonalAgents");
15070
+ const sparkecoderBin = process.env.SPARKECODER_BIN || process.argv[1] || "sparkecoder";
15071
+ if (normalizedPublicKey) {
15072
+ writeFileSync6(keyPath, normalizedPublicKey + "\n", { mode: 384 });
15073
+ } else if (!existsSync20(keyPath)) {
15074
+ writeFileSync6(keyPath, "PASTE_PUBLIC_KEY_HERE\n", { mode: 384 });
15075
+ }
15076
+ writeFileSync6(envPath, [
15077
+ shellExport("PERSONAL_AGENT_DASHBOARD", dashboard),
15078
+ shellExport("PERSONAL_AGENT_NAME", name),
15079
+ shellExport("PERSONAL_AGENT_PUBLIC_URL", publicUrl),
15080
+ shellExport("PERSONAL_AGENT_WEB_URL", webUrl),
15081
+ shellExport("PERSONAL_AGENT_INGEST_TOKEN", ingestToken),
15082
+ shellExport("PERSONAL_AGENT_PUBLIC_KEY", keyPath),
15083
+ ...remoteUrl ? [shellExport("SPARKECODER_REMOTE_URL", remoteUrl)] : [],
15084
+ ...authKey3 ? [shellExport("SPARKECODER_AUTH_KEY", authKey3)] : [],
15085
+ ""
15086
+ ].join("\n"), { mode: 384 });
15087
+ const flags = [
15088
+ "server",
15089
+ "--personal-agent-mode",
15090
+ '--personal-agent-dashboard "$PERSONAL_AGENT_DASHBOARD"',
15091
+ '--personal-agent-name "$PERSONAL_AGENT_NAME"',
15092
+ '--personal-agent-public-url "$PERSONAL_AGENT_PUBLIC_URL"',
15093
+ '--personal-agent-web-url "$PERSONAL_AGENT_WEB_URL"',
15094
+ '--personal-agent-ingest-token "$PERSONAL_AGENT_INGEST_TOKEN"',
15095
+ '--personal-agent-public-key "$PERSONAL_AGENT_PUBLIC_KEY"',
15096
+ ...options.acceptSignedOnly ? ["--accept-signed-only"] : [],
15097
+ ...options.enableComputerUse ? ["--enable-computer-use"] : []
15098
+ ];
15099
+ writeFileSync6(startPath, [
15100
+ "#!/usr/bin/env bash",
15101
+ "set -euo pipefail",
15102
+ `source "${envPath}"`,
15103
+ `exec '${sparkecoderBin.replace(/'/g, `'\\''`)}' ${flags.join(" \\\n ")}`,
15104
+ ""
15105
+ ].join("\n"), { mode: 493 });
15106
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
15107
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
15108
+ <plist version="1.0">
15109
+ <dict>
15110
+ <key>Label</key><string>com.studyfetch.personal-agent</string>
15111
+ <key>ProgramArguments</key>
15112
+ <array>
15113
+ <string>${xmlEscape(startPath)}</string>
15114
+ </array>
15115
+ <key>EnvironmentVariables</key>
15116
+ <dict>
15117
+ <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
15118
+ <key>HOME</key><string>${xmlEscape(process.env.HOME || "")}</string>
15119
+ </dict>
15120
+ <key>RunAtLoad</key><true/>
15121
+ <key>KeepAlive</key><true/>
15122
+ <key>ThrottleInterval</key><integer>10</integer>
15123
+ <key>StandardOutPath</key><string>${xmlEscape(join14(logsDir, "sparkecoder.log"))}</string>
15124
+ <key>StandardErrorPath</key><string>${xmlEscape(join14(logsDir, "sparkecoder.err.log"))}</string>
15125
+ </dict>
15126
+ </plist>
15127
+ `;
15128
+ writeFileSync6(plistPath, plist, { mode: 384 });
15129
+ mkdirSync9(logsDir, { recursive: true });
15130
+ try {
15131
+ chmodSync(envPath, 384);
15132
+ chmodSync(keyPath, 384);
15133
+ chmodSync(plistPath, 384);
15134
+ chmodSync(startPath, 493);
15135
+ } catch {
15136
+ }
15137
+ console.log(chalk.green("\n\u2713 Personal Agent setup folder created"));
15138
+ console.log(chalk.dim(` ${setupDir}`));
15139
+ console.log("");
15140
+ console.log(chalk.bold("Smoke test:"));
15141
+ console.log(chalk.cyan(` "${startPath}"`));
15142
+ console.log("");
15143
+ console.log(chalk.bold("Install launchd unit:"));
15144
+ console.log(chalk.cyan(` cp "${plistPath}" "$HOME/Library/LaunchAgents/com.studyfetch.personal-agent.plist"`));
15145
+ console.log(chalk.cyan(` launchctl bootstrap "gui/$UID" "$HOME/Library/LaunchAgents/com.studyfetch.personal-agent.plist"`));
15146
+ console.log(chalk.cyan(` launchctl kickstart -k "gui/$UID/com.studyfetch.personal-agent"`));
15147
+ } catch (error) {
15148
+ rl.close();
15149
+ console.error(chalk.red(`Error: ${error.message}`));
15150
+ process.exit(1);
15151
+ }
15152
+ });
13181
15153
  program.command("sessions").description("List all sessions").option("-l, --limit <limit>", "Number of sessions to show", "20").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").action(async (options) => {
13182
15154
  const baseUrl = `http://${options.host}:${options.port}`;
13183
15155
  try {
@@ -13425,10 +15397,10 @@ program.command("search").description("Search indexed repository using semantic
13425
15397
  process.exit(1);
13426
15398
  }
13427
15399
  });
13428
- program.command("api-key").description("Manage API keys for AI providers").argument("[provider]", "Provider name (anthropic, openai, google, xai, ai-gateway)").argument("[key]", "API key to set (if not provided, shows status)").option("-l, --list", "List all API key statuses").action((provider, key, options) => {
15400
+ program.command("api-key").description("Manage API keys for AI providers").argument("[provider]", "Provider name (anthropic, openai, google, xai, ai-gateway)").argument("[key]", "API key to set (if not provided, shows status)").option("-l, --list", "List all API key statuses").action((provider, key2, options) => {
13429
15401
  try {
13430
15402
  ensureAppDataDirectory();
13431
- if (options.list || !provider && !key) {
15403
+ if (options.list || !provider && !key2) {
13432
15404
  console.log(chalk.bold("\nAPI Key Status:\n"));
13433
15405
  const status = getApiKeyStatus();
13434
15406
  for (const s of status) {
@@ -13448,8 +15420,8 @@ program.command("api-key").description("Manage API keys for AI providers").argum
13448
15420
  console.log(chalk.dim(`Usage: sparkecoder api-key <provider> <key>`));
13449
15421
  return;
13450
15422
  }
13451
- if (provider && key) {
13452
- setApiKey(provider, key);
15423
+ if (provider && key2) {
15424
+ setApiKey(provider, key2);
13453
15425
  const status = getApiKeyStatus();
13454
15426
  const providerStatus = status.find((s) => s.provider === provider.toLowerCase());
13455
15427
  console.log(chalk.green(`
@@ -13461,7 +15433,7 @@ program.command("api-key").description("Manage API keys for AI providers").argum
13461
15433
  The key is stored securely and will be loaded automatically on startup.`));
13462
15434
  return;
13463
15435
  }
13464
- if (provider && !key) {
15436
+ if (provider && !key2) {
13465
15437
  const status = getApiKeyStatus();
13466
15438
  const providerStatus = status.find((s) => s.provider === provider.toLowerCase());
13467
15439
  if (!providerStatus) {
@@ -13489,5 +15461,112 @@ ${providerName} API Key:
13489
15461
  process.exit(1);
13490
15462
  }
13491
15463
  });
15464
+ program.command("check-permissions").description("Check macOS permissions required for the computer use tool (Accessibility + Screen Recording)").action(async () => {
15465
+ if (process.platform !== "darwin") {
15466
+ console.log(chalk.yellow(`Computer use is only supported on macOS. Current platform: ${process.platform}`));
15467
+ process.exit(0);
15468
+ }
15469
+ const {
15470
+ isCliclickInstalled: isCliclickInstalled2,
15471
+ hasAccessibilityPermissions: hasAccessibilityPermissions2,
15472
+ hasScreenRecordingPermissions: hasScreenRecordingPermissions2,
15473
+ detectScreenSize: detectScreenSize2
15474
+ } = await Promise.resolve().then(() => (init_computer_use(), computer_use_exports));
15475
+ console.log(chalk.bold("\nComputer use prerequisites:\n"));
15476
+ const cliclick = await isCliclickInstalled2();
15477
+ if (cliclick) {
15478
+ console.log(` ${chalk.green("\u2713")} cliclick installed`);
15479
+ } else {
15480
+ console.log(` ${chalk.red("\u2717")} cliclick not installed`);
15481
+ console.log(` ${chalk.dim("Install with: brew install cliclick")}`);
15482
+ }
15483
+ const acc = cliclick ? await hasAccessibilityPermissions2() : { ok: false, error: "cliclick not installed" };
15484
+ if (acc.ok) {
15485
+ console.log(` ${chalk.green("\u2713")} Accessibility permissions granted`);
15486
+ } else {
15487
+ console.log(` ${chalk.red("\u2717")} Accessibility permissions missing`);
15488
+ console.log(` ${chalk.dim(acc.error?.split("\n")[0] || "")}`);
15489
+ }
15490
+ const screen = await hasScreenRecordingPermissions2();
15491
+ if (screen) {
15492
+ console.log(` ${chalk.green("\u2713")} Screen Recording permissions granted`);
15493
+ } else {
15494
+ console.log(` ${chalk.red("\u2717")} Screen Recording permissions missing`);
15495
+ }
15496
+ const size = await detectScreenSize2();
15497
+ if (size) {
15498
+ console.log(` ${chalk.dim("\u2022")} Detected display: ${size.width}x${size.height} points`);
15499
+ }
15500
+ const allOk = cliclick && acc.ok && screen;
15501
+ console.log();
15502
+ if (allOk) {
15503
+ console.log(chalk.green("All checks passed. The agent can use computer use."));
15504
+ } else {
15505
+ console.log(chalk.yellow("Some prerequisites are missing."));
15506
+ console.log(chalk.dim("Run: sparkecoder request-permissions"));
15507
+ }
15508
+ console.log();
15509
+ process.exit(allOk ? 0 : 1);
15510
+ });
15511
+ program.command("request-permissions").description("Request macOS permissions for the computer use tool \u2014 triggers system prompts and opens System Settings").action(async () => {
15512
+ if (process.platform !== "darwin") {
15513
+ console.log(chalk.yellow(`Computer use is only supported on macOS. Current platform: ${process.platform}`));
15514
+ process.exit(0);
15515
+ }
15516
+ const {
15517
+ isCliclickInstalled: isCliclickInstalled2,
15518
+ hasAccessibilityPermissions: hasAccessibilityPermissions2,
15519
+ hasScreenRecordingPermissions: hasScreenRecordingPermissions2,
15520
+ requestAccessibilityPrompt: requestAccessibilityPrompt2,
15521
+ requestScreenRecordingPrompt: requestScreenRecordingPrompt2,
15522
+ openSystemSettings: openSystemSettings2,
15523
+ detectScreenSize: detectScreenSize2
15524
+ } = await Promise.resolve().then(() => (init_computer_use(), computer_use_exports));
15525
+ console.log(chalk.bold("\nRequesting computer use permissions...\n"));
15526
+ if (!await isCliclickInstalled2()) {
15527
+ console.log(` ${chalk.red("\u2717")} cliclick is not installed`);
15528
+ console.log(` ${chalk.dim("Run: brew install cliclick")}`);
15529
+ console.log(` ${chalk.dim("Then re-run: sparkecoder request-permissions")}`);
15530
+ console.log();
15531
+ process.exit(1);
15532
+ }
15533
+ const acc = await hasAccessibilityPermissions2();
15534
+ const screen = await hasScreenRecordingPermissions2();
15535
+ let needsRestart = false;
15536
+ if (!acc.ok) {
15537
+ console.log(` ${chalk.yellow("\u26A0")} Accessibility permission missing \u2014 triggering prompt and opening System Settings...`);
15538
+ await requestAccessibilityPrompt2();
15539
+ await openSystemSettings2("accessibility");
15540
+ console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Accessibility, click +")}`);
15541
+ console.log(` ${chalk.dim("2. Add the application running the agent (Terminal, iTerm, your IDE, or `node`)")}`);
15542
+ console.log(` ${chalk.dim("3. Toggle the switch ON")}`);
15543
+ needsRestart = true;
15544
+ } else {
15545
+ console.log(` ${chalk.green("\u2713")} Accessibility already granted`);
15546
+ }
15547
+ if (!screen) {
15548
+ console.log(` ${chalk.yellow("\u26A0")} Screen Recording permission missing \u2014 triggering prompt and opening System Settings...`);
15549
+ await requestScreenRecordingPrompt2();
15550
+ await openSystemSettings2("screen-recording");
15551
+ console.log(` ${chalk.dim("1. In Privacy & Security \u2192 Screen Recording, click +")}`);
15552
+ console.log(` ${chalk.dim("2. Add the application running the agent (Terminal, iTerm, your IDE, or `node`)")}`);
15553
+ console.log(` ${chalk.dim("3. Toggle the switch ON")}`);
15554
+ needsRestart = true;
15555
+ } else {
15556
+ console.log(` ${chalk.green("\u2713")} Screen Recording already granted`);
15557
+ }
15558
+ const size = await detectScreenSize2();
15559
+ if (size) {
15560
+ console.log(` ${chalk.dim("\u2022")} Detected display: ${size.width}x${size.height} points`);
15561
+ }
15562
+ console.log();
15563
+ if (needsRestart) {
15564
+ console.log(chalk.yellow("After granting permissions, RESTART the agent process for the new TCC entries to take effect."));
15565
+ console.log(chalk.dim("Then run: sparkecoder check-permissions"));
15566
+ } else {
15567
+ console.log(chalk.green("All permissions are already granted. Computer use is ready."));
15568
+ }
15569
+ console.log();
15570
+ });
13492
15571
  program.parse();
13493
15572
  //# sourceMappingURL=cli.js.map