sparkecoder 0.1.4 → 0.1.6
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.d.ts +2 -2
- package/dist/agent/index.js +193 -25
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +937 -495
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +53 -2
- package/dist/db/index.js +184 -0
- package/dist/db/index.js.map +1 -1
- package/dist/{index-DkR9Ln_7.d.ts → index-BeKylVnB.d.ts} +5 -5
- package/dist/index.d.ts +68 -5
- package/dist/index.js +864 -463
- package/dist/index.js.map +1 -1
- package/dist/{schema-cUDLVN-b.d.ts → schema-CkrIadxa.d.ts} +246 -2
- package/dist/server/index.js +858 -465
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +272 -133
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
package/dist/agent/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'ai';
|
|
2
|
-
import '../schema-
|
|
3
|
-
export { A as Agent, a as AgentOptions, b as AgentRunOptions, c as AgentStreamResult, C as ContextManager, d as buildSystemPrompt } from '../index-
|
|
2
|
+
import '../schema-CkrIadxa.js';
|
|
3
|
+
export { A as Agent, a as AgentOptions, b as AgentRunOptions, c as AgentStreamResult, C as ContextManager, d as buildSystemPrompt } from '../index-BeKylVnB.js';
|
|
4
4
|
import '../bash-CGAqW7HR.js';
|
|
5
5
|
import 'drizzle-orm/sqlite-core';
|
|
6
6
|
import 'zod';
|
package/dist/agent/index.js
CHANGED
|
@@ -93,6 +93,28 @@ var activeStreams = sqliteTable("active_streams", {
|
|
|
93
93
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
94
94
|
finishedAt: integer("finished_at", { mode: "timestamp" })
|
|
95
95
|
});
|
|
96
|
+
var checkpoints = sqliteTable("checkpoints", {
|
|
97
|
+
id: text("id").primaryKey(),
|
|
98
|
+
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
99
|
+
// The message sequence number this checkpoint was created BEFORE
|
|
100
|
+
// (i.e., the state before this user message was processed)
|
|
101
|
+
messageSequence: integer("message_sequence").notNull(),
|
|
102
|
+
// Optional git commit hash if in a git repo
|
|
103
|
+
gitHead: text("git_head"),
|
|
104
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
105
|
+
});
|
|
106
|
+
var fileBackups = sqliteTable("file_backups", {
|
|
107
|
+
id: text("id").primaryKey(),
|
|
108
|
+
checkpointId: text("checkpoint_id").notNull().references(() => checkpoints.id, { onDelete: "cascade" }),
|
|
109
|
+
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
110
|
+
// Relative path from working directory
|
|
111
|
+
filePath: text("file_path").notNull(),
|
|
112
|
+
// Original content (null means file didn't exist before)
|
|
113
|
+
originalContent: text("original_content"),
|
|
114
|
+
// Whether the file existed before this checkpoint
|
|
115
|
+
existed: integer("existed", { mode: "boolean" }).notNull().default(true),
|
|
116
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
117
|
+
});
|
|
96
118
|
|
|
97
119
|
// src/db/index.ts
|
|
98
120
|
var db = null;
|
|
@@ -202,6 +224,19 @@ var messageQueries = {
|
|
|
202
224
|
deleteBySession(sessionId) {
|
|
203
225
|
const result = getDb().delete(messages).where(eq(messages.sessionId, sessionId)).run();
|
|
204
226
|
return result.changes;
|
|
227
|
+
},
|
|
228
|
+
/**
|
|
229
|
+
* Delete all messages with sequence >= the given sequence number
|
|
230
|
+
* (Used when reverting to a checkpoint)
|
|
231
|
+
*/
|
|
232
|
+
deleteFromSequence(sessionId, fromSequence) {
|
|
233
|
+
const result = getDb().delete(messages).where(
|
|
234
|
+
and(
|
|
235
|
+
eq(messages.sessionId, sessionId),
|
|
236
|
+
sql`sequence >= ${fromSequence}`
|
|
237
|
+
)
|
|
238
|
+
).run();
|
|
239
|
+
return result.changes;
|
|
205
240
|
}
|
|
206
241
|
};
|
|
207
242
|
var toolExecutionQueries = {
|
|
@@ -245,6 +280,19 @@ var toolExecutionQueries = {
|
|
|
245
280
|
},
|
|
246
281
|
getBySession(sessionId) {
|
|
247
282
|
return getDb().select().from(toolExecutions).where(eq(toolExecutions.sessionId, sessionId)).orderBy(toolExecutions.startedAt).all();
|
|
283
|
+
},
|
|
284
|
+
/**
|
|
285
|
+
* Delete all tool executions after a given timestamp
|
|
286
|
+
* (Used when reverting to a checkpoint)
|
|
287
|
+
*/
|
|
288
|
+
deleteAfterTime(sessionId, afterTime) {
|
|
289
|
+
const result = getDb().delete(toolExecutions).where(
|
|
290
|
+
and(
|
|
291
|
+
eq(toolExecutions.sessionId, sessionId),
|
|
292
|
+
sql`started_at > ${afterTime.getTime()}`
|
|
293
|
+
)
|
|
294
|
+
).run();
|
|
295
|
+
return result.changes;
|
|
248
296
|
}
|
|
249
297
|
};
|
|
250
298
|
var todoQueries = {
|
|
@@ -310,6 +358,68 @@ var skillQueries = {
|
|
|
310
358
|
return !!result;
|
|
311
359
|
}
|
|
312
360
|
};
|
|
361
|
+
var fileBackupQueries = {
|
|
362
|
+
create(data) {
|
|
363
|
+
const id = nanoid();
|
|
364
|
+
const result = getDb().insert(fileBackups).values({
|
|
365
|
+
id,
|
|
366
|
+
checkpointId: data.checkpointId,
|
|
367
|
+
sessionId: data.sessionId,
|
|
368
|
+
filePath: data.filePath,
|
|
369
|
+
originalContent: data.originalContent,
|
|
370
|
+
existed: data.existed,
|
|
371
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
372
|
+
}).returning().get();
|
|
373
|
+
return result;
|
|
374
|
+
},
|
|
375
|
+
getByCheckpoint(checkpointId) {
|
|
376
|
+
return getDb().select().from(fileBackups).where(eq(fileBackups.checkpointId, checkpointId)).all();
|
|
377
|
+
},
|
|
378
|
+
getBySession(sessionId) {
|
|
379
|
+
return getDb().select().from(fileBackups).where(eq(fileBackups.sessionId, sessionId)).orderBy(fileBackups.createdAt).all();
|
|
380
|
+
},
|
|
381
|
+
/**
|
|
382
|
+
* Get all file backups from a given checkpoint sequence onwards (inclusive)
|
|
383
|
+
* (Used when reverting - need to restore these files)
|
|
384
|
+
*
|
|
385
|
+
* When reverting to checkpoint X, we need backups from checkpoint X and all later ones
|
|
386
|
+
* because checkpoint X's backups represent the state BEFORE processing message X.
|
|
387
|
+
*/
|
|
388
|
+
getFromSequence(sessionId, messageSequence) {
|
|
389
|
+
const checkpointsFrom = getDb().select().from(checkpoints).where(
|
|
390
|
+
and(
|
|
391
|
+
eq(checkpoints.sessionId, sessionId),
|
|
392
|
+
sql`message_sequence >= ${messageSequence}`
|
|
393
|
+
)
|
|
394
|
+
).all();
|
|
395
|
+
if (checkpointsFrom.length === 0) {
|
|
396
|
+
return [];
|
|
397
|
+
}
|
|
398
|
+
const checkpointIds = checkpointsFrom.map((c) => c.id);
|
|
399
|
+
const allBackups = [];
|
|
400
|
+
for (const cpId of checkpointIds) {
|
|
401
|
+
const backups = getDb().select().from(fileBackups).where(eq(fileBackups.checkpointId, cpId)).all();
|
|
402
|
+
allBackups.push(...backups);
|
|
403
|
+
}
|
|
404
|
+
return allBackups;
|
|
405
|
+
},
|
|
406
|
+
/**
|
|
407
|
+
* Check if a file already has a backup in the current checkpoint
|
|
408
|
+
*/
|
|
409
|
+
hasBackup(checkpointId, filePath) {
|
|
410
|
+
const result = getDb().select().from(fileBackups).where(
|
|
411
|
+
and(
|
|
412
|
+
eq(fileBackups.checkpointId, checkpointId),
|
|
413
|
+
eq(fileBackups.filePath, filePath)
|
|
414
|
+
)
|
|
415
|
+
).get();
|
|
416
|
+
return !!result;
|
|
417
|
+
},
|
|
418
|
+
deleteBySession(sessionId) {
|
|
419
|
+
const result = getDb().delete(fileBackups).where(eq(fileBackups.sessionId, sessionId)).run();
|
|
420
|
+
return result.changes;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
313
423
|
|
|
314
424
|
// src/config/index.ts
|
|
315
425
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
@@ -445,7 +555,6 @@ async function isTmuxAvailable() {
|
|
|
445
555
|
try {
|
|
446
556
|
const { stdout } = await execAsync("tmux -V");
|
|
447
557
|
tmuxAvailableCache = true;
|
|
448
|
-
console.log(`[tmux] Available: ${stdout.trim()}`);
|
|
449
558
|
return true;
|
|
450
559
|
} catch (error) {
|
|
451
560
|
tmuxAvailableCache = false;
|
|
@@ -1017,9 +1126,62 @@ Use this to understand existing code, check file contents, or gather context.`,
|
|
|
1017
1126
|
// src/tools/write-file.ts
|
|
1018
1127
|
import { tool as tool3 } from "ai";
|
|
1019
1128
|
import { z as z4 } from "zod";
|
|
1020
|
-
import { readFile as
|
|
1021
|
-
import { resolve as
|
|
1129
|
+
import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1130
|
+
import { resolve as resolve4, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname3 } from "path";
|
|
1131
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1132
|
+
|
|
1133
|
+
// src/checkpoints/index.ts
|
|
1134
|
+
import { readFile as readFile3, writeFile as writeFile2, unlink, mkdir as mkdir2 } from "fs/promises";
|
|
1022
1135
|
import { existsSync as existsSync4 } from "fs";
|
|
1136
|
+
import { resolve as resolve3, relative as relative2, dirname as dirname2 } from "path";
|
|
1137
|
+
import { exec as exec3 } from "child_process";
|
|
1138
|
+
import { promisify as promisify3 } from "util";
|
|
1139
|
+
var execAsync3 = promisify3(exec3);
|
|
1140
|
+
var activeManagers = /* @__PURE__ */ new Map();
|
|
1141
|
+
function getCheckpointManager(sessionId, workingDirectory) {
|
|
1142
|
+
let manager = activeManagers.get(sessionId);
|
|
1143
|
+
if (!manager) {
|
|
1144
|
+
manager = {
|
|
1145
|
+
sessionId,
|
|
1146
|
+
workingDirectory,
|
|
1147
|
+
currentCheckpointId: null
|
|
1148
|
+
};
|
|
1149
|
+
activeManagers.set(sessionId, manager);
|
|
1150
|
+
}
|
|
1151
|
+
return manager;
|
|
1152
|
+
}
|
|
1153
|
+
async function backupFile(sessionId, workingDirectory, filePath) {
|
|
1154
|
+
const manager = getCheckpointManager(sessionId, workingDirectory);
|
|
1155
|
+
if (!manager.currentCheckpointId) {
|
|
1156
|
+
console.warn("[checkpoint] No active checkpoint, skipping file backup");
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
1159
|
+
const absolutePath = resolve3(workingDirectory, filePath);
|
|
1160
|
+
const relativePath = relative2(workingDirectory, absolutePath);
|
|
1161
|
+
if (fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
let originalContent = null;
|
|
1165
|
+
let existed = false;
|
|
1166
|
+
if (existsSync4(absolutePath)) {
|
|
1167
|
+
try {
|
|
1168
|
+
originalContent = await readFile3(absolutePath, "utf-8");
|
|
1169
|
+
existed = true;
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
console.warn(`[checkpoint] Failed to read file for backup: ${error.message}`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
const backup = fileBackupQueries.create({
|
|
1175
|
+
checkpointId: manager.currentCheckpointId,
|
|
1176
|
+
sessionId,
|
|
1177
|
+
filePath: relativePath,
|
|
1178
|
+
originalContent,
|
|
1179
|
+
existed
|
|
1180
|
+
});
|
|
1181
|
+
return backup;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// src/tools/write-file.ts
|
|
1023
1185
|
var writeFileInputSchema = z4.object({
|
|
1024
1186
|
path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
|
|
1025
1187
|
mode: z4.enum(["full", "str_replace"]).describe('Write mode: "full" for complete file write, "str_replace" for targeted string replacement'),
|
|
@@ -1048,8 +1210,8 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1048
1210
|
inputSchema: writeFileInputSchema,
|
|
1049
1211
|
execute: async ({ path, mode, content, old_string, new_string }) => {
|
|
1050
1212
|
try {
|
|
1051
|
-
const absolutePath = isAbsolute2(path) ? path :
|
|
1052
|
-
const relativePath =
|
|
1213
|
+
const absolutePath = isAbsolute2(path) ? path : resolve4(options.workingDirectory, path);
|
|
1214
|
+
const relativePath = relative3(options.workingDirectory, absolutePath);
|
|
1053
1215
|
if (relativePath.startsWith("..") && !isAbsolute2(path)) {
|
|
1054
1216
|
return {
|
|
1055
1217
|
success: false,
|
|
@@ -1063,16 +1225,17 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1063
1225
|
error: 'Content is required for "full" mode'
|
|
1064
1226
|
};
|
|
1065
1227
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1228
|
+
await backupFile(options.sessionId, options.workingDirectory, absolutePath);
|
|
1229
|
+
const dir = dirname3(absolutePath);
|
|
1230
|
+
if (!existsSync5(dir)) {
|
|
1231
|
+
await mkdir3(dir, { recursive: true });
|
|
1069
1232
|
}
|
|
1070
|
-
const existed =
|
|
1071
|
-
await
|
|
1233
|
+
const existed = existsSync5(absolutePath);
|
|
1234
|
+
await writeFile3(absolutePath, content, "utf-8");
|
|
1072
1235
|
return {
|
|
1073
1236
|
success: true,
|
|
1074
1237
|
path: absolutePath,
|
|
1075
|
-
relativePath:
|
|
1238
|
+
relativePath: relative3(options.workingDirectory, absolutePath),
|
|
1076
1239
|
mode: "full",
|
|
1077
1240
|
action: existed ? "replaced" : "created",
|
|
1078
1241
|
bytesWritten: Buffer.byteLength(content, "utf-8"),
|
|
@@ -1085,13 +1248,14 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1085
1248
|
error: 'Both old_string and new_string are required for "str_replace" mode'
|
|
1086
1249
|
};
|
|
1087
1250
|
}
|
|
1088
|
-
if (!
|
|
1251
|
+
if (!existsSync5(absolutePath)) {
|
|
1089
1252
|
return {
|
|
1090
1253
|
success: false,
|
|
1091
1254
|
error: `File not found: ${path}. Use "full" mode to create new files.`
|
|
1092
1255
|
};
|
|
1093
1256
|
}
|
|
1094
|
-
|
|
1257
|
+
await backupFile(options.sessionId, options.workingDirectory, absolutePath);
|
|
1258
|
+
const currentContent = await readFile4(absolutePath, "utf-8");
|
|
1095
1259
|
if (!currentContent.includes(old_string)) {
|
|
1096
1260
|
const lines = currentContent.split("\n");
|
|
1097
1261
|
const preview = lines.slice(0, 20).join("\n");
|
|
@@ -1112,13 +1276,13 @@ Working directory: ${options.workingDirectory}`,
|
|
|
1112
1276
|
};
|
|
1113
1277
|
}
|
|
1114
1278
|
const newContent = currentContent.replace(old_string, new_string);
|
|
1115
|
-
await
|
|
1279
|
+
await writeFile3(absolutePath, newContent, "utf-8");
|
|
1116
1280
|
const oldLines = old_string.split("\n").length;
|
|
1117
1281
|
const newLines = new_string.split("\n").length;
|
|
1118
1282
|
return {
|
|
1119
1283
|
success: true,
|
|
1120
1284
|
path: absolutePath,
|
|
1121
|
-
relativePath:
|
|
1285
|
+
relativePath: relative3(options.workingDirectory, absolutePath),
|
|
1122
1286
|
mode: "str_replace",
|
|
1123
1287
|
linesRemoved: oldLines,
|
|
1124
1288
|
linesAdded: newLines,
|
|
@@ -1269,9 +1433,9 @@ import { tool as tool5 } from "ai";
|
|
|
1269
1433
|
import { z as z6 } from "zod";
|
|
1270
1434
|
|
|
1271
1435
|
// src/skills/index.ts
|
|
1272
|
-
import { readFile as
|
|
1273
|
-
import { resolve as
|
|
1274
|
-
import { existsSync as
|
|
1436
|
+
import { readFile as readFile5, readdir } from "fs/promises";
|
|
1437
|
+
import { resolve as resolve5, basename, extname } from "path";
|
|
1438
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1275
1439
|
function parseSkillFrontmatter(content) {
|
|
1276
1440
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
1277
1441
|
if (!frontmatterMatch) {
|
|
@@ -1302,15 +1466,15 @@ function getSkillNameFromPath(filePath) {
|
|
|
1302
1466
|
return basename(filePath, extname(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1303
1467
|
}
|
|
1304
1468
|
async function loadSkillsFromDirectory(directory) {
|
|
1305
|
-
if (!
|
|
1469
|
+
if (!existsSync6(directory)) {
|
|
1306
1470
|
return [];
|
|
1307
1471
|
}
|
|
1308
1472
|
const skills = [];
|
|
1309
1473
|
const files = await readdir(directory);
|
|
1310
1474
|
for (const file of files) {
|
|
1311
1475
|
if (!file.endsWith(".md")) continue;
|
|
1312
|
-
const filePath =
|
|
1313
|
-
const content = await
|
|
1476
|
+
const filePath = resolve5(directory, file);
|
|
1477
|
+
const content = await readFile5(filePath, "utf-8");
|
|
1314
1478
|
const parsed = parseSkillFrontmatter(content);
|
|
1315
1479
|
if (parsed) {
|
|
1316
1480
|
skills.push({
|
|
@@ -1352,7 +1516,7 @@ async function loadSkillContent(skillName, directories) {
|
|
|
1352
1516
|
if (!skill) {
|
|
1353
1517
|
return null;
|
|
1354
1518
|
}
|
|
1355
|
-
const content = await
|
|
1519
|
+
const content = await readFile5(skill.filePath, "utf-8");
|
|
1356
1520
|
const parsed = parseSkillFrontmatter(content);
|
|
1357
1521
|
return {
|
|
1358
1522
|
...skill,
|
|
@@ -1463,7 +1627,8 @@ function createTools(options) {
|
|
|
1463
1627
|
workingDirectory: options.workingDirectory
|
|
1464
1628
|
}),
|
|
1465
1629
|
write_file: createWriteFileTool({
|
|
1466
|
-
workingDirectory: options.workingDirectory
|
|
1630
|
+
workingDirectory: options.workingDirectory,
|
|
1631
|
+
sessionId: options.sessionId
|
|
1467
1632
|
}),
|
|
1468
1633
|
todo: createTodoTool({
|
|
1469
1634
|
sessionId: options.sessionId
|
|
@@ -1521,6 +1686,9 @@ You have access to powerful tools for:
|
|
|
1521
1686
|
- **todo**: Manage your task list to track progress on complex operations
|
|
1522
1687
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
1523
1688
|
|
|
1689
|
+
|
|
1690
|
+
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.
|
|
1691
|
+
|
|
1524
1692
|
Use the TODO tool to manage your task list to track progress on complex operations. Always ask the user what they want to do specifically before doing it, and make a plan.
|
|
1525
1693
|
Step 1 of the plan should be researching files and understanding the components/structure of what you're working on (if you don't already have context), then after u have done that, plan out the rest of the tasks u need to do.
|
|
1526
1694
|
You can clear the todo and restart it, and do multiple things inside of one session.
|
|
@@ -1957,8 +2125,8 @@ var Agent = class _Agent {
|
|
|
1957
2125
|
this.pendingApprovals.set(toolCallId, execution);
|
|
1958
2126
|
options.onApprovalRequired?.(execution);
|
|
1959
2127
|
sessionQueries.updateStatus(this.session.id, "waiting");
|
|
1960
|
-
const approved = await new Promise((
|
|
1961
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
2128
|
+
const approved = await new Promise((resolve6) => {
|
|
2129
|
+
approvalResolvers.set(toolCallId, { resolve: resolve6, sessionId: this.session.id });
|
|
1962
2130
|
});
|
|
1963
2131
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
1964
2132
|
approvalResolvers.delete(toolCallId);
|