sparkecoder 0.1.87 → 0.1.94

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 (125) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/index.d.ts +3 -3
  3. package/dist/agent/index.js +158 -68
  4. package/dist/agent/index.js.map +1 -1
  5. package/dist/cli.js +416 -108
  6. package/dist/cli.js.map +1 -1
  7. package/dist/db/index.d.ts +2 -2
  8. package/dist/{index-BvIissiB.d.ts → index-C7Kkn5vq.d.ts} +29 -29
  9. package/dist/index.d.ts +5 -5
  10. package/dist/index.js +272 -97
  11. package/dist/index.js.map +1 -1
  12. package/dist/{schema-CohdIL13.d.ts → schema-D7BJyHLl.d.ts} +3 -3
  13. package/dist/{search-CCffrVJE.d.ts → search-CVVfuBPZ.d.ts} +4 -4
  14. package/dist/server/index.js +272 -97
  15. package/dist/server/index.js.map +1 -1
  16. package/dist/tools/index.d.ts +31 -4
  17. package/dist/tools/index.js +38 -4
  18. package/dist/tools/index.js.map +1 -1
  19. package/package.json +5 -5
  20. package/web/.next/BUILD_ID +1 -1
  21. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  22. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  23. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  24. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  25. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  27. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  35. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  43. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  44. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +3 -3
  45. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +3 -3
  46. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +2 -2
  50. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  53. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  55. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +3 -3
  56. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +3 -3
  57. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +2 -2
  63. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  65. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +3 -3
  66. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +3 -3
  67. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  69. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +2 -2
  71. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +2 -2
  73. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  74. package/web/.next/standalone/web/.next/server/app/docs.rsc +3 -3
  75. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +3 -3
  76. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  78. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +2 -2
  80. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +2 -2
  81. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +1 -1
  82. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  83. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  84. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  85. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  86. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  87. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  89. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +1 -1
  91. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +1 -1
  92. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
  93. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +1 -1
  94. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  95. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  96. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  97. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  98. package/web/.next/{static/chunks/26eb5fab5216f3cc.js → standalone/web/.next/static/chunks/58fd0aaa2746b444.js} +1 -1
  99. package/web/.next/standalone/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
  100. package/web/.next/standalone/web/.next/static/chunks/{8d3efc76109d2efc.js → a888d448ceab1abe.js} +1 -1
  101. package/web/.next/standalone/web/.next/static/static/chunks/{26eb5fab5216f3cc.js → 58fd0aaa2746b444.js} +1 -1
  102. package/web/.next/standalone/web/.next/static/static/chunks/9fce2ce79c4c834e.js +1 -0
  103. package/web/.next/{static/chunks/8d3efc76109d2efc.js → standalone/web/.next/static/static/chunks/a888d448ceab1abe.js} +1 -1
  104. package/web/.next/standalone/web/package-lock.json +27 -27
  105. package/web/.next/standalone/web/package.json +1 -1
  106. package/web/.next/standalone/web/src/app/(main)/page.tsx +2 -2
  107. package/web/.next/standalone/web/src/app/api/config/route.ts +3 -2
  108. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +1 -1
  109. package/web/.next/standalone/web/src/lib/config.ts +2 -1
  110. package/web/.next/{standalone/web/.next/static/chunks/26eb5fab5216f3cc.js → static/chunks/58fd0aaa2746b444.js} +1 -1
  111. package/web/.next/static/chunks/9fce2ce79c4c834e.js +1 -0
  112. package/web/.next/{standalone/web/.next/static/static/chunks/8d3efc76109d2efc.js → static/chunks/a888d448ceab1abe.js} +1 -1
  113. package/web/package.json +1 -1
  114. package/web/.next/standalone/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
  115. package/web/.next/standalone/web/.next/static/static/chunks/b31b0765abe0c427.js +0 -1
  116. package/web/.next/static/chunks/b31b0765abe0c427.js +0 -1
  117. /package/web/.next/standalone/web/.next/static/{static/uUaN7Xe5kF_pP6zhfaeYi → glGjCSPew_3EV65tkHmVO}/_buildManifest.js +0 -0
  118. /package/web/.next/standalone/web/.next/static/{static/uUaN7Xe5kF_pP6zhfaeYi → glGjCSPew_3EV65tkHmVO}/_clientMiddlewareManifest.json +0 -0
  119. /package/web/.next/standalone/web/.next/static/{static/uUaN7Xe5kF_pP6zhfaeYi → glGjCSPew_3EV65tkHmVO}/_ssgManifest.js +0 -0
  120. /package/web/.next/standalone/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → static/glGjCSPew_3EV65tkHmVO}/_buildManifest.js +0 -0
  121. /package/web/.next/standalone/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → static/glGjCSPew_3EV65tkHmVO}/_clientMiddlewareManifest.json +0 -0
  122. /package/web/.next/standalone/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → static/glGjCSPew_3EV65tkHmVO}/_ssgManifest.js +0 -0
  123. /package/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → glGjCSPew_3EV65tkHmVO}/_buildManifest.js +0 -0
  124. /package/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → glGjCSPew_3EV65tkHmVO}/_clientMiddlewareManifest.json +0 -0
  125. /package/web/.next/static/{uUaN7Xe5kF_pP6zhfaeYi → glGjCSPew_3EV65tkHmVO}/_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;
@@ -598,7 +598,7 @@ var init_types = __esm({
598
598
  }).optional();
599
599
  SparkcoderConfigSchema = z.object({
600
600
  // Default model to use (Vercel AI Gateway format)
601
- defaultModel: z.string().default("anthropic/claude-opus-4-6"),
601
+ defaultModel: z.string().default("anthropic/claude-opus-4.7"),
602
602
  // Working directory for file operations
603
603
  workingDirectory: z.string().optional(),
604
604
  // Tool approval settings
@@ -872,7 +872,7 @@ function requiresApproval(toolName, sessionConfig) {
872
872
  }
873
873
  function createDefaultConfig() {
874
874
  return {
875
- defaultModel: "anthropic/claude-opus-4-6",
875
+ defaultModel: "anthropic/claude-opus-4.7",
876
876
  // workingDirectory is intentionally not set - defaults to where CLI is run
877
877
  toolApprovals: {
878
878
  bash: true,
@@ -984,6 +984,14 @@ function loadApiKeysIntoEnv() {
984
984
  }
985
985
  }
986
986
  }
987
+ function isRemoteInferenceConfigured() {
988
+ try {
989
+ const config = getConfig();
990
+ return config.resolvedRemoteServer.isConfigured;
991
+ } catch {
992
+ return false;
993
+ }
994
+ }
987
995
  function setApiKey(provider, apiKey) {
988
996
  const normalizedProvider = provider.toLowerCase();
989
997
  const envVar = PROVIDER_ENV_MAP[normalizedProvider];
@@ -1033,11 +1041,11 @@ function getApiKeyStatus() {
1033
1041
  };
1034
1042
  });
1035
1043
  }
1036
- function maskApiKey(key) {
1037
- if (key.length <= 12) {
1038
- return "****" + key.slice(-4);
1044
+ function maskApiKey(key2) {
1045
+ if (key2.length <= 12) {
1046
+ return "****" + key2.slice(-4);
1039
1047
  }
1040
- return key.slice(0, 4) + "..." + key.slice(-4);
1048
+ return key2.slice(0, 4) + "..." + key2.slice(-4);
1041
1049
  }
1042
1050
  var CONFIG_FILE_NAMES, cachedConfig, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
1043
1051
  var init_config = __esm({
@@ -1491,10 +1499,10 @@ function parseSkillFrontmatter(content) {
1491
1499
  }
1492
1500
  const colonIndex = line.indexOf(":");
1493
1501
  if (colonIndex > 0) {
1494
- const key = line.slice(0, colonIndex).trim();
1502
+ const key2 = line.slice(0, colonIndex).trim();
1495
1503
  let value = line.slice(colonIndex + 1).trim();
1496
1504
  if (value === "" || value === "[]") {
1497
- currentArrayKey = key;
1505
+ currentArrayKey = key2;
1498
1506
  currentArray = [];
1499
1507
  continue;
1500
1508
  }
@@ -1507,18 +1515,18 @@ function parseSkillFrontmatter(content) {
1507
1515
  }
1508
1516
  return trimmed;
1509
1517
  }).filter((item) => item.length > 0);
1510
- data[key] = items;
1518
+ data[key2] = items;
1511
1519
  continue;
1512
1520
  }
1513
1521
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1514
1522
  value = value.slice(1, -1);
1515
1523
  }
1516
1524
  if (value === "true") {
1517
- data[key] = true;
1525
+ data[key2] = true;
1518
1526
  } else if (value === "false") {
1519
- data[key] = false;
1527
+ data[key2] = false;
1520
1528
  } else {
1521
- data[key] = value;
1529
+ data[key2] = value;
1522
1530
  }
1523
1531
  }
1524
1532
  }
@@ -2256,9 +2264,9 @@ var init_chunker = __esm({
2256
2264
  });
2257
2265
 
2258
2266
  // src/semantic/client.ts
2259
- function initVectorClient(serverUrl, key) {
2267
+ function initVectorClient(serverUrl, key2) {
2260
2268
  remoteServerUrl2 = serverUrl.replace(/\/$/, "");
2261
- authKey2 = key;
2269
+ authKey2 = key2;
2262
2270
  }
2263
2271
  function isVectorClientConfigured() {
2264
2272
  return !!remoteServerUrl2 && !!authKey2;
@@ -2948,7 +2956,7 @@ import { promisify as promisify5 } from "util";
2948
2956
  import { mkdirSync as mkdirSync5, existsSync as existsSync15, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
2949
2957
  import { join as join8 } from "path";
2950
2958
  import { tmpdir } from "os";
2951
- import { nanoid as nanoid3 } from "nanoid";
2959
+ import { nanoid as nanoid4 } from "nanoid";
2952
2960
  function isMacOs() {
2953
2961
  return process.platform === "darwin";
2954
2962
  }
@@ -3069,9 +3077,9 @@ async function runScroll(dx, dy) {
3069
3077
  { timeout: 5e3 }
3070
3078
  );
3071
3079
  }
3072
- function translateKeyForCliclick(key) {
3073
- if (!key) return [];
3074
- const parts = key.split("+").map((p) => p.trim()).filter(Boolean);
3080
+ function translateKeyForCliclick(key2) {
3081
+ if (!key2) return [];
3082
+ const parts = key2.split("+").map((p) => p.trim()).filter(Boolean);
3075
3083
  if (parts.length === 0) return [];
3076
3084
  const modMap = {
3077
3085
  ctrl: "ctrl",
@@ -3167,7 +3175,7 @@ function createComputerUseTool(options) {
3167
3175
  try {
3168
3176
  switch (input.action) {
3169
3177
  case "screenshot": {
3170
- const path = join8(tmpdir(), `cu-${nanoid3(8)}.png`);
3178
+ const path = join8(tmpdir(), `cu-${nanoid4(8)}.png`);
3171
3179
  await runScreencapture(path);
3172
3180
  const resized = await resizeScreenshotToPoints(path, displayWidth, displayHeight);
3173
3181
  try {
@@ -3298,7 +3306,7 @@ function createComputerUseTool(options) {
3298
3306
  case "zoom": {
3299
3307
  const region = input.region ?? [0, 0, displayWidth, displayHeight];
3300
3308
  const [x1, y1, x2, y2] = region;
3301
- const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid3(8)}.png`);
3309
+ const tmpPath = join8(tmpdir(), `cu-zoom-${nanoid4(8)}.png`);
3302
3310
  await runScreencapture(tmpPath);
3303
3311
  const sharpModule = await import("sharp");
3304
3312
  const sharp2 = sharpModule.default || sharpModule;
@@ -3620,7 +3628,7 @@ import { promisify as promisify6 } from "util";
3620
3628
  import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
3621
3629
  import { join as join9 } from "path";
3622
3630
  import { tmpdir as tmpdir2 } from "os";
3623
- import { nanoid as nanoid4 } from "nanoid";
3631
+ import { nanoid as nanoid5 } from "nanoid";
3624
3632
  async function checkFfmpeg() {
3625
3633
  try {
3626
3634
  await execAsync6("ffmpeg -version", { timeout: 5e3 });
@@ -3675,7 +3683,7 @@ var init_recorder = __esm({
3675
3683
  */
3676
3684
  async encode() {
3677
3685
  if (this.frames.length === 0) return null;
3678
- const workDir = join9(tmpdir2(), `sparkecoder-recording-${nanoid4(8)}`);
3686
+ const workDir = join9(tmpdir2(), `sparkecoder-recording-${nanoid5(8)}`);
3679
3687
  await mkdir4(workDir, { recursive: true });
3680
3688
  try {
3681
3689
  for (let i = 0; i < this.frames.length; i++) {
@@ -4023,9 +4031,9 @@ function startPersonalAgent(cfg) {
4023
4031
  console.log(`${cfg.dashboardUrl.replace(/\/$/, "")}/admin/devices`);
4024
4032
  console.log("");
4025
4033
  const warned = /* @__PURE__ */ new Set();
4026
- function warnOnce(key, msg) {
4027
- if (warned.has(key)) return;
4028
- warned.add(key);
4034
+ function warnOnce(key2, msg) {
4035
+ if (warned.has(key2)) return;
4036
+ warned.add(key2);
4029
4037
  console.warn(msg);
4030
4038
  }
4031
4039
  async function tick() {
@@ -4109,6 +4117,9 @@ import chalk from "chalk";
4109
4117
  import ora from "ora";
4110
4118
  import "dotenv/config";
4111
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";
4112
4123
 
4113
4124
  // src/server/index.ts
4114
4125
  import "dotenv/config";
@@ -4130,7 +4141,7 @@ import { z as z16 } from "zod";
4130
4141
  import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
4131
4142
  import { readdir as readdir6 } from "fs/promises";
4132
4143
  import { join as join10, basename as basename5, extname as extname8, relative as relative9 } from "path";
4133
- import { nanoid as nanoid6 } from "nanoid";
4144
+ import { nanoid as nanoid7 } from "nanoid";
4134
4145
 
4135
4146
  // src/agent/index.ts
4136
4147
  import {
@@ -4305,6 +4316,23 @@ function isAnthropicModel(modelId) {
4305
4316
  const normalized = modelId.trim().toLowerCase();
4306
4317
  return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
4307
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
+ }
4308
4336
  function resolveModel(modelId) {
4309
4337
  try {
4310
4338
  const config = getConfig();
@@ -4328,7 +4356,7 @@ var SUBAGENT_MODELS = {
4328
4356
  init_db();
4329
4357
  init_config();
4330
4358
  import { z as z15 } from "zod";
4331
- import { nanoid as nanoid5 } from "nanoid";
4359
+ import { nanoid as nanoid6 } from "nanoid";
4332
4360
 
4333
4361
  // src/tools/bash.ts
4334
4362
  import { tool } from "ai";
@@ -4661,11 +4689,11 @@ async function sendInput(terminalId, input, options = {}) {
4661
4689
  return false;
4662
4690
  }
4663
4691
  }
4664
- async function sendKey(terminalId, key) {
4692
+ async function sendKey(terminalId, key2) {
4665
4693
  const session = getSessionName(terminalId);
4666
4694
  try {
4667
4695
  await execAsync(`tmux has-session -t ${session}`, { timeout: 1e3 });
4668
- await execAsync(`tmux send-keys -t ${session} ${key}`, { timeout: 1e3 });
4696
+ await execAsync(`tmux send-keys -t ${session} ${key2}`, { timeout: 1e3 });
4669
4697
  return true;
4670
4698
  } catch {
4671
4699
  return false;
@@ -4804,7 +4832,7 @@ bash({ id: "abc123", input: "my text" }) // send text input
4804
4832
  Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.`,
4805
4833
  inputSchema: bashInputSchema,
4806
4834
  execute: async (inputArgs) => {
4807
- const { command, background, id, kill, tail, input: textInput, key } = inputArgs;
4835
+ const { command, background, id, kill, tail, input: textInput, key: key2 } = inputArgs;
4808
4836
  if (id) {
4809
4837
  if (kill) {
4810
4838
  const success = await killTerminal(id);
@@ -4835,8 +4863,8 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
4835
4863
  message: `Sent input "${textInput}" to terminal`
4836
4864
  };
4837
4865
  }
4838
- if (key) {
4839
- const success = await sendKey(id, key);
4866
+ if (key2) {
4867
+ const success = await sendKey(id, key2);
4840
4868
  if (!success) {
4841
4869
  return {
4842
4870
  success: false,
@@ -4852,7 +4880,7 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
4852
4880
  id,
4853
4881
  output: truncatedOutput2,
4854
4882
  status: status2,
4855
- message: `Sent key "${key}" to terminal`
4883
+ message: `Sent key "${key2}" to terminal`
4856
4884
  };
4857
4885
  }
4858
4886
  const { output, status } = await getLogs(id, options.workingDirectory, { tail, sessionId: options.sessionId });
@@ -4994,13 +5022,13 @@ async function resizeImageIfNeeded(buffer, mediaType) {
4994
5022
  const needsResize = longEdge > MAX_LONG_EDGE;
4995
5023
  const needsShrink = buffer.length > MAX_FILE_BYTES;
4996
5024
  if (!needsResize && !needsShrink) return { buffer, mediaType: inputMediaType };
4997
- const key = cacheKey(buffer);
5025
+ const key2 = cacheKey(buffer);
4998
5026
  const cacheDir = getCacheDir();
4999
5027
  const isPng = inputMediaType.includes("png");
5000
5028
  const willConvertToJpeg = isPng && (needsShrink || buffer.length > 2 * 1024 * 1024);
5001
5029
  const outputMediaType = willConvertToJpeg || !isPng ? "image/jpeg" : "image/png";
5002
5030
  const ext = outputMediaType === "image/png" ? ".png" : ".jpg";
5003
- const cachePath = join3(cacheDir, key + ext);
5031
+ const cachePath = join3(cacheDir, key2 + ext);
5004
5032
  if (existsSync3(cachePath)) {
5005
5033
  console.log(`[image-resize] Cache hit for ${width}x${height} image`);
5006
5034
  return { buffer: readFileSync2(cachePath), mediaType: outputMediaType };
@@ -5891,31 +5919,31 @@ async function getClientForFile(filePath) {
5891
5919
  return null;
5892
5920
  }
5893
5921
  const root = dirname4(normalized);
5894
- const key = `${serverDef.id}:${root}`;
5895
- const existing = state.clients.get(key);
5922
+ const key2 = `${serverDef.id}:${root}`;
5923
+ const existing = state.clients.get(key2);
5896
5924
  if (existing) {
5897
5925
  return existing;
5898
5926
  }
5899
- if (state.broken.has(key)) {
5927
+ if (state.broken.has(key2)) {
5900
5928
  return null;
5901
5929
  }
5902
5930
  try {
5903
5931
  const handle = await serverDef.spawn(root);
5904
5932
  if (!handle) {
5905
- state.broken.add(key);
5933
+ state.broken.add(key2);
5906
5934
  return null;
5907
5935
  }
5908
5936
  console.log(`[lsp] Started ${serverDef.name} for ${root}`);
5909
5937
  const client = await createClient(serverDef.id, handle, root);
5910
- state.clients.set(key, client);
5938
+ state.clients.set(key2, client);
5911
5939
  handle.process.on("exit", (code) => {
5912
5940
  console.log(`[lsp] ${serverDef.name} exited with code ${code}`);
5913
- state.clients.delete(key);
5941
+ state.clients.delete(key2);
5914
5942
  });
5915
5943
  return client;
5916
5944
  } catch (error) {
5917
5945
  console.error(`[lsp] Failed to start ${serverDef.name}:`, error);
5918
- state.broken.add(key);
5946
+ state.broken.add(key2);
5919
5947
  return null;
5920
5948
  }
5921
5949
  }
@@ -7647,6 +7675,7 @@ init_semantic_search();
7647
7675
  import { tool as tool11 } from "ai";
7648
7676
  import { z as z12 } from "zod";
7649
7677
  import Ajv from "ajv";
7678
+ import { nanoid as nanoid3 } from "nanoid";
7650
7679
  var ajv = new Ajv({ allErrors: true });
7651
7680
  function createCompleteTaskTool(options) {
7652
7681
  const validate = ajv.compile(options.outputSchema);
@@ -7693,6 +7722,37 @@ function createTaskFailedTool(options) {
7693
7722
  }
7694
7723
  });
7695
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
+ }
7696
7756
 
7697
7757
  // src/tools/upload-file.ts
7698
7758
  import { tool as tool12 } from "ai";
@@ -8027,6 +8087,7 @@ async function createTools(options) {
8027
8087
  if (options.taskTools) {
8028
8088
  tools.complete_task = createCompleteTaskTool(options.taskTools);
8029
8089
  tools.task_failed = createTaskFailedTool(options.taskTools);
8090
+ tools.ask_question_to_user = createAskQuestionToUserTool(options.taskTools);
8030
8091
  }
8031
8092
  return tools;
8032
8093
  }
@@ -8434,9 +8495,10 @@ If you need to give the user a downloadable file (report, image, export, etc.),
8434
8495
  ### Rules
8435
8496
  1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
8436
8497
  2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
8437
- 3. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
8438
- 4. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
8439
- 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.
8440
8502
 
8441
8503
  ### Verification \u2014 BE EXTREMELY THOROUGH
8442
8504
  Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
@@ -8507,6 +8569,7 @@ ${JSON.stringify(outputSchema, null, 2)}
8507
8569
  ### Completion Tools
8508
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.
8509
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.
8510
8573
  `;
8511
8574
  }
8512
8575
  function createSummaryPrompt(conversationHistory) {
@@ -8662,6 +8725,7 @@ function sanitizeModelMessages(messages) {
8662
8725
 
8663
8726
  // src/agent/model-limits.ts
8664
8727
  var MODEL_LIMITS = {
8728
+ "anthropic/claude-opus-4.7": { contextWindow: 2e5, rollingTarget: 15e4 },
8665
8729
  "anthropic/claude-opus-4-6": { contextWindow: 2e5, rollingTarget: 15e4 },
8666
8730
  "anthropic/claude-sonnet-4": { contextWindow: 2e5, rollingTarget: 15e4 },
8667
8731
  "anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
@@ -8998,17 +9062,65 @@ function repairToolPairing(messages) {
8998
9062
 
8999
9063
  // src/agent/index.ts
9000
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
9001
9113
  var MAX_SSE_FIELD_LENGTH = 8 * 1024;
9002
9114
  var SSE_PREVIEW_LENGTH = 2 * 1024;
9003
9115
  function truncateWriteFileInput(input) {
9004
9116
  const out = { ...input };
9005
- for (const key of ["content", "old_string", "new_string"]) {
9006
- const val = out[key];
9117
+ for (const key2 of ["content", "old_string", "new_string"]) {
9118
+ const val = out[key2];
9007
9119
  if (typeof val === "string" && val.length > MAX_SSE_FIELD_LENGTH) {
9008
- out[key] = `${val.slice(0, SSE_PREVIEW_LENGTH)}
9120
+ out[key2] = `${val.slice(0, SSE_PREVIEW_LENGTH)}
9009
9121
  ... (truncated)`;
9010
- out[`${key}Truncated`] = true;
9011
- out[`${key}Length`] = val.length;
9122
+ out[`${key2}Truncated`] = true;
9123
+ out[`${key2}Length`] = val.length;
9012
9124
  }
9013
9125
  }
9014
9126
  return out;
@@ -9178,13 +9290,7 @@ ${prompt}` });
9178
9290
  abortSignal: options.abortSignal,
9179
9291
  // Enable extended thinking/reasoning for models that support it
9180
9292
  providerOptions: useAnthropic ? {
9181
- anthropic: {
9182
- toolStreaming: true,
9183
- thinking: {
9184
- type: "enabled",
9185
- budgetTokens: 1e4
9186
- }
9187
- }
9293
+ anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9188
9294
  } : void 0,
9189
9295
  onStepFinish: async (step) => {
9190
9296
  options.onStepFinish?.(step);
@@ -9231,12 +9337,7 @@ ${prompt}` });
9231
9337
  stopWhen: stepCountIs2(500),
9232
9338
  // Enable extended thinking/reasoning for models that support it
9233
9339
  providerOptions: useAnthropic ? {
9234
- anthropic: {
9235
- thinking: {
9236
- type: "enabled",
9237
- budgetTokens: 1e4
9238
- }
9239
- }
9340
+ anthropic: getAnthropicProviderOptions(this.session.model)
9240
9341
  } : void 0
9241
9342
  });
9242
9343
  const responseMessages = result.response.messages;
@@ -9323,7 +9424,38 @@ ${prompt}` });
9323
9424
  },
9324
9425
  taskTools: {
9325
9426
  outputSchema: options.taskConfig.outputSchema,
9326
- 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
+ }
9327
9459
  }
9328
9460
  });
9329
9461
  const baseSystemPrompt = await buildSystemPrompt({
@@ -9368,10 +9500,7 @@ ${taskAddendum}`;
9368
9500
  stopWhen: stepCountIs2(500),
9369
9501
  abortSignal: options.abortSignal,
9370
9502
  providerOptions: useAnthropic ? {
9371
- anthropic: {
9372
- toolStreaming: true,
9373
- thinking: { type: "enabled", budgetTokens: 1e4 }
9374
- }
9503
+ anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
9375
9504
  } : void 0,
9376
9505
  onStepFinish: async (step) => {
9377
9506
  options.onStepFinish?.(step);
@@ -9658,7 +9787,7 @@ ${taskAddendum}`;
9658
9787
  description: originalTool.description || "",
9659
9788
  inputSchema: originalTool.inputSchema || z15.object({}),
9660
9789
  execute: async (input, toolOptions) => {
9661
- const toolCallId = toolOptions.toolCallId || nanoid5();
9790
+ const toolCallId = toolOptions.toolCallId || nanoid6();
9662
9791
  const execution = toolExecutionQueries.create({
9663
9792
  sessionId: this.session.id,
9664
9793
  toolName: name,
@@ -10308,7 +10437,7 @@ sessions.post("/:id/attachments", async (c) => {
10308
10437
  return c.json({ error: "No file provided" }, 400);
10309
10438
  }
10310
10439
  const dir = ensureAttachmentsDir(sessionId);
10311
- const id = nanoid6(10);
10440
+ const id = nanoid7(10);
10312
10441
  const ext = extname8(file.name) || "";
10313
10442
  const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
10314
10443
  const filePath = join10(dir, safeFilename);
@@ -10334,7 +10463,7 @@ sessions.post("/:id/attachments", async (c) => {
10334
10463
  return c.json({ error: "Missing filename or data" }, 400);
10335
10464
  }
10336
10465
  const dir = ensureAttachmentsDir(sessionId);
10337
- const id = nanoid6(10);
10466
+ const id = nanoid7(10);
10338
10467
  const ext = extname8(body.filename) || "";
10339
10468
  const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
10340
10469
  const filePath = join10(dir, safeFilename);
@@ -10626,9 +10755,9 @@ var store = /* @__PURE__ */ new Map();
10626
10755
  var channels = /* @__PURE__ */ new Map();
10627
10756
  var cleanupInterval = setInterval(() => {
10628
10757
  const now = Date.now();
10629
- for (const [key, data] of store.entries()) {
10758
+ for (const [key2, data] of store.entries()) {
10630
10759
  if (data.expiresAt && data.expiresAt < now) {
10631
- store.delete(key);
10760
+ store.delete(key2);
10632
10761
  }
10633
10762
  }
10634
10763
  }, 6e4);
@@ -10652,27 +10781,27 @@ var publisher = {
10652
10781
  }
10653
10782
  }
10654
10783
  },
10655
- set: async (key, value, options) => {
10784
+ set: async (key2, value, options) => {
10656
10785
  const expiresAt = options?.EX ? Date.now() + options.EX * 1e3 : void 0;
10657
- store.set(key, { value, expiresAt });
10786
+ store.set(key2, { value, expiresAt });
10658
10787
  if (options?.EX) {
10659
- setTimeout(() => store.delete(key), options.EX * 1e3);
10788
+ setTimeout(() => store.delete(key2), options.EX * 1e3);
10660
10789
  }
10661
10790
  },
10662
- get: async (key) => {
10663
- const data = store.get(key);
10791
+ get: async (key2) => {
10792
+ const data = store.get(key2);
10664
10793
  if (!data) return null;
10665
10794
  if (data.expiresAt && data.expiresAt < Date.now()) {
10666
- store.delete(key);
10795
+ store.delete(key2);
10667
10796
  return null;
10668
10797
  }
10669
10798
  return data.value;
10670
10799
  },
10671
- incr: async (key) => {
10672
- const data = store.get(key);
10800
+ incr: async (key2) => {
10801
+ const data = store.get(key2);
10673
10802
  const current = data ? parseInt(data.value, 10) : 0;
10674
10803
  const next = (isNaN(current) ? 0 : current) + 1;
10675
- store.set(key, { value: String(next), expiresAt: data?.expiresAt });
10804
+ store.set(key2, { value: String(next), expiresAt: data?.expiresAt });
10676
10805
  return next;
10677
10806
  }
10678
10807
  };
@@ -10704,7 +10833,7 @@ var streamContext = createResumableStreamContext({
10704
10833
  });
10705
10834
 
10706
10835
  // src/server/routes/agents.ts
10707
- import { nanoid as nanoid7 } from "nanoid";
10836
+ import { nanoid as nanoid8 } from "nanoid";
10708
10837
  init_stream_proxy();
10709
10838
  init_recorder();
10710
10839
  init_remote();
@@ -11263,7 +11392,7 @@ ${prompt}` });
11263
11392
  userMessageContent = prompt;
11264
11393
  }
11265
11394
  await messageQueries.create(id, { role: "user", content: userMessageContent });
11266
- const streamId = `stream_${id}_${nanoid7(10)}`;
11395
+ const streamId = `stream_${id}_${nanoid8(10)}`;
11267
11396
  console.log(`[STREAM] Creating stream ${streamId} for session ${id}`);
11268
11397
  await activeStreamQueries.create(id, streamId);
11269
11398
  const stream = await streamContext.resumableStream(
@@ -11468,7 +11597,7 @@ agents.post(
11468
11597
  });
11469
11598
  const session = agent.getSession();
11470
11599
  const enrichedPrompt = enrichPromptWithDevtoolsContext(session.id, body.prompt);
11471
- const streamId = `stream_${session.id}_${nanoid7(10)}`;
11600
+ const streamId = `stream_${session.id}_${nanoid8(10)}`;
11472
11601
  await createCheckpoint(session.id, session.workingDirectory, 0);
11473
11602
  await activeStreamQueries.create(session.id, streamId);
11474
11603
  const createQuickStreamProducer = () => {
@@ -11821,7 +11950,8 @@ health.get("/", async (c) => {
11821
11950
  const config = getConfig();
11822
11951
  const apiKeyStatus = getApiKeyStatus();
11823
11952
  const gatewayKey = apiKeyStatus.find((s) => s.provider === "ai-gateway");
11824
- const hasApiKey = gatewayKey?.configured ?? false;
11953
+ const remoteInference = isRemoteInferenceConfigured();
11954
+ const hasApiKey = remoteInference || (gatewayKey?.configured ?? false);
11825
11955
  let hwid;
11826
11956
  try {
11827
11957
  hwid = getHardwareIdCached();
@@ -11832,6 +11962,7 @@ health.get("/", async (c) => {
11832
11962
  version: currentVersion,
11833
11963
  uptime: process.uptime(),
11834
11964
  apiKeyConfigured: hasApiKey,
11965
+ inferenceMode: remoteInference ? "remote" : "local",
11835
11966
  hwid,
11836
11967
  config: {
11837
11968
  workingDirectory: config.resolvedWorkingDirectory,
@@ -12258,7 +12389,7 @@ init_db();
12258
12389
  import { Hono as Hono5 } from "hono";
12259
12390
  import { zValidator as zValidator5 } from "@hono/zod-validator";
12260
12391
  import { z as z20 } from "zod";
12261
- import { nanoid as nanoid8 } from "nanoid";
12392
+ import { nanoid as nanoid9 } from "nanoid";
12262
12393
  init_config();
12263
12394
  var tasks = new Hono5();
12264
12395
  var taskAbortControllers = /* @__PURE__ */ new Map();
@@ -12332,7 +12463,7 @@ tasks.post(
12332
12463
  const taskId = agent.sessionId;
12333
12464
  const abortController = new AbortController();
12334
12465
  taskAbortControllers.set(taskId, abortController);
12335
- const streamId = `stream_${taskId}_${nanoid8(10)}`;
12466
+ const streamId = `stream_${taskId}_${nanoid9(10)}`;
12336
12467
  await activeStreamQueries.create(taskId, streamId);
12337
12468
  const taskStreamProducer = () => {
12338
12469
  const { readable, writable } = new TransformStream();
@@ -12459,6 +12590,7 @@ tasks.post("/:id/cancel", async (c) => {
12459
12590
  abortController.abort();
12460
12591
  taskAbortControllers.delete(id);
12461
12592
  }
12593
+ rejectTaskQuestions(id, "Task cancelled by user");
12462
12594
  const cancelledTask = {
12463
12595
  ...task,
12464
12596
  status: "failed",
@@ -12480,6 +12612,52 @@ tasks.post("/:id/cancel", async (c) => {
12480
12612
  }
12481
12613
  return c.json({ taskId: id, status: "failed", error: "Task cancelled by user" });
12482
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
+ );
12483
12661
  var tasks_default = tasks;
12484
12662
 
12485
12663
  // src/server/routes/system.ts
@@ -12575,15 +12753,15 @@ function loadPublicKey(input) {
12575
12753
  if (!input.includes("BEGIN") && existsSync18(input)) {
12576
12754
  pem = readFileSync11(input, "utf8");
12577
12755
  }
12578
- const key = createPublicKey({ key: pem, format: "pem" });
12579
- if (key.asymmetricKeyType !== "ed25519") {
12756
+ const key2 = createPublicKey({ key: pem, format: "pem" });
12757
+ if (key2.asymmetricKeyType !== "ed25519") {
12580
12758
  throw new Error(
12581
- `expected an ed25519 public key, got ${key.asymmetricKeyType}. Generate with personal-agents/scripts/generate-signing-keys.mjs.`
12759
+ `expected an ed25519 public key, got ${key2.asymmetricKeyType}. Generate with personal-agents/scripts/generate-signing-keys.mjs.`
12582
12760
  );
12583
12761
  }
12584
- _cachedKey = key;
12762
+ _cachedKey = key2;
12585
12763
  _cachedFromInput = input;
12586
- return key;
12764
+ return key2;
12587
12765
  }
12588
12766
  function bodyHashB64(body) {
12589
12767
  const hash = createHash3("sha256");
@@ -14097,8 +14275,30 @@ function generateOpenAPISpec() {
14097
14275
  init_config();
14098
14276
  init_semantic();
14099
14277
  init_db();
14100
- import { writeFileSync as writeFileSync6, readFileSync as readFileSync12, existsSync as existsSync20 } from "fs";
14278
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6, readFileSync as readFileSync12, existsSync as existsSync20, chmodSync } from "fs";
14101
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
+ }
14102
14302
  async function apiRequest(baseUrl, path, options = {}) {
14103
14303
  const url = `${baseUrl}${path}`;
14104
14304
  const init = {
@@ -14661,7 +14861,7 @@ Unexpected error: ${outerError.message}`));
14661
14861
  }
14662
14862
  }
14663
14863
  var program = new Command();
14664
- 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)").option("--enable-computer-use", "Enable the Anthropic computer use tool for all new sessions by default (macOS only)").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) => {
14665
14865
  if (options.enableComputerUse) {
14666
14866
  process.env.SPARKECODER_COMPUTER_USE = "1";
14667
14867
  console.log(chalk.cyan("\u2192 Computer use enabled by default for new sessions."));
@@ -14842,6 +15042,114 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
14842
15042
  console.log(chalk.dim(` ${configPath}`));
14843
15043
  console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
14844
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
+ });
14845
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) => {
14846
15154
  const baseUrl = `http://${options.host}:${options.port}`;
14847
15155
  try {
@@ -15089,10 +15397,10 @@ program.command("search").description("Search indexed repository using semantic
15089
15397
  process.exit(1);
15090
15398
  }
15091
15399
  });
15092
- 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) => {
15093
15401
  try {
15094
15402
  ensureAppDataDirectory();
15095
- if (options.list || !provider && !key) {
15403
+ if (options.list || !provider && !key2) {
15096
15404
  console.log(chalk.bold("\nAPI Key Status:\n"));
15097
15405
  const status = getApiKeyStatus();
15098
15406
  for (const s of status) {
@@ -15112,8 +15420,8 @@ program.command("api-key").description("Manage API keys for AI providers").argum
15112
15420
  console.log(chalk.dim(`Usage: sparkecoder api-key <provider> <key>`));
15113
15421
  return;
15114
15422
  }
15115
- if (provider && key) {
15116
- setApiKey(provider, key);
15423
+ if (provider && key2) {
15424
+ setApiKey(provider, key2);
15117
15425
  const status = getApiKeyStatus();
15118
15426
  const providerStatus = status.find((s) => s.provider === provider.toLowerCase());
15119
15427
  console.log(chalk.green(`
@@ -15125,7 +15433,7 @@ program.command("api-key").description("Manage API keys for AI providers").argum
15125
15433
  The key is stored securely and will be loaded automatically on startup.`));
15126
15434
  return;
15127
15435
  }
15128
- if (provider && !key) {
15436
+ if (provider && !key2) {
15129
15437
  const status = getApiKeyStatus();
15130
15438
  const providerStatus = status.find((s) => s.provider === provider.toLowerCase());
15131
15439
  if (!providerStatus) {