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.
@@ -1,17 +1,38 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res) => function __init() {
4
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
- };
6
2
  var __export = (target, all) => {
7
3
  for (var name in all)
8
4
  __defProp(target, name, { get: all[name], enumerable: true });
9
5
  };
10
6
 
7
+ // src/server/index.ts
8
+ import "dotenv/config";
9
+ import { Hono as Hono5 } from "hono";
10
+ import { serve } from "@hono/node-server";
11
+ import { cors } from "hono/cors";
12
+ import { logger } from "hono/logger";
13
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
14
+ import { resolve as resolve6, dirname as dirname4, join as join3 } from "path";
15
+ import { spawn } from "child_process";
16
+ import { createServer as createNetServer } from "net";
17
+ import { fileURLToPath } from "url";
18
+
19
+ // src/server/routes/sessions.ts
20
+ import { Hono } from "hono";
21
+ import { zValidator } from "@hono/zod-validator";
22
+ import { z as z8 } from "zod";
23
+
24
+ // src/db/index.ts
25
+ import Database from "better-sqlite3";
26
+ import { drizzle } from "drizzle-orm/better-sqlite3";
27
+ import { eq, desc, and, sql } from "drizzle-orm";
28
+ import { nanoid } from "nanoid";
29
+
11
30
  // src/db/schema.ts
12
31
  var schema_exports = {};
13
32
  __export(schema_exports, {
14
33
  activeStreams: () => activeStreams,
34
+ checkpoints: () => checkpoints,
35
+ fileBackups: () => fileBackups,
15
36
  loadedSkills: () => loadedSkills,
16
37
  messages: () => messages,
17
38
  sessions: () => sessions,
@@ -20,107 +41,108 @@ __export(schema_exports, {
20
41
  toolExecutions: () => toolExecutions
21
42
  });
22
43
  import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
23
- var sessions, messages, toolExecutions, todoItems, loadedSkills, terminals, activeStreams;
24
- var init_schema = __esm({
25
- "src/db/schema.ts"() {
26
- "use strict";
27
- sessions = sqliteTable("sessions", {
28
- id: text("id").primaryKey(),
29
- name: text("name"),
30
- workingDirectory: text("working_directory").notNull(),
31
- model: text("model").notNull(),
32
- status: text("status", { enum: ["active", "waiting", "completed", "error"] }).notNull().default("active"),
33
- config: text("config", { mode: "json" }).$type(),
34
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
35
- updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
36
- });
37
- messages = sqliteTable("messages", {
38
- id: text("id").primaryKey(),
39
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
40
- // Store the entire ModelMessage as JSON (role + content)
41
- modelMessage: text("model_message", { mode: "json" }).$type().notNull(),
42
- // Sequence number within session to maintain exact ordering
43
- sequence: integer("sequence").notNull().default(0),
44
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
45
- });
46
- toolExecutions = sqliteTable("tool_executions", {
47
- id: text("id").primaryKey(),
48
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
49
- messageId: text("message_id").references(() => messages.id, { onDelete: "cascade" }),
50
- toolName: text("tool_name").notNull(),
51
- toolCallId: text("tool_call_id").notNull(),
52
- input: text("input", { mode: "json" }),
53
- output: text("output", { mode: "json" }),
54
- status: text("status", { enum: ["pending", "approved", "rejected", "completed", "error"] }).notNull().default("pending"),
55
- requiresApproval: integer("requires_approval", { mode: "boolean" }).notNull().default(false),
56
- error: text("error"),
57
- startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
58
- completedAt: integer("completed_at", { mode: "timestamp" })
59
- });
60
- todoItems = sqliteTable("todo_items", {
61
- id: text("id").primaryKey(),
62
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
63
- content: text("content").notNull(),
64
- status: text("status", { enum: ["pending", "in_progress", "completed", "cancelled"] }).notNull().default("pending"),
65
- order: integer("order").notNull().default(0),
66
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
67
- updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
68
- });
69
- loadedSkills = sqliteTable("loaded_skills", {
70
- id: text("id").primaryKey(),
71
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
72
- skillName: text("skill_name").notNull(),
73
- loadedAt: integer("loaded_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
74
- });
75
- terminals = sqliteTable("terminals", {
76
- id: text("id").primaryKey(),
77
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
78
- name: text("name"),
79
- // Optional friendly name (e.g., "dev-server")
80
- command: text("command").notNull(),
81
- // The command that was run
82
- cwd: text("cwd").notNull(),
83
- // Working directory
84
- pid: integer("pid"),
85
- // Process ID (null if not running)
86
- status: text("status", { enum: ["running", "stopped", "error"] }).notNull().default("running"),
87
- exitCode: integer("exit_code"),
88
- // Exit code if stopped
89
- error: text("error"),
90
- // Error message if status is 'error'
91
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
92
- stoppedAt: integer("stopped_at", { mode: "timestamp" })
93
- });
94
- activeStreams = sqliteTable("active_streams", {
95
- id: text("id").primaryKey(),
96
- sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
97
- streamId: text("stream_id").notNull().unique(),
98
- // Unique stream identifier
99
- status: text("status", { enum: ["active", "finished", "error"] }).notNull().default("active"),
100
- createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
101
- finishedAt: integer("finished_at", { mode: "timestamp" })
102
- });
103
- }
44
+ var sessions = sqliteTable("sessions", {
45
+ id: text("id").primaryKey(),
46
+ name: text("name"),
47
+ workingDirectory: text("working_directory").notNull(),
48
+ model: text("model").notNull(),
49
+ status: text("status", { enum: ["active", "waiting", "completed", "error"] }).notNull().default("active"),
50
+ config: text("config", { mode: "json" }).$type(),
51
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
52
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
53
+ });
54
+ var messages = sqliteTable("messages", {
55
+ id: text("id").primaryKey(),
56
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
57
+ // Store the entire ModelMessage as JSON (role + content)
58
+ modelMessage: text("model_message", { mode: "json" }).$type().notNull(),
59
+ // Sequence number within session to maintain exact ordering
60
+ sequence: integer("sequence").notNull().default(0),
61
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
62
+ });
63
+ var toolExecutions = sqliteTable("tool_executions", {
64
+ id: text("id").primaryKey(),
65
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
66
+ messageId: text("message_id").references(() => messages.id, { onDelete: "cascade" }),
67
+ toolName: text("tool_name").notNull(),
68
+ toolCallId: text("tool_call_id").notNull(),
69
+ input: text("input", { mode: "json" }),
70
+ output: text("output", { mode: "json" }),
71
+ status: text("status", { enum: ["pending", "approved", "rejected", "completed", "error"] }).notNull().default("pending"),
72
+ requiresApproval: integer("requires_approval", { mode: "boolean" }).notNull().default(false),
73
+ error: text("error"),
74
+ startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
75
+ completedAt: integer("completed_at", { mode: "timestamp" })
76
+ });
77
+ var todoItems = sqliteTable("todo_items", {
78
+ id: text("id").primaryKey(),
79
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
80
+ content: text("content").notNull(),
81
+ status: text("status", { enum: ["pending", "in_progress", "completed", "cancelled"] }).notNull().default("pending"),
82
+ order: integer("order").notNull().default(0),
83
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
84
+ updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
85
+ });
86
+ var loadedSkills = sqliteTable("loaded_skills", {
87
+ id: text("id").primaryKey(),
88
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
89
+ skillName: text("skill_name").notNull(),
90
+ loadedAt: integer("loaded_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
91
+ });
92
+ var terminals = sqliteTable("terminals", {
93
+ id: text("id").primaryKey(),
94
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
95
+ name: text("name"),
96
+ // Optional friendly name (e.g., "dev-server")
97
+ command: text("command").notNull(),
98
+ // The command that was run
99
+ cwd: text("cwd").notNull(),
100
+ // Working directory
101
+ pid: integer("pid"),
102
+ // Process ID (null if not running)
103
+ status: text("status", { enum: ["running", "stopped", "error"] }).notNull().default("running"),
104
+ exitCode: integer("exit_code"),
105
+ // Exit code if stopped
106
+ error: text("error"),
107
+ // Error message if status is 'error'
108
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
109
+ stoppedAt: integer("stopped_at", { mode: "timestamp" })
110
+ });
111
+ var activeStreams = sqliteTable("active_streams", {
112
+ id: text("id").primaryKey(),
113
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
114
+ streamId: text("stream_id").notNull().unique(),
115
+ // Unique stream identifier
116
+ status: text("status", { enum: ["active", "finished", "error"] }).notNull().default("active"),
117
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
118
+ finishedAt: integer("finished_at", { mode: "timestamp" })
119
+ });
120
+ var checkpoints = sqliteTable("checkpoints", {
121
+ id: text("id").primaryKey(),
122
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
123
+ // The message sequence number this checkpoint was created BEFORE
124
+ // (i.e., the state before this user message was processed)
125
+ messageSequence: integer("message_sequence").notNull(),
126
+ // Optional git commit hash if in a git repo
127
+ gitHead: text("git_head"),
128
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
129
+ });
130
+ var fileBackups = sqliteTable("file_backups", {
131
+ id: text("id").primaryKey(),
132
+ checkpointId: text("checkpoint_id").notNull().references(() => checkpoints.id, { onDelete: "cascade" }),
133
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
134
+ // Relative path from working directory
135
+ filePath: text("file_path").notNull(),
136
+ // Original content (null means file didn't exist before)
137
+ originalContent: text("original_content"),
138
+ // Whether the file existed before this checkpoint
139
+ existed: integer("existed", { mode: "boolean" }).notNull().default(true),
140
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
104
141
  });
105
142
 
106
143
  // src/db/index.ts
107
- var db_exports = {};
108
- __export(db_exports, {
109
- activeStreamQueries: () => activeStreamQueries,
110
- closeDatabase: () => closeDatabase,
111
- getDb: () => getDb,
112
- initDatabase: () => initDatabase,
113
- messageQueries: () => messageQueries,
114
- sessionQueries: () => sessionQueries,
115
- skillQueries: () => skillQueries,
116
- terminalQueries: () => terminalQueries,
117
- todoQueries: () => todoQueries,
118
- toolExecutionQueries: () => toolExecutionQueries
119
- });
120
- import Database from "better-sqlite3";
121
- import { drizzle } from "drizzle-orm/better-sqlite3";
122
- import { eq, desc, and, sql } from "drizzle-orm";
123
- import { nanoid } from "nanoid";
144
+ var db = null;
145
+ var sqlite = null;
124
146
  function initDatabase(dbPath) {
125
147
  sqlite = new Database(dbPath);
126
148
  sqlite.pragma("journal_mode = WAL");
@@ -201,12 +223,35 @@ function initDatabase(dbPath) {
201
223
  finished_at INTEGER
202
224
  );
203
225
 
226
+ -- Checkpoints table - created before each user message
227
+ CREATE TABLE IF NOT EXISTS checkpoints (
228
+ id TEXT PRIMARY KEY,
229
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
230
+ message_sequence INTEGER NOT NULL,
231
+ git_head TEXT,
232
+ created_at INTEGER NOT NULL
233
+ );
234
+
235
+ -- File backups table - stores original file content
236
+ CREATE TABLE IF NOT EXISTS file_backups (
237
+ id TEXT PRIMARY KEY,
238
+ checkpoint_id TEXT NOT NULL REFERENCES checkpoints(id) ON DELETE CASCADE,
239
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
240
+ file_path TEXT NOT NULL,
241
+ original_content TEXT,
242
+ existed INTEGER NOT NULL DEFAULT 1,
243
+ created_at INTEGER NOT NULL
244
+ );
245
+
204
246
  CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
205
247
  CREATE INDEX IF NOT EXISTS idx_tool_executions_session ON tool_executions(session_id);
206
248
  CREATE INDEX IF NOT EXISTS idx_todo_items_session ON todo_items(session_id);
207
249
  CREATE INDEX IF NOT EXISTS idx_loaded_skills_session ON loaded_skills(session_id);
208
250
  CREATE INDEX IF NOT EXISTS idx_terminals_session ON terminals(session_id);
209
251
  CREATE INDEX IF NOT EXISTS idx_active_streams_session ON active_streams(session_id);
252
+ CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id);
253
+ CREATE INDEX IF NOT EXISTS idx_file_backups_checkpoint ON file_backups(checkpoint_id);
254
+ CREATE INDEX IF NOT EXISTS idx_file_backups_session ON file_backups(session_id);
210
255
  `);
211
256
  return db;
212
257
  }
@@ -223,321 +268,385 @@ function closeDatabase() {
223
268
  db = null;
224
269
  }
225
270
  }
226
- var db, sqlite, sessionQueries, messageQueries, toolExecutionQueries, todoQueries, skillQueries, terminalQueries, activeStreamQueries;
227
- var init_db = __esm({
228
- "src/db/index.ts"() {
229
- "use strict";
230
- init_schema();
231
- db = null;
232
- sqlite = null;
233
- sessionQueries = {
234
- create(data) {
235
- const id = nanoid();
236
- const now = /* @__PURE__ */ new Date();
237
- const result = getDb().insert(sessions).values({
238
- id,
239
- ...data,
240
- createdAt: now,
241
- updatedAt: now
242
- }).returning().get();
243
- return result;
244
- },
245
- getById(id) {
246
- return getDb().select().from(sessions).where(eq(sessions.id, id)).get();
247
- },
248
- list(limit = 50, offset = 0) {
249
- return getDb().select().from(sessions).orderBy(desc(sessions.createdAt)).limit(limit).offset(offset).all();
250
- },
251
- updateStatus(id, status) {
252
- return getDb().update(sessions).set({ status, updatedAt: /* @__PURE__ */ new Date() }).where(eq(sessions.id, id)).returning().get();
253
- },
254
- updateModel(id, model) {
255
- return getDb().update(sessions).set({ model, updatedAt: /* @__PURE__ */ new Date() }).where(eq(sessions.id, id)).returning().get();
256
- },
257
- update(id, updates) {
258
- return getDb().update(sessions).set({ ...updates, updatedAt: /* @__PURE__ */ new Date() }).where(eq(sessions.id, id)).returning().get();
259
- },
260
- delete(id) {
261
- const result = getDb().delete(sessions).where(eq(sessions.id, id)).run();
262
- return result.changes > 0;
263
- }
264
- };
265
- messageQueries = {
266
- /**
267
- * Get the next sequence number for a session
268
- */
269
- getNextSequence(sessionId) {
270
- const result = getDb().select({ maxSeq: sql`COALESCE(MAX(sequence), -1)` }).from(messages).where(eq(messages.sessionId, sessionId)).get();
271
- return (result?.maxSeq ?? -1) + 1;
272
- },
273
- /**
274
- * Create a single message from a ModelMessage
275
- */
276
- create(sessionId, modelMessage) {
277
- const id = nanoid();
278
- const sequence = this.getNextSequence(sessionId);
279
- const result = getDb().insert(messages).values({
280
- id,
281
- sessionId,
282
- modelMessage,
283
- sequence,
284
- createdAt: /* @__PURE__ */ new Date()
285
- }).returning().get();
286
- return result;
287
- },
288
- /**
289
- * Add multiple ModelMessages at once (from response.messages)
290
- * Maintains insertion order via sequence numbers
291
- */
292
- addMany(sessionId, modelMessages) {
293
- const results = [];
294
- let sequence = this.getNextSequence(sessionId);
295
- for (const msg of modelMessages) {
296
- const id = nanoid();
297
- const result = getDb().insert(messages).values({
298
- id,
299
- sessionId,
300
- modelMessage: msg,
301
- sequence,
302
- createdAt: /* @__PURE__ */ new Date()
303
- }).returning().get();
304
- results.push(result);
305
- sequence++;
306
- }
307
- return results;
308
- },
309
- /**
310
- * Get all messages for a session as ModelMessage[]
311
- * Ordered by sequence to maintain exact insertion order
312
- */
313
- getBySession(sessionId) {
314
- return getDb().select().from(messages).where(eq(messages.sessionId, sessionId)).orderBy(messages.sequence).all();
315
- },
316
- /**
317
- * Get ModelMessages directly (for passing to AI SDK)
318
- */
319
- getModelMessages(sessionId) {
320
- const messages2 = this.getBySession(sessionId);
321
- return messages2.map((m) => m.modelMessage);
322
- },
323
- getRecentBySession(sessionId, limit = 50) {
324
- return getDb().select().from(messages).where(eq(messages.sessionId, sessionId)).orderBy(desc(messages.sequence)).limit(limit).all().reverse();
325
- },
326
- countBySession(sessionId) {
327
- const result = getDb().select({ count: sql`count(*)` }).from(messages).where(eq(messages.sessionId, sessionId)).get();
328
- return result?.count ?? 0;
329
- },
330
- deleteBySession(sessionId) {
331
- const result = getDb().delete(messages).where(eq(messages.sessionId, sessionId)).run();
332
- return result.changes;
333
- }
334
- };
335
- toolExecutionQueries = {
336
- create(data) {
337
- const id = nanoid();
338
- const result = getDb().insert(toolExecutions).values({
339
- id,
340
- ...data,
341
- startedAt: /* @__PURE__ */ new Date()
342
- }).returning().get();
343
- return result;
344
- },
345
- getById(id) {
346
- return getDb().select().from(toolExecutions).where(eq(toolExecutions.id, id)).get();
347
- },
348
- getByToolCallId(toolCallId) {
349
- return getDb().select().from(toolExecutions).where(eq(toolExecutions.toolCallId, toolCallId)).get();
350
- },
351
- getPendingApprovals(sessionId) {
352
- return getDb().select().from(toolExecutions).where(
353
- and(
354
- eq(toolExecutions.sessionId, sessionId),
355
- eq(toolExecutions.status, "pending"),
356
- eq(toolExecutions.requiresApproval, true)
357
- )
358
- ).all();
359
- },
360
- approve(id) {
361
- return getDb().update(toolExecutions).set({ status: "approved" }).where(eq(toolExecutions.id, id)).returning().get();
362
- },
363
- reject(id) {
364
- return getDb().update(toolExecutions).set({ status: "rejected" }).where(eq(toolExecutions.id, id)).returning().get();
365
- },
366
- complete(id, output, error) {
367
- return getDb().update(toolExecutions).set({
368
- status: error ? "error" : "completed",
369
- output,
370
- error,
371
- completedAt: /* @__PURE__ */ new Date()
372
- }).where(eq(toolExecutions.id, id)).returning().get();
373
- },
374
- getBySession(sessionId) {
375
- return getDb().select().from(toolExecutions).where(eq(toolExecutions.sessionId, sessionId)).orderBy(toolExecutions.startedAt).all();
376
- }
377
- };
378
- todoQueries = {
379
- create(data) {
380
- const id = nanoid();
381
- const now = /* @__PURE__ */ new Date();
382
- const result = getDb().insert(todoItems).values({
383
- id,
384
- ...data,
385
- createdAt: now,
386
- updatedAt: now
387
- }).returning().get();
388
- return result;
389
- },
390
- createMany(sessionId, items) {
391
- const now = /* @__PURE__ */ new Date();
392
- const values = items.map((item, index) => ({
393
- id: nanoid(),
394
- sessionId,
395
- content: item.content,
396
- order: item.order ?? index,
397
- createdAt: now,
398
- updatedAt: now
399
- }));
400
- return getDb().insert(todoItems).values(values).returning().all();
401
- },
402
- getBySession(sessionId) {
403
- return getDb().select().from(todoItems).where(eq(todoItems.sessionId, sessionId)).orderBy(todoItems.order).all();
404
- },
405
- updateStatus(id, status) {
406
- return getDb().update(todoItems).set({ status, updatedAt: /* @__PURE__ */ new Date() }).where(eq(todoItems.id, id)).returning().get();
407
- },
408
- delete(id) {
409
- const result = getDb().delete(todoItems).where(eq(todoItems.id, id)).run();
410
- return result.changes > 0;
411
- },
412
- clearSession(sessionId) {
413
- const result = getDb().delete(todoItems).where(eq(todoItems.sessionId, sessionId)).run();
414
- return result.changes;
415
- }
416
- };
417
- skillQueries = {
418
- load(sessionId, skillName) {
419
- const id = nanoid();
420
- const result = getDb().insert(loadedSkills).values({
421
- id,
422
- sessionId,
423
- skillName,
424
- loadedAt: /* @__PURE__ */ new Date()
425
- }).returning().get();
426
- return result;
427
- },
428
- getBySession(sessionId) {
429
- return getDb().select().from(loadedSkills).where(eq(loadedSkills.sessionId, sessionId)).orderBy(loadedSkills.loadedAt).all();
430
- },
431
- isLoaded(sessionId, skillName) {
432
- const result = getDb().select().from(loadedSkills).where(
433
- and(
434
- eq(loadedSkills.sessionId, sessionId),
435
- eq(loadedSkills.skillName, skillName)
436
- )
437
- ).get();
438
- return !!result;
439
- }
440
- };
441
- terminalQueries = {
442
- create(data) {
443
- const id = nanoid();
444
- const result = getDb().insert(terminals).values({
445
- id,
446
- ...data,
447
- createdAt: /* @__PURE__ */ new Date()
448
- }).returning().get();
449
- return result;
450
- },
451
- getById(id) {
452
- return getDb().select().from(terminals).where(eq(terminals.id, id)).get();
453
- },
454
- getBySession(sessionId) {
455
- return getDb().select().from(terminals).where(eq(terminals.sessionId, sessionId)).orderBy(desc(terminals.createdAt)).all();
456
- },
457
- getRunning(sessionId) {
458
- return getDb().select().from(terminals).where(
459
- and(
460
- eq(terminals.sessionId, sessionId),
461
- eq(terminals.status, "running")
462
- )
463
- ).all();
464
- },
465
- updateStatus(id, status, exitCode, error) {
466
- return getDb().update(terminals).set({
467
- status,
468
- exitCode,
469
- error,
470
- stoppedAt: status !== "running" ? /* @__PURE__ */ new Date() : void 0
471
- }).where(eq(terminals.id, id)).returning().get();
472
- },
473
- updatePid(id, pid) {
474
- return getDb().update(terminals).set({ pid }).where(eq(terminals.id, id)).returning().get();
475
- },
476
- delete(id) {
477
- const result = getDb().delete(terminals).where(eq(terminals.id, id)).run();
478
- return result.changes > 0;
479
- },
480
- deleteBySession(sessionId) {
481
- const result = getDb().delete(terminals).where(eq(terminals.sessionId, sessionId)).run();
482
- return result.changes;
483
- }
484
- };
485
- activeStreamQueries = {
486
- create(sessionId, streamId) {
487
- const id = nanoid();
488
- const result = getDb().insert(activeStreams).values({
489
- id,
490
- sessionId,
491
- streamId,
492
- status: "active",
493
- createdAt: /* @__PURE__ */ new Date()
494
- }).returning().get();
495
- return result;
496
- },
497
- getBySessionId(sessionId) {
498
- return getDb().select().from(activeStreams).where(
499
- and(
500
- eq(activeStreams.sessionId, sessionId),
501
- eq(activeStreams.status, "active")
502
- )
503
- ).get();
504
- },
505
- getByStreamId(streamId) {
506
- return getDb().select().from(activeStreams).where(eq(activeStreams.streamId, streamId)).get();
507
- },
508
- finish(streamId) {
509
- return getDb().update(activeStreams).set({ status: "finished", finishedAt: /* @__PURE__ */ new Date() }).where(eq(activeStreams.streamId, streamId)).returning().get();
510
- },
511
- markError(streamId) {
512
- return getDb().update(activeStreams).set({ status: "error", finishedAt: /* @__PURE__ */ new Date() }).where(eq(activeStreams.streamId, streamId)).returning().get();
513
- },
514
- deleteBySession(sessionId) {
515
- const result = getDb().delete(activeStreams).where(eq(activeStreams.sessionId, sessionId)).run();
516
- return result.changes;
517
- }
518
- };
271
+ var sessionQueries = {
272
+ create(data) {
273
+ const id = nanoid();
274
+ const now = /* @__PURE__ */ new Date();
275
+ const result = getDb().insert(sessions).values({
276
+ id,
277
+ ...data,
278
+ createdAt: now,
279
+ updatedAt: now
280
+ }).returning().get();
281
+ return result;
282
+ },
283
+ getById(id) {
284
+ return getDb().select().from(sessions).where(eq(sessions.id, id)).get();
285
+ },
286
+ list(limit = 50, offset = 0) {
287
+ return getDb().select().from(sessions).orderBy(desc(sessions.createdAt)).limit(limit).offset(offset).all();
288
+ },
289
+ updateStatus(id, status) {
290
+ return getDb().update(sessions).set({ status, updatedAt: /* @__PURE__ */ new Date() }).where(eq(sessions.id, id)).returning().get();
291
+ },
292
+ updateModel(id, model) {
293
+ return getDb().update(sessions).set({ model, updatedAt: /* @__PURE__ */ new Date() }).where(eq(sessions.id, id)).returning().get();
294
+ },
295
+ update(id, updates) {
296
+ return getDb().update(sessions).set({ ...updates, updatedAt: /* @__PURE__ */ new Date() }).where(eq(sessions.id, id)).returning().get();
297
+ },
298
+ delete(id) {
299
+ const result = getDb().delete(sessions).where(eq(sessions.id, id)).run();
300
+ return result.changes > 0;
519
301
  }
520
- });
521
-
522
- // src/server/index.ts
523
- import { Hono as Hono5 } from "hono";
524
- import { serve } from "@hono/node-server";
525
- import { cors } from "hono/cors";
526
- import { logger } from "hono/logger";
527
- import { existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
528
- import { resolve as resolve5, dirname as dirname3, join as join3 } from "path";
529
- import { spawn } from "child_process";
530
- import { createServer as createNetServer } from "net";
531
- import { fileURLToPath } from "url";
532
-
533
- // src/server/routes/sessions.ts
534
- init_db();
535
- import { Hono } from "hono";
536
- import { zValidator } from "@hono/zod-validator";
537
- import { z as z8 } from "zod";
302
+ };
303
+ var messageQueries = {
304
+ /**
305
+ * Get the next sequence number for a session
306
+ */
307
+ getNextSequence(sessionId) {
308
+ const result = getDb().select({ maxSeq: sql`COALESCE(MAX(sequence), -1)` }).from(messages).where(eq(messages.sessionId, sessionId)).get();
309
+ return (result?.maxSeq ?? -1) + 1;
310
+ },
311
+ /**
312
+ * Create a single message from a ModelMessage
313
+ */
314
+ create(sessionId, modelMessage) {
315
+ const id = nanoid();
316
+ const sequence = this.getNextSequence(sessionId);
317
+ const result = getDb().insert(messages).values({
318
+ id,
319
+ sessionId,
320
+ modelMessage,
321
+ sequence,
322
+ createdAt: /* @__PURE__ */ new Date()
323
+ }).returning().get();
324
+ return result;
325
+ },
326
+ /**
327
+ * Add multiple ModelMessages at once (from response.messages)
328
+ * Maintains insertion order via sequence numbers
329
+ */
330
+ addMany(sessionId, modelMessages) {
331
+ const results = [];
332
+ let sequence = this.getNextSequence(sessionId);
333
+ for (const msg of modelMessages) {
334
+ const id = nanoid();
335
+ const result = getDb().insert(messages).values({
336
+ id,
337
+ sessionId,
338
+ modelMessage: msg,
339
+ sequence,
340
+ createdAt: /* @__PURE__ */ new Date()
341
+ }).returning().get();
342
+ results.push(result);
343
+ sequence++;
344
+ }
345
+ return results;
346
+ },
347
+ /**
348
+ * Get all messages for a session as ModelMessage[]
349
+ * Ordered by sequence to maintain exact insertion order
350
+ */
351
+ getBySession(sessionId) {
352
+ return getDb().select().from(messages).where(eq(messages.sessionId, sessionId)).orderBy(messages.sequence).all();
353
+ },
354
+ /**
355
+ * Get ModelMessages directly (for passing to AI SDK)
356
+ */
357
+ getModelMessages(sessionId) {
358
+ const messages2 = this.getBySession(sessionId);
359
+ return messages2.map((m) => m.modelMessage);
360
+ },
361
+ getRecentBySession(sessionId, limit = 50) {
362
+ return getDb().select().from(messages).where(eq(messages.sessionId, sessionId)).orderBy(desc(messages.sequence)).limit(limit).all().reverse();
363
+ },
364
+ countBySession(sessionId) {
365
+ const result = getDb().select({ count: sql`count(*)` }).from(messages).where(eq(messages.sessionId, sessionId)).get();
366
+ return result?.count ?? 0;
367
+ },
368
+ deleteBySession(sessionId) {
369
+ const result = getDb().delete(messages).where(eq(messages.sessionId, sessionId)).run();
370
+ return result.changes;
371
+ },
372
+ /**
373
+ * Delete all messages with sequence >= the given sequence number
374
+ * (Used when reverting to a checkpoint)
375
+ */
376
+ deleteFromSequence(sessionId, fromSequence) {
377
+ const result = getDb().delete(messages).where(
378
+ and(
379
+ eq(messages.sessionId, sessionId),
380
+ sql`sequence >= ${fromSequence}`
381
+ )
382
+ ).run();
383
+ return result.changes;
384
+ }
385
+ };
386
+ var toolExecutionQueries = {
387
+ create(data) {
388
+ const id = nanoid();
389
+ const result = getDb().insert(toolExecutions).values({
390
+ id,
391
+ ...data,
392
+ startedAt: /* @__PURE__ */ new Date()
393
+ }).returning().get();
394
+ return result;
395
+ },
396
+ getById(id) {
397
+ return getDb().select().from(toolExecutions).where(eq(toolExecutions.id, id)).get();
398
+ },
399
+ getByToolCallId(toolCallId) {
400
+ return getDb().select().from(toolExecutions).where(eq(toolExecutions.toolCallId, toolCallId)).get();
401
+ },
402
+ getPendingApprovals(sessionId) {
403
+ return getDb().select().from(toolExecutions).where(
404
+ and(
405
+ eq(toolExecutions.sessionId, sessionId),
406
+ eq(toolExecutions.status, "pending"),
407
+ eq(toolExecutions.requiresApproval, true)
408
+ )
409
+ ).all();
410
+ },
411
+ approve(id) {
412
+ return getDb().update(toolExecutions).set({ status: "approved" }).where(eq(toolExecutions.id, id)).returning().get();
413
+ },
414
+ reject(id) {
415
+ return getDb().update(toolExecutions).set({ status: "rejected" }).where(eq(toolExecutions.id, id)).returning().get();
416
+ },
417
+ complete(id, output, error) {
418
+ return getDb().update(toolExecutions).set({
419
+ status: error ? "error" : "completed",
420
+ output,
421
+ error,
422
+ completedAt: /* @__PURE__ */ new Date()
423
+ }).where(eq(toolExecutions.id, id)).returning().get();
424
+ },
425
+ getBySession(sessionId) {
426
+ return getDb().select().from(toolExecutions).where(eq(toolExecutions.sessionId, sessionId)).orderBy(toolExecutions.startedAt).all();
427
+ },
428
+ /**
429
+ * Delete all tool executions after a given timestamp
430
+ * (Used when reverting to a checkpoint)
431
+ */
432
+ deleteAfterTime(sessionId, afterTime) {
433
+ const result = getDb().delete(toolExecutions).where(
434
+ and(
435
+ eq(toolExecutions.sessionId, sessionId),
436
+ sql`started_at > ${afterTime.getTime()}`
437
+ )
438
+ ).run();
439
+ return result.changes;
440
+ }
441
+ };
442
+ var todoQueries = {
443
+ create(data) {
444
+ const id = nanoid();
445
+ const now = /* @__PURE__ */ new Date();
446
+ const result = getDb().insert(todoItems).values({
447
+ id,
448
+ ...data,
449
+ createdAt: now,
450
+ updatedAt: now
451
+ }).returning().get();
452
+ return result;
453
+ },
454
+ createMany(sessionId, items) {
455
+ const now = /* @__PURE__ */ new Date();
456
+ const values = items.map((item, index) => ({
457
+ id: nanoid(),
458
+ sessionId,
459
+ content: item.content,
460
+ order: item.order ?? index,
461
+ createdAt: now,
462
+ updatedAt: now
463
+ }));
464
+ return getDb().insert(todoItems).values(values).returning().all();
465
+ },
466
+ getBySession(sessionId) {
467
+ return getDb().select().from(todoItems).where(eq(todoItems.sessionId, sessionId)).orderBy(todoItems.order).all();
468
+ },
469
+ updateStatus(id, status) {
470
+ return getDb().update(todoItems).set({ status, updatedAt: /* @__PURE__ */ new Date() }).where(eq(todoItems.id, id)).returning().get();
471
+ },
472
+ delete(id) {
473
+ const result = getDb().delete(todoItems).where(eq(todoItems.id, id)).run();
474
+ return result.changes > 0;
475
+ },
476
+ clearSession(sessionId) {
477
+ const result = getDb().delete(todoItems).where(eq(todoItems.sessionId, sessionId)).run();
478
+ return result.changes;
479
+ }
480
+ };
481
+ var skillQueries = {
482
+ load(sessionId, skillName) {
483
+ const id = nanoid();
484
+ const result = getDb().insert(loadedSkills).values({
485
+ id,
486
+ sessionId,
487
+ skillName,
488
+ loadedAt: /* @__PURE__ */ new Date()
489
+ }).returning().get();
490
+ return result;
491
+ },
492
+ getBySession(sessionId) {
493
+ return getDb().select().from(loadedSkills).where(eq(loadedSkills.sessionId, sessionId)).orderBy(loadedSkills.loadedAt).all();
494
+ },
495
+ isLoaded(sessionId, skillName) {
496
+ const result = getDb().select().from(loadedSkills).where(
497
+ and(
498
+ eq(loadedSkills.sessionId, sessionId),
499
+ eq(loadedSkills.skillName, skillName)
500
+ )
501
+ ).get();
502
+ return !!result;
503
+ }
504
+ };
505
+ var activeStreamQueries = {
506
+ create(sessionId, streamId) {
507
+ const id = nanoid();
508
+ const result = getDb().insert(activeStreams).values({
509
+ id,
510
+ sessionId,
511
+ streamId,
512
+ status: "active",
513
+ createdAt: /* @__PURE__ */ new Date()
514
+ }).returning().get();
515
+ return result;
516
+ },
517
+ getBySessionId(sessionId) {
518
+ return getDb().select().from(activeStreams).where(
519
+ and(
520
+ eq(activeStreams.sessionId, sessionId),
521
+ eq(activeStreams.status, "active")
522
+ )
523
+ ).get();
524
+ },
525
+ getByStreamId(streamId) {
526
+ return getDb().select().from(activeStreams).where(eq(activeStreams.streamId, streamId)).get();
527
+ },
528
+ finish(streamId) {
529
+ return getDb().update(activeStreams).set({ status: "finished", finishedAt: /* @__PURE__ */ new Date() }).where(eq(activeStreams.streamId, streamId)).returning().get();
530
+ },
531
+ markError(streamId) {
532
+ return getDb().update(activeStreams).set({ status: "error", finishedAt: /* @__PURE__ */ new Date() }).where(eq(activeStreams.streamId, streamId)).returning().get();
533
+ },
534
+ deleteBySession(sessionId) {
535
+ const result = getDb().delete(activeStreams).where(eq(activeStreams.sessionId, sessionId)).run();
536
+ return result.changes;
537
+ }
538
+ };
539
+ var checkpointQueries = {
540
+ create(data) {
541
+ const id = nanoid();
542
+ const result = getDb().insert(checkpoints).values({
543
+ id,
544
+ sessionId: data.sessionId,
545
+ messageSequence: data.messageSequence,
546
+ gitHead: data.gitHead,
547
+ createdAt: /* @__PURE__ */ new Date()
548
+ }).returning().get();
549
+ return result;
550
+ },
551
+ getById(id) {
552
+ return getDb().select().from(checkpoints).where(eq(checkpoints.id, id)).get();
553
+ },
554
+ getBySession(sessionId) {
555
+ return getDb().select().from(checkpoints).where(eq(checkpoints.sessionId, sessionId)).orderBy(checkpoints.messageSequence).all();
556
+ },
557
+ getByMessageSequence(sessionId, messageSequence) {
558
+ return getDb().select().from(checkpoints).where(
559
+ and(
560
+ eq(checkpoints.sessionId, sessionId),
561
+ eq(checkpoints.messageSequence, messageSequence)
562
+ )
563
+ ).get();
564
+ },
565
+ getLatest(sessionId) {
566
+ return getDb().select().from(checkpoints).where(eq(checkpoints.sessionId, sessionId)).orderBy(desc(checkpoints.messageSequence)).limit(1).get();
567
+ },
568
+ /**
569
+ * Delete all checkpoints after a given sequence number
570
+ * (Used when reverting to a checkpoint)
571
+ */
572
+ deleteAfterSequence(sessionId, messageSequence) {
573
+ const result = getDb().delete(checkpoints).where(
574
+ and(
575
+ eq(checkpoints.sessionId, sessionId),
576
+ sql`message_sequence > ${messageSequence}`
577
+ )
578
+ ).run();
579
+ return result.changes;
580
+ },
581
+ deleteBySession(sessionId) {
582
+ const result = getDb().delete(checkpoints).where(eq(checkpoints.sessionId, sessionId)).run();
583
+ return result.changes;
584
+ }
585
+ };
586
+ var fileBackupQueries = {
587
+ create(data) {
588
+ const id = nanoid();
589
+ const result = getDb().insert(fileBackups).values({
590
+ id,
591
+ checkpointId: data.checkpointId,
592
+ sessionId: data.sessionId,
593
+ filePath: data.filePath,
594
+ originalContent: data.originalContent,
595
+ existed: data.existed,
596
+ createdAt: /* @__PURE__ */ new Date()
597
+ }).returning().get();
598
+ return result;
599
+ },
600
+ getByCheckpoint(checkpointId) {
601
+ return getDb().select().from(fileBackups).where(eq(fileBackups.checkpointId, checkpointId)).all();
602
+ },
603
+ getBySession(sessionId) {
604
+ return getDb().select().from(fileBackups).where(eq(fileBackups.sessionId, sessionId)).orderBy(fileBackups.createdAt).all();
605
+ },
606
+ /**
607
+ * Get all file backups from a given checkpoint sequence onwards (inclusive)
608
+ * (Used when reverting - need to restore these files)
609
+ *
610
+ * When reverting to checkpoint X, we need backups from checkpoint X and all later ones
611
+ * because checkpoint X's backups represent the state BEFORE processing message X.
612
+ */
613
+ getFromSequence(sessionId, messageSequence) {
614
+ const checkpointsFrom = getDb().select().from(checkpoints).where(
615
+ and(
616
+ eq(checkpoints.sessionId, sessionId),
617
+ sql`message_sequence >= ${messageSequence}`
618
+ )
619
+ ).all();
620
+ if (checkpointsFrom.length === 0) {
621
+ return [];
622
+ }
623
+ const checkpointIds = checkpointsFrom.map((c) => c.id);
624
+ const allBackups = [];
625
+ for (const cpId of checkpointIds) {
626
+ const backups = getDb().select().from(fileBackups).where(eq(fileBackups.checkpointId, cpId)).all();
627
+ allBackups.push(...backups);
628
+ }
629
+ return allBackups;
630
+ },
631
+ /**
632
+ * Check if a file already has a backup in the current checkpoint
633
+ */
634
+ hasBackup(checkpointId, filePath) {
635
+ const result = getDb().select().from(fileBackups).where(
636
+ and(
637
+ eq(fileBackups.checkpointId, checkpointId),
638
+ eq(fileBackups.filePath, filePath)
639
+ )
640
+ ).get();
641
+ return !!result;
642
+ },
643
+ deleteBySession(sessionId) {
644
+ const result = getDb().delete(fileBackups).where(eq(fileBackups.sessionId, sessionId)).run();
645
+ return result.changes;
646
+ }
647
+ };
538
648
 
539
649
  // src/agent/index.ts
540
- init_db();
541
650
  import {
542
651
  streamText,
543
652
  generateText as generateText2,
@@ -883,7 +992,6 @@ async function isTmuxAvailable() {
883
992
  try {
884
993
  const { stdout } = await execAsync("tmux -V");
885
994
  tmuxAvailableCache = true;
886
- console.log(`[tmux] Available: ${stdout.trim()}`);
887
995
  return true;
888
996
  } catch (error) {
889
997
  tmuxAvailableCache = false;
@@ -1503,9 +1611,198 @@ Use this to understand existing code, check file contents, or gather context.`,
1503
1611
  // src/tools/write-file.ts
1504
1612
  import { tool as tool3 } from "ai";
1505
1613
  import { z as z4 } from "zod";
1506
- import { readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1507
- import { resolve as resolve3, relative as relative2, isAbsolute as isAbsolute2, dirname as dirname2 } from "path";
1614
+ import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1615
+ import { resolve as resolve4, relative as relative3, isAbsolute as isAbsolute2, dirname as dirname3 } from "path";
1616
+ import { existsSync as existsSync5 } from "fs";
1617
+
1618
+ // src/checkpoints/index.ts
1619
+ import { readFile as readFile3, writeFile as writeFile2, unlink, mkdir as mkdir2 } from "fs/promises";
1508
1620
  import { existsSync as existsSync4 } from "fs";
1621
+ import { resolve as resolve3, relative as relative2, dirname as dirname2 } from "path";
1622
+ import { exec as exec3 } from "child_process";
1623
+ import { promisify as promisify3 } from "util";
1624
+ var execAsync3 = promisify3(exec3);
1625
+ async function getGitHead(workingDirectory) {
1626
+ try {
1627
+ const { stdout } = await execAsync3("git rev-parse HEAD", {
1628
+ cwd: workingDirectory,
1629
+ timeout: 5e3
1630
+ });
1631
+ return stdout.trim();
1632
+ } catch {
1633
+ return void 0;
1634
+ }
1635
+ }
1636
+ var activeManagers = /* @__PURE__ */ new Map();
1637
+ function getCheckpointManager(sessionId, workingDirectory) {
1638
+ let manager = activeManagers.get(sessionId);
1639
+ if (!manager) {
1640
+ manager = {
1641
+ sessionId,
1642
+ workingDirectory,
1643
+ currentCheckpointId: null
1644
+ };
1645
+ activeManagers.set(sessionId, manager);
1646
+ }
1647
+ return manager;
1648
+ }
1649
+ async function createCheckpoint(sessionId, workingDirectory, messageSequence) {
1650
+ const gitHead = await getGitHead(workingDirectory);
1651
+ const checkpoint = checkpointQueries.create({
1652
+ sessionId,
1653
+ messageSequence,
1654
+ gitHead
1655
+ });
1656
+ const manager = getCheckpointManager(sessionId, workingDirectory);
1657
+ manager.currentCheckpointId = checkpoint.id;
1658
+ return checkpoint;
1659
+ }
1660
+ async function backupFile(sessionId, workingDirectory, filePath) {
1661
+ const manager = getCheckpointManager(sessionId, workingDirectory);
1662
+ if (!manager.currentCheckpointId) {
1663
+ console.warn("[checkpoint] No active checkpoint, skipping file backup");
1664
+ return null;
1665
+ }
1666
+ const absolutePath = resolve3(workingDirectory, filePath);
1667
+ const relativePath = relative2(workingDirectory, absolutePath);
1668
+ if (fileBackupQueries.hasBackup(manager.currentCheckpointId, relativePath)) {
1669
+ return null;
1670
+ }
1671
+ let originalContent = null;
1672
+ let existed = false;
1673
+ if (existsSync4(absolutePath)) {
1674
+ try {
1675
+ originalContent = await readFile3(absolutePath, "utf-8");
1676
+ existed = true;
1677
+ } catch (error) {
1678
+ console.warn(`[checkpoint] Failed to read file for backup: ${error.message}`);
1679
+ }
1680
+ }
1681
+ const backup = fileBackupQueries.create({
1682
+ checkpointId: manager.currentCheckpointId,
1683
+ sessionId,
1684
+ filePath: relativePath,
1685
+ originalContent,
1686
+ existed
1687
+ });
1688
+ return backup;
1689
+ }
1690
+ async function revertToCheckpoint(sessionId, checkpointId) {
1691
+ const session = sessionQueries.getById(sessionId);
1692
+ if (!session) {
1693
+ return {
1694
+ success: false,
1695
+ filesRestored: 0,
1696
+ filesDeleted: 0,
1697
+ messagesDeleted: 0,
1698
+ checkpointsDeleted: 0,
1699
+ error: "Session not found"
1700
+ };
1701
+ }
1702
+ const checkpoint = checkpointQueries.getById(checkpointId);
1703
+ if (!checkpoint || checkpoint.sessionId !== sessionId) {
1704
+ return {
1705
+ success: false,
1706
+ filesRestored: 0,
1707
+ filesDeleted: 0,
1708
+ messagesDeleted: 0,
1709
+ checkpointsDeleted: 0,
1710
+ error: "Checkpoint not found"
1711
+ };
1712
+ }
1713
+ const workingDirectory = session.workingDirectory;
1714
+ const backupsToRevert = fileBackupQueries.getFromSequence(sessionId, checkpoint.messageSequence);
1715
+ const fileToEarliestBackup = /* @__PURE__ */ new Map();
1716
+ for (const backup of backupsToRevert) {
1717
+ if (!fileToEarliestBackup.has(backup.filePath)) {
1718
+ fileToEarliestBackup.set(backup.filePath, backup);
1719
+ }
1720
+ }
1721
+ let filesRestored = 0;
1722
+ let filesDeleted = 0;
1723
+ for (const [filePath, backup] of fileToEarliestBackup) {
1724
+ const absolutePath = resolve3(workingDirectory, filePath);
1725
+ try {
1726
+ if (backup.existed && backup.originalContent !== null) {
1727
+ const dir = dirname2(absolutePath);
1728
+ if (!existsSync4(dir)) {
1729
+ await mkdir2(dir, { recursive: true });
1730
+ }
1731
+ await writeFile2(absolutePath, backup.originalContent, "utf-8");
1732
+ filesRestored++;
1733
+ } else if (!backup.existed) {
1734
+ if (existsSync4(absolutePath)) {
1735
+ await unlink(absolutePath);
1736
+ filesDeleted++;
1737
+ }
1738
+ }
1739
+ } catch (error) {
1740
+ console.error(`Failed to restore ${filePath}: ${error.message}`);
1741
+ }
1742
+ }
1743
+ const messagesDeleted = messageQueries.deleteFromSequence(sessionId, checkpoint.messageSequence);
1744
+ toolExecutionQueries.deleteAfterTime(sessionId, checkpoint.createdAt);
1745
+ const checkpointsDeleted = checkpointQueries.deleteAfterSequence(sessionId, checkpoint.messageSequence);
1746
+ const manager = getCheckpointManager(sessionId, workingDirectory);
1747
+ manager.currentCheckpointId = checkpoint.id;
1748
+ return {
1749
+ success: true,
1750
+ filesRestored,
1751
+ filesDeleted,
1752
+ messagesDeleted,
1753
+ checkpointsDeleted
1754
+ };
1755
+ }
1756
+ function getCheckpoints(sessionId) {
1757
+ return checkpointQueries.getBySession(sessionId);
1758
+ }
1759
+ async function getSessionDiff(sessionId) {
1760
+ const session = sessionQueries.getById(sessionId);
1761
+ if (!session) {
1762
+ return { files: [] };
1763
+ }
1764
+ const workingDirectory = session.workingDirectory;
1765
+ const allBackups = fileBackupQueries.getBySession(sessionId);
1766
+ const fileToOriginalBackup = /* @__PURE__ */ new Map();
1767
+ for (const backup of allBackups) {
1768
+ if (!fileToOriginalBackup.has(backup.filePath)) {
1769
+ fileToOriginalBackup.set(backup.filePath, backup);
1770
+ }
1771
+ }
1772
+ const files = [];
1773
+ for (const [filePath, originalBackup] of fileToOriginalBackup) {
1774
+ const absolutePath = resolve3(workingDirectory, filePath);
1775
+ let currentContent = null;
1776
+ let currentExists = false;
1777
+ if (existsSync4(absolutePath)) {
1778
+ try {
1779
+ currentContent = await readFile3(absolutePath, "utf-8");
1780
+ currentExists = true;
1781
+ } catch {
1782
+ }
1783
+ }
1784
+ let status;
1785
+ if (!originalBackup.existed && currentExists) {
1786
+ status = "created";
1787
+ } else if (originalBackup.existed && !currentExists) {
1788
+ status = "deleted";
1789
+ } else {
1790
+ status = "modified";
1791
+ }
1792
+ files.push({
1793
+ path: filePath,
1794
+ status,
1795
+ originalContent: originalBackup.originalContent,
1796
+ currentContent
1797
+ });
1798
+ }
1799
+ return { files };
1800
+ }
1801
+ function clearCheckpointManager(sessionId) {
1802
+ activeManagers.delete(sessionId);
1803
+ }
1804
+
1805
+ // src/tools/write-file.ts
1509
1806
  var writeFileInputSchema = z4.object({
1510
1807
  path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
1511
1808
  mode: z4.enum(["full", "str_replace"]).describe('Write mode: "full" for complete file write, "str_replace" for targeted string replacement'),
@@ -1534,8 +1831,8 @@ Working directory: ${options.workingDirectory}`,
1534
1831
  inputSchema: writeFileInputSchema,
1535
1832
  execute: async ({ path, mode, content, old_string, new_string }) => {
1536
1833
  try {
1537
- const absolutePath = isAbsolute2(path) ? path : resolve3(options.workingDirectory, path);
1538
- const relativePath = relative2(options.workingDirectory, absolutePath);
1834
+ const absolutePath = isAbsolute2(path) ? path : resolve4(options.workingDirectory, path);
1835
+ const relativePath = relative3(options.workingDirectory, absolutePath);
1539
1836
  if (relativePath.startsWith("..") && !isAbsolute2(path)) {
1540
1837
  return {
1541
1838
  success: false,
@@ -1549,16 +1846,17 @@ Working directory: ${options.workingDirectory}`,
1549
1846
  error: 'Content is required for "full" mode'
1550
1847
  };
1551
1848
  }
1552
- const dir = dirname2(absolutePath);
1553
- if (!existsSync4(dir)) {
1554
- await mkdir2(dir, { recursive: true });
1849
+ await backupFile(options.sessionId, options.workingDirectory, absolutePath);
1850
+ const dir = dirname3(absolutePath);
1851
+ if (!existsSync5(dir)) {
1852
+ await mkdir3(dir, { recursive: true });
1555
1853
  }
1556
- const existed = existsSync4(absolutePath);
1557
- await writeFile2(absolutePath, content, "utf-8");
1854
+ const existed = existsSync5(absolutePath);
1855
+ await writeFile3(absolutePath, content, "utf-8");
1558
1856
  return {
1559
1857
  success: true,
1560
1858
  path: absolutePath,
1561
- relativePath: relative2(options.workingDirectory, absolutePath),
1859
+ relativePath: relative3(options.workingDirectory, absolutePath),
1562
1860
  mode: "full",
1563
1861
  action: existed ? "replaced" : "created",
1564
1862
  bytesWritten: Buffer.byteLength(content, "utf-8"),
@@ -1571,13 +1869,14 @@ Working directory: ${options.workingDirectory}`,
1571
1869
  error: 'Both old_string and new_string are required for "str_replace" mode'
1572
1870
  };
1573
1871
  }
1574
- if (!existsSync4(absolutePath)) {
1872
+ if (!existsSync5(absolutePath)) {
1575
1873
  return {
1576
1874
  success: false,
1577
1875
  error: `File not found: ${path}. Use "full" mode to create new files.`
1578
1876
  };
1579
1877
  }
1580
- const currentContent = await readFile3(absolutePath, "utf-8");
1878
+ await backupFile(options.sessionId, options.workingDirectory, absolutePath);
1879
+ const currentContent = await readFile4(absolutePath, "utf-8");
1581
1880
  if (!currentContent.includes(old_string)) {
1582
1881
  const lines = currentContent.split("\n");
1583
1882
  const preview = lines.slice(0, 20).join("\n");
@@ -1598,13 +1897,13 @@ Working directory: ${options.workingDirectory}`,
1598
1897
  };
1599
1898
  }
1600
1899
  const newContent = currentContent.replace(old_string, new_string);
1601
- await writeFile2(absolutePath, newContent, "utf-8");
1900
+ await writeFile3(absolutePath, newContent, "utf-8");
1602
1901
  const oldLines = old_string.split("\n").length;
1603
1902
  const newLines = new_string.split("\n").length;
1604
1903
  return {
1605
1904
  success: true,
1606
1905
  path: absolutePath,
1607
- relativePath: relative2(options.workingDirectory, absolutePath),
1906
+ relativePath: relative3(options.workingDirectory, absolutePath),
1608
1907
  mode: "str_replace",
1609
1908
  linesRemoved: oldLines,
1610
1909
  linesAdded: newLines,
@@ -1626,7 +1925,6 @@ Working directory: ${options.workingDirectory}`,
1626
1925
  }
1627
1926
 
1628
1927
  // src/tools/todo.ts
1629
- init_db();
1630
1928
  import { tool as tool4 } from "ai";
1631
1929
  import { z as z5 } from "zod";
1632
1930
  var todoInputSchema = z5.object({
@@ -1756,9 +2054,9 @@ import { tool as tool5 } from "ai";
1756
2054
  import { z as z6 } from "zod";
1757
2055
 
1758
2056
  // src/skills/index.ts
1759
- import { readFile as readFile4, readdir } from "fs/promises";
1760
- import { resolve as resolve4, basename, extname } from "path";
1761
- import { existsSync as existsSync5 } from "fs";
2057
+ import { readFile as readFile5, readdir } from "fs/promises";
2058
+ import { resolve as resolve5, basename, extname } from "path";
2059
+ import { existsSync as existsSync6 } from "fs";
1762
2060
  function parseSkillFrontmatter(content) {
1763
2061
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
1764
2062
  if (!frontmatterMatch) {
@@ -1789,15 +2087,15 @@ function getSkillNameFromPath(filePath) {
1789
2087
  return basename(filePath, extname(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1790
2088
  }
1791
2089
  async function loadSkillsFromDirectory(directory) {
1792
- if (!existsSync5(directory)) {
2090
+ if (!existsSync6(directory)) {
1793
2091
  return [];
1794
2092
  }
1795
2093
  const skills = [];
1796
2094
  const files = await readdir(directory);
1797
2095
  for (const file of files) {
1798
2096
  if (!file.endsWith(".md")) continue;
1799
- const filePath = resolve4(directory, file);
1800
- const content = await readFile4(filePath, "utf-8");
2097
+ const filePath = resolve5(directory, file);
2098
+ const content = await readFile5(filePath, "utf-8");
1801
2099
  const parsed = parseSkillFrontmatter(content);
1802
2100
  if (parsed) {
1803
2101
  skills.push({
@@ -1839,7 +2137,7 @@ async function loadSkillContent(skillName, directories) {
1839
2137
  if (!skill) {
1840
2138
  return null;
1841
2139
  }
1842
- const content = await readFile4(skill.filePath, "utf-8");
2140
+ const content = await readFile5(skill.filePath, "utf-8");
1843
2141
  const parsed = parseSkillFrontmatter(content);
1844
2142
  return {
1845
2143
  ...skill,
@@ -1858,7 +2156,6 @@ function formatSkillsForContext(skills) {
1858
2156
  }
1859
2157
 
1860
2158
  // src/tools/load-skill.ts
1861
- init_db();
1862
2159
  var loadSkillInputSchema = z6.object({
1863
2160
  action: z6.enum(["list", "load"]).describe('Action to perform: "list" to see available skills, "load" to load a skill'),
1864
2161
  skillName: z6.string().optional().describe('For "load" action: The name of the skill to load')
@@ -1951,7 +2248,8 @@ function createTools(options) {
1951
2248
  workingDirectory: options.workingDirectory
1952
2249
  }),
1953
2250
  write_file: createWriteFileTool({
1954
- workingDirectory: options.workingDirectory
2251
+ workingDirectory: options.workingDirectory,
2252
+ sessionId: options.sessionId
1955
2253
  }),
1956
2254
  todo: createTodoTool({
1957
2255
  sessionId: options.sessionId
@@ -1964,13 +2262,11 @@ function createTools(options) {
1964
2262
  }
1965
2263
 
1966
2264
  // src/agent/context.ts
1967
- init_db();
1968
2265
  import { generateText } from "ai";
1969
2266
  import { gateway } from "@ai-sdk/gateway";
1970
2267
 
1971
2268
  // src/agent/prompts.ts
1972
2269
  import os from "os";
1973
- init_db();
1974
2270
  function getSearchInstructions() {
1975
2271
  const platform3 = process.platform;
1976
2272
  const common = `- **Prefer \`read_file\` over shell commands** for reading files - don't use \`cat\`, \`head\`, or \`tail\` when \`read_file\` is available
@@ -2011,6 +2307,9 @@ You have access to powerful tools for:
2011
2307
  - **todo**: Manage your task list to track progress on complex operations
2012
2308
  - **load_skill**: Load specialized knowledge documents for specific tasks
2013
2309
 
2310
+
2311
+ 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.
2312
+
2014
2313
  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.
2015
2314
  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.
2016
2315
  You can clear the todo and restart it, and do multiple things inside of one session.
@@ -2447,8 +2746,8 @@ var Agent = class _Agent {
2447
2746
  this.pendingApprovals.set(toolCallId, execution);
2448
2747
  options.onApprovalRequired?.(execution);
2449
2748
  sessionQueries.updateStatus(this.session.id, "waiting");
2450
- const approved = await new Promise((resolve6) => {
2451
- approvalResolvers.set(toolCallId, { resolve: resolve6, sessionId: this.session.id });
2749
+ const approved = await new Promise((resolve7) => {
2750
+ approvalResolvers.set(toolCallId, { resolve: resolve7, sessionId: this.session.id });
2452
2751
  });
2453
2752
  const resolverData = approvalResolvers.get(toolCallId);
2454
2753
  approvalResolvers.delete(toolCallId);
@@ -2749,6 +3048,7 @@ sessions2.delete("/:id", async (c) => {
2749
3048
  }
2750
3049
  } catch (e) {
2751
3050
  }
3051
+ clearCheckpointManager(id);
2752
3052
  const deleted = sessionQueries.delete(id);
2753
3053
  if (!deleted) {
2754
3054
  return c.json({ error: "Session not found" }, 404);
@@ -2800,9 +3100,100 @@ sessions2.get("/:id/todos", async (c) => {
2800
3100
  } : null
2801
3101
  });
2802
3102
  });
3103
+ sessions2.get("/:id/checkpoints", async (c) => {
3104
+ const id = c.req.param("id");
3105
+ const session = sessionQueries.getById(id);
3106
+ if (!session) {
3107
+ return c.json({ error: "Session not found" }, 404);
3108
+ }
3109
+ const checkpoints2 = getCheckpoints(id);
3110
+ return c.json({
3111
+ sessionId: id,
3112
+ checkpoints: checkpoints2.map((cp) => ({
3113
+ id: cp.id,
3114
+ messageSequence: cp.messageSequence,
3115
+ gitHead: cp.gitHead,
3116
+ createdAt: cp.createdAt.toISOString()
3117
+ })),
3118
+ count: checkpoints2.length
3119
+ });
3120
+ });
3121
+ sessions2.post("/:id/revert/:checkpointId", async (c) => {
3122
+ const sessionId = c.req.param("id");
3123
+ const checkpointId = c.req.param("checkpointId");
3124
+ const session = sessionQueries.getById(sessionId);
3125
+ if (!session) {
3126
+ return c.json({ error: "Session not found" }, 404);
3127
+ }
3128
+ const activeStream = activeStreamQueries.getBySessionId(sessionId);
3129
+ if (activeStream) {
3130
+ return c.json({
3131
+ error: "Cannot revert while a stream is active. Stop the stream first.",
3132
+ streamId: activeStream.streamId
3133
+ }, 409);
3134
+ }
3135
+ const result = await revertToCheckpoint(sessionId, checkpointId);
3136
+ if (!result.success) {
3137
+ return c.json({ error: result.error }, 400);
3138
+ }
3139
+ return c.json({
3140
+ success: true,
3141
+ sessionId,
3142
+ checkpointId,
3143
+ filesRestored: result.filesRestored,
3144
+ filesDeleted: result.filesDeleted,
3145
+ messagesDeleted: result.messagesDeleted,
3146
+ checkpointsDeleted: result.checkpointsDeleted
3147
+ });
3148
+ });
3149
+ sessions2.get("/:id/diff", async (c) => {
3150
+ const id = c.req.param("id");
3151
+ const session = sessionQueries.getById(id);
3152
+ if (!session) {
3153
+ return c.json({ error: "Session not found" }, 404);
3154
+ }
3155
+ const diff = await getSessionDiff(id);
3156
+ return c.json({
3157
+ sessionId: id,
3158
+ files: diff.files.map((f) => ({
3159
+ path: f.path,
3160
+ status: f.status,
3161
+ hasOriginal: f.originalContent !== null,
3162
+ hasCurrent: f.currentContent !== null
3163
+ // Optionally include content (can be large)
3164
+ // originalContent: f.originalContent,
3165
+ // currentContent: f.currentContent,
3166
+ })),
3167
+ summary: {
3168
+ created: diff.files.filter((f) => f.status === "created").length,
3169
+ modified: diff.files.filter((f) => f.status === "modified").length,
3170
+ deleted: diff.files.filter((f) => f.status === "deleted").length,
3171
+ total: diff.files.length
3172
+ }
3173
+ });
3174
+ });
3175
+ sessions2.get("/:id/diff/:filePath", async (c) => {
3176
+ const sessionId = c.req.param("id");
3177
+ const filePath = decodeURIComponent(c.req.param("filePath"));
3178
+ const session = sessionQueries.getById(sessionId);
3179
+ if (!session) {
3180
+ return c.json({ error: "Session not found" }, 404);
3181
+ }
3182
+ const diff = await getSessionDiff(sessionId);
3183
+ const fileDiff = diff.files.find((f) => f.path === filePath);
3184
+ if (!fileDiff) {
3185
+ return c.json({ error: "File not found in diff" }, 404);
3186
+ }
3187
+ return c.json({
3188
+ sessionId,
3189
+ path: fileDiff.path,
3190
+ status: fileDiff.status,
3191
+ originalContent: fileDiff.originalContent,
3192
+ currentContent: fileDiff.currentContent
3193
+ });
3194
+ });
2803
3195
 
2804
3196
  // src/server/routes/agents.ts
2805
- init_db();
2806
3197
  import { Hono as Hono2 } from "hono";
2807
3198
  import { zValidator as zValidator2 } from "@hono/zod-validator";
2808
3199
  import { z as z9 } from "zod";
@@ -3076,8 +3467,9 @@ agents.post(
3076
3467
  if (!session) {
3077
3468
  return c.json({ error: "Session not found" }, 404);
3078
3469
  }
3079
- const { messageQueries: messageQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
3080
- messageQueries2.create(id, { role: "user", content: prompt });
3470
+ const nextSequence = messageQueries.getNextSequence(id);
3471
+ await createCheckpoint(id, session.workingDirectory, nextSequence);
3472
+ messageQueries.create(id, { role: "user", content: prompt });
3081
3473
  const streamId = `stream_${id}_${nanoid4(10)}`;
3082
3474
  activeStreamQueries.create(id, streamId);
3083
3475
  const stream = await streamContext.resumableStream(
@@ -3276,6 +3668,7 @@ agents.post(
3276
3668
  });
3277
3669
  const session = agent.getSession();
3278
3670
  const streamId = `stream_${session.id}_${nanoid4(10)}`;
3671
+ await createCheckpoint(session.id, session.workingDirectory, 0);
3279
3672
  activeStreamQueries.create(session.id, streamId);
3280
3673
  const createQuickStreamProducer = () => {
3281
3674
  const { readable, writable } = new TransformStream();
@@ -3456,10 +3849,14 @@ import { z as z10 } from "zod";
3456
3849
  var health = new Hono3();
3457
3850
  health.get("/", async (c) => {
3458
3851
  const config = getConfig();
3852
+ const apiKeyStatus = getApiKeyStatus();
3853
+ const gatewayKey = apiKeyStatus.find((s) => s.provider === "ai-gateway");
3854
+ const hasApiKey = gatewayKey?.configured ?? false;
3459
3855
  return c.json({
3460
3856
  status: "ok",
3461
3857
  version: "0.1.0",
3462
3858
  uptime: process.uptime(),
3859
+ apiKeyConfigured: hasApiKey,
3463
3860
  config: {
3464
3861
  workingDirectory: config.resolvedWorkingDirectory,
3465
3862
  defaultModel: config.defaultModel,
@@ -3536,7 +3933,6 @@ health.delete("/api-keys/:provider", async (c) => {
3536
3933
  import { Hono as Hono4 } from "hono";
3537
3934
  import { zValidator as zValidator4 } from "@hono/zod-validator";
3538
3935
  import { z as z11 } from "zod";
3539
- init_db();
3540
3936
  var terminals2 = new Hono4();
3541
3937
  var spawnSchema = z11.object({
3542
3938
  command: z11.string(),
@@ -3834,14 +4230,11 @@ data: ${JSON.stringify({ status: "stopped" })}
3834
4230
  );
3835
4231
  });
3836
4232
 
3837
- // src/server/index.ts
3838
- init_db();
3839
-
3840
4233
  // src/utils/dependencies.ts
3841
- import { exec as exec3 } from "child_process";
3842
- import { promisify as promisify3 } from "util";
4234
+ import { exec as exec4 } from "child_process";
4235
+ import { promisify as promisify4 } from "util";
3843
4236
  import { platform as platform2 } from "os";
3844
- var execAsync3 = promisify3(exec3);
4237
+ var execAsync4 = promisify4(exec4);
3845
4238
  function getInstallInstructions() {
3846
4239
  const os2 = platform2();
3847
4240
  if (os2 === "darwin") {
@@ -3874,7 +4267,7 @@ Install tmux:
3874
4267
  }
3875
4268
  async function checkTmux() {
3876
4269
  try {
3877
- const { stdout } = await execAsync3("tmux -V", { timeout: 5e3 });
4270
+ const { stdout } = await execAsync4("tmux -V", { timeout: 5e3 });
3878
4271
  const version = stdout.trim();
3879
4272
  return {
3880
4273
  available: true,
@@ -3921,13 +4314,13 @@ var DEFAULT_WEB_PORT = 6969;
3921
4314
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
3922
4315
  function getWebDirectory() {
3923
4316
  try {
3924
- const currentDir = dirname3(fileURLToPath(import.meta.url));
3925
- const webDir = resolve5(currentDir, "..", "web");
3926
- if (existsSync6(webDir) && existsSync6(join3(webDir, "package.json"))) {
4317
+ const currentDir = dirname4(fileURLToPath(import.meta.url));
4318
+ const webDir = resolve6(currentDir, "..", "web");
4319
+ if (existsSync7(webDir) && existsSync7(join3(webDir, "package.json"))) {
3927
4320
  return webDir;
3928
4321
  }
3929
- const altWebDir = resolve5(currentDir, "..", "..", "web");
3930
- if (existsSync6(altWebDir) && existsSync6(join3(altWebDir, "package.json"))) {
4322
+ const altWebDir = resolve6(currentDir, "..", "..", "web");
4323
+ if (existsSync7(altWebDir) && existsSync7(join3(altWebDir, "package.json"))) {
3931
4324
  return altWebDir;
3932
4325
  }
3933
4326
  return null;
@@ -3950,18 +4343,18 @@ async function isSparkcoderWebRunning(port) {
3950
4343
  }
3951
4344
  }
3952
4345
  function isPortInUse(port) {
3953
- return new Promise((resolve6) => {
4346
+ return new Promise((resolve7) => {
3954
4347
  const server = createNetServer();
3955
4348
  server.once("error", (err) => {
3956
4349
  if (err.code === "EADDRINUSE") {
3957
- resolve6(true);
4350
+ resolve7(true);
3958
4351
  } else {
3959
- resolve6(false);
4352
+ resolve7(false);
3960
4353
  }
3961
4354
  });
3962
4355
  server.once("listening", () => {
3963
4356
  server.close();
3964
- resolve6(false);
4357
+ resolve7(false);
3965
4358
  });
3966
4359
  server.listen(port, "0.0.0.0");
3967
4360
  });
@@ -3995,7 +4388,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false) {
3995
4388
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
3996
4389
  return { process: null, port: actualPort };
3997
4390
  }
3998
- const useNpm = existsSync6(join3(webDir, "package-lock.json"));
4391
+ const useNpm = existsSync7(join3(webDir, "package-lock.json"));
3999
4392
  const command = useNpm ? "npm" : "npx";
4000
4393
  const args = useNpm ? ["run", "dev", "--", "-p", String(actualPort)] : ["next", "dev", "-p", String(actualPort)];
4001
4394
  const child = spawn(command, args, {
@@ -4098,7 +4491,7 @@ async function startServer(options = {}) {
4098
4491
  if (options.workingDirectory) {
4099
4492
  config.resolvedWorkingDirectory = options.workingDirectory;
4100
4493
  }
4101
- if (!existsSync6(config.resolvedWorkingDirectory)) {
4494
+ if (!existsSync7(config.resolvedWorkingDirectory)) {
4102
4495
  mkdirSync2(config.resolvedWorkingDirectory, { recursive: true });
4103
4496
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
4104
4497
  }