sparkecoder 0.1.81 → 0.1.83

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 (90) hide show
  1. package/dist/agent/index.js +284 -71
  2. package/dist/agent/index.js.map +1 -1
  3. package/dist/cli.js +370 -144
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.js +358 -132
  6. package/dist/index.js.map +1 -1
  7. package/dist/server/index.js +358 -132
  8. package/dist/server/index.js.map +1 -1
  9. package/dist/skills/default/browser.md +30 -0
  10. package/dist/tools/index.d.ts +117 -1
  11. package/dist/tools/index.js +183 -41
  12. package/dist/tools/index.js.map +1 -1
  13. package/package.json +1 -1
  14. package/src/skills/default/browser.md +30 -0
  15. package/web/.next/BUILD_ID +1 -1
  16. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  17. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  18. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  19. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  20. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  22. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  23. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  30. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  34. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  35. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
  37. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  41. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  44. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
  48. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
  49. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  51. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  53. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  59. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  60. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  62. package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  68. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  69. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  70. package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
  71. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
  72. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
  73. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  74. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  75. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  76. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  77. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  78. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  79. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  80. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  81. package/web/.next/standalone/web/package-lock.json +3 -3
  82. /package/web/.next/standalone/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
  83. /package/web/.next/standalone/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
  84. /package/web/.next/standalone/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
  85. /package/web/.next/standalone/web/.next/static/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
  86. /package/web/.next/standalone/web/.next/static/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
  87. /package/web/.next/standalone/web/.next/static/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
  88. /package/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
  89. /package/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
  90. /package/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
@@ -683,9 +683,9 @@ __export(skills_exports, {
683
683
  loadSkillContent: () => loadSkillContent,
684
684
  loadSkillsFromDirectory: () => loadSkillsFromDirectory
685
685
  });
686
- import { readFile as readFile6, readdir } from "fs/promises";
686
+ import { readFile as readFile7, readdir } from "fs/promises";
687
687
  import { resolve as resolve6, basename, extname as extname4, relative as relative4 } from "path";
688
- import { existsSync as existsSync9 } from "fs";
688
+ import { existsSync as existsSync10 } from "fs";
689
689
  import { minimatch } from "minimatch";
690
690
  function parseSkillFrontmatter(content) {
691
691
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -763,7 +763,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
763
763
  defaultLoadType = "on_demand",
764
764
  forceAlwaysApply = false
765
765
  } = options;
766
- if (!existsSync9(directory)) {
766
+ if (!existsSync10(directory)) {
767
767
  return [];
768
768
  }
769
769
  const skills = [];
@@ -773,7 +773,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
773
773
  let fileName;
774
774
  if (entry.isDirectory()) {
775
775
  const skillMdPath = resolve6(directory, entry.name, "SKILL.md");
776
- if (existsSync9(skillMdPath)) {
776
+ if (existsSync10(skillMdPath)) {
777
777
  filePath = skillMdPath;
778
778
  fileName = entry.name;
779
779
  } else {
@@ -785,7 +785,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
785
785
  } else {
786
786
  continue;
787
787
  }
788
- const content = await readFile6(filePath, "utf-8");
788
+ const content = await readFile7(filePath, "utf-8");
789
789
  const parsed = parseSkillFrontmatter(content);
790
790
  if (parsed) {
791
791
  const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
@@ -864,7 +864,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
864
864
  const onDemandSkills = allSkills.filter((s) => !s.alwaysApply && s.loadType !== "always");
865
865
  const alwaysWithContent = await Promise.all(
866
866
  alwaysSkills.map(async (skill) => {
867
- const content = await readFile6(skill.filePath, "utf-8");
867
+ const content = await readFile7(skill.filePath, "utf-8");
868
868
  const parsed = parseSkillFrontmatter(content);
869
869
  return {
870
870
  ...skill,
@@ -901,7 +901,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
901
901
  });
902
902
  const matchedWithContent = await Promise.all(
903
903
  matchedSkills.map(async (skill) => {
904
- const content = await readFile6(skill.filePath, "utf-8");
904
+ const content = await readFile7(skill.filePath, "utf-8");
905
905
  const parsed = parseSkillFrontmatter(content);
906
906
  return {
907
907
  ...skill,
@@ -913,10 +913,10 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
913
913
  return matchedWithContent;
914
914
  }
915
915
  async function loadAgentsMd(agentsMdPath) {
916
- if (!agentsMdPath || !existsSync9(agentsMdPath)) {
916
+ if (!agentsMdPath || !existsSync10(agentsMdPath)) {
917
917
  return null;
918
918
  }
919
- const content = await readFile6(agentsMdPath, "utf-8");
919
+ const content = await readFile7(agentsMdPath, "utf-8");
920
920
  return content;
921
921
  }
922
922
  async function loadSkillContent(skillName, directories) {
@@ -927,7 +927,7 @@ async function loadSkillContent(skillName, directories) {
927
927
  if (!skill) {
928
928
  return null;
929
929
  }
930
- const content = await readFile6(skill.filePath, "utf-8");
930
+ const content = await readFile7(skill.filePath, "utf-8");
931
931
  const parsed = parseSkillFrontmatter(content);
932
932
  return {
933
933
  ...skill,
@@ -1188,7 +1188,7 @@ var init_client = __esm({
1188
1188
  });
1189
1189
 
1190
1190
  // src/semantic/indexer.ts
1191
- import { readFileSync as readFileSync4, statSync } from "fs";
1191
+ import { readFileSync as readFileSync5, statSync } from "fs";
1192
1192
  import { relative as relative6 } from "path";
1193
1193
  import { minimatch as minimatch2 } from "minimatch";
1194
1194
  async function getIndexStatus(workingDirectory) {
@@ -1273,8 +1273,8 @@ __export(semantic_search_exports, {
1273
1273
  });
1274
1274
  import { tool as tool8 } from "ai";
1275
1275
  import { z as z9 } from "zod";
1276
- import { existsSync as existsSync12, readFileSync as readFileSync5 } from "fs";
1277
- import { join as join5 } from "path";
1276
+ import { existsSync as existsSync13, readFileSync as readFileSync6 } from "fs";
1277
+ import { join as join6 } from "path";
1278
1278
  import { minimatch as minimatch3 } from "minimatch";
1279
1279
  function createSemanticSearchTool(options) {
1280
1280
  return tool8({
@@ -1341,13 +1341,13 @@ Returns matching code snippets with file paths, line numbers, and relevance scor
1341
1341
  if (language && matchLanguage !== language.toLowerCase()) {
1342
1342
  continue;
1343
1343
  }
1344
- const fullPath = join5(options.workingDirectory, filePath);
1345
- if (!existsSync12(fullPath)) {
1344
+ const fullPath = join6(options.workingDirectory, filePath);
1345
+ if (!existsSync13(fullPath)) {
1346
1346
  continue;
1347
1347
  }
1348
1348
  let snippet = "";
1349
1349
  try {
1350
- const content = readFileSync5(fullPath, "utf-8");
1350
+ const content = readFileSync6(fullPath, "utf-8");
1351
1351
  const lines = content.split("\n");
1352
1352
  const snippetLines = lines.slice(
1353
1353
  Math.max(0, startLine - 1),
@@ -1614,8 +1614,8 @@ __export(recorder_exports, {
1614
1614
  });
1615
1615
  import { exec as exec5 } from "child_process";
1616
1616
  import { promisify as promisify5 } from "util";
1617
- import { writeFile as writeFile4, mkdir as mkdir4, readFile as readFile10, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
1618
- import { join as join7 } from "path";
1617
+ import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
1618
+ import { join as join8 } from "path";
1619
1619
  import { tmpdir } from "os";
1620
1620
  import { nanoid as nanoid3 } from "nanoid";
1621
1621
  async function checkFfmpeg() {
@@ -1672,21 +1672,21 @@ var init_recorder = __esm({
1672
1672
  */
1673
1673
  async encode() {
1674
1674
  if (this.frames.length === 0) return null;
1675
- const workDir = join7(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
1675
+ const workDir = join8(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
1676
1676
  await mkdir4(workDir, { recursive: true });
1677
1677
  try {
1678
1678
  for (let i = 0; i < this.frames.length; i++) {
1679
- const framePath = join7(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
1680
- await writeFile4(framePath, this.frames[i].data);
1679
+ const framePath = join8(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
1680
+ await writeFile5(framePath, this.frames[i].data);
1681
1681
  }
1682
1682
  const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
1683
1683
  const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
1684
1684
  const clampedFps = Math.max(1, Math.min(fps, 30));
1685
- const outputPath = join7(workDir, `recording_${this.sessionId}.mp4`);
1685
+ const outputPath = join8(workDir, `recording_${this.sessionId}.mp4`);
1686
1686
  const hasFfmpeg = await checkFfmpeg();
1687
1687
  if (hasFfmpeg) {
1688
1688
  await execAsync5(
1689
- `ffmpeg -y -framerate ${clampedFps} -i "${join7(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
1689
+ `ffmpeg -y -framerate ${clampedFps} -i "${join8(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
1690
1690
  { timeout: 12e4 }
1691
1691
  );
1692
1692
  } else {
@@ -1694,11 +1694,11 @@ var init_recorder = __esm({
1694
1694
  await cleanup(workDir);
1695
1695
  return null;
1696
1696
  }
1697
- const outputBuf = await readFile10(outputPath);
1697
+ const outputBuf = await readFile11(outputPath);
1698
1698
  const files = await readdir5(workDir);
1699
1699
  for (const f of files) {
1700
1700
  if (f.startsWith("frame_")) {
1701
- await unlink2(join7(workDir, f)).catch(() => {
1701
+ await unlink2(join8(workDir, f)).catch(() => {
1702
1702
  });
1703
1703
  }
1704
1704
  }
@@ -3599,8 +3599,34 @@ Working directory: ${options.workingDirectory}`,
3599
3599
  init_db();
3600
3600
  import { tool as tool4 } from "ai";
3601
3601
  import { z as z5 } from "zod";
3602
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync, readFileSync as readFileSync3, appendFileSync } from "fs";
3603
+ import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
3604
+ import { join as join4 } from "path";
3605
+ function getPlansDir(workingDirectory, sessionId) {
3606
+ return join4(workingDirectory, ".sparkecoder", "plans", sessionId);
3607
+ }
3608
+ function ensurePlansDir(workingDirectory, sessionId) {
3609
+ const dir = getPlansDir(workingDirectory, sessionId);
3610
+ if (!existsSync9(dir)) {
3611
+ mkdirSync4(dir, { recursive: true });
3612
+ }
3613
+ const gitignorePath = join4(workingDirectory, ".gitignore");
3614
+ if (existsSync9(gitignorePath)) {
3615
+ try {
3616
+ const content = readFileSync3(gitignorePath, "utf-8");
3617
+ if (!content.includes(".sparkecoder")) {
3618
+ appendFileSync(gitignorePath, "\n.sparkecoder/\n");
3619
+ }
3620
+ } catch {
3621
+ }
3622
+ }
3623
+ return dir;
3624
+ }
3625
+ function slugify(name) {
3626
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "plan";
3627
+ }
3602
3628
  var todoInputSchema = z5.object({
3603
- action: z5.enum(["add", "list", "mark", "clear"]).describe("The action to perform on the todo list"),
3629
+ action: z5.enum(["add", "list", "mark", "clear", "save_plan", "list_plans", "get_plan", "delete_plan"]).describe("The action to perform"),
3604
3630
  items: z5.array(
3605
3631
  z5.object({
3606
3632
  content: z5.string().describe("Description of the task"),
@@ -3608,27 +3634,67 @@ var todoInputSchema = z5.object({
3608
3634
  })
3609
3635
  ).optional().describe('For "add" action: Array of todo items to add'),
3610
3636
  todoId: z5.string().optional().describe('For "mark" action: The ID of the todo item to update'),
3611
- status: z5.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe('For "mark" action: The new status for the todo item')
3637
+ status: z5.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe('For "mark" action: The new status for the todo item'),
3638
+ planName: z5.string().optional().describe('For plan actions: Name of the plan (e.g. "auth-system", "db-migration")'),
3639
+ planContent: z5.string().optional().describe('For "save_plan": Full plan content as markdown with hierarchical tasks using checkboxes')
3612
3640
  });
3613
3641
  function createTodoTool(options) {
3614
3642
  return tool4({
3615
- description: `Manage your task list for the current session. Use this to:
3616
- - Break down complex tasks into smaller steps
3617
- - Track progress on multi-step operations
3618
- - Organize your work systematically
3643
+ description: `Manage your task list and persistent plans for the current session.
3619
3644
 
3620
- Available actions:
3645
+ ## Todo Actions (for tracking current work)
3621
3646
  - "add": Add one or more new todo items to the list
3622
3647
  - "list": View all current todo items and their status
3623
3648
  - "mark": Update the status of a todo item (pending, in_progress, completed, cancelled)
3624
3649
  - "clear": Remove all todo items from the list
3625
3650
 
3626
- Best practices:
3627
- - Add todos before starting complex tasks
3628
- - Mark items as "in_progress" when actively working on them
3629
- - Update status as you complete each step`,
3651
+ ## Plan Actions (for complex, multi-phase work)
3652
+ - "save_plan": Create or update a named plan \u2014 a persistent markdown document with hierarchical tasks, subtasks, and notes. Plans survive context compaction and are always available.
3653
+ - "list_plans": List all plans for this session
3654
+ - "get_plan": Read a specific plan by name
3655
+ - "delete_plan": Remove a plan
3656
+
3657
+ ## Plans vs Todos
3658
+ - **Plans** are the big picture \u2014 the full spec with phases, subtasks, notes, and decisions. They persist on disk and are always injected into your context, even after old messages are summarized.
3659
+ - **Todos** are your current focus \u2014 the immediate steps you're working on right now.
3660
+
3661
+ ## Workflow for complex tasks
3662
+ 1. Create a plan with phases and subtasks (save_plan)
3663
+ 2. Create todos from the first uncompleted phase (add)
3664
+ 3. Work through the todos, marking them as you go
3665
+ 4. When all current todos are done, update the plan (mark completed sections with [x]) and save it
3666
+ 5. Create new todos from the next uncompleted phase
3667
+ 6. Repeat until the plan is fully complete
3668
+
3669
+ ## Plan format
3670
+ Plans should be markdown with this structure:
3671
+ \`\`\`markdown
3672
+ # Plan: [Title]
3673
+
3674
+ ## Overview
3675
+ [What we're doing and why]
3676
+
3677
+ ## Phase 1: [Name] [completed]
3678
+ - [x] Task 1
3679
+ - [x] Task 2
3680
+
3681
+ ## Phase 2: [Name] [in_progress]
3682
+ - [x] Subtask 2.1
3683
+ - [ ] Subtask 2.2
3684
+ - [ ] Sub-subtask 2.2.1
3685
+ - [ ] Sub-subtask 2.2.2
3686
+ - [ ] Subtask 2.3
3687
+
3688
+ ## Phase 3: [Name] [pending]
3689
+ - [ ] Task 1
3690
+ - [ ] Task 2
3691
+
3692
+ ## Notes
3693
+ - Key decisions and context to preserve
3694
+ - Important file paths discovered
3695
+ \`\`\``,
3630
3696
  inputSchema: todoInputSchema,
3631
- execute: async ({ action, items, todoId, status }) => {
3697
+ execute: async ({ action, items, todoId, status, planName, planContent }) => {
3632
3698
  try {
3633
3699
  switch (action) {
3634
3700
  case "add": {
@@ -3696,6 +3762,81 @@ Best practices:
3696
3762
  itemsRemoved: count
3697
3763
  };
3698
3764
  }
3765
+ // ── Plan actions ─────────────────────────────────────────
3766
+ case "save_plan": {
3767
+ if (!planName) {
3768
+ return { success: false, error: 'planName is required for "save_plan"' };
3769
+ }
3770
+ if (!planContent) {
3771
+ return { success: false, error: 'planContent is required for "save_plan"' };
3772
+ }
3773
+ const dir = ensurePlansDir(options.workingDirectory, options.sessionId);
3774
+ const filename = `${slugify(planName)}.md`;
3775
+ const filePath = join4(dir, filename);
3776
+ await writeFile4(filePath, planContent, "utf-8");
3777
+ return {
3778
+ success: true,
3779
+ action: "save_plan",
3780
+ planName,
3781
+ filename,
3782
+ path: filePath,
3783
+ sizeChars: planContent.length
3784
+ };
3785
+ }
3786
+ case "list_plans": {
3787
+ const dir = getPlansDir(options.workingDirectory, options.sessionId);
3788
+ if (!existsSync9(dir)) {
3789
+ return { success: true, action: "list_plans", plans: [], count: 0 };
3790
+ }
3791
+ const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
3792
+ const plans = [];
3793
+ for (const f of files) {
3794
+ try {
3795
+ const content = await readFile6(join4(dir, f), "utf-8");
3796
+ const titleMatch = content.match(/^#\s+(?:Plan:\s*)?(.+)/m);
3797
+ plans.push({
3798
+ name: f.replace(/\.md$/, ""),
3799
+ title: titleMatch?.[1]?.trim() || f.replace(/\.md$/, ""),
3800
+ filename: f,
3801
+ sizeChars: content.length
3802
+ });
3803
+ } catch {
3804
+ }
3805
+ }
3806
+ return { success: true, action: "list_plans", plans, count: plans.length };
3807
+ }
3808
+ case "get_plan": {
3809
+ if (!planName) {
3810
+ return { success: false, error: 'planName is required for "get_plan"' };
3811
+ }
3812
+ const dir = getPlansDir(options.workingDirectory, options.sessionId);
3813
+ const filename = `${slugify(planName)}.md`;
3814
+ const filePath = join4(dir, filename);
3815
+ if (!existsSync9(filePath)) {
3816
+ return { success: false, error: `Plan not found: "${planName}" (looked for ${filename})` };
3817
+ }
3818
+ const content = await readFile6(filePath, "utf-8");
3819
+ return {
3820
+ success: true,
3821
+ action: "get_plan",
3822
+ planName,
3823
+ content,
3824
+ sizeChars: content.length
3825
+ };
3826
+ }
3827
+ case "delete_plan": {
3828
+ if (!planName) {
3829
+ return { success: false, error: 'planName is required for "delete_plan"' };
3830
+ }
3831
+ const dir = getPlansDir(options.workingDirectory, options.sessionId);
3832
+ const filename = `${slugify(planName)}.md`;
3833
+ const filePath = join4(dir, filename);
3834
+ if (!existsSync9(filePath)) {
3835
+ return { success: false, error: `Plan not found: "${planName}"` };
3836
+ }
3837
+ unlinkSync(filePath);
3838
+ return { success: true, action: "delete_plan", planName, deleted: true };
3839
+ }
3699
3840
  default:
3700
3841
  return {
3701
3842
  success: false,
@@ -3720,6 +3861,21 @@ function formatTodoItem(item) {
3720
3861
  createdAt: item.createdAt.toISOString()
3721
3862
  };
3722
3863
  }
3864
+ async function readSessionPlans(workingDirectory, sessionId) {
3865
+ const dir = getPlansDir(workingDirectory, sessionId);
3866
+ if (!existsSync9(dir)) return [];
3867
+ const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
3868
+ if (files.length === 0) return [];
3869
+ const plans = [];
3870
+ for (const f of files) {
3871
+ try {
3872
+ const content = await readFile6(join4(dir, f), "utf-8");
3873
+ plans.push({ name: f.replace(/\.md$/, ""), content });
3874
+ } catch {
3875
+ }
3876
+ }
3877
+ return plans;
3878
+ }
3723
3879
 
3724
3880
  // src/tools/load-skill.ts
3725
3881
  init_skills();
@@ -3809,7 +3965,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
3809
3965
  import { tool as tool6 } from "ai";
3810
3966
  import { z as z7 } from "zod";
3811
3967
  import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname5 } from "path";
3812
- import { existsSync as existsSync10 } from "fs";
3968
+ import { existsSync as existsSync11 } from "fs";
3813
3969
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
3814
3970
  var linterInputSchema = z7.object({
3815
3971
  paths: z7.array(z7.string()).optional().describe("File or directory paths to check for lint errors. If not provided, returns diagnostics for all recently touched files."),
@@ -3877,7 +4033,7 @@ Working directory: ${options.workingDirectory}`,
3877
4033
  const filesToCheck = [];
3878
4034
  for (const path of paths) {
3879
4035
  const absolutePath = isAbsolute3(path) ? path : resolve7(options.workingDirectory, path);
3880
- if (!existsSync10(absolutePath)) {
4036
+ if (!existsSync11(absolutePath)) {
3881
4037
  continue;
3882
4038
  }
3883
4039
  const stats = await stat2(absolutePath);
@@ -4189,17 +4345,17 @@ import { tool as tool9 } from "ai";
4189
4345
  import { z as z10 } from "zod";
4190
4346
  import { exec as exec4 } from "child_process";
4191
4347
  import { promisify as promisify4 } from "util";
4192
- import { readFile as readFile8, stat as stat3, readdir as readdir4 } from "fs/promises";
4348
+ import { readFile as readFile9, stat as stat3, readdir as readdir4 } from "fs/promises";
4193
4349
  import { resolve as resolve9, relative as relative8, isAbsolute as isAbsolute5 } from "path";
4194
- import { existsSync as existsSync13 } from "fs";
4350
+ import { existsSync as existsSync14 } from "fs";
4195
4351
  init_semantic();
4196
4352
 
4197
4353
  // src/tools/code-graph.ts
4198
4354
  import { tool as tool7 } from "ai";
4199
4355
  import { z as z8 } from "zod";
4200
4356
  import { resolve as resolve8, relative as relative7, isAbsolute as isAbsolute4, basename as basename3 } from "path";
4201
- import { readFile as readFile7, readdir as readdir3 } from "fs/promises";
4202
- import { existsSync as existsSync11 } from "fs";
4357
+ import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
4358
+ import { existsSync as existsSync12 } from "fs";
4203
4359
  import { fileURLToPath as fileURLToPath2 } from "url";
4204
4360
  import { execFileSync } from "child_process";
4205
4361
  var codeGraphInputSchema = z8.object({
@@ -4336,7 +4492,7 @@ async function grepForSymbol(symbol, workingDirectory) {
4336
4492
  const ext = entry.name.substring(entry.name.lastIndexOf("."));
4337
4493
  if (!SUPPORTED_EXTS.has(ext)) continue;
4338
4494
  remaining--;
4339
- const content = await readFile7(fullPath, "utf-8");
4495
+ const content = await readFile8(fullPath, "utf-8");
4340
4496
  const lines = content.split("\n");
4341
4497
  for (let i = 0; i < lines.length; i++) {
4342
4498
  if (defPattern.test(lines[i])) {
@@ -4385,7 +4541,7 @@ Working directory: ${options.workingDirectory}`,
4385
4541
  let defSymbol = null;
4386
4542
  if (filePath) {
4387
4543
  const absPath = isAbsolute4(filePath) ? filePath : resolve8(options.workingDirectory, filePath);
4388
- if (!existsSync11(absPath)) {
4544
+ if (!existsSync12(absPath)) {
4389
4545
  return { success: false, error: `File not found: ${filePath}` };
4390
4546
  }
4391
4547
  if (!isSupported(absPath)) {
@@ -4399,7 +4555,7 @@ Working directory: ${options.workingDirectory}`,
4399
4555
  defLine = defSymbol.selectionRange.start.line;
4400
4556
  defChar = defSymbol.selectionRange.start.character;
4401
4557
  } else {
4402
- const content = await readFile7(absPath, "utf-8");
4558
+ const content = await readFile8(absPath, "utf-8");
4403
4559
  const lines2 = content.split("\n");
4404
4560
  const defPattern = new RegExp(
4405
4561
  `(export|function|const|let|var|class|interface|type|enum)\\s+.*\\b${symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
@@ -4812,7 +4968,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
4812
4968
  execute: async ({ path, startLine, endLine }) => {
4813
4969
  try {
4814
4970
  const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
4815
- if (!existsSync13(absolutePath)) {
4971
+ if (!existsSync14(absolutePath)) {
4816
4972
  return {
4817
4973
  success: false,
4818
4974
  error: `File not found: ${path}`
@@ -4825,7 +4981,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
4825
4981
  error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`
4826
4982
  };
4827
4983
  }
4828
- let content = await readFile8(absolutePath, "utf-8");
4984
+ let content = await readFile9(absolutePath, "utf-8");
4829
4985
  if (startLine !== void 0 || endLine !== void 0) {
4830
4986
  const lines = content.split("\n");
4831
4987
  const start = (startLine ?? 1) - 1;
@@ -4856,7 +5012,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
4856
5012
  execute: async ({ path, recursive, maxDepth }) => {
4857
5013
  try {
4858
5014
  const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
4859
- if (!existsSync13(absolutePath)) {
5015
+ if (!existsSync14(absolutePath)) {
4860
5016
  return {
4861
5017
  success: false,
4862
5018
  error: `Directory not found: ${path}`
@@ -5223,8 +5379,8 @@ function createTaskFailedTool(options) {
5223
5379
  // src/tools/upload-file.ts
5224
5380
  import { tool as tool12 } from "ai";
5225
5381
  import { z as z13 } from "zod";
5226
- import { readFile as readFile9, stat as stat4 } from "fs/promises";
5227
- import { join as join6, basename as basename4, extname as extname7 } from "path";
5382
+ import { readFile as readFile10, stat as stat4 } from "fs/promises";
5383
+ import { join as join7, basename as basename4, extname as extname7 } from "path";
5228
5384
  var MIME_TYPES = {
5229
5385
  ".txt": "text/plain",
5230
5386
  ".md": "text/markdown",
@@ -5266,7 +5422,7 @@ function createUploadFileTool(options) {
5266
5422
  error: "File upload is not available \u2014 remote server with GCS is not configured."
5267
5423
  };
5268
5424
  }
5269
- const fullPath = input.path.startsWith("/") ? input.path : join6(options.workingDirectory, input.path);
5425
+ const fullPath = input.path.startsWith("/") ? input.path : join7(options.workingDirectory, input.path);
5270
5426
  try {
5271
5427
  await stat4(fullPath);
5272
5428
  } catch {
@@ -5284,7 +5440,7 @@ function createUploadFileTool(options) {
5284
5440
  contentType,
5285
5441
  "general"
5286
5442
  );
5287
- const fileData = await readFile9(fullPath);
5443
+ const fileData = await readFile10(fullPath);
5288
5444
  const putRes = await fetch(uploadInfo.uploadUrl, {
5289
5445
  method: "PUT",
5290
5446
  headers: { "Content-Type": contentType },
@@ -5339,7 +5495,8 @@ async function createTools(options) {
5339
5495
  onProgress: options.onWriteFileProgress
5340
5496
  }),
5341
5497
  todo: createTodoTool({
5342
- sessionId: options.sessionId
5498
+ sessionId: options.sessionId,
5499
+ workingDirectory: options.workingDirectory
5343
5500
  }),
5344
5501
  load_skill: createLoadSkillTool({
5345
5502
  sessionId: options.sessionId,
@@ -5437,6 +5594,8 @@ async function buildSystemPrompt(options) {
5437
5594
  }
5438
5595
  const todos = await todoQueries.getBySession(sessionId);
5439
5596
  const todosContext = formatTodosForContext(todos);
5597
+ const plans = await readSessionPlans(workingDirectory, sessionId);
5598
+ const plansContext = formatPlansForContext(plans);
5440
5599
  const platform2 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
5441
5600
  const currentDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
5442
5601
  const searchInstructions = getSearchInstructions();
@@ -5453,7 +5612,7 @@ You have access to powerful tools for:
5453
5612
  - **read_file**: Read file contents to understand code and context
5454
5613
  - **write_file**: Create new files or edit existing ones (supports targeted string replacement)
5455
5614
  - **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
5456
- - **todo**: Manage your task list to track progress on complex operations
5615
+ - **todo**: Manage your task list AND persistent plans for complex multi-phase operations
5457
5616
  - **load_skill**: Load specialized knowledge documents for specific tasks
5458
5617
  - **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
5459
5618
  - **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
@@ -5462,9 +5621,23 @@ You have access to powerful tools for:
5462
5621
 
5463
5622
  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.
5464
5623
 
5465
- Use the TODO tool to manage your task list to track progress on complex operations. Always ask the user what they want to do specifically before doing it, and make a plan.
5466
- Step 1 of the plan should be researching files and understanding the components/structure of what you're working on (if you don't already have context), then after u have done that, plan out the rest of the tasks u need to do.
5467
- You can clear the todo and restart it, and do multiple things inside of one session.
5624
+ ### Planning & Task Management
5625
+ Use the **todo tool** to manage both immediate tasks AND persistent plans:
5626
+
5627
+ **For simple tasks (< 5 steps):** Just use regular todos (add/mark/clear).
5628
+
5629
+ **For complex, multi-phase tasks:** Create a persistent **plan** first.
5630
+ 1. Research the codebase to understand what you're working with
5631
+ 2. Create a plan with save_plan \u2014 a structured markdown document with phases and subtasks
5632
+ 3. Create todos from the first uncompleted phase
5633
+ 4. Work through the todos
5634
+ 5. When done, update the plan (mark completed phases with [x]), save it again
5635
+ 6. Create new todos from the next uncompleted phase
5636
+ 7. Repeat until the plan is fully complete
5637
+
5638
+ Plans persist on disk and are always injected into your context \u2014 they survive context compaction even in very long sessions. You can have multiple plans active at once (e.g., one for frontend, one for backend).
5639
+
5640
+ You can clear the todo list and restart it, and do multiple things inside of one session.
5468
5641
 
5469
5642
  ### bash Tool
5470
5643
  The bash tool runs commands in the terminal. Every command runs in its own session with logs saved to disk.
@@ -5688,6 +5861,8 @@ ${onDemandSkillsContext}
5688
5861
  ## Current Task List
5689
5862
  ${todosContext}
5690
5863
 
5864
+ ${plansContext}
5865
+
5691
5866
  ${customInstructions ? `## Custom Instructions
5692
5867
  ${customInstructions}` : ""}
5693
5868
 
@@ -5711,6 +5886,37 @@ function formatTodosForContext(todos) {
5711
5886
  }
5712
5887
  return lines.join("\n");
5713
5888
  }
5889
+ var MAX_PLAN_CHARS = 3e4;
5890
+ var MAX_TOTAL_PLANS_CHARS = 6e4;
5891
+ function formatPlansForContext(plans) {
5892
+ if (plans.length === 0) return "";
5893
+ let totalChars = 0;
5894
+ const sections = [];
5895
+ sections.push(`## Persistent Plans (${plans.length})`);
5896
+ sections.push("");
5897
+ sections.push("These plans persist across context compaction \u2014 they are always available.");
5898
+ sections.push("When you finish your current todos, check these plans for the next uncompleted phase,");
5899
+ sections.push("update the plan (mark completed items with [x]), then create new todos for the next phase.");
5900
+ sections.push("");
5901
+ for (const plan of plans) {
5902
+ let content = plan.content;
5903
+ if (content.length > MAX_PLAN_CHARS) {
5904
+ content = content.slice(0, MAX_PLAN_CHARS) + `
5905
+
5906
+ ... [plan truncated \u2014 ${content.length - MAX_PLAN_CHARS} chars omitted. Use get_plan to read the full plan.]`;
5907
+ }
5908
+ if (totalChars + content.length > MAX_TOTAL_PLANS_CHARS) {
5909
+ sections.push(`### \u{1F4CB} Plan: ${plan.name} [truncated \u2014 use get_plan("${plan.name}") to read]`);
5910
+ continue;
5911
+ }
5912
+ sections.push(`### \u{1F4CB} Plan: ${plan.name}`);
5913
+ sections.push("");
5914
+ sections.push(content);
5915
+ sections.push("");
5916
+ totalChars += content.length;
5917
+ }
5918
+ return sections.join("\n");
5919
+ }
5714
5920
  function buildTaskPromptAddendum(outputSchema) {
5715
5921
  return `
5716
5922
  ## Task Mode
@@ -5782,7 +5988,7 @@ Before calling \`complete_task\`, you MUST verify your work completely. Do not j
5782
5988
  - **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
5783
5989
  - **explore_agent**: Use for codebase exploration and understanding before making changes.
5784
5990
  - **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
5785
- - **todo**: Track your progress on multi-step tasks so you don't miss steps.
5991
+ - **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.
5786
5992
  - **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
5787
5993
  - **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.
5788
5994
 
@@ -6289,7 +6495,7 @@ async function sendWebhook(url, event) {
6289
6495
  try {
6290
6496
  const controller = new AbortController();
6291
6497
  const timeout = setTimeout(() => controller.abort(), 5e3);
6292
- await fetch(url, {
6498
+ const response = await fetch(url, {
6293
6499
  method: "POST",
6294
6500
  headers: {
6295
6501
  "Content-Type": "application/json",
@@ -6299,7 +6505,12 @@ async function sendWebhook(url, event) {
6299
6505
  signal: controller.signal
6300
6506
  });
6301
6507
  clearTimeout(timeout);
6302
- } catch {
6508
+ if (!response.ok) {
6509
+ console.warn(`[WEBHOOK] ${event.type} to ${url} returned HTTP ${response.status}`);
6510
+ }
6511
+ } catch (err) {
6512
+ const reason = err.name === "AbortError" ? "timeout (5s)" : err.message;
6513
+ console.warn(`[WEBHOOK] ${event.type} to ${url} failed: ${reason}`);
6303
6514
  }
6304
6515
  }
6305
6516
 
@@ -6553,12 +6764,14 @@ ${prompt}` });
6553
6764
  const config = getConfig();
6554
6765
  const maxIterations = options.taskConfig.maxIterations ?? 50;
6555
6766
  const webhookUrl = options.taskConfig.webhookUrl;
6767
+ const parentTaskId = options.taskConfig.parentTaskId;
6556
6768
  const fireWebhook = (type, data) => {
6557
6769
  if (!webhookUrl) return;
6558
6770
  sendWebhook(webhookUrl, {
6559
6771
  type,
6560
6772
  taskId: this.session.id,
6561
6773
  sessionId: this.session.id,
6774
+ ...parentTaskId ? { parentTaskId } : {},
6562
6775
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6563
6776
  data
6564
6777
  });
@@ -6751,14 +6964,14 @@ ${taskAddendum}`;
6751
6964
  for (const step of resultSteps) {
6752
6965
  if (step.toolCalls) {
6753
6966
  for (const tc of step.toolCalls) {
6754
- options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.args });
6755
- fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.args });
6967
+ options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input });
6968
+ fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.input });
6756
6969
  }
6757
6970
  }
6758
6971
  if (step.toolResults) {
6759
6972
  for (const tr of step.toolResults) {
6760
- options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.result });
6761
- fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.result });
6973
+ options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.output });
6974
+ fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.output });
6762
6975
  }
6763
6976
  }
6764
6977
  }
@@ -6850,14 +7063,14 @@ ${taskAddendum}`;
6850
7063
  const result = await recorder.encode();
6851
7064
  recorder.clear();
6852
7065
  if (!result) return [];
6853
- const { readFile: readFile11, unlink: unlink3 } = await import("fs/promises");
7066
+ const { readFile: readFile12, unlink: unlink3 } = await import("fs/promises");
6854
7067
  const uploadInfo = await storageQueries2.getUploadUrl(
6855
7068
  this.session.id,
6856
7069
  `browser-recording-${Date.now()}.mp4`,
6857
7070
  "video/mp4",
6858
7071
  "browser-recording"
6859
7072
  );
6860
- const fileData = await readFile11(result.path);
7073
+ const fileData = await readFile12(result.path);
6861
7074
  await fetch(uploadInfo.uploadUrl, {
6862
7075
  method: "PUT",
6863
7076
  headers: { "Content-Type": "video/mp4" },
@@ -6883,12 +7096,12 @@ ${taskAddendum}`;
6883
7096
  try {
6884
7097
  const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
6885
7098
  if (!isRemoteConfigured2()) return [];
6886
- const { readFile: readFile11 } = await import("fs/promises");
6887
- const { join: join8, basename: basename5 } = await import("path");
7099
+ const { readFile: readFile12 } = await import("fs/promises");
7100
+ const { join: join9, basename: basename5 } = await import("path");
6888
7101
  const urls = [];
6889
7102
  for (const filePath of filePaths) {
6890
7103
  try {
6891
- const fullPath = filePath.startsWith("/") ? filePath : join8(this.session.workingDirectory, filePath);
7104
+ const fullPath = filePath.startsWith("/") ? filePath : join9(this.session.workingDirectory, filePath);
6892
7105
  const fileName = basename5(fullPath);
6893
7106
  const ext = fileName.split(".").pop()?.toLowerCase() || "";
6894
7107
  const mimeMap = {
@@ -6913,7 +7126,7 @@ ${taskAddendum}`;
6913
7126
  contentType,
6914
7127
  "task-output"
6915
7128
  );
6916
- const fileData = await readFile11(fullPath);
7129
+ const fileData = await readFile12(fullPath);
6917
7130
  await fetch(uploadInfo.uploadUrl, {
6918
7131
  method: "PUT",
6919
7132
  headers: { "Content-Type": contentType },