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.
- package/dist/agent/index.js +284 -71
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +370 -144
- package/dist/cli.js.map +1 -1
- package/dist/index.js +358 -132
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +358 -132
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/browser.md +30 -0
- package/dist/tools/index.d.ts +117 -1
- package/dist/tools/index.js +183 -41
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/browser.md +30 -0
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/package-lock.json +3 -3
- /package/web/.next/standalone/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
- /package/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{CAGGCb0khU_QcA3lh6Rk6 → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
package/dist/agent/index.js
CHANGED
|
@@ -683,9 +683,9 @@ __export(skills_exports, {
|
|
|
683
683
|
loadSkillContent: () => loadSkillContent,
|
|
684
684
|
loadSkillsFromDirectory: () => loadSkillsFromDirectory
|
|
685
685
|
});
|
|
686
|
-
import { readFile as
|
|
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
|
|
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 (!
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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 || !
|
|
916
|
+
if (!agentsMdPath || !existsSync10(agentsMdPath)) {
|
|
917
917
|
return null;
|
|
918
918
|
}
|
|
919
|
-
const content = await
|
|
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
|
|
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
|
|
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
|
|
1277
|
-
import { join as
|
|
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 =
|
|
1345
|
-
if (!
|
|
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 =
|
|
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
|
|
1618
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
1680
|
-
await
|
|
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 =
|
|
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 "${
|
|
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
|
|
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(
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
3627
|
-
-
|
|
3628
|
-
-
|
|
3629
|
-
-
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
4202
|
-
import { existsSync as
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
5227
|
-
import { join as
|
|
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 :
|
|
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
|
|
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
|
|
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
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
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
|
-
|
|
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.
|
|
6755
|
-
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.
|
|
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.
|
|
6761
|
-
fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.
|
|
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:
|
|
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
|
|
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:
|
|
6887
|
-
const { join:
|
|
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 :
|
|
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
|
|
7129
|
+
const fileData = await readFile12(fullPath);
|
|
6917
7130
|
await fetch(uploadInfo.uploadUrl, {
|
|
6918
7131
|
method: "PUT",
|
|
6919
7132
|
headers: { "Content-Type": contentType },
|