sparkecoder 0.1.82 → 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/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
- /package/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_buildManifest.js +0 -0
- /package/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{Ne3ChQc_mw5oh4Y1Rr7qj → aCZCpTkVv_k-RisOFPegk}/_ssgManifest.js +0 -0
package/dist/server/index.js
CHANGED
|
@@ -854,27 +854,38 @@ function requiresApproval(toolName, sessionConfig) {
|
|
|
854
854
|
return false;
|
|
855
855
|
}
|
|
856
856
|
function loadStoredAuthKey() {
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
857
|
+
const locations = [
|
|
858
|
+
join(process.cwd(), ".sparkecoder", AUTH_KEY_FILE),
|
|
859
|
+
join(getAppDataDirectory(), AUTH_KEY_FILE)
|
|
860
|
+
];
|
|
861
|
+
for (const keysPath of locations) {
|
|
862
|
+
if (!existsSync(keysPath)) continue;
|
|
863
|
+
try {
|
|
864
|
+
const content = readFileSync(keysPath, "utf-8");
|
|
865
|
+
const data = JSON.parse(content);
|
|
866
|
+
if (data.authKey) return data.authKey;
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
867
869
|
}
|
|
870
|
+
return null;
|
|
868
871
|
}
|
|
869
872
|
function saveAuthKey(authKey3, userId) {
|
|
870
|
-
const appDir = ensureAppDataDirectory();
|
|
871
|
-
const keysPath = join(appDir, AUTH_KEY_FILE);
|
|
872
873
|
const data = {
|
|
873
874
|
authKey: authKey3,
|
|
874
875
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
875
876
|
userId
|
|
876
877
|
};
|
|
877
|
-
|
|
878
|
+
const json = JSON.stringify(data, null, 2);
|
|
879
|
+
const appDir = ensureAppDataDirectory();
|
|
880
|
+
writeFileSync(join(appDir, AUTH_KEY_FILE), json, { mode: 384 });
|
|
881
|
+
try {
|
|
882
|
+
const workspaceAuthDir = join(process.cwd(), ".sparkecoder");
|
|
883
|
+
if (!existsSync(workspaceAuthDir)) {
|
|
884
|
+
mkdirSync(workspaceAuthDir, { recursive: true });
|
|
885
|
+
}
|
|
886
|
+
writeFileSync(join(workspaceAuthDir, AUTH_KEY_FILE), json, { mode: 384 });
|
|
887
|
+
} catch {
|
|
888
|
+
}
|
|
878
889
|
}
|
|
879
890
|
async function registerWithRemoteServer(serverUrl, name) {
|
|
880
891
|
const response = await fetch(`${serverUrl}/auth/register`, {
|
|
@@ -1023,9 +1034,9 @@ __export(skills_exports, {
|
|
|
1023
1034
|
loadSkillContent: () => loadSkillContent,
|
|
1024
1035
|
loadSkillsFromDirectory: () => loadSkillsFromDirectory
|
|
1025
1036
|
});
|
|
1026
|
-
import { readFile as
|
|
1037
|
+
import { readFile as readFile7, readdir } from "fs/promises";
|
|
1027
1038
|
import { resolve as resolve6, basename, extname as extname4, relative as relative4 } from "path";
|
|
1028
|
-
import { existsSync as
|
|
1039
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1029
1040
|
import { minimatch } from "minimatch";
|
|
1030
1041
|
function parseSkillFrontmatter(content) {
|
|
1031
1042
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
@@ -1103,7 +1114,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
1103
1114
|
defaultLoadType = "on_demand",
|
|
1104
1115
|
forceAlwaysApply = false
|
|
1105
1116
|
} = options;
|
|
1106
|
-
if (!
|
|
1117
|
+
if (!existsSync10(directory)) {
|
|
1107
1118
|
return [];
|
|
1108
1119
|
}
|
|
1109
1120
|
const skills = [];
|
|
@@ -1113,7 +1124,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
1113
1124
|
let fileName;
|
|
1114
1125
|
if (entry.isDirectory()) {
|
|
1115
1126
|
const skillMdPath = resolve6(directory, entry.name, "SKILL.md");
|
|
1116
|
-
if (
|
|
1127
|
+
if (existsSync10(skillMdPath)) {
|
|
1117
1128
|
filePath = skillMdPath;
|
|
1118
1129
|
fileName = entry.name;
|
|
1119
1130
|
} else {
|
|
@@ -1125,7 +1136,7 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
1125
1136
|
} else {
|
|
1126
1137
|
continue;
|
|
1127
1138
|
}
|
|
1128
|
-
const content = await
|
|
1139
|
+
const content = await readFile7(filePath, "utf-8");
|
|
1129
1140
|
const parsed = parseSkillFrontmatter(content);
|
|
1130
1141
|
if (parsed) {
|
|
1131
1142
|
const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
|
|
@@ -1204,7 +1215,7 @@ async function loadAllSkillsFromDiscovered(discovered) {
|
|
|
1204
1215
|
const onDemandSkills = allSkills.filter((s) => !s.alwaysApply && s.loadType !== "always");
|
|
1205
1216
|
const alwaysWithContent = await Promise.all(
|
|
1206
1217
|
alwaysSkills.map(async (skill) => {
|
|
1207
|
-
const content = await
|
|
1218
|
+
const content = await readFile7(skill.filePath, "utf-8");
|
|
1208
1219
|
const parsed = parseSkillFrontmatter(content);
|
|
1209
1220
|
return {
|
|
1210
1221
|
...skill,
|
|
@@ -1241,7 +1252,7 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
|
|
|
1241
1252
|
});
|
|
1242
1253
|
const matchedWithContent = await Promise.all(
|
|
1243
1254
|
matchedSkills.map(async (skill) => {
|
|
1244
|
-
const content = await
|
|
1255
|
+
const content = await readFile7(skill.filePath, "utf-8");
|
|
1245
1256
|
const parsed = parseSkillFrontmatter(content);
|
|
1246
1257
|
return {
|
|
1247
1258
|
...skill,
|
|
@@ -1253,10 +1264,10 @@ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
|
|
|
1253
1264
|
return matchedWithContent;
|
|
1254
1265
|
}
|
|
1255
1266
|
async function loadAgentsMd(agentsMdPath) {
|
|
1256
|
-
if (!agentsMdPath || !
|
|
1267
|
+
if (!agentsMdPath || !existsSync10(agentsMdPath)) {
|
|
1257
1268
|
return null;
|
|
1258
1269
|
}
|
|
1259
|
-
const content = await
|
|
1270
|
+
const content = await readFile7(agentsMdPath, "utf-8");
|
|
1260
1271
|
return content;
|
|
1261
1272
|
}
|
|
1262
1273
|
async function loadSkillContent(skillName, directories) {
|
|
@@ -1267,7 +1278,7 @@ async function loadSkillContent(skillName, directories) {
|
|
|
1267
1278
|
if (!skill) {
|
|
1268
1279
|
return null;
|
|
1269
1280
|
}
|
|
1270
|
-
const content = await
|
|
1281
|
+
const content = await readFile7(skill.filePath, "utf-8");
|
|
1271
1282
|
const parsed = parseSkillFrontmatter(content);
|
|
1272
1283
|
return {
|
|
1273
1284
|
...skill,
|
|
@@ -1528,7 +1539,7 @@ var init_client = __esm({
|
|
|
1528
1539
|
});
|
|
1529
1540
|
|
|
1530
1541
|
// src/semantic/indexer.ts
|
|
1531
|
-
import { readFileSync as
|
|
1542
|
+
import { readFileSync as readFileSync5, statSync } from "fs";
|
|
1532
1543
|
import { relative as relative6 } from "path";
|
|
1533
1544
|
import { minimatch as minimatch2 } from "minimatch";
|
|
1534
1545
|
async function getIndexStatus(workingDirectory) {
|
|
@@ -1613,8 +1624,8 @@ __export(semantic_search_exports, {
|
|
|
1613
1624
|
});
|
|
1614
1625
|
import { tool as tool8 } from "ai";
|
|
1615
1626
|
import { z as z9 } from "zod";
|
|
1616
|
-
import { existsSync as
|
|
1617
|
-
import { join as
|
|
1627
|
+
import { existsSync as existsSync13, readFileSync as readFileSync6 } from "fs";
|
|
1628
|
+
import { join as join6 } from "path";
|
|
1618
1629
|
import { minimatch as minimatch3 } from "minimatch";
|
|
1619
1630
|
function createSemanticSearchTool(options) {
|
|
1620
1631
|
return tool8({
|
|
@@ -1681,13 +1692,13 @@ Returns matching code snippets with file paths, line numbers, and relevance scor
|
|
|
1681
1692
|
if (language && matchLanguage !== language.toLowerCase()) {
|
|
1682
1693
|
continue;
|
|
1683
1694
|
}
|
|
1684
|
-
const fullPath =
|
|
1685
|
-
if (!
|
|
1695
|
+
const fullPath = join6(options.workingDirectory, filePath);
|
|
1696
|
+
if (!existsSync13(fullPath)) {
|
|
1686
1697
|
continue;
|
|
1687
1698
|
}
|
|
1688
1699
|
let snippet = "";
|
|
1689
1700
|
try {
|
|
1690
|
-
const content =
|
|
1701
|
+
const content = readFileSync6(fullPath, "utf-8");
|
|
1691
1702
|
const lines = content.split("\n");
|
|
1692
1703
|
const snippetLines = lines.slice(
|
|
1693
1704
|
Math.max(0, startLine - 1),
|
|
@@ -1755,7 +1766,7 @@ async function sendWebhook(url, event) {
|
|
|
1755
1766
|
try {
|
|
1756
1767
|
const controller = new AbortController();
|
|
1757
1768
|
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
1758
|
-
await fetch(url, {
|
|
1769
|
+
const response = await fetch(url, {
|
|
1759
1770
|
method: "POST",
|
|
1760
1771
|
headers: {
|
|
1761
1772
|
"Content-Type": "application/json",
|
|
@@ -1765,7 +1776,12 @@ async function sendWebhook(url, event) {
|
|
|
1765
1776
|
signal: controller.signal
|
|
1766
1777
|
});
|
|
1767
1778
|
clearTimeout(timeout);
|
|
1768
|
-
|
|
1779
|
+
if (!response.ok) {
|
|
1780
|
+
console.warn(`[WEBHOOK] ${event.type} to ${url} returned HTTP ${response.status}`);
|
|
1781
|
+
}
|
|
1782
|
+
} catch (err) {
|
|
1783
|
+
const reason = err.name === "AbortError" ? "timeout (5s)" : err.message;
|
|
1784
|
+
console.warn(`[WEBHOOK] ${event.type} to ${url} failed: ${reason}`);
|
|
1769
1785
|
}
|
|
1770
1786
|
}
|
|
1771
1787
|
var init_webhook = __esm({
|
|
@@ -1982,8 +1998,8 @@ __export(recorder_exports, {
|
|
|
1982
1998
|
});
|
|
1983
1999
|
import { exec as exec5 } from "child_process";
|
|
1984
2000
|
import { promisify as promisify5 } from "util";
|
|
1985
|
-
import { writeFile as
|
|
1986
|
-
import { join as
|
|
2001
|
+
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
2002
|
+
import { join as join8 } from "path";
|
|
1987
2003
|
import { tmpdir } from "os";
|
|
1988
2004
|
import { nanoid as nanoid3 } from "nanoid";
|
|
1989
2005
|
async function checkFfmpeg() {
|
|
@@ -2040,21 +2056,21 @@ var init_recorder = __esm({
|
|
|
2040
2056
|
*/
|
|
2041
2057
|
async encode() {
|
|
2042
2058
|
if (this.frames.length === 0) return null;
|
|
2043
|
-
const workDir =
|
|
2059
|
+
const workDir = join8(tmpdir(), `sparkecoder-recording-${nanoid3(8)}`);
|
|
2044
2060
|
await mkdir4(workDir, { recursive: true });
|
|
2045
2061
|
try {
|
|
2046
2062
|
for (let i = 0; i < this.frames.length; i++) {
|
|
2047
|
-
const framePath =
|
|
2048
|
-
await
|
|
2063
|
+
const framePath = join8(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
2064
|
+
await writeFile5(framePath, this.frames[i].data);
|
|
2049
2065
|
}
|
|
2050
2066
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
2051
2067
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
2052
2068
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
2053
|
-
const outputPath =
|
|
2069
|
+
const outputPath = join8(workDir, `recording_${this.sessionId}.mp4`);
|
|
2054
2070
|
const hasFfmpeg = await checkFfmpeg();
|
|
2055
2071
|
if (hasFfmpeg) {
|
|
2056
2072
|
await execAsync5(
|
|
2057
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
2073
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join8(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
2058
2074
|
{ timeout: 12e4 }
|
|
2059
2075
|
);
|
|
2060
2076
|
} else {
|
|
@@ -2062,11 +2078,11 @@ var init_recorder = __esm({
|
|
|
2062
2078
|
await cleanup(workDir);
|
|
2063
2079
|
return null;
|
|
2064
2080
|
}
|
|
2065
|
-
const outputBuf = await
|
|
2081
|
+
const outputBuf = await readFile11(outputPath);
|
|
2066
2082
|
const files = await readdir5(workDir);
|
|
2067
2083
|
for (const f of files) {
|
|
2068
2084
|
if (f.startsWith("frame_")) {
|
|
2069
|
-
await unlink2(
|
|
2085
|
+
await unlink2(join8(workDir, f)).catch(() => {
|
|
2070
2086
|
});
|
|
2071
2087
|
}
|
|
2072
2088
|
}
|
|
@@ -2093,8 +2109,8 @@ import { Hono as Hono6 } from "hono";
|
|
|
2093
2109
|
import { serve } from "@hono/node-server";
|
|
2094
2110
|
import { cors } from "hono/cors";
|
|
2095
2111
|
import { logger } from "hono/logger";
|
|
2096
|
-
import { existsSync as
|
|
2097
|
-
import { resolve as resolve10, dirname as dirname7, join as
|
|
2112
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
2113
|
+
import { resolve as resolve10, dirname as dirname7, join as join12 } from "path";
|
|
2098
2114
|
import { spawn as spawn2 } from "child_process";
|
|
2099
2115
|
import { createServer as createNetServer } from "net";
|
|
2100
2116
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -2104,9 +2120,9 @@ init_db();
|
|
|
2104
2120
|
import { Hono } from "hono";
|
|
2105
2121
|
import { zValidator } from "@hono/zod-validator";
|
|
2106
2122
|
import { z as z15 } from "zod";
|
|
2107
|
-
import { existsSync as
|
|
2123
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2108
2124
|
import { readdir as readdir6 } from "fs/promises";
|
|
2109
|
-
import { join as
|
|
2125
|
+
import { join as join9, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
2110
2126
|
import { nanoid as nanoid5 } from "nanoid";
|
|
2111
2127
|
|
|
2112
2128
|
// src/agent/index.ts
|
|
@@ -4173,8 +4189,34 @@ Working directory: ${options.workingDirectory}`,
|
|
|
4173
4189
|
init_db();
|
|
4174
4190
|
import { tool as tool4 } from "ai";
|
|
4175
4191
|
import { z as z5 } from "zod";
|
|
4192
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync, unlinkSync, readFileSync as readFileSync3, appendFileSync } from "fs";
|
|
4193
|
+
import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
4194
|
+
import { join as join4 } from "path";
|
|
4195
|
+
function getPlansDir(workingDirectory, sessionId) {
|
|
4196
|
+
return join4(workingDirectory, ".sparkecoder", "plans", sessionId);
|
|
4197
|
+
}
|
|
4198
|
+
function ensurePlansDir(workingDirectory, sessionId) {
|
|
4199
|
+
const dir = getPlansDir(workingDirectory, sessionId);
|
|
4200
|
+
if (!existsSync9(dir)) {
|
|
4201
|
+
mkdirSync4(dir, { recursive: true });
|
|
4202
|
+
}
|
|
4203
|
+
const gitignorePath = join4(workingDirectory, ".gitignore");
|
|
4204
|
+
if (existsSync9(gitignorePath)) {
|
|
4205
|
+
try {
|
|
4206
|
+
const content = readFileSync3(gitignorePath, "utf-8");
|
|
4207
|
+
if (!content.includes(".sparkecoder")) {
|
|
4208
|
+
appendFileSync(gitignorePath, "\n.sparkecoder/\n");
|
|
4209
|
+
}
|
|
4210
|
+
} catch {
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
return dir;
|
|
4214
|
+
}
|
|
4215
|
+
function slugify(name) {
|
|
4216
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "plan";
|
|
4217
|
+
}
|
|
4176
4218
|
var todoInputSchema = z5.object({
|
|
4177
|
-
action: z5.enum(["add", "list", "mark", "clear"]).describe("The action to perform
|
|
4219
|
+
action: z5.enum(["add", "list", "mark", "clear", "save_plan", "list_plans", "get_plan", "delete_plan"]).describe("The action to perform"),
|
|
4178
4220
|
items: z5.array(
|
|
4179
4221
|
z5.object({
|
|
4180
4222
|
content: z5.string().describe("Description of the task"),
|
|
@@ -4182,27 +4224,67 @@ var todoInputSchema = z5.object({
|
|
|
4182
4224
|
})
|
|
4183
4225
|
).optional().describe('For "add" action: Array of todo items to add'),
|
|
4184
4226
|
todoId: z5.string().optional().describe('For "mark" action: The ID of the todo item to update'),
|
|
4185
|
-
status: z5.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe('For "mark" action: The new status for the todo item')
|
|
4227
|
+
status: z5.enum(["pending", "in_progress", "completed", "cancelled"]).optional().describe('For "mark" action: The new status for the todo item'),
|
|
4228
|
+
planName: z5.string().optional().describe('For plan actions: Name of the plan (e.g. "auth-system", "db-migration")'),
|
|
4229
|
+
planContent: z5.string().optional().describe('For "save_plan": Full plan content as markdown with hierarchical tasks using checkboxes')
|
|
4186
4230
|
});
|
|
4187
4231
|
function createTodoTool(options) {
|
|
4188
4232
|
return tool4({
|
|
4189
|
-
description: `Manage your task list for the current session.
|
|
4190
|
-
- Break down complex tasks into smaller steps
|
|
4191
|
-
- Track progress on multi-step operations
|
|
4192
|
-
- Organize your work systematically
|
|
4233
|
+
description: `Manage your task list and persistent plans for the current session.
|
|
4193
4234
|
|
|
4194
|
-
|
|
4235
|
+
## Todo Actions (for tracking current work)
|
|
4195
4236
|
- "add": Add one or more new todo items to the list
|
|
4196
4237
|
- "list": View all current todo items and their status
|
|
4197
4238
|
- "mark": Update the status of a todo item (pending, in_progress, completed, cancelled)
|
|
4198
4239
|
- "clear": Remove all todo items from the list
|
|
4199
4240
|
|
|
4200
|
-
|
|
4201
|
-
-
|
|
4202
|
-
-
|
|
4203
|
-
-
|
|
4241
|
+
## Plan Actions (for complex, multi-phase work)
|
|
4242
|
+
- "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.
|
|
4243
|
+
- "list_plans": List all plans for this session
|
|
4244
|
+
- "get_plan": Read a specific plan by name
|
|
4245
|
+
- "delete_plan": Remove a plan
|
|
4246
|
+
|
|
4247
|
+
## Plans vs Todos
|
|
4248
|
+
- **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.
|
|
4249
|
+
- **Todos** are your current focus \u2014 the immediate steps you're working on right now.
|
|
4250
|
+
|
|
4251
|
+
## Workflow for complex tasks
|
|
4252
|
+
1. Create a plan with phases and subtasks (save_plan)
|
|
4253
|
+
2. Create todos from the first uncompleted phase (add)
|
|
4254
|
+
3. Work through the todos, marking them as you go
|
|
4255
|
+
4. When all current todos are done, update the plan (mark completed sections with [x]) and save it
|
|
4256
|
+
5. Create new todos from the next uncompleted phase
|
|
4257
|
+
6. Repeat until the plan is fully complete
|
|
4258
|
+
|
|
4259
|
+
## Plan format
|
|
4260
|
+
Plans should be markdown with this structure:
|
|
4261
|
+
\`\`\`markdown
|
|
4262
|
+
# Plan: [Title]
|
|
4263
|
+
|
|
4264
|
+
## Overview
|
|
4265
|
+
[What we're doing and why]
|
|
4266
|
+
|
|
4267
|
+
## Phase 1: [Name] [completed]
|
|
4268
|
+
- [x] Task 1
|
|
4269
|
+
- [x] Task 2
|
|
4270
|
+
|
|
4271
|
+
## Phase 2: [Name] [in_progress]
|
|
4272
|
+
- [x] Subtask 2.1
|
|
4273
|
+
- [ ] Subtask 2.2
|
|
4274
|
+
- [ ] Sub-subtask 2.2.1
|
|
4275
|
+
- [ ] Sub-subtask 2.2.2
|
|
4276
|
+
- [ ] Subtask 2.3
|
|
4277
|
+
|
|
4278
|
+
## Phase 3: [Name] [pending]
|
|
4279
|
+
- [ ] Task 1
|
|
4280
|
+
- [ ] Task 2
|
|
4281
|
+
|
|
4282
|
+
## Notes
|
|
4283
|
+
- Key decisions and context to preserve
|
|
4284
|
+
- Important file paths discovered
|
|
4285
|
+
\`\`\``,
|
|
4204
4286
|
inputSchema: todoInputSchema,
|
|
4205
|
-
execute: async ({ action, items, todoId, status }) => {
|
|
4287
|
+
execute: async ({ action, items, todoId, status, planName, planContent }) => {
|
|
4206
4288
|
try {
|
|
4207
4289
|
switch (action) {
|
|
4208
4290
|
case "add": {
|
|
@@ -4270,6 +4352,81 @@ Best practices:
|
|
|
4270
4352
|
itemsRemoved: count
|
|
4271
4353
|
};
|
|
4272
4354
|
}
|
|
4355
|
+
// ── Plan actions ─────────────────────────────────────────
|
|
4356
|
+
case "save_plan": {
|
|
4357
|
+
if (!planName) {
|
|
4358
|
+
return { success: false, error: 'planName is required for "save_plan"' };
|
|
4359
|
+
}
|
|
4360
|
+
if (!planContent) {
|
|
4361
|
+
return { success: false, error: 'planContent is required for "save_plan"' };
|
|
4362
|
+
}
|
|
4363
|
+
const dir = ensurePlansDir(options.workingDirectory, options.sessionId);
|
|
4364
|
+
const filename = `${slugify(planName)}.md`;
|
|
4365
|
+
const filePath = join4(dir, filename);
|
|
4366
|
+
await writeFile4(filePath, planContent, "utf-8");
|
|
4367
|
+
return {
|
|
4368
|
+
success: true,
|
|
4369
|
+
action: "save_plan",
|
|
4370
|
+
planName,
|
|
4371
|
+
filename,
|
|
4372
|
+
path: filePath,
|
|
4373
|
+
sizeChars: planContent.length
|
|
4374
|
+
};
|
|
4375
|
+
}
|
|
4376
|
+
case "list_plans": {
|
|
4377
|
+
const dir = getPlansDir(options.workingDirectory, options.sessionId);
|
|
4378
|
+
if (!existsSync9(dir)) {
|
|
4379
|
+
return { success: true, action: "list_plans", plans: [], count: 0 };
|
|
4380
|
+
}
|
|
4381
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
4382
|
+
const plans = [];
|
|
4383
|
+
for (const f of files) {
|
|
4384
|
+
try {
|
|
4385
|
+
const content = await readFile6(join4(dir, f), "utf-8");
|
|
4386
|
+
const titleMatch = content.match(/^#\s+(?:Plan:\s*)?(.+)/m);
|
|
4387
|
+
plans.push({
|
|
4388
|
+
name: f.replace(/\.md$/, ""),
|
|
4389
|
+
title: titleMatch?.[1]?.trim() || f.replace(/\.md$/, ""),
|
|
4390
|
+
filename: f,
|
|
4391
|
+
sizeChars: content.length
|
|
4392
|
+
});
|
|
4393
|
+
} catch {
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
return { success: true, action: "list_plans", plans, count: plans.length };
|
|
4397
|
+
}
|
|
4398
|
+
case "get_plan": {
|
|
4399
|
+
if (!planName) {
|
|
4400
|
+
return { success: false, error: 'planName is required for "get_plan"' };
|
|
4401
|
+
}
|
|
4402
|
+
const dir = getPlansDir(options.workingDirectory, options.sessionId);
|
|
4403
|
+
const filename = `${slugify(planName)}.md`;
|
|
4404
|
+
const filePath = join4(dir, filename);
|
|
4405
|
+
if (!existsSync9(filePath)) {
|
|
4406
|
+
return { success: false, error: `Plan not found: "${planName}" (looked for ${filename})` };
|
|
4407
|
+
}
|
|
4408
|
+
const content = await readFile6(filePath, "utf-8");
|
|
4409
|
+
return {
|
|
4410
|
+
success: true,
|
|
4411
|
+
action: "get_plan",
|
|
4412
|
+
planName,
|
|
4413
|
+
content,
|
|
4414
|
+
sizeChars: content.length
|
|
4415
|
+
};
|
|
4416
|
+
}
|
|
4417
|
+
case "delete_plan": {
|
|
4418
|
+
if (!planName) {
|
|
4419
|
+
return { success: false, error: 'planName is required for "delete_plan"' };
|
|
4420
|
+
}
|
|
4421
|
+
const dir = getPlansDir(options.workingDirectory, options.sessionId);
|
|
4422
|
+
const filename = `${slugify(planName)}.md`;
|
|
4423
|
+
const filePath = join4(dir, filename);
|
|
4424
|
+
if (!existsSync9(filePath)) {
|
|
4425
|
+
return { success: false, error: `Plan not found: "${planName}"` };
|
|
4426
|
+
}
|
|
4427
|
+
unlinkSync(filePath);
|
|
4428
|
+
return { success: true, action: "delete_plan", planName, deleted: true };
|
|
4429
|
+
}
|
|
4273
4430
|
default:
|
|
4274
4431
|
return {
|
|
4275
4432
|
success: false,
|
|
@@ -4294,6 +4451,21 @@ function formatTodoItem(item) {
|
|
|
4294
4451
|
createdAt: item.createdAt.toISOString()
|
|
4295
4452
|
};
|
|
4296
4453
|
}
|
|
4454
|
+
async function readSessionPlans(workingDirectory, sessionId) {
|
|
4455
|
+
const dir = getPlansDir(workingDirectory, sessionId);
|
|
4456
|
+
if (!existsSync9(dir)) return [];
|
|
4457
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
4458
|
+
if (files.length === 0) return [];
|
|
4459
|
+
const plans = [];
|
|
4460
|
+
for (const f of files) {
|
|
4461
|
+
try {
|
|
4462
|
+
const content = await readFile6(join4(dir, f), "utf-8");
|
|
4463
|
+
plans.push({ name: f.replace(/\.md$/, ""), content });
|
|
4464
|
+
} catch {
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
return plans;
|
|
4468
|
+
}
|
|
4297
4469
|
|
|
4298
4470
|
// src/tools/load-skill.ts
|
|
4299
4471
|
init_skills();
|
|
@@ -4383,7 +4555,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
4383
4555
|
import { tool as tool6 } from "ai";
|
|
4384
4556
|
import { z as z7 } from "zod";
|
|
4385
4557
|
import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname5 } from "path";
|
|
4386
|
-
import { existsSync as
|
|
4558
|
+
import { existsSync as existsSync11 } from "fs";
|
|
4387
4559
|
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
4388
4560
|
var linterInputSchema = z7.object({
|
|
4389
4561
|
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."),
|
|
@@ -4451,7 +4623,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
4451
4623
|
const filesToCheck = [];
|
|
4452
4624
|
for (const path of paths) {
|
|
4453
4625
|
const absolutePath = isAbsolute3(path) ? path : resolve7(options.workingDirectory, path);
|
|
4454
|
-
if (!
|
|
4626
|
+
if (!existsSync11(absolutePath)) {
|
|
4455
4627
|
continue;
|
|
4456
4628
|
}
|
|
4457
4629
|
const stats = await stat2(absolutePath);
|
|
@@ -4763,17 +4935,17 @@ import { tool as tool9 } from "ai";
|
|
|
4763
4935
|
import { z as z10 } from "zod";
|
|
4764
4936
|
import { exec as exec4 } from "child_process";
|
|
4765
4937
|
import { promisify as promisify4 } from "util";
|
|
4766
|
-
import { readFile as
|
|
4938
|
+
import { readFile as readFile9, stat as stat3, readdir as readdir4 } from "fs/promises";
|
|
4767
4939
|
import { resolve as resolve9, relative as relative8, isAbsolute as isAbsolute5 } from "path";
|
|
4768
|
-
import { existsSync as
|
|
4940
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4769
4941
|
init_semantic();
|
|
4770
4942
|
|
|
4771
4943
|
// src/tools/code-graph.ts
|
|
4772
4944
|
import { tool as tool7 } from "ai";
|
|
4773
4945
|
import { z as z8 } from "zod";
|
|
4774
4946
|
import { resolve as resolve8, relative as relative7, isAbsolute as isAbsolute4, basename as basename3 } from "path";
|
|
4775
|
-
import { readFile as
|
|
4776
|
-
import { existsSync as
|
|
4947
|
+
import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
|
|
4948
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4777
4949
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4778
4950
|
import { execFileSync } from "child_process";
|
|
4779
4951
|
var codeGraphInputSchema = z8.object({
|
|
@@ -4910,7 +5082,7 @@ async function grepForSymbol(symbol, workingDirectory) {
|
|
|
4910
5082
|
const ext = entry.name.substring(entry.name.lastIndexOf("."));
|
|
4911
5083
|
if (!SUPPORTED_EXTS.has(ext)) continue;
|
|
4912
5084
|
remaining--;
|
|
4913
|
-
const content = await
|
|
5085
|
+
const content = await readFile8(fullPath, "utf-8");
|
|
4914
5086
|
const lines = content.split("\n");
|
|
4915
5087
|
for (let i = 0; i < lines.length; i++) {
|
|
4916
5088
|
if (defPattern.test(lines[i])) {
|
|
@@ -4959,7 +5131,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
4959
5131
|
let defSymbol = null;
|
|
4960
5132
|
if (filePath) {
|
|
4961
5133
|
const absPath = isAbsolute4(filePath) ? filePath : resolve8(options.workingDirectory, filePath);
|
|
4962
|
-
if (!
|
|
5134
|
+
if (!existsSync12(absPath)) {
|
|
4963
5135
|
return { success: false, error: `File not found: ${filePath}` };
|
|
4964
5136
|
}
|
|
4965
5137
|
if (!isSupported(absPath)) {
|
|
@@ -4973,7 +5145,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
4973
5145
|
defLine = defSymbol.selectionRange.start.line;
|
|
4974
5146
|
defChar = defSymbol.selectionRange.start.character;
|
|
4975
5147
|
} else {
|
|
4976
|
-
const content = await
|
|
5148
|
+
const content = await readFile8(absPath, "utf-8");
|
|
4977
5149
|
const lines2 = content.split("\n");
|
|
4978
5150
|
const defPattern = new RegExp(
|
|
4979
5151
|
`(export|function|const|let|var|class|interface|type|enum)\\s+.*\\b${symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`
|
|
@@ -5386,7 +5558,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
5386
5558
|
execute: async ({ path, startLine, endLine }) => {
|
|
5387
5559
|
try {
|
|
5388
5560
|
const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
|
|
5389
|
-
if (!
|
|
5561
|
+
if (!existsSync14(absolutePath)) {
|
|
5390
5562
|
return {
|
|
5391
5563
|
success: false,
|
|
5392
5564
|
error: `File not found: ${path}`
|
|
@@ -5399,7 +5571,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
5399
5571
|
error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`
|
|
5400
5572
|
};
|
|
5401
5573
|
}
|
|
5402
|
-
let content = await
|
|
5574
|
+
let content = await readFile9(absolutePath, "utf-8");
|
|
5403
5575
|
if (startLine !== void 0 || endLine !== void 0) {
|
|
5404
5576
|
const lines = content.split("\n");
|
|
5405
5577
|
const start = (startLine ?? 1) - 1;
|
|
@@ -5430,7 +5602,7 @@ Keep it concise but INCLUDE THE ACTUAL DATA.`;
|
|
|
5430
5602
|
execute: async ({ path, recursive, maxDepth }) => {
|
|
5431
5603
|
try {
|
|
5432
5604
|
const absolutePath = isAbsolute5(path) ? path : resolve9(workingDirectory, path);
|
|
5433
|
-
if (!
|
|
5605
|
+
if (!existsSync14(absolutePath)) {
|
|
5434
5606
|
return {
|
|
5435
5607
|
success: false,
|
|
5436
5608
|
error: `Directory not found: ${path}`
|
|
@@ -5797,8 +5969,8 @@ function createTaskFailedTool(options) {
|
|
|
5797
5969
|
// src/tools/upload-file.ts
|
|
5798
5970
|
import { tool as tool12 } from "ai";
|
|
5799
5971
|
import { z as z13 } from "zod";
|
|
5800
|
-
import { readFile as
|
|
5801
|
-
import { join as
|
|
5972
|
+
import { readFile as readFile10, stat as stat4 } from "fs/promises";
|
|
5973
|
+
import { join as join7, basename as basename4, extname as extname7 } from "path";
|
|
5802
5974
|
var MIME_TYPES = {
|
|
5803
5975
|
".txt": "text/plain",
|
|
5804
5976
|
".md": "text/markdown",
|
|
@@ -5840,7 +6012,7 @@ function createUploadFileTool(options) {
|
|
|
5840
6012
|
error: "File upload is not available \u2014 remote server with GCS is not configured."
|
|
5841
6013
|
};
|
|
5842
6014
|
}
|
|
5843
|
-
const fullPath = input.path.startsWith("/") ? input.path :
|
|
6015
|
+
const fullPath = input.path.startsWith("/") ? input.path : join7(options.workingDirectory, input.path);
|
|
5844
6016
|
try {
|
|
5845
6017
|
await stat4(fullPath);
|
|
5846
6018
|
} catch {
|
|
@@ -5858,7 +6030,7 @@ function createUploadFileTool(options) {
|
|
|
5858
6030
|
contentType,
|
|
5859
6031
|
"general"
|
|
5860
6032
|
);
|
|
5861
|
-
const fileData = await
|
|
6033
|
+
const fileData = await readFile10(fullPath);
|
|
5862
6034
|
const putRes = await fetch(uploadInfo.uploadUrl, {
|
|
5863
6035
|
method: "PUT",
|
|
5864
6036
|
headers: { "Content-Type": contentType },
|
|
@@ -5913,7 +6085,8 @@ async function createTools(options) {
|
|
|
5913
6085
|
onProgress: options.onWriteFileProgress
|
|
5914
6086
|
}),
|
|
5915
6087
|
todo: createTodoTool({
|
|
5916
|
-
sessionId: options.sessionId
|
|
6088
|
+
sessionId: options.sessionId,
|
|
6089
|
+
workingDirectory: options.workingDirectory
|
|
5917
6090
|
}),
|
|
5918
6091
|
load_skill: createLoadSkillTool({
|
|
5919
6092
|
sessionId: options.sessionId,
|
|
@@ -6011,6 +6184,8 @@ async function buildSystemPrompt(options) {
|
|
|
6011
6184
|
}
|
|
6012
6185
|
const todos = await todoQueries.getBySession(sessionId);
|
|
6013
6186
|
const todosContext = formatTodosForContext(todos);
|
|
6187
|
+
const plans = await readSessionPlans(workingDirectory, sessionId);
|
|
6188
|
+
const plansContext = formatPlansForContext(plans);
|
|
6014
6189
|
const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
|
|
6015
6190
|
const currentDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" });
|
|
6016
6191
|
const searchInstructions = getSearchInstructions();
|
|
@@ -6027,7 +6202,7 @@ You have access to powerful tools for:
|
|
|
6027
6202
|
- **read_file**: Read file contents to understand code and context
|
|
6028
6203
|
- **write_file**: Create new files or edit existing ones (supports targeted string replacement)
|
|
6029
6204
|
- **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
|
|
6030
|
-
- **todo**: Manage your task list
|
|
6205
|
+
- **todo**: Manage your task list AND persistent plans for complex multi-phase operations
|
|
6031
6206
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
6032
6207
|
- **explore_agent**: Explore agent for semantic discovery - for exploratory questions and finding code by meaning
|
|
6033
6208
|
- **code_graph**: Inspect a symbol's type hierarchy and usage graph via the TypeScript language server
|
|
@@ -6036,9 +6211,23 @@ You have access to powerful tools for:
|
|
|
6036
6211
|
|
|
6037
6212
|
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.
|
|
6038
6213
|
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6214
|
+
### Planning & Task Management
|
|
6215
|
+
Use the **todo tool** to manage both immediate tasks AND persistent plans:
|
|
6216
|
+
|
|
6217
|
+
**For simple tasks (< 5 steps):** Just use regular todos (add/mark/clear).
|
|
6218
|
+
|
|
6219
|
+
**For complex, multi-phase tasks:** Create a persistent **plan** first.
|
|
6220
|
+
1. Research the codebase to understand what you're working with
|
|
6221
|
+
2. Create a plan with save_plan \u2014 a structured markdown document with phases and subtasks
|
|
6222
|
+
3. Create todos from the first uncompleted phase
|
|
6223
|
+
4. Work through the todos
|
|
6224
|
+
5. When done, update the plan (mark completed phases with [x]), save it again
|
|
6225
|
+
6. Create new todos from the next uncompleted phase
|
|
6226
|
+
7. Repeat until the plan is fully complete
|
|
6227
|
+
|
|
6228
|
+
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).
|
|
6229
|
+
|
|
6230
|
+
You can clear the todo list and restart it, and do multiple things inside of one session.
|
|
6042
6231
|
|
|
6043
6232
|
### bash Tool
|
|
6044
6233
|
The bash tool runs commands in the terminal. Every command runs in its own session with logs saved to disk.
|
|
@@ -6262,6 +6451,8 @@ ${onDemandSkillsContext}
|
|
|
6262
6451
|
## Current Task List
|
|
6263
6452
|
${todosContext}
|
|
6264
6453
|
|
|
6454
|
+
${plansContext}
|
|
6455
|
+
|
|
6265
6456
|
${customInstructions ? `## Custom Instructions
|
|
6266
6457
|
${customInstructions}` : ""}
|
|
6267
6458
|
|
|
@@ -6285,6 +6476,37 @@ function formatTodosForContext(todos) {
|
|
|
6285
6476
|
}
|
|
6286
6477
|
return lines.join("\n");
|
|
6287
6478
|
}
|
|
6479
|
+
var MAX_PLAN_CHARS = 3e4;
|
|
6480
|
+
var MAX_TOTAL_PLANS_CHARS = 6e4;
|
|
6481
|
+
function formatPlansForContext(plans) {
|
|
6482
|
+
if (plans.length === 0) return "";
|
|
6483
|
+
let totalChars = 0;
|
|
6484
|
+
const sections = [];
|
|
6485
|
+
sections.push(`## Persistent Plans (${plans.length})`);
|
|
6486
|
+
sections.push("");
|
|
6487
|
+
sections.push("These plans persist across context compaction \u2014 they are always available.");
|
|
6488
|
+
sections.push("When you finish your current todos, check these plans for the next uncompleted phase,");
|
|
6489
|
+
sections.push("update the plan (mark completed items with [x]), then create new todos for the next phase.");
|
|
6490
|
+
sections.push("");
|
|
6491
|
+
for (const plan of plans) {
|
|
6492
|
+
let content = plan.content;
|
|
6493
|
+
if (content.length > MAX_PLAN_CHARS) {
|
|
6494
|
+
content = content.slice(0, MAX_PLAN_CHARS) + `
|
|
6495
|
+
|
|
6496
|
+
... [plan truncated \u2014 ${content.length - MAX_PLAN_CHARS} chars omitted. Use get_plan to read the full plan.]`;
|
|
6497
|
+
}
|
|
6498
|
+
if (totalChars + content.length > MAX_TOTAL_PLANS_CHARS) {
|
|
6499
|
+
sections.push(`### \u{1F4CB} Plan: ${plan.name} [truncated \u2014 use get_plan("${plan.name}") to read]`);
|
|
6500
|
+
continue;
|
|
6501
|
+
}
|
|
6502
|
+
sections.push(`### \u{1F4CB} Plan: ${plan.name}`);
|
|
6503
|
+
sections.push("");
|
|
6504
|
+
sections.push(content);
|
|
6505
|
+
sections.push("");
|
|
6506
|
+
totalChars += content.length;
|
|
6507
|
+
}
|
|
6508
|
+
return sections.join("\n");
|
|
6509
|
+
}
|
|
6288
6510
|
function buildTaskPromptAddendum(outputSchema) {
|
|
6289
6511
|
return `
|
|
6290
6512
|
## Task Mode
|
|
@@ -6356,7 +6578,7 @@ Before calling \`complete_task\`, you MUST verify your work completely. Do not j
|
|
|
6356
6578
|
- **load_skill**: Load specialized skills/knowledge relevant to the task. Check what skills are available and use them.
|
|
6357
6579
|
- **explore_agent**: Use for codebase exploration and understanding before making changes.
|
|
6358
6580
|
- **code_graph**: Use to understand type hierarchies, references, and impact before refactoring.
|
|
6359
|
-
- **todo**: Track your progress on multi-step tasks so you don't miss steps.
|
|
6581
|
+
- **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.
|
|
6360
6582
|
- **bash**: Full shell access \u2014 run builds, tests, dev servers, open browsers, curl endpoints, anything.
|
|
6361
6583
|
- **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.
|
|
6362
6584
|
|
|
@@ -7109,12 +7331,14 @@ ${prompt}` });
|
|
|
7109
7331
|
const config = getConfig();
|
|
7110
7332
|
const maxIterations = options.taskConfig.maxIterations ?? 50;
|
|
7111
7333
|
const webhookUrl = options.taskConfig.webhookUrl;
|
|
7334
|
+
const parentTaskId = options.taskConfig.parentTaskId;
|
|
7112
7335
|
const fireWebhook = (type, data) => {
|
|
7113
7336
|
if (!webhookUrl) return;
|
|
7114
7337
|
sendWebhook(webhookUrl, {
|
|
7115
7338
|
type,
|
|
7116
7339
|
taskId: this.session.id,
|
|
7117
7340
|
sessionId: this.session.id,
|
|
7341
|
+
...parentTaskId ? { parentTaskId } : {},
|
|
7118
7342
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7119
7343
|
data
|
|
7120
7344
|
});
|
|
@@ -7307,14 +7531,14 @@ ${taskAddendum}`;
|
|
|
7307
7531
|
for (const step of resultSteps) {
|
|
7308
7532
|
if (step.toolCalls) {
|
|
7309
7533
|
for (const tc of step.toolCalls) {
|
|
7310
|
-
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.
|
|
7311
|
-
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.
|
|
7534
|
+
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input });
|
|
7535
|
+
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.input });
|
|
7312
7536
|
}
|
|
7313
7537
|
}
|
|
7314
7538
|
if (step.toolResults) {
|
|
7315
7539
|
for (const tr of step.toolResults) {
|
|
7316
|
-
options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.
|
|
7317
|
-
fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.
|
|
7540
|
+
options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.output });
|
|
7541
|
+
fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.output });
|
|
7318
7542
|
}
|
|
7319
7543
|
}
|
|
7320
7544
|
}
|
|
@@ -7406,14 +7630,14 @@ ${taskAddendum}`;
|
|
|
7406
7630
|
const result = await recorder.encode();
|
|
7407
7631
|
recorder.clear();
|
|
7408
7632
|
if (!result) return [];
|
|
7409
|
-
const { readFile:
|
|
7633
|
+
const { readFile: readFile12, unlink: unlink3 } = await import("fs/promises");
|
|
7410
7634
|
const uploadInfo = await storageQueries2.getUploadUrl(
|
|
7411
7635
|
this.session.id,
|
|
7412
7636
|
`browser-recording-${Date.now()}.mp4`,
|
|
7413
7637
|
"video/mp4",
|
|
7414
7638
|
"browser-recording"
|
|
7415
7639
|
);
|
|
7416
|
-
const fileData = await
|
|
7640
|
+
const fileData = await readFile12(result.path);
|
|
7417
7641
|
await fetch(uploadInfo.uploadUrl, {
|
|
7418
7642
|
method: "PUT",
|
|
7419
7643
|
headers: { "Content-Type": "video/mp4" },
|
|
@@ -7439,12 +7663,12 @@ ${taskAddendum}`;
|
|
|
7439
7663
|
try {
|
|
7440
7664
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7441
7665
|
if (!isRemoteConfigured2()) return [];
|
|
7442
|
-
const { readFile:
|
|
7443
|
-
const { join:
|
|
7666
|
+
const { readFile: readFile12 } = await import("fs/promises");
|
|
7667
|
+
const { join: join13, basename: basename6 } = await import("path");
|
|
7444
7668
|
const urls = [];
|
|
7445
7669
|
for (const filePath of filePaths) {
|
|
7446
7670
|
try {
|
|
7447
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
7671
|
+
const fullPath = filePath.startsWith("/") ? filePath : join13(this.session.workingDirectory, filePath);
|
|
7448
7672
|
const fileName = basename6(fullPath);
|
|
7449
7673
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
7450
7674
|
const mimeMap = {
|
|
@@ -7469,7 +7693,7 @@ ${taskAddendum}`;
|
|
|
7469
7693
|
contentType,
|
|
7470
7694
|
"task-output"
|
|
7471
7695
|
);
|
|
7472
|
-
const fileData = await
|
|
7696
|
+
const fileData = await readFile12(fullPath);
|
|
7473
7697
|
await fetch(uploadInfo.uploadUrl, {
|
|
7474
7698
|
method: "PUT",
|
|
7475
7699
|
headers: { "Content-Type": contentType },
|
|
@@ -8074,12 +8298,12 @@ sessions.get("/:id/diff/:filePath", async (c) => {
|
|
|
8074
8298
|
});
|
|
8075
8299
|
function getAttachmentsDir(sessionId) {
|
|
8076
8300
|
const appDataDir = getAppDataDirectory();
|
|
8077
|
-
return
|
|
8301
|
+
return join9(appDataDir, "attachments", sessionId);
|
|
8078
8302
|
}
|
|
8079
8303
|
function ensureAttachmentsDir(sessionId) {
|
|
8080
8304
|
const dir = getAttachmentsDir(sessionId);
|
|
8081
|
-
if (!
|
|
8082
|
-
|
|
8305
|
+
if (!existsSync15(dir)) {
|
|
8306
|
+
mkdirSync5(dir, { recursive: true });
|
|
8083
8307
|
}
|
|
8084
8308
|
return dir;
|
|
8085
8309
|
}
|
|
@@ -8090,12 +8314,12 @@ sessions.get("/:id/attachments", async (c) => {
|
|
|
8090
8314
|
return c.json({ error: "Session not found" }, 404);
|
|
8091
8315
|
}
|
|
8092
8316
|
const dir = getAttachmentsDir(sessionId);
|
|
8093
|
-
if (!
|
|
8317
|
+
if (!existsSync15(dir)) {
|
|
8094
8318
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
8095
8319
|
}
|
|
8096
|
-
const files =
|
|
8320
|
+
const files = readdirSync2(dir);
|
|
8097
8321
|
const attachments = files.map((filename) => {
|
|
8098
|
-
const filePath =
|
|
8322
|
+
const filePath = join9(dir, filename);
|
|
8099
8323
|
const stats = statSync2(filePath);
|
|
8100
8324
|
return {
|
|
8101
8325
|
id: filename.split("_")[0],
|
|
@@ -8130,7 +8354,7 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
8130
8354
|
const id = nanoid5(10);
|
|
8131
8355
|
const ext = extname8(file.name) || "";
|
|
8132
8356
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
8133
|
-
const filePath =
|
|
8357
|
+
const filePath = join9(dir, safeFilename);
|
|
8134
8358
|
const arrayBuffer = await file.arrayBuffer();
|
|
8135
8359
|
writeFileSync3(filePath, Buffer.from(arrayBuffer));
|
|
8136
8360
|
return c.json({
|
|
@@ -8156,7 +8380,7 @@ sessions.post("/:id/attachments", async (c) => {
|
|
|
8156
8380
|
const id = nanoid5(10);
|
|
8157
8381
|
const ext = extname8(body.filename) || "";
|
|
8158
8382
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
8159
|
-
const filePath =
|
|
8383
|
+
const filePath = join9(dir, safeFilename);
|
|
8160
8384
|
let base64Data = body.data;
|
|
8161
8385
|
if (base64Data.includes(",")) {
|
|
8162
8386
|
base64Data = base64Data.split(",")[1];
|
|
@@ -8185,16 +8409,16 @@ sessions.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
8185
8409
|
return c.json({ error: "Session not found" }, 404);
|
|
8186
8410
|
}
|
|
8187
8411
|
const dir = getAttachmentsDir(sessionId);
|
|
8188
|
-
if (!
|
|
8412
|
+
if (!existsSync15(dir)) {
|
|
8189
8413
|
return c.json({ error: "Attachment not found" }, 404);
|
|
8190
8414
|
}
|
|
8191
|
-
const files =
|
|
8415
|
+
const files = readdirSync2(dir);
|
|
8192
8416
|
const file = files.find((f) => f.startsWith(attachmentId + "_"));
|
|
8193
8417
|
if (!file) {
|
|
8194
8418
|
return c.json({ error: "Attachment not found" }, 404);
|
|
8195
8419
|
}
|
|
8196
|
-
const filePath =
|
|
8197
|
-
|
|
8420
|
+
const filePath = join9(dir, file);
|
|
8421
|
+
unlinkSync2(filePath);
|
|
8198
8422
|
return c.json({ success: true, id: attachmentId });
|
|
8199
8423
|
});
|
|
8200
8424
|
var filesQuerySchema = z15.object({
|
|
@@ -8276,7 +8500,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
8276
8500
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
8277
8501
|
for (const entry of entries) {
|
|
8278
8502
|
if (results.length >= limit * 2) break;
|
|
8279
|
-
const fullPath =
|
|
8503
|
+
const fullPath = join9(currentDir, entry.name);
|
|
8280
8504
|
const relativePath = relative9(baseDir, fullPath);
|
|
8281
8505
|
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
|
|
8282
8506
|
continue;
|
|
@@ -8324,7 +8548,7 @@ sessions.get(
|
|
|
8324
8548
|
return c.json({ error: "Session not found" }, 404);
|
|
8325
8549
|
}
|
|
8326
8550
|
const workingDirectory = session.workingDirectory;
|
|
8327
|
-
if (!
|
|
8551
|
+
if (!existsSync15(workingDirectory)) {
|
|
8328
8552
|
return c.json({
|
|
8329
8553
|
sessionId,
|
|
8330
8554
|
workingDirectory,
|
|
@@ -8435,8 +8659,8 @@ init_db();
|
|
|
8435
8659
|
import { Hono as Hono2 } from "hono";
|
|
8436
8660
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
8437
8661
|
import { z as z16 } from "zod";
|
|
8438
|
-
import { existsSync as
|
|
8439
|
-
import { join as
|
|
8662
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
8663
|
+
import { join as join10 } from "path";
|
|
8440
8664
|
init_config();
|
|
8441
8665
|
|
|
8442
8666
|
// src/server/resumable-stream.ts
|
|
@@ -8642,12 +8866,12 @@ var rejectSchema = z16.object({
|
|
|
8642
8866
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
8643
8867
|
function getAttachmentsDirectory(sessionId) {
|
|
8644
8868
|
const appDataDir = getAppDataDirectory();
|
|
8645
|
-
return
|
|
8869
|
+
return join10(appDataDir, "attachments", sessionId);
|
|
8646
8870
|
}
|
|
8647
8871
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
8648
8872
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
8649
|
-
if (!
|
|
8650
|
-
|
|
8873
|
+
if (!existsSync16(attachmentsDir)) {
|
|
8874
|
+
mkdirSync6(attachmentsDir, { recursive: true });
|
|
8651
8875
|
}
|
|
8652
8876
|
let filename = attachment.filename;
|
|
8653
8877
|
if (!filename) {
|
|
@@ -8665,7 +8889,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
8665
8889
|
attachment.mediaType = resized.mediaType;
|
|
8666
8890
|
attachment.data = buffer.toString("base64");
|
|
8667
8891
|
}
|
|
8668
|
-
const filePath =
|
|
8892
|
+
const filePath = join10(attachmentsDir, filename);
|
|
8669
8893
|
writeFileSync4(filePath, buffer);
|
|
8670
8894
|
return filePath;
|
|
8671
8895
|
}
|
|
@@ -9606,26 +9830,26 @@ init_config();
|
|
|
9606
9830
|
import { Hono as Hono3 } from "hono";
|
|
9607
9831
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
9608
9832
|
import { z as z17 } from "zod";
|
|
9609
|
-
import { readFileSync as
|
|
9833
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
9610
9834
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9611
|
-
import { dirname as dirname6, join as
|
|
9835
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
9612
9836
|
var __filename = fileURLToPath3(import.meta.url);
|
|
9613
9837
|
var __dirname = dirname6(__filename);
|
|
9614
9838
|
var possiblePaths = [
|
|
9615
|
-
|
|
9839
|
+
join11(__dirname, "../package.json"),
|
|
9616
9840
|
// From dist/server -> dist/../package.json
|
|
9617
|
-
|
|
9841
|
+
join11(__dirname, "../../package.json"),
|
|
9618
9842
|
// From dist/server (if nested differently)
|
|
9619
|
-
|
|
9843
|
+
join11(__dirname, "../../../package.json"),
|
|
9620
9844
|
// From src/server/routes (development)
|
|
9621
|
-
|
|
9845
|
+
join11(process.cwd(), "package.json")
|
|
9622
9846
|
// From current working directory
|
|
9623
9847
|
];
|
|
9624
9848
|
var currentVersion = "0.0.0";
|
|
9625
9849
|
var packageName = "sparkecoder";
|
|
9626
9850
|
for (const packageJsonPath of possiblePaths) {
|
|
9627
9851
|
try {
|
|
9628
|
-
const packageJson = JSON.parse(
|
|
9852
|
+
const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
|
|
9629
9853
|
if (packageJson.name === "sparkecoder") {
|
|
9630
9854
|
currentVersion = packageJson.version || "0.0.0";
|
|
9631
9855
|
packageName = packageJson.name || "sparkecoder";
|
|
@@ -10195,6 +10419,7 @@ tasks.post(
|
|
|
10195
10419
|
type: "task.failed",
|
|
10196
10420
|
taskId,
|
|
10197
10421
|
sessionId: taskId,
|
|
10422
|
+
...taskConfig.parentTaskId ? { parentTaskId: taskConfig.parentTaskId } : {},
|
|
10198
10423
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10199
10424
|
data: { status: "failed", error: errorMsg }
|
|
10200
10425
|
});
|
|
@@ -10284,6 +10509,7 @@ tasks.post("/:id/cancel", async (c) => {
|
|
|
10284
10509
|
type: "task.failed",
|
|
10285
10510
|
taskId: id,
|
|
10286
10511
|
sessionId: id,
|
|
10512
|
+
...task.parentTaskId ? { parentTaskId: task.parentTaskId } : {},
|
|
10287
10513
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10288
10514
|
data: { status: "failed", error: "Task cancelled by user" }
|
|
10289
10515
|
});
|
|
@@ -10382,11 +10608,11 @@ function getWebDirectory() {
|
|
|
10382
10608
|
try {
|
|
10383
10609
|
const currentDir = dirname7(fileURLToPath4(import.meta.url));
|
|
10384
10610
|
const webDir = resolve10(currentDir, "..", "web");
|
|
10385
|
-
if (
|
|
10611
|
+
if (existsSync17(webDir) && existsSync17(join12(webDir, "package.json"))) {
|
|
10386
10612
|
return webDir;
|
|
10387
10613
|
}
|
|
10388
10614
|
const altWebDir = resolve10(currentDir, "..", "..", "web");
|
|
10389
|
-
if (
|
|
10615
|
+
if (existsSync17(altWebDir) && existsSync17(join12(altWebDir, "package.json"))) {
|
|
10390
10616
|
return altWebDir;
|
|
10391
10617
|
}
|
|
10392
10618
|
return null;
|
|
@@ -10444,23 +10670,23 @@ async function findWebPort(preferredPort) {
|
|
|
10444
10670
|
return { port: preferredPort, alreadyRunning: false };
|
|
10445
10671
|
}
|
|
10446
10672
|
function hasProductionBuild(webDir) {
|
|
10447
|
-
const buildIdPath =
|
|
10448
|
-
return
|
|
10673
|
+
const buildIdPath = join12(webDir, ".next", "BUILD_ID");
|
|
10674
|
+
return existsSync17(buildIdPath);
|
|
10449
10675
|
}
|
|
10450
10676
|
function hasSourceFiles(webDir) {
|
|
10451
|
-
const appDir =
|
|
10452
|
-
const pagesDir =
|
|
10453
|
-
const rootAppDir =
|
|
10454
|
-
const rootPagesDir =
|
|
10455
|
-
return
|
|
10677
|
+
const appDir = join12(webDir, "src", "app");
|
|
10678
|
+
const pagesDir = join12(webDir, "src", "pages");
|
|
10679
|
+
const rootAppDir = join12(webDir, "app");
|
|
10680
|
+
const rootPagesDir = join12(webDir, "pages");
|
|
10681
|
+
return existsSync17(appDir) || existsSync17(pagesDir) || existsSync17(rootAppDir) || existsSync17(rootPagesDir);
|
|
10456
10682
|
}
|
|
10457
10683
|
function getStandaloneServerPath(webDir) {
|
|
10458
10684
|
const possiblePaths2 = [
|
|
10459
|
-
|
|
10460
|
-
|
|
10685
|
+
join12(webDir, ".next", "standalone", "server.js"),
|
|
10686
|
+
join12(webDir, ".next", "standalone", "web", "server.js")
|
|
10461
10687
|
];
|
|
10462
10688
|
for (const serverPath of possiblePaths2) {
|
|
10463
|
-
if (
|
|
10689
|
+
if (existsSync17(serverPath)) {
|
|
10464
10690
|
return serverPath;
|
|
10465
10691
|
}
|
|
10466
10692
|
}
|
|
@@ -10500,13 +10726,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
10500
10726
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
10501
10727
|
return { process: null, port: actualPort };
|
|
10502
10728
|
}
|
|
10503
|
-
const usePnpm =
|
|
10504
|
-
const useNpm = !usePnpm &&
|
|
10729
|
+
const usePnpm = existsSync17(join12(webDir, "pnpm-lock.yaml"));
|
|
10730
|
+
const useNpm = !usePnpm && existsSync17(join12(webDir, "package-lock.json"));
|
|
10505
10731
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
10506
10732
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
10507
10733
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
10508
10734
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
10509
|
-
const runtimeConfigPath =
|
|
10735
|
+
const runtimeConfigPath = join12(webDir, "runtime-config.json");
|
|
10510
10736
|
try {
|
|
10511
10737
|
writeFileSync5(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
10512
10738
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
@@ -10699,8 +10925,8 @@ async function startServer(options = {}) {
|
|
|
10699
10925
|
if (options.workingDirectory) {
|
|
10700
10926
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
10701
10927
|
}
|
|
10702
|
-
if (!
|
|
10703
|
-
|
|
10928
|
+
if (!existsSync17(config.resolvedWorkingDirectory)) {
|
|
10929
|
+
mkdirSync7(config.resolvedWorkingDirectory, { recursive: true });
|
|
10704
10930
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
10705
10931
|
}
|
|
10706
10932
|
if (!config.resolvedRemoteServer.url) {
|