sparkecoder 0.1.123 → 0.1.125

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 (142) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +145 -71
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +265 -131
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/{index-Bcz0aCAR.d.ts → index-DczYH89U.d.ts} +104 -104
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.js +188 -89
  10. package/dist/index.js.map +1 -1
  11. package/dist/{schema-BWbWmfDQ.d.ts → schema-DxrKyetI.d.ts} +3 -3
  12. package/dist/{search-DOzC4ojH.d.ts → search-CVVfuBPZ.d.ts} +4 -4
  13. package/dist/server/index.js +188 -89
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/skills/default/memory.md +49 -0
  16. package/dist/skills/default/recording.md +2 -2
  17. package/dist/skills/default/skill-authoring.md +96 -0
  18. package/dist/tools/index.d.ts +3 -3
  19. package/dist/tools/index.js +12 -4
  20. package/dist/tools/index.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/skills/default/memory.md +49 -0
  23. package/src/skills/default/recording.md +2 -2
  24. package/src/skills/default/skill-authoring.md +96 -0
  25. package/web/.next/BUILD_ID +1 -1
  26. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  27. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  28. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  29. package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +1 -1
  30. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  31. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  32. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  33. package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
  34. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  35. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
  50. package/web/.next/standalone/web/.next/server/app/agents.rsc +3 -3
  51. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +2 -2
  52. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +2 -2
  54. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +3 -3
  55. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  59. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  62. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  68. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  70. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  77. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  78. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  81. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  82. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  83. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  84. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  86. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  87. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  91. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  92. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  93. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  94. package/web/.next/standalone/web/.next/server/app/index.rsc +3 -3
  95. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  96. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  97. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +3 -3
  98. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  99. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  100. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  101. package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
  102. package/web/.next/standalone/web/.next/server/app/settings.rsc +3 -3
  103. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
  104. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
  105. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +2 -2
  106. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  107. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  108. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  109. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  110. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c87abaf4._.js → 2374f_12d55e68._.js} +1 -1
  111. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1f3f2d00._.js → 2374f_1c0639c2._.js} +1 -1
  112. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_a0d5caeb._.js → 2374f_28cd6777._.js} +1 -1
  113. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_570c34dc._.js → 2374f_5f47a9b7._.js} +1 -1
  114. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d8122230._.js → 2374f_aa218457._.js} +1 -1
  115. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9c560f3a._.js → 2374f_f678a96f._.js} +1 -1
  116. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_38945fd9._.js → 2374f_fac4000d._.js} +1 -1
  117. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__4de426bd._.js → [root-of-the-server]__e5911ea8._.js} +4 -4
  118. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_62ca4286._.js → web_2966b3a3._.js} +2 -2
  119. package/web/.next/standalone/web/.next/server/chunks/ssr/web_4fe3c244._.js +1 -1
  120. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  121. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  122. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  123. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  124. package/web/.next/standalone/web/.next/static/chunks/{91988e253d5fa420.js → 4d95c15f712c9e06.js} +5 -5
  125. package/web/.next/standalone/web/.next/static/chunks/780c93257fac7d43.js +1 -0
  126. package/web/.next/standalone/web/.next/static/static/chunks/{91988e253d5fa420.js → 4d95c15f712c9e06.js} +5 -5
  127. package/web/.next/standalone/web/.next/static/static/chunks/780c93257fac7d43.js +1 -0
  128. package/web/.next/standalone/web/src/components/chat-interface.tsx +112 -1
  129. package/web/.next/static/chunks/{91988e253d5fa420.js → 4d95c15f712c9e06.js} +5 -5
  130. package/web/.next/static/chunks/780c93257fac7d43.js +1 -0
  131. package/web/.next/standalone/web/.next/static/chunks/f0f19357f3fb7cf8.js +0 -1
  132. package/web/.next/standalone/web/.next/static/static/chunks/f0f19357f3fb7cf8.js +0 -1
  133. package/web/.next/static/chunks/f0f19357f3fb7cf8.js +0 -1
  134. /package/web/.next/standalone/web/.next/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_buildManifest.js +0 -0
  135. /package/web/.next/standalone/web/.next/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_clientMiddlewareManifest.json +0 -0
  136. /package/web/.next/standalone/web/.next/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_ssgManifest.js +0 -0
  137. /package/web/.next/standalone/web/.next/static/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_buildManifest.js +0 -0
  138. /package/web/.next/standalone/web/.next/static/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_clientMiddlewareManifest.json +0 -0
  139. /package/web/.next/standalone/web/.next/static/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_ssgManifest.js +0 -0
  140. /package/web/.next/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_buildManifest.js +0 -0
  141. /package/web/.next/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_clientMiddlewareManifest.json +0 -0
  142. /package/web/.next/static/{MP4p8_EldjbZ69dONoEcM → qtMOCCjmqN22PUb49g4j-}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -706,7 +706,10 @@ var init_types = __esm({
706
706
  // not listed here. Values match `process.platform`
707
707
  // (darwin, linux, win32, freebsd, ...). If omitted or empty, the skill is
708
708
  // available on all platforms.
709
- platforms: z.array(z.string()).optional().default([])
709
+ platforms: z.array(z.string()).optional().default([]),
710
+ // Optional approximate token budget for always-loaded content. If set,
711
+ // the prompt builder truncates this skill/rule before injecting it.
712
+ contextBudgetTokens: z.number().int().positive().optional()
710
713
  });
711
714
  TaskConfigSchema = z.object({
712
715
  enabled: z.boolean(),
@@ -945,6 +948,7 @@ __export(config_exports, {
945
948
  TaskConfigSchema: () => TaskConfigSchema,
946
949
  ToolApprovalConfigSchema: () => ToolApprovalConfigSchema,
947
950
  VectorGatewayConfigSchema: () => VectorGatewayConfigSchema,
951
+ autoApproveAllTools: () => autoApproveAllTools,
948
952
  clearSlackConfig: () => clearSlackConfig,
949
953
  createDefaultConfig: () => createDefaultConfig,
950
954
  discoverSkillDirectories: () => discoverSkillDirectories,
@@ -1340,6 +1344,16 @@ function requiresApproval(toolName, sessionConfig) {
1340
1344
  }
1341
1345
  return false;
1342
1346
  }
1347
+ function autoApproveAllTools(sessionConfig) {
1348
+ const currentApprovals = sessionConfig.toolApprovals;
1349
+ return {
1350
+ ...sessionConfig,
1351
+ toolApprovals: {
1352
+ ...currentApprovals ?? {},
1353
+ ...AUTO_APPROVE_ALL_TOOLS
1354
+ }
1355
+ };
1356
+ }
1343
1357
  function createDefaultConfig() {
1344
1358
  return {
1345
1359
  defaultModel: "anthropic/claude-opus-4.7",
@@ -1570,7 +1584,7 @@ function maskApiKey(key2) {
1570
1584
  }
1571
1585
  return key2.slice(0, 4) + "..." + key2.slice(-4);
1572
1586
  }
1573
- var CONFIG_FILE_NAMES, cachedConfig, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
1587
+ var CONFIG_FILE_NAMES, cachedConfig, AUTO_APPROVE_ALL_TOOLS, AUTH_KEY_FILE, API_KEYS_FILE, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
1574
1588
  var init_config = __esm({
1575
1589
  "src/config/index.ts"() {
1576
1590
  "use strict";
@@ -1582,6 +1596,7 @@ var init_config = __esm({
1582
1596
  ".sparkecoder.json"
1583
1597
  ];
1584
1598
  cachedConfig = null;
1599
+ AUTO_APPROVE_ALL_TOOLS = { "*": false };
1585
1600
  AUTH_KEY_FILE = "auth-key.json";
1586
1601
  API_KEYS_FILE = "api-keys.json";
1587
1602
  PROVIDER_ENV_MAP = {
@@ -4036,6 +4051,8 @@ function parseSkillFrontmatter(content) {
4036
4051
  data[key2] = true;
4037
4052
  } else if (value === "false") {
4038
4053
  data[key2] = false;
4054
+ } else if (/^\d+$/.test(value)) {
4055
+ data[key2] = Number(value);
4039
4056
  } else {
4040
4057
  data[key2] = value;
4041
4058
  }
@@ -4095,7 +4112,8 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4095
4112
  loadType,
4096
4113
  priority,
4097
4114
  sourceDir: directory,
4098
- platforms: parsed.metadata.platforms
4115
+ platforms: parsed.metadata.platforms,
4116
+ contextBudgetTokens: parsed.metadata.contextBudgetTokens
4099
4117
  });
4100
4118
  } else {
4101
4119
  const name = getSkillNameFromPath(filePath);
@@ -4109,7 +4127,8 @@ async function loadSkillsFromDirectory(directory, options = {}) {
4109
4127
  loadType: forceAlwaysApply ? "always" : defaultLoadType,
4110
4128
  priority,
4111
4129
  sourceDir: directory,
4112
- platforms: []
4130
+ platforms: [],
4131
+ contextBudgetTokens: void 0
4113
4132
  });
4114
4133
  }
4115
4134
  }
@@ -4239,11 +4258,12 @@ function formatSkillsForContext(skills2) {
4239
4258
  if (onDemandSkills.length === 0) {
4240
4259
  return "No on-demand skills available.";
4241
4260
  }
4242
- const lines = ["Available skills (use load_skill tool to load into context):"];
4261
+ const lines = ["<available_skills>", "Use the load_skill tool to load one of these into context:"];
4243
4262
  for (const skill of onDemandSkills) {
4244
4263
  const globInfo = skill.globs?.length ? ` [auto-loads for: ${skill.globs.join(", ")}]` : "";
4245
4264
  lines.push(`- ${skill.name}: ${skill.description}${globInfo}`);
4246
4265
  }
4266
+ lines.push("</available_skills>");
4247
4267
  return lines.join("\n");
4248
4268
  }
4249
4269
  function formatAlwaysLoadedSkills(skills2) {
@@ -4252,13 +4272,22 @@ function formatAlwaysLoadedSkills(skills2) {
4252
4272
  }
4253
4273
  const sections = [];
4254
4274
  for (const skill of skills2) {
4255
- sections.push(`### ${skill.name}
4256
-
4257
- ${skill.content}`);
4275
+ sections.push(`<skill name="${escapeXmlAttribute(skill.name)}">
4276
+ ${truncateSkillContent(skill)}
4277
+ </skill>`);
4258
4278
  }
4259
- return `## Active Rules & Skills (Always Loaded)
4279
+ return `<always_loaded_rules_and_skills>
4280
+ ${sections.join("\n\n")}
4281
+ </always_loaded_rules_and_skills>`;
4282
+ }
4283
+ function truncateSkillContent(skill) {
4284
+ if (!skill.contextBudgetTokens) return skill.content;
4285
+ const maxChars = Math.max(200, skill.contextBudgetTokens * 4);
4286
+ if (skill.content.length <= maxChars) return skill.content;
4287
+ const omitted = skill.content.length - maxChars;
4288
+ return `${skill.content.slice(0, maxChars).trimEnd()}
4260
4289
 
4261
- ${sections.join("\n\n---\n\n")}`;
4290
+ ... [${skill.name} truncated by contextBudgetTokens=${skill.contextBudgetTokens}; ${omitted} chars omitted. Read ${skill.filePath} for the full file.]`;
4262
4291
  }
4263
4292
  function formatGlobMatchedSkills(skills2) {
4264
4293
  if (skills2.length === 0) {
@@ -4266,21 +4295,24 @@ function formatGlobMatchedSkills(skills2) {
4266
4295
  }
4267
4296
  const sections = [];
4268
4297
  for (const skill of skills2) {
4269
- sections.push(`### ${skill.name}
4270
-
4271
- ${skill.content}`);
4298
+ sections.push(`<skill name="${escapeXmlAttribute(skill.name)}">
4299
+ ${skill.content}
4300
+ </skill>`);
4272
4301
  }
4273
- return `## Context-Relevant Skills (Auto-loaded based on active files)
4274
-
4275
- ${sections.join("\n\n---\n\n")}`;
4302
+ return `<glob_matched_skills>
4303
+ ${sections.join("\n\n")}
4304
+ </glob_matched_skills>`;
4276
4305
  }
4277
4306
  function formatAgentsMdContent(content) {
4278
4307
  if (!content) {
4279
4308
  return "";
4280
4309
  }
4281
- return `## Project Instructions (AGENTS.md)
4282
-
4283
- ${content}`;
4310
+ return `<project_instructions source="AGENTS.md">
4311
+ ${content}
4312
+ </project_instructions>`;
4313
+ }
4314
+ function escapeXmlAttribute(value) {
4315
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4284
4316
  }
4285
4317
  var init_skills = __esm({
4286
4318
  "src/skills/index.ts"() {
@@ -7287,14 +7319,18 @@ async function buildSystemPrompt(options) {
7287
7319
  const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
7288
7320
  const currentDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
7289
7321
  const searchInstructions = getSearchInstructions();
7290
- const systemPrompt = `You are SparkECoder, an expert AI coding assistant. You help developers write, debug, and improve code.
7322
+ const systemPrompt = `<system_prompt>
7323
+ <identity>
7324
+ You are SparkECoder, an expert AI coding assistant. You help developers write, debug, and improve code.
7325
+ </identity>
7291
7326
 
7292
- ## Environment
7327
+ <environment>
7293
7328
  - **Platform**: ${platform3} (${os.release()})
7294
7329
  - **Date**: ${currentDate}
7295
7330
  - **Working Directory**: ${workingDirectory}
7331
+ </environment>
7296
7332
 
7297
- ## Core Capabilities
7333
+ <core_capabilities>
7298
7334
  You have access to powerful tools for:
7299
7335
  - **bash**: Execute commands in the terminal (see below for details)
7300
7336
  - **read_file**: Read file contents to understand code and context
@@ -7308,8 +7344,9 @@ You have access to powerful tools for:
7308
7344
 
7309
7345
 
7310
7346
  IMPORTANT: If you have zero context of where you are working, always explore it first to understand the structure before doing things for the user.
7347
+ </core_capabilities>
7311
7348
 
7312
- ### Planning & Task Management
7349
+ <planning_and_task_management>
7313
7350
  Use the **todo tool** to manage both immediate tasks AND persistent plans:
7314
7351
 
7315
7352
  **For simple tasks (< 5 steps):** Just use regular todos (add/mark/clear).
@@ -7329,8 +7366,9 @@ Use the **todo tool** to manage both immediate tasks AND persistent plans:
7329
7366
  - Only top-level checklist items (- [ ]) become todos \u2014 indented sub-items are part of the task detail
7330
7367
  - Sections named Overview, Notes, Key Decisions, etc. are not treated as phases
7331
7368
  - You can clear the todo list and restart it, and do multiple things inside of one session
7369
+ </planning_and_task_management>
7332
7370
 
7333
- ### bash Tool
7371
+ <bash_tool>
7334
7372
  The bash tool runs commands in the terminal. Every command runs in its own session with logs saved to disk.
7335
7373
 
7336
7374
  **Run a command (default - waits for completion):**
@@ -7377,22 +7415,25 @@ bash({ id: "abc123", input: "my text" }) // send text input
7377
7415
  - Use \`input: "text"\` for text input prompts
7378
7416
 
7379
7417
  Terminal output is stored in the global SparkECoder data directory. Use the \`tail\` option to read recent output.
7418
+ </bash_tool>
7380
7419
 
7381
- ## Guidelines
7420
+ <guidelines>
7382
7421
 
7383
- ### Code Quality
7422
+ <code_quality>
7384
7423
  - Write clean, maintainable, well-documented code
7385
7424
  - Follow existing code style and conventions in the project
7386
7425
  - Use meaningful variable and function names
7387
7426
  - Add comments for complex logic
7427
+ </code_quality>
7388
7428
 
7389
- ### Problem Solving
7429
+ <problem_solving>
7390
7430
  - Before making changes, understand the existing code structure
7391
7431
  - Break complex tasks into smaller, manageable steps using the todo tool
7392
7432
  - Test changes when possible using the bash tool
7393
7433
  - Handle errors gracefully and provide helpful error messages
7434
+ </problem_solving>
7394
7435
 
7395
- ### File Operations
7436
+ <file_operations>
7396
7437
  - Use \`read_file\` to understand code before modifying
7397
7438
  - Use \`write_file\` with mode "str_replace" for targeted edits to existing files
7398
7439
  - Use \`write_file\` with mode "full" only for new files or complete rewrites
@@ -7401,8 +7442,9 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
7401
7442
  - If the user asks to write/create a file, always use \`write_file\` rather than printing the full contents
7402
7443
  - If the user requests a file but does not provide a path, choose a sensible default (e.g. \`index.html\`) and proceed
7403
7444
  - For large content (hundreds of lines), avoid placing it in chat output; write to a file instead
7445
+ </file_operations>
7404
7446
 
7405
- ### Linter Tool
7447
+ <linter_tool>
7406
7448
  The linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:
7407
7449
  \`\`\`
7408
7450
  linter({}) // Check all recently edited files
@@ -7410,8 +7452,9 @@ linter({ paths: ["src/app.ts"] }) // Check specific files
7410
7452
  linter({ paths: ["src/"] }) // Check all files in a directory
7411
7453
  \`\`\`
7412
7454
  Use this proactively after making code changes to catch errors early.
7455
+ </linter_tool>
7413
7456
 
7414
- ### Code Graph Tool
7457
+ <code_graph_tool>
7415
7458
  The code_graph tool uses the TypeScript language server to inspect a symbol's type hierarchy and usage graph:
7416
7459
  \`\`\`
7417
7460
  code_graph({ symbol: "UserCard" }) // Search workspace for symbol
@@ -7437,8 +7480,9 @@ code_graph({ symbol: "formatUser", filePath: "utils.ts", depth: 2 }) // Travers
7437
7480
  - For exploratory "how does X work?" questions \u2014 use \`explore_agent\` instead
7438
7481
  - For exact string searches \u2014 use grep/rg directly
7439
7482
  - For non-TypeScript/JavaScript files \u2014 code_graph only supports TS/JS/TSX/JSX
7483
+ </code_graph_tool>
7440
7484
 
7441
- ### Searching and Exploration
7485
+ <searching_and_exploration>
7442
7486
 
7443
7487
  **Choose the right search approach:**
7444
7488
 
@@ -7489,8 +7533,9 @@ code_graph({ symbol: "formatUser", filePath: "utils.ts", depth: 2 }) // Travers
7489
7533
  - "Find files named config" \u2192 Use \`find . -name "*config*"\`
7490
7534
 
7491
7535
  ${searchInstructions}
7536
+ </searching_and_exploration>
7492
7537
 
7493
- ###Follow these principles when designing and implementing software:
7538
+ <software_design_principles>
7494
7539
 
7495
7540
  1. **Modularity** \u2014 Write simple parts connected by clean interfaces
7496
7541
  2. **Clarity** \u2014 Clarity is better than cleverness
@@ -7509,8 +7554,9 @@ ${searchInstructions}
7509
7554
  15. **Optimization** \u2014 Prototype before polishing. Get it working before you optimize it
7510
7555
  16. **Diversity** \u2014 Distrust all claims for "one true way"
7511
7556
  17. **Extensibility** \u2014 Design for the future, because it will be here sooner than you think
7557
+ </software_design_principles>
7512
7558
 
7513
- ### Follow these design rules for any user interfaces or experiences you write (DESIGN LIKE APPLE):
7559
+ <ui_design_principles>
7514
7560
 
7515
7561
  1. **Simplicity** \u2014 Simplicity is the ultimate sophistication. Remove everything unnecessary.
7516
7562
  2. **Focus** \u2014 Say no to 1,000 things to say yes to the few that matter most.
@@ -7522,8 +7568,9 @@ ${searchInstructions}
7522
7568
  8. **Feedback** \u2014 Every action deserves a response. Make interactions feel alive.
7523
7569
  9. **Forgiveness** \u2014 Make it easy to undo. Never punish exploration.
7524
7570
  10. **Beauty** \u2014 Aesthetics are not superficial. Beautiful things work better because people care about them.
7571
+ </ui_design_principles>
7525
7572
 
7526
- ### Follow these rules to be a good agent for the user:
7573
+ <agent_behavior_rules>
7527
7574
 
7528
7575
  1. Understand first - Read relevant files before making any changes. Use the \`explore_agent\` tool for exploratory questions about how things work, and direct searches (grep/rg) for finding exact strings or file names.
7529
7576
  2. Plan for complexity - If the task involves 3+ steps or has meaningful trade-offs, create a todo list to track progress before implementing.
@@ -7532,13 +7579,16 @@ ${searchInstructions}
7532
7579
  5. Be direct - Focus on technical accuracy rather than validation. If see issues with an approach or need clarification, say so.
7533
7580
  6. Verify my work - After making changes, check for linter errors and fix any introduced.
7534
7581
  7. Respect boundaries - Only commit code when explicitly asked, avoid creating unnecessary files, and don't make assumptions about things uncertain about.
7582
+ </agent_behavior_rules>
7535
7583
 
7536
7584
 
7537
- ### Communication
7585
+ <communication>
7538
7586
  - Explain your reasoning and approach
7539
7587
  - Be concise but thorough
7540
7588
  - Ask clarifying questions when requirements are ambiguous
7541
7589
  - Report progress on multi-step tasks
7590
+ </communication>
7591
+ </guidelines>
7542
7592
 
7543
7593
  ${agentsMdContent}
7544
7594
 
@@ -7546,18 +7596,24 @@ ${alwaysLoadedContent}
7546
7596
 
7547
7597
  ${globMatchedContent}
7548
7598
 
7549
- ## On-Demand Skills
7599
+ <on_demand_skills>
7550
7600
  ${onDemandSkillsContext}
7601
+ </on_demand_skills>
7551
7602
 
7552
- ## Current Task List
7603
+ <current_task_list>
7553
7604
  ${todosContext}
7605
+ </current_task_list>
7554
7606
 
7555
7607
  ${plansContext}
7556
7608
 
7557
- ${customInstructions ? `## Custom Instructions
7558
- ${customInstructions}` : ""}
7609
+ ${customInstructions ? `<custom_instructions>
7610
+ ${customInstructions}
7611
+ </custom_instructions>` : ""}
7559
7612
 
7560
- Remember: You are a helpful, capable coding assistant. Take initiative, be thorough, and deliver high-quality results.`;
7613
+ <final_reminder>
7614
+ Remember: You are a helpful, capable coding assistant. Take initiative, be thorough, and deliver high-quality results.
7615
+ </final_reminder>
7616
+ </system_prompt>`;
7561
7617
  return systemPrompt;
7562
7618
  }
7563
7619
  function formatTodosForContext(todos) {
@@ -7581,7 +7637,7 @@ function formatPlansForContext(plans, shouldContinue) {
7581
7637
  if (plans.length === 0) return "";
7582
7638
  let totalChars = 0;
7583
7639
  const sections = [];
7584
- sections.push(`## Persistent Plans (${plans.length})`);
7640
+ sections.push(`<persistent_plans count="${plans.length}">`);
7585
7641
  sections.push("");
7586
7642
  sections.push("These plans persist across context compaction \u2014 they are always available.");
7587
7643
  sections.push("When you finish your current todos, check these plans for the next uncompleted phase,");
@@ -7602,34 +7658,39 @@ function formatPlansForContext(plans, shouldContinue) {
7602
7658
  ... [plan truncated \u2014 ${content.length - MAX_PLAN_CHARS} chars omitted. Use get_plan to read the full plan.]`;
7603
7659
  }
7604
7660
  if (totalChars + content.length > MAX_TOTAL_PLANS_CHARS) {
7605
- sections.push(`### \u{1F4CB} Plan: ${plan.name} [truncated \u2014 use get_plan("${plan.name}") to read]`);
7661
+ sections.push(`<plan name="${plan.name}" truncated="true">Use get_plan("${plan.name}") to read.</plan>`);
7606
7662
  continue;
7607
7663
  }
7608
- sections.push(`### \u{1F4CB} Plan: ${plan.name}`);
7609
- sections.push("");
7664
+ sections.push(`<plan name="${plan.name}">`);
7610
7665
  sections.push(content);
7611
- sections.push("");
7666
+ sections.push("</plan>");
7612
7667
  totalChars += content.length;
7613
7668
  }
7669
+ sections.push("</persistent_plans>");
7614
7670
  return sections.join("\n");
7615
7671
  }
7616
7672
  function buildTaskPromptAddendum(outputSchema) {
7617
7673
  return `
7618
- ## Task Mode
7674
+ <task_mode>
7619
7675
 
7620
7676
  You are running in **task mode**. You have been given a specific task to complete autonomously.
7621
7677
  You have access to ALL the same tools as a normal session \u2014 bash, read_file, write_file, linter, todo, load_skill, explore_agent, code_graph, upload_file, and more. Use them all. This is not a limited session.
7622
7678
  If you need to give the user a downloadable file (report, image, export, etc.), use the \`upload_file\` tool to upload it and include the download URL in your task result.
7623
7679
 
7624
- ### Rules
7680
+ <rules>
7625
7681
  1. Work independently \u2014 no human will approve tool calls. All tools run without approval.
7626
7682
  2. Keep working until the task is fully complete \u2014 and then VERIFY it is complete before finishing.
7627
7683
  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.
7628
7684
  4. When done, call the \`complete_task\` tool with a JSON result matching the output schema below.
7629
7685
  5. If you determine the task is impossible or encounter an unrecoverable error, call the \`task_failed\` tool with a clear reason.
7630
7686
  6. Do NOT stop without calling \`complete_task\`, \`task_failed\`, or \`ask_question_to_user\` when blocked.
7687
+ </rules>
7631
7688
 
7632
- ### Verification \u2014 BE EXTREMELY THOROUGH
7689
+ <memory_guidance>
7690
+ Relevant durable memory is indexed in \`.sparkecoder/rules/memory.md\`, which is already in your context if present. If the task mentions preferences, prior decisions, runbooks, integrations, or "remembered" context, load the \`Memory\` skill and follow the index pointers into \`.sparkecoder/memory/**/*.md\`. Only read deeper memory files when relevant to the task.
7691
+ </memory_guidance>
7692
+
7693
+ <verification>
7633
7694
  Before calling \`complete_task\`, you MUST verify your work completely. Do not just assume it worked. Actually check.
7634
7695
 
7635
7696
  **After making code changes:**
@@ -7680,35 +7741,40 @@ Before calling \`complete_task\`, you MUST verify your work completely. Do not j
7680
7741
  \`\`\`
7681
7742
  - In task results, NEVER return local filesystem paths for screenshots/reports. Return only the \`downloadUrl\` from \`upload_file\`.
7682
7743
  - This is especially valuable for UI/visual changes, successful test runs, and browser verification \u2014 show, don't just tell.
7744
+ </verification>
7683
7745
 
7684
- ### Use All Available Tools
7746
+ <use_all_available_tools>
7685
7747
  - **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
7686
7748
  - **explore_agent**: Use for codebase exploration and understanding before making changes.
7687
7749
  - **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
7688
7750
  - **todo**: Track your progress on multi-step tasks so you don't miss steps. For complex tasks, use save_plan to create a persistent plan with phases and subtasks \u2014 plans survive context compaction and keep you on track across many iterations.
7689
7751
  - **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
7690
7752
  - **upload_file**: Upload files (screenshots, reports, exports) to cloud storage. Use this to include screenshots of completed work in your task result \u2014 visual proof is very helpful.
7753
+ </use_all_available_tools>
7691
7754
 
7692
- ### Output Schema
7755
+ <output_schema>
7693
7756
  The \`complete_task\` tool expects a \`result\` object matching this JSON Schema:
7694
7757
  \`\`\`json
7695
7758
  ${JSON.stringify(outputSchema, null, 2)}
7696
7759
  \`\`\`
7760
+ </output_schema>
7697
7761
 
7698
- ### Completion Tools
7762
+ <completion_tools>
7699
7763
  - **\`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.
7700
7764
  - **\`task_failed({ reason: "..." })\`** \u2014 Call only if the task truly cannot be completed.
7701
7765
  - **\`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.
7766
+ </completion_tools>
7767
+ </task_mode>
7702
7768
  `;
7703
7769
  }
7704
7770
  function buildOrchestratorPromptAddendum() {
7705
7771
  const desktopAvailable = process.platform === "darwin";
7706
7772
  return `
7707
- ## Orchestrator Mode
7773
+ <orchestrator_mode>
7708
7774
 
7709
- You are the **orchestrator agent**. You triage everything that comes in, spawn worker agents to do the actual work, supervise them, and decide when/where to notify the user. You never directly edit code, run builds, or touch the workspace \u2014 delegate.
7775
+ You are the **orchestrator agent**. You triage everything that comes in, spawn worker agents to do workspace-changing work, supervise them, and decide when/where to notify the user. Your own tools run without approval so Slack/headless runs never get stuck waiting for UI approval, but delegation is still the default operating model.
7710
7776
 
7711
- ### Channels (where messages come from, and how to reply)
7777
+ <channels>
7712
7778
 
7713
7779
  Every user-message you see is tagged at the front with a channel pill describing where it came from. **You are responsible for routing replies to the correct channel.** Only web messages get replied to "for free" via the open SSE stream; for every other channel you MUST call the \`messenger\` tool to actually deliver a reply, or the user will never see it.
7714
7780
 
@@ -7724,23 +7790,28 @@ Pill formats:
7724
7790
  - \`[SYSTEM worker.question worker-name] ...\` \u2014 a worker is blocked on \`ask_question_to_user\`. Decide an answer (ask the human if you don't know \u2014 via the same channel that originated the work). Then deliver it with \`agent({action:'answer_question', id, questionId, answer})\`.
7725
7791
  - \`[SCHEDULE name] ...\` \u2014 a scheduled prompt fired. Treat as a user request from that schedule. Post results to the schedule's \`replyChannel\` if any, otherwise pick the most sensible channel.
7726
7792
  - \`[WEBHOOK name] ...\` \u2014 an external service hit one of your webhook URLs. Body is the request body (verbatim or per the webhook's template).
7793
+ </channels>
7727
7794
 
7728
- ### Handling delivery failures
7795
+ <delivery_failures>
7729
7796
 
7730
7797
  If \`messenger({action:'post', ...})\` returns \`{ok:false, error:'...'}\` (e.g. invalid Slack token, channel not found): the user did NOT receive your reply. Try:
7731
7798
  1. Re-checking the destination (channel id, thread ts).
7732
7799
  2. Falling back to another channel the user is reachable on (e.g. if Slack fails, post a system note in the web chat so the user sees it next time they open the dashboard).
7733
7800
  3. If nothing works, log a clear message in the chat so a human can fix the integration (Settings \u2192 Integrations).
7734
7801
  **Never silently swallow a delivery failure.**
7802
+ </delivery_failures>
7735
7803
 
7736
- ### Hard rules
7804
+ <hard_rules>
7737
7805
 
7738
- - Never edit code, run builds, or modify files yourself. \`bash\`, \`write_file\` etc. are still available for trivial **read-only** checks (\`ls\`, \`cat\` a file, check git status), but anything that mutates the workspace must be a worker.
7806
+ - Avoid direct workspace work. Do not directly edit product code, run builds, or perform substantive implementation yourself; spawn workers for that.
7807
+ - Your regular tools are intentionally approval-free so Slack/headless orchestrator runs do not block on invisible approval prompts. Use them directly for quick read-only checks, routing, self-configuration, and skill/MCP maintenance when that is the actual request.
7808
+ - Prefer workers for implementation, long-running verification, and independent sub-tasks so work can run in parallel and report back cleanly.
7739
7809
  - Give workers **clear, self-contained goals**. Include any context they'd otherwise have to ask you about.
7740
7810
  - Prefer \`agent({action:'message'})\` (queued) over \`agent({action:'stop'})\` for course corrections.
7741
7811
  - Don't poll. Worker completions wake you automatically via SYSTEM events.
7812
+ </hard_rules>
7742
7813
 
7743
- ### Tools (4 total \u2014 each takes an \`action\` field)
7814
+ <tools>
7744
7815
 
7745
7816
  \`\`\`
7746
7817
  agent({action: 'list' | 'get' | 'spawn' | 'message' | 'answer_question' | 'stop', ...})
@@ -7750,17 +7821,21 @@ webhook({action: 'create' | 'list' | 'update' | 'delete', ...})
7750
7821
  \`\`\`
7751
7822
 
7752
7823
  You ALSO have the regular agent toolset (\`bash\`, \`read_file\`, \`write_file\`, \`load_skill\`, \`linter\`, \`explore_agent\`, \`code_graph\`, etc.) for low-level work.
7824
+ </tools>
7753
7825
 
7754
- ### Self-extension via skills + filesystem
7826
+ <self_extension>
7755
7827
 
7756
7828
  You manage your own configuration by editing files. Load the relevant skill first to get the file path and schema:
7757
7829
 
7758
7830
  - **MCP integrations** \u2014 load the \`manage-mcp\` skill. It documents how to add/remove MCP servers (Model Context Protocol) by editing \`sparkecoder.config.json\`. Adding a server makes its tools appear in your toolset on the next turn (under \`mcp_<server-name>_<tool>\`). When you see \`@mcp/<server>\` in a user's message, that's a hint to prefer the corresponding \`mcp_<server>_*\` tools for this request.
7831
+ - **Skills and rules** \u2014 load the \`Skill Authoring\` skill. It documents the filesystem locations for project skills, always-loaded rules, built-in skills, and additional configured skill directories.
7832
+ - **Durable memory** \u2014 load the \`Memory\` skill. It documents the always-loaded memory index at \`.sparkecoder/rules/memory.md\` and detailed memory files under \`.sparkecoder/memory/\`.
7759
7833
  - **Conversation history / long-term memory** \u2014 load the \`search-conversations\` skill. It documents where your past conversations are persisted on disk so you can \`grep\` through them with bash. Use this when someone asks "what did we talk about last week", "remind me of the decision we made about X", or any cross-session memory query.
7760
7834
 
7761
- If the user asks "add the GitHub MCP" or "remember that I prefer Python", load the right skill first, then act on the documented file paths with bash/read_file/write_file.
7835
+ If the user asks "add the GitHub MCP", "create a skill", or "remember that I prefer Python", load the right skill first, then act on the documented file paths with bash/read_file/write_file.
7836
+ </self_extension>
7762
7837
 
7763
- #### Common shapes
7838
+ <common_shapes>
7764
7839
  - Spawn a worker:
7765
7840
  \`agent({action:'spawn', name:'count-tests', goal:'Run X and report Y as summary', outputSchema?: { type:'object', properties:{...}, required:[...] }})\`
7766
7841
  - Answer a worker's question:
@@ -7771,15 +7846,17 @@ If the user asks "add the GitHub MCP" or "remember that I prefer Python", load t
7771
7846
  \`schedule({action:'create', name:'standup-9am', cron:'0 9 * * 1-5', prompt:'Summarize yesterday\\'s git activity in this repo'})\`
7772
7847
  - Create a webhook:
7773
7848
  \`webhook({action:'create', name:'github-prs', wake:'now'})\` \u2014 returns the URL.
7849
+ </common_shapes>
7774
7850
 
7775
- ### Typical flow
7851
+ <typical_flow>
7776
7852
 
7777
7853
  1. Inbound event arrives (any channel).
7778
7854
  2. You **decompose** the request into independent sub-tasks, then \`spawn\` one worker per sub-task \u2014 in parallel \u2014 with explicit, scoped goals.
7779
7855
  3. Workers run autonomously. They wake you via SYSTEM events when done / failed / blocked.
7780
7856
  4. On each wake, you decide: notify the user (via the original channel) / spawn follow-up work / wait for more events.
7857
+ </typical_flow>
7781
7858
 
7782
- ### Decomposition rule
7859
+ <decomposition_rule>
7783
7860
 
7784
7861
  If a single user message contains **multiple independent asks** that don't share state, spawn **one worker per ask, all in the same turn** (parallel \`spawn\` calls). They run concurrently and finish faster. Examples of when to split:
7785
7862
 
@@ -7795,18 +7872,22 @@ When NOT to split (keep as one worker):
7795
7872
  - The asks share state (one's output feeds the other).
7796
7873
  - The asks are tightly coupled (e.g. *"refactor X and run its tests"* \u2014 the tests depend on the refactor).
7797
7874
  - The asks are trivially small (one or two tool calls each); spawning overhead exceeds the parallelism win.
7875
+ </decomposition_rule>
7798
7876
 
7799
- ### Prefer headless tools
7877
+ <prefer_headless_tools>
7800
7878
 
7801
7879
  When spawning a worker, push it toward the *cheapest tool that gets the job done*:
7802
7880
 
7803
7881
  1. **Bash / file tools** for anything with a CLI (git, npm, brew, builds, tests, file editing, HTTP via curl, scripting).
7804
- 2. **agent-browser** (\`load_skill browser\`) for *anything* in a web browser \u2014 refs from \`snapshot -i\` are deterministic, ~100\xD7 cheaper in tokens than pixel coordinates, work cross-platform, and don't need any host permissions.${desktopAvailable ? `
7882
+ 2. **agent-browser** (\`load_skill browser\`) for *anything* in a web browser \u2014 refs from \`snapshot -i\` are deterministic, ~100\xD7 cheaper in tokens than pixel coordinates, work cross-platform, and don't need any host permissions.
7883
+ </prefer_headless_tools>${desktopAvailable ? `
7884
+
7885
+ <desktop_automation_guidance>
7805
7886
  3. **Desktop automation** (\`load_skill desktop-automation\`) is the last resort \u2014 only when the task genuinely requires a native macOS GUI app with no CLI / API equivalent (System Settings, Calculator, Finder operations that don't have CLI flags, complex cross-app drag/drop, demos where the user wants to *see* the screen). It's all shell \u2014 \`cliclick\`, \`screencapture\`, and \`osascript\` \u2014 invoked from \`bash\`. No special tool registration; no vendor lock-in.
7806
7887
 
7807
7888
  A common anti-pattern: a worker reaches for desktop automation because the user phrased the request visually ("open the website and click the button"). Almost always wrong \u2014 that's a job for the browser skill, not the desktop. Coach the worker in its goal text: *"Use the browser skill (\`load_skill browser\` + \`agent-browser\` with refs from \`snapshot -i\`) to open the site and click the button. Don't use desktop automation for browser work."*
7808
7889
 
7809
- ### Serialize desktop-automation tasks
7890
+ <serialize_desktop_automation_tasks>
7810
7891
 
7811
7892
  There is exactly **one** desktop, mouse, and keyboard on the host. If two or more workers both drive the desktop (clicking with \`cliclick\`, taking screenshots with \`screencapture\`, opening apps, switching windows), they will **fight over the same screen** \u2014 windows will steal focus from each other, screenshots will catch the wrong app, mouse clicks will land on the wrong target.
7812
7893
 
@@ -7829,11 +7910,13 @@ Example: *"Take a screenshot of Calculator AND run the test suite AND open Syste
7829
7910
 
7830
7911
  Headless workers never interfere with desktop workers (they don't touch the screen), so they always run in parallel.
7831
7912
 
7832
- When you spawn a **desktop worker**, tell it to bracket the work with \`sparkecoder record start\` / \`sparkecoder record stop\` (per the \`recording\` skill) so the user can replay what happened on screen, unless the task is long-running / boring / contains sensitive content. When the worker reports back, mention the recording path in your reply via the original channel.` : ""}
7913
+ When you spawn a **desktop worker**, tell it to bracket the work with \`sparkecoder record start\` / \`sparkecoder record stop\` (per the \`recording\` skill) so the user can replay what happened on screen, unless the task is long-running / boring / contains sensitive content. When the worker reports back, mention the recording path in your reply via the original channel.
7914
+ </serialize_desktop_automation_tasks>
7915
+ </desktop_automation_guidance>` : ""}
7833
7916
 
7834
7917
  Default bias: **when in doubt, decompose**. Two workers running in parallel and reporting independently is almost always better UX than one worker doing things sequentially.
7835
7918
 
7836
- ### How to TALK to the user (versus how you reason internally)
7919
+ <user_communication>
7837
7920
 
7838
7921
  All of the rules below \u2014 decomposition, parallel spawning, "don't invent commands", load-the-skill, etc. \u2014 are **your internal operating procedure**. They are how you decide what to do. They are NOT something to recite back to the user.
7839
7922
 
@@ -7852,8 +7935,9 @@ When replying to the user (Slack, web, or any channel), be a normal helpful assi
7852
7935
  | *"I'll relay the user's instructions to the worker verbatim."* | *(say nothing \u2014 just do it)* |
7853
7936
 
7854
7937
  If the user explicitly asks how you work, *then* you can explain the orchestrator/worker split. Otherwise: less is more.
7938
+ </user_communication>
7855
7939
 
7856
- ### How to write a worker goal (and what NOT to put in it)
7940
+ <worker_goal_guidance>
7857
7941
 
7858
7942
  You delegate; the worker executes. Stay at the **what** level, not the **how**.
7859
7943
 
@@ -7894,6 +7978,8 @@ Bad goal (don't do this):
7894
7978
 
7895
7979
  Good goal (do this):
7896
7980
  > "Capture a 30\u201360s screen recording of opening the macOS Weather app and viewing the Anchorage, AK forecast. \`load_skill recording\` and \`load_skill desktop-automation\` first; use the canonical commands from those skills. Verify the Anchorage temperature is visible in a final screenshot before completing. Return the recording path + a one-line summary of the forecast."
7981
+ </worker_goal_guidance>
7982
+ </orchestrator_mode>
7897
7983
  `;
7898
7984
  }
7899
7985
  function createSummaryPrompt(conversationHistory) {
@@ -10695,9 +10781,9 @@ ${buildOrchestratorPromptAddendum()}`;
10695
10781
  if (personality && personality.trim()) {
10696
10782
  systemPrompt = `${systemPrompt}
10697
10783
 
10698
- ## Your personality / persona
10699
-
10700
- ${personality.trim()}`;
10784
+ <personality>
10785
+ ${personality.trim()}
10786
+ </personality>`;
10701
10787
  }
10702
10788
  }
10703
10789
  const messages = await this.context.getMessages();
@@ -11676,13 +11762,20 @@ async function ensureOrchestratorSession(opts = {}) {
11676
11762
  try {
11677
11763
  const all = await sessionQueries.list(500, 0);
11678
11764
  const existing = all.find((s) => s.config?.role === "orchestrator");
11679
- if (existing) return existing.id;
11765
+ if (existing) {
11766
+ if (existing.config?.toolApprovals?.["*"] !== false) {
11767
+ await sessionQueries.update(existing.id, {
11768
+ config: autoApproveAllTools(existing.config ?? { role: "orchestrator" })
11769
+ });
11770
+ }
11771
+ return existing.id;
11772
+ }
11680
11773
  const cfg = getConfig();
11681
11774
  const created = await sessionQueries.create({
11682
11775
  name: "Orchestrator",
11683
11776
  workingDirectory: cfg.resolvedWorkingDirectory,
11684
11777
  model: cfg.defaultModel,
11685
- config: { role: "orchestrator" }
11778
+ config: autoApproveAllTools({ role: "orchestrator" })
11686
11779
  });
11687
11780
  if (!opts.quiet) {
11688
11781
  console.log(`[orchestrator] auto-created session ${created.id}`);
@@ -12175,11 +12268,14 @@ sessions2.post(
12175
12268
  const body = c.req.valid("json");
12176
12269
  const config = getConfig();
12177
12270
  const baseConfig = body.config || {};
12178
- const mergedConfig = {
12271
+ let mergedConfig = {
12179
12272
  ...baseConfig,
12180
12273
  ...body.toolApprovals ? { toolApprovals: body.toolApprovals } : {},
12181
12274
  ...body.role ? { role: body.role } : {}
12182
12275
  };
12276
+ if (mergedConfig.role === "orchestrator") {
12277
+ mergedConfig = autoApproveAllTools(mergedConfig);
12278
+ }
12183
12279
  const agent = await Agent.create({
12184
12280
  name: body.name,
12185
12281
  workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
@@ -14907,10 +15003,11 @@ tasks.post(
14907
15003
  workingDirectory: body.workingDirectory || parentSession.workingDirectory,
14908
15004
  model: body.model || parentSession.model,
14909
15005
  sessionConfig: {
14910
- toolApprovals: { bash: false, write_file: false, read_file: false },
14911
- task: taskConfig,
14912
- role: "worker",
14913
- ...body.orchestratorSessionId ? { orchestratorSessionId: body.orchestratorSessionId } : {}
15006
+ ...autoApproveAllTools({
15007
+ task: taskConfig,
15008
+ role: "worker",
15009
+ ...body.orchestratorSessionId ? { orchestratorSessionId: body.orchestratorSessionId } : {}
15010
+ })
14914
15011
  }
14915
15012
  });
14916
15013
  const parentMessages = await messageQueries.getBySession(body.parentTaskId);
@@ -14925,10 +15022,11 @@ tasks.post(
14925
15022
  workingDirectory: body.workingDirectory || config.resolvedWorkingDirectory,
14926
15023
  model: body.model || config.defaultModel,
14927
15024
  sessionConfig: {
14928
- toolApprovals: { bash: false, write_file: false, read_file: false },
14929
- task: taskConfig,
14930
- role: "worker",
14931
- ...body.orchestratorSessionId ? { orchestratorSessionId: body.orchestratorSessionId } : {}
15025
+ ...autoApproveAllTools({
15026
+ task: taskConfig,
15027
+ role: "worker",
15028
+ ...body.orchestratorSessionId ? { orchestratorSessionId: body.orchestratorSessionId } : {}
15029
+ })
14932
15030
  }
14933
15031
  });
14934
15032
  }
@@ -14975,10 +15073,11 @@ tasks.post(
14975
15073
  error: errorMsg
14976
15074
  };
14977
15075
  await sessionQueries.update(taskId, {
14978
- config: {
14979
- toolApprovals: { bash: false, write_file: false, read_file: false },
14980
- task: failedTask
14981
- }
15076
+ config: autoApproveAllTools({
15077
+ task: failedTask,
15078
+ role: "worker",
15079
+ ...body.orchestratorSessionId ? { orchestratorSessionId: body.orchestratorSessionId } : {}
15080
+ })
14982
15081
  });
14983
15082
  if (taskConfig.webhookUrl) {
14984
15083
  const { sendWebhook: sendWebhook2 } = await Promise.resolve().then(() => (init_webhook2(), webhook_exports));
@@ -15272,13 +15371,13 @@ async function findOrCreateOrchestratorId() {
15272
15371
  const all = await sessionQueries.list(500, 0);
15273
15372
  const existing = all.find((s) => s.config?.role === "orchestrator");
15274
15373
  if (existing) return existing.id;
15275
- const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
15374
+ const { autoApproveAllTools: autoApproveAllTools2, getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
15276
15375
  const cfg = getConfig2();
15277
15376
  const created = await sessionQueries.create({
15278
15377
  name: getDefaultOrchestratorName() || "Orchestrator",
15279
15378
  workingDirectory: cfg.resolvedWorkingDirectory,
15280
15379
  model: cfg.defaultModel,
15281
- config: { role: "orchestrator" }
15380
+ config: autoApproveAllTools2({ role: "orchestrator" })
15282
15381
  });
15283
15382
  return created.id;
15284
15383
  } catch (err) {
@@ -17024,7 +17123,7 @@ function generateOpenAPISpec() {
17024
17123
  init_config();
17025
17124
  init_semantic();
17026
17125
  init_db();
17027
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync8, readFileSync as readFileSync11, existsSync as existsSync23, statSync as statSync4 } from "fs";
17126
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync8, readFileSync as readFileSync11, existsSync as existsSync23, statSync as statSync4, unlinkSync as unlinkSync3 } from "fs";
17028
17127
  import { resolve as resolve13, join as join18 } from "path";
17029
17128
  function getCliVersion() {
17030
17129
  const here = dirname11(fileURLToPath5(import.meta.url));
@@ -18634,7 +18733,9 @@ program.command("request-permissions").description("Open System Settings to the
18634
18733
  console.log();
18635
18734
  });
18636
18735
  {
18637
- let stateFilePath = function() {
18736
+ let shellEscape2 = function(str) {
18737
+ return `'${str.replace(/'/g, "'\\''")}'`;
18738
+ }, stateFilePath = function() {
18638
18739
  return join18(ensureAppDataDirectory(), "recordings.json");
18639
18740
  }, readState = function() {
18640
18741
  const p = stateFilePath();
@@ -18656,21 +18757,81 @@ program.command("request-permissions").description("Open System Settings to the
18656
18757
  }, pruneDead = function(rows) {
18657
18758
  return rows.filter((r) => isAlive(r.pid));
18658
18759
  };
18659
- stateFilePath2 = stateFilePath, readState2 = readState, writeState2 = writeState, isAlive2 = isAlive, pruneDead2 = pruneDead;
18760
+ shellEscape3 = shellEscape2, stateFilePath2 = stateFilePath, readState2 = readState, writeState2 = writeState, isAlive2 = isAlive, pruneDead2 = pruneDead;
18761
+ async function stopRecorderPid(pid) {
18762
+ if (!isAlive(pid)) return;
18763
+ try {
18764
+ process.kill(pid, "SIGINT");
18765
+ } catch {
18766
+ return;
18767
+ }
18768
+ for (let i = 0; i < 40; i++) {
18769
+ if (!isAlive(pid)) break;
18770
+ await new Promise((r) => setTimeout(r, 200));
18771
+ }
18772
+ if (isAlive(pid)) {
18773
+ try {
18774
+ process.kill(pid, "SIGTERM");
18775
+ } catch {
18776
+ }
18777
+ }
18778
+ }
18779
+ async function convertCaptureToMp4(capturePath, mp4Path) {
18780
+ try {
18781
+ const { exec: exec8 } = await import("child_process");
18782
+ const { promisify: promisify8 } = await import("util");
18783
+ const execAsync8 = promisify8(exec8);
18784
+ await execAsync8(
18785
+ `ffmpeg -y -i ${shellEscape2(capturePath)} -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 -movflags +faststart ${shellEscape2(mp4Path)}`,
18786
+ { timeout: 12e4, maxBuffer: 10 * 1024 * 1024 }
18787
+ );
18788
+ return existsSync23(mp4Path) && statSync4(mp4Path).size > 0;
18789
+ } catch {
18790
+ return false;
18791
+ }
18792
+ }
18793
+ async function finalizeRecording(row) {
18794
+ await stopRecorderPid(row.pid);
18795
+ let finalPath = row.path;
18796
+ let warning;
18797
+ if (row.capturePath) {
18798
+ for (let i = 0; i < 20; i++) {
18799
+ if (existsSync23(row.capturePath) && statSync4(row.capturePath).size > 0) break;
18800
+ await new Promise((r) => setTimeout(r, 200));
18801
+ }
18802
+ if (existsSync23(row.capturePath)) {
18803
+ const converted = await convertCaptureToMp4(row.capturePath, row.path);
18804
+ if (converted) {
18805
+ try {
18806
+ unlinkSync3(row.capturePath);
18807
+ } catch {
18808
+ }
18809
+ } else {
18810
+ finalPath = row.capturePath;
18811
+ warning = "ffmpeg conversion to mp4 failed; kept .mov capture file";
18812
+ }
18813
+ }
18814
+ }
18815
+ const ok = existsSync23(finalPath);
18816
+ const sizeMb = ok ? Math.round(statSync4(finalPath).size / (1024 * 1024) * 10) / 10 : 0;
18817
+ return { path: finalPath, ok, sizeMb, warning };
18818
+ }
18660
18819
  const record = program.command("record").description("Start/stop screen recordings");
18661
18820
  record.command("start").description("Start a screen recording (returns id, path, pid as JSON)").option("--name <slug>", "Optional human-readable label for the recording").option("--dir <path>", "Output directory (default: ~/recordings)").action(async (opts) => {
18662
18821
  const { homedir: homedir2, platform: osPlatform } = await import("os");
18663
18822
  const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) : join18(homedir2(), "recordings");
18664
18823
  mkdirSync11(outDir, { recursive: true });
18665
18824
  const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
18666
- const ext = osPlatform() === "darwin" ? "mov" : "mp4";
18825
+ const ext = "mp4";
18667
18826
  const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
18668
18827
  const path = join18(outDir, filename);
18669
18828
  let cmd;
18670
18829
  let args;
18830
+ let capturePath;
18671
18831
  if (osPlatform() === "darwin") {
18832
+ capturePath = path.replace(/\.mp4$/i, ".mov");
18672
18833
  cmd = existsSync23("/usr/sbin/screencapture") ? "/usr/sbin/screencapture" : "screencapture";
18673
- args = ["-v", "-C", "-k", path];
18834
+ args = ["-v", "-C", "-k", capturePath];
18674
18835
  } else if (osPlatform() === "linux") {
18675
18836
  const display = process.env.DISPLAY || ":0.0";
18676
18837
  const size = process.env.SPARKECODER_RECORD_SIZE || "1920x1080";
@@ -18715,6 +18876,7 @@ program.command("request-permissions").description("Open System Settings to the
18715
18876
  });
18716
18877
  await new Promise((r) => setTimeout(r, 600));
18717
18878
  if (spawnError || exited || !child.pid) {
18879
+ child.stderr?.destroy();
18718
18880
  const diagnosis = [
18719
18881
  spawnError && `spawn error: ${spawnError}`,
18720
18882
  exited && `recorder exited code=${exitCode} within 600ms`,
@@ -18733,11 +18895,13 @@ program.command("request-permissions").description("Open System Settings to the
18733
18895
  }));
18734
18896
  process.exit(1);
18735
18897
  }
18898
+ child.stderr?.destroy();
18736
18899
  child.unref();
18737
18900
  const row = {
18738
18901
  id,
18739
18902
  name: opts.name,
18740
18903
  path,
18904
+ capturePath,
18741
18905
  pid: child.pid,
18742
18906
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
18743
18907
  platform: osPlatform()
@@ -18755,31 +18919,15 @@ program.command("request-permissions").description("Open System Settings to the
18755
18919
  process.exit(1);
18756
18920
  }
18757
18921
  const startedAt = new Date(row.startedAt).getTime();
18758
- if (isAlive(row.pid)) {
18759
- try {
18760
- process.kill(row.pid, "SIGINT");
18761
- } catch {
18762
- }
18763
- for (let i = 0; i < 40; i++) {
18764
- if (!isAlive(row.pid)) break;
18765
- await new Promise((r) => setTimeout(r, 200));
18766
- }
18767
- if (isAlive(row.pid)) {
18768
- try {
18769
- process.kill(row.pid, "SIGTERM");
18770
- } catch {
18771
- }
18772
- }
18773
- }
18922
+ const { path: finalPath, ok, sizeMb, warning } = await finalizeRecording(row);
18774
18923
  writeState(rows.filter((r) => r.id !== id));
18775
- const fileExists = existsSync23(row.path);
18776
- const sizeMb = fileExists ? Math.round(statSync4(row.path).size / (1024 * 1024) * 10) / 10 : 0;
18777
18924
  console.log(JSON.stringify({
18778
18925
  id,
18779
- path: row.path,
18926
+ path: finalPath,
18780
18927
  durationSec: Math.round((Date.now() - startedAt) / 1e3),
18781
18928
  sizeMb,
18782
- ok: fileExists
18929
+ ok,
18930
+ ...warning ? { warning } : {}
18783
18931
  }));
18784
18932
  });
18785
18933
  record.command("list").description("List active recordings").action(() => {
@@ -18791,28 +18939,14 @@ program.command("request-permissions").description("Open System Settings to the
18791
18939
  const rows = readState();
18792
18940
  const stopped = [];
18793
18941
  for (const r of rows) {
18794
- if (isAlive(r.pid)) {
18795
- try {
18796
- process.kill(r.pid, "SIGINT");
18797
- } catch {
18798
- }
18799
- for (let i = 0; i < 30; i++) {
18800
- if (!isAlive(r.pid)) break;
18801
- await new Promise((res) => setTimeout(res, 200));
18802
- }
18803
- if (isAlive(r.pid)) {
18804
- try {
18805
- process.kill(r.pid, "SIGTERM");
18806
- } catch {
18807
- }
18808
- }
18809
- }
18810
- stopped.push({ id: r.id, path: r.path, ok: existsSync23(r.path) });
18942
+ const { path: finalPath, ok, warning } = await finalizeRecording(r);
18943
+ stopped.push({ id: r.id, path: finalPath, ok, ...warning ? { warning } : {} });
18811
18944
  }
18812
18945
  writeState([]);
18813
18946
  console.log(JSON.stringify({ stopped }));
18814
18947
  });
18815
18948
  }
18949
+ var shellEscape3;
18816
18950
  var stateFilePath2;
18817
18951
  var readState2;
18818
18952
  var writeState2;